DIBs and the CDib Class
MFC includes a class for plain GDI bitmaps ( CBitmap). However, MFC does not include a class for managing DIBs, so this chapter includes a class for managing DIBs. It's a complete rewrite of the CDib class from the early editions of this book (before the fourth edition), and it takes advantage of Win32 features such as memory-mapped files, improved memory management, and DIB sections. It also includes palette support. Before you examine the CDib class, however, you need a little background on DIBs.
A Few Words About Palette Programming
Windows palette programming is quite complex, but you've got to deal with it if you expect your users to run their displays in the 8-bpp (bits per pixel) mode— and many users will if they have video cards with 1 MB or less of memory. Suppose you're displaying a single DIB in a window. First, you must create a logical palette, a GDI object that contains the colors in the DIB. Then you must "realize" this logical palette into the hardware system palette, a table of the 256 colors that the video card can display at that instant. If your program is the foreground program, the realization process tries to copy all your colors into the system palette, but it doesn't touch the 20 standard Windows colors. For the most part, your DIB looks just like you want it to look.
But what if another program is the foreground program, and what if that program has a forest scene DIB with 236 shades of green? Your program will still realize its palette, but something different will happen. The system palette won't change, but Windows will set up a new mapping between your logical palette and the system palette. If your DIB contains a neon pink color, for example, Windows will map it to the standard red color. If your program forgets to realize its palette, your neon pink stuff will turn green when the other program becomes active. The forest scene example is extreme because we assume that the other program grabs 236 colors. If the other program instead realizes a logical palette with only 200 colors, Windows will let your program load 36 of its own colors (including, one hopes, neon pink). So when is a program supposed to realize its palette? The Windows message WM_PALETTECHANGED is sent to your program's main window whenever a program, including yours, realizes its palette. Another message, WM_QUERYNEWPALETTE, is sent whenever one of the windows in your program gets the input focus. Your program should realize its palette in response to both these messages (unless your program generated the message). These palette messages are not sent to your view window, however. You must map them in your application's main frame window and then notify the view. Chapter 14 will discuss the relationship between the frame window and the view. You call the Win32 RealizePalette function to perform the realization, but first you must call SelectPalette to select your DIB's logical palette into the device context. SelectPalette has a flag parameter that you normally set to FALSE in your WM_PALETTECHANGED and WM_QUERYNEWPALETTE handlers. This flag ensures that your palette is realized as a foreground palette if your application is indeed running in the foreground. If you use a TRUE flag parameter here, you can force Windows to realize the palette as if the application were in the background. You must also call SelectPalette for each DIB that you display in your OnDraw function. This time, you call it with a TRUE flag parameter. Things get complicated if you're displaying several DIBs, each with its own palette. Basically, you've got to select a palette for one of the DIBs and realize it (by selecting it with the FALSE parameter) in the palette message handlers. The selected DIB will end up looking better than the other DIBs. There are ways of merging palettes, but it might be easier to go out and buy more video memory.
DIBs, Pixels, and Color Tables
A DIB contains a two-dimensional array of elements called pixels. In many cases, each DIB pixel will be mapped to a display pixel, but the DIB pixel might be mapped to some logical area on the display, depending on the mapping mode and the display function stretch parameters. A pixel consists of 1, 4, 8, 16, 24, or 32 contiguous bits, depending on the color resolution of the DIB. For 16-bpp, 24-bpp, and 32-bpp DIBs, each pixel represents an RGB color. A pixel in a 16-bpp DIB typically contains 5 bits each for red, green, and blue values; a pixel in a 24-bpp DIB has 8 bits for each color value. The 16-bpp and 24-bpp DIBs are optimized for video cards that can display 65,536 or 16.7 million simultaneous colors. A 1-bpp DIB is a monochrome DIB, but these DIBs don't have to be black and white—they can contain any two colors selected from the color table that is built into each DIB. A monochrome bitmap has two 32-bit color table entries, each containing 8 bits for red, green, and blue values plus another 8 bits for flags. Zero (0) pixels use the first entry, and one (1) pixel uses the second. Whether you have a 65,536-color video card or a 16.7-million-color card, Windows can display the two colors directly. (Windows truncates 8-bits-per-color values to 5 bits for 65,536-color displays.) If your video card is running in 256- color palettized mode, your program can adjust the system palette to load the two specified colors. Eight-bpp DIBs are quite common. Like a monochrome DIB, an 8-bpp DIB has a color table, but the color table has 256 (or fewer) 32-bit entries. Each pixel is an index into this color table. If you have a palettized video card, your program can create a logical palette from the 256 entries. If another program (running in the foreground) has control of the system palette, Windows will do its best to match your logical palette colors to the system palette. What if you're trying to display a 24-bpp DIB with a 256-color palettized video card? If the DIB author was nice, he will have included a color table containing the most important colors in the DIB. Your program can build a logical palette from that table, and the DIB will look fine. If the DIB has no color table, use the palette returned by the Win32 CreateHalftonePalette function; it's better than the 20 standard colors you'd get with no palette at all. Another option is to analyze the DIB to identify the most important colors, but you can buy a utility to do that.
The Structure of a DIB Within a BMP File
You know that the DIB is the standard Windows bitmap format and that a BMP file contains a DIB. So let's look inside a BMP file to see what's there. Figure 6-2 shows a layout for a BMP file.

