Chapter 17

Manipulating Device-Independent Bitmaps


If you've done any Windows programming, you know that bitmaps are everywhere. This is because the BMP graphics format is the only graphical-image format (not including icons, which are very limited) that Windows directly supports. Look through your Windows programming manuals, and you'll find functions such as CreateBitmap(), LoadBitmap(), StretchDIBits(), and BitBlt() that enable you to create, load, and display bitmaps on the screen, but you won't find similar functions for other graphics formats like PCX, TIF, and GIF.

The bitmaps you dealt with in the previous chapter were device-dependent bitmaps (DDBs). In this chapter, you’ll learn about DDB’s important cousin, device-independent bitmaps (DIBs).

Reviewing DDBs and DIBs

Device-dependent bitmaps are graphical images that can be displayed on only one type of physical device. For example, when you use Windows functions such as CreateBitmap() and LoadBitmap(), you're creating in memory a bitmap image that is compatible with a certain device, usually the screen. These types of bitmaps are also sometimes called GDI bitmaps because Windows' GDI (graphics device interface) can handle them directly. DDBs are stored without color tables because they use the colors of their associated device. Moreover, DDBs usually reside only in memory, rather than as files on a disk.

Device-independent bitmaps are graphical images that can be displayed on many different devices. These types of bitmaps carry with them a color table that the current device must use to display the bitmap so that the bitmap looks similar from one device to another. For example, a DIB should look almost the same under Windows as it does under DOS or OS/2. Because DIBs are generally portable between systems, you often find them as disk files. If you look in your Windows directory, for example, you'll see many files that have the BMP extension. These are DIBs. You can create your own DIBs using various types of paint programs, including Windows Paintbrush, which comes with every copy of Windows. You also can use Visual C++'s bitmap editor.

The DIB Format

Whether a DIB is stored on disk or in memory, it has almost exactly the same structure. Actually, a DIB is made up of several different types of structures, one following the other. These structures include the BITMAPFILEHEADER, BITMAPINFO, BITMAPINFOHEADER, and RGBQUAD types. The following sections describe these structure types and how they're used in Windows programs.

The BITMAPFILEHEADER Structure

At the beginning of a DIB file is the BITMAPFILEHEADER structure, which is defined by Windows as shown in Listing 17.1.

Listing 17.1[em]LST17_01.CPP The BITMAPFILEHEADER Structure

typedef struct tagBITMAPFILEHEADER {
   WORD bfType;
   DWORD bfSize;
   WORD bfReserved1;
   WORD bfReserved2;
   DWORD bfOffBits;
} BITMAPFILEHEADER;

Although this structure resides at the beginning of a DIB disk file, it need not be part of the DIB in memory. The first structure member, bfType, identifies the file as a DIB and should be the ASCII codes of the letters BM. In hex, the bfType word should be 4D42. Otherwise, the file is probably not a DIB. The second member, bfSize, is supposed to be the size of the DIB file in bytes. However, due to a mistake in the original Windows documentation, bfSize is not reliable and should be ignored. On the other hand, you can count on the bfOffBits member to contain the number of bytes from the start of the DIB file to the bitmap data. The BITMAPFILEHEADER structure is summarized in Table 17.1.

Table 17.1 The BITMAPFILEHEADER Structure

Member Type Description
bfType WORD Contains the ASCII values BM
bfSize DWORD The size of the file
bfReserved1 WORD Always 0
bfReserved2 WORD Always 0
bfOffBits DWORD The number of bytes from the beginning of the file to the bitmap

The BITMAPINFO Structure

Following the BITMAPFILEHEADER structure is the BITMAPINFO structure, which is defined by Windows as shown in Listing 17.2.

Listing 17.2 LST17_02.CPP The BITMAPINFO Structure

typedef struct tagBITMAPINFO {
   BITMAPINFOHEADER bmiHeader;
   RGBQUAD bmiColors[1];
} BITMAPINFO;

As you can see, this structure is made up of a header, represented by the BITMAPINFOHEADER structure and a color table, represented by an array of RGBQUAD structures.

The BITMAPINFOHEADER Structure

A BITMAPINFOHEADER structure, also defined by Windows, looks like Listing 17.3.

Listing 17.3 LST17_03.CPP The BITMAPINFOHEADER Structure

typedef struct tagBITMAPINFOHEADER {
   DWORD biSize;
   DWORD biWidth;
   DWORD biHeight;
   WORD biPlanes;
   WORD biBitCount;
   DWORD biCompression;
   DWORD biSizeImage;
   DWORD biXPelsPerMeter;
   DWORD biYPelsPerMeter;
   DWORD biClrUsed;
   DWORD biClrImportant;
} BITMAPINFOHEADER;

The member biSize contains the size of the BITMAPINFOHEADER structure, which should be 40 bytes. The members biWidth and biHeight are the width and height of the bitmap in pixels. The member biPlanes is always set to 1. The biBitCount member, which indicates the number of bits per pixel, can be 1, 4, 8, or 24, which indicate monochrome, 16-color, 256-color, and 16.7 million color images. (In this book, you mostly use 256-color, or 8-bit, images.)

The biCompression member indicates the type of compression used with the bitmap image, where a 0 indicates no compression; a 1 indicates RLE-8 compression; and a 2 indicates RLE-4 compression. If you're not familiar with data compression techniques, don't worry about it. DIBs are rarely compressed. You'll usually find a 0 in the biCompression structure member.

The biSizeImage member is the size of the bitmap in bytes and is usually used only with compressed bitmaps. This value takes into account that the number of bytes in each row of a bitmap is always a multiple of 4. The rows are padded with blank bytes, when necessary, to ensure a multiple of 4. However, unless you're writing a program that creates DIBs, you don't need to deal with row padding and the code complications that arise from it.

The biXPelsPerMeter and biYPelsPerMeter members contain the horizontal and vertical number of pixels per meter of the intended display device, but are usually just set to 0. The biClrUsed and biClrImportant members, which contain the total number of colors used in the bitmap and the number of important colors in the bitmap, are also usually set to 0.

You might have noticed that BITMAPINFOHEADER structure members after biBitCount are likely to contain a 0, so after reading the structure from the disk file, you'll probably ignore the values stored in these structure members. In this chapter, you'll see how you can calculate any values you need such as the number of colors used in the image and store those values in the proper members for later retrieval. For easy reference, the BITMAPINFOHEADER structure is summarized in Table 17.2.

Table 17.2 The BITMAPINFOHEADER Structure

Member Type Description
biSize DWORD Size in bytes of this
structure
biWidth DWORD Bitmap's width in pixels
biHeight DWORD Bitmap's height in pixels
biPlanes WORD Always 1
biBitCount WORD Number of bits per pixel
biCompression DWORD Compression type: 0 = None, 1= RLE-8, 2 = RLE-4
biSizeImage DWORD Bitmap's size in bytes
biXPelsPerMeter DWORD Horizontal pixels per meter
biYPelsPerMeter DWORD Vertical pixels per meter
biClrUsed DWORD Number of colors used
biClrImportant DWORD Number of important colors

The RGBQUAD Structure

The final data structure, RGBQUAD, is defined by Windows as shown in Listing 17.4.

Listing 17.4 LST17_04.CPP The RGBQUAD Structure

typedef struct tagRGBQUAD {
   BYTE rgbBlue;
   BYTE rgbGreen;
   BYTE rgbRed;
   BYTE rgbReserved;
} RGBQUAD;

This structure simply contains the intensities of a color's red, green, and blue elements. Each color in a DIB is represented by an RGBQUAD structure. That is, a 16-color (4-bit) bitmap has a color table made up of 16 RGBQUAD structures, whereas a 256-color (8-bit) bitmap has a color table containing 256 RGBQUAD structures. The exception is 24-bit color images, which have no color table.

