Created: March 20, 1992
ABSTRACT
This article describes some techniques that can help improve your application’s video performance:
Using an identity palette to speed the drawing of images
Dealing with differences in video adapters
Modifying device-independent bitmaps (DIBs) using the DIB driver
Using the DisplayDib application programming interface (API) to display 256-color images on a standard VGA adapter
The MicrosoftÒ WindowsÔ graphical environment reserves a group of system palette entries for the system colors, a fixed number of colors used for drawing screen elements such as scroll bars. Windows also uses the system colors as replacement color entries when inactive windows request more color entries than are available in the system palette. Windows places the system colors at the top and the bottom of the system palette to ensure that logical operations (such as XOR) work correctly.
By arranging your logical palettes the way that Windows arranges the system palette, you can avoid unexpected color changes and improve the speed at which your application draws device-independent bitmaps (DIBs). To do this, you create a special palette called an identity palette. An identity palette exactly matches the system palette. To use identity palettes, you need to understand how Windows sets up the system palette.
When an application realizes a palette, Windows adds the logical palette entries to the system palette. Windows always reserves system palette entries for the system colors. On a 256-color VGA driver, which uses 20 system colors, your application can use a maximum of 236 system palette entries. If your logical palette contains more entries than will fit in the system palette (after the system colors are added), Windows truncates the palette, using only as many colors as it can fit without encroaching on the reserved system colors. You can force Windows to relinquish the system color entries (by using the SetSystemPaletteUse function), but you’ll change the coloring of all Windows screen elements to black and white.
Fact 1: The maximum number of colors available to a foreground window equals the number of colors supported by the video device driver, minus the number of system reserved colors, minus the number of application-reserved palette entries.
Windows places the system colors at the top and the bottom of the system palette. For example, with a 256-color VGA driver, the top 10 and the bottom 10 system palette entries contain the system colors. If a logical palette does not contain the system colors or if the system colors appear in locations other than those Windows expects, Windows changes the ordering of the palette entries when you realize your palette. At this point, logical palette entry n does not necessarily match system palette entry n. When your application draws a bitmap to the device context (DC), Windows must translate the bitmap palette indexes to the new locations on the system palette, which takes time.
Fact 2: Windows rearranges the palette entries if the system colors do not appear as the top and the bottom entries of the logical palette. If the palette is rearranged, the video driver must translate the color indexes in the bitmap to the new color indexes of the system palette.
The goal is to arrange the logical palette so that Windows can use the same ordering of palette entries in the system palette. Your images can be colored exactly as you expect, and the video driver can draw the images faster because it avoids the translation step.
An identity palette has the following attributes:
It has the same number of entries as the system palette.
It includes color entries for the system colors.
The system colors appear at the top and the bottom of the color table.
Creating an identity palette for your DIBs has the following advantages:
You avoid unexpected color changes caused by the truncation of palette entries.
The video driver can draw the bitmap without translating the palette indexes.
The Microsoft Windows PaintbrushÔ program included with the Windows graphical environment always saves bitmaps with an identity palette. To convert a bitmap palette to an identity palette, you can open the bitmap in Paintbrush and then save it.
The PalEdit tool included with the Multimedia Development Kit (MDK) offers you control over the conversion. PalEdit includes an option to create an identity palette. Before you choose this option, trim your palette entries to make room for the system colors (otherwise, PalEdit does it for you). PalEdit includes commands to locate and consolidate similar palette colors.
Note:
The RGB values of the low-intensity system colors can differ across display devices (for example, between super-VGA and 8514 devices). See the next section for information on adapting your logical palette to a different display type.
The multimedia extensions video drivers include 80286-specific and 80386-specific code that improves the speed of DIB drawing routines significantly. They also include the useful NEWTRANSPARENT transparent mode. Your application can’t assume, however, that these enhanced drivers will be available on your delivery machines.
To determine whether the device driver supports the NEWTRANSPARENT mode, use the following code fragment (the CAPS1 and C1_TRANSPARENT constants are defined in MMSYSTEM.H):
if(GetDeviceCaps(hDC, CAPS1) & C1_TRANSPARENT)
// NEWTRANSPARENT is available
So, your device driver doesn’t support NEWTRANSPARENT? No optimized processor-specific code either? Call your video adapter manufacturer and ask when they plan to implement multimedia display drivers for their display adapter. You might also add “multimedia-enhanced display driver” to your list of system software requirements.
Most super-VGA adapters are single-plane devices, which makes them well suited for displaying DIBs. On a super-VGA adapter, the speed at which you draw DIBs and device-dependent bitmaps is about the same; you can choose whichever format is more convenient for your application.
Standard VGA adapters have multiple planes and are not as well suited for displaying DIBs; working with device-dependent bitmaps is thus faster. To determine whether a standard VGA adapter is present, determine whether the display device is a multiplane device. You can use a routine such as the following:
hDC = CreateDC("DISPLAY", NULL, NULL, NULL);
bIsMultiplane = (GetDeviceCaps(hDC, PLANES) > 1);
DeleteDC(hDC);
For more information on the architecture and features of VGA adapters, refer to Programmer’s Guide to PC & PS/2 Video Systems (see “Additional Reading” at the end of this article).
Even if two display devices use the same number of system colors, you can’t assume that the RGB values for the low-intensity colors will match. One particular problem is super-VGA versus 8514 devices: Both provide 256 colors, and both use 20 system colors, but the low-intensity system color values for the VGA are different from those for the 8514. An identity palette created on a VGA system is not an identity palette on an 8514 system.
If you create an identity palette on a VGA system and then display the DIB on an 8514 system, Windows recognizes the low-intensity colors in the logical palette as custom colors rather than as system colors. It puts these colors in the custom-color section of the palette (within entries 10 through 245) and writes the 8514 system colors to the top and the bottom of the system palette.
To avoid this problem, an application can do the following:
When the application loads, it can use GetSystemColors to retrieve the system colors from the system palette and compare these colors with the system colors in the DIB palettes.
If the colors don’t match, the application can copy the current system colors (retrieved from the system palette) over the DIB system colors.
You can use the following function to determine whether a logical palette is an identity palette:
BOOL IsIdentityPalette(NPLOGPALETTE npLogPal)
{
PALETTEENTRY pe;
int i, iPalEntry;
HDC hdc;
int nNumPalColors, nNumSysColors;
BOOL bIsIdentity = TRUE;
hdc = CreateDC("DISPLAY", NULL, NULL, NULL);
nNumPalColors = GetDeviceCaps(hdc, SIZEPALETTE);
nNumSysColors = GetDeviceCaps(hdc, NUMRESERVED);
for(i = 0; i < (nNumSysColors / 2); i++)
{
GetSystemPaletteEntries(hdc, i, 1, &pe);
if(pe.peRed != npLogPal->palPalEntry[i].peRed ||
pe.peBlue != npLogPal->palPalEntry[i].peBlue ||
pe.peGreen != npLogPal->palPalEntry[i].peGreen)
{
bIsIdentity = FALSE;
goto ExitIsIdentityPalette;
}
}
for(i = 0; i < (nNumSysColors / 2); i++)
{
iPalEntry = i+(nNumPalColors - (nNumSysColors / 2));
GetSystemPaletteEntries(hdc, iPalEntry, 1, &pe);
if(pe.peRed != npLogPal->palPalEntry[iPalEntry].peRed ||
pe.peBlue != npLogPal->palPalEntry[iPalEntry].peBlue ||
pe.peGreen != npLogPal->palPalEntry[iPalEntry].peGreen)
{
bIsIdentity = FALSE;
goto ExitIsIdentityPalette;
}
}
ExitIsIdentityPalette:
DeleteDC(hdc);
return bIsIdentity;
}
If this function returns FALSE, copy the current system colors into the logical palette.
To copy the current system colors into an identity palette, use a function such as the following:
int GetSysPal(int nDibSysColors, NPLOGPALETTE pPal)
{
HDC hdc = CreateDC("DISPLAY", NULL, NULL, NULL);
int nNumPalColors, nNumSysColors;
nNumPalColors = GetDeviceCaps(hdc, SIZEPALETTE);
nNumSysColors = GetDeviceCaps(hdc, NUMRESERVED);
if(nNumPalColors != pPal->palNumEntries)
return ERR_NUMPALCOLORS;
if(nNumSysColors != nDibSysColors)
return ERR_NUMSYSCOLORS;
GetSystemPaletteEntries(hdc,
0, nNumSysColors / 2,
&(pPal->palPalEntry[0]));
GetSystemPaletteEntries(hdc,
nNumPalColors - nNumSysColors / 2, nNumSysColors / 2,
&(pPal->palPalEntry[pPal->palNumEntries -
nNumSysColors / 2]));
DeleteDC(hdc);
return 0;
}
DIBs offer many advantages over device-dependent bitmaps. Unlike device-dependent bitmaps, however, DIBs cannot be selected into a video DC. As a result, before the multimedia extensions were available, applications could not take advantage of the extensive graphics device interface (GDI) to modify DIBs directly. To use GDI routines to draw on or otherwise modify a DIB, an application would follow this procedure:
1.Create a memory DC.
2.Use CreateDIBitmap to convert the DIB to device-dependent format.
3.Select the device-dependent bitmap into the memory DC.
4.Call GDI routines to modify the device-dependent bitmap.
5.Use GetDIBits to convert the device-dependent bitmap to DIB format.
This procedure works well if you use only GDI routines to modify the bitmap. If you write replacement functions that directly modify the DIB bits, however, your direct-manipulation routines work on the DIB, and the GDI routines work on the device-dependent bitmap.
Direct manipulation can be considerably faster than using equivalent GDI routines; in one sample application, a direct-manipulation function (drawing a triangle) ran eight times faster than the equivalent GDI operation. Also, you might already have a library of direct-manipulation routines that you used with previous products. Fortunately, the multimedia extensions include the DIB driver, a driver that lets you mix GDI functions with direct-manipulation routines.
The DIB driver (DIB.DRV) included with the multimedia extensions allows you to create a DIB DC. To create the DIB DC, call CreateDC, supplying a pointer to the DIB BITMAPINFO data structure:
hdc = CreateDC("DIB", NULL, NULL, (LPSTR)lpbi);
With the HDC returned from CreateDC, you can call GDI routines to modify the bitmap. Concurrently, you can call your own direct-manipulation functions to modify the actual bitmap bits. Any direct changes made to the bitmap bits are reflected in the DIB driver DC. When you finish modifying the bitmap, you can use StretchDIBits to transfer the DIB to the video DC. (You can also use SetDIBitsToDevice; however, you may find StretchDIBits easier to use.)
The DIB driver can handle 1-bit, 4-bit, or 8-bit DIBs. You can create multiple DIB driver DCs. Note the following limitations:
You must keep the BITMAPINFO structure locked for the life of the DC.
The DIB driver handles only the Windows BITMAPINFOHEADER format. It does not support the BITMAPCOREHEADER format.
The RLE format is not supported. To decompress an RLE DIB, convert it to a device-dependent bitmap and then convert it back into DIB format.
The DIB must use the DIB_RGB_COLORS format. The DIB driver does not support the DIB_PAL_COLORS (palette indexes) format.
The DIB driver is part of the multimedia extensions run-time components, so you can distribute it with your applications that run under Windows as well as under Windows with multimedia.
The following code fragment is taken from the CropDIB sample application included with the MDK. This code copies an image region from a source DIB (lpbiOld) to a destination DIB (lpbiNew). It copies the header and palette information to the destination DIB, creates a DIB DC for the destination DIB, selects the DIB palette into the DIB DC, and calls StretchDIBits to copy the specified region to the destination DC. (The helper functions are located in the source module DIB.C.)
// Create a DIB to hold the cropped portion. The
// CreateDib function copies the DIB header and palette
// to the new DIB and allocates space to hold the bits.
//
hdibNew = CreateDib(hdibOld, wCrop, hCrop);
if(hdibNew == NULL)
{
Error(NULL, IDS_OUTOFMEMORY);
goto CropDIB_EXIT;
}
lpbiOld = (LPBITMAPINFOHEADER)GlobalLock(hdibOld);
lpbiNew = (LPBITMAPINFOHEADER)GlobalLock(hdibNew);
// Get a DC onto the destination DIB.
//
hdcNew = CreateDC("DIB", NULL, NULL, (LPSTR)lpbiNew);
if(hdcNew == NULL)
{
Error(NULL, IDS_CANTLOADDIBDRV);
goto CropDIB_EXIT;
}
// Copy from old DIB to new DIB. Get the BITMAPINFOHEADER
// values and calculate the y-origin of the crop area. (The
// origin is at the lower-left corner.)
//
DibInfo(hdibOld, &bihOld);
yCrop = (int)bihOld.biHeight - (yCrop + hCrop);
pBitsOld = (LPSTR)lpbiOld + (WORD)lpbiOld->biSize +
PaletteSize(lpbiOld);
StretchDIBits( hdcNew, // destination DC
0, 0, wCrop, hCrop, // destination rect
xCrop, yCrop, wCrop, hCrop, // source rect
pBitsOld, // bitmap bits
(LPBITMAPINFO)lpbiOld, // bitmap header
DIB_RGB_COLORS, SRCCOPY);
CropDIB_EXIT:
if(hdcNew != NULL)
DeleteDC(hdcNew);
GlobalUnlock(hdibOld);
GlobalUnlock(hdibNew);
The multimedia extensions include a dynamic link library (DLL) called DISPDIB.DLL. This DLL allows you to display 256-color DIBs on a standard VGA monitor, using 320-by-240 or 320-by-200 pixel resolution. DISPDIB.DLL is part of the multimedia extensions run-time components, so you can distribute it with your applications that run under Windows as well as under Windows with multimedia.
DISPDIB supports a single API, DisplayDib, that switches the screen to low-resolution mode and displays the bitmap. Because Windows doesn’t support switching of video modes, DISPDIB bypasses the Windows video driver to display the bitmap. The video driver is disabled while DISPDIB has control of the screen; during this time, you can’t do anything with video DCs. When DISPDIB is finished displaying the bitmap, it resets the screen to the previous video mode, enables the Windows video driver, and invalidates all windows on the screen.
You can’t use GDI to modify the screen, but you can use GDI with the DIB driver to modify the bitmaps that you display using DisplayDib. DISPDIB works quickly, so you can get good results by calling DisplayDib to display the bitmap, modifying the bitmap using the DIB driver DC, and then calling DisplayDib again to display the modified bitmap.
Because DISPDIB essentially steals the display from Windows, your application must be the active application when you call the DisplayDib function; otherwise, DisplayDib returns with an error flag. By default, DisplayDib does not return control to your application until the user presses a key or clicks a mouse button. You can override this behavior using the DISPLAYDIB_NOWAIT flag or the DISPLAYDIB_BEGIN and DISPLAYDIB_END flags.
If you use the DISPLAYDIB_BEGIN and DISPLAYDIB_END flags to mark the beginning and end of the slide show, DISPDIB immediately returns control to your application. Although the video driver is disabled, all other components of Windows are active. Your application continues to run and receive messages, including timer, keyboard, and mouse messages (although the mouse pointer is not visible).
This example presents a simple slide show application. The application displays a series of DIBs using the DisplayDib API. The application also uses the DIB driver to draw a white border around each DIB before displaying it. The slide changes are timed using a Windows timer. The application monitors WM_KEYDOWN messages for the ESC key (ends the slide show), the SPACEBAR (pauses or restarts the show), and the F1 key (blocks out the current bitmap). For the “end” and “pause” features, the application sets flags and performs the operation when the next WM_TIMER message is received. We’ll look at the “block-out” function later.
This application uses some helper routines from the DIB.C file. The latest version of DIB.C is included with the CropDIB sample application in the MDK. You can also use the DIB.C module of the ShowDIB sample application included with the Windows SDK.
Most of the work gets done in the window function. The slide show starts when the user picks the IDM_SHOW menu option; here’s the WM_COMMAND handler of the window function:
case WM_COMMAND:
switch(wParam)
{
case IDM_SHOW:
i = -1; // reset the slide counter
SetTimer(hWnd, 1, 1000, NULL);
DisplayDib(NULL, NULL,
DISPLAYDIB_BEGIN | DISPLAYDIB_MODE_320x240x8);
bShowsOver = bHoldPicture = FALSE;
break;
case IDM_EXIT:
if(!bShowsOver)
{
bShowsOver = TRUE;
SendMessage(hWnd, WM_TIMER, 0, 0L);
}
PostQuitMessage(1);
break;
default:
break;
}
break;
The first call to DisplayDib resets the video mode and notifies DisplayDib that the application has started a series of DisplayDib calls. When DisplayDib is used in a series, the DISPLAYDIB_MODE flags are valid only with the first call (the one called with DISPLAYDIB_BEGIN). Until DISPLAYDIB_END is specified, DISPDIB keeps the screen in the video mode specified with the first DisplayDib call.
The slide advances are handled when the WM_TIMER messages are received. Here’s the WM_TIMER handler:
case WM_TIMER:
{
HDChdcDib; // DIB driver DC
HANDLE hDib; // DIB to display
LPBITMAPINFOHEADER lpbi; // pointer to DIB header
HPEN hpenOld; // save old pen and brush
HBRUSH hbrOld;
// The bShowsOver flag is TRUE if the user pressed ESC to cancel
// the slide show. To end the show, kill the timer and call
// DisplayDib with DISPLAYDIB_END.
//
if(bShowsOver)
{
KillTimer(hWnd, 1);
DisplayDib(NULL, NULL, DISPLAYDIB_END | DISPLAYDIB_NOWAIT);
break;
}
// bHoldPicture is TRUE if the user pressed SPACEBAR to pause
// the slide show. Return without advancing the show.
//
if(bHoldPicture)
break;
if(alpszDibs[++i] == NULL)
i = 0;
// Open the DIB. OpenDIB is located in DIB.C.
//
if((hDib = OpenDIB(alpszDibs[i])) == NULL)
break;
// Create a DIB driver DC, and draw the border.
//
lpbi = (LPBITMAPINFOHEADER)GlobalLock(hDib);
hdcDib = CreateDC("DIB", NULL, NULL, (LPSTR)lpbi);
hpenOld = SelectObject(hdcDib, GetStockObject(WHITE_PEN));
hbrOld = SelectObject(hdcDib, GetStockObject(NULL_BRUSH));
Rectangle(hdcDib, 0, 0, (int)lpbi->biWidth, (int)lpbi->biHeight);
// Display the DIB. If the images have the same palette,
// you could specify the DISPLAYDIB_NOPALETTE flag to reduce
// screen flicker.
//
DisplayDib(lpbi, NULL, NULL );
SelectObject(hdcDib, hpenOld);
SelectObject(hdcDib, hbrOld);
DeleteDC(hdcDib);
GlobalUnlock(hDib);
GlobalFree(hDib);
break;
}
You can do some limited animation using DisplayDib and the DIB driver. The following function uses the DisplayDib DISPLAYDIB_NOPALETTE flag to repeatedly draw the same bitmap without realizing the palette each time. It modifies the bitmap by drawing on the DIB driver DC and then draws the modified picture using DisplayDib. DISPDIB works fast enough to provide reasonably smooth movement (even though it draws the entire bitmap to the screen with each update).
void BlockOutPicture(LPSTR lpszDib)
{
HDC hdcDib;
HANDLE hDib;
LPBITMAPINFOHEADER lpbi;
HPEN hpenOld;
HBRUSH hbrOld;
int xBlock, yBlock, x, y;
if(lpszDib == NULL)
return;
// Load the bitmap.
//
if((hDib = OpenDIB(lpszDib)) == NULL)
return;
lpbi = (LPBITMAPINFOHEADER)GlobalLock(hDib);
xBlock = (int)(lpbi->biWidth / 10);
yBlock = (int)(lpbi->biHeight / 10);
// Create the DIB driver DC, and write a series of
// white rectangles. Update the display after each change.
//
hdcDib = CreateDC("DIB", NULL, NULL, (LPSTR)lpbi);
hpenOld = SelectObject(hdcDib, GetStockObject(BLACK_PEN));
hbrOld = SelectObject(hdcDib, GetStockObject(WHITE_BRUSH));
for(y = 0; y <= 10; y++)
{
for(x = 0; x <= 10; x++)
{
Rectangle(hdcDib, (x*xBlock), (y*yBlock),
(x*xBlock) + xBlock, (y*yBlock) + yBlock);
DisplayDib(lpbi, NULL, DISPLAYDIB_NOPALETTE);
}
}
SelectObject(hdcDib, hpenOld);
SelectObject(hdcDib, hbrOld);
DeleteDC(hdcDib);
GlobalUnlock(hDib);
GlobalFree(hDib);
}
Refer to the following books and articles for more information:
Adler, Marc. “Learning Windows Part VI: Bitmaps, Fonts, and Printing.”
Microsoft Systems Journal (May 1991).
Petzold, Charles. “Getting More Color with the Windows Palette Manager.”
PC Magazine (12 March 1991).
Petzold, Charles. “An Introduction to the Windows 3.0 Palette Manager.”
PC Magazine (26 February 1991).
Wilton, Richard. Programmer’s Guide to PC & PS/2 Video Systems. Redmond, Wash.: Microsoft Press, 1987. ISBN 1-55615-103-9 (Call 1-800-MSPRESS).