Topic : Bitmaps in MFC
Author : Unknown
Page : << Previous 6  Next >>
Go to page :

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.

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 8\GDI demonstrates how to edit DIB image pixel by pixel. It is based on sample 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.

9 DIB Section: Using Both DIB and DDB

Sample 9\GDI demonstrates how to draw an image with transparent background on the client window. It is based on sample 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

Page : << Previous 6  Next >>