Following a DIB's BITMAPINFOHEADER structure is the bitmap's actual image data. The size of this data depends, of course, on the size of the image. Figure 17.1 illustrates the entire layout of a DIB file.


FIG. 17.1

A DIB file contains several different structures.

Introducing the CDib Class

Although MFC features classes for many graphical objects, you won't find one for DIBs. You don't, of course, necessarily need a special class to handle DIBs, but having such a class helps you organize your code into reusable modules. For that reason, in this chapter you develop a simple class called CDib, which reads DIBs from disk into memory and returns important information about the DIB.

The CDib Class's Interface

The CDib class's interface is represented by its header file, which is shown in Listing 17.5.

Listing 17.5 CDIB.H The CDib Class's Header File

///////////////////////////////////////////////////////////
// CDIB.H: Header file for the DIB class.
///////////////////////////////////////////////////////////
#ifndef __CDIB_H
#define __CDIB_H
class CDib : public CObject
{
protected:
    LPBITMAPFILEHEADER m_pBmFileHeader;
    LPBITMAPINFO m_pBmInfo;
    LPBITMAPINFOHEADER m_pBmInfoHeader;
    RGBQUAD* m_pRGBTable;
    BYTE* m_pDibBits;
    UINT m_numColors;
    
public:
    CDib(const char* fileName);
    ~CDib();
    DWORD GetDibSizeImage();
    UINT GetDibWidth();
    UINT GetDibHeight();
    UINT GetDibNumColors();
    LPBITMAPINFOHEADER GetDibInfoHeaderPtr();
    LPBITMAPINFO GetDibInfoPtr();
    LPRGBQUAD GetDibRGBTablePtr();
    BYTE* GetDibBitsPtr();
protected:
    void LoadBitmapFile(const char* fileName);
};
#endif

The CDib class's data members consist mostly of pointers to the various parts of a DIB, as shown in Listing 17.6.

Listing 17.6 LST17_06.CPP Data Members of the CDib Class

LPBITMAPFILEHEADER m_pBmFileHeader;
LPBITMAPINFO m_pBmInfo;
LPBITMAPINFOHEADER m_pBmInfoHeader;
RGBQUAD* m_pRGBTable;
BYTE* m_pDibBits;

The pointers in Listing 17.6 store the addresses of the DIB's BITMAPFILEHEADER, BITMAPINFO, and BITMAPINFOHEADER structures, as well as the addresses of the DIB's color table and image data. The final data member holds the number of colors in the DIB:

UINT m_numColors;

Like most classes, CDib has both a constructor and a destructor:

CDib(const char* fileName);
~CDib();

As you can see, you can create a CDib object by passing the file name of the bitmap that you want to load to the CDib constructor.

To enable you to obtain important information about a DIB after it is loaded, the CDib class features eight public member functions, as shown in Listing 17.7.

Listing 17.7 LST17_07.CPP Member Functions of the CDib Class

DWORD GetDibSizeImage();
UINT GetDibWidth();
UINT GetDibHeight();
UINT GetDibNumColors();
LPBITMAPINFOHEADER GetDibInfoHeaderPtr();
LPBITMAPINFO GetDibInfoPtr();
LPRGBQUAD GetDibRGBTablePtr();
BYTE* GetDibBitsPtr();

Table 17.3 lists each of the public member functions and what they do.

Table 17.3 The CDib Class's public Member Functions

Name Description
GetDibSizeImage() Returns the size in bytes of the image
GetDibWidth() Returns the DIB's width in pixels
GetDibHeight() Returns the DIB's height in pixels
GetDibNumColors() Returns the number of colors in the DIB
GetDibInfoHeaderPtr() Returns a pointer to the DIB's BITMAPINFOHEADER structure
GetDibInfoPtr() Returns a pointer to the DIB's BITMAPINFO structure
GetDibRGBTablePtr() Returns a pointer to the DIB's color table
GetDibBitsPtr() Returns a pointer to the DIB's image data

Finally, the CDib class has a single protected member function that it calls internally to load a DIB file:

void LoadBitmapFile(const char* fileName);

You'll never call this member function directly.

Programming the CDib Class

The code that defines the CDib class is found in the CDIB.CPP file, which is shown in Listing 17.8. In this file, each of the functions in the CDib class are defined.

Listing 17.8 CDIB.CPP The Implementation of the CDib Class

///////////////////////////////////////////////////////////
// CDIB.CPP: Implementation file for the DIB class.
///////////////////////////////////////////////////////////
#include "stdafx.h"
#include "cdib.h"
#include "windowsx.h"
///////////////////////////////////////////////////////////
// CDib::CDib()
///////////////////////////////////////////////////////////
CDib::CDib(const char* fileName)
{
    // Load the bitmap and initialize
    // the class's data members.
    LoadBitmapFile(fileName);
}
///////////////////////////////////////////////////////////
// CDib::~CDib()
///////////////////////////////////////////////////////////
CDib::~CDib()
{
    // Free the memory assigned to the bitmap.
    GlobalFreePtr(m_pBmInfo);
}
///////////////////////////////////////////////////////////
// CDib::LoadBitmapFile()
//
// This function loads a DIB from disk into memory. It
// also initializes the various class data members.
///////////////////////////////////////////////////////////
void CDib::LoadBitmapFile
    (const char* fileName)
{
    // Construct and open a file object.
    CFile file(fileName, CFile::modeRead);
    
    // Read the bitmap's file header into memory.
    BITMAPFILEHEADER bmFileHeader;
    file.Read((void*)&bmFileHeader, sizeof(bmFileHeader));
    // Check whether the file is really a bitmap.
    if (bmFileHeader.bfType != 0x4d42)
    {
        AfxMessageBox("Not a bitmap file");
        m_pBmFileHeader = 0;
        m_pBmInfo = 0;
        m_pBmInfoHeader = 0;
        m_pRGBTable = 0;
        m_pDibBits = 0;
        m_numColors = 0;
    }    
    // If the file checks out okay, continue loading.
    else
    {
        // Calculate the size of the DIB, which is the
        // file size minus the size of the file header.
        DWORD fileLength = file.GetLength();    
        DWORD dibSize = fileLength - sizeof(bmFileHeader);
        // Allocate enough memory to fit the bitmap.
        BYTE* pDib =
            (BYTE*)GlobalAllocPtr(GMEM_MOVEABLE, dibSize);
        
        // Read the bitmap into memory and close the file.
        file.Read((void*)pDib, dibSize);
        file.Close();
        // Initialize pointers to the bitmap's BITMAPINFO
        // and BITMAPINFOHEADER structures.
        m_pBmInfo = (LPBITMAPINFO) pDib;
        m_pBmInfoHeader = (LPBITMAPINFOHEADER) pDib;
        
        // Calculate a pointer to the bitmap's color table.
        m_pRGBTable =
            (RGBQUAD*)(pDib + m_pBmInfoHeader->biSize);
        // Get the number of colors in the bitmap.
        int m_numColors = GetDibNumColors();
        // Calculate the bitmap image's size.
        m_pBmInfoHeader->biSizeImage =
            GetDibSizeImage();
        
        // Make sure the biClrUsed field
        // is initialized properly.
        if (m_pBmInfoHeader->biClrUsed == 0)
            m_pBmInfoHeader->biClrUsed = m_numColors;
        // Calculate a pointer to the bitmap's actual data.
        DWORD clrTableSize = m_numColors * sizeof(RGBQUAD);
        m_pDibBits =
            pDib + m_pBmInfoHeader->biSize + clrTableSize;
    }
}
///////////////////////////////////////////////////////////
// CDib::GetDibSizeImage()
//
// This function calculates and returns the size of the
// bitmap's image in bytes.
///////////////////////////////////////////////////////////
DWORD CDib::GetDibSizeImage()
{
    // If the bitmap's biSizeImage field contains
    // invalid information, calculate the correct size.
    if (m_pBmInfoHeader->biSizeImage == 0)
    {
        // Get the width in bytes of a single row.
        DWORD byteWidth = (DWORD) GetDibWidth();
        
        // Get the height of the bitmap.
        DWORD height = (DWORD) GetDibHeight();
        
        // Multiply the byte width by the number of rows.
        DWORD imageSize = byteWidth * height;
        
        return imageSize;
    }
    // Otherwise, just return the size stored in
    // the BITMAPINFOHEADER structure.
    else
        return m_pBmInfoHeader->biSizeImage;
}
///////////////////////////////////////////////////////////
// CDib::GetDibWidth()
//
// This function returns the width in bytes of a single
// row in the bitmap.
///////////////////////////////////////////////////////////
UINT CDib::GetDibWidth()
{
    return (UINT) m_pBmInfoHeader->biWidth;
}
        
