Windows Programming


Using the Windows DIB Color Table

Philip Joslin


Philip Joslin is currently a senior software engineer with Photomatrix Corporation, a developer of high-speed paper and film scanners. He is a former senior engineer with Intel Corporation. He holds a Bachelor of Science in quantitative analysis from BYU and has taken computer science course work at ASU. He also holds an Arizona Community Colleges teaching certificate in Computer Information Systems. He can be reached it (602) 926-2600.

Introduction

In the process of developing a high-speed paper scanner that would be operated within an MS-Windows environment, I found it necessary to develop a generic gray-scale display utility in the form of a Dynamic Link Library (DLL). Among its assorted features, this display utility would provide some fast, minimal image processing/enhancement capabilities. In this article I describe the utility's display process. I also provide information on how to manipulate the system and bitmap palettes to achieve simple image enhancement without extensive processing on the bitmap image data itself (see the sidebar "Simple Image Enhancement").

The Program Structure

To display an image in gray-scale form, the program performs three steps. First, the program creates a logical palette and installs it in place of the system palette to provide a range of gray colors to the display (see the sidebar "Understanding Palettes"). Second, the program transfers the raw image data (which may be in any format, including RGB color) to a Device Independent Bitmap (DIB). Third, the program calls a Windows function to display the DIB.

Defining a Gray-Scale Logical Palette

MS-Windows enables an application to define a logical palette that can replace the system palette. The LOGPALETTE and PALETTEENTRY data types and related functions are used to perform this operation. These data types are structures defined as follows:

 typedef struct
 {
 WORD         palVersion;
 WORD         palNumEntries;
 PALETTEENTRY palPalEntry[];
 } LOGPALETTE;
 
 typedef struct
 {
 BYTE peRed;
 BYTE peGreen;
 BYTE peBlue;
 BYTE peFlags;
 } PALETTEENTRY;
The LOGPALETTE structure defines the new palette. For a display device that supports 256 palette entries, palNumEntries will be 256. palPalEntry should then contain 256 RGB entries, as defined by the PALETTEENTRY structure.

MS-Windows sets aside 20 entries in the system palette as default values. These entries correspond to the 16 standard colors one would find on an EGA/VGA display along with four other color combinations used as system or desktop colors by MS-Windows. When an application substitutes its own, or logical, palette for the system palette, it can request that the 20 default system colors (called static colors) be maintained. In this case, the first 10 static colors will be placed in the first 10 entries of the system palette, and the next 10 static colors placed in the last 10 entries of the system palette. This default arrangement will leave 236 free entries in the system palette.

Listing 1 shows the process for initializing and installing a logical palette. The program first initializes the LOGPALETTE entries, giving the red, green, and blue equal values from 0 through 255 to form a smooth gray-scale palette. It then calls CreatePalette, passing it the LOGPALETTE structure. CreatePalette builds a representation of the palette internal to the Windows Graphics Device Interface (GDI) and returns a handle (of type HPALETTE) to this representation. At this point, the program selects the new palette into the given device context and saves the old palette.

The next call, SetSystemPaletteUse, sets the use of the 20 static colors in the system palette and saves the previous usage. In this instance, I have instructed the GDI to maintain the 20 system colors. This instruction has two major effects: it preserves the standard system colors for window title bars, scroll bars, etc., so that they will not be changed to some unusable value (i.e., black on black or white on white); and it reduces the newly installed, 256-shade gray-scale palette to as few as 236 shades of gray. The latter result does not adversely affect the images displayed, though a couple of the lighter mappings may appear as light tan or peach instead of true gray.

The final calls "realize" the palette. The call to UnrealizeObject instructs the GDI to treat the specified palette as if it had never before been submitted to the GDI. As a result, when RealizePalette is called, the GDI will remap the entire system palette using the new logical palette. At this point all colors other than the 20 static colors will change on the screen. Unless there is a colorful background on the desktop, or unless some other application has previously taken over the system palette, the user may not even notice.

Listing 2 shows the reverse process of reinstalling the old system palette.

Device Independent Bitmaps

A Device-Independent Bitmap is a bitmap represented in a generalized format so that it can be transferred between and displayed upon various output devices. This format differs from a device-dependent format, in that the characteristics of the device-dependent format may vary from display device to display device.

The second step the gray-scale display utility performs is to transfer raw image data to a DIB. Part of this process involves allocating memory for the DIB and filling in various portions of the DIB, including the color table. This section of the article describes the structure of a DIB and how it is created by the program.

