Bitmaps

The bitmapped image, or simply bitmap, is a staple of modern computer graphics because it allows computers to store complex images in the form of 1s and 0s. In Windows, bitmaps are GDI objects that are handled at a fairly high level just like fonts, brushes, pens, and other GDI objects. You can create bitmaps with a paint program, embed them as resources in an application's EXE file, and load them with a simple function call; or you can create bitmaps on the fly by using GDI functions to draw to virtual display surfaces in memory. Once created, a bitmap can be displayed on the screen or reproduced on the printer with a few simple function calls.

Two types of bitmaps are supported in 32-bit Windows: device-dependent bitmaps (DDBs) and device-independent bitmaps (DIBs). Also supported in 32-bit Windows is a variation on the device-independent bitmap that was first introduced in Windows NT—something programmers refer to as a DIB section. DDBs are the simplest of the lot as well as the most limiting. They also happen to be the only type of bitmap that MFC thoroughly encapsulates. We'll get the fundamentals out of the way first by covering CBitmaps and DDBs, and later we'll move on to the more powerful DIBs and DIB sections. As you read, be aware that I'll often use the term bitmap interchangeably with the more specific terms DDB, DIB, and DIB section. Which type of bitmap I'm referring to (or whether I'm using the term generically) should be clear from the context of the discussion.

DDBs and the CBitmap Class

It goes without saying that before you can do anything with a bitmap, you must first create it. One way to create a bitmap is to construct a CBitmap object and call CBitmap::CreateCompatibleBitmap:

CBitmap bitmap;
bitmap.CreateCompatibleBitmap (&dc, nWidth, nHeight);

In this example, dc represents a screen device context and nWidth and nHeight are the bitmap's dimensions in pixels. The reason CreateCompatibleBitmap requires a device context pointer is that the format of the resulting DDB is closely tied to the architecture of the output device. Providing a pointer to a device context enables Windows to structure the DDB so that it's compatible with the device on which you intend to display it. The alternative is to call CBitmap::CreateBitmap or CBitmap::CreateBitmapIndirect and specify the number of color planes and number of bits per pixel per color plane, both of which are device-dependent values. These days, about the only practical use for CreateBitmap and CreateBitmapIndirect is for creating monochrome bitmaps. Monochrome bitmaps are sometimes useful even in color environments, as one of this chapter's sample programs will demonstrate.

A DDB created with CreateCompatibleBitmap initially contains random data. If you want to do something with the DDB—say, display it in a window—you'll probably want to draw something into the bitmap first. You can use GDI functions to draw into a bitmap by first creating a special type of device context known as a memory device context (DC) and then selecting the bitmap into the memory DC. In essence, a bitmap selected into a memory DC becomes the device context's display surface, just as the display surface that corresponds to a screen DC is the screen itself. The following code creates an uninitialized DDB that measures 100 pixels square. It then creates a memory DC, selects the bitmap into it, and initializes all the pixels in the bitmap to blue:

CClientDC dcScreen (this);
CBitmap bitmap;
bitmap.CreateCompatibleBitmap (&dcScreen, 100, 100);

CDC dcMem;
dcMem.CreateCompatibleDC (&dcScreen);

CBrush brush (RGB (0, 0, 255));
CBitmap* pOldBitmap = dcMem.SelectObject (&bitmap);
dcMem.FillRect (CRect (0, 0, 100, 100), &brush);
dcMem.SelectObject (pOldBitmap);

CDC::CreateCompatibleDC creates a memory DC that's compatible with the specified device context. The device context whose address you pass in is usually a screen DC, but it could just as easily be a printer DC if the image you're preparing is destined for a printer rather than the screen. Once a bitmap is selected into a memory DC, you can draw to the memory DC (and hence into the bitmap) using the same CDC member functions you use to draw to a screen or printer DC.

The big difference between drawing to a memory DC and drawing to a screen DC is that pixels drawn to a memory DC aren't displayed. To display them, you have to copy them from the memory DC to a screen DC. Drawing to a memory DC first and then transferring pixels to a screen DC can be useful for replicating the same image on the screen several times. Rather than draw the image anew each time, you can draw it once in a memory DC and then transfer the image to a screen DC as many times as you want. (Be aware, however, that many display adapters will perform better if you copy the image from the memory DC to the screen DC one time and then replicate the image already present in the screen DC as needed.) Bitmaps play an important role in the process because when a memory DC is first created it contains just one pixel you can draw to, and that pixel is a monochrome pixel. Selecting a bitmap into a memory DC gives you a larger display surface to draw on and also more colors to work with as long as the bitmap isn't monochrome.

Blitting Bitmaps to Screens and Other Devices

How do you draw a bitmap on the screen? Bitmaps can't be selected into nonmemory DCs; if you try, SelectObject will return NULL. But you can use CDC::BitBlt or CDC::StretchBlt to "blit" pixels from a memory DC to a screen DC. BitBlt transfers a block of pixels from one DC to another and preserves the block's dimensions; StretchBlt transfers a block of pixels between DCs and scales the block to the dimensions you specify. If dcMem is a memory DC that contains a 100-pixel by 100-pixel bitmap image and dcScreen is a screen DC, the statement

dcScreen.BitBlt (0, 0, 100, 100, &dcMem, 0, 0, SRCCOPY);

copies the image to the screen DC and consequently displays it on the screen. The first two parameters passed to BitBlt specify the coordinates of the image's upper left corner in the destination (screen) DC, the next two specify the width and height of the block to be transferred, the fifth is a pointer to the source (memory) DC, the sixth and seventh specify the coordinates of the upper left corner of the block of pixels in the source DC, and the eighth and final parameter specifies the type of raster operation to be used in the transfer. SRCCOPY copies the pixels unchanged from the memory DC to the screen DC.

You can shrink or expand a bitmap as it's blitted by using StretchBlt instead of BitBlt. StretchBlt's argument list looks a lot like BitBlt's, but it includes an additional pair of parameters specifying the width and height of the resized image. The following statement blits a 100-by-100 image from a memory DC to a screen DC and stretches the image to fit a 50-by-200 rectangle:

dcScreen.StretchBlt (0, 0, 50, 200, &dcMem, 0, 0, 100, 100, SRCCOPY);

By default, rows and columns of pixels are simply removed from the resultant image when the width or height in the destination DC is less than the width or height in the source DC. You can call CDC::SetStretchBltMode before calling StretchBlt to specify other stretching modes that use various methods to preserve discarded color information. Refer to the documentation on SetStretchBltMode for further details, but be advised that the most potentially useful alternative stretching mode—HALFTONE, which uses dithering to simulate colors that can't be displayed directly—works in Windows NT and Windows 2000 but not in Windows 95 and Windows 98.

You can get information about a bitmap by passing a pointer to a BITMAP structure to CBitmap::GetBitmap. BITMAP is defined as follows:

typedef struct tagBITMAP {
    LONG    bmType;
    LONG    bmWidth;
    LONG    bmHeight;
    LONG    bmWidthBytes;
    WORD    bmPlanes;
    WORD    bmBitsPixel;
    LPVOID  bmBits;
} BITMAP;

The bmType field always contains 0. bmWidth and bmHeight specify the bitmap's dimensions in pixels. bmWidthBytes specifies the length (in bytes) of each line in the bitmap and is always a multiple of 2 because rows of bits are padded to 16-bit boundaries. bmPlanes and bmBitsPixel specify the number of color planes and the number of pixels per bit in each color plane. If bm is an initialized BITMAP, you can determine the maximum number of colors the bitmap can contain by using the following statement:

int nColors = 1 << (bm.bmPlanes * bm.bmBitsPixel);

Finally, bmBits contains a NULL pointer following a call to GetBitmap if the bitmap is a DDB. If bitmap represents a CBitmap object, the statements

BITMAP bm;
bitmap.GetBitmap (&bm);

initialize bm with information about the bitmap.

The bitmap dimensions returned by GetBitmap are expressed in device units (pixels), but both BitBlt and StretchBlt use logical units. If you want to write a generic DrawBitmap function that blits a bitmap to a DC, you must anticipate the possibility that the DC might be set to a mapping mode other than MM_TEXT. The following DrawBitmap function, which is designed to be a member function of a class derived from CBitmap, works in all mapping modes. pDC points to the device context the bitmap is being blitted to; x and y specify the location of the image's upper left corner at the destination:

void CMyBitmap::DrawBitmap (CDC* pDC, int x, int y)
{
    BITMAP bm;
    GetBitmap (&bm);
    CPoint size (bm.bmWidth, bm.bmHeight);
    pDC->DPtoLP (&size);

    CPoint org (0, 0);
    pDC->DPtoLP (&org);

    CDC dcMem;
    dcMem.CreateCompatibleDC (pDC);
    CBitmap* pOldBitmap = dcMem.SelectObject (this);
    dcMem.SetMapMode (pDC->GetMapMode ());
    pDC->BitBlt (x, y, size.x, size.y, &dcMem, org.x, org.y, SRCCOPY);
    dcMem.SelectObject (pOldBitmap);
}

Because of some inadvertent skullduggery that MFC's CDC::DPtoLP function performs on CSize objects, the size variable that holds the bitmap's dimensions is a CPoint object, not a CSize object. When you pass CDC::DPtoLP the address of a CPoint object, the call goes straight through to the ::DPtoLP API function and the conversion is performed properly, even if one or more of the coordinates comes back negative. But when you pass CDC::DPtoLP the address of a CSize object, MFC performs the conversion itself and converts any negatives to positives. It might make intuitive sense that sizes shouldn't be negative, but that's exactly what BitBlt expects in mapping modes in which the y axis points upward.

Bitmap Resources

If all you want to do is display a predefined bitmap image—one created with the Visual C++ resource editor or any paint program or image editor that generates BMP files—you can add a bitmap resource to your application's RC file like this:

IDB_MYLOGO BITMAP Logo.bmp

Then you can load it like this:

CBitmap bitmap;
bitmap.LoadBitmap (IDB_MYLOGO);

In this example, IDB_MYLOGO is the bitmap's integer resource ID and Logo.bmp is the name of the file that contains the bitmap image. You can also assign a bitmap resource a string ID and load it this way:

bitmap.LoadBitmap (_T ("MyLogo"));

LoadBitmap accepts resource IDs of either type. After loading a bitmap resource, you display it the way you display any other bitmap—by selecting it into a memory DC and blitting it to a screen DC. Splash screens like the one you see when Visual C++ starts up are typically stored as bitmap resources and loaded with LoadBitmap (or its API equivalent, ::LoadBitmap) just before they're displayed.

CBitmap includes a related member function named LoadMappedBitmap that loads a bitmap resource and transforms one or more colors in the bitmap to the colors you specify. LoadMappedBitmap is a wrapper around ::CreateMappedBitmap, which was added to the API so that colors in bitmaps used to paint owner-draw buttons, toolbar buttons, and other controls could be transformed into system colors upon loading. The statement

bitmap.LoadMappedBitmap (IDB_BITMAP);

loads a bitmap resource and automatically transforms black pixels to the system color COLOR_BTNTEXT, dark gray (R=128, G=128, B=128) pixels to COLOR_BTNSHADOW, light gray (R=192, G=192, B=192) pixels to COLOR _BTNFACE, white pixels to COLOR_BTNHIGHLIGHT, dark blue (R=0, G=0, B=128) pixels to COLOR_HIGHLIGHT, and magenta (R=255, G=0, B=255) pixels to COLOR_WINDOW. The idea behind mapping magenta to COLOR_WINDOW is that you can add "transparent" pixels to a bitmap by coloring them magenta. If LoadMappedBitmap transforms magenta pixels into COLOR_WINDOW pixels and the bitmap is displayed against a COLOR_WINDOW background, the remapped pixels will be invisible against the window background.

You can perform custom color conversions by passing LoadMappedBitmap a pointer to an array of COLORMAP structures specifying the colors you want changed and the colors you want to change them to. One use for custom color mapping is for simulating transparent pixels by transforming an arbitrary background color to the background color of your choice. Later in this chapter, we'll examine a technique for drawing bitmaps with transparent pixels that works with any kind of background (even those that aren't solid) and requires no color mapping.