///////////////////////////////////////////////////////////
// CDib::GetDibHeight()
//
// This function returns the bitmap's height in pixels.
///////////////////////////////////////////////////////////
UINT CDib::GetDibHeight()
{
    return (UINT) m_pBmInfoHeader->biHeight;
}
        
///////////////////////////////////////////////////////////
// CDib::GetDibNumColors()
//
// This function returns the number of colors in the
// bitmap.
///////////////////////////////////////////////////////////
UINT CDib::GetDibNumColors()
{
    if ((m_pBmInfoHeader->biClrUsed == 0) &&
          (m_pBmInfoHeader->biBitCount < 9))
        return (1 << m_pBmInfoHeader->biBitCount);
    else
        return (int) m_pBmInfoHeader->biClrUsed;
}
    
///////////////////////////////////////////////////////////
// CDib::GetDibInfoHeaderPtr()
//
// This function returns a pointer to the bitmap's
// BITMAPINFOHEADER structure.
///////////////////////////////////////////////////////////
LPBITMAPINFOHEADER CDib::GetDibInfoHeaderPtr()
{
    return m_pBmInfoHeader;
}
///////////////////////////////////////////////////////////
// CDib::GetDibInfoPtr()
//
// This function returns a pointer to the bitmap's
// BITMAPINFO structure.
///////////////////////////////////////////////////////////
LPBITMAPINFO CDib::GetDibInfoPtr()
{
    return m_pBmInfo;
}
///////////////////////////////////////////////////////////
// CDib::GetDibRGBTablePtr()
//
// This function returns a pointer to the bitmap's
// color table.
///////////////////////////////////////////////////////////
LPRGBQUAD CDib::GetDibRGBTablePtr()
{
    return m_pRGBTable;
}
///////////////////////////////////////////////////////////
// CDib::GetDibBitsPtr()
//
// This function returns a pointer to the bitmap's
// actual image data.
///////////////////////////////////////////////////////////
BYTE* CDib::GetDibBitsPtr()
{
    return m_pDibBits;
}

Loading a DIB into Memory

Look at the class's constructor, which is shown in Listing 17.9.

Listing 17.9 LST17_09.CPP The CDib Class's Constructor

CDib::CDib(const char* fileName)
{
    // Load the bitmap and initialize
    // the class's data members.
    LoadBitmapFile(fileName);
}

You create a CDib object by calling the CDib class's constructor with the file name of the DIB that you want to load. The constructor passes this file name to the protected member function, LoadBitmapFile(), which actually loads the bitmap. LoadBitmapFile()'s code is fairly complex. In fact, it makes up the bulk of the CDib class's code. The other CDib member functions generally return values calculated by LoadBitmapFile().

First, LoadBitmapFile() constructs an MFC CFile object:

CFile file(fileName, CFile::modeRead);

This line of code not only constructs a CFile object named file but also opens the file (whose name is passed in the fileName argument) in the read-only mode.

Next, the function declares a BITMAPFILEHEADER structure and reads the DIB's file header into the structure:

BITMAPFILEHEADER bmFileHeader;
file.Read((void*)&bmFileHeader, sizeof(bmFileHeader));

The CFile object's Read() member function requires as its arguments a pointer to the buffer in which to store the data and the size of the buffer. In this case, the buffer is the BITMAPFILEHEADER structure.

After the preceding lines, the DIB's file header is stored in the bmFileHeader structure. The first task is to check whether the opened file is actually a bitmap. The function does this (as shown in Listing 17.10) by checking the bfType member for the value 0x4D42, which is the ASCII code for the letters BM.

Listing 17.10 LST17_10.CPP Checking that the File is a Bitmap

if (bmFileHeader.bfType != 0x4d42)
{
    AfxMessageBox("Not a bitmap file");
    m_pBmFileHeader = 0;
    m_pBmInfo = 0;
    m_pBmInfoHeader = 0;
    m_pRGBTable = 0;
    m_pDibBits = 0;
    m_numColors = 0;
}

If the bfType structure member does not contain the correct value, the function sets all the class's data members to 0 and exits. Otherwise, LoadBitmapFile() calls the CFile object's GetLength() member function to get the size of the opened file:

DWORD fileLength = file.GetLength();

The function then calculates the size of the DIB by subtracting the size of the file header from the size of the file:

DWORD dibSize = fileLength - sizeof(bmFileHeader);

The program uses the resulting value, dibSize, to allocate enough memory in which to store the DIB:

BYTE* pDib =
    (BYTE*)GlobalAllocPtr(GMEM_MOVEABLE, dibSize);

GlobalAllocPtr() is a Windows function that allocates memory and returns a pointer to that memory. It requires two arguments: a flag indicating how the memory should be allocated and the size of the memory block to be allocated. Please refer to your Windows programming manual for more information on GlobalAllocPtr().

After allocating memory, the function calls the CFile object's Read() member function to read the DIB into memory and then closes the file by calling the Close() member function:

file.Read((void*)pDib, dibSize);
file.Close();

At this point, the DIB is stored in memory, and LoadBitmapFile() can now calculate the values that the class needs. The addresses of the DIB's BITMAPINFO and BITMAPINFOHEADER structures are identical, being the same address as the buffer in which the DIB is stored:

m_pBmInfo = (LPBITMAPINFO) pDib;
m_pBmInfoHeader = (LPBITMAPINFOHEADER) pDib;

The function then calculates a pointer to the color table by adding the size of the BITMAPINFOHEADER structure to the address of the DIB in memory:

m_pRGBTable =
    (RGBQUAD*)(pDib + m_pBmInfoHeader->biSize);

When doing this type of pointer math, be careful that you're using the correct data types. For example, because pDib is a pointer to BYTE, the number of bytes stored in biSize is added to this pointer. However, if pDib were defined as a pointer to a BITMAPINFO structure, the value biSize*sizeof(BITMAPINFO) would be added to pDib. If you don't understand this, you should review how pointer math works.

Next, the function initializes the m_numColors data member, by calling the member function GetDibNumColors():

int m_numColors = GetDibNumColors();

You'll see how GetDibNumColors() works a little later in this chapter, in the section "Other CDib Member Functions."

The biSizeImage data member of the DIB's BITMAPINFOHEADER structure is filled in by calling yet another CDib member function, GetDibSizeImage():

m_pBmInfoHeader->biSizeImage =
    GetDibSizeImage();

Next, if the BITMAPINFOHEADER's biClrUsed member is 0, LoadBitmapFile() initializes it to the correct value, which is now stored in the m_numColors data member:

if (m_pBmInfoHeader->biClrUsed == 0)
    m_pBmInfoHeader->biClrUsed = m_numColors;

Finally, LoadBitmapFile() calculates the address of the DIB's image by adding the size of the BITMAPINFOHEADER structure and the size of the color table to the pDib pointer, which contains the address of the DIB in memory:

DWORD clrTableSize = m_numColors * sizeof(RGBQUAD);
m_pDibBits =
    pDib + m_pBmInfoHeader->biSize + clrTableSize;

Other CDib Member Functions

