[Home] [Tim] [Tina] [Brynn] [Baby Lesher]

Programming for High-Color Video Modes

This material originally appeared in the September 1999 issue of Windows Developer's Journal (copyright 1999 Miller Freeman Inc.) and is reproduced here with permission of the publisher.

Windows has historically supported a plethora of color modes, ranging from basic 16-color VGA to 32-bit "true color" modes. While the 24-bit and 32-bit true color modes are most accurate and easiest to work with (for example, when writing image processing code), they require large amounts of video-card memory to store the screen data at high resolutions. The "high color" modes, 15- and 16-bit modes, are a compromise between the palettized color modes and the true color modes.

This compromise has never been very well documented. In 1993, Microsoft released an SDK document1, buried in the Video for Windows documentation, that detailed 16-bit and 32-bit device-independent bitmap (DIB) formats. However, authors of video-card drivers were still free to define their own device-dependent 16-bit video formats, so the 16-bit format remained obscure, rearing its head only in strange, machine-specific bugs in graphic-intensive applications.

In this article, I’ll discuss the 16-bit video mode under Windows, and how to detect and even take advantage of user displays set to 16-bit mode.

Windows Pixel Formats

Figure 1: Common Windows pixel formats
Figure 1:  Common Windows pixel formats
To understand the implications of using 16-bit video modes, one needs to understand the format of a logical Windows pixel. A video adapter cottains enough memory to represent all the pixels on the screen, and the adapter must somehow map the bits that represent each memory pixel onto appropriate values to set the red, green, and blue intensities for that video pixel. With a 24-bit color mode, this mapping is simple and direct: each pixel contains three bytes, and each byte sets the red, green, or blue intensity to a value from 0 to 255. With an 8-bit mode, this mapping is more indirect: the video adapter uses each 8-bit memory pixel as an index into a 24-bit color table to obtain the correct values for the video pixel--you can still display any RGB color, but only 256 different colors are possible at any given moment. Figure 1 shows some common pixel formats. With 32-bit color modes, the extra byte may be unused (or may be for some other purpose, such as a transparency feature).

The 16-bit modes are unique in that they involve splitting the color components across byte boundaries. The position of this split and the allocation of bits to the color channels are the decision of the device-driver writers. The most common formats are the 5:5:5 format (32,768 colors), and the 5:6:5 format (65,536 colors)

Some drivers call the 5:5:5 format a "15-bit" format; however, the pixels are actually stored in two full bytes for efficiency. The highest bit is unused. In addition, the order of the bits is driver specific. In general, the bits are stored in B:G:R format (blue, then green, then red), but they could be stored in R:G:B format as well.

Detecting 16-bit Displays

Most Windows applications never need to know the details of the current video adapter, but there are some instances in which you need to know the exact format of the screen pixels. For example, you might be writing a screen-capture application and want to be able to alter a DIB image of the currently captured screen, or you may be writing a 3D rendering application that needs to be able to render scan lines into a screen buffer with the correct format. Knowing the video mode also helps when debugging strange display glitches that show up on some machines, but not on others. Windows' Graphic Display Interface (GDI) provides programs with two calls to get the current video-display format, GetDeviceCaps() and EnumDisplaySettings().

However, GetDeviceCaps() reports formats by the number of bits required to store a pixel, not by the number of color bits used. Therefore, it doesn't differentiate between 5:5:5 and 5:6:5 color modes--it reports them both as 16-bit formats. EnumDisplaySettings() does report 15 bits for 5:5:5 modes and 16 bits for 5:6:5 modes, but it doesn't report anything about the component ordering, so you can't tell whether the bits are ordered blue-green-red or red-green-blue. Therefore, neither call sufficiently describes the current video mode.

Microsoft’s DirectDraw API provides a much lower-level interface than GDI does and allows direct access to the memory buffers of video-display hardware. It provides a direct call to find out the exact format of a pixel in the current display mode, IDirectDrawSurface::GetPixelFormat(). However, using DirectDraw is not a trivial undertaking. It involves a great deal of COM "plumbing" code, and is certainly overkill for a single function call. In addition, DirectDraw isn’t available on Windows 3.1 or Windows NT 3.51 platforms and requires a large additional update for base Windows 95 machines, limiting its usefulness.

