There's an MFC class for GDI bitmaps
(CBitmap), but there's no MFC class for DIBs. Don't worryI'm giving you one here. It's a complete rewrite of
the CDib class from the early editions of this book (prior to the fourth edition),
and it takes advantage of Win32 features such as memory-mapped files,
improved memory management, and DIB sections. It also includes palette support.
Before you examine the CDib class, however, you need a little background on DIBs.
A Few Words About Palette Programming
Windows palette programming is quite complex, but you've got to deal with it if you expect your users to run their displays in the 8-bpp (bits per pixel) modeand many users will if they have video cards with 1 MB or less of memory.
Suppose you're displaying a single DIB in a window. First you must create a logical palette, a GDI object that contains the colors in the DIB. Then you must "realize" this logical palette into the hardware system palette, a table of the 256 colors the video card can display at that instant. If your program is the foreground program, the realization process tries to copy all your colors into the system palette, but it doesn't touch the 20 standard Windows colors. For the most part, your DIB looks just like you want it to look.
But what if another program is the foreground program, and what if that program has a forest scene DIB with 236 shades of green? Your program still realizes its palette, but something different happens this time. Now the system palette won't change, but Windows sets up a new mapping between your logical palette and the system palette. If your DIB contains a neon pink color, for example, Windows maps it to the standard red color. If your program forgot to realize its palette, your neon pink stuff would turn green when the other program went active.
The forest scene example is extreme because we assumed that the other program grabbed 236 colors. If instead the other program realized a logical palette with only 200 colors, Windows would let your program load 36 of its own colors, including, one hopes, neon pink.
So when is a program supposed to realize its palette? The Windows message WM_PALETTECHANGED is sent to your program's main window whenever a program, including yours, realizes its palette. Another message, WM_QUERYNEWPALETTE, is sent whenever one of the windows in your program gets the input focus. Your program should realize its palette in response to both these messages (unless your program generated the message). These palette messages are not sent to your view window, however. You must map them in your application's main frame window and then notify the view. Chapter 13 discusses the relationship between the frame window and the view, and Chapter 26 contains a complete palette-aware MDI application (EX26A).
You call the Win32 RealizePalette function to perform the realization, but first you must call SelectPalette to select your DIB's logical palette into the device context. SelectPalette has a flag parameter that you normally set to FALSE in your WM_PALETTECHANGED and WM_QUERYNEWPALETTE handlers. This flag ensures that your palette is realized as a foreground palette if your application is indeed running in the foreground. If you use a TRUE flag parameter here, you can force Windows to realize the palette as though the application were in the background.
You must also call SelectPalette for each DIB that you display in your
OnDraw function. This time you call it with a
TRUE flag parameter. Things do get complicated if you're displaying several DIBs, each with its own palette. Basically, you've got to choose a palette for one of the DIBs and realize it (by
selecting it with the FALSE parameter) in the palette message handlers. The chosen
DIB will end up looking better than the other DIBs. There are ways of
merging palettes, but it might be easier to go out and buy more video memory.
DIBs, Pixels, and Color Tables
A DIB contains a two-dimensional array of elements called pixels. In many cases, each DIB pixel will be mapped to a display pixel, but the DIB pixel might be mapped to some logical area on the display, depending on the mapping mode and the display function stretch parameters.
A pixel consists of 1, 4, 8, 16, 24, or 32 contiguous bits, depending on the color resolution of the DIB. For 16-bpp, 24-bpp, and 32-bpp DIBs, each pixel represents an RGB color. A pixel in a 16-bpp DIB typically contains 5 bits each for red, green, and blue values; a pixel in a 24-bpp DIB has 8 bits for each color value. The 16-bpp and 24-bpp DIBs are optimized for video cards that can display 65,536 or 16.7 million simultaneous colors.
A 1-bpp DIB is a monochrome DIB, but these DIBs don't have to be black and whitethey can contain any two colors chosen from the color table that is built into each DIB. A monochrome bitmap has two 32-bit color table entries, each containing 8 bits for red, green, and blue values plus another 8 bits for flags. Zero (0) pixels use the first entry, and one (1) pixel uses the second. Whether you have a 65,536-color video card or a 16.7-million-color card, Windows can display the two colors directly. (Windows truncates 8-bits-per-color values to 5 bits for 65,536-color displays.) If your video card is running in 256-color palettized mode, your program can adjust the system palette to load the two specified colors.
Eight-bpp DIBs are quite common. Like a monochrome DIB, an 8-bpp DIB has a color table, but the color table has 256 (or fewer) 32-bit entries. Each pixel is an index into this color table. If you have a palettized video card, your program can create a logical palette from the 256 entries. If another program (running in the foreground) has control of the system palette, Windows does its best to match your logical palette colors to the system palette.
What if you're trying to display a 24-bpp DIB with a 256-color
palettized video card? If the DIB author was nice, he or she included a color table
containing the most important colors in the DIB. Your program can build a
logical palette from that table, and the DIB will look fine. If the DIB has no color
table, use the palette returned by the Win32
CreateHalftonePalette function; it's better than the 20 standard colors you'd get with no palette at all. Another option
is to analyze the DIB to identify the most important colors, but you can buy
a utility to do that.
The Structure of a DIB Within a BMP File
You know that the DIB is the standard Windows bitmap format and that a BMP file contains a DIB. So let's look inside a BMP file to see what's there. Figure 11-1 shows a layout for a BMP file.
Figure 11-1. The layout for a BMP file.
The BITMAPFILEHEADER structure contains the offset to the image bits, which you can use to compute the combined size of the BITMAPINFOHEADER structure and the color table that follows. The BITMAPFILEHEADER structure contains a file size member, but you can't depend on it because you don't know whether the size is measured in bytes, words, or double words.
The BITMAPINFOHEADER structure contains the bitmap dimensions, the bits per pixel, compression information for both 4-bpp and 8-bpp bitmaps, and the number of color table entries. If the DIB is compressed, this header contains the size of the pixel array; otherwise, you can compute the size from the dimensions and the bits per pixel. Immediately following the header is the color table (if the DIB has a color table). The DIB image comes after that. The DIB image consists of pixels arranged by column within rows, starting with the bottom row. Each row is padded to a 4-byte boundary.
The only place you'll find a BITMAPFILEHEADER structure, however, is in a BMP file. If you get a DIB from the clipboard, for example, there will not be a file header. You can always count on the color table to follow the BITMAPINFOHEADER structure, but you can't count on the image to follow the color table. If you're using the CreateDIBSection function, for example, you must allocate the bitmap info header and color table and then let Windows allocate the image somewhere else.
This chapter and all the associated code are specific to Windows DIBs. There's also a well-documented variation of the DIB format for OS/2. If you need to process these OS/2 DIBs, you'll have to modify the CDib class.
Windows supplies some important DIB access functions. None of these functions is wrapped by MFC, so you'll need to refer to the online Win32 documentation for details. Here's a summary:
If DIBs look intimidating, don't worry. The CDib class makes DIB programming easy. The best way to get to know the CDib class is to look at the public member functions and data members. Figure 11-2 shows the CDib header file. Consult the ex11c folder on the companion CD-ROM to see the implementation code.
CDIB.H #ifndef _INSIDE_VISUAL_CPP_CDIB #define _INSIDE_VISUAL_CPP_CDIB class CDib : public CObject { enum Alloc {noAlloc, crtAlloc, heapAlloc}; // applies to BITMAPINFOHEADER DECLARE_SERIAL(CDib) public: LPVOID m_lpvColorTable; HBITMAP m_hBitmap; LPBYTE m_lpImage; // starting address of DIB bits LPBITMAPINFOHEADER m_lpBMIH; // buffer containing the // BITMAPINFOHEADER private: HGLOBAL m_hGlobal; // for external windows we need to free; // could be allocated by this class or // allocated externally Alloc m_nBmihAlloc; Alloc m_nImageAlloc; DWORD m_dwSizeImage; // of bitsnot BITMAPINFOHEADER // or BITMAPFILEHEADER int m_nColorTableEntries; HANDLE m_hFile; HANDLE m_hMap; LPVOID m_lpvFile; HPALETTE m_hPalette; public: CDib(); CDib(CSize size, int nBitCount); // builds BITMAPINFOHEADER ~CDib(); int GetSizeImage() {return m_dwSizeImage;} int GetSizeHeader() {return sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * m_nColorTableEntries;} CSize GetDimensions(); BOOL AttachMapFile(const char* strPathname, BOOL bShare = FALSE); BOOL CopyToMapFile(const char* strPathname); BOOL AttachMemory(LPVOID lpvMem, BOOL bMustDelete = FALSE, HGLOBAL hGlobal = NULL); BOOL Draw(CDC* pDC, CPoint origin, CSize size); // until we implement CreateDibSection HBITMAP CreateSection(CDC* pDC = NULL); UINT UsePalette(CDC* pDC, BOOL bBackground = FALSE); BOOL MakePalette(); BOOL SetSystemPalette(CDC* pDC); BOOL Compress(CDC* pDC, BOOL bCompress = TRUE); // FALSE means decompress HBITMAP CreateBitmap(CDC* pDC); BOOL Read(CFile* pFile); BOOL ReadSection(CFile* pFile, CDC* pDC = NULL); BOOL Write(CFile* pFile); void Serialize(CArchive& ar); void Empty(); private: void DetachMapFile(); void ComputePaletteSize(int nBitCount); void ComputeMetrics(); }; #endif // _INSIDE_VISUAL_CPP_CDIB |
Here's a rundown of the CDib member functions, starting with the constructors and the destructor:
Parameter | Description |
size | CSize object that contains the width and height of the DIB |
nBitCount | Bits per pixel; should be 1, 4, 8, 16, 24, or 32 |
Parameter | Description |
strPathname | Pathname of the file to be mapped |
bShare | Flag that is TRUE if the file is to be opened in share mode; the default value is FALSE |
Return value | TRUE if successful |
Parameter | Description |
lpvMem | Address of the memory to be attached |
bMustDelete | Flag that is TRUE if the CDib class is responsible for deleting this memory; the default value is FALSE |
hGlobal | If memory was obtained with a call to the Win32 GlobalAlloc function, the CDib object needs to keep the handle in order to free it later, assuming that bMustDelete was set to TRUE |
Return value | TRUE if successful |
Parameter | Description |
pDC | Pointer to the display device context |
bCompress | TRUE (default) to compress the DIB; FALSE to uncompress it |
Return value | TRUE if successful |
Parameter | Description |
strPathname | Pathname of the file to be mapped |
Return value | TRUE if successful |
Parameter | Description |
pDC | Pointer to the display or printer device context |
Return value | Handle to a GDI bitmapNULL if unsuccessful. This handle is
not stored as a public data member. |
Parameter | Description |
pDC | Pointer to the display or printer device context |
Return value | Handle to a GDI bitmapNULL if unsuccessful. This handle is also stored as a public data member. |
Parameter | Description |
pDC | Pointer to the display or printer device context that will receive the DIB image |
origin | CPoint object that holds the logical coordinates at which the DIB will be displayed |
size | CSize object that represents the display rectangle's width and height in logical units |
Return value | TRUE if successful |
Parameter | Description |
Return value | CSize object |
Parameter | Description |
Return value | 32-bit integer |
Parameter | Description |
Return value | 32-bit integer |
Parameter | Description |
Return value | TRUE if successful |
Parameter | Description |
pFile | Pointer to a CFile object; the corresponding disk file contains the DIB |
Return value | TRUE if successful |
Parameter | Description |
pFile | Pointer to a CFile object; the corresponding disk file contains the DIB |
pDC | Pointer to the display or printer device context |
Return value | TRUE if successful |
Parameter | Description |
pDC | Pointer to the display context |
Return value | TRUE if successful |
Parameter | Description |
pDC | Pointer to the display device context for realization |
bBackground | If this flag is FALSE (the default value) and the application is running in the foreground, Windows realizes the palette as the foreground palette (copies as many colors as possible into the system palette). If this flag is TRUE, Windows realizes the palette as a background palette (maps the logical palette to the system palette as best it can). |
Return value | Number of entries in the logical palette mapped to the
system palette. If the function fails, the return value is GDI_ERROR. |
Parameter | Description |
pFile | Pointer to a CFile object; the DIB will be
written to the corresponding disk file. |
Return value | TRUE if successful |
Optimized DIB processing is now a major feature of Windows. Modern video cards have frame buffers that conform to the standard DIB image format. If you have one of these cards, your programs can take advantage of the new Windows DIB engine, which speeds up the process of drawing directly from DIBs. If you're still running in VGA mode, however, you're out of luck; your programs will still work, but not as fast.
If you're running Windows in 256-color mode, your 8-bpp bitmaps will be drawn very quickly, either with StretchBlt or with StretchDIBits. If, however, you are displaying 16-bpp or 24-bpp bitmaps, those drawing functions will be too slow. Your bitmaps will appear more quickly in this situation if you create a separate 8-bbp GDI bitmap and then call StretchBlt. Of course, you must be careful to realize the correct palette prior to creating the bitmap and prior to drawing it.
Here's some code that you might insert just after loading your CDib object from a BMP file:
// m_hBitmap is a data member of type HBITMAP // m_dcMem is a memory device context object of class CDC m_pDib->UsePalette(&dc); m_hBitmap = m_pDib->CreateBitmap(&dc); // could be slow ::SelectObject(m_dcMem.GetSafeHdc(), m_hBitmap);Here is the code that you use in place of CDib::Draw in your view's OnDraw member function:
m_pDib->UsePalette(pDC); // could be in palette msg handlerDon't forget to call DeleteObject for m_hBitmap when you're done with it.CSize sizeDib = m_pDib->GetDimensions();
pDC->StretchBlt(0, 0, sizeDib.cx, sizeDib.cy, &m_dcMem, 0, 0, sizeToDraw.cx, sizeToDraw.cy, SRCCOPY);
Now you'll put the CDib class to work in an application. The EX11C program displays two DIBs, one from a resource and the other loaded from a BMP file that you select at runtime. The program manages the system palette and displays the DIBs correctly on the printer. Compare the EX11C code with the GDI bitmap code in EX11A. Notice that you're not dealing with a memory device context and all the GDI selection rules!
Following are the steps to build EX11C. It's a good idea to type in the view class code, but you'll want to use the cdib.h and cdib.cpp files from the companion CD-ROM.
Add m_dibFile in the same way. The result should be two data members at the bottom of the header file:
CDib m_dibFile; CDib m_dibResource;
ClassView also adds this statement at the top of the ex11cView.h file:
#include "cdib.h" // Added by ClassView
void CEx11cView::OnInitialUpdate() { CScrollView::OnInitialUpdate(); CSize sizeTotal(30000, 40000); // 30-by-40 cm CSize sizeLine = CSize(sizeTotal.cx / 100, sizeTotal.cy / 100); SetScrollSizes(MM_HIMETRIC, sizeTotal, sizeTotal, sizeLine); LPVOID lpvResource = (LPVOID) ::LoadResource(NULL, ::FindResource(NULL, MAKEINTRESOURCE(IDB_REDBLOCKS), RT_BITMAP)); m_dibResource.AttachMemory(lpvResource); // no need for // ::LockResource CClientDC dc(this); TRACE("bits per pixel = %d\n", dc.GetDeviceCaps(BITSPIXEL)); }
void CEx11cView::OnDraw(CDC* pDC) { BeginWaitCursor(); m_dibResource.UsePalette(pDC); // should be in palette m_dibFile.UsePalette(pDC); // message handlers, not here pDC->TextOut(0, 0, "Press the left mouse button here to load a file."); CSize sizeResourceDib = m_dibResource.GetDimensions(); sizeResourceDib.cx *= 30; sizeResourceDib.cy *= -30; m_dibResource.Draw(pDC, CPoint(0, -800), sizeResourceDib); CSize sizeFileDib = m_dibFile.GetDimensions(); sizeFileDib.cx *= 30; sizeFileDib.cy *= -30; m_dibFile.Draw(pDC, CPoint(1800, -800), sizeFileDib); EndWaitCursor(); }
#define MEMORY_MAPPED_FILES void CEx11cView::OnLButtonDown(UINT nFlags, CPoint point) { CFileDialog dlg(TRUE, "bmp", "*.bmp"); if (dlg.DoModal() != IDOK) { return; } #ifdef MEMORY_MAPPED_FILES if (m_dibFile.AttachMapFile(dlg.GetPathName(), TRUE) == TRUE) { // share Invalidate(); } #else CFile file; file.Open(dlg.GetPathName(), CFile::modeRead); if (m_dibFile.Read(&file) == TRUE) { Invalidate(); } #endif // MEMORY_MAPPED_FILES CClientDC dc(this); m_dibFile.SetSystemPalette(&dc); }