DIBs and DIB Sections

The problem with device-dependent bitmaps is—well, that they're device-dependent. You can manipulate the bits in a DDB directly using CBitmap::GetBitmapBits and CBitmap::SetBitmapBits, but because pixel color data is stored in a device-dependent format, it's difficult to know what to do with the data returned by GetBitmapBits (or what to pass to SetBitmapBits) unless the bitmap is monochrome. Worse, the color information encoded in a DDB is meaningful only to the device driver that displays it. If you write a DDB to disk on one PC and read it back on another, there's a very good chance that the colors won't come out the same. DDBs are fine for loading and displaying bitmap resources (although you'll get poor results if a bitmap resource contains more colors than your hardware is capable of displaying) and for drawing images in memory DCs before rendering them on an output device. But their lack of portability makes them unsuitable for just about anything else.

That's why Windows 3.0 introduced the device-independent bitmap, or DIB. The term DIB describes a device-independent format for storing bitmap data, a format that's meaningful outside the context of a display driver and even outside the framework of Windows itself. When you call ::CreateBitmap (the API equivalent of CBitmap::CreateBitmap) to create a bitmap, you get back an HBITMAP handle. When you call ::CreateDIBitmap to create a bitmap, you also get back an HBITMAP. The difference is what's inside. Pixel data passed to ::CreateBitmap is stored in device driver format, but pixel data passed to ::CreateDIBitmap is stored in DIB format. Moreover, the DIB format includes color information that enables different device drivers to interpret colors consistently. The API includes a pair of functions named ::GetDIBits and ::SetDIBits for reading and writing DIB-formatted bits. It also includes functions for rendering raw DIB data stored in a buffer owned by the application to an output device. Windows BMP files store bitmaps in DIB format, so it's relatively easy to write a function that uses ::CreateDIBitmap to convert the contents of a BMP file into a GDI bitmap object.

DIB sections are similar to DIBs and were created to solve a performance problem involving the ::StretchDIBits function in Windows NT. Some graphics programs allocate a buffer to hold DIB bits and then render those bits directly to the screen with ::StretchDIBits. By not passing the bits to ::CreateDIBitmap and creating an HBITMAP, the programs enjoy direct access to the bitmap data but can still display the bitmap on the screen. Unfortunately, the client/server architecture of Windows NT and Windows 2000 dictates that bits blitted from a buffer on the client side be copied to a buffer on the server side before they're transferred to the frame buffer, and the extra overhead causes ::StretchDIBits to perform sluggishly.

Rather than compromise the system architecture, the Windows NT team came up with DIB sections. A DIB section is the Windows NT and Windows 2000 equivalent of having your cake and eating it, too: you can select a DIB section into a DC and blit it to the screen (thus avoiding the undesirable memory-to-memory moves), but you can also access the bitmap bits directly. Speed isn't as much of an issue with the ::StretchDIBits function in Windows 95 and Windows 98 because these operating systems are architected differently than Windows NT and Windows 2000, but Windows 95 and Windows 98 support DIB sections just as Windows NT and Windows 2000 do and also offer some handy API functions for dealing with them. Win32 programmers are encouraged to use DIB sections in lieu of ordinary DIBs and DDBs whenever possible to give the operating system the greatest amount of flexibility in handling bitmap data.

The bad news about DIBs and DIB sections is that current versions of MFC don't encapsulate them. To use DIBs and DIB sections in your MFC applications, you have to either resort to the API or write your own classes to encapsulate the relevant API functions. Writing a basic CDib class or extending CBitmap to include functions for DIBs and DIB sections isn't difficult, but I'm not going to do either here because it's very likely that some future version of MFC will include a comprehensive set of classes representing DIBs and DIB sections. What I'll do instead is show you how to get the most out of MFC's CBitmap class and how to combine CBitmap with API functions to get some very DIB-like behavior out of ordinary CBitmaps.

Blits, Raster Operations, and Color Mapping

The most common use for CDC::BitBlt is to blit bitmap images to the screen. But BitBlt does more than just transfer raw bits. In reality, it's a complex function that computes the color of each pixel it outputs by using Boolean operations to combine pixels from the source DC, the destination DC, and the brush currently selected in the destination DC. The SRCCOPY raster-op code is simple; it merely copies pixels from the source to the destination. Other raster-op codes aren't so simple. MERGEPAINT, for example, inverts the colors of the source pixels with a Boolean NOT operation and ORs the result with the pixel colors at the destination. BitBlt supports 256 raster-op codes in all. The 15 shown in the following table are given names with #define statements in Wingdi.h.

BitBlt Raster-Op Codes

Name Binary Equivalent Operation(s) Performed
SRCCOPY 0xCC0020 dest = source
SRCPAINT 0xEE0086 dest = source OR dest
SRCAND 0x8800C6 dest = source AND dest
SRCINVERT 0x660046 dest = source XOR dest
SRCERASE 0x440328 dest = source AND (NOT dest )
NOTSRCCOPY 0x330008 dest = (NOT source)
NOTSRCERASE 0x1100A6 dest = (NOT src) AND (NOT dest)
MERGECOPY 0xC000CA dest = (source AND pattern)
MERGEPAINT 0xBB0226 dest = (NOT source) OR dest
PATCOPY 0xF00021 dest = pattern
PATPAINT 0xFB0A09 dest = pattern OR (NOT src) OR dest
PATINVERT 0x5A0049 dest = pattern XOR dest
DSTINVERT 0x550009 dest = (NOT dest)
BLACKNESS 0x000042 dest = BLACK
WHITENESS 0xFF0062 dest = WHITE

You can derive custom raster-op codes by applying the logical operations you want to the bit values in the following list and using the result to look up a DWORD-sized raster-op code in the "Ternary Raster Operations" section of Microsoft's Platform SDK.

Pat      1     1     1     1     0     0     0     0
Src      1     1     0     0     1     1     0     0
Dest     1     0     1     0     1     0     1     0

Pat (for "pattern") represents the color of the brush selected into the destination DC; Src represents the pixel color in the source DC; and Dest represents the pixel color in the destination DC. Let's say you want to find a raster-op code that inverts a source bitmap, ANDs it with the pixels at the destination, and ORs the result with the brush color. First apply these same operations to each column of bits in the list. The result is shown here:

Pat      1     1     1     1     0     0     0     0     
Src      1     1     0     0     1     1     0     0
Dest     1     0     1     0     1     0     1     0
        ---------------------------------------------
         1     1     1     1     0     0     1     0     =      0xF2

Look up 0xF2 in the ternary raster operations table, and you'll find that the full raster-op code is 0xF20B05. Consequently, you can pass BitBlt the hex value 0xF20B05 instead of SRCCOPY or some other raster-op code and it will perform the raster operation described above.

So what can you do with all those raster-op codes? The truth is that in color environments you probably won't use many of them. After SRCCOPY, the next most useful raster operations are SRCAND, SRCINVERT, and SRCPAINT. But as the sample program in the next section demonstrates, using an unnamed raster-op code can sometimes reduce the number of steps required to achieve a desired result.

BitBlt is part of a larger family of CDC blitting functions that includes StretchBlt (which we've already discussed), PatBlt, MaskBlt, and PlgBlt. PatBlt combines pixels in a rectangle in the destination DC with the brush selected into the device context, basically duplicating the subset of BitBlt raster operations that don't use a source DC. MaskBlt combines pixels in source and destination DCs and uses a monochrome bitmap as a mask. One raster operation (the "foreground" raster operation) is performed on pixels that correspond to 1s in the mask, and another raster operation (the "background" raster operation) is performed on pixels that correspond to 0s in the mask. PlgBlt blits a rectangular block of pixels in a source DC to a parallelogram in the destination DC and optionally uses a monochrome bitmap as a mask during the transfer. Pixels that correspond to 1s in the mask are blitted to the parallelogram; pixels that correspond to 0s in the mask are not. Unfortunately, MaskBlt and PlgBlt are supported in Windows NT 3.1 and higher and in Windows 2000 but not in Windows 95 and Windows 98. If you call either of them in Windows 95 or Windows 98, you'll get a 0 return, indicating that the function failed.

Some output devices (notably plotters) don't support BitBlt and other blitting functions. To determine whether BitBlts are supported on a given device, get a device context and call GetDeviceCaps with a RASTERCAPS parameter. If the RC_BITBLT bit is set in the return value, the device supports BitBlts; if the RC_STRETCHBLT bit is set, the device also supports StretchBlts. There are no specific RASTERCAPS bits for other blit functions, but if you're writing for Windows NT and BitBlt isn't supported, you should assume that PatBlt, MaskBlt, and PlgBlt aren't supported, either. Generally, plotters and other vector-type devices that don't support blits will set the RC_NONE bit in the value returned by GetDeviceCaps to indicate that they don't support raster operations of any type.

BitBlt and other blitting functions produce the best results (and also perform the best) when the color characteristics of the source and destination DCs match. If you blit a 256-color bitmap to a 16-color destination DC, Windows must map the colors in the source DC to the colors in the destination DC. On some occasions, however, you can use color mapping to your advantage. When BitBlt blits a monochrome bitmap to a color DC, it converts 0 bits to the destination DC's current foreground color (CDC::SetTextColor) and 1 bits to the destination DC's current background color (CDC::SetBkColor). Conversely, when it blits a color bitmap to a monochrome DC, BitBlt converts pixels that match the destination DC's background color to 1 and all other pixels to 0. You can use the latter form of color mapping to create a monochrome mask from a color bitmap and use that mask in a routine that blits all pixels except those of a certain color from a bitmap to a screen DC, in effect creating transparent pixels in the bitmap.

Sound interesting? Icons implement transparent pixels by storing two bitmaps for every icon image: a monochrome AND mask and a color XOR mask. You can draw bitmaps with transparent pixels by writing an output routine that uses BitBlts and raster operations to build the AND and XOR masks on the fly. The BitmapDemo sample program in the next section shows how.

The BitmapDemo Application

BitmapDemo is a non-document/view application created with AppWizard that demonstrates how to load a bitmap resource and BitBlt it to the screen. It also shows how to make clever use of BitBlts and raster-op codes to blit irregularly shaped images by designating one color in the bitmap as the transparency color. The program's output consists of a rectangular array of bitmap images drawn against a background that fades from blue to black. When Draw Opaque is checked in the Options menu, bitmaps are blitted to the screen unchanged, producing the result shown in Figure 15-5. If Draw Transparent is checked instead, red pixels are removed from the bitmaps when they're blitted to the screen. The result is pictured in Figure 15-6.

Click to view at full size.

Figure 15-5. The BitmapDemo window with transparency disabled.

Click to view at full size.

Figure 15-6. The BitmapDemo window with transparency enabled.

BitmapDemo uses a CBitmap-derived class named CMaskedBitmap to represent bitmaps. CMaskedBitmap contains two member functions that CBitmap doesn't: a Draw function for blitting a bitmap to a DC and a DrawTransparent function for blitting a bitmap to a DC and simultaneously filtering out all pixels of a specified color. With CMaskedBitmap to lend a hand, the statements

CMaskedBitmap bitmap;
bitmap.LoadBitmap (IDB_BITMAP);
bitmap.Draw (pDC, x, y);

are all you need to create a bitmap object, load a bitmap resource into it, and draw that bitmap on the device represented by pDC. The x and y parameters specify the placement of the bitmap's upper left corner. The statements

CMaskedBitmap bitmap;
bitmap.LoadBitmap (IDB_BITMAP);
bitmap.DrawTransparent (pDC, x, y, RGB (255, 0, 255));

do the same but don't blit any pixels in the bitmap whose color is bright magenta—RGB (255, 0, 255). With CMaskedBitmap to help out, drawing bitmaps with "holes" or nonrectangular profiles is easy: just assign all the transparent pixels in the bitmap a common color and pass that color to DrawTransparent. DrawTransparent will see to it that the transparent pixels don't get blitted along with the others.

The source code for CMaskedBitmap::Draw should look familiar to you: it's identical to the DrawBitmap function discussed earlier. CMaskedBitmap::DrawTransparent is a little more complicated. The comments in the source code should help you understand what's going on. If the comments don't make things clear enough, here's a summary of the steps involved in blitting a bitmap to the screen but omitting pixels of a certain color:

  1. Create a memory DC, and select the bitmap into it.
  2. Create a second memory DC, and select in a monochrome bitmap whose size is identical to that of the original bitmap. Create an AND mask by setting the background color of the memory DC created in step 1 to the transparency color and blitting the bitmap to the DC. The resultant AND mask has 1s everywhere the original bitmap has pixels whose color equals the transparency color and 0s everywhere else.
  3. Create a third memory DC, and select in a bitmap whose size and color characteristics match those of the original bitmap. Create an XOR mask in this DC by first blitting the image from the memory DC created in step 1 to this DC with a SRCCOPY raster-op code and then blitting the AND mask to this DC with the raster-op code 0x220326.
  4. Create a fourth memory DC, and select in a bitmap whose size and color characteristics match those of the original bitmap. Blit the pixels from the rectangle in which the bitmap will go in the output DC to the newly created memory DC.
  5. Create the final image in the memory DC created in step 4 by first blitting in the AND mask with a SRCAND raster-op code and then blitting in the XOR mask with a SRCINVERT raster-op code.
  6. Copy the image from the memory DC to the output DC.

Notice how BitBlt is used to generate the AND mask in step 2. Because the destination DC is monochrome, the GDI translates pixels whose color equals the background color to 1s and all other pixels to 0s at the destination. It's important to set the source DC's background color equal to the bitmap's transparency color first so that the transformation will be performed properly. If you look at the code in CMaskedBitmap::DrawTransparent that corresponds to step 2, you'll see that the destination DC's size and color characteristics are set by using CBitmap::CreateBitmap to create a monochrome bitmap whose dimensions equal the dimensions of the original bitmap and then selecting the monochrome bitmap into the DC. You control the size of a memory DC's display surface and the number of colors that it supports by selecting a bitmap into it. That's why you see so many calls to CreateBitmap and CreateCompatibleBitmap in DrawTransparent.

One other point of interest in DrawTransparent is the raster-op code 0x220326 used in step 3, which performs the following raster operation involving pixels at the source and destination.

dest = (NOT src) AND dest

You can accomplish the same thing using "standard" raster-op codes by calling BitBlt twice: once with the raster-op code NOTSRCCOPY to invert the image in the source DC and again with SRCAND to AND the inverted image with the pixels in the destination DC. One BitBlt is obviously more efficient than two, but don't be surprised if the 0x220326 code doesn't perform any faster than the NOTSRCCOPY/SRCAND combination on some PCs. Most display drivers are optimized to perform certain raster operations faster than others, and it's always possible that a NOTSRCCOPY or a SRCAND will execute very quickly but a 0x220326 won't.

As you experiment with BitmapDemo (whose source code appears in Figure 15-7), notice that the window takes longer to repaint when BitmapDemo draws transparent pixels. That's because DrawTransparent has to do a lot more work than Draw to get a single image to the screen. The worst performance hit occurs when DrawTransparent generates the same AND and XOR masks over and over again. If you want the functionality of DrawTransparent in an application in which output performance is critical (for example, if you use transparent bitmaps to create spritelike objects that move about the screen), you should modify the CMaskedBitmap class so that the masks are generated just once and then reused as needed. Performance can also be improved by applying the AND and XOR masks directly to the destination DC rather than to a memory DC containing a copy of the pixels at the destination, but the small amount of flickering produced by the short delay between the application of the masks might be too much if you're using the bitmap for animation.

Figure 15-7. The BitmapDemo application.

MainFrm.h

// MainFrm.h : interface of the CMainFrame class
//
///////////////////////////////////////////////////////////////////////////

#if !defined(
    AFX_MAINFRM_H__D71EF549_A6FE_11D2_8E53_006008A82731__INCLUDED_)
#define AFX_MAINFRM_H__D71EF549_A6FE_11D2_8E53_006008A82731__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

#include "ChildView.h"

class CMainFrame : public CFrameWnd
{
public:
    CMainFrame();
protected: 
    DECLARE_DYNAMIC(CMainFrame)

// Attributes
public:

// Operations
public:

// Overrides
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CMainFrame)
    virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
    virtual BOOL OnCmdMsg(UINT nID, int nCode, void* pExtra, 
        AFX_CMDHANDLERINFO* pHandlerInfo);
    //}}AFX_VIRTUAL

// Implementation
public:
    virtual ~CMainFrame();
#ifdef _DEBUG
    virtual void AssertValid() const;
    virtual void Dump(CDumpContext& dc) const;
#endif

protected:  // control bar embedded members
    CStatusBar  m_wndStatusBar;
    CChildView    m_wndView;

// Generated message map functions
protected:
    //{{AFX_MSG(CMainFrame)
    afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
    afx_msg void OnSetFocus(CWnd *pOldWnd);
    afx_msg BOOL OnQueryNewPalette();
    afx_msg void OnPaletteChanged(CWnd* pFocusWnd);
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
};

