Just a Minute: Although image lists (described in Section 17, "Using Image Lists and Bitmaps") provide extra features, usually a simple bitmap is the easiest way to display an image on the screen.
After you've created the bitmap using the image editor, you can manipulate it by using the MFC CBitmap class. You can load the bitmap into your program by calling the LoadBitmap member function:
bmpHello.LoadBitmap( IDB_HELLO );After the bitmap has been loaded, it can be displayed to any output device by using a device context.
Insert a new bitmap resource into the project by right-clicking the resource tree and selecting Insert from the shortcut menu. An Insert Resource dialog box is displayed; select Bitmap and click the New button. A new bitmap will be inserted into the project and loaded for editing.
The image editor displays a large grid that represents the bitmap surface, as well as two dockable toolbars:
You can change the properties for a bitmap resource by double-clicking the edge of the bitmap grid or by right-clicking the bitmap's edge and selecting Properties from the pop-up menu. Change the name of the bitmap resource to IDB_HELLO. Select the text tool from the Graphics toolbar, and choose your favorite color from the Colors palette. Type a hello message, as shown in Figure 15.1.
The IDB_HELLO bitmap used in the Bitmap sample program.
Just a Minute: The font shown in Figure 15.1 is 18-point Times New Roman Italic.
As with most resources, you can adjust the size of the bitmap by dragging the edges of the grid in any direction. Change the size of the bitmap so that the text fits inside the bitmap without any clipping. You can select a different font by pressing the Font button on the text tool. Feel free to add other effects by selecting other tools from the Graphics toolbar.
Just a Minute: Using names that begin with IDB_ for bitmaps is a standard naming convention in Windows programming.
void CBitmapView::OnDraw(CDC* pDC)
{
CBitmap bmpHello;
bmpHello.LoadBitmap( IDB_HELLO );
// Calculate bitmap size using a BITMAP structure.
BITMAP bm;
bmpHello.GetObject( sizeof(BITMAP), &bm );
// Create a memory DC, select the bitmap into the
// memory DC, and BitBlt it into the view.
CDC dcMem;
dcMem.CreateCompatibleDC( pDC );
CBitmap* pbmpOld = dcMem.SelectObject( &bmpHello );
pDC->BitBlt( 10,10, bm.bmWidth, bm.bmHeight,
&dcMem, 0,0, SRCCOPY );
// Reselect the original bitmap into the memory DC.
dcMem.SelectObject( pbmpOld );
}
Before the bitmap is displayed in Listing 15.1, information about the bitmap
is collected using the SelectObject member function, which fills
a BITMAP structure with information. Two pieces of useful information
the
BITMAP structure can provide are the width and height of the
bitmap.
New Term: A memory device context, or memory DC, is a memory location that allows for images to be drawn off-screen, which improves performance.
When displaying a bitmap, the bitmap is first selected into a memory DC. The BitBlt function is used to transfer the image from the memory DC to the view's device context, passed as a parameter to OnDraw. BitBlt is an abbreviation for Bit-Block Transfer, which is the process used to move the image from the memory DC to the actual device context. The first two parameters passed to BitBlt are the coordinates of the destination rectangle.
When you compile and run the Bitmap project, the IDB_HELLO bitmap is displayed in the upper-right corner of the view window. Experiment by changing the size and color of the bitmap or by combining the source code provided in Listing 15.1 with the DrawText function.
If you experiment with the Bitmap program long enough, you might discover a problem that occurs when using bitmaps. Although the background of the IDB_HELLO bitmap is white, it isn't transparent. If the color of the view background is gray or another color, the bitmap will look like a white square containing text. It is possible to display a bitmap with a transparent background, although it takes a great deal of advanced graphics work. Fortunately, the image list, first introduced for Windows 95, has the capability to draw a transparent bitmap. Image lists are thoroughly discussed in Section 17, "Using Image Lists and Bitmaps."
One of the problems with DDBs is that an application must supply bitmaps in a format supported by the driver. The application must either store bitmaps in multiple formats or it must be capable of converting a bitmap from one format into another. Either way, dealing with a DDB can be difficult and time consuming.
A device-dependent bitmap is controlled by the device driver.
Figure 15.3
DIBs contain four data structures.
After the BITMAPINFOHEADER structure, the bmiColors variable marks the beginning of the color table. This table is used if the bitmap isn't a 16-, 24-, or 32-bits-per-pixel bitmap. The color table is an array of RGBQUAD structures, with each entry storing one of the colors used by the bitmap. The members of the RGBQUAD structure are
BYTE rgbBlue; BYTE rgbGreen; BYTE rgbRed; BYTE rgbReserved; // Always set to zeroThe members of the RGBQUAD structure represent the red, green, and blue color intensity for a color stored in the color table. Each structure member has a range of 0-255. If all members have a value of 0, the color is black; if all members have a value of 255, the color is white.
Every entry in the image array refers to a pixel in the displayed image.
If this is a 16-, 24-, or 32-bits-per-pixel bitmap, there was no color table and each element of the array contains the RGB color for a single pixel. In effect, the palette has been moved out to the pixel array for these types of bitmaps.
Before examining the C++ class used to display 256-color bitmaps, it's important to discuss how Windows determines the colors available to your application. Unfortunately, when a bitmap is loaded, Windows makes no special effort to make sure that color entries in the bitmap's color table are added to the system's color palette. The result is an ugly-looking bitmap.
Time Saver: To display a 256-color bitmap, you must always create and manage a logical palette for your application.
The Windows GDI uses palettes to manage color selection for 256-color devices. There are actually several different types of palettes, as shown in Figure 15.5.
The different types of color palettes used in Windows.
Three different types of palettes are shown in Figure 15.5:
Just a Minute: To maintain some level of consistency, Windows reserves the first 10 and last 10 palette entries for its own use, leaving 236 palette entries for application use, as shown in Figure 15.6.
Windows makes 236 palette entries available to applications.
Just a Minute: At first glance, it might seem unfair for 20 entries to be removed from the system palette. These entries are removed to keep the basic window display predictable. The 20 reserved palette entries include the colors used by 16-color VGA devices. As long as these palette entries are available, Windows applications that don't use the palette are displayed as expected.
2. Select the palette into a DC. Unlike other GDI objects, SelectObject doesn't work for logical palettes. You must use the SelectPalette function.
3. Realize the palette. Basically, realizing the palette asks the Windows Palette Manager to add your palette to the system palette or map your palette to a reasonably close substitute. Selecting and realizing the palette always happens at the same time; there's no point in selecting a palette unless you intend to realize it immediately.
4. Use the palette. If the system palette was updated, the application should redraw itself. This is usually done by invalidating any windows that depend on palette entries.
5. Deselect the palette by selecting the previous palette back into the DC.
6. Delete the palette object. This step is usually performed only when you're sure the palette is no longer needed.
#ifndef DIBMP_TYS
#define DIBMP_TYS
class CDIBitmap
{
friend class CBmpPalette;
//constructors
public:
CDIBitmap();
virtual ~CDIBitmap();
private:
CDIBitmap( const CDIBitmap& dbmp ){};
//operations
public:
inline BITMAPINFO* GetHeaderPtr();
inline BYTE* GetPixelPtr();
virtual void DrawDIB ( CDC* pDC, int x, int y );
virtual BOOL Load( CFile* pFile );
RGBQUAD* GetColorTablePtr();
protected:
int GetPalEntries() const;
int GetPalEntries( BITMAPINFOHEADER& infoHeader ) const;
protected:
int GetWidth() const;
int GetHeight() const;
//implementation
private:
BITMAPINFO* m_pInfo;
BYTE* m_pPixels;
};
#endif
Note that CDIBitmap isn't derived from CBitmap, or CObject
for that matter. The class itself consumes only a few bytes, requiring
space for two pointers and a virtual function table. CDIBitmap
has five public functions:
#include "stdafx.h"
#include "dib256.h"
CDIBitmap::CDIBitmap()
{
m_pInfo = 0;
m_pPixels = 0;
}
CDIBitmap::~CDIBitmap()
{
delete [] (BYTE*)m_pInfo;
delete [] m_pPixels;
}
BOOL CDIBitmap::Load( CFile* pFile )
{
ASSERT( pFile );
BOOL fReturn = TRUE;
delete [] (BYTE*)m_pInfo;
delete [] m_pPixels;
m_pInfo = 0;
m_pPixels = 0;
DWORD dwStart = pFile->GetPosition();
// Check to make sure we have a bitmap. The first two bytes must
// be `B' and `M'.
BITMAPFILEHEADER fileHeader;
pFile->Read(&fileHeader, sizeof(fileHeader));
if( fileHeader.bfType != 0x4D42 )
return FALSE;
BITMAPINFOHEADER infoHeader;
pFile->Read( &infoHeader, sizeof(infoHeader) );
if( infoHeader.biSize != sizeof(infoHeader) )
return FALSE;
// Store the sizes of the DIB structures
int cPaletteEntries = GetPalEntries( infoHeader );
int cColorTable = 256 * sizeof(RGBQUAD);
int cInfo = sizeof(BITMAPINFOHEADER) + cColorTable;
int cPixels = fileHeader.bfSize - fileHeader.bfOffBits;
// Allocate space for a new bitmap info header, and copy
// the info header that was loaded from the file. Read the
// the file and store the results in the color table.
m_pInfo = (BITMAPINFO*)new BYTE[cInfo];
memcpy( m_pInfo, &infoHeader, sizeof(BITMAPINFOHEADER) );
pFile->Read( ((BYTE*)m_pInfo) + sizeof(BITMAPINFOHEADER),
cColorTable );
//
// Allocate space for the pixel area, and load the pixel
// info from the file.
m_pPixels = new BYTE[cPixels];
pFile->Seek(dwStart + fileHeader.bfOffBits, CFile::begin);
pFile->Read( m_pPixels, cPixels );
return fReturn;
}
// DrawDib uses StretchDIBits to display the bitmap.
void CDIBitmap::DrawDIB( CDC* pDC, int x, int y )
{
HDC hdc = pDC->GetSafeHdc();
if( m_pInfo )
StretchDIBits( hdc, x, y,
GetWidth(),
GetHeight(),
0, 0,
GetWidth(),
GetHeight(),
GetPixelPtr(),
GetHeaderPtr(),
DIB_RGB_COLORS,
SRCCOPY );
}
BITMAPINFO* CDIBitmap::GetHeaderPtr()
{
return m_pInfo;
}
RGBQUAD* CDIBitmap::GetColorTablePtr()
{
RGBQUAD* pColorTable = 0;
if( m_pInfo != 0 )
{
int cOffset = sizeof(BITMAPINFOHEADER);
pColorTable = (RGBQUAD*)(((BYTE*)(m_pInfo)) + cOffset);
}
return pColorTable;
}
BYTE* CDIBitmap::GetPixelPtr()
{
return m_pPixels;
}
int CDIBitmap::GetWidth() const
{
return m_pInfo->bmiHeader.biWidth;
}
int CDIBitmap::GetHeight() const
{
return m_pInfo->bmiHeader.biHeight;
}
int CDIBitmap::GetPalEntries() const
{
return GetPalEntries( *(BITMAPINFOHEADER*)m_pInfo );
}
int CDIBitmap::GetPalEntries( BITMAPINFOHEADER& infoHeader ) const
{
int nReturn;
if( infoHeader.biClrUsed == 0 )
{
nReturn = ( 1 << infoHeader.biBitCount );
}
else
nReturn = infoHeader.biClrUsed;
return nReturn;
}
Most of the work in the CDIBitmap class is done by the CDIBitmap::Load
member function. This member function takes a pointer to an MFC CFile
object as its only parameter. Later in this section, you will see how the
sample program provides a CFile pointer to this function during
serialization.
After verifying that the CFile object refers to a Windows bitmap, the Load function reads each part of the bitmap data structure and creates a DIB dynamically. Note that there actually are two calls to the new operator; there is no requirement that the DIB exist in one solid chunk of memory. The BITMAPINFOHEADER is stored in one location, and the pixel image array is stored in another location.
The CDIBitmap::DrawDIB member function calls StretchDIBits to display the DIB. Very little work is actually done in this function. For example, the width and height of the DIB are calculated using CDIBitmap member functions.
The remaining member functions are used to calculate various bits of information about the DIB. Only a pointer to the beginning of the BITMAPINFO structure and a pointer to the beginning of the pixel image array are stored; all other information is calculated as it is needed.
#ifndef BMP_PAL_TYS
#define BMP_PAL_TYS
class CBmpPalette : public CPalette
{
public:
CBmpPalette( CDIBitmap* pBmp );
};
#endif
All the work done by CBmpPalette is done in the constructor; there
are no member functions other than the function inherited from CPalette,
the MFC base class. The CBmpPalette class is always used with
CDIBitmap. A pointer to a CDIBitmap object is passed
to CBmpPalette as a constructor parameter.
CBmpPalette allocates a logical palette with enough entries to store the palette required by the CDIBitmap object. After storing some basic palette information, the palette entries are filled in, using the values collected from the CDIBitmap object. After the palette is created, the logical palette is deleted. The implementation from CBmpPalette is provided in Listing 15.5 and is included in the Dib project as dibpal.cpp.
#include "stdafx.h"
#include "dib256.h"
#include "dibpal.h"
CBmpPalette::CBmpPalette( CDIBitmap* pBmp )
{
ASSERT( pBmp );
int cPaletteEntries = pBmp->GetPalEntries();
int cPalette = sizeof(LOGPALETTE) +
sizeof(PALETTEENTRY) * cPaletteEntries;
// Since the LOGPALETTE structure is open-ended, you
// must dynamically allocate it.
LOGPALETTE* pPal = (LOGPALETTE*)new BYTE[cPalette];
RGBQUAD* pColorTab = pBmp->GetColorTablePtr();
pPal->palVersion = 0x300;
pPal->palNumEntries = cPaletteEntries;
// Roll through the color table, and add each color to
// the logical palette.
for( int ndx = 0; ndx < cPaletteEntries; ndx++ )
{
pPal->palPalEntry[ndx].peRed = pColorTab[ndx].rgbRed;
pPal->palPalEntry[ndx].peGreen = pColorTab[ndx].rgbGreen;
pPal->palPalEntry[ndx].peBlue = pColorTab[ndx].rgbBlue;
pPal->palPalEntry[ndx].peFlags = NULL;
}
VERIFY( CreatePalette( pPal ) );
delete [] (BYTE*)pPal;
}
// Operations public: CDIBitmap* GetBitmap(); CPalette* GetPalette(); protected: CDIBitmap m_dib; CBmpPalette* m_pPal;Add the following two #include directives just before the CDibDoc class declaration:
#include "dib256.h" #include "dibpal.h"The CDIBitmap object will be loaded during serialization. After it has been loaded, the CBmpPalette object will be created dynamically. m_pPal, the pointer to CBmpPalette, will be initialized in the constructor and deleted in the destructor. The changes for the constructor, destructor, OnNewDocument, and Serialize member functions for the CDibDoc class are shown in Listing 15.7.
CDibDoc::CDibDoc()
{
m_pPal = 0;
}
CDibDoc::"CDibDoc()
{
delete m_pPal;
}
BOOL CDibDoc::OnNewDocument()
{
if (!CDocument::OnNewDocument())
return FALSE;
delete m_pPal;
m_pPal = 0;
return TRUE;
}
void CDibDoc::Serialize(CArchive& ar)
{
if (ar.IsStoring())
{
TRACE( TEXT("Storing a bitmap is not supported") );
ASSERT(FALSE);
}
else
{
CFile* pFile = ar.GetFile();
ASSERT( pFile );
ar.Flush();
BOOL fLoaded = m_dib.Load( pFile );
if( fLoaded != FALSE )
{
delete m_pPal;
m_pPal = new CBmpPalette( &m_dib );
UpdateAllViews( NULL );
}
else
AfxMessageBox( TEXT("Error Loading Bitmap") );
}
}
As discussed earlier, the CDibDoc class has two new member functions
to return pointers to the bitmap and palette data members. Add the source
code provided in Listing 15.8 to the dibdoc.cpp file.
CDIBitmap* CDibDoc::GetBitmap()
{
return &m_dib;
}
CPalette* CDibDoc::GetPalette()
{
return m_pPal;
}
Using ClassWizard, add message-handling functions for WM_PALETTECHANGED and WM_QUERYNEWPALETTE. Edit the functions using the source code provided in Listing 15.9.
void CMainFrame::OnPaletteChanged(CWnd* pFocusWnd)
{
CView* pView = GetActiveView();
if( pView )
{
HWND hWndFocus = pView->GetSafeHwnd();
pView->SendMessage( WM_PALETTECHANGED,
(WPARAM)hWndFocus,
(LPARAM)0 );
}
}
BOOL CMainFrame::OnQueryNewPalette()
{
CView* pView = GetActiveView();
if( pView )
{
HWND hWndFocus = pView->GetSafeHwnd();
pView->SendMessage( WM_QUERYNEWPALETTE,
(WPARAM)hWndFocus,
(LPARAM)0 );
}
return TRUE;
}
void CDibView::OnDraw(CDC* pDC)
{
CDibDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
CPalette* pPal = pDoc->GetPalette();
CPalette* pOldPal = pDC->SelectPalette( pPal, FALSE );
pDC->RealizePalette();
CDIBitmap* pBmp = pDoc->GetBitmap();
pBmp->DrawDIB( pDC, 0, 0 );
pDC->SelectPalette( pOldPal, FALSE );
}
OnDraw fetches pointers to the bitmap and
palette from CDibDoc, using the new member functions added to
the document class earlier. The palette is selected and realized, and then
the bitmap is drawn. After drawing the bitmap, the previous palette is
selected back into the DC.
The CMainFrame class forwards WM_PALETTECHANGED and WM_QUERYNEWPALETTE messages to the view class. However, there is one small problem: ClassWizard doesn't offer direct support for palette messages sent to child window classes such as CDibView. Therefore, some trickery is required. To add the palette-handling functions, follow these steps:
2. Select the CDibView class.
3. Select the Class Info tab.
4. In the Advanced Options group, click the Message Filter combo box, and select Topmost Frame instead of Child Window.
5. Select the Message Maps tab and add the message-handling functions for WM_PALETTECHANGED and add WM_QUERYNEWPALETTE to the CDibView class.
6. Select the Class Info tab.
7. In the Advanced Options group, click the Message Filter combo box and select Child Window instead of Topmost Frame.
8. Close ClassWizard.
// OnPaletteChanged - Handles WM_PALETTECHANGED, which is a
// notification that a window has changed the current palette. If
// this view did not change the palette, forward this message to
// OnQueryNewPalette so the palette can be updated, and redrawn
// if possible.
void CDibView::OnPaletteChanged(CWnd* pFocusWnd)
{
if( pFocusWnd != this )
OnQueryNewPalette();
}
// Notification that the view is about to become active,
// and the view should realize its palette.
BOOL CDibView::OnQueryNewPalette()
{
CDibDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
CBmpPalette* pPal = (CBmpPalette*)pDoc->GetPalette();
if( pPal )
{
CDC* pDC = GetDC();
CPalette* pOldPal = pDC->SelectPalette( pPal, FALSE );
UINT uChanges = pDC->RealizePalette();
pDC->SelectPalette( pOldPal, FALSE );
ReleaseDC( pDC );
if( uChanges != 0 )
InvalidateRect( NULL );
}
return TRUE;
}
In most cases, OnPaletteChanged calls the OnQueryNewPalette
function directly. The only exception is when the WM_PALETTECHANGED
message was sent because this view had updated the system palette. If this
view is the foreground window, the Windows NT Palette Manager gives you
first crack at setting the system's palette. If you are in the background,
you have access to the unused entries only. If there's no more room in
the palette, your palette is mapped to the closest possible match.
Remember to include the declarations for the CDIBitmap class at the top of the DibView.cpp source file, after the existing #include directives:
#include "dib256.h"Compile and run the Dib example. If you have a 256-color display, load a 256-color bitmap and notice that you receive all the colors. If you run several instances of the program using different 256-color bitmaps, you might notice the palette change if you switch between windows. Figure 15.7 shows the Dib example displaying the 256-color Windows NT logo.
The Dib sample program displaying a 256-color bitmap.
A No, your display will properly manage the colors used by the bitmap. However, if your application is used on a system with a 256-color display, the application will not display the bitmap properly.
Q Can my application use more than one color palette?
A Yes, if you were to build an MDI version of the Dib sample program, each view would need to manage its own color palette.
2. What MFC base class is used to manage color palettes?
3. What type of device context is used to draw into a bitmap off-screen?
4. What function is used to transfer bitmaps to an output device?
5. How many colors are kept by Windows in the system color palette?
6. How many entries in the system color palette are reserved by Windows?
7. What two messages must be handled in order to manage the color palette?
8. What are the differences between the two palette messages?
2. Modify the CBmpPalette class used in the Dib example so that it is monochromatic, with only various shades of one color selected for the palette instead of colors from the bitmap.
Download Sample Programs - Dib.zip
|
|
|
|