Samples in this chapter are specially designed to work on 256-color palette device. To customize them for non-palette devices, we can just eleminate logical palette creation and realization procedure.
10.1 BitBlt and StretchBlt
DIB & DDB
There are two type of bitmaps under Windows(: device independent bitmap (DIB) and device dependent bitmap (DDB). As we know, each computer may be equipped with a different type of device, therefore may use different format to store bitmap images in the hardware. Before displaying any image on the screen, we need to convert the image data to a format that is understandable by the hardware device, otherwise the image could not be displayed correctly. This format is called device dependent bitmap format (DDB), because it is hardware dependent. However, using DDB format will cause incompatibility between different systems, because one DDB format may not be understandable by another device. To solve this problem, under Windows(, a device independent bitmap format (DIB format) is supported by all device drivers. When we call the standard function to load a device independent bitmap, the device driver will convert it to DDB that is understandable by the device.
Drawing DDB
There are several ways to include bitmap image in an application. The simplest one is to treat it as bitmap resource. To load the image, we can call function CBitmap::LoadBitmap(...) and pass the bitmap resource ID to it.
After the bitmap is loaded, we need to output it to the target device (such as screen). The procedure of outputting a bitmap to a target device is different from using a pen or brush to draw a line or fill a rectangle: we cannot select bitmap into the target DC and draw the bitmap directly. Instead, we must create a compatible memory DC and select the bitmap into it, then copy the bitmap from memory DC to the target DC.
The functions that can be used to copy a bitmap between two DCs are CDC::BitBlt(...) and CDC::StretchBlt(...). The former function allows us to copy the bitmap in 1:1 ratio, and the latter one allows us to enlarge or reduce the dimension of the original image. Lets first take a look at the first member function:
BOOL CDC::BitBlt
(
int x, int y, int nWidth, int nHeight,
CDC *pSrcDC,
int xSrc, int ySrc,
DWORD dwRop
);
There are eight parameters, first four of them specify the origin and size of the target bitmap that will be drawn. Here x and y can be any position in the target device, also, nWidth and nHeight can be less than the dimension of source image (In this case, only a portion of the source image will be drawn). The fifth parameter is a pointer to the source DC. The seventh and eighth parameters specify the origin of the source bitmap. Here, we can select any position in the source bitmap as origin. The last parameter specifes the bitmap drawing mode. We can draw a bitmap using many modes, for example, we can copy the original bitmap to the target, turn the output black or white, do bit-wise OR, AND or XOR operation between source bitmap and target bitmap.
Creating Memory DC
A memory DC used for copying bitmap image must be compatible with the target DC. We can call function CDC::CreateCompatibleDC(...) to create this type of DC. The following is the format of this function:
BOOL CDC::CreateCompatibleDC(CDC* pDC);
The only parameter to this function (pDC) must be a pointer to the target DC.
Retrieving the Dimension of Bitmap Image
We see that in order to copy the bitmap from one DC to another, we need to know the dimension of the bitmap. The bitmap size, along with other information, can be retrieved by calling function CBitmap::GetBitmap(...). If we pass a BITMAP type pointer to this function, the object will be filled with the bitmap information. The bitmap dimension is stored in members bmWidth and bmHeight of structure BITMAP.
Sample 10.1\GDI
Sample 10.1-1\GDI demonstrates how to use function CDC::BitBlt(...). It is a standard SDI application generated by Application Wizard, and its view is based on class CScrollView. In the sample, first a bitmap resource is added to the application, whose ID is IDB_BITMAP.
A CBitmap type variable is declared in class CGDIDoc, it will be used to load this bitmap:
(Code omitted)
Variable m_bmpDraw will be used to load the bitmap, and function GetBitmap() will be used to access it outside class CGDIDoc. Bitmap IDB_BITMAP is loaded in the constructor of class CGDIDoc:
(Code omitted)
In function CGDIView::OnInitialUpdate(), we need to use bitmap dimension to set the total window scroll sizes so that if the window is not big enough, we can scroll the image to see the covered portion:
(Code omitted)
The bitmap pointer is obtained from the document. By calling function CBitmap::GetBitmap(...), all the bitmap information (including its dimension) is obtained and stored in variable bm. Then the scroll sizes are set using bitmap dimension. By doing this, the scroll bars will pop up automatically if the dimension of the client window becomes smaller than the dimension of the bitmap.
Function CGDIView::OnDraw(...) is implemented as follows:
(Code omitted)
First we call CDC::CreateCompatibleDC(...) to create a compatible memory DC, then use it to select the bitmap obtained from the document. Like other GDI objects, after using a bitmap, we need to select it out of the DC. For this purpose, a local variable pBmpOld is used to store the returned address when we call CDC::SelectObject(...) to select the bitmap (pBmp) into memory DC. After the bitmap is drawn, we call this function again to select pBmpOld, this will select bitmap stored by pBmp out of the DC.
In the next step function CBitmap::GetBitmap(...) is called to retrieve all the bitmap information into variable bm, whose bmHeight and bmWidth members (represent the dimension of bitmap) will be used for copying the bitmap. Then we call CDC::BitBlt(...) to copy the bitmap from the memory DC to the target DC. The origin of the target bitmap is specified at (0, 0), also, the source bitmap and target bitmap have the same size.
Sample 10.1-2\GDI
Sample 10.1-2\GDI demonstrates how to use function CDC::StretchBlt(...) to output the bitmap image. It is based on sample 10.1-1\GDI. In this sample, the image is enlarged to twice of its original size and output to the client window.
Because the target image has a bigger dimension now, we need to adjust the scroll sizes. First function CGDIView::OnInitialUpdate() is modified as follows for this purpose:
(Code omitted)
The scroll sizes are set to twice of the bitmap size.
Function CDC::StretchBlt(...) has 10 parameters:
(Code omitted)
There are two extra parameters nSrcWidth and nSrcHeight here (compared to function CDC::BitBlt()), which specify the extent of original bitmap that will be output to the target. Obviously, nWidth and nSrcWidth determine the horizontal ratio of the output bitmap (relative to source bitmap). Likewise, nHeight and nSrcHeight determine the vertical ratio.
In the sample, both horizontal and vertical ratios are set to 200%, and function CGDIView::OnDraw(...) is modified as follows:
(Code omitted)
With the above modifications, we will have an enlarged bitmap image in the client window.
10.2 Extracting Palette from DIB
If we execute the previous two samples on a palette device, we may experience color distortion (Please open file 10.1-1\bitmap.bmp using a standard graphic editor and compare the results). This is because we didn't implement logic palette for displaying the bitmap, so the the colors of the bitmap pixels are mapped to the nearest colors available in the system. To avoid color distortion, we must implement logical palette before drawing the bitmap.
Another problem is that when we call function CBitmap::LoadBitmap(...) to load the bitmap resource, it will create a device dependent bitmap from the data stored in the resource. So after the the bitmap is loaded, it becomes DDB, which could only be understood by the device. To extract palette information from the bitmap data, we must use DIB format.
To avoid color distortion, we must implement logical palette for device dependent bitmap before it is drawn to the target device. Because function CBitmap::LoadBitmap(...) does not extract palette and the bitmap data stored in the resource is in the format of DIB, we must convert the bitmap to DDB and extract palette information from it by ourselves.
DIB Format
A DIB comprises three parts: bitmap information header, color table, and bitmap bit values. The bitmap information header stores the information about the bitmap such as its width, height, bit count per pixel, etc. The color table contains an array of RGB colors, it can be referenced by the color indices. The bitmap bit values represent bitmap pattern by specifying an index into the color table for every pixel. The color table can also be empty, in which case the bitmap bit vlues must be actual R, G, B combinations.
There are several type of DIBs: monocrome, 16 colors, 256 colors and 24 bit. The first three formats use color table and color indices to form a bitmap. The last format does not have a color table and all the pixels are represented by R, G, B combinations.
The following is the format of bitmap information header:
typedef struct tagBITMAPINFOHEADER{
DWORD biSize;
LONG biWidth;
LONG biHeight;
WORD biPlanes;
WORD biBitCount
DWORD biCompression;
DWORD biSizeImage;
LONG biXPelsPerMeter;
LONG biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
} BITMAPINFOHEADER;
It has 11 members, the most important ones are biSize, biWidth, biHeight, biBitCount and biSizeImage.
Member biSize specifies the length of this structure, which can be specified by sizeof(BITMAPINFORHEADER). Members biWidth and biHeight specify the dimension of the bitmap image. Member biBitCount specifies how many bits are used to represent one pixel (Bit count per pixel). This factor determines the total number of colors that can be used by the bitmap and also, the size of the color table. For example, if this member is 1, the bitmap can use only two colors. In this case, the size of color table is 2. If it is 4, the bitmap can use up to 16 colors and the size of the color table is 16. The possible values of this member are 1, 4, 8, 16, 24, and 32. Member biSizeImage specifies the total number of bytes that must be allocated for storing image data. This is a very important member, because we must know its value before allocating memory.
An image is composed of multiple raster lines, each raster line is made up of an array of pixels. To speed up image loading, each raster line must use a multiple of four-byte buffers (This means if we have a 2-color (monochrom) 1(1 bitmap, we need four bytes instead of one byte to store only one pixel). Because of this, the value of biSizeImage can not be simply calculated by the following fomulae:
biHeight*biWidth*biBitCount/8
We will discuss how to calculate this value later.
For bitmaps under Windows(, member biPlanes is always set to 1. If the bitmap is not compressed, member biCompress should be set to BI_RGB.
The next part of the DIB data is color table, which comprises an array of RGBQUAD objects. The bitmap information header and color table together form a bitmap header, which can be described by a BITMAPINFO structure:
typedef struct tagBITMAPINFO {
BITMAPINFOHEADER bmiHeader;
RGBQUAD bmiColors[1];
} BITMAPINFO;
DIB Example
Following the bitmap header are bitmap bit values. Image data is stored in the memory one pixel after another from left to right, and vertically from the bottom to the top. The following is an example of 3(4 image:
(Table omitted)
This image has only two colors: black and white. If we store it using monochrome DIB format (2 colors), we need only 3 bits to store one raster line. For 16-color DIB format, we need 12 bits to store one line. Since each raster line must use multiple of four-byte buffers (32 bits), if one raster line can not use up all the bits, the rest will simply be left unused.
The following table compares four different types of DIB formats by listing the following information: the necessary bits needed, the actual bits used, and number of bits wasted by one raster line:
(Table omitted)
We can define a macro that allows us to calculate the number of bytes needed for each raster line for different bitmap formats:
#define WIDTHBYTES(bits) ((((bits)+31)/32)*4)
Here bits represents the number of bits that are needed for one raster line, it can be obtained from BITMAPINFORHEADER by doing the following calculation:
biWidth(biBitCount
Now we know how to calculate the value of biSizeImage from other members of structure BITMAPINFOHEADER:
WIDTHBYTES(biWidth*biBitCount)*biHeight
The following is the image data for the above DIB example, assume all unused bits are set to 0:
2 color bitmap (assuming in the color table, index to white color = 0, index to black color = 1):
C0 00 00 00
40 00 00 00
C0 00 00 00
40 00 00 00
4 color bitmap (assuming in the color table, index to white color = 0, index to black color = 15):
F0 00 00 00
0F 00 00 00
F0 F0 00 00
0F 00 00 00
8 color bitmap (assuming in the color table, index to white color = 0, index to black color = 255):
FF 00 FF 00
00 FF 00 00
FF 00 FF 00
00 FF 00 00
24 bit color bitmap:
00 00 00 FF FF FF 00 00 00 00 00
FF FF FF 00 00 00 FF FF FF 00 00
00 00 00 FF FF FF 00 00 00 00 00
FF FF FF 00 00 00 FF FF FF 00 00
The bitmap resource is stored exactly in the format mentioned above. To avoid color distortion, we need to extract the color table contained in the DIB to create logic palette, and convert DIB to DDB before drawing it.
Creating DDB from DIB
To create a DDB from DIB data, we need to call function ::CreateDIBitmap(...), which has the following format:
HBITMAP ::CreateDIBitmap
(
HDC hdc,
CONST BITMAPINFOHEADER *lpbmih, DWORD fdwInit,
CONST VOID *lpbInit, CONST BITMAPINFO *lpbmi, UINT fuUsage
);
The first parameter of this function is a handle to target DC. Because DDB is device dependent, we must know the DC information in order to create the bitmap. The second parameter is a pointer to BITMAPINFORHEADER type object, it contains bitmap information. The third parameter is a flag, if we set it to CBM_INIT, the bitmap will be initialized with the data pointed by lpbInit and lpbmi; if this flag is 0, a blank bitmap will be created. The final parameter specifies how to use color table. If the color table is contained in the bitmap header, we can set its value to DIB_RGB_COLORS.
Loading Resource
To access data stored in the resource, we need to call the following three funcitons:
HRSRC ::FindResource(HMODULE hModule, LPCTSTR lpName, LPCTSTR lpType);
HGLOBAL ::LoadResource(HMODULE hModule, HRSRC hResInfo);
LPVOID ::LockResource(HGLOBAL hResData);
The first function will find the specified resource and return a resource handle. When calling this function, we need to provide module handle (which can be obtained by calling function AfxGetResourceHandle()), the resource ID, and the resource type. The second function loads the resource found by the first function. When calling this function, we need to provide the module handle, along with the resource handle returned from the first function. The third function locks the resource. By doing this, we can access the data contained in it. The input parameter to this function must be the global handle obtained from the second function.
Sample
Sample 10.2\GDI demonstrates how to extract color table from DIB and convert it to DDB. It is based on sample 10.1-2\GDI.
Because we need to implement logical palette, first a variable and a function are added to class CGDIDoc:
(Code omitted)
Variable m_palDraw is added for creating logical palette and function GetPalette() is used to access it outside class CGDIDoc.
In class CGDIView, some new variables are added for bitmap drawing:
(Code omitted)
Variable m_dcMem will be used to implement memory DC at the initialization stage of the client window. It will be used later for drawing bitmap. By implementing memory DC this way, we don't have to create it every time. Also, we will select the bitmap and palette into the memory DC after they are avialabe, and selet them out of the DC when the window is being destroyed. For this purpose, two pointers m_pPalMemOld and m_pBmpMemOld are declared, they will be used to select the palette and bitmap out of DC. Variable m_bmInfo is used to store the information of bitmap.
The best place to create bitmap and palette is in CGDIView::OnInitialUpdate(). First, we must locate and load the bitmap resource:
(Code omitted)
We call funcitons ::FindResource(...) and ::LoadResource(...) to load the bitmap resource. Function ::FindResource(...) will return a handle to the specified resource block, which can be passed to ::LoadResource(...) for loading the resource. This function returns a global memory handle. We can call ::LockResource(...) to lock the resource. This function will return an address that can be used to access the bitmap data. In the sample, we use pointer lpBi to store this address.
Next, we must calculate the size of color table and allocate enough memory for creating logical palette. The color table size can be calculated from "bit count per pixel" information of the bitmap as follows:
(Code omitted)
The color table size is stored in variable nSizeCT. Next, the logical palette is created from the color table stored in the DIB data:
(Code omitted)
The color table is obtained from member bmiColors of structure BITMAPINFO (pointed by lpBi). Since the palette is stored in the document, we first call CGDIDoc::GetPalette() to obtain the address of the palette (CGDIDoc::m_palDraw), then call CPalette::CreatePalette(...) to create the palette. After this we select the palette into the client DC, and call CDC::RealizePalette() to let the logical palette be mapped to the system palette.
Then, we create the DDB from DIB data:
(Code omitted)
Function ::CreateDIBitmap(...) returns an HBITMAP type handle, which must be associated with a CBitmap type varible by calling function CBitmap::Attach(...).
The rest part of this funciton fills variable CGDIView::m_bmInfo with bitmap information, sets the scroll sizes, create the memory DC, select bitmap and palette into it, then free the bitmap resource loaded before:
(Code omitted)
Because the bitmap and the palette are selected into the memory DC here, we must select them out before application exits. The best place to do this is in WM_DESTROY message handler. In the sample, a WM_DESTROY message handler is added to class CGDIView through using Class Wizard, and the corresponding function CGDIView::OnDestroy() is implemented as follows:
(Code omitted)
We must modify function CGDIView::OnDraw(...). Since everything is prepared at the initialization stage (the memory DC, the palette, the bitmap), the only thing we need to do in this function is calling CDC:: StrethBlt(...) or CDC::BitBlt(...) to copy the bitmap from memory DC to target DC:
(Code omitted)
With the above implementations, the bitmap will become more vivid.
10.3 Loading DIB from File
Now that we understand how to convert a DIB to DDB, it is fairly easy for us to load a device independent bitmap into memory and convert it to DDB. Sample 10.3\GDI demonstrates how to read DIB file and display the image in the clinet window. It is based on sample 10.2\GDI.
File Format
All bitmap images stored on the hard disk are in the format of DIB, and therefore can be any of the following formats: monochrome, 16 color, 256 color and 24-bit. The difference between DIB stored in a file and DIB stored as a resource is that there is an extra bitmap file header for DIB stored in file. This header has the following format:
typedef struct tagBITMAPFILEHEADER {
WORD bfType;
DWORD bfSize;
WORD bfReserved1;
WORD bfReserved2;
DWORD bfOffBits;
} BITMAPFILEHEADER;
This header specifies three factors: the file type, the bitmap file size, and the offset specifying where the DIB data really starts. So a real DIB file has the following format:
BITMAPFILEHEADER
BITMAPINFOHEADER
Color Table
Bitmap Bits
Member bfType must be set to 'BM', which indicates that the file is a bitmap file. Member bfSize specifies the whole length of the bitmap file, which is counted from the beginning of the file to its end. Member bfOffBits specifies the offset from BITMAPFILEHEADER to bitmap bits, which should be the size of BITMAPINFOHEADER plus the size of color table.
Supporting File Type
Using SDI or MDI model, it is easy to support certain type of files in "Open" or "Save As" common dialog box (activated when the user executes File | Open or File | Save As command). This feature can be added by changing IDR_MAINFRAME string resource. By default, this string resource contains the following sub-strings (Sub-strings are separated by '\n'):
GDI\n\nGDI\n\n\nGDI.Document\nGDI Document
The meaning of each sub string is listed in the following table:
(Table omitted)
By default, no special file types are supported. If we want the file dialog box to contain certain filters, we need to change the fourth and fifth items of the above string. In the sample, string resource IDR_MAFRAME is changed to the following:
GDI\n\nGDI\nBitmap Files(*.bmp)\n.bmp\nGDI.Document\nGDI Document
The fourth item (Bitmap Files(*.bmp)) is a descriptive name for bitmap files, and the fifth item (.bmp) is the filter.
Loading DIB through Serialization
Since we are going to store data read from the file in globally allocated memory, first we must add an HGLOBAL type varible (along with a member function) to class CGDIDoc. Because DIB data will be changed to DDB data before drawing, a CBitmap type variable and its associated funciton are also declared in class CGDIDoc:
(Code omitted)
Variable m_hDIB is initialized in the constructor:
CGDIDoc::CGDIDoc()
{
m_hDIB=NULL;
}
We will allocate global memory each time a new bitmap file is opened. So when the application exits, we need to check variable m_hDIB to see if the memory has been allocated. If so, we need to release it:
(Code omitted)
We need to modify function CGDIDoc::Serialize(...) to load data from DIB file. First, when a file is being opened, variable m_hDIB may be currently in use. In this case, we need to release the global memory:
(Code omitted)
Reading data from a DIB file needs the following steps:
1) Read bitmap file header.
2) Verify that the the file format is correct.
3) From the bitmap file header, calculate the total memory needed, then allocate enough buffers.
4) Read the DIB data into the allocated buffers.
We can call CArchive::Read(...) to read bytes from the file into the memory. In the sample, first bitmap file header (data contained in structure BITMAPFILEHEADER) is read:
(Code omitted)
Then the file type is checked. If it is DIB format, global memory is allocated, which will be used to store DIB data:
(Code omitted)
Next, the DIB data is read into the global memory:
(Code omitted)
This completes reading the bitmap file. The DIB data is stored in m_hDIB now.
Creating DDB
On the view side, we need to display the bitmap stored in memory (whose handle is m_hDIB) instead of the bitmap stored as resource. So we need to modify function CGDIView::OnInitialUpdate(), which will be called whenever a new file is opened successfully. First, instead of obtaining data from the resource, function CGDIDoc::GetHDib() is called to get the handle of DIB data:
(Code omitted)
Remember in the previous sample, after the DDB and palette are created, we will select them into the memory DC so that the image can be drawn directly later. In this sample, when a new DIB file is opened, there may be a bitmap (also a palette) that is being currently selected by the memory DC. If so, we need to select it out of the memory DC, then lock the global memory and obtain its address that can be used to access the DIB (In order to create DDB, we need the information contained in the DIB):
(Code omitted)
The rest portion of this function calculates the size of the color table, creates the palette (If there is an existing palette, delete it first), and creates DDB from DIB data. Then the newly created bitmap and palette are selected into the memory DC, and m_bmInfo is updated with the new bitmap information. Finally, the global memory is unlocked.
Other functions remain unchanged. With the above implementation, we can open any DIB file with our application and display the image in the client window.
10.4 Saving DDB to File
We cannot save DDB data directly to a file. Saving bitmap to a file is a reverse procedure of loading it from the disk: we must first convert the DDB back to DIB then write the data to file.
Converting DDB to DIB
To convert a DDB to DIB, we need to call API function ::GetDIBits(...) to receive the bitmap data in DIB format. When calling this function, we must allocate enough buffers for receiving data. As we know, DIB data contains three parts: header BITMAPINFOHEADER, whose size is fixed; color table, whose size depends on the data format; bitmap bit values, whose size depends upon both the bitmap format and bitmap dimension.
We need to calculate the size of color table and bitmap bit values before allocating memory for storing DIB. The attributes of a DDB can be obtained from function CBitmap::GetBitmap(...). The information will be stuffed into a BITMAP type object, from which we know the properties of a bitmap such as its height and width, bit count per pixel. The size of the color table can be calculated from bit count per pixel information.
Lets further take a look at function ::GetDIBits(...):
(Code omitted)
Its parameters are similar to that of funciton ::CreateDIBitmap(). Since DDB is device dependent, we must know the attribute of device context in order to convert DDB to DIB. So we must pass the handle of the target DC to the first parameter of this function. Parameter uStartScan and uScanLines specify the starting raster line and total number of raster lines whose data is to be retrieved. Parameter lpBits specifies the buffer address that can be used to recieve bitmap data. Pointer lpbi provides a BITMAPINFO structure specifying the desired format of DIB data.
When calling this function, we can pass NULL to pointer lpvBits. This will cause the function to fill the bitmap information into a BITMAPINFO object. By doing this, we can get the color table that is being used by the DDB.
So the conversion takes three steps: 1) Call function CBitmap::GetBitmap(...) to obtain the information of the bitmap, calculate the color table size, allocate enough buffers for storing bitmap information header and color table. 2) Call function ::GetDIBits(...) and pass NULL to parameter lpvBits to receive bitmap information header and color table. 3) Reallocate buffers for storing bitmap data and call ::GetDIBits(...) again to get the DIB data.
New Functions
Sample 10.4\GDI demonstrates how to convert DDB to DIB and save the data to hard disk.
First some functions are added to class CGDIDoc, they will be used for converting DDB to DIB:
(Code omitted)
Function CGDIDoc::ConvertDDBtoDIB(...) converts DDB to DIB, its input parameter is a CBitmap type pointer and its return value is a global memory handle. Function CGDIDoc::GetColorTableSize(...) is used to calculate the size of color table from bit count per pixel information (In the previouse samples, color table size calculation is implemented within function CGDIView::OnInitialUpdate(). Since we need color table size information more frequently now, this calculation is implemented as a single member function):
(Code omitted)
In function CGDIDoc::ConvertDDBtoDIB(...), first we must obtain a handle to the client window that can be used to create a DC:
(Code omitted)
Then function CBitmap::GetBitmap(...) is called to retrieve the information of bitmap and allocate enough buffers for storing structure BITMAPINFOHEADER and color table:
(Code omitted)
We first fill the information obtained previously into a BITMAPINFOHEADER object. This is necessary because when calling function ::GetDIBits(...), we need to provide a BITMAPINFOHEADER type pointer which contains useful information. Here, some unimportant members of BITMAPINFOHEADER are assigned 0s (biSizeImage, biXPelsPerMeter...). Then the size of the color table is calculated and a global memory that is big enough for holding bitmap information header and color table is allocated, and the bitmap information header is stored into the buffers. We will use these buffers to receive color table.
Although the memory size for storing bitmap data can be calculated from the information already known, usually it is not done at this point. Generally the color table and the bitmap data are retrieved separately, in the first step, only the memory that is big enough for storing structure BITMAPINFOHEADER and the color table is prepared. When color table is being retrieved, the bitmap information header will also be updated at the same time. Since it is more desirable to calculate the bitmap data size using the updated information, in the sample, the memory size is updated after the color table is obtained successfully, and the global memory is reallocated for retrieving the bitmap data.
We also need to select logical palette into the DC and realize it so that the bitmap pixels will be intepreted by its own color table.
Function ::GetDIBits(...) is called in the next step to recieve BITMAPINFOHEADER data and the color table. Because some device drivers do not fill member biImageSize (This member carries redunant information with members biWidth, biHeight, and biBitCount), we need to calculate it if necessary:
(Code omitted)
Now the size of DIB data is already known, we can reallocate the buffers, and call function ::GetDIBits(...) again to receive bitmap data. Finally we need to select the logical palette out of the DC, and return the handle of the global memory before function exits:
(Code omitted)
Using New Functions
Using function CGDIDoc::ConvertDDBtoDIB(...), it is farily easy to save the bitmap into a file. All we need is to call this function to convert DDB to DIB, add a BITMAPFILEHEADER structure to it, and write the whole data into a file. In the sample, file saving is implement in function CGDIDoc::Serialize(...):
(Code omitted)
In the sample, command File | Save is disabled so that the user can only save the image through File | Save As command by specifying a new file name. To implement this, UPDATE_COMMAND_UI message handlers are added for both ID_FILE_SAVE and ID_FILE_SAVE_AS commands, and the corresponding member functions are implemented as follows:
(Code omitted)
It seems unnecessary to conver the DDB to DIB before saving the image to a disk file because its original format is DIB. However, if the DDB is changed after being loaded (This is possible for a graphic editor application), the new DDB is inconsistent with the original DIB data.
The DDB to DIB converting procedure is a little complex. If we are programming for Windows 95 or Windows NT 4.0, we can create DIB section (will be introduced in later sections) to let the format be converted automatically. If we are writing Win32 programs that will be run on Windows 3.1, we must use the method discussed in this section to implement the conversion.
10.5 Drawing DIB Directly
The counterpart function of ::GetDIBits(...) is ::SetDIBits(...), it can be used to convert DIB data to device dependent bitmap. The two functions provide us a way of implementing image editing: whenever we want to make change to the image, we can first retrieve DIB data from the DDB, edit the DIB data, and set it back to DDB.
New Functions
Sometimes it is easier to edit DDB directly instead of using DIB data. For example, if we want to reverse every pixel of the image, we can just call one API funciton to let this be handled by lower level driver instead of editting every single pixel by ourselves. This is why we need to handle both DIB and DDB in the applications.
If our application is restricted on edittng only DIB data, we can call an API function directly to draw DIB in the client window. By doing so, we eleminte the complexity of converting DIB to DDB back and forth. This function is ::SetDIBitsToDevice(...), which has the following format:
(Code omitted)
There are altogether 12 parameters, whose meanings are listed in the following table:
(Table omitted)
This function can output image at 1:1 ratio with respect to the source image. Similar to CDC::BitBlt(...) and CDC::StretchBlt(...), there is another function ::StretchDIBits(...), which allows us to enlarge or reduce the original image and output it to the target device:
(Code omitted)
The ratio between source and target image can be set through the following four parameters: nDestWidth, nDestHeight, nSrcWidth, nSrcHeight.
Modifications Made to Document
Sample 10.5\GDI is based on sample 10.4\GDI, it demonstrates how to use the above two functions.
With the new functions, many implementations in the previouse sample can be eleminated. First, on the document side, we no longer need CBitmap type variable (CGDDoc::m_bmpDraw) any more. We will use CGDIDoc::m_hDIB to store DIB data, which can be used directly to draw the image. Also we do not need function CGDIDoc::ConvertDDBtoDIB(...). Function CGDIDoc::GetColorTableSize(...) is still needed because we can use it to calculate the size of BITMAPINFO structure (BITMAPINFOHEADER and color table). In the sample, variable CGDIDoc::m_bmpDraw and function CGDIDoc::ConvertDDBtoDIB() are removed. The following is the modified class:
(Code omitted)
When saving image to file, we do not need to convert DDB to DIB any more. Instead, CGDIDoc::m_hDIB can be used directly for storing data:
(Code omitted)
Note since biSizeImage member of BITMAPINFOHEADER structure may be zero, we need to calculate its value before saving the image to file. Also, the original statement for releasing global memory is deleted because CGDIDoc::m_hDIB is the only variable that is used for storing image in the application.
Function CGDIDoc::OnUpdateFileSaveAs(...) is changed to the following:
void CGDIDoc::OnUpdateFileSaveAs(CCmdUI* pCmdUI)
{
pCmdUI->Enable(m_hDIB != NULL);
}
Modifications Made to View
On the view side, we do not need memory DC any more, so three variables CGDIView::m_dcMem, CGDIView::m_pBmpMemOld and CGDIView::m_pPalMemOld are deleted. Since the image size can be obtained from DIB data, variable CGDIView::m_bmInfo can also be eleminated. The following code fragment shows the modified class CGDIView:
(Code omitted)
In function CGDIView::OnInitialUpdate(), there is no need to create DDB any more. So in the updated function, only the logical palette is created:
(Code omitted)
In this functon, DIB handle is obtained from the document and locked. From the global memory buffers, the color table contained in the DIB is obtained and is used for creating the logical palette. The a flag is set to indicate that the bitmap is loaded successfully.
In function CGDIView::OnDraw(...), the DIB is painted to the client window:
(Code omitted)
The procedure of selecting and realizing the logical palette is the same with the previous sample. The difference between them is that function CDC::BitBlt(...) is replaced by function ::SetDIBitsToDevice(...) here.
Message handler CGDI::OnDestroy() is removed through using Class Wizard in the sample. The reason for this is that we no longer need to select objects (palette, bitmap) out of memory DC any more. Also, the constructor of CGDIView is changed as follows:
(Code omitted)
With the above modification, the application is able to display any DIB image without doing DIB to DDB conversion.
10.6 Bitmap Format Conversion: 256-color to 24-bit
Now that we understand different DIB formats, we can easily implement conversion from one format to another. Sample 10.6\GDI demonstrates how to convert 256-color DIB format to 24-bit format, it is based on sample 10.5\GDI.
Conversion
We need to delete the color table and expand the indices to explicit RGB combinations in order to implement this conversoin. Also in the bitmap information header, we need to change the value of member biBitCount to 24, and recalculate member biImageSize. There is also another difference in the bitmap header bwteen 256-color and 24-bit formats: for DIB that does not contain the color table, member biClrUsed is 0; for DIB that contains the color table, this member specifies the number of color indices in the color table that are actually used by the bitmap.
Current Format
In the sample, a new command Convert | 256 to RGB is added to the mainframe menu IDR_MAINFRAME, whose command ID is ID_CONVERT_256TORGB. Also, WM_COMMAND and UPDATE_COMMAND_UI message handlers are added for this command through using Class Wizard. The corresponding functions are CGDIDoc::OnConvert256toRGB() and CGDIDoc::OnUpdateConvert256toRGB(...) respectively. This command will be used to convert the image from 256-color format to 24-bit format. We want to disable this menu item if the current DIB is not 256 color format.
Before doing the conversion, we must know the current format of the image. So in the sample, a new variable is declared in class CGDIDoc for this purpose:
class CGDIDoc : public CDocument
{
protected:
......
int m_nBmpFormat;
......
}
The following macros are defined in the header file of class CGDIDoc:
#define BMP_FORMAT_NONE 0
#define BMP_FORMAT_MONO 1
#define BMP_FORMAT_16COLOR 2
#define BMP_FORMAT_256COLOR 3
#define BMP_FORMAT_24BIT 4
Variable CGDIDoc::m_nBmpFormat is initialized in the constructor:
CGDIDoc::CGDIDoc()
{
......
m_nBmpFormat=BMP_FORMAT_NONE;
}
Whenever a DIB is loaded, function CGDIDoc::GetColorTableSize(...) will be called by CGDIView:: OnInitialUpdate(), so it is convenient to set CGDIDoc::m_nBmpFormat to an appropriate value to indicate the image format in this function. The following code fragment shows the modified function CGDIDoc::GetColorTableSize():
(Code omitted)
Function Implementation
Function CGDIDoc::OnUpdateConvert256toRGB(...) is implemented as follows so that the menu command will be enabled only when the current DIB format is 256-color:
void CGDIDoc::OnUpdateConvert256toRGB(CCmdUI* pCmdUI)
{
pCmdUI->Enable(m_nBmpFormat == BMP_FORMAT_256COLOR);
}
In function CGDIDoc::OnConvert256toRGB(), first we need to lock the current DIB data, calculate the size of new DIB data (after format conversion) and allocate enough buffers:
(Code omitted)
The new DIB size is stored in local variable dwSize. Here macro WIDTHBYTES is used to calculate the actual bytes needed for one raster line (We use 24 instead of member biBitCount when using this macro to implement calculation for the new format). The size of new DIB data is the size of BITMAPINFOHEADER structure plus the size of bitmap data (Equal to bytes needed for one raster line multiplied by the height of bitmap, there is no color table any more). Then we allocate buffers from global memory and lock them, whose address is stored in pointer lpBi24.
Then we need to fill structure BITMAPINFOHEADER. Most of the members are the same for two different formats, such as biHeight, biWidth. There are three members we need to change: biBitCount must be set to 24, biImageSize should be recalculated, and biClrUsed needs to be 0:
(Code omitted)
Then we need to fill the DIB bit values. The image is converted pixel by pixel using two loops (one is embedded within another): the outer loop converts one raster line, and the inner loop converts one single pixel. As we move to a new raster line, we need to calculate the starting buffer address so that it can be used as the origin of the pixels for the whole raster line (For each single pixel, we can obtain its address by adding an offset to the origin address). The starting address of each raster line can be calculated through multiplying the current line index (0 based) by total number of bytes needed for one raster line. As we move from one pixel to the next of the same raster line, we can just move to the neighboring buffer (for RGB format, next three buffers). However, the final pixel of one raster line and the first pixel of next raster line may not use neighboring buffers, this is because there may exist some unused bits between them (Since each raster line must use a multiple of 4-byte buffers). The following portion of function CGDIDoc::OnConvert256toRGB() shows how to convert bitmap pixels from one format to another:
(Code omitted)
Finally, we must unlock the global memory, release the previous DIB data and assign the new memory handle to CGDIDoc::m_hDIB. We also need to inform the view to reload the image because the bitmap format has changed. For this purpose, a new function CGDIView::LoadBitmap(...) is implemented, it will be called from CGDIDoc::OnConvert256toRGB() and CGDIView::OnInitialUpdate() (The original portion of this funciton that loads the bitmap is replaced by calling the new function). The following is the portion of funciton CGDIDoc::OnConvert256toRGB() which shows what should be done after the format is converted:
(Code omitted)
Function CGDIView::LoadBitmap(...) should implement the following: delete the old palette, check if the DIB contains color table. If so, create a new palette. The function is declared in class CGDIView as follows:
(Code omitted)
The function is implemented as follows (Most part of this function is copied from function CGDIView::OnInitialUpdate()):
(Code omitted)
With this function, CGDIView::OnInitialUpdate() can be simplified to the following:
(Code omitted)
With the above implementation, the application is able to convert a bitmap from 256-color format format to 24-bit format.
10.7 Converting 24-bit Format to 256-color Format
Sample 5.7\GDI is based on sample 5.6\GDI, it demonstrates how to convert 24-bit bitmap format to 256-color format.
Two Cases
To convert a 24-bit format bitmap to 256-color format bitmap, we must extract a color table from the explicit RGB values. There are two cases that must be handled differently: the bitmap uses less than 256 colors, and the bitmap uses more than 256 colors.
If the bitmap uses less than 256 colors, the conversion is relatively simple: we just examine every pixel of the bitmap, and extract a color table from all the colors contained in the bitmap.
The following is the conversion procedure for this situation: At the beginning, the color table contains no color. Then for each pixel in the bitmap, we examine if the color is contained in the color table. If so, we move to the next pixel. If not, we add the color used by this pixel to the color table. After we go over all the pixels contained in the bitmap, the color table should contain all the colors that are used by the bitmap image.
If the bitmap uses more than 256 colors, we must find 256 colors that best represent all the colors used by the image. There are many algorithms for doing this, a relatively simple one is to omit some lower bits of RGB values so that maximum number of colors used by a bitmap does not exceed 256. For example, 24-bit bitmap format uses 8 bit to represent a basic color, it can result in 256(256(256 different colors. If we use only 3 bits to represent red and green color, and use 2 bits to represent blue color, the total number of possible combinations are 8(8(4=256.
In this situation, when we examine a pixel, we use the 3 most significant bits of red and green colors, along with 2 most significant bits of blue color to form a new color that will be used to create color table (Other bits will be filled with 0s). By doing this, the colors contained in the color table will not exceed 256. Although this algorithm may result in color distortion, it is relatively fast and less image dependent.
Sample
In the sample, a new command Convert | RGB to 256 is added to mainframe menu IDR_MAINFRAME, whose command ID is ID_CONVERT_RGBTO256. Also, WM_COMMAND and UPDATE_COMMAND_UI message handlers are added through using Class Wizard. The new corresponding functions are CGDIDoc:: OnConvertRGBto256() and CGDIDoc::OnUpdateConvertRGBto256(...) respectively.
Function CGDIDoc::OnUpdateConvertRGBto256(...) is implemented as follows:
void CGDIDoc::OnUpdateConvertRGBto256(CCmdUI* pCmdUI)
{
pCmdUI->Enable(m_nBmpFormat == BMP_FORMAT_24BIT);
}
If the current bitmap format is 24-bit, command Convert | RGB to 256 will be enabled.
The implementation of function CGDIDoc::OnConvertRGBto256() is somehow similar to that of CGDIDoc::OnConvert256toRGB(): we must first lock the global memory where the current 24-bit bitmap image is stored, then calculate the size of new bitmap image (256-color format), allocate enough buffers from global memory, and fill the new bitmap bit values one by one.
The first thing we need to do is creating the color table for the new bitmap image. The following portion of function CGDIDoc::OnConvertRGBto256() shows how to extract the color table from explicit RGB colors contained in a 24-bit bitmap image:
(Code omitted)
We examine from the first pixel. The color table will be stored in array arRgbQuad, which is empty at the beginning. For each pixel, we compare the color with every color contained in the color table, if there is a hit, we move on to next pixel, otherwise, we add this color to the color table.
The size of color table obtained this way may be less or greater than 256. In the first case, the conversion is done after the above operation. If the color table size is greater than 256, we must create a new color table using the alogrithsm discussed above:
(Code omitted)
If the size of color table is greater than 256, we first delete the color table, then create a new color table that contains only 256 colors. This color table comprises 256 colors that are evenly distributed in a 8(8(4 3-D space, which has the following contents:
(Table omitted)
For a 24-bit color, if we use only 3 most significant bits of red and green colors, and 2 most significant bits of blue color, and set rest bits to 0. Every possible RGB combination (8 bits for each color) has a corresponding entry in this table.
We use a flag bStandardPal to indicate which algorithm was used to generate the color table. This is important because for the two situations the procedure of converting explicit RGB values to indices of color table is different. If the color table is generated directly from the colors contained in the bitmap (first case), each pixel can be mapped to an index in the color table by comparing it with every color in the color table (there must be a hit). Otherwise, we must omit some bits before looking up the color table (second case).
Following is a portion of function CGDIDoc::OnConvertRGBto256() that allocates buffers from global memory, fill the buffers with bitmap information header and color table:
(Code omitted)
The differences between the new and old bitmap information headers are member bitBitCount (8 for 256-color format), biSizeImage, and biClrUsed (Member biClrUsed can be used to indicate the color usage. For simplicity, it is set to zero).
Next we need to convert explicit RGB values to color table indices. As mentioned before, there are two situations. If the color table is extracted directly from the bitmap, we must compare each pixel with every entry of the color table, find the index, and use it as the bitmap bit value. Otherwise the index can be formed by omitting the lower 5 bits of red and green colors, the lower 6 bits of blue color then combining them together. This eleminates the procedure of looking up the color table. It is possible for us to do so because the color table is created in a way that if we implement the above operation on any color contained in the table, the result will become the index of the corresponding entry.
For example, entry 1 contains color (32, 0, 0), which is (0x20, 0x00, 0x00). After bit omission, it becomes (0x01, 0x00, 0x00). The followng calculation will result in the index of this entry:
red | (green << 3) | (blue << 6)
By using this method, we do not need to look up the color table.
The following portion of function CGDIDoc::OnConvertRGBto256() shows how to convert explicit RGB values to indices of the color table:
(Code omitted)
Finally, we must unlock the global memory, destroy the original 24 bit bitmap, and assign the new handle to variable CGDIDoc::m_hDIB. We also need to delete the array that holds the temprory color table and update the view to redraw the bitmap image:
(Code omitted)
With this application, we can convert between 256-color and 24-bit bitmap formats back and forth. If the application is executed on a palette device with 256-color configuration, we may experience color distortion after converting a 256-color format bitmap to 24-bit format bitmap. This is because for this kind of bitmap, no logical palette is implemented in the application, so the color approximation method is applied by the OS.
10.8 Pixel Manipulation
With the above knowledge, it is easy for us to implement pixel manipulation on a DIB image. For different types of DIB formats, the procedure of manipulating pixel is different. If the format is color table based, we need to retrieve the color of a pixel through the color table. If the format is not color table based, we can directly edit the color of a pixel.
Sample 10.8\GDI demonstrates how to edit DIB image pixel by pixel. It is based on sample 10.7\GDI. The application will convert any color image to a black-and-white image. To make the conversion, we need to examine every pixel and find its brightness, average it, and assign the averaged value to each of the R, G, B factors. The brightness of a pixel can be calculated by adding up its R, G and B values. For 256-color format, since all the colors are stored in the color table, we can just convert the color table to a black-and-white one in order to make this change. No modification needs to be made to the pixels. For 24 bit format, we need to edit every pixel.
In the sample, a new command Convert | Black White is added to the mainframe menu IDR_MAINFRAME. Also, WM_COMMAND and UPDATE_COMMAND_UI message handlers are added to class CGDIDoc through using Class Wizard. The corresponding two new functions are CGDIDoc::OnConvertBlackwhite() and CGDIDoc::OnUpdateConvertBlackwhite(...) respectively.
Function CGDIDOC::OnUpdateConvertBlackwhite(...) is implemented as follows for supporting both 256-color and 24-bit formats:
(Code omitted)
For function CGDIDoc::OnConvertBlackwhite(), first we need to lock the global memory that is used for storing the bitmap, and judge its format by examining biBitCount member of structure BITMAPINFOHEADER:
(Code omitted)
If its value is 8, the format of the bitmap is 256-color. We need to change each color contained in the color table to either black or white color:
(Code omitted)
For every color, we add up its R, G, B values and average the result. Then we assign this result to each of the R, G, B factors.
The 24 bit format is slightly different. We need to examine each pixel one by one and implement the same conversion:
(Code omitted)
Finally, we need to unlock the global memory and update the view to reload the bitmap image.
Based on the knowledge we already have, it is not so difficult for us to enhance the quality of the images using other image processing methods, such as contrast and brightness adjustment, color manipulation, etc.
10.9 DIB Section: Using Both DIB and DDB
Sample 10.9\GDI demonstrates how to draw an image with transparent background on the client window. It is based on sample 10.8\GDI.
Importance of DDB
By now everything seems fine. We use DIB format to load, store image data. We also use it to draw images. Withoug converting from DIB to DDB and vice versa, we can manage the image successfully.
However, sometimes it is very inconvenient without DDB. For example, it is almost impossible to draw an image with transparency by solely using DIB (We will call the transparent part of an image "background" and the rest part "foreground"). Although we can edit every pixel and change its color, there is no way for us to prevent a pixel from being drawn, because functon ::SetDIBitsToDevice(...) will simply copy every pixel contained in an image to the target device (It does not provide different drawing mode such as bit-wise AND, OR, or XOR).
To draw image with transparency, we need to prepare two images, one is normal image and the other is mask image. The mask image has the same dimension with the normal image and contains only two colors: black and white, which indicate if a corresponding pixel contained in the normal image should be drawn or not. If a pixel in the normal image has a corresponding black pixel in the mask image, it should be drawn. If the corresponding pixel is white, it should not be drawn. By doing this, any image can be drawn with transparency.
A DDB image can be painted with various drawing modes: bit-wise AND, OR, XOR, etc. Different drawing modes will combine the pixels in the source image and the pixels in the target device differently. Special effects can be made by applying different drawing modes consequently.
When drawing a DDB image, we can use bit-wise XOR along with AND operation to achieve transparency. First, the normal image can be output to the target device by using bit-wise XOR mode. After this operaton, the output pattern on the target device is the XORing result of its original pattern and the normal image. Then the mask bitmap is output to the same position using bit-wise AND mode, so the background part (corresponding to white pixels in the mask image) of the device still remains unchanged (it is still the XORing result of the original pattern and the normal image), however, the foreground part (corresponding to black pixels in the mask image) becomes black. Now lets ouput the normal image to the target device using bit-wise XOR mode again. For the background part, this is equivalent to XORing the normal image twice with the original pattern on the target device, which will resume its original pattern (A^B^A = B). For the foreground part, this operation is equivalent to XORing the normal image with 0s, which will put the normal image to the device (0^B = B).
Although the result is an image with a transparent background, when we implement the above-mentioned drawings, the target device will experience pattern changes (Between two XOR operations, the pattern on the target device is neigher the original pattern nor the normal image, this will cause flickering). So if we do all these things directly to the device, we will see a very short flickering every time the image is drawn. To make everything perfect, we can prepare a bitmap in the memory and copy the pattern on the target device to it, then perform XOR and AND drawing on the memory bitmap. After the the drawing is complete, we can copy the memory bitmap back to the device. For the memory bitmap, since its background portion has the same pattern with that of the target device, we will not see any flickering.
To paint a DDB image, we need to prepare a memory DC, select the bitmap into it, then call function CDC::BitBlt(...) or CDC::StretchBlt(...) to copy the image from one device to another.
In order to draw an image with transparent background, we need to prepare three DDBs: the normal image, the mask image, and the memory bitmap. For each DDB, we must prepare a memory DC to select it. Also, because the DDB must be selected out of DC after drawing, we need to prepare a CBitmap type pointer for each image.
Since the usr can load a new image when there is an image being displayed, we need to check the states of variables that are used to implement DCs and bitmaps. Generally, before creating a new memory DC, it would be safer to check if the DC has already been initialized. If so, we need to delete the current DC and create a new one. Before deleting a DC, we further need to check if there are objects (such as bitmap, palette) currently being selected. All the objects created by the user must be selected out before a DC is delected. The DC can be deleted by calling function CDC::DeleteDC(). Also, before creating a bitmap, we need to check if the bitmap has been initialized. If so, before creating a new bitmap, we need to call function CGDIObject::DeleteObject() to destroy the current bitmap first.
Functions CBitmap::CreateBitmap(...) and CDC::CreateCompatibleDC(...) will fail if CBitmap and CDC type variables have already been initialized.
If a logical palette is implemented, we must select it into every DC before performing drawing operations. Before the application exits, all the objects selected by the DCs must be selected out, otherwise it may cause the system to crash.
Although we can prepare normal image and mask image separately, it is not the most convenient way to implement transparent background. The mask image can also be generated from the normal image so long as all the background pixels of the normal image are set to the same color (For example, white). In this situation, pixel in the mask image can be set by examing the corresponding pixel in the normal image: if it is the background color, the pixel in the mask image should be set to white, otherwise it should be set to black.
DIB Section
Both DIB and DDB are needed in order to implement transparent background drawing: we need DIB format to generate mask image, and need DDB to draw the image. Of course we can call ::GetDIBits(...) and ::SetDIBits(...) to convert between DIB and DDB format, however, there exists an easier way to let us handle DIB and DDB simultaneously.
A DIB section can be created to manage the image so that we can have both the DIB and DDB features without doing the conversion. A DIB section is a memory section that can be shared between the process and the system. When a change is made within the process, it is automatically updated to the system. By doing this, there is no need to update the data using functions ::GetDIBits(...) and ::SetDIBits(...).
We can call function ::CreateDIBSection(...) to create a DIB section. This function will return an HBITMAP handle, which can be attached to a CBitmap variable by calling function CBitmap::Attach(...).
Function ::CreateDIBSection(...) has six parameters:
HBITMAP ::CreateDIBSection
(
HDC hdc,
CONST BITMAPINFO *pbmi, UINT iUsage, VOID *ppvBits, HANDLE hSection,
DWORD dwOffset
);
(Table omitted)
After calling this function, we can access the buffers pointed by ppvBits and make change to the DIB bits directly, there is no need for us to do any DIB to DDB conversion or vice versa. After the change is made, we can draw the bitmap immediately by calling funciton CDC::BitBlt(...), this will draw the updated image to the window.
New Variables
The following new variables are declared in class CGDIView for drawing bitmap with transparancy:
(Code omitted)
Altogether there are four CBitmap type variables, three CBitmap type pointers, three CDC type variables, and three CPalette type pointers. Their meanings are explained in the following table:
(Table omitted)
To make the application more interesting, we will also draw the background of client window using bitmap. This bitmap will be loaded into m_bmpBkd variable.
The six pointers are initialized to NULL in the constructor:
(Code omitted)
Cleaning Up
A new function CGDIView::CleanUp() is added to the application for doing the clean up job. Within this function, all the objects selected by the DCs are selected out, then DCs and bitmaps are deleted:
(Code omitted)
If a pointer is not NULL, it means that there is an object being currently selected by the DC, so funciton CGDIObject::SelectObject(...) is called to select the object (palette or bitmap) out of the DC before destroying it.
We need to call this function just before the application exits. In the sample, a WM_DESTROY message handler is added to class CGDIView through using Class Wizard. The corresponding member function is implemented as follows:
(Code omitted)
Loading Bitmap & Creating Mask Bitmap
In the sample, funciton CGDIView::LoadBitmap(...) is changed. In the new function, a DIB section is created from the DIB data, and the mask bitmap image is generated from the normal image. The palette creation precedure is still the same. The handle retruned from the DIB section is attached to variable CGDIView::m_bmpDraw:
(Code omitted)
Please note that function CGDIView::CleanUp() is called before the bitmap is created. After the DIB section is created, we use the DIB data passed through hData parameter to initialize the image. The buffers that store DIB bit values are pointed by pointer pBits. We can use it to edit the image pixels directly, there is no need to convert between DDB and DIB foramts any more.
After the bitmap is loaded, we need to create the mask bitmap, memory bitmap, and theree memory DCs. We also need to select the bitmaps and the logical palette into the DCs if necessary:
(Code omitted)
The mask and memory bitmaps must be created by calling function CBitmap:: CreateCompatibleBitmap(...), this will allow the created bitmaps to be compatible with the device context.
Next, the mask bitmap is generated from the normal bitmap image:
(Code omitted)
Every pixel of the normal image is examined to generate the mask image. Here functions CDC::GetPixel(...) and CDC::SetPixel(...) are called for manipulating single pixels. Although the two functions hide the details of device context and bitmap format, they are very slow, and should not be used for fast bitmap drawing or image processing.
Drawing Bitmap with Transparancy
Function CGDIView::OnDraw(...) is modified as follows for drawing bitmap with transparency:
(Code omitted)
In the above function, the pattern on the target device is first copied to the memory bitmap. Then function CDC::BitBlt(...) is called three times to draw the normal image and mask image on the memory bitmap, with two XOR drawings of the normal image (first and thrid operations) and one AND mode drawing of the mask image (second operation). Finally, the new pattern in the memory bitmap is copied back to the targert device.
Adding Background
If the window's background is also white, it is difficult for us to see the transparency effect. To show this effect, in the sample, the background of the client window is also painted with a bitmap image.
The image that is used to paint the background is prepared as a resource, whose ID is IDB_BITMAPBKD. The bitmap is loaded to variable CGDIView::m_bmpBkd in function CGDIView:: OnInitialUpdate():
(Code omitted)
The bitmap can also be loaded in the constructor of CGDIView.
To paint the background of a window, we need to handle message WM_ERASEBKGND and implement background drawing by ourseleves in the message hanlder. In the sample, this message handler is added through using Class Wizard, and the corresponding function is implemented as follows:
(Code omitted)
This function simply draw the bitmap image repeatedly so that the whole client area is covered by the image.
To test this sample, we may use it to load any bitmap images with white background.
Figure 10-1 shows the result after 10.9\Rose.bmp is loaded into the application.
(Figure 10-1 omitted)
10.10 Creating Chiseled Effect
We will continue to show the power of DDB. Sample 10.10\GDI demonstrates how to convert a normal bitmap to a grayed image with chiseled effect. It is based on sample 10.9\GDI. We can see that with DDB, this effect can be easily implemented with just a few CDC::BitBlt(...) calls. If we use DIB, we will have to make very complex mathematical calculation, and the program will become very slow.
The chiseled effect can be implemented by drawing the outline of an object with two different colors: highlighted color and shadowed color. Usually white is used as the the highlighted color and dark gray is used as the shadowed color. For example: the rectangle in Figure 10-2 uses white and dark gray as the highlighted and shadowed colors respectivly, it creates a chiseled effect:
Algorithm
The chiseled effect can be implemented with the following algorithm: find out the object's outline, imagine some parallel lines with 135( angle are drawn from the upper left side to bottom right side (Figure 10-3). Think these lines as rays of light. If we draw the portion of the outline that first encounters these parallel lines (the portion facing the light) with shadowed color, and draw the rest part of the outline (the portion facing away fromt he light) with highlighted color, the object will have a chiseled effect. If we swap the shadowed and highlighted colors, it will result in an embossed effect.
If we have a 2-D binary image (an image that contains only two colors: white (255, 255, 255) and black (0, 0, 0)), the outline can be generated by combining the inverse image with the original image at an offset origin. For example, the outline that should be drawn using the shadowed color can be generated with the following steps:
1) Draw the original bitmap at position (0, 0).
2) Invert the bitmap image.
3) Combine the inverted image with the image drawn in step 1) with bit-wise OR operation, with the inverted image be put at position (1, 1) (Figure 10-4).
The highlighted outline can be obtained in the same way, but we need to combine the original bitmap and the inverted bitmap differently here:
1) Draw the original bitmap at position (1, 1).
2) Invert the bitmap image.
3) Combine the inverted image with the original image with bit-wise OR operation, with the inverted image be put at position (0, 0) (Figure 10-5).
If we combine the two outlines and paint them with highlighted and shadowed colors respectively, then fill the rest part with a normal color (A color between the highlighted and shadowed color), we will have a 3D effect. For example, we can use white as the highlighted color, dark gray as the shadowed color, and light gray as the normal color.
Creating Binary Bitmap Image
So we need to generate monochrome black/white binary image from the original color bitmap. Of course we can examine every pixel one by one and compare its brightness with a threshold (Usually the threshold value is set to the middle between the brightest and darkest color, which are white and black respectively). If the brightness of a pixel is greater than the threshold, its color is set to white, otherwise it will be set to black.
However, we can implement this conversion in an easier way. Remember, when creating the bitmap by calling function CBitmap::CreateBitmap(...), we are allowed to specify the bitmap's attributes, which includes the dimension of the image, number of bytes in each raster line, and bit count per pixel. Here, the bit count per pixel indicates how many bits will be used for representing a single pixel. If we set it to 1, the image can have only 2 colors, and will be automatically converted to mono chrome fomat no matter what kind of data we use to initialize the bitmap.
Raster Operation Mode
Another issue needs to be discussed here is how to draw the outline portion using specified color without affecting rest part of the image. In order to implement this, we can treat the two outline bitmaps as masks, and draw only the unmasked pixels with specified colors. This procedure is similar to that of drawing bitmaps with transparency.
When calling function CDC::BitBlt(...) or CDC::StretchBlt(...) to paint the bitmap, we always need to specify the drawing mode, which specifies how to combine the pixels in the source bitmap with the corresponding destination pixels. This drawing mode is also called raster operation mode, because it is applicable only to raster devices (Contary to the raster devices are vector devices, for example, a plotter is a vector device). We have many choices such as bit-wise AND, OR, XOR etc. Actually, we can specify more complex combinations among the following three different objects when calling the above two functions: the pixel in the source bitmap, the corresponding pixel in the target device, and the brush currently being selected by the DC. We can specify up to three Boolean bit-wise operations among them.
For example, the following operation will draw the outline on the destination bitmap with the brush color:
(Brush Color) XOR (Destinaton Color) AND (Source Color) XOR (Brush Color)
The reason is simple: After the first operation (between the brush and destination pixels), the pixels in the target device will become the XOR combination between the original pixel colors and the brush color. Next, this XORed result will be ANDed with the source bitmap (Only the outlined part is black, rest part is white), the outlined part on the target device will become black and the rest part remains unchanged (still XORed result from the first operation). Then we do XOR again between the target device and the brush, for the outlined part, this operation will fill it with the brush color (A ^ 0 = A); for the rest part, this will resume the original color for every pixel (A ^ B ^ A = B).
Chiselled Effect
With the following steps, we can create chiselled effect:
1) Create a brush with shadowed or highlighted color.
2) Paint the destination with the brush using bit-wise XOR operation mode.
3) Draw the mask bitmap on the target device using bit-wise AND operation mode.
4) Paint the destination with the brush using bit-wise XOR operation mode again.
This will draw one of the outlines. Creating new brush and repeating the above steps using the other mask bitmap can result in the chiselled effect.
The first and fourth steps can be implemented by calling function CDC::PatBlt(...), which allows us to fill a bitmap with a brush using specified operation mode:
BOOL CDC::PatBlt(int x, int y, int nWidth, int nHeight, DWORDdwRop);
The last parameter allows us to specify how to combine the color of the brush with the destination pixels. To do bitwise XOR, we need to specify PATINVERT mode.
So we can call CDC::PatBlt(...), CDC::BitBlt(...) and CDC::PatBlt(...) again to draw the outline using the brush created by our own. However, there is a simpler way. When calling function CDC::BitBlt(...), we can pass it a custom operation code and let it do the above-mentioned operations in one stroke.
To find out the custom operation code, we need to enumerate all the possible results from the combinations among brush, destination and source bits for our raster operation:
(Brush Color) XOR (Destinaton Color) AND (Source Color) XOR (Brush Color)
The following table lists all the possible results from the above fomulae:
(Table omitted)
The sequence in the table must be arragned so that brush is in the first column, source pixel in the second column and destination pixel in the third column. The code resulted from the combination of the three bits must increment by one for adjacent rows (In the above sample, for the first row it is 000, second row it is 001, third row it is 010...). If we read the output from the bit contained in the last row to the bit in the first row (10111000), we will have 0xB8.
With this index, we can find a raster code that will implement this typical operation for either CDC::BitBlt(...) or CDC::StretchBlt(...) calling. The table is documented in Win32 programming, we can also find it in Appendix B.
By looking up the table, we know that the raster code needs to be use is 0x00B8074A.
Highlighted and Shadowed Colors
The standard highlighted and shadowed colors in the system can be retrieved by calling function ::GetSysColor(...), which has the following format (The standard system colors can be set by calling the counterpart function ::SetSysColor(...)):
DWORD ::GetSysColor(int nIndex);
The following is a list of some values of nIndex parameter that could be used to retrieve some standard colors in the system:
(Table omitted)
In the sample, we choose COLOR_BTNHIGHLIGHT as the highlighted color, and COLOR_BTNSHADOW as the shadowed color.
New Function
In the sample, a new function CreateGrayedBitmap(...) is declared in class CGDIView to create grayed image from a nomal bimap:
(Code omitted)
The only parameter to this function is a CBitmap type pointer. The function will return an HBITMAP handle, which is the grayed bitmap. Within the function, we must prepare three bitmaps: the bitmap that will be used to store the final grayed image, the mask image that stores the shadowed outline, and the mask image that stores the highlighted outline. The function starts with creating these bitmaps:
(Code omitted)
The final grayed image will be stored in variable bmpGray. We will refer image created by this variable as "grayed image", although the image may not be grayed in the interim.
The other two CBitmap type variables, bmpHilight and bmpShadow will be used to store the outline mask images. We need two memory DCs, one used to select the color bitmap (normal image passed through pointer pBmp, and grayed image bmpGray) and one for binary bitmaps (the outline mask bitmaps). Note that binary bitmaps are created with bit count per pixel set to 1 and the grayed bitmap (actually it is a color bitmap, but we use only monochrom colors) is created by calling function CBitmap:: CreateCompatibleBitmap(...). Since the DC supports color bitmap (If the program is being run on a system with a color monitor) , this will create a color bitmap compatible with the window DC.
Then we select the color bitmap and the binary bitmaps into the memory DCs and create the outline mask images:
(Code omitted)
First we fill the mask bitmap with white color. Then we copy the patterns from the original image to the mask bitmap image. When doing this copy, the first horizontal line (the upper-most line) and the first vertical line (the left-most vertical line) are eleminated from the original image (Pixels with coordinates (0, y) and (x, 0) are elemented, where x can be 0, 1, ... , up to width of image -1; y can be 0, 1, ..., up to height of image -1). The colors contained in the souce bitmap will be automatically converted to black and white colors when we call function CDC::BitBlt(...) because the target image is a binary bitmap. The souce image is copied to the mask bitmap at the position of (0, 0). Then the original bitmap is inverted, and merged with the mask image with bit-wise OR operation. Here flag MERGEPAINT allows the pixels in the souce image and pixels in the target image to be combined in this way. After these operations, the binary bitmap image will contain the outline that should be drawn with the shadowed color.
The following portion of the function generates the highlighted outline:
(Code omitted)
Next we create a brush with standard button face color and used it to fill the grayed image (By default, the standard button face color is light gray. It can also be customized to other colors):
(Code omitted)
The button face color is retrieved by calling ::GetSystColor(...) API function. Actually, all the standard colors defined in the system can be retrieved by calling this function. Next, we draw the highlighted outline of the grayed bitmap using the standard highlighted color:
(Code omitted)
Also, the shadowed outline is drawn on the grayed image in the same way:
(Code omitted)
Finally, some clean up routines. Before this function exits, we call function CBitmap::Detach() to detach HBITMAP type handle fromCBitmap type variable. This is because we want to leave the HBITMAP handle for further use. If we do not detach it, when CBitmap type variable goes out of scope, the destructor will destroy the bitmap automatically, and therefore, the bitmap handle will no longer be valid from then on.
Function CGDIView::LoadBitmap(...) & CGDIView::OnDraw(...)
In the sample, funciton CGDIView::LoadBitmap(...) is changed so that when the user opens a normal color bitmap, the application will automatically convert it to a grayed bitmap image. The original variables that are used to implement image transparency along with the relavant functions are deleted. The only variable remained in class CGDIView are m_bmpDraw, m_dcMem, m_pBmpOld and m_pPalOld:
(Code omitted)
Function CGDIView::OnInitialUpdate(...) and CGDIView::OnCleanUp() are modified in order to conform this change. Also, WM_ERASEBKGND message handler is removed through using Class Wizard.
In function CGDIView::OnLoadBitmap(...), after the palette is created, we call function CGDIView:: CreateGrayedBitamp(...) to create the grayed bitmap and attach the returned handle to variable m_bmpDraw:
(Code omitted)
The bitmap that was originally created by function ::CreateDIBSection(...) is destroyed. Finally, function CGIDView::OnDraw(...) is changed to draw the grayed bitmap to the client window:
(Code omitted)
With the above implementations, the application is able to create grayed images with chiselled effect.
Summary
1) Usually an image is stored to disk using DIB format. To display it on a specific type of device, we must first convert it to DDB format, which may be different from device to device.
2) To draw bitmap, we must prepare a memory DC, select the bitmap into it, and copy the image between the memory DC and target DC.
3) Function CDC::BitBlt(...) can be used to draw the image with 1:1 ratio. To draw an image with an enlarged or shrunk size, we need to use function CDC::StretchBlt(...).
4) A DIB contains three parts: 1) Bitmap information header. 2) Color Table. 3) DIB bit values. For the DIB files stored on the disk, there is an extra bitmap file header ahead of DIB data.
5) We can call function ::GetDIBits(...) to get DIB bit values from a DDB selected by a DC, and call function ::SetDIBits(...) to set DIB bit values to the DDB.
6) There are several DIB formats: monochrom format (2 colors), 16-color format, 256-color format, 24-bit format. For each format, the total number of colors contained in the color table is different. The pixels of 24-bit DIB contain explict RGB values, for the rest formats, they contain indices to a color table which resides in the bitmap information header.
7) In order to accelarate bitmap image loading and saving, each raster line of the imge must use multiple of 4 bytes for storing the image data. The extra buffers will simply be wasted if there are not enough image data.
8) The following information contained in the bitmap information header is very important: 1) The dimension of the image (width and height). 2) Bit count per pixel. 3) Image size, which can be calculated from the image dimension and bit cout per pixel.
9) The size of a color table (in number of bytes) can be calculated from the following formula:
for 24-bit format: 0
for other formats that contain color table: (size of structure RGBQUAD) ( 2Bit count per pixel
10) The total image size can be calculated from the following formula:
for 24-bit format:
(size of bitmap information header) + (number of bytes for one raster line) ( image height
for other formats that contain color table:
(size of bitmap information header) + size of color table + (number of bytes for one raster line) ( image height
where number of bytes for on raster line can be calculated as:
(((bit count per pixel) ( (image width) + 31)/32) ( 4
Before preparing DIB, we need the above information to allocate enough buffers for storing DIB data.
11) Function ::SetDIBitsToDevice(...) can be used to draw DIB directly to a device. We don't need to implement any DIB-to-DDB conversion. However, using this function, we also lose the control over DDB.
12) DIB section can be created for managing the image in both DIB and DDB format.
13) Bitmap with transparency can be implemented by using a mask image, and drawing the normal and mask images using bit-wise XOR and AND operation modes.
14) We can convert a color bitmap to a grayed image with chiselled or embossed effect by finding out the outline of the object, then drawing the portion facing the light with highlighted (shadowed) color and the portion facing away from the light with shadowed (highlighted) color.