///////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations 
// immediately before the previous line.

#endif 
// !defined(
//     AFX_MAINFRM_H__D71EF549_A6FE_11D2_8E53_006008A82731__INCLUDED_)

MainFrm.cpp

// MainFrm.cpp : implementation of the CMainFrame class
//

#include "stdafx.h"
#include "BitmapDemo.h"
#include "MaskedBitmap.h"
#include "MainFrm.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

///////////////////////////////////////////////////////////////////////////
// CMainFrame

IMPLEMENT_DYNAMIC(CMainFrame, CFrameWnd)

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
    //{{AFX_MSG_MAP(CMainFrame)
    ON_WM_CREATE()
    ON_WM_SETFOCUS()
    ON_WM_QUERYNEWPALETTE()
    ON_WM_PALETTECHANGED()
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

static UINT indicators[] =
{
    ID_SEPARATOR
};

///////////////////////////////////////////////////////////////////////////
// CMainFrame construction/destruction
CMainFrame::CMainFrame()
{
}

CMainFrame::~CMainFrame()
{
}

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
        return -1;
    // create a view to occupy the client area of the frame
    if (!m_wndView.Create(NULL, NULL, AFX_WS_DEFAULT_VIEW,
        CRect(0, 0, 0, 0), this, AFX_IDW_PANE_FIRST, NULL))
    {
        TRACE0("Failed to create view window\n");
        return -1;
    }

    if (!m_wndStatusBar.Create(this) ¦¦
        !m_wndStatusBar.SetIndicators(indicators,
          sizeof(indicators)/sizeof(UINT)))
    {
        TRACE0("Failed to create status bar\n");
        return -1;      // fail to create
    }

    return 0;
}

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
    if( !CFrameWnd::PreCreateWindow(cs) )
        return FALSE;
    // TODO: Modify the Window class or styles here by modifying
    //  the CREATESTRUCT cs

    cs.dwExStyle &= ~WS_EX_CLIENTEDGE;
    cs.lpszClass = AfxRegisterWndClass(0);
    return TRUE;
}