In the previous section, some CDib data members were initialized by calling CDib member functions. One of those member functions was GetDibSizeImage(), which calculates the size of the DIB in bytes. This function first checks whether the biSizeImage member of the BITMAPINFOHEADER structure already contains a value other than 0:

if (m_pBmInfoHeader->biSizeImage == 0)

If it does, the function simply returns the value stored in biSizeImage:

return m_pBmInfoHeader->biSizeImage;

Otherwise, the function must calculate the image size using the information it already has.

First, it gets the DIB's width and height in pixels:

DWORD byteWidth = (DWORD) GetDibWidth();
DWORD height = (DWORD) GetDibHeight();

Then it multiplies the width by the height to get the size of the entire bitmap image:

DWORD imageSize = byteWidth * height;

Notice that all these calculations are done with DWORDs to avoid the truncation errors that might occur with regular integers. DIBs can be big!

Another member function that must do some calculations is GetDibNumColors(), shown in Listing 17.11.

Listing 17.11 LST17_11.CPP Getting the Number of Colors

UINT CDib::GetDibNumColors()
{
    if ((m_pBmInfoHeader->biClrUsed == 0) &&
          (m_pBmInfoHeader->biBitCount < 9))
        return (1 << m_pBmInfoHeader->biBitCount);
    else
        return (int) m_pBmInfoHeader->biClrUsed;
}

This function first checks the BITMAPINFOHEADER structure's biClrUsed member for a value other than 0. If it finds a value other than 0, it simply returns that value from the function. Otherwise, it calculates the number of colors by shifting the value 1 to the left, using the biBitCount member as the shift count. This results in a value of 2 for 1-bit (monochrome) DIBs, 16 for 4-bit DIBs, and 256 for 8-bit DIBs.

The remaining CDib member functions just return the values of the class's data members. For example, the GetDibRGBTablePtr() function (Listing 17.12) returns a pointer to the DIB's color table, a value that has already been stored in the data member m_pRGBTable.

Listing 17.12 LST17_12.CPP Getting a Pointer to the Color Table

LPRGBQUAD CDib::GetDibRGBTablePtr()
{
    return m_pRGBTable;
}

Creating ShowDib, Version 1

Now that you have a class for handling DIBs, it's time to put the class to work. In this section, you'll use Visual C++'s AppWizard to create an application that can load and display DIBs.

Creating the Basic Application

In this section, you'll create the ShowDib application, which displays DIBs in its main window. Just follow the steps that come next to create the first version of the application.

ON THE WEB

http://www.quecorp.com/semfc The complete source code and executable file for this part of the ShowDib application can be found in the CHAP17\ShowDib, Part 1 directory at this book's Web site.

1. Start Visual C++, and select the File menu's New command. The New property sheet appears, as shown in Figure 17.2.


FIG. 17.2

The New property sheet enables you to start many types of new projects.

2. In the Project Name edit box, type the project name ShowDib, and in the Project Path box, select the directory in which you want to store the project. Finally, make sure that the selected project type in the left-hand pane is MFC AppWizard (exe).

3. Select the OK button. The Step 1 dialog box appears. Choose the Single Document option, as shown in Figure 17.3.


FIG. 17.3

The Step 1 dialog box determines the basic application type.

4. Click the Next button three times. You see Step 4 of 6 dialog box. Turn off all of the features except Use 3D Controls, as shown in Figure 17.4.


FIG. 17.4

The Step 4 of 6 dialog box determines what standard features the application will support.

5. Click the Finish button. When the New Project Information dialog box appears (see Figure 17.5), click the OK button to close the dialog box and to generate the files needed for the project.


FIG. 17.5

The New Project Information dialog box displays the project selections you've made.

6. Select the Build, Build command (or press F7) to compile and link the new ShowDib application.

7. To run the program after it's compiled, select the Build, Execute command or press Ctrl+F5 on your keyboard. The window shown in Figure 17.6 appears.


FIG. 17.6

The basic ShowDib application looks like this.

Modifying ShowDib's Resources

Now that you have the basic ShowDib application created, you can use Visual C++'s tools to modify the application to fit your needs. The following steps describe how to modify the ShowDib application's resources:

ON THE WEB

http://www.quecorp.com/semfc The complete source code and executable file for this part of the ShowDib application can be found in the CHAP17\ShowDib, Part 2 directory at this book's Web site.

1. Click the ResourceView tab. Visual C++ displays the ResourceView window, as shown in Figure 17.7.


FIG. 17.7

The ResourceView window displays the project's resources.

2. In the ResourceView window, double-click ShowDib Resources, double-click the Menu resource, and then double-click the IDR_MAINFRAME menu ID. Visual C++'s menu editor appears, as shown in Figure 17.8.


FIG. 17.8

The menu editor enables you to change the default menus.

3. Click on ShowDib's Edit menu (not Visual C++'s Edit menu), and then press your keyboard's Delete key to delete the E dit menu. When you do, a dialog box asks for verification of the delete command. Click the OK button.

4. Click on ShowDib's File menu (not Visual C++'s File menu). The File menu appears, showing its various commands. Using your mouse and your keyboard's Delete key, delete all the File menu's commands except Open and Exit. When you're done, ShowDib's menus should look like Figure 17.9.


FIG. 17.9

Here's what ShowDib's new menus should look like.

5. Close the menu editor, and then double-click the Accelerator resource in the browser window. Double-click the IDR_MAINFRAME accelerator ID to bring up the accelerator editor, as shown in Figure 17.10.


FIG. 17.10

The accelerator editor enables you to modify the default accelerators.

6. Using your keyboard's arrow and Delete keys, delete all accelerators except ID_FILE_OPEN, as shown in Figure 17.11.


FIG. 17.11

This is the accelerator editor after you delete unneeded accelerators.

7. Close the accelerator editor and then double-click the Dialog resource in the browser window. Double-click the IDD_ABOUTBOX dialog box ID to bring up the dialog box editor.

8. Modify the dialog box by adding the static string "by Macmillan Computer Publishing", as shown in Figure 17.12.


FIG. 17.12

Modify the About dialog box so that it looks like this.

9. Close the dialog box editor. Then select the Build, Build command to compile the modified application.

When you run the newly compiled application, you see the application's main window. If you select the File menu's Open command, the File Open dialog box appears, from which you can select a file. When you select a file, its name appears in the window in place of the "Untitled" string, as shown in Figure 17.13. If you select the Help menu's About ShowDib command, you see the About dialog box (also shown in Figure 17.13), which you modified in the dialog box editor.


FIG. 17.13

Here is ShowDib after you open MAINFRM.CPP and display the About dialog box.

Adding Code to ShowDib, Version 1

After you've edited the application's resources, ShowDib's user interface is complete. However, the program is still incapable of loading and displaying DIBs. Even when you use the Open command to select a DIB file, nothing happens (except that the DIB's file name appears in the window's title bar). The next step, then, is to add the code needed to make ShowDib do what you want it to do. The following steps describe how to add this code to the application.

ON THE WEB

http://www.quecorp.com/semfc The complete source code and executable file for this part of the ShowDib application can be found in the CHAP17\ShowDib, Part 3 directory at this book's Web site.

1. Load the MainFrm.cpp file, and then add the following lines to the PreCreateWindow() function, right before the function's return statement:

    cs.cx = 640;
    cs.cy = 480;

2. Copy the cdib.cpp and cdib.h files into ShowDib's project directory. (You'll find cdib.cpp and cdib.h at this book's Web site, in the CHAP17\ShowDib directory.)

3. Select the Project, Add to Project, Files command. The Insert Files Into Project dialog box appears, as shown in Figure 17.14. In the file window, double-click the cdib.cpp file to add it to the project.


FIG. 17.14

Use the Insert Files into Project dialog box to add cdib.cpp to the ShowDib project.

4. Load the ShowDibDoc.h file, and then add the following line to the top of the source code, right before the declaration of the CShowDibDoc class:

#include "cdib.h"

This line includes the CDib class's declaration into the ShowDib application's document header file so that you can use the CDib class in the document class.

5. In ShowDibDoc.h, add the following line to the Attributes section of the CShowDibDoc class's declaration, right after the public keyword:

CDib* m_pDib;

This line declares a pointer to a CDib object as a data member of the CShowDibDoc class. The CDib object represents whatever DIB the application is currently displaying.

6. Load the ShowDibDoc.cpp file, and then add the following line to the class's constructor, after the // TODO: add one-time construction code here comment:

m_pDib = 0;

This line ensures that the CDib pointer starts off as NULL.

7. Select the View, ClassWizard command. The MFC ClassWizard dialog box appears.

8. In the Class Name box, select the CShowDibDoc class. Then click ID_FILE_OPEN in the Object IDs box, and double-click COMMAND in the Messages box. The Add Member Function dialog box appears.

9. Click the OK button to add the OnFileOpen() function to the class. Click the Edit Code button to open the source code window to this new function.

10. Add the code shown in Listing 17.13 to the new OnFileOpen() function, after the // TODO: Add your command handler code here comment.

Listing 17.13 LST17_13.CPP Code for the OnFileOpen() Function

    // Construct an Open dialog-box object.
    CFileDialog fileDialog(TRUE, "bmp", "*.bmp");
    
    // Display the Open dialog box.
    int result = fileDialog.DoModal();
    
    // If the user exited the dialog box
    // via the OK button...
    if (result == IDOK)
    {
        // Get the selected path and file name.
        CString string = fileDialog.GetPathName();
        
        // Construct a new CDib object.
        m_pDib = new CDib(string);
        
        // Check that the CDib object was created okay.
        // If there was an error, the pBmInfo pointer
        // will be 0.
        LPBITMAPINFO pBmInfo = m_pDib->GetDibInfoPtr();
        
        // If the CDib object was not constructed
        // properly, delete it.
        if (!pBmInfo)
            DeleteContents();
            
        // Otherwise, set the document's title to
        // the DIB's path and file name.
        else
            SetTitle(string);
    }
    // Notify the view object that it has new data to display.
    UpdateAllViews(0);

The OnFileOpen() function will now respond to the File menu's Open command, not only by enabling the user to select a file, but also by creating a new CDib object from the selected DIB. This function is discussed in detail later in this chapter, in the section "Examining the OnFileOpen() Function."

11. Select the View, ClassWizard command. When the MFC ClassWizard dialog box appears, select the CShowDibDoc class in the Class Name box; click CShowDibDoc in the Object IDs box, and double-click DeleteContents in the Messages box to add the virtual DeleteContents() function.

12. Click the Edit Code button to jump to the DeleteContents() function and then add the code shown in Listing 17.14 to the function, after the // TODO: Add your specialized code here and/or call the base class comment.

Listing 17.14 LST17_14.CPP Code for the DeleteContents() Function

    // If there's a valid CDib object, delete it.
    if (m_pDib)
    {
        delete m_pDib;
        m_pDib = 0;
    }

The DeleteContents() function overrides the CDocument base class's DeleteContents() function. MFC calls this function whenever the application needs to delete the current document's data. Because DeleteContents() is also called when a new document is created (to ensure that the new document is empty), you must check that m_pDib is not 0 before you delete it.

13. Open the ShowDibView.cpp file, and then add the code shown in Listing 17.15 to the OnDraw() function, right after the // TODO: add draw code for native data here comment.

Listing 17.15 LST17_15.CPP Code for the OnDraw() Function

    // Get a pointer to the current CDib object.
    CDib* pDib = pDoc->m_pDib;
    // If the CDib object is valid, display it.
    if (pDib)
    {
        // Get a pointer to the DIB's image data.
        BYTE* pBmBits = pDib->GetDibBitsPtr();
    
        // Get a pointer to the DIB's info structure.
        LPBITMAPINFO pBmInfo = pDib->GetDibInfoPtr();
    
        // Get the DIB's width and height.
        UINT bmWidth = pDib->GetDibWidth();
        UINT bmHeight = pDib->GetDibHeight();
        // Display the DIB.
        StretchDIBits(pDC->m_hDC,
            10, 10, bmWidth, bmHeight,
            0, 0, bmWidth, bmHeight,
            pBmBits, pBmInfo, DIB_RGB_COLORS, SRCCOPY);
    }

MFC calls the OnDraw() function whenever the application's main window must be redrawn. The code in Listing 17.15, which is described in detail later in the section "Examining the OnDraw() Function," displays the currently loaded DIB.

14. Select the Build, Build command to compile and link the new version of the ShowDib application.

Version 1 of ShowDib is now complete. Before you run the application, however, read the following sections, which explain how the OnFileOpen() and OnDraw() functions work.

Examining the OnFileOpen() Function

As you put together the first version of the ShowDib application, you encountered a couple of functions that needed further explanation. One of those functions isOnFileOpen().

Normally, an application created by AppWizard responds to the File menu's Open command by displaying an Open dialog box and then calling the document class's Serialize() function, which loads the requested data. You saw this happen when you ran the basic version of ShowDib.

Unfortunately, because ShowDib requires some special file handling, it doesn't take advantage of the Serialize() function. Instead, the program uses the OnFileOpen() function to load a DIB. Because you "attached" this function to the File menu's Open command, MFC calls it whenever a file needs to be opened.

The function's first task is to display the Open dialog box so that the user can choose the file that he or she wants to view:

CFileDialog fileDialog(TRUE, "bmp", "*.bmp");

MFC encapsulates the Open dialog box in the CFileDialog class. To construct a CFileDialog object, call its constructor with three arguments: the Boolean value TRUE, a string containing the default file extension, and a string containing the initial file name to appear.

After you've constructed a new CFileDialog object, display the dialog box by calling the class's DoModal() member function:

int result = fileDialog.DoModal();

The function DoModal() returns the ID of the button used to exit the dialog box. If that ID equals ID_OK, the user has selected a valid file name. In that case, OnFileOpen() gets the selected file name by calling the file dialog's GetPathName() member function:

CString string = fileDialog.GetPathName();

The program then uses the returned file name to construct a new CDib object, which, as you already know, also loads the DIB into memory. If CDib's constructor cannot load the DIB, it sets all of the CDib object's data members to 0. So to check whether the DIB was loaded correctly, OnFileOpen() calls the CDib object's GetDibInfoPtr() function to get a pointer to the DIB's BITMAPINFO structure:

LPBITMAPINFO pBmInfo = m_pDib->GetDibInfoPtr();

If the returned pointer is 0, the DIB was not loaded, and the program calls DeleteContents() to delete the unusable CDib object:

if (!pBmInfo)
    DeleteContents();

Otherwise, the program calls the document object's SetTitle() member function to set the window's title to the DIB's file name:

SetTitle(string);

SetTitle()'s single argument is a string containing the window's new title; in this case, the string returned from the dialog object's GetPathName() function.

Because the window now has a new DIB to display, OnFileOpen() calls the document object's UpdateAllViews() member function, to tell the view object that it must redraw the window:

UpdateAllViews(0);

This function call's single parameter is a pointer to the view object that modified the document. Because, in this case, the document itself changed its contents, this argument should be 0, which indicates that all views should be updated.

Examining the OnDraw() Function

Another function that you need to look at in more detail is the view object's OnDraw() function, which is responsible for updating the main window's display. In the ShowDib application, OnDraw() must display the currently selected DIB.

Because the CDib object is part of the document class, OnDraw() first gets a pointer to the CDib object, accessing the document object through the pDoc pointer supplied by OnDraw(). (The code that supplies this pointer was generated by AppWizard.) As you can see, pDoc points to the application's document object:

CDib* pDib = pDoc->m_pDib;