Ideally, you would like to be able to take a look at the bits that make up the display in the same way as DirectDraw’s IDirectDrawSurface::GetPixelFormat(), but without having to include all of DirectDraw. Fortunately, GDI provides the tools to do this, with a little cleverness.

Using BITMAPINFOHEADERs

The structure of a BITMAPINFOHEADER is well known to writers of graphic applications (Figure 2).
Figure 2: The BITMAPINFOHEADER struct
typedef struct {
    DWORD      biSize;
    LONG       biWidth;
    LONG       biHeight;
    WORD       biPlanes;
    WORD       biBitCount;
    DWORD      biCompression;
    DWORD      biSizeImage;
    LONG       biXPelsPerMeter;
    LONG       biYPelsPerMeter;
    DWORD      biClrUsed;
    DWORD      biClrImportant;
} BITMAPINFOHEADER;
The BITMAPINFOHEADER represents the image type information of a DIB, and includes a field that specifies the number of bits per pixel. However, this value is the same value returned by EnumDisplaySettings(). So how can we use a BITMAPINFOHEADER to determine real pixel format?

The solution lies in Microsoft’s extension to DIBs introduced in Video for Windows. Every DIB has a biCompression field in the DIB header. For most images, this value is 0 (BI_RGB or no compression). However, for 16-bit and 32-bit images, Microsoft defined a pseudo-compression type, BI_BITFIELDS. This value doesn’t indicate any compression--instead, it indicates that an array of three 32-bit bitfields follows the BITMAPINFOHEADER. These represent the bit masks one can use to "slice off" the red, green, and blue components of the DIB pixels. For example, to find the red component of a pixel in a 16-bit DIB:

BITMAPINFOHEADER * pDibHeader;  /* assumed filled already */
unsigned short pixel;           /* assumed filled already */

unsigned long * mask = (unsigned long *) ((char *)
    pDibHeader + pDibHeader->biSize );
unsigned short pixel;
unsigned long value = (unsigned short) pixel;
unsigned long red_part = pixel & mask[0];
unsigned long green_part = pixel & mask[1];
unsigned long blue_part = pixel & mask[2];

So the only remaining task is to figure out how to get a BITMAPINFOHEADER that describes the current display. This can be done using GetDC() to obtain a device context that represents the entire screen, followed by calling GetDIBits() to fill in a BITMAPINFOHEADER. Note that GetDIBits() is called twice. The first call simply fills in the BITMAPINFOHEADER structure; the second call fills in the bitfields.

vidfmt.c (Listing 1, 2KB) illustrates this technique. The application obtains a compatible DIB, prints the current video mode, and exits. For 16-bit and 32-bit formats that support BI_BITFIELDS, it also prints the color-component bit size and ordering. Note that when allocating the BITMAPINFOHEADER, the program allocates enough space for a BITMAPINFOHEADER plus another 1,024 bytes. If the current display mode is 8-bit palettized, the second call to GetDIBits() will fill in the section after BITMAPINFOHEADER with the current palette; 1,024 bytes is the maximum size of a Windows palette (256 * sizeof(RGBQUAD)).

Interested readers may note that, beginning with Windows 95 and NT 4.0, Microsoft defined a new BITMAPV4HEADER, which is a superset of the BITMAPINFOHEADER. Immediately after the last BITMAPINFOHEADER field, a BITMAPV4HEADER includes three 32-bit values, named bV4RedMask, bV4GreenMask, and bV4BlueMask. These values work just like the mask[] array from the sample code, but are more self-documenting. A BITMAPV4HEADER header also includes alpha-mask information, which can be used to draw partially-transparent images in Windows 98 and 2000. Hopefully, as Microsoft continues to add new capabilities to graphics software, they will make the information to use these capabilities similarly accessible.

When I originally wrote this code, one of my goals was compatibility, since the software for which it was written ran on a number of different Windows versions. The method displayed here should work with all versions of Windows, from Windows 3.1 (with Video for Windows extensions) to Windows 98, and all versions of NT, whereas neither EnumDisplaySettings() nor DirectDraw exists before NT 4.0 or Windows 95. In addition, the method is simple enough that it should continue to work with whatever new operating systems come from Microsoft.

Reference

1 "DIB Format Extensions for Microsoft Windows." Video for Windows Software Development Kit, Microsoft Corporation, 1993. [Home] [Tim] [Tina] [Brynn] [Baby Lesher]