///////////////////////////////////////////////////////////////////////////
// CMainFrame diagnostics

#ifdef _DEBUG
void CMainFrame::AssertValid() const
{
    CFrameWnd::AssertValid();
}

void CMainFrame::Dump(CDumpContext& dc) const
{
    CFrameWnd::Dump(dc);
}

#endif //_DEBUG

///////////////////////////////////////////////////////////////////////////
// CMainFrame message handlers
void CMainFrame::OnSetFocus(CWnd* pOldWnd)
{
    // forward focus to the view window
    m_wndView.SetFocus();
}

BOOL CMainFrame::OnCmdMsg(UINT nID, int nCode, void* pExtra, 
    AFX_CMDHANDLERINFO* pHandlerInfo)
{
    // let the view have first crack at the command
    if (m_wndView.OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
        return TRUE;

    // otherwise, do default handling
    return CFrameWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
}

BOOL CMainFrame::OnQueryNewPalette() 
{
    m_wndView.Invalidate ();
    return TRUE;
}

void CMainFrame::OnPaletteChanged(CWnd* pFocusWnd) 
{
    m_wndView.Invalidate ();
}

ChildView.h

// ChildView.h : interface of the CChildView class
//
///////////////////////////////////////////////////////////////////////////

#if !defined(
    AFX_CHILDVIEW_H__D71EF54B_A6FE_11D2_8E53_006008A82731__INCLUDED_)
#define AFX_CHILDVIEW_H__D71EF54B_A6FE_11D2_8E53_006008A82731__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

///////////////////////////////////////////////////////////////////////////
// CChildView window

class CChildView : public CWnd
{
// Construction
public:
    CChildView();

// Attributes
public:

// Operations
public:

// Overrides
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CChildView)
    protected:
    virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
    //}}AFX_VIRTUAL

// Implementation
public:
    virtual ~CChildView();

    // Generated message map functions
protected:
    void DoGradientFill (CDC* pDC, LPRECT pRect);
    CPalette m_palette;
    CMaskedBitmap m_bitmap;
    BOOL m_bDrawOpaque;
    //{{AFX_MSG(CChildView)
    afx_msg void OnPaint();
    afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
    afx_msg BOOL OnEraseBkgnd(CDC* pDC);
    afx_msg void OnOptionsDrawOpaque();
    afx_msg void OnOptionsDrawTransparent();
    afx_msg void OnUpdateOptionsDrawOpaque(CCmdUI* pCmdUI);
    afx_msg void OnUpdateOptionsDrawTransparent(CCmdUI* pCmdUI);
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
};

///////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations 
// immediately before the previous line.

#endif 
// !defined(
//     AFX_CHILDVIEW_H__D71EF54B_A6FE_11D2_8E53_006008A82731__INCLUDED_)

ChildView.cpp

// ChildView.cpp : implementation of the CChildView class
//

#include "stdafx.h"
#include "BitmapDemo.h"
#include "MaskedBitmap.h"
#include "ChildView.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

///////////////////////////////////////////////////////////////////////////
// CChildView

CChildView::CChildView()
{
    m_bDrawOpaque = TRUE;
}

CChildView::~CChildView()
{
}

BEGIN_MESSAGE_MAP(CChildView,CWnd )
    //{{AFX_MSG_MAP(CChildView)
    ON_WM_PAINT()
    ON_WM_CREATE()
    ON_WM_ERASEBKGND()
    ON_COMMAND(ID_OPTIONS_DRAW_OPAQUE, OnOptionsDrawOpaque)
    ON_COMMAND(ID_OPTIONS_DRAW_TRANSPARENT, OnOptionsDrawTransparent)
    ON_UPDATE_COMMAND_UI(ID_OPTIONS_DRAW_OPAQUE, OnUpdateOptionsDrawOpaque)
    ON_UPDATE_COMMAND_UI(ID_OPTIONS_DRAW_TRANSPARENT, 
        OnUpdateOptionsDrawTransparent)
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

