Bitmap: 10.1 Bitblt and Stretchblt
Bitmap: 10.1 Bitblt and Stretchblt
Bitmap
Chapter 10
Bitmap
F rom this chapter we are going to deal with another GDI object ¾ bitmap, which is a very complex
issue in Windowsä programming. There are many topics on how to use bitmaps, how to avoid
color distortion, how obtain palette from bitmap, how convert from one bitmap format to another,
and how to manipulate image pixels.
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.
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,
Chapter 10. Bitmap
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:
The only parameter to this function ( pDC) must be a pointer to the target DC.
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:
public:
CBitmap *GetBitmap(){return &m_bmpDraw;}
//{{AFX_VIRTUAL(CGDIDoc)
……
}
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:
CGDIDoc::CGDIDoc()
{
m_bmpDraw.LoadBitmap(IDB_BITMAP);
}
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:
void CGDIView::OnInitialUpdate()
{
Chapter 10. Bitmap
CGDIDoc *pDoc;
CBitmap *pBmp;
BITMAP bm;
pDoc=GetDocument();
ASSERT_VALID(pDoc);
pBmp=pDoc->GetBitmap();
pBmp->GetBitmap(&bm);
SetScrollSizes(MM_TEXT, CSize(bm.bmWidth, bm.bmHeight));
CScrollView::OnInitialUpdate();
}
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:
CGDIDoc *pDoc=GetDocument();
ASSERT_VALID(pDoc);
dcMemory.CreateCompatibleDC(pDC);
pBmp=pDoc->GetBitmap();
pBmpOld=dcMemory.SelectObject(pBmp);
pBmp->GetBitmap(&bm);
pDC->BitBlt
(
0,
0,
bm.bmWidth,
bm.bmHeight,
&dcMemory,
0,
0,
SRCCOPY
);
dcMemory.SelectObject(pBmpOld);
}
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:
Chapter 10. Bitmap
void CGDIView::OnInitialUpdate()
{
……
SetScrollSizes(MM_TEXT, CSize(2*bm.bmWidth, 2*bm.bmHeight));
CScrollView::OnInitialUpdate();
}
BOOL CDC::StretchBlt
(
int x, int y, int nWidth, int nHeight,
CDC *pSrcDC,
int xSrc, int ySrc, int nSrcWidth, int nSrcHeight,
DWORD dwRop
);
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:
With the above modifications, we will have an enlarged bitmap image in the client window.
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:
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
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:
Chapter 10. Bitmap
W B W
B W B
W B W
B W B
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:
DIB format Bits Needed for One Actual Bits Used by One Bits Wasted for One
Raster Line Raster Line Raster Line
2 color 3 32 29
4 color 12 32 20
8 color 24 32 8
24 bit 72 96 24
We can define a macro that allows us to calculate the number of bytes needed for each raster line for
different bitmap formats:
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
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.
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:
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:
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:
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:
void CGDIView::OnInitialUpdate()
{
CClientDC dc(this);
CGDIDoc *pDoc;
CBitmap *pBmp;
CPalette *pPalDraw;
LPBITMAPINFO lpBi;
LPLOGPALETTE lpLogPal;
CPalette *pPalOld;
int nSizeCT;
int i;
HBITMAP hBmp;
HRSRC hRes;
HGLOBAL hData;
CScrollView::OnInitialUpdate();
pDoc=GetDocument();
ASSERT_VALID(pDoc);
if
(
(
hRes=::FindResource
(
AfxGetResourceHandle(),
MAKEINTRESOURCE(IDB_BITMAP),
RT_BITMAP
)
) != NULL &&
(
hData=::LoadResource
(
AfxGetResourceHandle(),
hRes
)
) != NULL
)
{
lpBi=(LPBITMAPINFO)::LockResource(hData);
ASSERT(lpBi);
Chapter 10. Bitmap
……
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:
……
switch(lpBi->bmiHeader.biBitCount)
{
case 1:
{
nSizeCT=2;
break;
}
case 4:
{
nSizeCT=16;
break;
}
case 8:
{
nSizeCT=256;
break;
}
default: nSizeCT=0;
}
……
The color table size is stored in variable nSizeCT. Next, the logical palette is created from the color
table stored in the DIB data:
……
lpLogPal=(LPLOGPALETTE) new BYTE
[
sizeof(LOGPALETTE)+(nSizeCT-1)*sizeof(PALETTEENTRY)
];
lpLogPal->palVersion=0x300;
lpLogPal->palNumEntries=nSizeCT;
for(i=0; i<nSizeCT; i++)
{
lpLogPal->palPalEntry[i].peRed=lpBi->bmiColors[i].rgbRed;
lpLogPal->palPalEntry[i].peGreen=lpBi->bmiColors[i].rgbGreen;
lpLogPal->palPalEntry[i].peBlue=lpBi->bmiColors[i].rgbBlue;
lpLogPal->palPalEntry[i].peFlags=NULL;
}
pPalDraw=pDoc->GetPalette();
pPalDraw->CreatePalette(lpLogPal);
delete [](BYTE *)lpLogPal;
pPalOld=dc.SelectPalette(pPalDraw, FALSE);
dc.RealizePalette();
……
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:
……
hBmp=::CreateDIBitmap
(
Chapter 10. Bitmap
dc.GetSafeHdc(),
&lpBi->bmiHeader,
CBM_INIT,
(LPSTR)lpBi+sizeof(BITMAPINFOHEADER)+sizeof(RGBQUAD)*nSizeCT,
lpBi,
DIB_RGB_COLORS
);
ASSERT(hBmp);
dc.SelectPalette(pPalOld, FALSE);
pBmp=pDoc->GetBitmap();
pBmp->Attach(hBmp);
……
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:
……
pBmp->GetBitmap(&m_bmInfo);
SetScrollSizes(MM_TEXT, CSize(m_bmInfo.bmWidth, m_bmInfo.bmHeight));
m_dcMem.CreateCompatibleDC(&dc);
m_pBmpMemOld=m_dcMem.SelectObject(pBmp);
m_pPalMemOld=m_dcMem.SelectPalette(pPalDraw, FALSE);
::FreeResource(hData);
}
}
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:
void CGDIView::OnDestroy()
{
m_dcMem.SelectPalette(m_pPalMemOld, FALSE);
m_dcMem.SelectObject(m_pBmpMemOld);
CScrollView::OnDestroy();
}
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:
CGDIDoc *pDoc=GetDocument();
ASSERT_VALID(pDoc);
pPal=pDoc->GetPalette();
pPalOld=pDC->SelectPalette(pPal, FALSE);
pDC->RealizePalette();
pDC->StretchBlt
(
0,
0,
m_bmInfo.bmWidth,
m_bmInfo.bmHeight,
&m_dcMem,
0,
0,
m_bmInfo.bmWidth,
m_bmInfo.bmHeight,
Chapter 10. Bitmap
SRCCOPY
);
pDC->SelectPalette(pPalOld, FALSE);
}
With the above implementations, the bitmap will become more vivid.
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:
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
BITMAPINFOHEADE
R
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.
GDI\n\nGDI\n\n\nGDI.Document\nGDI Document
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:
The fourth item (Bitmap Files(*.bmp)) is a descriptive name for bitmap files, and the fifth item (.bmp) is
the filter.
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:
CGDIDoc::~CGDIDoc()
{
if(m_hDIB != NULL)
{
::GlobalFree(m_hDIB);
m_hDIB=NULL;
}
}
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:
}
else
{
BITMAPFILEHEADER bf;
DWORD dwSize;
LPSTR lpDIB;
if(m_hDIB != NULL)
{
::GlobalFree(m_hDIB);
m_hDIB=NULL;
}
……
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:
……
if
(
ar.Read
(
(LPSTR)&bf,
sizeof(BITMAPFILEHEADER)
) != sizeof(BITMAPFILEHEADER)
)return;
……
Then the file type is checked. If it is DIB format, global memory is allocated, which will be used to
store DIB data:
……
if(bf.bfType != 'MB')return;
dwSize=bf.bfSize-sizeof(BITMAPFILEHEADER);
m_hDIB=::GlobalAlloc(GHND, dwSize);
ASSERT(m_hDIB);
lpDIB=(LPSTR)::GlobalLock(m_hDIB);
ASSERT(lpDIB);
……
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
Chapter 10. Bitmap
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:
void CGDIView::OnInitialUpdate()
{
CClientDC dc(this);
CGDIDoc *pDoc;
CBitmap *pBmp;
CPalette *pPalDraw;
LPBITMAPINFO lpBi;
LPLOGPALETTE lpLogPal;
CPalette *pPalOld;
int nSizeCT;
int i;
HBITMAP hBmp;
HGLOBAL hData;
CScrollView::OnInitialUpdate();
pDoc=GetDocument();
ASSERT_VALID(pDoc);
hData=pDoc->GetHDib();
……
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):
……
if(hData != NULL)
{
if(m_pPalMemOld != NULL)m_dcMem.SelectPalette(m_pPalMemOld, FALSE);
if(m_pBmpMemOld != NULL)m_dcMem.SelectObject(m_pBmpMemOld);
lpBi=(LPBITMAPINFO)::GlobalLock(hData);
ASSERT(lpBi);
……
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.
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(…):
int ::GetDIBits
(
HDC hdc, HBITMAP hbmp,
UINT uStartScan, UINT cScanLines, LPVOID lpvBits, LPBITMAPINFO lpbi,
UINT uUsage
);
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:
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):
switch(wBitCount)
{
case 1:
{
dwSizeCT=2;
break;
}
case 4:
{
dwSizeCT=16;
break;
}
case 8:
Chapter 10. Bitmap
{
dwSizeCT=256;
break;
}
case 24:
{
dwSizeCT=0;
}
}
return dwSizeCT;
}
In function CGDIDoc::ConvertDDBtoDIB(…), first we must obtain a handle to the client window that can
be used to create a DC:
pos=GetFirstViewPosition();
ptrView=(CGDIView *)GetNextView(pos);
ASSERT(ptrView->IsKindOf(RUNTIME_CLASS(CGDIView)));
CClientDC dc(ptrView);
……
}
Then function CBitmap::GetBitmap(…) is called to retrieve the information of bitmap and allocate
enough buffers for storing structure BITMAPINFOHEADER and color table:
……
pBmp->GetBitmap(&bm);
bi.biSize=sizeof(BITMAPINFOHEADER);
bi.biWidth=bm.bmWidth;
bi.biHeight=bm.bmHeight;
bi.biPlanes=bm.bmPlanes;
bi.biBitCount=bm.bmPlanes*bm.bmBitsPixel;
bi.biCompression=BI_RGB;
bi.biSizeImage=0;
bi.biXPelsPerMeter=0;
bi.biYPelsPerMeter=0;
bi.biClrUsed=0;
bi.biClrImportant=0;
dwSizeCT=GetColorTableSize(bi.biBitCount);
dwDibLen=bi.biSize+dwSizeCT*sizeof(RGBQUAD);
pPalOld=dc.SelectPalette(&m_palDraw, FALSE);
dc.RealizePalette();
hDib=::GlobalAlloc(GHND, dwDibLen);
lpBi=(LPBITMAPINFO)::GlobalLock(hDib);
lpBi->bmiHeader=bi;
……
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.
Chapter 10. Bitmap
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:
……
VERIFY
(
::GetDIBits
(
dc.GetSafeHdc(),
(HBITMAP)pBmp->GetSafeHandle(),
0,
(WORD)bi.biHeight,
NULL,
lpBi,
DIB_RGB_COLORS
)
);
bi=lpBi->bmiHeader;
::GlobalUnlock(hDib);
if(bi.biSizeImage == 0)
{
bi.biSizeImage=WIDTHBYTES(bi.biBitCount*bi.biWidth)*bi.biHeight;
}
……
Now the size of DIB data is already known, we can reallocate the buffers, and call function
again to receive bitmap data. Finally we need to select the logical palette out of the DC, and
::GetDIBits(…)
return the handle of the global memory before function exits:
……
dwDibLen+=bi.biSizeImage;
hDib=::GlobalReAlloc(hDib, dwDibLen, GHND);
ASSERT(hDib);
lpBi=(LPBITMAPINFO)::GlobalLock(hDib);
ASSERT(hDib);
VERIFY
(
::GetDIBits
(
dc.GetSafeHdc(),
(HBITMAP)pBmp->GetSafeHandle(),
0,
(WORD)bi.biHeight,
(LPSTR)lpBi+sizeof(BITMAPINFOHEADER)+dwSizeCT*sizeof(RGBQUAD),
lpBi,
DIB_RGB_COLORS
)
);
::GlobalUnlock(hDib);
dc.SelectPalette(pPalOld, FALSE);
return hDib;
}
Chapter 10. Bitmap
hDib=ConvertDDBtoDIB(&m_bmpDraw);
ASSERT(hDib);
bmf.bfType='MB';
bmf.bfSize=sizeof(bmf)+::GlobalSize(hDib);
bmf.bfReserved1=0;
bmf.bfReserved2=0;
lpBi=(LPBITMAPINFOHEADER)::GlobalLock(hDib);
bi=*lpBi;
dwSizeCT=GetColorTableSize(bi.biBitCount);
bmf.bfOffBits=sizeof(bmf)+bi.biSize+dwSizeCT*sizeof(RGBQUAD);
ar.Write((char *)&bmf, sizeof(bmf));
ar.Write((LPSTR)lpBi, bi.biSize+dwSizeCT*sizeof(RGBQUAD)+bi.biSizeImage);
::GlobalUnlock(hDib);
::GlobalFree(hDib);
}
else
……
}
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:
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.
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:
int ::SetDIBitsToDevice
(
HDC hdc,
int XDest, int YDest, DWORD dwWidth, DWORD dwHeight,
int XSrc, int YSrc,
UINT uStartScan, UINT cScanLines,
CONST VOID *lpvBits, CONST BITMAPINFO *lpbmi, UINT fuColorUse
);
There are altogether 12 parameters, whose meanings are listed in the following table:
Parameter Meaning
hdc Handle of target DC.
Xdest, Ydest Position on the target DC where the image should be drawn.
dwWidth, dwHeight Image size on the target DC.
uStartScan Starting scan line of the source bitmap image.
UscanLines Number of scan lines in the source bitmap image that will be output to the target.
lpvBits Pointer to the DIB bits (image data).
Lpbmi Pointer to BITMAPINFO type object.
fuColorUse Specifies where to find the color table. If it is DIB_PALCOLORS, the bmiColors
member of structure BITMAPINFO contains indices into the currently selected
logical palette. If the flag is DIB_RGB_COLORS, bmiColors contains explicit red,
green, blue (RGB) values.
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:
int ::StretchDIBits
(
HDC hdc,
int XDest, int YDest, int nDestWidth, int nDestHeight,
int XSrc, int YSrc, int nSrcWidth, int nSrcHeight,
CONST VOID *lpBits, CONST BITMAPINFO *lpBitsInfo, UINT iUsage, DWORD dwRop
);
The ratio between source and target image can be set through the following four parameters:
nDestWidth, nDestHeight, nSrcWidth, nSrcHeight.
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:
CGDIDoc();
DECLARE_DYNCREATE(CGDIDoc)
public:
CPalette *GetPalette(){return &m_palDraw;}
HGLOBAL GetHDib(){return m_hDIB;}
DWORD GetColorTableSize(WORD);
//{{AFX_VIRTUAL(CGDIDoc)
public:
……
}
When saving image to file, we do not need to convert DDB to DIB any more. Instead,
CGDIDoc::m_hDIBcan be used directly for storing data:
hDib=m_hDIB;
ASSERT(hDib);
bmf.bfType='MB';
bmf.bfSize=sizeof(bmf)+::GlobalSize(hDib);
bmf.bfReserved1=0;
bmf.bfReserved2=0;
lpBi=(LPBITMAPINFOHEADER)::GlobalLock(hDib);
bi=*lpBi;
if(bi.biSizeImage == 0)
{
bi.biSizeImage=WIDTHBYTES(bi.biBitCount*bi.biWidth)*bi.biHeight;
}
dwSizeCT=GetColorTableSize(bi.biBitCount);
bmf.bfOffBits=sizeof(bmf)+bi.biSize+dwSizeCT*sizeof(RGBQUAD);
ar.Write((char *)&bmf, sizeof(bmf));
ar.Write((LPSTR)lpBi, bi.biSize+dwSizeCT*sizeof(RGBQUAD)+bi.biSizeImage);
::GlobalUnlock(hDib);
}
……
}
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:
CGDIView();
DECLARE_DYNCREATE(CGDIView)
……
}
In function CGDIView::OnInitialUpdate(), there is no need to create DDB any more. So in the updated
function, only the logical palette is created:
void CGDIView::OnInitialUpdate()
{
CGDIDoc *pDoc;
CPalette *pPalDraw;
LPBITMAPINFO lpBi;
LPLOGPALETTE lpLogPal;
int nSizeCT;
int i;
HGLOBAL hData;
CScrollView::OnInitialUpdate();
pDoc=GetDocument();
ASSERT_VALID(pDoc);
hData=pDoc->GetHDib();
if(hData != NULL)
{
lpBi=(LPBITMAPINFO)::GlobalLock(hData);
ASSERT(lpBi);
nSizeCT=pDoc->GetColorTableSize(lpBi->bmiHeader.biBitCount);
pPalDraw=pDoc->GetPalette();
if(pPalDraw->GetSafeHandle() != NULL)pPalDraw->DeleteObject();
VERIFY(pPalDraw->CreatePalette(lpLogPal));
delete [](BYTE *)lpLogPal;
::GlobalUnlock(hData);
m_bBitmapLoaded=TRUE;
SetScrollSizes
(
MM_TEXT,
CSize(lpBi->bmiHeader.biWidth, lpBi->bmiHeader.biHeight)
);
Invalidate();
}
Chapter 10. Bitmap
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:
if(m_bBitmapLoaded == FALSE)return;
CGDIDoc *pDoc=GetDocument();
ASSERT_VALID(pDoc);
hDib=pDoc->GetHDib();
ASSERT(hDib);
lpBi=(LPBITMAPINFO)::GlobalLock(hDib);
ASSERT(lpBi);
pPal=pDoc->GetPalette();
pPalOld=pDC->SelectPalette(pPal, FALSE);
pDC->RealizePalette();
dwBitOffset=
(
sizeof(BITMAPINFOHEADER)+
pDoc->GetColorTableSize
(
lpBi->bmiHeader.biBitCount
)*sizeof(RGBQUAD)
);
::SetDIBitsToDevice
(
pDC->GetSafeHdc(),
0,
0,
lpBi->bmiHeader.biWidth,
lpBi->bmiHeader.biHeight,
0,
0,
0,
lpBi->bmiHeader.biHeight,
(LPSTR)lpBi+dwBitOffset,
lpBi,
DIB_RGB_COLORS
);
pDC->SelectPalette(pPalOld, FALSE);
::GlobalUnlock(hDib);
}
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:
CGDIView::CGDIView()
{
m_bBitmapLoaded=FALSE;
}
With the above modification, the application is able to display any DIB image without doing DIB to
DDB conversion.
Chapter 10. Bitmap
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:
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
CGDIDoc::CGDIDoc()
{
……
m_nBmpFormat=BMP_FORMAT_NONE;
}
switch(wBitCount)
{
Chapter 10. Bitmap
case 1:
{
dwSizeCT=2;
m_nBmpFormat=BMP_FORMAT_MONO;
break;
}
case 4:
{
dwSizeCT=16;
m_nBmpFormat=BMP_FORMAT_16COLOR;
break;
}
case 8:
{
dwSizeCT=256;
m_nBmpFormat=BMP_FORMAT_256COLOR;
break;
}
case 24:
{
dwSizeCT=0;
m_nBmpFormat=BMP_FORMAT_24BIT;
}
}
return dwSizeCT;
}
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:
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:
void CGDIDoc::OnConvert256toRGB()
{
LPBITMAPINFO lpBi;
LPBITMAPINFO lpBi24;
HGLOBAL hDIB24;
DWORD dwSize;
int nSizeCT;
int i, j;
LPSTR lpRowSrc;
LPSTR lpRowTgt;
BYTE byIndex;
RGBQUAD rgbQuad;
POSITION pos;
CGDIView *ptrView;
lpBi=(LPBITMAPINFO)::GlobalLock(m_hDIB);
ASSERT(lpBi);
nSizeCT=GetColorTableSize(lpBi->bmiHeader.biBitCount);
dwSize=
(
sizeof(BITMAPINFOHEADER)+
WIDTHBYTES(24*lpBi->bmiHeader.biWidth)*lpBi->bmiHeader.biHeight
);
hDIB24=::GlobalAlloc(GHND, dwSize);
ASSERT(hDIB24);
lpBi24=(LPBITMAPINFO)::GlobalLock(hDIB24);
……
}
Chapter 10. Bitmap
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:
……
*lpBi24=*lpBi;
lpBi24->bmiHeader.biBitCount=24;
lpBi24->bmiHeader.biSizeImage=WIDTHBYTES
(
24*lpBi->bmiHeader.biWidth
)*lpBi->bmiHeader.biHeight;
lpBi24->bmiHeader.biClrUsed=0;
……
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:
……
for(j=0; j<lpBi->bmiHeader.biHeight; j++)
{
lpRowSrc=
(
(LPSTR)lpBi+
sizeof(BITMAPINFOHEADER)+
nSizeCT*sizeof(RGBQUAD)+
WIDTHBYTES(lpBi->bmiHeader.biBitCount*lpBi->bmiHeader.biWidth)*j
);
lpRowTgt=
(
(LPSTR)lpBi24+
sizeof(BITMAPINFOHEADER)+
WIDTHBYTES(lpBi24->bmiHeader.biBitCount*lpBi24->bmiHeader.biWidth)*j
);
for(i=0; i<lpBi->bmiHeader.biWidth; i++)
{
byIndex=*lpRowSrc;
rgbQuad=lpBi->bmiColors[byIndex];
*lpRowTgt=rgbQuad.rgbBlue;
*(lpRowTgt+1)=rgbQuad.rgbGreen;
*(lpRowTgt+2)=rgbQuad.rgbRed;
lpRowSrc++;
lpRowTgt+=3;
}
}
……
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
Chapter 10. Bitmap
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:
……
m_hDIB=hDIB24;
m_nBmpFormat=BMP_FORMAT_24BIT;
pos=GetFirstViewPosition();
ptrView=(CGDIView *)GetNextView(pos);
ASSERT(ptrView->IsKindOf(RUNTIME_CLASS(CGDIView)));
ptrView->LoadBitmap(m_hDIB);
}
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:
The function is implemented as follows (Most part of this function is copied from function
CGDIView::OnInitialUpdate()):
pDoc=GetDocument();
ASSERT_VALID(pDoc);
lpBi=(LPBITMAPINFO)::GlobalLock(hData);
ASSERT(lpBi);
nSizeCT=pDoc->GetColorTableSize(lpBi->bmiHeader.biBitCount);
pPalDraw=pDoc->GetPalette();
if(pPalDraw->GetSafeHandle() != NULL)pPalDraw->DeleteObject();
if(nSizeCT != 0)
{
lpLogPal=(LPLOGPALETTE) new BYTE
[
sizeof(LOGPALETTE)+(nSizeCT-1)*sizeof(PALETTEENTRY)
];
lpLogPal->palVersion=0x300;
lpLogPal->palNumEntries=nSizeCT;
for(i=0; i<nSizeCT; i++)
{
lpLogPal->palPalEntry[i].peRed=lpBi->bmiColors[i].rgbRed;
lpLogPal->palPalEntry[i].peGreen=lpBi->bmiColors[i].rgbGreen;
lpLogPal->palPalEntry[i].peBlue=lpBi->bmiColors[i].rgbBlue;
lpLogPal->palPalEntry[i].peFlags=NULL;
}
VERIFY(pPalDraw->CreatePalette(lpLogPal));
delete [](BYTE *)lpLogPal;
}
::GlobalUnlock(hData);
m_bBitmapLoaded=TRUE;
SetScrollSizes
Chapter 10. Bitmap
(
MM_TEXT,
CSize(lpBi->bmiHeader.biWidth, lpBi->bmiHeader.biHeight)
);
Invalidate();
}
void CGDIView::OnInitialUpdate()
{
CGDIDoc *pDoc;
HGLOBAL hData;
CScrollView::OnInitialUpdate();
pDoc=GetDocument();
ASSERT_VALID(pDoc);
hData=pDoc->GetHDib();
if(hData != NULL)LoadBitmap(hData);
else SetScrollSizes(MM_TEXT, CSize(0, 0));
}
With the above implementation, the application is able to convert a bitmap from 256-color format
format to 24-bit 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
Chapter 10. Bitmap
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:
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:
void CGDIDoc::OnConvertRGBto256()
{
LPBITMAPINFO lpBi;
LPBITMAPINFO lpBi24;
HGLOBAL hDIB;
DWORD dwSize;
int nSizeCT;
int i, j;
int n;
LPSTR lpRowSrc;
LPSTR lpRowTgt;
POSITION pos;
CGDIView *ptrView;
CPtrArray arRgbQuad;
LPRGBQUAD lpRgbQuad;
RGBQUAD rgbQuad;
BOOL bHit;
BOOL bStandardPal;
BYTE red, green, blue;
AfxGetApp()->DoWaitCursor(TRUE);
lpBi24=(LPBITMAPINFO)::GlobalLock(m_hDIB);
ASSERT(lpBi24);
for(j=0; j<lpBi24->bmiHeader.biHeight; j++)
{
lpRowSrc=
(
(LPSTR)lpBi24+
sizeof(BITMAPINFOHEADER)+
WIDTHBYTES(lpBi24->bmiHeader.biBitCount*lpBi24->bmiHeader.biWidth)*j
);
for(i=0; i<lpBi24->bmiHeader.biWidth; i++)
{
rgbQuad.rgbBlue=*lpRowSrc;
rgbQuad.rgbGreen=*(lpRowSrc+1);
rgbQuad.rgbRed=*(lpRowSrc+2);
rgbQuad.rgbReserved=0;
bHit=FALSE;
for(n=0; n<arRgbQuad.GetSize(); n++)
{
if
(
!memcmp
(
(LPSTR)&rgbQuad,
(LPSTR)arRgbQuad[n],
sizeof(RGBQUAD)
)
)
{
Chapter 10. Bitmap
bHit=TRUE;
break;
}
}
if(bHit == FALSE)
{
lpRgbQuad=new RGBQUAD;
*lpRgbQuad=rgbQuad;
arRgbQuad.Add(lpRgbQuad);
}
lpRowSrc+=3;
}
}
……
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:
……
if(arRgbQuad.GetSize() > 256)
{
while(arRgbQuad.GetSize())
{
delete (LPRGBQUAD)arRgbQuad.GetAt(0);
arRgbQuad.RemoveAt(0);
}
red=green=blue=0;
for(i=0; i<256; i++)
{
lpRgbQuad=new RGBQUAD;
lpRgbQuad->rgbBlue=blue;
lpRgbQuad->rgbGreen=green;
lpRgbQuad->rgbRed=red;
lpRgbQuad->rgbReserved=0;
if(!(red+=32))if(!(green+=32))blue+=64;
arRgbQuad.Add(lpRgbQuad);
}
bStandardPal=TRUE;
}
else bStandardPal=FALSE;
……
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:
(0, 64, 192) (32, 64, 192) (64, 64, 192) … (224, 64, 192)
……
(0, 224, 192) (32, 224, 192) (64, 224, 192) … (224, 224, 192)
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:
……
nSizeCT=256;
dwSize=
(
sizeof(BITMAPINFOHEADER)+
nSizeCT*sizeof(RGBQUAD)+
WIDTHBYTES(8*lpBi24->bmiHeader.biWidth)*lpBi24->bmiHeader.biHeight
);
hDIB=::GlobalAlloc(GHND, dwSize);
ASSERT(hDIB);
lpBi=(LPBITMAPINFO)::GlobalLock(hDIB);
ASSERT(lpBi);
*lpBi=*lpBi24;
lpBi->bmiHeader.biBitCount=8;
lpBi->bmiHeader.biSizeImage=WIDTHBYTES
(
8*lpBi->bmiHeader.biWidth
)*lpBi->bmiHeader.biHeight;
lpBi->bmiHeader.biClrUsed=0;
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:
……
for(j=0; j<lpBi->bmiHeader.biHeight; j++)
{
lpRowTgt=
(
(LPSTR)lpBi+
sizeof(BITMAPINFOHEADER)+
nSizeCT*sizeof(RGBQUAD)+
WIDTHBYTES(lpBi->bmiHeader.biBitCount*lpBi->bmiHeader.biWidth)*j
);
lpRowSrc=
(
(LPSTR)lpBi24+
sizeof(BITMAPINFOHEADER)+
WIDTHBYTES(lpBi24->bmiHeader.biBitCount*lpBi24->bmiHeader.biWidth)*j
);
for(i=0; i<lpBi->bmiHeader.biWidth; i++)
{
rgbQuad.rgbBlue=*lpRowSrc;
rgbQuad.rgbGreen=*(lpRowSrc+1);
rgbQuad.rgbRed=*(lpRowSrc+2);
rgbQuad.rgbReserved=0;
if(bStandardPal == TRUE)
{
if(rgbQuad.rgbBlue <= 0xdf)rgbQuad.rgbBlue+=0x20;
if(rgbQuad.rgbGreen <= 0xef)rgbQuad.rgbGreen+=0x10;
if(rgbQuad.rgbRed <= 0xef)rgbQuad.rgbRed+=0x10;
*lpRowTgt=(BYTE)
(
(0x00c0&rgbQuad.rgbBlue) |
((0x00e0&rgbQuad.rgbGreen)>>2) |
((0x00e0&rgbQuad.rgbRed)>>5)
);
}
else
{
for(n=0; n<arRgbQuad.GetSize(); n++)
{
if
(
!memcmp
(
(LPSTR)&rgbQuad,
(LPSTR)arRgbQuad[n],
sizeof(RGBQUAD)
)
)break;
}
*lpRowTgt=(BYTE)n;
}
lpRowSrc+=3;
lpRowTgt++;
}
}
……
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:
……
::GlobalUnlock(m_hDIB);
::GlobalFree(m_hDIB);
::GlobalUnlock(hDIB);
m_hDIB=hDIB;
m_nBmpFormat=BMP_FORMAT_256COLOR;
while(arRgbQuad.GetSize())
{
delete (LPRGBQUAD)arRgbQuad.GetAt(0);
arRgbQuad.RemoveAt(0);
Chapter 10. Bitmap
pos=GetFirstViewPosition();
ptrView=(CGDIView *)GetNextView(pos);
ASSERT(ptrView->IsKindOf(RUNTIME_CLASS(CGDIView)));
ptrView->LoadBitmap(m_hDIB);
AfxGetApp()->DoWaitCursor(FALSE);
}
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.
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:
void CGDIDoc::OnConvertBlackwhite()
{
LPBITMAPINFO lpBi;
int i, j;
LPSTR lpRow;
RGBQUAD rgbQuad;
POSITION pos;
CGDIView *ptrView;
AfxGetApp()->DoWaitCursor(TRUE);
lpBi=(LPBITMAPINFO)::GlobalLock(m_hDIB);
ASSERT(lpBi);
switch(lpBi->bmiHeader.biBitCount)
{
……
Chapter 10. Bitmap
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:
……
case 8:
{
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:
……
case 24:
{
for(j=0; j<lpBi->bmiHeader.biHeight; j++)
{
lpRow=
(
(LPSTR)lpBi+
sizeof(BITMAPINFOHEADER)+
WIDTHBYTES
(
lpBi->bmiHeader.biBitCount*
lpBi->bmiHeader.biWidth
)*j
);
for(i=0; i<lpBi->bmiHeader.biWidth; i++)
{
rgbQuad.rgbBlue=*lpRow;
rgbQuad.rgbGreen=*(lpRow+1);
rgbQuad.rgbRed=*(lpRow+2);
*(lpRow)=*(lpRow+1)=*(lpRow+2)=
(
rgbQuad.rgbBlue+rgbQuad.rgbGreen+rgbQuad.rgbBlue
)/3;
lpRow+=3;
}
}
break;
}
}
……
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.
Chapter 10. Bitmap
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
Chapter 10. Bitmap
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
);
Parameter Meaning
hdc The handle of the device context.
pbmi A BITMAPINFO type pointer that provides the bitmap information.
iUsage Specifies data type contained in member bmiColors of structure BITMAPINFO, which is
pointed by pbmi. If the value is DIB_PAL_COLORS, array bmiColors contains logical palette
indices; if the value is DIB_RGB_COLORS, array bmiColors contains literal RGB values.
ppvBits A pointer that can be used to receive device-independent bitmap’s bit values.
hSection Specifies the section handle, we can pass a file handle to it to allow the bitmap to be
mapped to the file. If we pass NULL, operating system will automatically allocate memory
for bitmap mapping.
dwOffset Specifies the offset from the beginning of the file to the bitmap data if the bitmap will be
mapped to a file. The parameter will be neglected if hSection is NULL.
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:
Chapter 10. Bitmap
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:
Variable Meaning
m_bmpDraw Load the normal bitmap image.
m_bmpMask Store the mask bitmap image.
m_bmpBS Used for creating memory bitmap.
m_dcMem Selects m_bmpDraw.
m_dcMemMask Selects m_bmpMask.
m_dcMemBS Selects m_bmpBS.
m_pBmpOld Used for selecting m_bmpDraw out of m_dcMem.
m_pBmpMaskOld Used for selecting m_bmpMask out of m_dcMemMask.
m_pBmpBSOld Used for selecting m_bmpBS out of m_dcMemBS.
m_pPalOld Used for selecting the logical palette out of m_dcMem.
m_pPalMaskOld Used for selecting the logical palette out of m_dcMemMask.
m_pPalBSOld Used for selecting the logical palette out of m_dcMemBS.
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:
CGDIView::CGDIView()
{
m_bBitmapLoaded=FALSE;
m_pBmpOld=NULL;
m_pBmpMaskOld=NULL;
m_pBmpBSOld=NULL;
m_pPalOld=NULL;
m_pPalMaskOld=NULL;
m_pPalBSOld=NULL;
}
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:
void CGDIView::CleanUp()
{
if(m_pPalOld != NULL)
Chapter 10. Bitmap
{
m_dcMem.SelectPalette(m_pPalOld, FALSE);
m_pPalOld=NULL;
}
if(m_pPalMaskOld != NULL)
{
m_dcMemMask.SelectPalette(m_pPalMaskOld, FALSE);
m_pPalMaskOld=NULL;
}
if(m_pPalBSOld != NULL)
{
m_dcMemBS.SelectPalette(m_pPalBSOld, FALSE);
m_pPalBSOld=NULL;
}
if(m_pBmpOld != NULL)
{
m_dcMem.SelectObject(m_pBmpOld);
m_pBmpOld=NULL;
}
if(m_pBmpMaskOld != NULL)
{
m_dcMemMask.SelectObject(m_pBmpMaskOld);
m_pBmpMaskOld=NULL;
}
if(m_pBmpBSOld != NULL)
{
m_dcMemBS.SelectObject(m_pBmpBSOld);
m_pBmpBSOld=NULL;
}
if(m_dcMem.GetSafeHdc() != NULL)m_dcMem.DeleteDC();
if(m_dcMemMask.GetSafeHdc() != NULL)m_dcMemMask.DeleteDC();
if(m_dcMemBS.GetSafeHdc() != NULL)m_dcMemBS.DeleteDC();
if(m_bmpDraw.GetSafeHandle() != NULL)m_bmpDraw.DeleteObject();
if(m_bmpMask.GetSafeHandle() != NULL)m_bmpMask.DeleteObject();
if(m_bmpBS.GetSafeHandle() != NULL)m_bmpBS.DeleteObject();
}
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:
void CGDIView::OnDestroy()
{
CleanUp();
CScrollView::OnDestroy();
}
CGDIDoc *pDoc;
pDoc=GetDocument();
ASSERT_VALID(pDoc);
AfxGetApp()->DoWaitCursor(TRUE);
lpBi=(LPBITMAPINFO)::GlobalLock(hData);
ASSERT(lpBi);
nSizeCT=pDoc->GetColorTableSize(lpBi->bmiHeader.biBitCount);
pPalDraw=pDoc->GetPalette();
if(pPalDraw->GetSafeHandle() != NULL)pPalDraw->DeleteObject();
if(nSizeCT != 0)
{
lpLogPal=(LPLOGPALETTE) new BYTE
[
sizeof(LOGPALETTE)+(nSizeCT-1)*sizeof(PALETTEENTRY)
];
lpLogPal->palVersion=0x300;
lpLogPal->palNumEntries=nSizeCT;
for(i=0; i<nSizeCT; i++)
{
lpLogPal->palPalEntry[i].peRed=lpBi->bmiColors[i].rgbRed;
lpLogPal->palPalEntry[i].peGreen=lpBi->bmiColors[i].rgbGreen;
lpLogPal->palPalEntry[i].peBlue=lpBi->bmiColors[i].rgbBlue;
lpLogPal->palPalEntry[i].peFlags=NULL;
}
VERIFY(pPalDraw->CreatePalette(lpLogPal));
delete [](BYTE *)lpLogPal;
pPalOld=dc.SelectPalette(pPalDraw, FALSE);
dc.RealizePalette();
}
CleanUp();
hBmp=::CreateDIBSection
(
dc.GetSafeHdc(),
lpBi,
DIB_RGB_COLORS,
(void **)&pBits,
NULL,
0
);
memcpy
(
(LPSTR)pBits,
(LPSTR)lpBi+sizeof(BITMAPINFOHEADER)+nSizeCT*sizeof(RGBQUAD),
lpBi->bmiHeader.biSizeImage
);
ASSERT(hBmp);
m_bmpDraw.Attach(hBmp);
……
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:
……
m_dcMem.CreateCompatibleDC(&dc);
m_dcMemMask.CreateCompatibleDC(&dc);
m_dcMemBS.CreateCompatibleDC(&dc);
ASSERT(m_dcMem.GetSafeHdc());
ASSERT(m_dcMemMask.GetSafeHdc());
ASSERT(m_dcMemBS.GetSafeHdc());
if(nSizeCT != 0)
{
Chapter 10. Bitmap
m_pPalOld=m_dcMem.SelectPalette(pPalDraw, FALSE);
m_pPalMaskOld=m_dcMemMask.SelectPalette(pPalDraw, FALSE);
m_pPalBSOld=m_dcMemBS.SelectPalette(pPalDraw, FALSE);
}
m_bmpMask.CreateCompatibleBitmap
(
&dc,
lpBi->bmiHeader.biWidth,
lpBi->bmiHeader.biHeight
);
m_bmpBS.CreateCompatibleBitmap
(
&dc,
lpBi->bmiHeader.biWidth,
lpBi->bmiHeader.biHeight
);
m_pBmpOld=m_dcMem.SelectObject(&m_bmpDraw);
m_pBmpMaskOld=m_dcMemMask.SelectObject(&m_bmpMask);
m_pBmpBSOld=m_dcMemBS.SelectObject(&m_bmpBS);
……
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:
……
for(j=0; j<lpBi->bmiHeader.biHeight; j++)
{
for(i=0; i<lpBi->bmiHeader.biWidth; i++)
{
if(m_dcMem.GetPixel(i, j) == RGB(255, 255, 255))
{
m_dcMemMask.SetPixel(i, j, RGB(255, 255, 255));
}
else m_dcMemMask.SetPixel(i, j, RGB(0, 0, 0));
}
}
m_bBitmapLoaded=TRUE;
SetScrollSizes
(
MM_TEXT,
CSize(lpBi->bmiHeader.biWidth, lpBi->bmiHeader.biHeight)
);
::GlobalUnlock(hData);
if(nSizeCT != 0)dc.SelectPalette(pPalOld, FALSE);
Invalidate();
AfxGetApp()->DoWaitCursor(FALSE);
}
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.
if(m_bBitmapLoaded == FALSE)return;
CGDIDoc *pDoc=GetDocument();
ASSERT_VALID(pDoc);
Chapter 10. Bitmap
pPal=pDoc->GetPalette();
pPalOld=pDC->SelectPalette(pPal, FALSE);
pDC->RealizePalette();
m_bmpDraw.GetBitmap(&bm);
m_dcMemBS.BitBlt
(
0,
0,
bm.bmWidth,
bm.bmHeight,
pDC,
0,
0,
SRCCOPY
);
m_dcMemBS.BitBlt
(
0,
0,
bm.bmWidth,
bm.bmHeight,
&m_dcMem,
0,
0,
SRCINVERT
);
m_dcMemBS.BitBlt
(
0,
0,
bm.bmWidth,
bm.bmHeight,
&m_dcMemMask,
0,
0,
SRCAND
);
m_dcMemBS.BitBlt
(
0,
0,
bm.bmWidth,
bm.bmHeight,
&m_dcMem,
0,
0,
SRCINVERT
);
pDC->BitBlt
(
0,
0,
bm.bmWidth,
bm.bmHeight,
&m_dcMemBS,
0,
0,
SRCCOPY
);
pDC->SelectPalette(pPalOld, FALSE);
}
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.
Chapter 10. Bitmap
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():
void CGDIView::OnInitialUpdate()
{
CGDIDoc *pDoc;
HGLOBAL hData;
BITMAP bm;
if(m_bmpBkd.GetSafeHandle() == NULL)
{
m_bmpBkd.LoadBitmap(IDB_BITMAPBKD);
m_bmpBkd.GetBitmap(&bm);
m_bmpBkd.SetBitmapDimension(bm.bmWidth, bm.bmHeight);
ASSERT(m_bmpBkd.GetSafeHandle());
}
CScrollView::OnInitialUpdate();
……
}
pt=GetScrollPosition();
dcMem.CreateCompatibleDC(pDC);
pBmpOld=dcMem.SelectObject(&m_bmpBkd);
size=m_bmpBkd.GetBitmapDimension();
GetClientRect(rect);
rect.right+=pt.x;
rect.bottom+=pt.y;
nRepX=(rect.Width()+size.cx-1)/size.cx;
nRepY=(rect.Height()+size.cy-1)/size.cy;
for(i=0; i<nRepX; i++)
{
for(j=0; j<nRepY; j++)
{
pDC->BitBlt
(
i*size.cx-pt.x,
j*size.cy-pt.y,
size.cx,
size.cy,
&dcMem,
0,
0,
SRCCOPY
);
}
}
dcMem.SelectObject(pBmpOld);
return TRUE;
}
Chapter 10. Bitmap
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.
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:
Chapter 10. Bitmap
Draw this
portion with
shadowed color
Draw this
portion with
highlighted color
Figure 10-3. Implement chiseled effect
The highlighted outline can be obtained in the same way, but we need to combine the original
bitmap and the inverted bitmap differently here:
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.
Draw original
image at (0, 0) Resulted outline
Combine them
using bit-wise OR
operation
Draw inverted
image at (1, 1)
Figure 10-4. Generate the outline that should be drawn with shadowed color
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.
Draw original
image at (1, 1)
Combine them
using bit-wise OR
operation
Draw inverted
image at (0, 0)
Resulted outline
Figure 10-5. Generate the outline that should be drawn with highlighted 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).
Chapter 10. Bitmap
Chiselled Effect
With the following steps, we can create chiselled effect:
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:
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:
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.
The following is a list of some values of nIndex parameter that could be used to retrieve some
standard colors in the system:
Parameter Meaning
COLOR_3DFACE Face color that should be used to draw three-dimensional
COLOR_BTNFACE
display elements such as a button.
COLOR_3DHILIGHT Highlight color of three-dimensional display elements, this
COLOR_3DHIGHLIGHT
COLOR_BTNHILIGHT
color is used for drawing edges facing the light source.
COLOR_BTNHIGHLIGHT
COLOR_3DLIGHT Light color of three-dimensional display elements, this color is
used for drawing edges facing the light source.
COLOR_3DSHADOW Shadow color for three-dimensional display elements, this
COLOR_BTNSHADOW
color is used for drawing edges facing away from the light
source.
New Function
In the sample, a new function CreateGrayedBitmap(…) is declared in class CGDIView to create grayed
image from a nomal bimap:
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:
dcMono.CreateCompatibleDC(&dc);
dcColor.CreateCompatibleDC(&dc);
ASSERT(dcMono.GetSafeHdc());
ASSERT(dcColor.GetSafeHdc());
pBmp->GetBitmap(&bm);
bmpShadow.CreateBitmap(bm.bmWidth, bm.bmHeight, 1, 1, NULL);
bmpHilight.CreateBitmap(bm.bmWidth, bm.bmHeight, 1, 1, NULL);
bmpGray.CreateCompatibleBitmap(&dc, bm.bmWidth, bm.bmHeight);
pBmpOld=dcColor.SelectObject(pBmp);
……
Chapter 10. Bitmap
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:
……
pBmpShadowOld=dcMono.SelectObject(&bmpShadow);
dcMono.FillSolidRect(0, 0, bm.bmWidth, bm.bmHeight, RGB(255, 255, 255));
dcMono.BitBlt
(
0, 0,
bm.bmWidth-1, bm.bmHeight-1,
&dcColor,
1, 1,
SRCCOPY
);
dcMono.BitBlt
(
0, 0,
bm.bmWidth, bm.bmHeight,
&dcColor,
0, 0,
MERGEPAINT
);
dcMono.SelectObject(pBmpShadowOld);
……
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:
……
pBmpHilightOld=dcMono.SelectObject(&bmpHilight);
dcMono.BitBlt
(
0, 0,
bm.bmWidth, bm.bmHeight,
&dcColor,
0, 0,
SRCCOPY
);
dcMono.BitBlt
(
0, 0,
bm.bmWidth-1, bm.bmHeight-1,
&dcColor,
1, 1,
MERGEPAINT
);
dcMono.SelectObject(pBmpHilightOld);
Chapter 10. Bitmap
dcColor.SelectObject(pBmpOld);
pBmpOld=dcColor.SelectObject(&bmpGray);
……
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):
……
brush.CreateSolidBrush(::GetSysColor(COLOR_BTNFACE));
pBrOld=dcColor.SelectObject(&brush);
dcColor.PatBlt
(
0, 0,
bm.bmWidth, bm.bmHeight,
PATCOPY
);
dcColor.SelectObject(pBrOld);
brush.DeleteObject();
dcColor.SetBkColor(RGB(255, 255, 255));
dcColor.SetTextColor(RGB(0, 0, 0));
……
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:
……
brush.CreateSolidBrush(::GetSysColor(COLOR_BTNHIGHLIGHT));
pBrOld=dcColor.SelectObject(&brush);
pBmpHilightOld=dcMono.SelectObject(&bmpHilight);
dcColor.BitBlt
(
0, 0,
bm.bmWidth, bm.bmHeight,
&dcMono,
0, 0,
0x00B8074A
);
dcColor.SelectObject(pBrOld);
brush.DeleteObject();
dcMono.SelectObject(pBmpHilightOld);
……
Also, the shadowed outline is drawn on the grayed image in the same way:
……
brush.CreateSolidBrush(::GetSysColor(COLOR_BTNSHADOW));
pBrOld=dcColor.SelectObject(&brush);
pBmpShadowOld=dcMono.SelectObject(&bmpShadow);
dcColor.BitBlt
(
0, 0,
bm.bmWidth, bm.bmHeight,
&dcMono,
0, 0,
0x00B8074A
);
dcColor.SelectObject(pBrOld);
brush.DeleteObject();
dcMono.SelectObject(pBmpShadowOld);
dcColor.SelectObject(pBmpOld);
return (HBITMAP)bmpGray.Detach();
}
Finally, some clean up routines. Before this function exits, we call function CBitmap::Detach() to detach
HBITMAP type handle from CBitmap type variable. This is because we want to leave the HBITMAP handle for
Chapter 10. Bitmap
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.
pDoc=GetDocument();
ASSERT_VALID(pDoc);
AfxGetApp()->DoWaitCursor(TRUE);
……
memcpy
(
(LPSTR)pBits,
(LPSTR)lpBi+sizeof(BITMAPINFOHEADER)+nSizeCT*sizeof(RGBQUAD),
lpBi->bmiHeader.biSizeImage
);
ASSERT(hBmp);
m_bmpDraw.Attach(hBmp);
hBmpGray=CreateGrayedBitmap(&m_bmpDraw);
ASSERT(hBmpGray);
m_bmpDraw.DeleteObject();
m_bmpDraw.Attach(hBmpGray);
m_dcMem.CreateCompatibleDC(&dc);
ASSERT(m_dcMem.GetSafeHdc());
……
}
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:
Chapter 10. Bitmap
if(m_bBitmapLoaded == FALSE)return;
CGDIDoc *pDoc=GetDocument();
ASSERT_VALID(pDoc);
pPal=pDoc->GetPalette();
pPalOld=pDC->SelectPalette(pPal, FALSE);
pDC->RealizePalette();
m_bmpDraw.GetBitmap(&bm);
pDC->BitBlt
(
0,
0,
bm.bmWidth,
bm.bmHeight,
&m_dcMem,
0,
0,
SRCCOPY
);
pDC->SelectPalette(pPalOld, FALSE);
}
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:
10) The total image size can be calculated from the following formula:
Chapter 10. Bitmap
(size of bitmap information header) + (number of bytes for one raster line) ´ image height
(size of bitmap information header) + size of color table + (number of bytes for one raster line) ´
image height
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.
Chapter 10. Bitmap