Wednesday, November 4, 2009

12.4 Bitmap Color




< BACK  NEXT >

[oR]

12.4
BITMAP COLOR TRANSFORMATION


There are numerous algorithms that need to transform color for each pixel in the bitmap in a certain way. A color transformation is applied to each pixel independently and without a global context. Sample usage for this group of algorithms includes converting color bitmaps to grayscale, gamma correction, color space conversion, adjusting bitmap hue, lightness, or saturation, etc.



Such bitmap color transformation algorithms have the same pattern. If the bitmap has a color table, each entry in the color table needs to be transformed. Otherwise, we have to scan through each pixel in the pixel array and apply a transformation to each of them. An easy way to implement them would be to use a generic algorithm and a function pointer parameter. Each color transformation is a simple static function. To transform a bitmap with a given color transformation, just call the generic algorithm with the specific color transformation routine a parameter. A similar way is to define an abstract color transformation class, which has a virtual member function that does the color transformation.



Performance is normally considered critical for bitmap-handling algorithms, so calling a function or a virtual function for every pixel in a huge bitmap is normally unacceptable. We certainly do not want to repeat the same code over and over again, nor would we like to use macro-based programming. So the only alternative is a template function.



We would like to define these algorithms on the KDIB class. But the trouble with a template function is that our available C++ compiler does not support a template member function. So we have to use a static template function that accepts a pointer to a KDIB instance. To make things worse, our C++ compiler does not support a friend template function, which forces us to change some private members to be public members.



Listing 12-4 shows a template-based DIB color transformation algorithm.





Listing 12-4 Template for Bitmap Color Transformation Algorithms


template <class Dummy>
bool ColorTransform(KDIB * dib, Dummy map)
{
// OS/2 DIB color table: 1,4,8-bpp, include RLE compression
if ( dib->m_pRGBTRIPLE )
{
for (int i=0; i<dib->m_nClrUsed; i++)
map(dib->m_pRGBTRIPLE[i].rgbtRed,
dib->m_pRGBTRIPLE[i].rgbtGreen,
dib->m_pRGBTRIPLE[i].rgbtBlue);

return true;
}

// Windows DIB color table: 1,2,4,8-bpp, include RLE compression
if ( dib->m_pRGBQUAD )
{
for (int i=0; i<dib->m_nClrUsed; i++)
map(dib->m_pRGBQUAD[i].rgbRed,
dib->m_pRGBQUAD[i].rgbGreen,
dib->m_pRGBQUAD[i].rgbBlue);

return true;
}

for (int y=0; y<dib->m_nHeight; y++)
{
int width = dib->m_nWidth;
unsigned char * pBuffer = (unsigned char *) dib->m_pBits +
dib->m_nBPS * y;

switch ( dib->m_nImageFormat )
{
case DIB_16RGB555: // 15-bit RGB color image, 5-5-5
for (; width>0; width�)
{
BYTE red = ( (* (WORD *) pBuffer) & 0x7C00 ) >> 7;
BYTE green = ( (* (WORD *) pBuffer) & 0x03E0 ) >> 2;
BYTE blue = ( (* (WORD *) pBuffer) & 0x001F ) << 3;

map( red, green, blue );
* ( WORD *) pBuffer = ( ( red >> 3 ) << 10 ) |
( ( green >> 3 ) << 5 ) |
( blue >> 3 );

pBuffer += 2;
}
break;

case DIB_16RGB565: // 16-bit RGB color image, 5-6-5
for (; width>0; width�)
{
BYTE red = ( (* (WORD *) pBuffer) & 0xF800 ) >> 8;
BYTE green = ( (* (WORD *) pBuffer) & 0x07E0 ) >> 3;
BYTE blue = ( (* (WORD *) pBuffer) & 0x001F ) << 3;

map( red, green, blue );

* ( WORD *) pBuffer = ( ( red >> 3 ) << 11 ) |
( ( green >> 2 ) << 5 ) |
( blue >> 3 );

pBuffer += 2;
}
break;

case DIB_24RGB888: // 24-bpp RGB
for (; width>0; width�)
{
map( pBuffer[2], pBuffer[1], pBuffer[0] );
pBuffer += 3;
}
break;

case DIB_32RGBA8888: // 32-bpp RGBA
case DIB_32RGB888: // 32-bpp RGB
for (; width>0; width�)
{
map( pBuffer[2], pBuffer[1], pBuffer[0] );
pBuffer += 4;
}
break;
default:
return false;
}
}

return true;
}

The ColorTransform function accepts two parameters: a pointer to a KDIB instance and a function pointer. It's strange to pass a function pointer a template function without specifying the function prototype. But apparently, this method is supported and used by STL. The first part of the function handles the color table in the OS/2 BMP file format; each of the RGBTRIPLE structures is handled by calling the color transformation function through the map parameters. The color transformation routine accepts three reference parameters for the red, green, and blue channels, and returns the transformed color using the same variable. The code handling Windows color table is very similar, except now the RGBQUAD structure needs to be mapped. This part of the code handles all palette-based DIB formats, including RLE compressed and uncompressed formats.