///////////////////////////////////////////////////////////////////////////
// CChildView message handlers

BOOL CChildView::PreCreateWindow(CREATESTRUCT& cs) 
{
    if (!CWnd::PreCreateWindow(cs))
        return FALSE;

    cs.dwExStyle ¦= WS_EX_CLIENTEDGE;
    cs.style &= ~WS_BORDER;
    cs.lpszClass = AfxRegisterWndClass(CS_HREDRAW¦CS_VREDRAW¦CS_DBLCLKS, 
        ::LoadCursor(NULL, IDC_ARROW), HBRUSH(COLOR_WINDOW+1), NULL);

    return TRUE;
}

void CChildView::OnPaint() 
{
    CRect rect;
    GetClientRect (&rect);
    CPaintDC dc (this);

    BITMAP bm;
    m_bitmap.GetBitmap (&bm);
    int cx = (rect.Width () / (bm.bmWidth + 8)) + 1;
    int cy = (rect.Height () / (bm.bmHeight + 8)) + 1;

    int i, j, x, y;
    for (i=0; i<cx; i++) {
        for (j=0; j<cy; j++) {
            x = 8 + (i * (bm.bmWidth + 8));
            y = 8 + (j * (bm.bmHeight + 8));
            if (m_bDrawOpaque)
                m_bitmap.Draw (&dc, x, y);
            else
                m_bitmap.DrawTransparent (&dc, x, y, RGB (255, 0, 0));
        }
    }
}

int CChildView::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
    if (CWnd ::OnCreate(lpCreateStruct) == -1)
        return -1;
    
    //
    // Load the bitmap.
    //
    m_bitmap.LoadBitmap (IDB_BITMAP);

    //
    // Create a palette for a gradient fill if this is a palettized device.
    //
    CClientDC dc (this);
    if (dc.GetDeviceCaps (RASTERCAPS) & RC_PALETTE) {
        struct {
            LOGPALETTE lp;
            PALETTEENTRY ape[63];
        } pal;

        LOGPALETTE* pLP = (LOGPALETTE*) &pal;
        pLP->palVersion = 0x300;
        pLP->palNumEntries = 64;

        for (int i=0; i<64; i++) {
            pLP->palPalEntry[i].peRed = 0;
            pLP->palPalEntry[i].peGreen = 0;
            pLP->palPalEntry[i].peBlue = 255 - (i * 4);
            pLP->palPalEntry[i].peFlags = 0;
        }
        m_palette.CreatePalette (pLP);
    }
    return 0;
}
BOOL CChildView::OnEraseBkgnd(CDC* pDC) 
{
    CRect rect;
    GetClientRect (&rect);

    CPalette* pOldPalette;
    if ((HPALETTE) m_palette != NULL) {
        pOldPalette = pDC->SelectPalette (&m_palette, FALSE);
        pDC->RealizePalette ();
    }

    DoGradientFill (pDC, &rect);

    if ((HPALETTE) m_palette != NULL)
        pDC->SelectPalette (pOldPalette, FALSE);
    return TRUE;
}

void CChildView::DoGradientFill(CDC *pDC, LPRECT pRect)
{
    CBrush* pBrush[64];
    for (int i=0; i<64; i++)
        pBrush[i] = new CBrush (PALETTERGB (0, 0, 255 - (i * 4)));

    int nWidth = pRect->right - pRect->left;
    int nHeight = pRect->bottom - pRect->top;
    CRect rect;

    for (i=0; i<nHeight; i++) {
        rect.SetRect (0, i, nWidth, i + 1);
        pDC->FillRect (&rect, pBrush[(i * 63) / nHeight]);
    }

    for (i=0; i<64; i++)
        delete pBrush[i];
}

void CChildView::OnOptionsDrawOpaque() 
{
    m_bDrawOpaque = TRUE;
    Invalidate ();
}

void CChildView::OnOptionsDrawTransparent() 
{
    m_bDrawOpaque = FALSE;
    Invalidate ();
}

void CChildView::OnUpdateOptionsDrawOpaque(CCmdUI* pCmdUI) 
{
    pCmdUI->SetCheck (m_bDrawOpaque ? 1 : 0);
}

void CChildView::OnUpdateOptionsDrawTransparent(CCmdUI* pCmdUI) 
{
    pCmdUI->SetCheck (m_bDrawOpaque ? 0 : 1);
}

MaskedBitmap.h

// MaskedBitmap.h: interface for the CMaskedBitmap class.
//
//////////////////////////////////////////////////////////////////////

#if !defined(
    AFX_MASKEDBITMAP_H__D71EF554_A6FE_11D2_8E53_006008A82731__INCLUDED_)
#define AFX_MASKEDBITMAP_H__D71EF554_A6FE_11D2_8E53_006008A82731__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

class CMaskedBitmap : public CBitmap  
{
public:
    void DrawTransparent (CDC* pDC, int x, int y, 
        COLORREF clrTransparency);
    void Draw (CDC* pDC, int x, int y);
};

#endif 
// !defined(
//     AFX_MASKEDBITMAP_H__D71EF554_A6FE_11D2_8E53_006008A82731__INCLUDED_)

MaskedBitmap.cpp

// MaskedBitmap.cpp: implementation of the CMaskedBitmap class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "BitmapDemo.h"
#include "MaskedBitmap.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

void CMaskedBitmap::Draw(CDC *pDC, int x, int y)
{
    BITMAP bm;
    GetBitmap (&bm);
    CPoint size (bm.bmWidth, bm.bmHeight);
    pDC->DPtoLP (&size);

    CPoint org (0, 0);
    pDC->DPtoLP (&org);

    CDC dcMem;
    dcMem.CreateCompatibleDC (pDC);
    CBitmap* pOldBitmap = dcMem.SelectObject (this);
    dcMem.SetMapMode (pDC->GetMapMode ());

    pDC->BitBlt (x, y, size.x, size.y, &dcMem, org.x, org.y, SRCCOPY);

    dcMem.SelectObject (pOldBitmap);
}

void CMaskedBitmap::DrawTransparent(CDC *pDC, int x, int y,
    COLORREF clrTransparency)
{
    BITMAP bm;
    GetBitmap (&bm);
    CPoint size (bm.bmWidth, bm.bmHeight);
    pDC->DPtoLP (&size);

    CPoint org (0, 0);
    pDC->DPtoLP (&org);

    //
    // Create a memory DC (dcImage) and select the bitmap into it.
    //
    CDC dcImage;
    dcImage.CreateCompatibleDC (pDC);
    CBitmap* pOldBitmapImage = dcImage.SelectObject (this);
    dcImage.SetMapMode (pDC->GetMapMode ());

    //
    // Create a second memory DC (dcAnd) and in it create an AND mask.
    //
    CDC dcAnd;
    dcAnd.CreateCompatibleDC (pDC);
    dcAnd.SetMapMode (pDC->GetMapMode ());

    CBitmap bitmapAnd;
    bitmapAnd.CreateBitmap (bm.bmWidth, bm.bmHeight, 1, 1, NULL);
    CBitmap* pOldBitmapAnd = dcAnd.SelectObject (&bitmapAnd);

    dcImage.SetBkColor (clrTransparency);
    dcAnd.BitBlt (org.x, org.y, size.x, size.y, &dcImage, org.x, org.y,
        SRCCOPY);

    //
    // Create a third memory DC (dcXor) and in it create an XOR mask.
    //
    CDC dcXor;
    dcXor.CreateCompatibleDC (pDC);
    dcXor.SetMapMode (pDC->GetMapMode ());

    CBitmap bitmapXor;
    bitmapXor.CreateCompatibleBitmap (&dcImage, bm.bmWidth, bm.bmHeight);
    CBitmap* pOldBitmapXor = dcXor.SelectObject (&bitmapXor);

    dcXor.BitBlt (org.x, org.y, size.x, size.y, &dcImage, org.x, org.y,
        SRCCOPY);

    dcXor.BitBlt (org.x, org.y, size.x, size.y, &dcAnd, org.x, org.y,
        0x220326);

    //
    // Copy the pixels in the destination rectangle to a temporary
    // memory DC (dcTemp).
    //
    CDC dcTemp;
    dcTemp.CreateCompatibleDC (pDC);
    dcTemp.SetMapMode (pDC->GetMapMode ());

    CBitmap bitmapTemp;
    bitmapTemp.CreateCompatibleBitmap (&dcImage, bm.bmWidth, bm.bmHeight);
    CBitmap* pOldBitmapTemp = dcTemp.SelectObject (&bitmapTemp);

    dcTemp.BitBlt (org.x, org.y, size.x, size.y, pDC, x, y, SRCCOPY);

    //
    // Generate the final image by applying the AND and XOR masks to
    // the image in the temporary memory DC.
    //
    dcTemp.BitBlt (org.x, org.y, size.x, size.y, &dcAnd, org.x, org.y,
        SRCAND);

    dcTemp.BitBlt (org.x, org.y, size.x, size.y, &dcXor, org.x, org.y,
        SRCINVERT);

    //
    // Blit the resulting image to the screen.
    //
    pDC->BitBlt (x, y, size.x, size.y, &dcTemp, org.x, org.y, SRCCOPY);

    //
    // Restore the default bitmaps.
    //
    dcTemp.SelectObject (pOldBitmapTemp);
    dcXor.SelectObject (pOldBitmapXor);
    dcAnd.SelectObject (pOldBitmapAnd);
    dcImage.SelectObject (pOldBitmapImage);
}

