DIBs and the CDib Class

There's an MFC class for GDI bitmaps (CBitmap), but there's no MFC class for DIBs. Don't worry—I'm giving you one here. It's a complete rewrite of the CDib class from the early editions of this book (prior to 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 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 still realizes its palette, but something different happens this time. Now the system palette won't change, but Windows sets up a new mapping between your logical palette and the system palette. If your DIB contains a neon pink color, for example, Windows maps it to the standard red color. If your program forgot to realize its palette, your neon pink stuff would turn green when the other program went active.

The forest scene example is extreme because we assumed that the other program grabbed 236 colors. If instead the other program realized a logical palette with only 200 colors, Windows would 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 13 discusses the relationship between the frame window and the view, and Chapter 26 contains a complete palette-aware MDI application (EX26A).

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 though 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 do get complicated if you're displaying several DIBs, each with its own palette. Basically, you've got to choose a palette for one of the DIBs and realize it (by selecting it with the FALSE parameter) in the palette message handlers. The chosen 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 chosen 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 does 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 or she 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 11-1 shows a layout for a BMP file.

Figure 11-1. 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 not be a 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.

This chapter and all the associated code are specific to Windows DIBs. There's also a well-documented variation of the DIB format for OS/2. If you need to process these OS/2 DIBs, you'll have to modify the CDib class.

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:

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. Figure 11-2 shows the CDib header file. Consult the ex11c folder on the companion CD-ROM 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();
    BOOL AttachMapFile(const char* strPathname, BOOL bShare = FALSE);
    BOOL CopyToMapFile(const char* strPathname);
    BOOL AttachMemory(LPVOID lpvMem, BOOL bMustDelete = FALSE,
        HGLOBAL hGlobal = NULL);
    BOOL Draw(CDC* pDC, CPoint origin,
        CSize size); // until we implement CreateDibSection
    HBITMAP CreateSection(CDC* pDC = NULL);
    UINT UsePalette(CDC* pDC, BOOL bBackground = FALSE);
    BOOL MakePalette();
    BOOL SetSystemPalette(CDC* pDC);
    BOOL Compress(CDC* pDC,
        BOOL bCompress = TRUE); // FALSE means decompress
    HBITMAP CreateBitmap(CDC* pDC);
    BOOL Read(CFile* pFile);
    BOOL ReadSection(CFile* pFile, CDC* pDC = NULL);
    BOOL Write(CFile* pFile);
    void Serialize(CArchive& ar);
    void Empty();
private:
    void DetachMapFile();
    void ComputePaletteSize(int nBitCount);
    void ComputeMetrics();
};
#endif // _INSIDE_VISUAL_CPP_CDIB
Figure 11-2. The CDib class declaration.

Here's a rundown of the CDib member functions, starting with the constructors and the destructor:

ParameterDescription
sizeCSize object that contains the width and height of the DIB
nBitCountBits per pixel; should be 1, 4, 8, 16, 24, or 32
ParameterDescription
strPathnamePathname of the file to be mapped
bShareFlag that is TRUE if the file is to be opened in share mode; the default value is FALSE
Return valueTRUE if successful
ParameterDescription
lpvMemAddress of the memory to be attached
bMustDeleteFlag that is TRUE if the CDib class is responsible for deleting this memory; the default value is FALSE
hGlobalIf memory was obtained with a call to the Win32 GlobalAlloc function, the CDib object needs to keep the handle in order to free it later, assuming that bMustDelete was set to TRUE
Return valueTRUE if successful
ParameterDescription
pDCPointer to the display device context
bCompressTRUE (default) to compress the DIB; FALSE to uncompress it
Return valueTRUE if successful
ParameterDescription
strPathnamePathname of the file to be mapped
Return valueTRUE if successful
ParameterDescription
pDCPointer to the display or printer device context
Return valueHandle to a GDI bitmap—NULL if unsuccessful. This handle is not stored as a public

data member.

ParameterDescription
pDCPointer to the display or printer device context
Return valueHandle to a GDI bitmap—NULL if unsuccessful. This handle is also stored as a public data member.
ParameterDescription
pDCPointer to the display or printer device context that will receive the DIB image
originCPoint object that holds the logical coordinates at which the DIB will be displayed
sizeCSize object that represents the display rectangle's width and height in logical units
Return valueTRUE if successful
ParameterDescription
Return valueCSize object
ParameterDescription
Return value32-bit integer
ParameterDescription
Return value32-bit integer
ParameterDescription
Return valueTRUE if successful
ParameterDescription
pFilePointer to a CFile object; the corresponding disk file contains the DIB
Return valueTRUE if successful
ParameterDescription
pFilePointer to a CFile object; the corresponding disk file contains the DIB
pDCPointer to the display or printer device context
Return valueTRUE if successful
ParameterDescription
pDCPointer to the display context
Return valueTRUE if successful
ParameterDescription
pDCPointer to the display device context for realization
bBackgroundIf this flag is FALSE (the default value) and the application is running in the foreground, Windows realizes the palette as the foreground palette (copies as many colors as possible into the system palette). If this flag is TRUE, Windows realizes the palette as a background palette (maps the logical palette to the system palette as best it can).
Return valueNumber of entries in the logical palette mapped to the