If the application has loaded a DIB (pDib is not 0), OnDraw() gets pointers to the DIB's image and BITMAPINFO structure:

BYTE* pBmBits = pDib->GetDibBitsPtr();
LPBITMAPINFO pBmInfo = pDib->GetDibInfoPtr();

The function also gets the DIB's width and height:

UINT bmWidth = pDib->GetDibWidth();
UINT bmHeight = pDib->GetDibHeight();

The program then displays the DIB by calling the Windows function StretchDIBits():

StretchDIBits(pDC->m_hDC, 10, 10, bmWidth, bmHeight,
    0, 0, bmWidth, bmHeight, pBmBits, pBmInfo,
    DIB_RGB_COLORS, SRCCOPY);

This function requires a whopping 13 arguments. The first argument is a handle to the destination device context (that is, the device context on which the DIB should be displayed). Because OnDraw() supplies a pointer to the destination device context (DC), you must use this pointer to access the DC's handle, which is stored in the DC class's m_hDC data member.

To display data, a Windows application must obtain a device context. In review, a device context is little more than a collection of attributes that describe how data should be displayed in a window. These attributes include pen colors and sizes, brush colors and sizes, fill colors, and other important graphical information.

In a normal Windows program, you'd be responsible for creating and managing a device context whenever you wanted to display something on the screen. However, Microsoft Foundation Classes takes care of much of the dirty work for you. In functions like OnDraw(), MFC supplies the device context as one of the function's parameters.

The next four arguments are the X coordinate, Y coordinate, width, and height of the destination rectangle. This rectangle is the area of the destination DC into which you want to copy the DIB. The next four arguments define the source rectangle. In this case, because you want to display the entire DIB, this rectangle has X,Y coordinates of 0,0 and is the same width and height as the DIB.

The ninth and tenth arguments are pointers to the DIB's image data and BITMAPINFO structure. The eleventh argument is a flag that describes the colors used with the DIB. The flag DIB_RGB_COLORS means that the DIB's color table contains actual RGB values, whereas the DIB_PAL_COLORS flag indicates that the color table contains 16-bit indexes into the logical palette. (You learn about logical palettes later in this chapter, in the section "Creating ShowDib, Version 2.") Finally, the last argument is a flag that tells the function how to combine the source and destination rectangles. In this case, SRCCOPY means that the source rectangle should overwrite the destination rectangle.

The StretchDIBits() function can automatically stretch or shrink the size of an image when the size of the destination surface is larger or smaller than the bitmap.

Now that you know how the program works, it's time to try it out.

Running Version 1 of ShowDib

The ShowDib application can now load and display a DIB. To use the program, select the File menu's Open command and select a DIB file. ShowDib loads the selected DIB and displays it in its main window. Figure 17.15 shows the results when you select the DOGGIE2.BMP file, which came from Microsoft's WinG Developer's Kit and is also found at this book's Web site, in the CHAP17\ShowDib directory.


FIG. 17.15

ShowDib displays a bitmap.

When using this first version of ShowDib, you'll probably notice that, although the program can load and display a DIB, the resulting display sometimes looks pretty weird. This is because, currently, ShowDib ignores the DIB's color table. To display a DIB correctly, a program must create a logical palette from the DIB's color table and then select that palette before actually updating the display window. In the next section, then, you'll add palette-handling features to ShowDib so that it can display DIBs using the correct colors.

Creating ShowDib, Version 2

Currently, ShowDib cannot display a DIB properly because it does not set Windows' colors to those that were used to create the DIB. The colors that you need are found in the DIB's color palette. To properly display the DIB, you must create a logical palette from the DIB's color table and then realize that palette before displaying the DIB. When you realize a palette, you're telling Windows to change its colors to those that you've defined in the logical palette.

But what exactly is a logical palette? Every application that must define colors has its own logical palette. When the user switches between applications, the new application gives its logical palette to Windows so that Windows can set its colors properly. A logical palette, then, is a set of colors that's associated with a particular application. The system palette, on the other hand, contains the colors that are currently displayed on the screen. When a user switches applications, the new application's logical palette is mapped into Windows' system palette. You can have many logical palettes, but there is never more than one system palette.

The process by which Windows maps a logical palette into the system palette, however, is complex and is a cause for much confusion. That's why you see so little about palettes in most Windows programming books. But, as you might have guessed, if you are going to display DIBs, you must know how Windows' palettes work.

Mapping Between a Logical and a System Palette

When an application realizes a logical palette, Windows must map the application's palette to the system palette. Because there is only one system palette and because that system palette must be shared by every currently running application mapping a logical palette is not as simple as copying the logical palette to the system palette. Although taking this easy way out would work well for the active application, any applications running in the background might end up with bizarre displays. When Windows maps palettes, it must do its best to keep every application's display looking as good as possible. Windows follows these steps to set an application's requested colors:

1. Exactly match colors in the logical palette to existing colors in the system palette. This step requires that no colors in the system palette be modified, which, of course, also ensures that other applications retain their own colors.

2. Use empty system palette entries to create colors for those logical palette entries that could not be matched in Step 1. By using empty system palette entries (entries not being used by any other application), Windows again ensures that colors used by other applications remain unaffected.

3. Match remaining entries in the logical palette to the closest color in the system palette. Obviously, with many applications vying for colors, the system palette might become filled. When this happens, Windows can no longer plug colors from a logical palette into empty system palette entries. At this point, a background application's display suffers some degradation, depending on how closely the selected colors match the application's palette.

Although Windows follows the preceding rules when mapping colors, the order in which each application is handled is obviously important. For this reason, Windows maps the active application's colors first, after which it maps background applications' colors starting with the most recently active and working its way back to the least recently active.

Need an example? For the sake of simplicity, suppose that the system palette can display only ten colors. Suppose also that you have two applications running App1 and App2 each of which has realized a six-color palette. Before any mapping occurs, the palettes look like Figure 17.16.


FIG. 17.16

The unmapped palettes look like this.

App1 is the active application, so Windows maps App1's colors first. Windows first maps App1's black to the system palette's black because they are an exact match. Then Windows fills the next five empty system palette entries with the five remaining colors in App1's logical palette. After App1's logical palette has been fully realized, the palettes look like Figure 17.17.


FIG. 17.17

Here are the palettes after mapping App1's palette.

Because App2 is running in the background, Windows maps App2's colors after App1's. Again, Windows maps black to black because it's an exact match. Next, Windows fills the system palette's remaining three empty entries with the next three colors in App2's palette. Now the system palette is filled. Unfortunately, App2 still has two colors pink and blue that need to be mapped. Windows now has no choice but to map these two remaining colors to the closest colors in the system palette. So Windows matches pink to red and blue to aqua. The palettes now look like figure 17.18.


FIG. 17.18

Here are the palettes after mapping App2's palette.

After both applications' palettes have been mapped, App1 has exactly the colors it needs, whereas App2 has suffered a slight degradation in its display. But because App2 is currently running in the background, its display isn't as important as App1's. When App2 becomes active, it will get its chance to create the display that it needs.

Now that you know how Windows handles color palettes, it's time to add palette-handling capabilities to the ShowDib application. You'll do this in the next section.

The Windows system palette reserves the first ten and last ten colors of a 256-color palette for its own use. It uses these colors to draw window borders, buttons, menus, and other Windows objects. Normally, you should not attempt to change these 20 reserved colors.

Adding Code to ShowDib, Version 2

You're now ready to add palette handling to the ShowDib application. When you're finished with this section, ShowDib will display DIBs correctly, using the DIB's color table to create a logical palette for the application. To add these capabilities, perform the following steps:

ON THE WEB

http://www.quecorp.com/semfc The complete source code and executable file for this part of the ShowDib application can be found in the CHAP17\ShowDib, Part 4 directory at this book's Web site.

1. Add the following function declaration to the CShowDibView class, found in the file ShowDibView.h. Place the code in the class's Implementation section, right before the // Generated message map functions comment and right after the protected keyword.