Both Windows 98 and Windows 2000 support a new API function named ::TransparentBlt that performs the equivalent of a StretchBlt and also accepts a transparency color. Like BitmapDemo's DrawTransparent function, ::TransparentBlt skips pixels whose color equals the transparency color. I didn't use ::TransparentBlt because I wanted BitmapDemo to work as well on down-level systems as it works on Windows 98 and Windows 2000 systems. Which of these transparency functions you should use depends on the platforms you're targeting.

Writing a BMP File Viewer

The disk-and-drive image drawn by BitmapDemo looks pretty good because it's a simple 16-color bitmap whose colors match the static colors in the system palette. As long as you draw the bitmaps yourself and stick to the colors in the default palette, bitmaps will display just fine without custom CPalettes. But if you write an application that reads arbitrary BMP files created by other programs and you rely on the default palette for color mapping, bitmaps containing 256 or more colors will be posterized—some rather severely. You can dramatically improve the quality of the output by creating a CPalette whose colors match the colors in the bitmap. The sample program in this section demonstrates how. It also shows one way that MFC programmers can combine CBitmaps with DIB sections to create more functional bitmaps.

The sample program, which I'll call Vista, is shown in Figure 15-8. Vista is a document/view BMP file viewer that will read virtually any BMP file containing any number of colors and draw a reasonable representation of it on a screen that's capable of displaying 256 or more colors. (Vista works with 16-color screens, too, but don't expect a lot from the output if the bitmap contains more than 16 colors.) The source code, selected portions of which appear in Figure 15-9, is surprisingly simple. Other than the code that creates a logical palette after a BMP file is read from disk, the application includes very little other than the standard stuff that forms the core of every document/view application.

Click to view at full size.

Figure 15-8. The Vista window with a bitmap displayed.

The view's OnDraw function displays bitmaps on the screen by selecting the logical palette associated with the bitmap into the device context (provided such a palette exists) and BitBlting the bitmap to a CScrollView. OnDraw retrieves the logical palette by calling the document's GetPalette function, and it retrieves the bitmap by calling the document's GetBitmap function. GetPalette returns a CPalette pointer to the palette that the document object creates when the bitmap is loaded. A NULL return means that no palette is associated with the bitmap, which in turn means that Vista is running on a nonpalettized video adapter. GetBitmap returns a pointer to the bitmap that constitutes the document itself. Vista's document class CVistaDoc stores the bitmap in a CBitmap data member named m_bitmap and the palette (if any) that goes with the bitmap in a CPalette member named m_palette. The bitmap and palette objects are initialized when the document's OnOpenDocument function is called (when the user selects Open from the File menu) and destroyed when the document's DeleteContents function is called.

One simple statement in OnOpenDocument reads the BMP file named in the function's parameter list:

HBITMAP hBitmap = (HBITMAP) ::LoadImage (NULL, lpszPathName,
    IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE ¦ LR_CREATEDIBSECTION);

The value returned by ::LoadImage is a valid HBITMAP if the DIB section was successfully created and NULL if it wasn't. If ::LoadImage fails, it's highly likely that the file doesn't contain a DIB. OnOpenDocument indicates as much in the error message it displays when ::LoadImage returns NULL. If the HBITMAP isn't NULL, OnOpenDocument attaches it to m_bitmap. The document (bitmap) is now loaded and ready to be displayed—almost.

If Vista is running on a palettized display device, the bitmap probably won't look very good unless there's a logical palette to go with it. After ::LoadImage returns, OnOpenDocument grabs a device context and calls GetDeviceCaps to determine whether palettes are supported. If the return value doesn't contain an RC_PALETTE flag, OnOpenDocument returns immediately and leaves m_palette uninitialized. Otherwise, OnOpenDocument initializes m_palette with a logical palette.

To determine how best to create the palette, OnOpenDocument first finds out how many colors the bitmap contains by calling GetObject with a pointer to a DIBSECTION structure. One of the members of a DIBSECTION structure is a BITMAPINFOHEADER structure, and the BITMAPINFOHEADER structure's biClrUsed and biBitCount fields reveal the number of colors in the bitmap. If biClrUsed is nonzero, it specifies the color count. If biClrUsed is 0, the number of colors equals

1 << biBitCount

The following code in OnOpenDocument sets nColors equal to the number of colors in the bitmap:

DIBSECTION ds;
m_bitmap.GetObject (sizeof (DIBSECTION), &ds);

int nColors;
if (ds.dsBmih.biClrUsed != 0)
    nColors = ds.dsBmih.biClrUsed;
else
    nColors = 1 << ds.dsBmih.biBitCount;

What OnOpenDocument does next depends on the value of nColors. If nColors is greater than 256, indicating that the bitmap has a color depth of 16, 24, or 32 bits (images stored in BMP files always use 1-bit, 4-bit, 8-bit, 16-bit, 24-bit, or 32-bit color), OnOpenDocument creates a halftone palette by calling CPalette::CreateHalftonePalette with a pointer to the screen DC it obtained earlier:

if (nColors > 256)
    m_palette.CreateHalftonePalette (&dc);

In return, the system creates a generic palette with a rainbow of colors that's suited to the device context. In most cases, a logical palette created by CreateHalftonePalette will contain 256 colors. That's not enough to allow a bitmap containing thousands or perhaps millions of colors to be displayed with 100 percent accuracy, but it will produce much better results than you'd get if you used the device context's default palette.

If nColors is less than or equal to 256, OnOpenDocument initializes m_palette with a logical palette whose colors match the colors in the bitmap. The key to matching the bitmap's colors is the API function ::GetDIBColorTable, which copies the color table associated with a 1-bit, 4-bit, or 8-bit DIB section to an array of RGBQUAD structures. That array, in turn, is used to initialize an array of PALETTEENTRY structures and create a logical palette:

RGBQUAD* pRGB = new RGBQUAD[nColors];

CDC memDC;
memDC.CreateCompatibleDC (&dc);
CBitmap* pOldBitmap = memDC.SelectObject (&m_bitmap);
::GetDIBColorTable ((HDC) memDC, 0, nColors, pRGB);
memDC.SelectObject (pOldBitmap);

UINT nSize = sizeof (LOGPALETTE) +
    (sizeof (PALETTEENTRY) * (nColors - 1));
LOGPALETTE* pLP = (LOGPALETTE*) new BYTE[nSize];

pLP->palVersion = 0x300;
pLP->palNumEntries = nColors;

for (int i=0; i<nColors; i++) {
    pLP->palPalEntry[i].peRed = pRGB[i].rgbRed;
    pLP->palPalEntry[i].peGreen = pRGB[i].rgbGreen;
    pLP->palPalEntry[i].peBlue = pRGB[i].rgbBlue;
    pLP->palPalEntry[i].peFlags = 0;
}

m_palette.CreatePalette (pLP);

::GetDIBColorTable works only if the DIB section is selected into a device context, so OnOpenDocument creates a memory DC and selects m_bitmap into it before making the call. The rest is just detail: allocating memory for a LOGPALETTE structure, transferring the RGBQUAD values from the color table to the corresponding PALETTEENTRY entries, and calling CreatePalette. Once it has a palette to work with, Vista will display most 256-color bitmaps with stunning accuracy on 256-color screens.

For a nice touch, Vista includes a readout in its status bar that identifies the bitmap's dimensions and color depth (bits per pixel). The status bar is updated when OnOpenDocument sends Vista's main window a WM_USER_UPDATE_STATS message containing a pointer to the string that it wants to appear in the status bar pane. A message handler in the frame window class fields the message and updates the status bar accordingly.

Figure 15-9. The Vista application.

MainFrm.h

// MainFrm.h : interface of the CMainFrame class
//
///////////////////////////////////////////////////////////////////////////

#if !defined(
    AFX_MAINFRM_H__3597FEA9_A70E_11D2_8E53_006008A82731__INCLUDED_)
#define AFX_MAINFRM_H__3597FEA9_A70E_11D2_8E53_006008A82731__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