The remaining code handles 16-bit high color bitmaps, 24-bit true color bitmaps, and 32-bit true color with alpha bitmaps. The logical order of the pixel array is not important here, so the code just goes through each pixel in the order they appear. For 16-bit bitmaps, two common formats are supported. The code needs to extract the RGB channels, convert them to 8 bits each, call the color transformation routine, and pack the results back into a 16-bit WORD. A 24-bit bitmap is very easy to handle. For a 32-bit bitmap, its alpha channel is untouched.



Any other rare DIB formats, like JPEG or PNG compressed bitmaps, or bitmaps with unusual bit fields, are not supported by the currently implemented ColorTransform routine.



Converting Bitmaps to Grayscale


The commonly used formula for converting color in RGB space to grayscale is





For computer implementation, we would like to implement the conversion without any floating-point computation. Based on the ColorTransform template, here is our RGB-bitmap to grayscale-bitmap conversion method.





// 0.299 * red + 0.587 * green + 0.114 * blue
inline void MaptoGray(BYTE & red, BYTE & green, BYTE & blue)
{
red = ( red * 77 + green * 150 + blue * 29 + 128 ) / 256;
green = red;
blue = red;
}

class KImage : public KDIB
{
public:
bool ToGreyScale(void);
...
};

bool KImage::ToGreyScale(void)
{
return ColorTransform(this, MaptoGray);
}


A new class KImage is derived from the KDIB class to encapsulate image-processing algorithms developed in this chapter. The KImage class has no extra member variables. Its only method shown here is ToGreyScale. More methods will be added later in this chapter.



The KImage::ToGreyScale method transforms the current color DIB to a grayscale DIB. It simply calls the ColorTransform template function with the MaptoGray routine as the color transformation routine. MaptoGray uses integer operation to calculate the lightness of a color and assign it to all three RGB channels.



In a debug build, MaptoGray is compiled as a subroutine and its pointer is passed to ColorTransform. In a release build, all calls to MaptoGray are in-lined to get the best performance.




Gamma Correction


Image displaying suffers from photometric distortions caused by the nonlinear response of display devices to lightness. The photometric response of a displaying device is known as the gamma response characteristic. Display monitors for different operating systems use different gammas. When an image prepared on a Macintosh screen is displayed on a PC screen, it normally looks too dark. On the other hand, an image downloaded from a PC server to a Macintosh machine may look too bright. To compensate for these differences, the gamma of the image needs to be corrected.



Gamma correction is normally done in the three RGB channels independently. Three arrays of 256 bytes each are precalculated and passed to either the software gamma transformer or the hardware display card. Each of the three arrays is applied to one of the channels.



Gamma correction can be easily implemented using the ColorTransform template function.





BYTE redGammaRamp[256];
BYTE greenGammaRamp[256];
BYTE blueGammaRamp[256];

inline void MapGamma(BYTE & red, BYTE & green, BYTE & blue)
{
red = redGammaRamp[red];
green = greenGammaRamp[green];
blue = blueGammaRamp[blue];
}

BYTE gamma(double g, int index)
{
return min(255, (int) ( (255.0 * pow(index/255.0, 1.0/g)) + 0.5 ) );
}

bool KImage::GammaCorrect(double redgamma, double greengamma,
double bluegamma)
{
for (int i=0; i<256; i++)
{
redGammaRamp[i] = gamma( redgamma, i);
greenGammaRamp[i] = gamma(greengamma, i);
blueGammaRamp[i] = gamma( bluegamma, i);
}

return ColorTransform(this, MapGamma);
}


The user level routine provided here is KDIB::GammaCorrect. It accepts three independent gamma values, which normally range between 0.2 and 5.0. The routine pre-calculates three gamma ramps for each of the RGB channels, according to the definition of gamma. It then calls ColorTransform with the MapGamma routine as the color transformer. MapGamma does a simple table lookup for each pixel.



A gamma correction with gamma equal to 1.0 is an identity color transformation. Gamma less than one makes a picture look �darker,� and gamma larger than one makes a picture look �lighter.�



If you apply a gamma correction of 2.2 to images prepared on a Macintosh, they will look exactly as they originally appeared on their creator's screen. Figure 12-2 shows a tiger picture with the wrong gamma and its gamma-corrected version.




Figure 12-2. Gamma correction.


The table lookup mechanism implemented by MapGamma can also be used to adjust color according to other criteria. Actually, the only condition is that the RGB channels are independent of each other. For example, you can define a red Gamma Ramp to reduce the red channel by 10%, while leaving the other two as identity transformations.



Win32 GDI supports setting a graphics device's gamma ramp if the hardware and device driver supports downloadable gamma ramps. The related function is SetDeviceGammaRamp, which is part of ICM 2.0. DirectDraw also supports gamma ramps through the IDirectDrawGammaControl interface. Newer PC hardware should all support downloadable gamma ramps.







< BACK  NEXT >





No comments:

Post a Comment