Figure 6-2: The layout for a BMP file.
The BITMAPFILEHEADER structure contains the offset to the image bits, which you can use to compute the combined size of the BITMAPINFOHEADER structure and the color table that follows. The BITMAPFILEHEADER structure contains a file size member, but you can't depend on it because you don't know whether the size is measured in bytes, words, or double words. The BITMAPINFOHEADER structure contains the bitmap dimensions, the bits per pixel, compression information for both 4-bpp and 8-bpp bitmaps, and the number of color table entries. If the DIB is compressed, this header contains the size of the pixel array; otherwise, you can compute the size from the dimensions and the bits per pixel. Immediately following the header is the color table (if the DIB has a color table). The DIB image comes after that. The DIB image consists of pixels arranged by column within rows, starting with the bottom row. Each row is padded to a 4-byte boundary. The only place you'll find a BITMAPFILEHEADER structure, however, is in a BMP file. If you get a DIB from the Clipboard, for example, there will be no file header. You can always count on the color table to follow the BITMAPINFOHEADER structure, but you can't count on the image to follow the color table. If you're using the CreateDIBSection function, for example, you must allocate the bitmap info header and color table and then let Windows allocate the image somewhere else.
DIB Access Functions
Windows supplies some important DIB access functions. None of these functions is wrapped by MFC, so you'll need to refer to the online Win32 documentation for details. Here's a summary:
SetDIBitsToDevice This function displays a DIB directly on the display or printer. No scaling occurs; one bitmap bit corresponds to one display pixel or one printer dot. This scaling restriction limits the function's usefulness. The function doesn't work like BitBlt because BitBlt uses logical coordinates.
StretchDIBits This function displays a DIB directly on the display or printer in a manner similar to that of StretchBlt.
GetDIBits This function constructs a DIB from a GDI bitmap, using memory that you allocate. You have some control over the format of the DIB because you can specify the number of color bits per pixel and the compression. If you're using compression, you have to call GetDIBits twice—once to calculate the memory needed and once to generate the DIB data.
CreateDIBitmap This function creates a GDI bitmap from a DIB. As for all these DIB functions, you must supply a device context pointer as a parameter. A display device context will do; you don't need a memory device context.
CreateDIBSection This Win32 function creates a special kind of DIB known as a DIB section. It then returns a GDI bitmap handle. This function gives you the best features of DIBs and GDI bitmaps. You have direct access to the DIB's memory, and with the bitmap handle and a memory device context, you can call GDI functions to draw into the DIB.
The CDib Class
If DIBs look intimidating, don't worry. The CDib class makes DIB programming easy. The best way to get to know the CDib class is to look at the public member functions and data members. The CDib header file is shown below. Consult the Ex06d folder on the companion CD to see the implementation code.
CDib.h
#ifndef _INSIDE_VISUAL_CPP_CDIB
#define _INSIDE_VISUAL_CPP_CDIB
class CDib : public CObject
{
enum Alloc {noAlloc, crtAlloc,
heapAlloc}; // applies to BITMAPINFOHEADER
DECLARE_SERIAL(CDib)
public:
LPVOID m_lpvColorTable;
HBITMAP m_hBitmap;
LPBYTE m_lpImage; // starting address of DIB bits
LPBITMAPINFOHEADER m_lpBMIH; // buffer containing the
// BITMAPINFOHEADER
private:
HGLOBAL m_hGlobal; // for external windows we need to free;
// could be allocated by this class or
// allocated externally
Alloc m_nBmihAlloc;
Alloc m_nImageAlloc;
DWORD m_dwSizeImage; // of bits—not BITMAPINFOHEADER
// or BITMAPFILEHEADER
int m_nColorTableEntries;
HANDLE m_hFile;
HANDLE m_hMap;
LPVOID m_lpvFile;
HPALETTE m_hPalette;
public:
CDib();
CDib(CSize size, int nBitCount); // builds BITMAPINFOHEADER
~CDib();
int GetSizeImage() {return m_dwSizeImage;}
int GetSizeHeader()
{return sizeof(BITMAPINFOHEADER) +
sizeof(RGBQUAD) * m_nColorTableEntries;}
CSize GetDimensions();
BOOLAttachMapFile(const char* strPathname,
BOOLbShare = FALSE);
BOOLCopyToMapFile(const char* strPathname);
BOOLAttachMemory(L PVOID lpvMem, BOOLbMustDelete = FALSE,
HGLOBAL hGlobal = NULL);
BOOLDraw(CDC* pDC, CPoint origin,
CSize size); // until we implement CreateDibSection
HBITMAP CreateSection(CDC* pDC = NULL);
UINT UsePalette(CDC* pDC, BOOLbBackground = FALSE);
BOOLMakePalette();
BOOLSetSystemPalette(CDC* pDC);
BOOLCompress(CDC* pDC,
BOOLbCompress = TRUE); // FALSE means decompress
HBITMAP CreateBitmap(CDC* pDC);
BOOLRead(CFile* pFile);
BOOL ReadSection(CFile* pFile, CDC* pDC = NULL);
BOOLWrite(CFile* pFile);
void Serialize(CArchive& ar);
void Empty();
private:
void DetachMapFile();
void ComputePaletteSize(int nBitCount);
void ComputeMetrics();
};
#endif // _INSIDE_VISUAL_CPP_CDIB
Here's a rundown of the CDib member functions, starting with the constructors and the destructor:
Default constructor You'll use the default constructor in preparation for loading a DIB from a file or for attaching to a DIB in memory. The default constructor creates an empty DIB object.
DIB section constructor If you need a DIB section that is created by the CreateDIBSection function, use this constructor. Its parameters determine DIB size and the number of colors. The constructor allocates info header memory but not image memory. You can also use this constructor if you need to allocate your own image memory.
Parameter | Description |
---|---|
size | CSize object that contains the width and height of the DIB |
nBitCount | Bits per pixel; should be 1, 4, 8, 16, 24, or 32 |
Destructor The CDib destructor frees all allocated DIB memory.
AttachMapFile This function opens a memory-mapped file in read mode and attaches it to the CDib object. The return is immediate because the file isn't actually read into memory until it is used. When you access the DIB, however, a delay might occur as the file is paged in. The AttachMapFile function releases existing allocated memory and closes any previously attached memory-mapped file.
Parameter | Description |
---|---|
strPathname | Pathname of the file to be mapped |
bShare | Flag that is TRUE if the file is to be opened in share mode; the default value is FALSE |
Return value | TRUE if successful |
AttachMemory This function associates an existing CDib object with a DIB in memory. This memory can be in the program's resources, or it can be Clipboard or OLE data object memory. Memory might have been allocated from the CRT heap using the new operator, or it might have been allocated from the Windows heap using GlobalAlloc.
Parameter | Description |
---|---|
lpvMem | Address of the memory to be attached |
bMustDelete | Flag that is TRUE if the CDib class is responsible for deleting this memory; the default value is FALSE |
hGlobal | If memory was obtained with a call to the Win32 Global-Alloc function, the CDib object needs to keep the handle in order to free it later, assuming that bMustDelete was set to TRUE |
Return value | TRUE if successful |
Compress This function regenerates the DIB as a compressed or an uncompressed DIB. Internally, it converts the existing DIB to a GDI bitmap and then makes a new compressed or an uncompressed DIB. Compression is supported only for 4-bpp and 8-bpp DIBs. You can't compress a DIB section.
Parameter | Description |
---|---|
pDC | Pointer to the display device context |
bCompress | TRUE (default) to compress the DIB; FALSE to uncompress it |
Return value | TRUE if successful |
CopyToMapFile This function creates a new memory-mapped file and copies the existing CDib data to the file's memory, releasing any previously allocated memory and closing any existing memorymapped file. The data isn't actually written to disk until the new file is closed, but that happens when the CDib object is reused or destroyed.
Parameter | Description |
---|---|
strPathname | Pathname of the file to be mapped |
Return value | TRUE if successful |
CreateBitmap This function creates a GDI bitmap from an existing DIB and is called by the Compress function. Don't confuse this function with CreateSection, which generates a DIB and stores the handle.
Parameter | Description |
---|---|
pDC | Pointer to the display or printer device context |
Return value | Handle to a GDI bitmap— NULL if unsuccessful. This handle is not stored as a public data member. |
CreateSection This function creates a DIB section by calling the Win32 CreateDIBSection function. The image memory will be uninitialized.
Parameter | Description |
---|---|
pDC | Pointer to the display or printer device context |
Return value | Handle to a GDI bitmap— NULL if unsuccessful. This handle is stored as a public data member. |
Draw This function outputs the CDib object to the display (or to the printer) with a call to the Win32 StretchDIBits function. The bitmap will be stretched as necessary to fit the specified rectangle.
Parameter | Description |
---|---|
pDC | Pointer to the display or printer device context that will receive the DIB image |
origin | CPoint object that holds the logical coordinates at which the DIB will be displayed |
size | CSize object that represents the display rectangle's width and height in logical units |
Return value | TRUE if successful |
Empty This function empties the DIB, freeing allocated memory and closing the map file if necessary.
GetDimensions This function returns the width and height of a DIB in pixels.
Parameter | Description |
---|---|
Return value | CSize object |
GetSizeHeader This function returns the number of bytes in the info header and color table combined.
Parameter | Description |
---|---|
Return value | 32-bit integer |
GetSizeImage This function returns the number of bytes in the DIB image (excluding the info header and the color table).
Parameter | Description |
---|---|
Return value | 32-bit integer |
MakePalette If the color table exists, this function reads it and creates a Windows palette. The HPALETTE handle is stored in a data member.
Parameter | Description |
---|---|
Return value | TRUE if successful |
Read This function reads a DIB from a file into the CDib object. The file must have been successfully opened. If the file is a BMP file, reading starts from the beginning of the file. If the file is a document, reading starts from the current file pointer.
Parameter | Description |
---|---|
pFile | Pointer to a CFile object; the corresponding disk file contains the DIB |
Return value | TRUE if successful |
ReadSection This function reads the info header from a BMP file, calls CreateDIBSection to allocate image memory, and then reads the image bits from the file into that memory. Use this function if you want to read a DIB from disk and then edit it by calling GDI functions. You can write the DIB back to disk using Write or CopyToMapFile.
Parameter | Description |
---|---|
pFile | Pointer to a CFile object; the corresponding disk file contains the DIB |
pDC | Pointer to the display or printer device context |
Return value | TRUE if successful |
Serialize Serialization is covered in Chapter 16. The CDib:: Serialize function, which overrides the MFC CObject::Serialize function, calls the Read and Write member functions. See the MSDN Library for a description of the parameters.
SetSystemPalette If you have a 16-bpp, 24-bpp, or 32-bpp DIB that doesn't have a color table, you can call this function to create for your CDib object a logical palette that matches the palette returned by the CreateHalftonePalette function. If your program is running on a 256-color palettized display and you don't call SetSystemPalette, you'll have no palette at all, and only the 20 standard Windows colors will appear in your DIB.
Parameter | Description |
---|---|
pDC | Pointer to the display context |
Return value | TRUE if successful |
UsePalette This function selects the CDib object's logical palette into the device context and then realizes the palette. The Draw member function calls UsePalette before painting the DIB.
Parameter | Description |
---|---|
pDC | Pointer to the display device context for realization |
bBackground | If this flag is FALSE (the default value) and the application is running in the foreground, Windows will realize the palette as the foreground palette. (It will copy as many colors as possible into the system palette.) If this flag is TRUE, Windows will realize the palette as a background palette. (It will map the logical palette to the system palette as best it can.) |
Return value | Number of entries in the logical palette mapped to the system palette. If the function fails, the return value is GDI_ERROR. |
Write This function writes a DIB from the CDib object to a file. The file must have been successfully opened or created.
Parameter | Description |
---|---|
pFile | Pointer to a CFile object; the DIB will be written to the corresponding disk file |
Return value | TRUE if successful |
For your convenience, four public data members give you access to the DIB memory and to the DIB section handle. These members should give you a clue about the structure of a CDib object. A CDib is just a bunch of pointers to heap memory. That memory might be owned by the DIB or by someone else. Additional private data members determine whether the CDib class frees the memory.
DIB Display Performance
Optimized DIB processing is now a major feature of Windows. Modern video cards have frame buffers that conform to the standard DIB image format. If you have one of these cards, your programs can take advantage of the new Windows DIB engine, which speeds up the process of drawing directly from DIBs. If you're still running in VGA mode, however, you're out of luck; your programs will still work, but not as quickly. If you're running Windows in 256-color mode, your 8-bpp bitmaps will be drawn quickly, either with StretchBlt or with StretchDIBits. If, however, you're displaying 16-bpp or 24-bpp bitmaps, those drawing functions will be too slow. Your bitmaps will appear more quickly in this situation if you create a separate 8-bbp GDI bitmap and then call StretchBlt. Of course, you must be careful to realize the correct palette before creating the bitmap and before drawing it. Here's some code that you might insert just after loading your CDib object from a BMP file:
// m_hBitmap is a data member of type HBITMAP
// m_dcMem is a memory device context object of class CDC
m_pDib->UsePalette(&dc);
m_hBitmap = m_pDib->CreateBitmap(&dc); // could be slow
::SelectObject(m_dcMem.GetSafeHdc(), m_hBitmap);
Here's the code you use in place of CDib::Draw in your view's OnDraw member function:
m_pDib->UsePalette(pDC); // could be in palette msg handler
CSize sizeDib = m_pDib->GetDimensions();
pDC->StretchBlt(0, 0, sizeDib.cx, sizeDib.cy, &m_dcMem,
0, 0, sizeToDraw.cx, sizeToDraw.cy, SRCCOPY);
Don't forget to call DeleteObject for m_hBitmap when you're done with it.
The Ex06d Example
Now you'll put the CDib class to work in an application. The Ex06d program displays two DIBs, one from a resource and the other loaded from a BMP file that you select at run time. The program manages the system palette and displays the DIBs correctly on the printer.
Here are the steps to build Ex06d. It's a good idea to type in the view class code, but you'll want to use the