system palette. If the function fails, the return value is GDI_ERROR.

ParameterDescription
pFilePointer to a CFile object; the DIB will be

written to the corresponding disk file.

Return valueTRUE 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 fast.

If you're running Windows in 256-color mode, your 8-bpp bitmaps will be drawn very quickly, either with StretchBlt or with StretchDIBits. If, however, you are 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 prior to creating the bitmap and prior to 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 is the code that 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 EX11C Example

Now you'll put the CDib class to work in an application. The EX11C program displays two DIBs, one from a resource and the other loaded from a BMP file that you select at runtime. The program manages the system palette and displays the DIBs correctly on the printer. Compare the EX11C code with the GDI bitmap code in EX11A. Notice that you're not dealing with a memory device context and all the GDI selection rules!

Following are the steps to build EX11C. It's a good idea to type in the view class code, but you'll want to use the cdib.h and cdib.cpp files from the companion CD-ROM.

  1. Run AppWizard to produce \vcpp32\ex11c\ex11c. Accept all the defaults but two: select Single Document and select the CScrollView view base class for CEx11cView. The options and the default class names are shown here.

  1. Import the Red Blocks bitmap. Choose Resource from Visual C++'s Insert menu. Import Red Blocks.bmp from the \WINDOWS directory. (If your version of Windows doesn't include this bitmap, load it from the companion CD-ROM.) Visual C++ will copy this bitmap file into your project's \res subdirectory. Assign IDB_REDBLOCKS as the ID, and save the changes.

  2. Integrate the CDib class with this project. If you've created this project from scratch, copy the cdib.h and cdib.cpp files from \vcpp32\ex11c on the companion CD-ROM. Simply copying the files to disk isn't enough; you must also add the CDib files to the project. Choose Add To Project from Visual C++'s Project menu, and then choose Files. Select cdib.h and cdib.cpp, and click the OK button. If you now switch to ClassView in the Workspace window, you will see the class CDib and all of its member variables and functions.

  3. Add two private CDib data members to the class CEx11cView. In the ClassView window, right-click the CEx11cView class. Choose Add Member Variable from the resulting pop-up menu, and then add the m_dibResource member as shown in the following illustration.

    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;

    ClassView also adds this statement at the top of the ex11cView.h file:

    #include "cdib.h"   // Added by ClassView

  4. Edit the OnInitialUpdate member function in ex11cView.cpp. This function sets the mapping mode to MM_HIMETRIC and loads the m_dibResource object directly from the IDB_REDBLOCKS resource. Note that we're not calling LoadBitmap to load a GDI bitmap as we did in EX11A. The CDib::AttachMemory function connects the object to the resource in your EXE file. Add the following boldface code:

    void CEx11cView::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));
    }
  5. Edit the OnDraw member function in the file ex11cView.cpp. This code calls CDib::Draw for each of the DIBs. The UsePalette calls should really be made by message handlers for the WM_QUERYNEWPALETTE and WM_PALETTECHANGED messages. These messages are hard to deal with because they don't go to the view directly, so we'll take a shortcut. Add the following boldface code:

    void CEx11cView::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();
    }
  6. Map the WM_LBUTTONDOWN message in the CEx11cView class. Edit the file ex11cView.cpp. OnLButtonDown contains code to read a DIB in two different ways. If you leave the MEMORY_MAPPED_FILES definition intact, the AttachMapFile code is activated to read a memory-mapped file. If you comment out the first line, the Read call is activated. The SetSystemPalette call is there for DIBs that don't have a color table. Add the following boldface code:

    #define MEMORY_MAPPED_FILES
    void CEx11cView::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);
    }
  7. Build and run the application. The bitmaps directory on the companion CD-ROM contains several interesting bitmaps. The Chicago.bmp file is an 8-bpp DIB with 256-color table entries; the forest.bmp and clouds.bmp files are also 8-bpp, but they have smaller color tables. The balloons.bmp is a 24-bpp DIB with no color table. Try some other BMP files if you have them. Note that Red Blocks is a 16-color DIB that uses standard colors, which are always included in the system palette.