A DIB consists of three parts: (1) bitmap header information, (2) the bitmap color table, and (3) the actual bitmap data. The first two of these parts are represented by the BITMAPINFO structure:

 typedef struct
 {
 BITMAPINFOHEADER bmiHeader;
 RGBQUAD          bmiColors[256];
 } BITMAPINFO;
The first entry is another structure, the BITMAPINFOHEADER, which contains detailed information about the size and type of the bitmap:

typedef struct
{
 DWORD   biSize;            /* size of this structure */
 LONG    biWidth;           /* in pixels */
 LONG    biHeight;          /* in lines */
 WORD    biPlanes;
 WORD    biBitCount;
 DWORD   biCompression;
 DWORD   biSizeImage;       /* image size */
 LONG    biXPelsPerMeter;   /* not used */
 LONG    biYPelsPerMeter;   /* not used */
 DWORD   biClrUsed;         /* OL */
 DWORD   biClrImportant;    /* OL */
 } BITMAPINFOHEADER;
My application always sets the biPlanes and biBitCount fields to 1 and 8 respectively. BI_RGB in the biCompression field indicates that the bitmap is not compressed.

The second entry is the color table. The table is initially set up as a 256-entry array of RGBQUAD structures, each of which is defined as:

 typedef struct
 {
 BYTE rgbBlue;
 BYTE rgbGreen;
 BYTE rgbRed;
 BYTE rgbReserved;
 } RGBQUAD;
When calling the actual display routines for device-independent bitmaps, you can specify whether the color table contains RBG values (relative red, green, and blue intensities) or palette indices. Since the color table in this application contains palette indices, which are 16-bit values, I have replaced the BITMAPINFO structure with a new structure:

 typedef struct
 {
 BITMAPINFOHEADER bmiHeader;
 WORD             bmiColors[256];
 WORD             reserved[256];
 } pBITMAPINFO;
The reserved field ensures that the pBITMAPINFO structure is the same size as the BITMAPINFO structure, so that a structure of its type can be safely passed to Windows function GetDIBits (introduced later).

Figure 2 shows how the bitmap color table fits into the color mapping scheme.

Checking the Device

Before doing any palette-oriented operations on a display, it's necessary to confirm that the display is indeed a palette device and a palette device that supports the application's particular requirements. I have defined a general data structure and a general purpose routine to perform this check. The routine repeatedly calls the MS-Windows GetDeviceCaps function to obtain the device information and stores it in the structure.

Listing 3 shows the data structure and the code for retrieving the device information.

Creating the DIB

Creating the DIB and its display is a three-part process in my application. First, I create a temporary device-dependent bitmap using the raw bitmap data. Second, I determine the required size of the DIB, and allocate enough memory to store the DIB. Third, I create the DIB, and fill in the DIB color table. Listing 4 contains fragments of simplified code to create the DIB. Note that in calls to GetDIBits and SetDIBitsToDevice, the usage parameter is set to DIB_PAL_COLORS, indicating that the DIB's color table contains (or will contain) palette indices and not RGB values.

The code first calls CreateBitmap to create a temporary device-dependent bitmap using the raw bitmap data. Next, the program fills in a BITMAPINFO structure and, using the bitmap handle returned from CreateBitmap, makes a preliminary call to GetDIBits. This first call to GetDIBits does not actually build the DIB; it simply returns the size, in bytes, needed to hold the new DIB. The program uses this information to allocate the memory amount specified and then calls GetDIBits again, this time instructing it to create a DIB.

This second call to GetDIBits also affects the color table in the BITMAPINFO structure. Since the call to GetDIBits specifies DIB_PAL_COLORS and my program requires 8 bits per pixel, the function will map what it thinks are the RGB colors in the table to the nearest color in the system palette. That resulting palette index is placed in the color table. (This mapping method may not always produce the results you would expect, depending upon the entries in the color table.)

The program can now treat the color table of the DIB (at least the first 512 bytes) like a color table with 256 palette index entries (it can ignore the remaining 256 bytes). By filling in the appropriate values for these indices, the program can access the newly installed gray-scale palette as a simple, linear, 256-shade gray scale.

Displaying the DIB

As shown in Listing 4, the program now places the new DIB into the display surface, using the SetDIBitsToDevice GDI call, and then "cleans up." It destroys the temporary device-dependent bitmap. At this point, the program could destroy the original, raw bitmap as well: the only structure it needs for further image processing is the DIB.

Conclusion

In this article I have shown how to create a logical palette to produce gray-scale images in MS-Windows. I have also demonstrated how to create a Device Independent Bitmap, and fill it with raw image data. Once you succeed in creating and displaying DIBs, you can expand upon these techniques to perform a variety of image enhancements.