HPALETTE CreateDibPalette(CDib* pDib);

The preceding line declares the function CreateDibPalette() which returns a handle to a logical palette as a protected member function of the CShowDibView class.

2. Add the function shown in Listing 17.16 to the view's implementation file. Place the code at the end of the ShowDibView.cpp file, right after the // CShowDibView message handlers comment.

Listing 17.16 LST17_16.CPP The CreateDibPalette() Function

HPALETTE CShowDibView::CreateDibPalette(CDib* pDib)
{
    // Get a pointer to the DIB's color table.
    LPRGBQUAD pColorTable = pDib->GetDibRGBTablePtr();
    
    // Get the number of colors in the DIB.
    UINT numColors = pDib->GetDibNumColors();
    struct
    {
        WORD Version;
        WORD NumberOfEntries;
        PALETTEENTRY aEntries[256];
    } logicalPalette = { 0x300, 256 };
    
    // Fill the palette structure with the DIB's colors.
    for(UINT i=0; i<numColors; ++i)
    {
        logicalPalette.aEntries[i].peRed =
            pColorTable[i].rgbRed;
        logicalPalette.aEntries[i].peGreen =
            pColorTable[i].rgbGreen;
        logicalPalette.aEntries[i].peBlue =
            pColorTable[i].rgbBlue;
        logicalPalette.aEntries[i].peFlags = 0;
    }
    // Create the palette object.
    HPALETTE hPalette =
        CreatePalette((LPLOGPALETTE)&logicalPalette);
    return hPalette;
}

This function creates the application's logical palette based on the DIB's color table. You'll examine this function in greater detail later in this chapter, in the section "Examining the CreateDibPalette() Function."

3. Add the code shown in Listing 17.17 to the view's OnDraw() function. Place the code after the if statement's opening brace.

Listing 17.17 LST17_17.CPP Code for the OnDraw() Function

        // Create the DIB's palette;
        HPALETTE hPalette = CreateDibPalette(pDib);
        // Select the DIB's palette into the DC.
        HPALETTE hOldPalette =
            SelectPalette(pDC->m_hDC, hPalette, FALSE);
        // Map the DIB's palette to the system palette.
        RealizePalette(pDC->m_hDC);

The code in Listing 17.17 creates and realizes the application's logical palette before displaying the DIB. You examine this code in greater detail later in this chapter, in the section "Examining Changes to the OnDraw() Function."

4. Add the code shown in Listing 17.18 immediately after the StretchDIBits() call in OnDraw().

Listing 17.18 LST0R_18.CPP More Code for the OnDraw() Function

        // Deselect the logical palette from the DC.
        SelectPalette(pDC->m_hDC, hOldPalette, FALSE);
        // Delete the logical palette.
        DeleteObject(hPalette);

This code deletes the logical palette. Later in this chapter, in the section "Examining Changes to the OnDraw() Function," you'll see why deleting the palette is necessary.

Version 2 of ShowDib is now complete. Before you run the application, however, read the following sections, which explain how the new code that you added works.

Examining the CreateDibPalette() Function

The CreateDibPalette() function that you just added to the application's view class creates a logical palette from the currently loaded DIB's color table. In that function, the program's first task is to obtain a pointer to the CDib object's color table. It does this by calling the CDib object's GetDibRGBTablePtr() member function:

LPRGBQUAD pColorTable = pDib->GetDibRGBTablePtr();

CreateDibPalette() also needs to know how many colors there are in the DIB's color table. It gets this value by calling the CDib object's GetDibNumColors() member function:

UINT numColors = pDib->GetDibNumColors();

Next, CreateDibPalette() defines a structure, as shown in Listing 17.19, to hold the information that Windows needs to create a logical palette.

Listing 17.19 LST17_19.CPP The Logical Palette Structure

struct
{
    WORD Version;
    WORD NumberOfEntries;
    PALETTEENTRY aEntries[256];
} logicalPalette = { 0x300, 256 };

The lines in Listing 17.19 represent a LOGPALETTE structure, a data type defined by Windows and used when handling logical palettes. The first structure member is a version number, which should be the hex value 0x300. The second member is the number of colors in the palette. As you can see, CreateDibPalette() creates a 256-color logical palette. The final member is an array of PALETTEENTRY structures. Windows defines the PALETTEENTRY structure as shown in Listing 17.20.

Listing 17.20 LST17_20.CPP The PALETTEENTRY Structure

typedef struct {
   BYTE peRed;
   BYTE peGreen;
   BYTE peBlue;
   BYTE peFlags;
} PALETTEENTRY;

The peRed, peGreen, and peBlue members of this structure hold the intensities of the color's red, green, and blue components. The peFlags member specifies how Windows should handle the palette entry and can be the values PC_EXPLICIT, PC_NOCOLLAPSE, PC_RESERVED, or 0.

The PC_EXPLICIT flag indicates that the palette entry contains an index into the system palette, rather than actual color values. You store this index in the low-order word of the entry. You'll rarely need to use the PC_EXPLICIT flag.

The PC_NOCOLLAPSE flag specifies that Windows should place the color into an empty system-palette entry, rather than map it to an existing entry. If there are no empty entries in the system palette, Windows overrides the PC_NOCOLLAPSE flag and performs normal matching instead. Other applications might map their own colors to a palette entry marked PC_NOCOLLAPSE.

The PC_RESERVED flag prevents Windows from matching other applications' colors to the color entry. You'd usually use the PC_RESERVED flag with animated palettes, which frequently change color.

Set the peFlags member to 0 when you want to let Windows handle the color entry any way it sees fit.

Getting back to CreateDibPalette(), the function next copies the DIB's color table into the logicalPalette structure and sets the peFlags member of each entry to 0, as shown in Listing 17.21.

Listing 17.21 LST17_21.CPP Initializing the Logical Palette Structure

for(UINT i=0; i<numColors; ++i)
{
    logicalPalette.aEntries[i].peRed =
        pColorTable[i].rgbRed;
    logicalPalette.aEntries[i].peGreen =
        pColorTable[i].rgbGreen;
    logicalPalette.aEntries[i].peBlue =
        pColorTable[i].rgbBlue;
    logicalPalette.aEntries[i].peFlags = 0;
}

Finally, CreateDibPalette() creates the logical palette by calling the Windows API function CreatePalette(), after which it returns a handle to the palette:

hPalette =
    CreatePalette((LPLOGPALETTE)&logicalPalette);
return hPalette;

The Windows API function CreatePalette(), which returns a handle to a logical palette, takes as its single argument a pointer to a LOGPALETTE structure.

Examining Changes to the OnDraw() Function

In ShowDib's OnDraw() function, you just added several lines of code that you might not understand. This section describes more fully what that new code accomplishes.

The first line that you added simply calls your new function, CreateDibPalette(), to create a logical palette from the DIB's color table:

HPALETTE hPalette = CreateDibPalette(pDib);

The next line selects the newly created logical palette into the device context:

HPALETTE hOldPalette =
    SelectPalette(pDC->m_hDC, hPalette, FALSE);

The Windows API function SelectPalette() selects a palette into a DC. Its three arguments are a handle to the DC, a handle to the palette, and a Boolean value indicating whether the palette is to always be a background palette (TRUE) or whether the palette should be a foreground palette when the window is active (FALSE). You'll usually use FALSE for this argument. Note that SelectPalette() returns a handle to the old palette. You need to save this handle so that later you can remove the palette that you create.

Might OnDraw() selects the logical palette into the DC, it must realize the palette, which tells Windows to map the palette to the system palette. The next line does this by calling the Windows API function RealizePalette():

RealizePalette(pDC->m_hDC);

This function takes as its single argument the handle of the device context whose palette should be realized.