class CMainFrame : public CFrameWnd
{
    
protected: // create from serialization only
    CMainFrame();
    DECLARE_DYNCREATE(CMainFrame)

// Attributes
public:

// Operations
public:

// Overrides
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CMainFrame)
    virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
    //}}AFX_VIRTUAL

// Implementation
public:
    virtual ~CMainFrame();

#ifdef _DEBUG
    virtual void AssertValid() const;
    virtual void Dump(CDumpContext& dc) const;
#endif

protected:  // control bar embedded members
    CStatusBar  m_wndStatusBar;

// Generated message map functions
protected:
    //{{AFX_MSG(CMainFrame)
    afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
    afx_msg BOOL OnQueryNewPalette();
    afx_msg void OnPaletteChanged(CWnd* pFocusWnd);
    //}}AFX_MSG
    afx_msg LRESULT OnUpdateImageStats (WPARAM wParam, LPARAM lParam);
    DECLARE_MESSAGE_MAP()
};

///////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations 
// immediately before the previous line.

#endif 
// !defined(AFX_MAINFRM_H__3597FEA9_A70E_11D2_8E53_006008A82731__INCLUDED_)

MainFrm.cpp

// MainFrm.cpp : implementation of the CMainFrame class
//

#include "stdafx.h"
#include "Vista.h"

#include "MainFrm.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

///////////////////////////////////////////////////////////////////////////
// CMainFrame

IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd)

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
    //{{AFX_MSG_MAP(CMainFrame)
    ON_WM_CREATE()
    ON_WM_QUERYNEWPALETTE()
    ON_WM_PALETTECHANGED()
    //}}AFX_MSG_MAP
    ON_MESSAGE (WM_USER_UPDATE_STATS, OnUpdateImageStats)
END_MESSAGE_MAP()

static UINT indicators[] =
{
    ID_SEPARATOR,
    ID_SEPARATOR
};

///////////////////////////////////////////////////////////////////////////
// CMainFrame construction/destruction

CMainFrame::CMainFrame()
{
}

CMainFrame::~CMainFrame()
{
}

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
        return -1;

    //
    // Create the status bar.
    //
    if (!m_wndStatusBar.Create(this) ¦¦
        !m_wndStatusBar.SetIndicators(indicators,
            sizeof(indicators)/sizeof(UINT)))
    {
        TRACE0("Failed to create status bar\n");
        return -1;      // fail to create
    }

    //
    // Size the status bar's rightmost pane to hold a text string.
    //
    TEXTMETRIC tm;
    CClientDC dc (this);
    CFont* pFont = m_wndStatusBar.GetFont ();
    CFont* pOldFont = dc.SelectObject (pFont);
    dc.GetTextMetrics (&tm);
    dc.SelectObject (pOldFont);

    int cxWidth;
    UINT nID, nStyle;
    m_wndStatusBar.GetPaneInfo (1, nID, nStyle, cxWidth);
    cxWidth = tm.tmAveCharWidth * 24;
    m_wndStatusBar.SetPaneInfo (1, nID, nStyle, cxWidth);
    return 0;
}

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
    if( !CFrameWnd::PreCreateWindow(cs) )
        return FALSE;
    return TRUE;
}

///////////////////////////////////////////////////////////////////////////
// CMainFrame diagnostics

#ifdef _DEBUG
void CMainFrame::AssertValid() const
{
    CFrameWnd::AssertValid();
}

void CMainFrame::Dump(CDumpContext& dc) const
{
    CFrameWnd::Dump(dc);
}

#endif //_DEBUG

///////////////////////////////////////////////////////////////////////////
// CMainFrame message handlers

BOOL CMainFrame::OnQueryNewPalette() 
{
    CDocument* pDoc = GetActiveDocument ();
    if (pDoc != NULL)
        GetActiveDocument ()->UpdateAllViews (NULL);
    return TRUE;
}

void CMainFrame::OnPaletteChanged(CWnd* pFocusWnd) 
{
    if (pFocusWnd != this) {
        CDocument* pDoc = GetActiveDocument ();
        if (pDoc != NULL)
            GetActiveDocument ()->UpdateAllViews (NULL);
    }
}

LRESULT CMainFrame::OnUpdateImageStats (WPARAM wParam, LPARAM lParam)
{
    m_wndStatusBar.SetPaneText (1, (LPCTSTR) lParam, TRUE);
    return 0;
}

VistaDoc.h

// VistaDoc.h : interface of the CVistaDoc class
//
///////////////////////////////////////////////////////////////////////////

#if !defined(
    AFX_VISTADOC_H__3597FEAB_A70E_11D2_8E53_006008A82731__INCLUDED_)
#define AFX_VISTADOC_H__3597FEAB_A70E_11D2_8E53_006008A82731__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000


class CVistaDoc : public CDocument
{
protected: // create from serialization only
    CVistaDoc();
    DECLARE_DYNCREATE(CVistaDoc)

// Attributes
public:

// Operations
public:

// Overrides
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CVistaDoc)
    public:
    virtual BOOL OnNewDocument();
    virtual void Serialize(CArchive& ar);
    virtual BOOL OnOpenDocument(LPCTSTR lpszPathName);
    virtual void DeleteContents();
    //}}AFX_VIRTUAL

// Implementation
public:
    CPalette* GetPalette();
    CBitmap* GetBitmap();
    virtual ~CVistaDoc();
#ifdef _DEBUG
    virtual void AssertValid() const;
    virtual void Dump(CDumpContext& dc) const;
#endif

protected:

// Generated message map functions
protected:
    CPalette m_palette;
    CBitmap m_bitmap;
    //{{AFX_MSG(CVistaDoc)
       // NOTE - the ClassWizard will add and remove member functions here.
       //    DO NOT EDIT what you see in these blocks of generated code !
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
};

///////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations 
// immediately before the previous line.

#endif 
// !defined(
//     AFX_VISTADOC_H__3597FEAB_A70E_11D2_8E53_006008A82731__INCLUDED_)

VistaDoc.cpp

// VistaDoc.cpp : implementation of the CVistaDoc class
//

#include "stdafx.h"
#include "Vista.h"

#include "VistaDoc.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

///////////////////////////////////////////////////////////////////////////
// CVistaDoc

IMPLEMENT_DYNCREATE(CVistaDoc, CDocument)

BEGIN_MESSAGE_MAP(CVistaDoc, CDocument)
    //{{AFX_MSG_MAP(CVistaDoc)
        // NOTE - the ClassWizard will add and remove mapping macros here.
        //    DO NOT EDIT what you see in these blocks of generated code!
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

///////////////////////////////////////////////////////////////////////////
// CVistaDoc construction/destruction

CVistaDoc::CVistaDoc()
{
}

CVistaDoc::~CVistaDoc()
{
}

BOOL CVistaDoc::OnNewDocument()
{
    if (!CDocument::OnNewDocument())
        return FALSE;
    return TRUE;
}

///////////////////////////////////////////////////////////////////////////
// CVistaDoc serialization

void CVistaDoc::Serialize(CArchive& ar)
{
    if (ar.IsStoring())
    {
        // TODO: add storing code here
    }
    else
    {
        // TODO: add loading code here
    }
}

///////////////////////////////////////////////////////////////////////////
// CVistaDoc diagnostics

#ifdef _DEBUG
void CVistaDoc::AssertValid() const
{
    CDocument::AssertValid();
}

void CVistaDoc::Dump(CDumpContext& dc) const
{
    CDocument::Dump(dc);
}
#endif //_DEBUG

///////////////////////////////////////////////////////////////////////////
// CVistaDoc commands

