1.3 DIBSECTION and Palette
In the last section, I used LoadImage() to load a bitmap from a file. You may not see anything in the bitmap demo if your display is set to 256 colors. Even you used LoadBitmap() or LoadImage() with LR_CREATEDIBSECTION as parameters instead of the original function, the bitmap still displayed badly.
In order to understand what is happening, you have to learn what is 256 colors display. In 256 colors display mode, your system is capable of display 256 colors simultaneouly. A palette controls which 256 colors to display. The palette contains 256 entries. Each entry contain the actual color value which represents the strength of corresponding red, green and blue color components. This palette is called system palette under Windows. Since Windows is a multi-tasking environment, it does not allow any application to change the system palette entries directly (except using DirectDraw). You have to first create a logical palette with CreatePalette() and call SelectPalette() to select the logical palette to the device context. Then, the entries in the logical palette will be mapped to the system palette. I used the word "mapping" here since each color in the logical palette may be allocated with a empty entry or may be mapped to a closest entry in the system palette. This mapping process allows multiple applications which requires colorful graphics output share the system palette but still have acceptable quality. Windows generally give the highest priority to the foreground application which select its logical palette as foreground palette (through one of the parameter of SelectPalette()) for the palette mapping process. Therefore, the foreground application normally looks the best. However, at most 236 colors can mapped to unique entries in the system palette since 20 of them (static entries) are set aside for Windows itself for drawing non-client area such as caption, border, button, etc.
Let's go back to the problem of why the bitmap looks bad in 256 colors display. Bitmaps are stored in a device independent format (DIB) no matter they are stored as bitmap files (BMP) or as resources. When they are loaded into memory by calling LoadImage() or LoadBitmap(), they are converted from device indepentent format to device dependent format for maximum performance. However, the conversion uses only the default palette (static entries only). Therefore, most of the color information is lost in the conversion process. Even you keep it in the device independent format (LoadImage() with LR_CREATEDIBSECTION as parameter) to preserve the color information, the system palette may not contain the color for your bitmap and therefore your output to screen still did not get all the color information.
In order to solve the first problem, we just need to load the bitmap and convert it ourselves. Bitmap file contains the following structure:
BITMAPFILEHEADER BITMAPINFOHEADER RGBQUAD array Pixel data array
The BITMAPFILEHEADER structure identify the file. The BITMAPINFOHEADER structure specify the format of the bitmap such as width, height (in pixels), number of bits per pixel, compression type and number of entries in the color table. If the bitmap contains a palette, there will be a RGBQUAD array right after the BITMAPINFOHEADER structure. The RGBQUAD structures specify the RGB intensity values for each of the colors in the devices palette. The pixel data array contains the actual bitmap pixel data. If the bitmap has 8 or fewer pixels, the pixel data array uses the palette indices to represent the color of each pixel. Otherwise, the pixel data array contains the actual color value. The pixel data array are arranged as an array of scan line. The first element of the array is the last scan line and the last element is the first scan line. Each scan line must be DWORD aligned and it is padded if necessary. Each scan line is started with the pixel from the left handed side. Bitmap in resources has similar structure except it does not have BITMAPFILEHEADER structure. Please read the WIN32 SDK documentation about BITMAPINFO for more details.
There are 2 ways to convert device independent bitmap to the Windows required format. The first way is to collect the starting offset of each of the structure, create a palette and pass them to the function CreateDIBitmap(). The second way is to create a DIB section and copy the pixel data directly to its buffer. DIB section is very similar to the device dependent bitmap except to expose the pixel data buffer and therefore we can directly access it to change the content. It is particular useful if we want low level access to the bitmap itself such as the drawing of sprites which will be introduced in the next section. There are two functions, LoadBitmapResource() and LoadBitmapFile(), in this section source code which uses the first method. I also write another class CDibSection to manipulate the DIB section. Please read the source code for the implementation details.
Let's us talk about how an application can handle palette. The application main window procedure must handle the message WM_PALETTECHANGED and WM_QUERYNEWPALETTE. WM_PALETTECHANGED is sent to top level window whenever the system palette is changed. WM_QUERYNEWPALETTE is sent whenever the top level window is going to be activated. They are normally handled in the following way:
HPALETTE g_hMainPalette; ..... LRESULT CALLBACK WindowProc( HWND a_hwnd, // handle to window UINT a_uMsg, // message identifier WPARAM a_wParam, // first message parameter LPARAM a_lParam) // second message parameter { switch (a_uMsg) { case WM_PALETTECHANGED: if ((HWND) a_wParam == a_hwnd) { return 0; } // fall through if not the current window changed the system palette case WM_QUERYNEWPALETTE: { HDC hDC = GetDC(a_hwnd); HPALETTE hOldPalette = SelectPalette(hDC, g_hMainPalette, FALSE); UINT nChanged = RealizePalette(hDC); // logical palette is already mapped. Restore the dc and release it now SelectPalette(hDC, hOldPalette, TRUE); ReleaseDC(a_hwnd, hDC); if (nChanged == 0) { return 0; } // make sure the screen get redraw InvalidateRect(a_hwnd, NULL, FALSE); return 1; }
..... }
When you draw anything to the device context, you also need to select the palette to the device context as follows:
case WM_PAINT: { PAINTSTRUCT ps HDC hDC = BeginPaint(a_hwnd, &ps); HPALETTE hOldPalette = SelectPalette(hDC, g_hMainPalette, TRUE); RealizePalette(hDC); // do actual drawing here .... // SelectPalette(hDC, hOldPalette, TRUE); EndPaint(a_hwnd, &ps); return 1; }
I think I should stop writing here now. Please download sample3.zip and try out the program. Certainly, remember to set the display to 256 colors.
Professional
Games/Multimedia Programming
Copyright John Wong, 1998
Last revised on June 14, 1998
Please send any comments on this web site to [email protected]