After realizing the logical palette, OnDraw() displays the DIB, after which it deletes the logical palette. However, because an application must never delete an object that's selected into a DC, OnDraw() must first remove the logical palette from the DC. It does this by selecting the old palette back into the DC:

SelectPalette(pDC->m_hDC, hOldPalette, FALSE);

Then the program can safely delete the logical palette that it created:

DeleteObject(hPalette);

Why delete a logical palette? Because if you don't delete graphical objects that you create under Windows 3.1, they continue to take up memory after your application ends. If your application causes enough of this type of memory leak, you could prevent other applications from running correctly. However, although it's always good practice to delete allocated objects, this hard-and-fast rule really applies only to 16-bit applications or 32-bit applications running under Win32s in Windows 3.1. Because 32-bit applications running in Windows 95 or Windows NT have their own address spaces, undeleted objects don't affect other applications in the system.

Running Version 2 of ShowDib

The ShowDib application can now load and display a DIB, using the correct color palette. To use the program, select the File menu's Open command and select a DIB file. ShowDib loads the selected DIB and displays it in its main window. Figure 17.19 shows the results when you select the DOGGIE2.BMP file. As you see, ShowDib's display looks much better when the program handles palettes correctly.


FIG. 17.19

Version 2 of ShowDib now displays bitmaps with correct palette handling.

Unfortunately, although ShowDib now displays DIBs properly when it's the active application, it sometimes produces less than desirable results when it's running in the background. For example, Figure 17.20 shows ShowDib running in the background while PC Paintbrush is running in the foreground with a different 256-color palette from the one that ShowDib needs to properly display the DOGGIE2.BMP DIB. Ouch!


FIG. 17.20

This figure shows version 2 of ShowDib running in the background without responding to the WM_PALETTECHANGED message.

Why isn't ShowDib correctly updating its display? Shouldn't Windows be mapping colors such that ShowDib produces a better looking display than that shown in Figure 17.20? The truth is that Windows is doing everything that it can. The problem is ShowDib, which is not responding to Windows' instructions. Specifically, ShowDib is not responding to WM_QUERYNEWPALETTE and WM_PALETTECHANGED messages. You'll learn about these messages as you create version 3 of the ShowDib application.

Creating ShowDib, Version 3

Version 2 of ShowDib enables you to load a DIB and display it correctly. But ShowDib does not yet behave correctly when it's relegated to background operation. Its display can become badly corrupted when the user activates another palette-managed application. Version 3 of ShowDib handles this problem by responding to two important Windows messages, WM_PALETTECHANGED and WM_QUERYNEWPALETTE.

Responding to Palette Messages

When the user activates an application, that application might realize its own logical palette. When this happens, the colors in the system palette might change significantly, leaving background applications looking strange. The solution to this problem is to notify background applications that the system palette has changed, which Windows does by sending a WM_PALETTECHANGED message.

When a background application receives this message, it can realize its own logical palette, which tells Windows to remap the logical palette to the system palette. Because the active application has first claim on the colors in the system palette, background applications might lose some of their display integrity even when responding to WM_PALETTECHANGED, but at least Windows will try for the best match possible between each background application's logical palette and the new system palette.

When a background application is reactivated, it can reclaim the system palette. Windows notifies the application of this opportunity by sending a WM_QUERYNEWPALETTE message. The newly activated application responds to this message by realizing its logical palette, which forces all of the logical palette's colors into the system palette. At this point, the other running applications, which are now background applications, receive WM_PALETTECHANGED messages; then they can remap their logical palettes to the system palette, which you just changed.

In the following section, you'll learn how to modify a Visual C++ program to respond to the WM_PALETTECHANGED and WM_QUERYNEWPALETTE messages.

Adding Code to ShowDib, Version 3

To complete the ShowDib application, giving it the capability to update its display properly when it's running in the background, follow the steps listed next.

ON THE WEB

http://www.quecorp.com/semfc The complete source code and executable file for this final part of the ShowDib application can be found in the CHAP17\ShowDib directory at this book's Web site.

 

1. Select the View, ClassWizard command. When the MFC ClassWizard dialog box appears, select CMainFrame in the Class Name list box.

Selecting the CMainFrame class in ClassWizard.

2. Select CMainFrame in the Object IDs list box, and then double-click the WM_PALETTECHANGED and WM_QUERYNEWPALETTE messages in the Messages list box. ClassWizard adds the OnPaletteChanged() and OnQueryNewPalette() member functions to the CMainFrame class. Click the OK button to finalize your choices.

3. Click the Edit Code button to open the MainFrm.cpp file to the new OnPaletteChanged() and OnQueryNewPalette() functions.

4. Add the code shown in Listing 17.22 to the OnPaletteChanged() function right after the // TODO: Add your message handler code here comment.

Listing 17.22 LST17_22.CPP Code for the OnPaletteChanged() Function

    // Get a pointer to the view window.
    CView* pView = GetActiveView();
    // If it isn't the view window changing
    // the palette, redraw the view window with
    // the newly mapped palette.
    if (pFocusWnd != pView)
        pView->Invalidate();

This code, which you'll examine in detail later in this chapter, in the section "Examining the OnPaletteChanged() Function," notifies the application's view that it must realize the logical palette and redraw the display.

5. Add the code shown in Listing 17.23 to the OnQueryNewPalette() function, right after the // TODO: Add your message handler code here and/or call default comment.

Listing 17.23 LST17_23.CPP Code for the OnQueryNewPalette() Function

    // Get a pointer to the view window.
    CView* pView = GetActiveView();
    // Redraw the view window with its own colors.
    pView->Invalidate();

This code, which you'll examine in detailin the section "Examining the OnQueryNewPalette() Function," , notifies the application's view that it must realize the logical palette and redraw the display.

Version 3 of ShowDib is now complete. Before you run the application, however, read the following sections, which explain how the new code that you added works.

Examining the OnPaletteChanged() Function

The OnPaletteChanged() function is called whenever Windows sends the application a WM_PALETTECHANGED message, which it does whenever another application changes the system palette. When ShowDib receives this message, it must realize its palette and redraw its display. Because the view class is responsible for updating the display, OnPaletteChanged() must first acquire a pointer to the view:

CView* pView = GetActiveView();

Next, the program must check whether it was its own view that modified the system palette. If it was, OnPaletteChanged() should ignore the WM_PALETTECHANGED message. Failure to handle the message in this way will leave your application in an infinite loop as it continually realizes its palette and then responds to the resulting WM_PALETTECHANGED message. Because the OnPaletteChanged() function receives as its single parameter a pointer to the window that changed the palette, you can compare this pointer with a pointer to your application's view:

if (pFocusWnd != pView)

If the two pointers are not equal, some other application changed the palette. In this case, OnPaletteChanged() calls the view's Invalidate() member function, which forces a call to the view's OnDraw() function:

pView->Invalidate();

In OnDraw(), the program realizes its palette and draws the currently selected DIB. By realizing its palette in response to the WM_PALETTECHANGED message, the program's logical palette is remapped to the newly changed system palette, creating the best possible display for this application as it's running in the background.

Examining the OnQueryNewPalette() Function

The OnQueryNewPalette() function is called whenever Windows sends the application a WM_QUERYNEWPALETTE message, which it does whenever the application regains the focus (becomes the top window). Just as with the WM_PALETTECHANGED message, when ShowDib receives the WM_QUERYNEWPALETTE message, it must realize its palette and redraw its display. Because the view class is responsible for updating the display, OnQueryNewPalette() must first acquire a pointer to the view:

CView* pView = GetActiveView();

OnQueryNewPalette() then calls the view's Invalidate() member function, which forces a call to the view's OnDraw() function:

pView->Invalidate();

In OnDraw(), the program realizes its palette and draws the currently selected DIB. By realizing its palette in response to the WM_QUERYNEWPALETTE message, the program's logical palette is remapped to the system palette. Because ShowDib is, at this point, the active application, it can take over the system palette and create exactly the palette that it needs.