573 lines
14 KiB
C++
573 lines
14 KiB
C++
/*
|
|
* Modification History
|
|
*
|
|
* 2001-February-26 Jason Rohrer
|
|
* Created.
|
|
* Added functions for converting to and from gray byte arrays.
|
|
*
|
|
* 2001-September-19 Jason Rohrer
|
|
* Added an RGB->HSB conversion function, which was copied
|
|
* from minorGems/ai/robotics/ImageStatistics.
|
|
* Added RGB<->YIQ functions.
|
|
*
|
|
* 2001-September-20 Jason Rohrer
|
|
* Fixed a bug in the YIQ conversion.
|
|
* Got rid of this bug fix, as it distorts the YIQ space,
|
|
* and there is no way to prevent colors from going out of the
|
|
* [0,1] range all of the time anyway.
|
|
*
|
|
* 2001-September-24 Jason Rohrer
|
|
* Added RGB<->YCbCr functions.
|
|
* Abstracted out a common coefficient multiplying function.
|
|
*/
|
|
|
|
#ifndef IMAGE_COLOR_CONVERTER_INCLUDED
|
|
#define IMAGE_COLOR_CONVERTER_INCLUDED
|
|
|
|
#include "Image.h"
|
|
|
|
|
|
/**
|
|
* A container class for static functions that convert
|
|
* images between various color spaces.
|
|
*
|
|
* @author Jason Rohrer
|
|
*/
|
|
class ImageColorConverter {
|
|
|
|
public:
|
|
|
|
/**
|
|
* Converts a 3-channel RGB image to a 1-channel grayscale
|
|
* image using an NTSC luminosity standard.
|
|
*
|
|
* @param inImage the RGB image to convert. Must be destroyed
|
|
* by caller.
|
|
*
|
|
* @return a new, grayscale version of inImage. Must be
|
|
* destroyed by the caller. Returns NULL if inImage
|
|
* is not a 3-channel image.
|
|
*/
|
|
static Image *RGBToGrayscale( Image *inImage );
|
|
|
|
/**
|
|
* Converts a 1-channel grayscae image to a 3-channel RGB
|
|
* image.
|
|
*
|
|
* @param inImage the grayscale image to convert. Must be destroyed
|
|
* by caller.
|
|
*
|
|
* @return a new, RGB version of inImage. Must be
|
|
* destroyed by the caller. Returns NULL if inImage
|
|
* is not a 1-channel image.
|
|
*/
|
|
static Image *grayscaleToRGB( Image *inImage );
|
|
|
|
|
|
/**
|
|
* Converts a 1-channel grayscae image to a 1-channel byte array.
|
|
*
|
|
* @param inImage the grayscale image to convert. Must be destroyed
|
|
* by caller.
|
|
* @param inChannelNumber the channel number to use as the
|
|
* gray channel. Defaults to 0;
|
|
*
|
|
* @return a new byte array with one byte per image pixel,
|
|
* and image [0,1] values mapped to [0,255].
|
|
*/
|
|
static unsigned char *grayscaleToByteArray( Image *inImage,
|
|
int inChannelNumber = 0 );
|
|
|
|
/**
|
|
* Converts a byte array to a 1-channel grayscale
|
|
* image.
|
|
*
|
|
* @param inBytes the byte array to convert. Must be destroyed
|
|
* by caller.
|
|
* @param inWidth the width of the image contained in the byte array.
|
|
* @param inHeight the height of the image contained in the byte array.
|
|
*
|
|
* @return a new, grayscale image version of the byte array. Must be
|
|
* destroyed by the caller.
|
|
*/
|
|
static Image *byteArrayToGrayscale( unsigned char *inBytes,
|
|
int inWidth, int inHeight );
|
|
|
|
|
|
|
|
/**
|
|
* Converts an RGB image to HSB.
|
|
*
|
|
* @param inRGBImage the rgb image to convert.
|
|
* Must be destroyed by caller.
|
|
*
|
|
* @return a new image that is the HSB conversion of the
|
|
* RGB image. Must be destroyed by caller.
|
|
*/
|
|
static Image *RGBToHSB( Image *inRGBImage );
|
|
|
|
|
|
|
|
/**
|
|
* Converts an RGB image to YIQ.
|
|
*
|
|
* Note that color values in the resulting YIQ
|
|
* image may lie outside of the range [0,1].
|
|
*
|
|
* @param inRGBImage the rgb image to convert.
|
|
* Must be destroyed by caller.
|
|
*
|
|
* @return a new image that is the YIQ conversion of the
|
|
* RGB image. Must be destroyed by caller.
|
|
*/
|
|
static Image *RGBToYIQ( Image *inRGBImage );
|
|
|
|
|
|
|
|
/**
|
|
* Converts a YIQ image to RGB.
|
|
*
|
|
*
|
|
* Note that color values in the resulting RGB
|
|
* image may lie outside of the range [0,1].
|
|
*
|
|
* @param inYIQImage the rgb image to convert.
|
|
* Must be destroyed by caller.
|
|
*
|
|
* @return a new image that is the RGB conversion of the
|
|
* YIQ image. Must be destroyed by caller.
|
|
*/
|
|
static Image *YIQToRGB( Image *inYIQImage );
|
|
|
|
|
|
|
|
/**
|
|
* Converts an RGB image to YCbCr.
|
|
*
|
|
* Note that in the YCbCr standard, Y is in the range
|
|
* [0,1], while Cb and Cr are both in the range [-0.5, 0.5].
|
|
* This function returns Cb and Cr components shifted
|
|
* into the range [0,1].
|
|
*
|
|
* @param inRGBImage the rgb image to convert.
|
|
* Must be destroyed by caller.
|
|
*
|
|
* @return a new image that is the YCbCr conversion of the
|
|
* RGB image. Must be destroyed by caller.
|
|
*/
|
|
static Image *RGBToYCbCr( Image *inRGBImage );
|
|
|
|
|
|
|
|
/**
|
|
* Converts a YCbCr image to RGB.
|
|
*
|
|
*
|
|
* Note that in the YCbCr standard, Y is in the range
|
|
* [0,1], while Cb and Cr are both in the range [-0.5, 0.5].
|
|
* This function expects input Cb and Cr components to be shifted
|
|
* into the range [0,1].
|
|
*
|
|
* @param inYCbCrImage the rgb image to convert.
|
|
* Must be destroyed by caller.
|
|
*
|
|
* @return a new image that is the RGB conversion of the
|
|
* YCbCr image. Must be destroyed by caller.
|
|
*/
|
|
static Image *YCbCrToRGB( Image *inYCbCrImage );
|
|
|
|
|
|
|
|
protected:
|
|
|
|
/**
|
|
* Converts an 3-channel image to another 3-channel
|
|
* image using a matrix of conversion coefficients.
|
|
*
|
|
* The following formulae are used;
|
|
* outChan0 = inC00 * inChan0 + inC01 * inChan1 + inC02 * inChan2
|
|
* outChan1 = inC10 * inChan0 + inC11 * inChan1 + inC12 * inChan2
|
|
* outChan2 = inC20 * inChan0 + inC21 * inChan1 + inC22 * inChan2
|
|
*
|
|
* @param inImage the image to convert.
|
|
* Must be destroyed by caller.
|
|
*
|
|
* @return a new image that is inImage converted, or NULL if
|
|
* conversion failed (usually because inImage does not
|
|
* contain 3 channels).
|
|
* Must be destroyed by caller.
|
|
*/
|
|
static Image *coefficientConvert( Image *inImage,
|
|
double inC00,
|
|
double inC01,
|
|
double inC02,
|
|
double inC10,
|
|
double inC11,
|
|
double inC12,
|
|
double inC20,
|
|
double inC21,
|
|
double inC22 );
|
|
|
|
};
|
|
|
|
|
|
|
|
inline Image *ImageColorConverter::RGBToGrayscale( Image *inImage ) {
|
|
int w = inImage->getWidth();
|
|
int h = inImage->getHeight();
|
|
int numPixels = w * h;
|
|
|
|
Image *grayImage = new Image( w, h, 1 );
|
|
|
|
double *red = inImage->getChannel( 0 );
|
|
double *green = inImage->getChannel( 1 );
|
|
double *blue = inImage->getChannel( 2 );
|
|
|
|
double *gray = grayImage->getChannel( 0 );
|
|
|
|
for( int i=0; i<numPixels; i++ ) {
|
|
// NTSC luminosity formula
|
|
gray[i] = .299 * red[i] + .587 * green[i] + .114 * blue[i];
|
|
}
|
|
|
|
return grayImage;
|
|
}
|
|
|
|
|
|
|
|
inline Image *ImageColorConverter::grayscaleToRGB( Image *inImage ) {
|
|
int w = inImage->getWidth();
|
|
int h = inImage->getHeight();
|
|
int numPixels = w * h;
|
|
|
|
Image *rgbImage = new Image( w, h, 3 );
|
|
|
|
double *red = rgbImage->getChannel( 0 );
|
|
double *green = rgbImage->getChannel( 1 );
|
|
double *blue = rgbImage->getChannel( 2 );
|
|
|
|
double *gray = inImage->getChannel( 0 );
|
|
|
|
for( int i=0; i<numPixels; i++ ) {
|
|
red[i] = gray[i];
|
|
green[i] = gray[i];
|
|
blue[i] = gray[i];
|
|
}
|
|
|
|
return rgbImage;
|
|
}
|
|
|
|
|
|
inline unsigned char *ImageColorConverter::
|
|
grayscaleToByteArray( Image *inImage, int inChannelNumber ) {
|
|
|
|
int w = inImage->getWidth();
|
|
int h = inImage->getHeight();
|
|
int numPixels = w * h;
|
|
|
|
unsigned char *bytes = new unsigned char[ numPixels ];
|
|
|
|
double *gray = inImage->getChannel( inChannelNumber );
|
|
|
|
for( int i=0; i<numPixels; i++ ) {
|
|
bytes[i] = (unsigned char)( 255 * gray[i] );
|
|
}
|
|
|
|
return bytes;
|
|
}
|
|
|
|
|
|
|
|
inline Image *ImageColorConverter::
|
|
byteArrayToGrayscale( unsigned char *inBytes,
|
|
int inWidth, int inHeight ) {
|
|
|
|
int w = inWidth;
|
|
int h = inHeight;
|
|
int numPixels = w * h;
|
|
|
|
Image *grayImage = new Image( w, h, 1 );
|
|
|
|
|
|
double *gray = grayImage->getChannel( 0 );
|
|
|
|
for( int i=0; i<numPixels; i++ ) {
|
|
gray[i] = (double)( inBytes[i] ) / 255.0;
|
|
}
|
|
|
|
return grayImage;
|
|
}
|
|
|
|
|
|
|
|
inline Image *ImageColorConverter::RGBToHSB( Image *inRGBImage ) {
|
|
// idea modeled after Java Color class.
|
|
|
|
if( inRGBImage->getNumChannels() != 3 ) {
|
|
printf(
|
|
"RGBtoHSB requires a 3-channel image as input.\n" );
|
|
return NULL;
|
|
}
|
|
|
|
int w = inRGBImage->getWidth();
|
|
int h = inRGBImage->getHeight();
|
|
|
|
int numPixels = w * h;
|
|
|
|
Image *hsbImage = new Image( w, h, 3 );
|
|
|
|
double *redChannel = inRGBImage->getChannel( 0 );
|
|
double *greenChannel = inRGBImage->getChannel( 1 );
|
|
double *blueChannel = inRGBImage->getChannel( 2 );
|
|
|
|
double *hueChannel = hsbImage->getChannel( 0 );
|
|
double *satChannel = hsbImage->getChannel( 1 );
|
|
double *brightChannel = hsbImage->getChannel( 2 );
|
|
|
|
|
|
for( int i=0; i<numPixels; i++ ) {
|
|
int r = (int)( 255 * redChannel[i] );
|
|
int g = (int)( 255 * greenChannel[i] );
|
|
int b = (int)( 255 * blueChannel[i] );
|
|
|
|
double hue, sat, bright;
|
|
|
|
int cmax = (r > g) ? r : g;
|
|
if (b > cmax) {
|
|
cmax = b;
|
|
}
|
|
|
|
int cmin = (r < g) ? r : g;
|
|
if (b < cmin) {
|
|
cmin = b;
|
|
}
|
|
|
|
bright = ( (double)cmax ) / 255.0;
|
|
if( cmax != 0 ) {
|
|
sat = ( (double)( cmax - cmin ) ) / ( (double) cmax );
|
|
}
|
|
else {
|
|
sat = 0;
|
|
}
|
|
if( sat == 0 ) {
|
|
hue = 0;
|
|
}
|
|
else {
|
|
double redc =
|
|
( (double)( cmax - r ) ) / ( (double)( cmax - cmin ) );
|
|
double greenc =
|
|
( (double) ( cmax - g ) ) / ( (double)( cmax - cmin ) );
|
|
double bluec =
|
|
( (double)( cmax - b ) ) / ( (double)( cmax - cmin ) );
|
|
|
|
if( r == cmax ) {
|
|
hue = bluec - greenc;
|
|
}
|
|
else if( g == cmax ) {
|
|
hue = 2.0 + redc - bluec;
|
|
}
|
|
else {
|
|
hue = 4.0 + greenc - redc;
|
|
}
|
|
hue = hue / 6.0;
|
|
|
|
if( hue < 0 ) {
|
|
hue = hue + 1.0;
|
|
}
|
|
}
|
|
|
|
hueChannel[i] = hue;
|
|
satChannel[i] = sat;
|
|
brightChannel[i] = bright;
|
|
}
|
|
|
|
return hsbImage;
|
|
}
|
|
|
|
|
|
|
|
inline Image *ImageColorConverter::RGBToYIQ( Image *inRGBImage ) {
|
|
if( inRGBImage->getNumChannels() != 3 ) {
|
|
printf(
|
|
"RGBtoYIQ requires a 3-channel image as input.\n" );
|
|
return NULL;
|
|
}
|
|
|
|
Image *yiqImage = coefficientConvert( inRGBImage,
|
|
0.299, 0.587, 0.114,
|
|
0.596, -0.274, -0.322,
|
|
0.212, -0.523, 0.311 );
|
|
|
|
return yiqImage;
|
|
}
|
|
|
|
|
|
|
|
inline Image *ImageColorConverter::YIQToRGB( Image *inYIQImage ) {
|
|
if( inYIQImage->getNumChannels() != 3 ) {
|
|
printf(
|
|
"YIQtoRGB requires a 3-channel image as input.\n" );
|
|
return NULL;
|
|
}
|
|
|
|
Image *rgbImage = coefficientConvert( inYIQImage,
|
|
1.0, 0.956, 0.621,
|
|
1.0, -0.272, -0.647,
|
|
1.0, -1.105, 1.702 );
|
|
return rgbImage;
|
|
}
|
|
|
|
|
|
|
|
inline Image *ImageColorConverter::RGBToYCbCr( Image *inRGBImage ) {
|
|
if( inRGBImage->getNumChannels() != 3 ) {
|
|
printf(
|
|
"RGBtoYCbCr requires a 3-channel image as input.\n" );
|
|
return NULL;
|
|
}
|
|
|
|
// coefficients taken from the color space faq
|
|
/*
|
|
RGB -> YCbCr (with Rec 601-1 specs)
|
|
Y = 0.2989 * Red + 0.5866 * Green + 0.1145 * Blue
|
|
Cb = -0.1687 * Red - 0.3312 * Green + 0.5000 * Blue
|
|
Cr = 0.5000 * Red - 0.4183 * Green - 0.0816 * Blue
|
|
|
|
YCbCr (with Rec 601-1 specs) -> RGB
|
|
Red = Y + 0.0000 * Cb + 1.4022 * Cr
|
|
Green = Y - 0.3456 * Cb - 0.7145 * Cr
|
|
Blue = Y + 1.7710 * Cb + 0.0000 * Cr
|
|
*/
|
|
|
|
Image *ycbcrImage = coefficientConvert( inRGBImage,
|
|
0.2989, 0.5866, 0.1145,
|
|
-0.1687, -0.3312, 0.5000,
|
|
0.5000, -0.4183, -0.0816 );
|
|
|
|
// adjust the Cb and Cr channels so they are in the range [0,1]
|
|
int numPixels = ycbcrImage->getWidth() * ycbcrImage->getHeight();
|
|
double *cbChannel = ycbcrImage->getChannel( 1 );
|
|
double *crChannel = ycbcrImage->getChannel( 2 );
|
|
for( int i=0; i<numPixels; i++ ) {
|
|
cbChannel[i] += 0.5;
|
|
crChannel[i] += 0.5;
|
|
}
|
|
|
|
// no need to clip pixels to the range [0,1], since
|
|
// all possible rgb pixel values are represented by in-range
|
|
// yCbCr pixel values
|
|
|
|
|
|
return ycbcrImage;
|
|
}
|
|
|
|
|
|
|
|
inline Image *ImageColorConverter::YCbCrToRGB( Image *inYCbCrImage ) {
|
|
if( inYCbCrImage->getNumChannels() != 3 ) {
|
|
printf(
|
|
"YCbCrtoRGB requires a 3-channel image as input.\n" );
|
|
return NULL;
|
|
}
|
|
|
|
// adjust the normalized Cb and Cr channels
|
|
// so they are in the range [-0.5,0.5]
|
|
|
|
int numPixels = inYCbCrImage->getWidth() * inYCbCrImage->getHeight();
|
|
double *cbChannel = inYCbCrImage->getChannel( 1 );
|
|
double *crChannel = inYCbCrImage->getChannel( 2 );
|
|
for( int i=0; i<numPixels; i++ ) {
|
|
cbChannel[i] -= 0.5;
|
|
crChannel[i] -= 0.5;
|
|
}
|
|
|
|
// coefficients taken from the color space faq
|
|
/*
|
|
RGB -> YCbCr (with Rec 601-1 specs)
|
|
Y = 0.2989 * Red + 0.5866 * Green + 0.1145 * Blue
|
|
Cb = -0.1687 * Red - 0.3312 * Green + 0.5000 * Blue
|
|
Cr = 0.5000 * Red - 0.4183 * Green - 0.0816 * Blue
|
|
|
|
YCbCr (with Rec 601-1 specs) -> RGB
|
|
Red = Y + 0.0000 * Cb + 1.4022 * Cr
|
|
Green = Y - 0.3456 * Cb - 0.7145 * Cr
|
|
Blue = Y + 1.7710 * Cb + 0.0000 * Cr
|
|
*/
|
|
|
|
Image *rgbImage = coefficientConvert( inYCbCrImage,
|
|
1.0, 0.0000, 1.4022,
|
|
1.0, -0.3456, -0.7145,
|
|
1.0, 1.7710, 0.0000 );
|
|
|
|
// clip r, g, and b channels to the range [0,1], since
|
|
// some YCbCr pixel values might map out of this range
|
|
// (in other words, some YCbCr values map outside of rgb space)
|
|
for( int c=0; c<3; c++ ) {
|
|
double *channel = rgbImage->getChannel( c );
|
|
|
|
for( int p=0; p<numPixels; p++ ) {
|
|
if( channel[p] < 0 ) {
|
|
channel[p] = 0;
|
|
}
|
|
else if( channel[p] > 1 ) {
|
|
channel[p] = 1.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
return rgbImage;
|
|
}
|
|
|
|
|
|
|
|
inline Image *ImageColorConverter::coefficientConvert( Image *inImage,
|
|
double inC00,
|
|
double inC01,
|
|
double inC02,
|
|
double inC10,
|
|
double inC11,
|
|
double inC12,
|
|
double inC20,
|
|
double inC21,
|
|
double inC22 ) {
|
|
if( inImage->getNumChannels() != 3 ) {
|
|
return NULL;
|
|
}
|
|
|
|
|
|
int w = inImage->getWidth();
|
|
int h = inImage->getHeight();
|
|
|
|
int numPixels = w * h;
|
|
|
|
Image *outImage = new Image( w, h, 3 );
|
|
|
|
double *outChannel0 = outImage->getChannel( 0 );
|
|
double *outChannel1 = outImage->getChannel( 1 );
|
|
double *outChannel2 = outImage->getChannel( 2 );
|
|
|
|
double *inChannel0 = inImage->getChannel( 0 );
|
|
double *inChannel1 = inImage->getChannel( 1 );
|
|
double *inChannel2 = inImage->getChannel( 2 );
|
|
|
|
for( int i=0; i<numPixels; i++ ) {
|
|
outChannel0[i] =
|
|
inC00 * inChannel0[i] +
|
|
inC01 * inChannel1[i] +
|
|
inC02 * inChannel2[i];
|
|
outChannel1[i] =
|
|
inC10 * inChannel0[i] +
|
|
inC11 * inChannel1[i] +
|
|
inC12 * inChannel2[i];
|
|
outChannel2[i] =
|
|
inC20 * inChannel0[i] +
|
|
inC21 * inChannel1[i] +
|
|
inC22 * inChannel2[i];
|
|
}
|
|
|
|
return outImage;
|
|
}
|
|
|
|
|
|
|
|
#endif
|