Run the MFC Application Wizard to generate the Ex06d project. Accept all the defaults but two: Select Single Document and select the CScrollView view base class for CEx06dView.
Import the Red Blocks bitmap. Choose Add Resource from Visual C++ .NET's Project menu. In the Add Resource dialog box, click the Import button. Next, import

Integrate the CDib class with this project. If you've created this project from scratch, copy the




Add two private CDib data members to the class CEx06dView. In Class View, right-click on the CEx06dView class. Choose Add Variable from the shortcut menu, and then add the m_dibResource member. Add m_dibFile in the same way. The result should be two data members at the bottom of the header file:
CDib m_dibFile;
CDib m_dibResource;
Class View also adds this statement at the top of the

#include "cdib.h" // Added by Class View
Edit the OnInitialUpdate member function in

void CEx06dView::OnInitialUpdate()
{
CScrollView::OnInitialUpdate();
CSize sizeTotal(30000, 40000); // 30-by-40 cm
CSize sizeLine = CSize(sizeTotal.cx / 100, sizeTotal.cy / 100);
SetScrollSizes(MM_HIMETRIC, sizeTotal, sizeTotal, sizeLine);
LPVOID lpvResource = (LPVOID) ::LoadResource(NULL,
::FindResource(NULL, MAKEINTRESOURCE(IDB_REDBLOCKS),
RT_BITMAP));
m_dibResource.AttachMemory(lpvResource); // no need for
// ::LockResource
CClientDC dc(this);
TRACE("bits per pixel = %d\n", dc.GetDeviceCaps(BITSPIXEL));
}
Edit the OnDraw member function in the file