BOOL CVistaDoc::OnOpenDocument(LPCTSTR lpszPathName) 
{
    if (!CDocument::OnOpenDocument (lpszPathName))
        return FALSE;

    //
    // Open the file and create a DIB section from its contents.
    //
    HBITMAP hBitmap = (HBITMAP) ::LoadImage (NULL, lpszPathName,
        IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE ¦ LR_CREATEDIBSECTION);

    if (hBitmap == NULL) {
        CString string;
        string.Format (_T ("%s does not contain a DIB"), lpszPathName);
        AfxMessageBox (string);
        return FALSE;
    }

    m_bitmap.Attach (hBitmap);

    //
    // Return now if this device doesn't support palettes.
    //
    CClientDC dc (NULL);
    if ((dc.GetDeviceCaps (RASTERCAPS) & RC_PALETTE) == 0)
        return TRUE;

    //
    // Create a palette to go with the DIB section.
    //
    if ((HBITMAP) m_bitmap != NULL) {
        DIBSECTION ds;
        m_bitmap.GetObject (sizeof (DIBSECTION), &ds);

        int nColors;
        if (ds.dsBmih.biClrUsed != 0)
            nColors = ds.dsBmih.biClrUsed;
        else
            nColors = 1 << ds.dsBmih.biBitCount;

        //
        // Create a halftone palette if the DIB section contains more
        // than 256 colors.
        //
        if (nColors > 256)
            m_palette.CreateHalftonePalette (&dc);

        //
        // Create a custom palette from the DIB section's color table
        // if the number of colors is 256 or less.
        //
        else {
            RGBQUAD* pRGB = new RGBQUAD[nColors];

            CDC memDC;
            memDC.CreateCompatibleDC (&dc);
            CBitmap* pOldBitmap = memDC.SelectObject (&m_bitmap);
            ::GetDIBColorTable ((HDC) memDC, 0, nColors, pRGB);
            memDC.SelectObject (pOldBitmap);
            UINT nSize = sizeof (LOGPALETTE) +
                (sizeof (PALETTEENTRY) * (nColors - 1));
            LOGPALETTE* pLP = (LOGPALETTE*) new BYTE[nSize];

            pLP->palVersion = 0x300;
            pLP->palNumEntries = nColors;

            for (int i=0; i<nColors; i++) {
                pLP->palPalEntry[i].peRed = pRGB[i].rgbRed;
                pLP->palPalEntry[i].peGreen = pRGB[i].rgbGreen;
                pLP->palPalEntry[i].peBlue = pRGB[i].rgbBlue;
                pLP->palPalEntry[i].peFlags = 0;
            }

            m_palette.CreatePalette (pLP);
            delete[] pLP;
            delete[] pRGB;
        }
    }
    return TRUE;
}

void CVistaDoc::DeleteContents() 
{
    if ((HBITMAP) m_bitmap != NULL)
        m_bitmap.DeleteObject ();

    if ((HPALETTE) m_palette != NULL)
        m_palette.DeleteObject ();
    
    CDocument::DeleteContents();
}

CBitmap* CVistaDoc::GetBitmap()
{
    return ((HBITMAP) m_bitmap == NULL) ? NULL : &m_bitmap;
}

CPalette* CVistaDoc::GetPalette()
{
    return ((HPALETTE) m_palette == NULL) ? NULL : &m_palette;
}

VistaView.h

// VistaView.h : interface of the CVistaView class
//
///////////////////////////////////////////////////////////////////////////

#if !defined(
    AFX_VISTAVIEW_H__3597FEAD_A70E_11D2_8E53_006008A82731__INCLUDED_)
#define AFX_VISTAVIEW_H__3597FEAD_A70E_11D2_8E53_006008A82731__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000


class CVistaView : public CScrollView
{
protected: // create from serialization only
    CVistaView();
    DECLARE_DYNCREATE(CVistaView)

// Attributes
public:
    CVistaDoc* GetDocument();

// Operations
public:

// Overrides
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CVistaView)
    public:
    virtual void OnDraw(CDC* pDC);  // overridden to draw this view
    virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
    protected:
    virtual void OnInitialUpdate(); // called first time after construct
    //}}AFX_VIRTUAL

// Implementation
public:
    virtual ~CVistaView();
#ifdef _DEBUG
    virtual void AssertValid() const;
    virtual void Dump(CDumpContext& dc) const;
#endif

protected:

// Generated message map functions
protected:
    //{{AFX_MSG(CVistaView)
       // NOTE - the ClassWizard will add and remove member functions here.
       //    DO NOT EDIT what you see in these blocks of generated code !
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
};

#ifndef _DEBUG  // debug version in VistaView.cpp
inline CVistaDoc* CVistaView::GetDocument()
   { return (CVistaDoc*)m_pDocument; }
#endif

///////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations 
// immediately before the previous line.

#endif 
// !defined(
//     AFX_VISTAVIEW_H__3597FEAD_A70E_11D2_8E53_006008A82731__INCLUDED_)

VistaView.cpp

// VistaView.cpp : implementation of the CVistaView class
//

#include "stdafx.h"
#include "Vista.h"

#include "VistaDoc.h"
#include "VistaView.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

///////////////////////////////////////////////////////////////////////////
// CVistaView

IMPLEMENT_DYNCREATE(CVistaView, CScrollView)

BEGIN_MESSAGE_MAP(CVistaView, CScrollView)
    //{{AFX_MSG_MAP(CVistaView)
        // NOTE - the ClassWizard will add and remove mapping macros here.
        //    DO NOT EDIT what you see in these blocks of generated code!
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

///////////////////////////////////////////////////////////////////////////
// CVistaView construction/destruction

CVistaView::CVistaView()
{
}

CVistaView::~CVistaView()
{
}

BOOL CVistaView::PreCreateWindow(CREATESTRUCT& cs)
{
    return CScrollView::PreCreateWindow(cs);
}

///////////////////////////////////////////////////////////////////////////
// CVistaView drawing

void CVistaView::OnDraw(CDC* pDC)
{
    CVistaDoc* pDoc = GetDocument();
    ASSERT_VALID(pDoc);

    CBitmap* pBitmap = pDoc->GetBitmap ();

    if (pBitmap != NULL) {
        CPalette* pOldPalette;
        CPalette* pPalette = pDoc->GetPalette ();

        if (pPalette != NULL) {
            pOldPalette = pDC->SelectPalette (pPalette, FALSE);
            pDC->RealizePalette ();
        }
        DIBSECTION ds;
        pBitmap->GetObject (sizeof (DIBSECTION), &ds);

        CDC memDC;
        memDC.CreateCompatibleDC (pDC);
        CBitmap* pOldBitmap = memDC.SelectObject (pBitmap);

        pDC->BitBlt (0, 0, ds.dsBm.bmWidth, ds.dsBm.bmHeight, &memDC,
            0, 0, SRCCOPY);

        memDC.SelectObject (pOldBitmap);

        if (pPalette != NULL)
            pDC->SelectPalette (pOldPalette, FALSE);
    }
}

void CVistaView::OnInitialUpdate()
{
    CScrollView::OnInitialUpdate ();

    CString string;
    CSize sizeTotal;
    CBitmap* pBitmap = GetDocument ()->GetBitmap ();

    //
    // If a bitmap is loaded, set the view size equal to the bitmap size.
    // Otherwise, set the view's width and height to 0.
    //
    if (pBitmap != NULL) {
        DIBSECTION ds;
        pBitmap->GetObject (sizeof (DIBSECTION), &ds);
        sizeTotal.cx = ds.dsBm.bmWidth;
        sizeTotal.cy = ds.dsBm.bmHeight;
        string.Format (_T ("\t%d x %d, %d bpp"), ds.dsBm.bmWidth,
            ds.dsBm.bmHeight, ds.dsBmih.biBitCount);
    }
    else {
        sizeTotal.cx = sizeTotal.cy = 0;
        string.Empty ();
    }

    AfxGetMainWnd ()->SendMessage (WM_USER_UPDATE_STATS, 0,
        (LPARAM) (LPCTSTR) string);
    SetScrollSizes (MM_TEXT, sizeTotal);
}

///////////////////////////////////////////////////////////////////////////
// CVistaView diagnostics

#ifdef _DEBUG
void CVistaView::AssertValid() const
{
    CScrollView::AssertValid();
}

void CVistaView::Dump(CDumpContext& dc) const
{
    CScrollView::Dump(dc);
}

CVistaDoc* CVistaView::GetDocument() // non-debug version is inline
{
    ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CVistaDoc)));
    return (CVistaDoc*)m_pDocument;
}
#endif //_DEBUG

///////////////////////////////////////////////////////////////////////////
// CVistaView message handlers

More on the ::LoadImage Function

One reason Vista can do so much with so little code is that the ::LoadImage function allows a DIB section to be built from a BMP file with just one statement. Here's that statement again:

HBITMAP hBitmap = (HBITMAP) ::LoadImage (NULL, lpszPathName,
    IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE ¦ LR_CREATEDIBSECTION);

::LoadImage is to DIB sections what ::LoadBitmap and CDC::LoadBitmap are to DDBs. But it's also much more. I won't rehash all the input values it accepts because you can get that from the documentation, but here's a short summary of some of the things you can do with ::LoadImage:

Keep in mind that ::LoadImage's color-mapping capabilities work only with images that contain 256 or fewer colors. DIBs with 256 or fewer colors contain built-in color tables that make color mapping fast and efficient. Rather than examine every pixel in the image to perform a color conversion, ::LoadImage simply modifies the color table.

Vista demonstrates how ::LoadImage can be used to create a DIB section from a BMP file and attach it to a CBitmap object. One advantage of loading a bitmap as a DIB section instead of as an ordinary DDB is that you can call functions such as ::GetDIBColorTable on it. Had the LR_CREATEDIBSECTION flag been omitted from the call to ::LoadImage, we would have been unable to access the bitmap's color table and create a logical palette from it. In general, your applications will port more easily to future versions of Windows (and probably perform better, too) if you now start using DIB sections instead of DDBs whenever possible.