Initial source commit
This commit is contained in:
commit
f1384c11ee
335 changed files with 52715 additions and 0 deletions
573
minorGems/graphics/ImageColorConverter.h
Normal file
573
minorGems/graphics/ImageColorConverter.h
Normal file
|
@ -0,0 +1,573 @@
|
|||
/*
|
||||
* 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
|
Loading…
Add table
Add a link
Reference in a new issue