void CEx06cView::OnDraw(CDC* pDC)
{
BeginWaitCursor();
m_dibResource.UsePalette(pDC); // should be in palette
m_dibFile.UsePalette(pDC); // message handlers, not here
pDC->TextOut(0, 0,
"Press the left mouse button here to load a file.");
CSize sizeResourceDib = m_dibResource.GetDimensions();
sizeResourceDib.cx *= 30;
sizeResourceDib.cy *= -30;
m_dibResource.Draw(pDC, CPoint(0, -800), sizeResourceDib);
CSize sizeFileDib = m_dibFile.GetDimensions();
sizeFileDib.cx *= 30;
sizeFileDib.cy *= -30;
m_dibFile.Draw(pDC, CPoint(1800, -800), sizeFileDib);
EndWaitCursor();
}
Map the WM_LBUTTONDOWN message in the CEx06dView class. Edit the file

#define MEMORY_MAPPED_files
void CEx06cView::OnLButtonDown(UINT nFlags, CPoint point)
{
CFileDialog dlg(TRUE, "bmp", "*.bmp");
if (dlg.DoModal() != IDOK) {
return;
}
#ifdef MEMORY_MAPPED_files
if (m_dibFile.AttachMapFile(dlg.GetPathName(),
TRUE) == TRUE) { // share
Invalidate();
}
#else
CFile file;
file.Open(dlg.GetPathName(), CFile::modeRead);
if (m_dibFile.Read(&file) == TRUE) {
Invalidate();
}
#endif // MEMORY_MAPPED_files
CClientDC dc(this);
m_dibFile.SetSystemPalette(&dc);
}
Build and run the application. The bitmaps directory on the companion CD contains several interesting bitmaps. The



