Ron Gery
Microsoft Developer Network Technology Group
Created: March 20, 1992
Click to view or copy the sample application files for this technical article.
This article discusses using palettes in conjunction with DIBs (device-independent bitmaps). It does not delve into involved uses of the Microsoft® Windows™ Palette Manager.
A DIB (device-independent bitmap) displays only as well as the target device can present its color table. Images with respectable color quality can be generated using 256 colors, but how well those colors display depends on how many colors the device can display and, hence, the color resolution and hardware configuration of the device driver. When a DIB is displayed, each color in the color table is mapped to a specific color on the device. If the device cannot display the exact color, it is mapped to the nearest available color, which degrades the image.
The number of colors that a device can display ranges from almost none (monochrome devices) to virtually limitless (24-bit devices). About midway in this range is a class of output devices that can support full-color resolution (or near full-color resolution) but that can display concurrently only a limited number of those colors. The most common example (and the one to which the Microsoft® Windows™ Palette Manager is targeted) is the super VGA-type display, an 8-bit device that represents each pixel with an 8-bit index into a 256-entry color lookup table (CLUT), also known as the hardware, or physical, palette. This class of devices (commonly known as palette devices) can display exact colors but only a limited number at any one time. Using palettes, an application can choose the values of those colors. The 256 colors of an 8-bit DIB can be put into the CLUT, and thus the image displays at full-color resolution.
The application can use logical palettes to choose the colors for the hardware palette. Although palettes were designed specifically for palette devices, all techniques discussed in this article are device independent. Using palettes to display DIBs on any type of device results in the best representation of the DIB that can be achieved on that device. The use of logical palettes is not limited to palette devices.
How a device interprets a DIB's color table depends on the device. The two variations considered here are palette devices and nonpalette devices. Call GetDeviceCaps with the nIndex parameter set to RASTERCAPS, and check the return value for the RC_PALETTE bit to determine the type of the device. Nonpalette devices see only RGB values. Palette devices see only indexes into the hardware palette, which the graphics device interface (GDI) calculates. An application passes the color table to GDI in two ways, and GDI is responsible for getting the color table in a format that the device expects.
The most common use of the DIB functions involves the application passing in a DIB with an RGB color table. This case is indicated when the wUsage parameter is set to DIB_RGB_COLORS. On a nonpalette device, this color table is passed to the device as is. On a palette device, the color table must be converted to indexes. To do this, each color in the table is compared with the colors in the current palette to find the closest match. The matching is done using a nearest Euclidean distance algorithm (search for smallest deltaR^2 + deltaG^2 + deltaB^2). Instead of the actual RGB value, the index associated with this chosen color in the palette is passed to the driver.
When the DIB's color table is actually a table of palette indexes (the application sets wUsage to DIB_PAL_COLORS), things happen in reverse. On a nonpalette device, each index in the table is dereferenced in the current palette and replaced by the actual RGB value found in the palette. The replacing is done on a private copy GDI builds up. On a palette device, color matching is unnecessary because the index already identifies the proper palette entry.
DIB_PAL_COLORS and its indexed table are used primarily for speed on palette devices. The color matching process is inherently slow, and if an application can prematch colors, the DIB processing in GDI can be sped up significantly. On the negative side, the DIB becomes attached to a palette and loses its device independence. A DIB with an indexed color table cannot be placed in the Clipboard because it can't be viewed without a palette to decode the indexes.
Using the DIB with the color table intact maintains device independence, but on palette devices the conversion to an internal color scheme is slow.
A simple solution is to maintain both versions. Because the headers do not have to be contiguous to the bits, two headers can be maintained for the DIB. The color table is used for placing in the Clipboard, writing to a file, and so on, and the index table is used for actually displaying the DIB or converting it to a device-dependent bitmap.
Because 24-bit DIBs do not have a color table that GDI uses, the wUsage parameter is not meaningful. If a representative palette is selected to go with the 24-bit DIB, reasonable color accuracy can be achieved on palette devices.
The Palette Manager's primary task is to control how colors in a logical palette are mapped into the device's hardware palette. The basic problem is that the number of slots in the hardware palette is limited, and the number of colors that applications want to put in that palette is not so limited. The Palette Manager chooses which colors are put into the hardware palette and maps the remaining color requests to the colors currently available.
The application that gets priority in color selection is the one with the current focus and is known as the foreground application. All other applications are background applications, and the Z-order determines their color priority. An application requests a color with the RealizePalette function after the palette is selected into the device context (DC) with SelectPalette. The foreground application gets first shot at the hardware palette. Its color requests are filled exactly (its colors are placed in the hardware palette) until the hardware palette is filled. Colors that do not fit (it is a first-come-first-served process) are mapped to the nearest color now available on the hardware. When a background application realizes a palette, the openings are set as requested until the table is filled if the hardware palette has any openings. Colors requested after the hardware palette is filled are mapped to the nearest available color. No background color can replace a foreground color or a previous background color. Thus, the foreground application can display an image with high color accuracy; background applications use the chosen colors as best they can.
GDI reserves entries in the hardware palette for system use. These colors, 20 on a standard 8-bit palette device, are used by any application that does not explicitly select a palette and by the system itself to draw window borders, buttons, dialogs, menus, and so on. These colors are known as the static colors and consist of the 16 colors of the Windows VGA driver plus 4 colors that are used to beautify the desktop color schemes. Static colors reduce the number of free slots in the hardware palette (to 236 on an 8-bit device) but allow VGA-like support for nonpalette applications. Applications that do not explicitly use palettes are given a default palette that provides color resolution roughly equivalent to that of a 16-color VGA driver.
If a color table is associated with a DIB, creating a corresponding palette is easy because the color table is basically already a palette. The code sample below is paraphrased from the MakeDIBPalette function in the DIBIT sample application.
// lpInfo - pointer to the DIB header; lpInfo->biClrUsed assumed
// nonzero and correct
// lpRGB - pointer (RGBQUAD FAR*) to color table of the DIB
// pPal - pointer to LOGPALETTE structure with room for
// lpInfo->biClrUsed entries
pPal->palVersion = 0x300;
pPal->palNumEntries = lpInfo->biClrUsed;
for (i = 0; i < lpInfo->biClrUsed; i++, lpRGB++)
{
pPal->palEntry[i].peRed = lpRGB->rgbRed;
pPal->palEntry[i].peGreen = lpRGB->rgbGreen;
pPal->palEntry[i].peBlue = lpRGB->rgbBlue;
pPal->palEntry[i].peFlags = 0;
}
hLogPal = CreatePalette((LPLOGPALETTE)pPal);
Each portion of a color entry is copied over separately because the RGBQUAD and PALETTEENTRY structures are incompatible.
This article does not deal with the intricacies of the Palette Manager, but displaying a DIB using a palette does not have to be complicated. Simply insert the following two lines before the actual DIB painting occurs:
SelectPalette(hDC, hLogPal, FALSE);
RealizePalette(hDC);
The hDC parameter above is the target DC for the subsequent DIB operation. Setting the third parameter of SelectPalette to FALSE ensures that the selected palette receives the best possible color matching. The RealizePalette function actually maps the logical palette to the device's hardware palette.
When the above code executes on a nonpalette device, it has no effect other than to associate the logical palette with the target DC. For device-independent applications, the palette should be selected and realized regardless of the color capabilities of the device.
On palette devices, other applications may cause the hardware palette to change. A displayed image can change in appearance because its pixels now reference a changed hardware palette on the device. This event is signaled by a broadcast of the WM_PALETTECHANGED message to every window in the system. To remap the image's colors to the new hardware palette, repaint the image. The painting code itself does not need to change—calling RealizePalette under these conditions remaps the logical palette to the current hardware palette.
Because any application realizing the logical palette can change the hardware palette, an application handling the WM_PALETTECHANGED message should not respond when the message is caused by the application's own palette's realization. (The wParam parameter identifies the "owner" window of the message.)
When a DIB is converted into a device-dependent bitmap on a palette device, that bitmap is bound to the palette. The bitmap is stored with palette-based information in it, and if it is used without the same palette selected into the DC, it may not display properly. Applications placing palette-based bitmaps in the Clipboard should also place the associated palette there (using the CF_PALETTE format). Bitmaps created without explicitly using a palette do not need an associated palette for display.
An application calling GetDIBits receives black and white as the colors in the color table for 1-bit DIBs, the 16 VGA colors for 4-bit DIBs, and no color table for 24-bit DIBs. If DIB_PAL_COLORS is specified, the colors in the table are mapped to the nearest color in the palette, and that index is placed in the table. In the 8-bit case, the colors of the current palette find their way into the color table. The static colors are placed in the color table regardless of the current palette, the first 10 starting at index 0 and the second 10 ending at index 255. (More precisely, the number of static colors is not necessarily 20—it is given by the NUMRESERVED index of GetDeviceCaps.) If the application selected a palette, the colors from that palette are placed in the remaining slots. Any colors in the logical palette that cannot fit into the color table are lost. (Note, though, that the colors were essentially lost when the palette was realized because the hardware palette also had insufficient room for them.) If DIB_PAL_COLORS was requested, the corresponding palette indexes are substituted for the palette colors in the table, and the static colors are mapped to the nearest logical palette entries to get corresponding indexes.