Friday, October 30, 2009

The Union of DIBs and DDBs



The Union of DIBs and DDBs

You can do a lot knowing the format of the DIB and by calling the two DIB-drawing
functions, SetDIBitsToDevice and
StretchDIBits. You have direct access to every single bit,
byte, and pixel in the DIB, and once you come up with a bunch of functions that let you
examine and alter this data in a structured manner, there are no restrictions on what you can do.

Actually, we've found that there are some restrictions. In the last chapter, we saw
how you can use GDI functions to draw images on a DDB. So far, there doesn't appear to
be any way we can do that with DIBs. Another problem is that
SetDIBitsToDevice and StretchDIBits are not nearly as fast as
BitBlt and StretchBlt, particularly under Windows NT
and when many nearest-color searches have to be performed, such as when 24-bit DIBs
are displayed on 8-bit video boards.

So, it might be advantageous to convert between DIBs and DDBs. For example,
if we had a DIB that we needed to display to the screen and we might have to do this
numerous times, then it would make more sense to convert the DIB into a DDB so that
we could use the faster BitBlt and
StretchBlt functions with it.

Creating a DDB from a DIB

Is it possible to create a GDI bitmap object from a DIB? We basically already know
how to do it: If you have a DIB, you can use
CreateCompatibleBitmap to create a GDI
bitmap object of the same size as the DIB and compatible with the video display. You then
select the bitmap object into a memory device context and call
SetDIBitsToDevice to draw on that memory DC. The result is a DDB with the same image as the DIB but with a color
organization that is compatible with the video display.

Or you can do the job with a fewer number of steps by using
CreateDIBitmap. The function has the following syntax:


hBitmap = CreateDIBitmap (
hdc, // device context handle
pInfoHdr, // pointer to DIB information header
fInit, // 0 or CBM_INIT
pBits, // pointer to DIB pixel bits
pInfo, // pointer to DIB information
fClrUse) ; // color use flag

Notice the two arguments I've called
pInfoHdr and pInfo. These are defined as
pointers to a BITMAPINFOHEADER structure and a BITMAPINFO structure, respectively. As
we know, the BITMAPINFO structure is a BITMAPINFOHEADER structure followed by a
color table. We'll see how this distinction works shortly. The last argument is either
DIB_RGB_ COLORS (which equals 0) or DIB_PAL_COLORS, as with the
SetDIBitsToDevice functions. I'll have more to say about this in the next chapter.

It is important in understanding the full array of bitmap functions in Windows to
realize that, despite its name, the
CreateDIBitmap function does not create a
device-independent bitmap
. It creates a
device-dependent bitmap from a device-independent
specification. Notice that the function returns a handle to a GDI bitmap object, the same as
CreateBitmap, CreateBitmapIndirect, and
CreateCompatibleBitmap.

The simplest way to call the
CreateDIBitmap function is like so:


hBitmap = CreateDIBitmap (NULL, pbmih, 0, NULL, NULL, 0) ;

The only argument is a pointer to a BITMAPINFOHEADER structure (without the
color table). In this form, the function creates a monochrome GDI bitmap object. The
second simplest way to call the function is


hBitmap = CreateDIBitmap (hdc, pbmih, 0, NULL, NULL, 0) ;

In this form, the function creates a DDB that is compatible with the device context
indicated by the hdc argument. So far, we've done nothing we couldn't have done
using CreateBitmap (to create a monochrome bitmap) or
CreateCompatibleBitmap (to create one compatible with the video display).

In these two simplified forms of
CreateDIBitmap, the pixel bits remain
uninitialized. If the third argument to
CreateDIBitmap is CBM_INIT, Windows creates the DDB and
uses the last three arguments to initialize the bitmap bits. The
pInfo argument is a pointer to a BITMAPINFO structure that includes a color table. The
pBits argument is a pointer to an array of bits in the color format indicated by the BITMAPINFO structure. Based on the
color table, these bits are converted to the color format of the device. This is identical to
what happens in SetDIBitsToDevice. Indeed, the entire
CreateDIBitmap function could probably be implemented with the following code:


HBITMAP CreateDIBitmap (HDC hdc, CONST BITMAPINFOHEADER * pbmih,
DWORD fInit, CONST VOID * pBits,
CONST BITMAPINFO * pbmi, UINT fUsage)
{
HBITMAP hBitmap ;
HDC hdc ;
int cx, cy, iBitCount ;

if (pbmih->biSize == sizeof (BITMAPCOREHEADER))
{
cx = ((PBITMAPCOREHEADER) pbmih)->bcWidth ;
cy = ((PBITMAPCOREHEADER) pbmih)->bcHeight ;
iBitCount = ((PBITMAPCOREHEADER) pbmih)->bcBitCount ;
}
else
{
cx = pbmih->biWidth ;
cy = pbmih->biHeight ;
iBitCount = pbmih->biBitCount ;
}
if (hdc)
hBitmap = CreateCompatibleBitmap (hdc, cx, cy) ;
else
hBitmap = CreateBitmap (cx, cy, 1, 1, NULL) ;

if (fInit == CBM_INIT)
{
hdcMem = CreateCompatibleDC (hdc) ;
SelectObject (hdcMem, hBitmap) ;
SetDIBitsToDevice (hdcMem, 0, 0, cx, cy, 0, 0, 0 cy,
pBits, pbmi, fUsage) ;
DeleteDC (hdcMem) ;
}

return hBitmap ;
}

If you're going to display a DIB only once and you're worried about the
performance of SetDIBitsToDevice, it doesn't make much sense to call
CreateDIBitmap and then display the DDB by using
BitBlt or StretchBlt. The two jobs will take the same length of
time because SetDIBitsToDevice and
CreateDIBitmap both have to perform a color
conversion. Only if you're displaying a DIB multiple times—which is very likely when
processing WM_PAINT messages—does this conversion make sense.

The DIBCONV program shown in Figure 15-10 shows how you can use
SetDIBitsToDevice to convert a DIB file to a DDB.

Figure 15-10. The DIBCONV program.



DIBCONV.C



/*----------------------------------------
DIBCONV.C -- Converts a DIB to a DDB
(c) Charles Petzold, 1998
----------------------------------------*/


#include <windows.h>
#include <commdlg.h>
#include "resource.h"
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

TCHAR szAppName[] = TEXT ("DibConv") ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
HWND hwnd ;
MSG msg ;
WNDCLASS wndclass ;

wndclass.style = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = szAppName ;
wndclass.lpszClassName = szAppName ;

if (!RegisterClass (&wndclass))
{
MessageBox (NULL, TEXT ("This program requires Windows NT!"),
szAppName, MB_ICONERROR) ;
return 0 ;
}

hwnd = CreateWindow (szAppName, TEXT ("DIB to DDB Conversion"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL) ;

ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;

while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;

}
return msg.wParam ;
}
HBITMAP CreateBitmapObjectFromDibFile (HDC hdc, PTSTR szFileName)
{
BITMAPFILEHEADER * pbmfh ;
BOOL bSuccess ;
DWORD dwFileSize, dwHighSize, dwBytesRead ;
HANDLE hFile ;
HBITMAP hBitmap ;

// Open the file: read access, prohibit write access

hFile = CreateFile (szFileName, GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL) ;

if (hFile == INVALID_HANDLE_VALUE)
return NULL ;

// Read in the whole file

dwFileSize = GetFileSize (hFile, &dwHighSize) ;

if (dwHighSize)
{
CloseHandle (hFile) ;
return NULL ;
}

pbmfh = malloc (dwFileSize) ;

if (!pbmfh)
{
CloseHandle (hFile) ;
return NULL ;
}

bSuccess = ReadFile (hFile, pbmfh, dwFileSize, &dwBytesRead, NULL) ;
CloseHandle (hFile) ;

// Verify the file

if (!bSuccess || (dwBytesRead != dwFileSize)
|| (pbmfh->bfType != * (WORD *) "BM")
|| (pbmfh->bfSize != dwFileSize))
{
free (pbmfh) ;
return NULL ;
}
// Create the DDB

hBitmap = CreateDIBitmap (hdc,
(BITMAPINFOHEADER *) (pbmfh + 1),
CBM_INIT,
(BYTE *) pbmfh + pbmfh->bfOffBits,
(BITMAPINFO *) (pbmfh + 1),
DIB_RGB_COLORS) ;
free (pbmfh) ;
return hBitmap ;
}

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static HBITMAP hBitmap ;
static int cxClient, cyClient ;
static OPENFILENAME ofn ;
static TCHAR szFileName [MAX_PATH], szTitleName [MAX_PATH] ;
static TCHAR szFilter[] = TEXT ("Bitmap Files (*.BMP)\0*.bmp\0")
TEXT ("All Files (*.*)\0*.*\0\0") ;
BITMAP bitmap ;
HDC hdc, hdcMem ;
PAINTSTRUCT ps ;

switch (message)
{
case WM_CREATE:
ofn.lStructSize = sizeof (OPENFILENAME) ;
ofn.hwndOwner = hwnd ;
ofn.hInstance = NULL ;
ofn.lpstrFilter = szFilter ;
ofn.lpstrCustomFilter = NULL ;
ofn.nMaxCustFilter = 0 ;
ofn.nFilterIndex = 0 ;
ofn.lpstrFile = szFileName ;
ofn.nMaxFile = MAX_PATH ;
ofn.lpstrFileTitle = szTitleName ;
ofn.nMaxFileTitle = MAX_PATH ;
ofn.lpstrInitialDir = NULL ;
ofn.lpstrTitle = NULL ;
ofn.Flags = 0 ;
ofn.nFileOffset = 0 ;
ofn.nFileExtension = 0 ;
ofn.lpstrDefExt = TEXT ("bmp") ;
ofn.lCustData = 0 ;
ofn.lpfnHook = NULL ;
ofn.lpTemplateName = NULL ;

return 0 ;

case WM_SIZE:
cxClient = LOWORD (lParam) ;
cyClient = HIWORD (lParam) ;
return 0 ;
case WM_COMMAND:
switch (LOWORD (wParam))
{
case IDM_FILE_OPEN:

// Show the File Open dialog box

if (!GetOpenFileName (&ofn))
return 0 ;

// If there's an existing DIB, delete it

if (hBitmap)
{
DeleteObject (hBitmap) ;
hBitmap = NULL ;
}
// Create the DDB from the DIB

SetCursor (LoadCursor (NULL, IDC_WAIT)) ;
ShowCursor (TRUE) ;

hdc = GetDC (hwnd) ;
hBitmap = CreateBitmapObjectFromDibFile (hdc, szFileName) ;
ReleaseDC (hwnd, hdc) ;

ShowCursor (FALSE) ;
SetCursor (LoadCursor (NULL, IDC_ARROW)) ;

// Invalidate the client area for later update

InvalidateRect (hwnd, NULL, TRUE) ;

if (hBitmap == NULL)
{
MessageBox (hwnd, TEXT ("Cannot load DIB file"),
szAppName, MB_OK | MB_ICONEXCLAMATION) ;
}
return 0 ;
}
break ;

case WM_PAINT:
hdc = BeginPaint (hwnd, &ps) ;

if (hBitmap)
{
GetObject (hBitmap, sizeof (BITMAP), &bitmap) ;

hdcMem = CreateCompatibleDC (hdc) ;
SelectObject (hdcMem, hBitmap) ;

BitBlt (hdc, 0, 0, bitmap.bmWidth, bitmap.bmHeight,
hdcMem, 0, 0, SRCCOPY) ;

DeleteDC (hdcMem) ;
}

EndPaint (hwnd, &ps) ;
return 0 ;

case WM_DESTROY:
if (hBitmap)
DeleteObject (hBitmap) ;

PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}





DIBCONV.RC (excerpts)



//Microsoft Developer Studio generated resource script.



#include "resource.h"
#include "afxres.h"
/////////////////////////////////////////////////////////////////////////////
// Menu

DIBCONV MENU DISCARDABLE
BEGIN
POPUP "&File"
BEGIN
MENUITEM "&Open", IDM_FILE_OPEN
END
END





RESOURCE.H (excerpts)




// Microsoft Developer Studio generated include file.
// Used by DibConv.rc

#define IDM_FILE_OPEN 40001



DIBCONV.C is self-contained and requires no earlier files. In response to its only
menu command (File Open), WndProc calls the program's
CreateBitmapObjectFromDibFile function. This function reads the entire file into memory and passes pointers to the
memory block to the CreateDIBitmap function. The function returns a handle to the bitmap.
The memory block containing the DIB can then be freed. During the WM_PAINT
message, WndProc selects the bitmap in a compatible memory device context and uses
BitBlt rather than SetDIBitsToDevice to display the bitmap on the client area. It obtains the width
and height of the bitmap by calling
GetObject with the BITMAP structure on the bitmap handle.

You do not need to initialize the DDB pixel bits while creating the bitmap from
CreateDIBitmap. You can do it later by calling
SetDIBits. This function has the following syntax:


iLines = SetDIBits (
hdc, // device context handle
hBitmap, // bitmap handle
yScan, // first scan line to convert
cyScans, // number of scan lines to convert
pBits, // pointer to pixel bits
pInfo, // pointer to DIB information
fClrUse) ; // color use flag

The function uses the color table in the BITMAPINFO structure to convert the bits into
the device-dependent format. The device context handle is required only if the last
argument is set to DIB_PAL_COLORS.

From DDB to DIB

A function similar to the SetDIBits function is
GetDIBits. You can use this function for
converting a DDB to a DIB:


int WINAPI GetDIBits (
hdc, // device context handle
hBitmap, // bitmap handle
yScan, // first scan line to convert
cyScans, // number of scan lines to convert
pBits, // pointer to pixel bits (out)
pInfo, // pointer to DIB information (out)
fClrUse) ; // color use flag

However, I'm afraid that this function is not simply the reverse of
SetDIBits. In the general case, if you convert a DIB to a DDB using
CreateDIBitmap and SetDIBits and
then convert back to a DIB using GetDIBits, you won't get what you started out with. This
is because some information is lost when a DIB is converted to a device-dependent
format. How much information is lost depends on the particular video mode you're running
Windows under when you do the conversion.

You probably won't find a need to use
GetDIBits much. Think about it: In what circumstances does your program find itself with a bitmap handle without having the
data used to create the bitmap in the first place? The clipboard? But the clipboard
provides automatic conversion to DIBs. The one instance in which the
GetDIBits function does come in handy is when you're doing screen captures, such as what the BLOWUP program
did in Chapter 14. I won't be demonstrating this function, but some information is
available in Knowledge Base article Q80080.

The DIB Section

Now, I hope, you have a good feel for the difference between device-dependent and
device-independent bitmaps. A DIB can have one of several color organizations; a DDB must
be either monochrome or the same format as a real-output device. A DIB is a file or a

block of memory; a DDB is a GDI bitmap object and is represented by a bitmap handle. A
DIB can be displayed or converted to a DDB and back again, but this involves a process
to convert between device-independent bits and device-specific bits.

Now you're about to encounter a function that seems to break these rules. This
function was introduced in the 32-bit versions of Windows and is called
CreateDIBSection. The syntax is


hBitmap = CreateDIBSection (
hdc, // device context handle
pInfo, // pointer to DIB information
fClrUse, // color use flag
ppBits, // pointer to pointer variable
hSection, // file-mapping object handle
dwOffset) ; // offset to bits in file-mapping object

CreateDIBSection is one of the most important functions in the Windows API (well, at
least if you're working with bitmaps a lot), yet it's burdened with such weirdness that you
may find it inordinately esoteric and difficult to comprehend.

Let's begin with the very name of the function. We know what a DIB is, but what
on earth is a "DIB section"? When you first began examining
CreateDIBSection, you may have kept looking for some way that the function works with only part of the DIB. That's
almost right. What CreateDIBSection does is indeed create a section of the DIB—a
memory block for the bitmap pixel bits.

Now let's look at the return value. It's a handle to a GDI bitmap object. That
return value is probably the most deceptive aspect of the function call. The return value
seems to imply that CreateDIBSection is similar in functionality to
CreateDIBitmap. Yes, it's similar but also totally different. In fact, the bitmap handle returned from
CreateDIBSection is intrinsically different from the bitmap handle returned from all the previous
bitmap-creation functions we've encountered in this chapter and the last chapter.

Once you understand the true nature of
CreateDIBSection, you might wonder why the return value wasn't defined somewhat differently. You might also conclude that
CreateDIBSection should have been called
CreateDIBitmap and that
CreateDIBitmap should have been called, as I indicated earlier,
CreateDDBitmap.

To first approach CreateDIBSection, let's examine how we can simplify it and put
it to use right away. First, you can set the last two arguments,
hSection and dwOffset, to NULL and 0, respectively. I'll discuss the use of these arguments towards the end of this
chapter. Second, the hdc parameter is used only if the
fColorUse parameter is set to DIB_ PAL_COLORS. If
fColorUse is DIB_RGB_COLORS (or 0),
hdc is ignored. (This is not the case with
CreateDIBitmap, in which the hdc parameter is used to get the color format of
the device that the DDB is to be compatible with.)

So, in its simplest form,
CreateDIBSection requires only the second and fourth
arguments. The second argument is a pointer to a BITMAPINFO structure, something
we've worked with before. I hope the pointer to a pointer definition of the fourth argument
doesn't upset you too much. It's actually quite simple when using the function.

Let's suppose you want to create a 384×256-bit DIB with 24 bits per pixel. The
24-bit format is simplest because it doesn't require a color table, so we can use a
BITMAPINFOHEADER structure for the BITMAPINFO parameter.

You define three variables: a BITMAPINFOHEADER structure, a BYTE pointer,
and a bitmap handle:


BITMAPINFOHEADER bmih ;
BYTE * pBits ;
HBITMAP hBitmap ;

Now initialize the fields of the BITMAPINFOHEADER structure:


bmih->biSize = sizeof (BITMAPINFOHEADER) ;
bmih->biWidth = 384 ;
bmih->biHeight = 256 ;
bmih->biPlanes = 1 ;
bmih->biBitCount = 24 ;
bmih->biCompression = BI_RGB ;
bmih->biSizeImage = 0 ;
bmih->biXPelsPerMeter = 0 ;
bmih->biYPelsPerMeter = 0 ;
bmih->biClrUsed = 0 ;
bmih->biClrImportant = 0 ;

With this minimum amount of preparation, we are now ready to call the function:


hBitmap = CreateDIBSection (NULL, (BITMAPINFO *) &bmih, 0, &pBits, NULL, 0) ;

Notice that we're taking the address of the BITMAPINFOHEADER structure for the
second argument, as usual, but also the address of the BYTE pointer
pBits, which is not usual. Thus, the fourth argument is a pointer to a pointer, as required by the function.

Here's what the function call does:
CreateDIBSection examines the BITMAPINFOHEADER structure and allocates a block of memory sufficient to hold the DIB pixel
bits. (In this particular case, the block is 384×256×3 bytes in size.) It stores a pointer to
this memory block in the pBits parameter that you've supplied. The function also returns
a handle to a bitmap, which, as I've said, is not quite the same as the handle returned
from CreateDIBitmap and other bitmap-creation functions.

We're not quite done yet, however. The bitmap pixel bits are uninitialized. If
you're reading a DIB file, you can simply pass the
pBits parameter to the ReadFile function
and read them in. Or you can set them "manually" with some program code.

The DIBSECT program shown in Figure 15-11 is similar to the DIBCONV
program except that it calls
CreateDIBSection rather than
CreateDIBitmap.

Figure 15-11. The DIBSECT program.



DIBSECT.C



/*--------------------------------------------------------
DIBSECT.C -- Displays a DIB Section in the client area
(c) Charles Petzold, 1998
--------------------------------------------------------*/
#include <windows.h>
#include <commdlg.h>
#include "resource.h"


LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

TCHAR szAppName[] = TEXT ("DibSect") ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
HWND hwnd ;
MSG msg ;
WNDCLASS wndclass ;
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = szAppName ;
wndclass.lpszClassName = szAppName ;

if (!RegisterClass (&wndclass))
{
MessageBox (NULL, TEXT ("This program requires Windows NT!"),
szAppName, MB_ICONERROR) ;
return 0 ;
}

hwnd = CreateWindow (szAppName, TEXT ("DIB Section Display"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL) ;

ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;

while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;

}
return msg.wParam ;
}
HBITMAP CreateDibSectionFromDibFile (PTSTR szFileName)
{
BITMAPFILEHEADER bmfh ;
BITMAPINFO * pbmi ;
BYTE * pBits ;
BOOL bSuccess ;
DWORD dwInfoSize, dwBytesRead ;
HANDLE hFile ;
HBITMAP hBitmap ;
// Open the file: read access, prohibit write access

hFile = CreateFile (szFileName, GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, 0, NULL) ;

if (hFile == INVALID_HANDLE_VALUE)
return NULL ;

// Read in the BITMAPFILEHEADER

bSuccess = ReadFile (hFile, &bmfh, sizeof (BITMAPFILEHEADER),
&dwBytesRead, NULL) ;

if (!bSuccess || (dwBytesRead != sizeof (BITMAPFILEHEADER))
|| (bmfh.bfType != * (WORD *) "BM"))
{
CloseHandle (hFile) ;
return NULL ;
}

// Allocate memory for the BITMAPINFO structure & read it in

dwInfoSize = bmfh.bfOffBits - sizeof (BITMAPFILEHEADER) ;

pbmi = malloc (dwInfoSize) ;

bSuccess = ReadFile (hFile, pbmi, dwInfoSize, &dwBytesRead, NULL) ;

if (!bSuccess || (dwBytesRead != dwInfoSize))
{
free (pbmi) ;
CloseHandle (hFile) ;
return NULL ;
}
// Create the DIB Section

hBitmap = CreateDIBSection (NULL, pbmi, DIB_RGB_COLORS, &pBits, NULL, 0) ;

if (hBitmap == NULL)
{
free (pbmi) ;
CloseHandle (hFile) ;
return NULL ;
}

// Read in the bitmap bits

ReadFile (hFile, pBits, bmfh.bfSize - bmfh.bfOffBits, &dwBytesRead, NULL) ;

free (pbmi) ;
CloseHandle (hFile) ;

return hBitmap ;
}

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static HBITMAP hBitmap ;
static int cxClient, cyClient ;
static OPENFILENAME ofn ;
static TCHAR szFileName [MAX_PATH], szTitleName [MAX_PATH] ;
static TCHAR szFilter[] = TEXT ("Bitmap Files (*.BMP)\0*.bmp\0")
TEXT ("All Files (*.*)\0*.*\0\0") ;
BITMAP bitmap ;
HDC hdc, hdcMem ;
PAINTSTRUCT ps ;

switch (message)
{
case WM_CREATE:
ofn.lStructSize = sizeof (OPENFILENAME) ;
ofn.hwndOwner = hwnd ;
ofn.hInstance = NULL ;
ofn.lpstrFilter = szFilter ;
ofn.lpstrCustomFilter = NULL ;
ofn.nMaxCustFilter = 0 ;
ofn.nFilterIndex = 0 ;
ofn.lpstrFile = szFileName ;
ofn.nMaxFile = MAX_PATH ;
ofn.lpstrFileTitle = szTitleName ;
ofn.nMaxFileTitle = MAX_PATH ;
ofn.lpstrInitialDir = NULL ;
ofn.lpstrTitle = NULL ;
ofn.Flags = 0 ;
ofn.nFileOffset = 0 ;
ofn.nFileExtension = 0 ;
ofn.lpstrDefExt = TEXT ("bmp") ;
ofn.lCustData = 0 ;
ofn.lpfnHook = NULL ;
ofn.lpTemplateName = NULL ;

return 0 ;

case WM_SIZE:
cxClient = LOWORD (lParam) ;
cyClient = HIWORD (lParam) ;
return 0 ;

case WM_COMMAND:
switch (LOWORD (wParam))
{
case IDM_FILE_OPEN:

// Show the File Open dialog box

if (!GetOpenFileName (&ofn))
return 0 ;

// If there's an existing bitmap, delete it

if (hBitmap)
{
DeleteObject (hBitmap) hBitmap = NULL ;
}
// Create the DIB Section from the DIB file

SetCursor (LoadCursor (NULL, IDC_WAIT)) ;
ShowCursor (TRUE) ;

hBitmap = CreateDibSectionFromDibFile (szFileName) ;
ShowCursor (FALSE) ;
SetCursor (LoadCursor (NULL, IDC_ARROW)) ;

// Invalidate the client area for later update

InvalidateRect (hwnd, NULL, TRUE) ;

if (hBitmap == NULL)
{
MessageBox (hwnd, TEXT ("Cannot load DIB file"),
szAppName, MB_OK | MB_ICONEXCLAMATION) ;
}
return 0 ;
}
break ;

case WM_PAINT:
hdc = BeginPaint (hwnd, &ps) ;

if (hBitmap)
{
GetObject (hBitmap, sizeof (BITMAP), &bitmap) ;

hdcMem = CreateCompatibleDC (hdc) ;
SelectObject (hdcMem, hBitmap) ;

BitBlt (hdc, 0, 0, bitmap.bmWidth, bitmap.bmHeight,
hdcMem, 0, 0, SRCCOPY) ;

DeleteDC (hdcMem) ;
}

EndPaint (hwnd, &ps) ;
return 0 ;

case WM_DESTROY:
if (hBitmap)
DeleteObject (hBitmap) ;

PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}




DIBSECT.RC (excerpts)



//Microsoft Developer Studio generated resource script.

#include "resource.h"
#include "afxres.h"

/////////////////////////////////////////////////////////////////////////////

// Menu

DIBSECT MENU DISCARDABLE
BEGIN
POPUP "&File"
BEGIN
MENUITEM "&Open", IDM_FILE_OPEN
END
END





RESOURCE.H (excerpts)



// Microsoft Developer Studio generated include file.
// Used by DibSect.rc

#define IDM_FILE_OPEN 40001




Notice the differences between the
CreateBitmapObjectFromDibFile function in DIBCONV and the
CreateDibSectionFromDibFile function in DIBSECT. DIBCONV
reads the entire file in one shot and then passes pointers to the DIB memory block to the
CreateDIBitmap function. DIBSECT reads in the BITMAPFILEHEADER structure first and
then determines how big the BITMAPINFO structure is. Memory is allocated for that, and
it's read in on the second ReadFile call. The function then passes pointers to the
BITMAPINFO structure and to the pointer variable
pBits to CreateDIBSection. The function returns
a bitmap handle and sets pBits to point to a block of memory into which the function
then reads the DIB pixel bits.

The memory block pointed to by pBits is owned by the system. The memory is
automatically freed when you delete the bitmap by calling
DeleteObject. However, programs can use the pointer to alter the DIB bits directly. That the system owns this memory
block makes it not subject to the speed penalty incurred under Windows NT when an
application passes large memory blocks across the API.

As I noted above, when you display a DIB on a video display, at some point
it must undergo a conversion from device-independent pixels to
device-dependent pixels. Sometimes this format conversion can be lengthy. Let's look at the
three approaches we've used to display DIBs:


  • When you use SetDIBitsToDevice or
    StretchDIBits to display a DIB directly to the screen, the format conversion occurs during the
    SetDIBitsToDevice or StretchDIBits call.

  • When you convert a DIB to a DDB using
    CreateDIBitmap and (possibly)
    SetDIBits and then use BitBlt or
    StretchBlt to display it, the format conversion
    occurs during CreateDIBitmap, if the CBM_INIT flag is set, or
    SetDIBits.

  • When you create a DIB section using
    CreateDIBSection and then display it using
    BitBlt or StretchBlt, the format conversion occurs during the
    BitBlt to StretchBlt call.

Read that last sentence over again and make sure you didn't misread it. This is
one way in which the bitmap handle returned from
CreateDIBSection is different from the
other bitmap handles we've encountered. This bitmap handle actually references a DIB that
is stored in memory maintained by the system but to which an application has access.
This DIB is converted to a particular color format when necessary, which is usually when
it's displayed using BitBlt or
StretchBlt.

You can also select the bitmap handle into a memory device context and use
GDI functions to draw on it. The results will be reflected in the DIB pixel bits pointed to
by the pBits variable. Because of batching of GDI calls under Windows NT, call
GdiFlush after drawing on the memory device context before accessing the bits "manually."

In DIBSECT we discarded the pBits variable because it was no longer required
by the program. If you need to alter the bits directly, which is a major reason why you'll
use CreateDIBSection, hold onto it. There seems to be no way to later obtain the bits
pointer after the CreateDIBSection call.

More DIB Section Differences

The bitmap handle returned from
CreateDIBitmap has the same planes and
bits-per-pixel organization as the device referenced by the
hdc parameter to the function. You can
verify this by calling GetObject with the BITMAP structure.

CreateDIBSection is different. If you call
GetObject with the BITMAP structure on the bitmap handle returned from the function, you'll find that the bitmap has the same
color organization as indicated by the fields of the BITMAPINFOHEADER structure. Yet you
can select this handle into a memory device context compatible with the video display.
This contradicts what I said in the last chapter about DDBs, of course, but that's why I
contend that this DIB section bitmap handle is different.

Another oddity: As you'll recall, the byte length of the rows of pixel data in DIBs
is always a multiple of 4. The byte length of rows in GDI bitmap objects, which you can
get from the bmWidthBytes field of the BITMAP structure used with
GetObject, is always a multiple of 2. Well, if you set up the BITMAPINFOHEADER structure shown above
with 24 bits per pixel and a width of 2 pixels (for example) and later call
GetObject, you'll find that the
bmWidthBytes field is 8 rather than 6.

With the bitmap handle returned from
CreateDIBSection, you can also call
GetObject with a DIBSECTION structure, like so:


GetObject (hBitmap, sizeof (DIBSECTION), &dibsection) ;

This won't work with a bitmap handle returned from any of the other
bitmap-creation functions. The DIBSECTION structure is defined like so:


typedef struct tagDIBSECTION // ds
{
BITMAP dsBm ; // BITMAP structure
BITMAPINFOHEADER dsBmih ; // DIB information header
DWORD dsBitfields [3] ; // color masks
HANDLE dshSection ; // file-mapping object handle
DWORD dsOffset ; // offset to bitmap bits
}
DIBSECTION, * PDIBSECTION ;

This structure contains both a BITMAP structure and a BITMAPINFOHEADER structure.
The last two fields are the last two arguments passed to
CreateDIBSection, which I'll discuss shortly.

The DIBSECTION structure tells you much of what you need to know about
the bitmap, except for the color table. When you select the DIB section bitmap handle into
a memory device context, you can get the color table by calling
GetDIBColorTable:


hdcMem = CreateCompatibleDC (NULL) ;
SelectObject (hdcMem, hBitmap) ;
GetDIBColorTable (hdcMem, uFirstIndex, uNumEntries, &rgb) ;
DeleteDC (hdcMem) ;

Similary, you can set entries in the color table by calling SetDIBColorTable.

The File-Mapping Option

I haven't yet discussed the last two arguments to
CreateDIBSection, which are a handle to a file-mapping object and an offset within that file where the bitmap bits begin. A
file-mapping object allows you to treat a file as if it were located in memory. That is, you
can access the file by using memory pointers, but the file needn't be entirely located in memory.

In the case of large DIBs, this technique can help reduce memory requirements.
The DIB pixel bits can remain on disk but still be accessed as if they were in memory,
albeit with a performance penalty. The problem is, while the pixel bits can indeed remain
stored on disk, they can't be part of an actual DIB file. They'd have to be in some other file.

To demonstrate, the function shown below is very similar to the function that
creates the DIB section in DIBSECT except that it doesn't read the pixel bits into
memory; instead, it supplies a file-mapping object and an offset to the
CreateDIBSection function:


HBITMAP CreateDibSectionMappingFromFile (PTSTR szFileName)
{
BITMAPFILEHEADER bmfh ;
BITMAPINFO * pbmi ;
BYTE * pBits ;
BOOL bSuccess ;
DWORD dwInfoSize, dwBytesRead ;
HANDLE hFile, hFileMap ;
HBITMAP hBitmap ;

hFile = CreateFile (szFileName, GENERIC_READ | GENERIC_WRITE,
0, // No sharing!
NULL, OPEN_EXISTING, 0, NULL) ;

if (hFile == INVALID_HANDLE_VALUE)
return NULL ;

bSuccess = ReadFile (hFile, &bmfh, sizeof (BITMAPFILEHEADER),
&dwBytesRead, NULL) ;

if (!bSuccess || (dwBytesRead != sizeof (BITMAPFILEHEADER))
|| (bmfh.bfType != * (WORD *) "BM"))
{
CloseHandle (hFile) ;
return NULL ;
}
dwInfoSize = bmfh.bfOffBits - sizeof (BITMAPFILEHEADER) ;
pbmi = malloc (dwInfoSize) ;
bSuccess = ReadFile (hFile, pbmi, dwInfoSize, &dwBytesRead, NULL) ;

if (!bSuccess || (dwBytesRead != dwInfoSize))
{
free (pbmi) ;
CloseHandle (hFile) ;
return NULL ;
}
hFileMap = CreateFileMapping (hFile, NULL, PAGE_READWRITE, 0, 0, NULL) ;

hBitmap = CreateDIBSection (NULL, pbmi, DIB_RGB_COLORS, &pBits,
hFileMap, bmfh.bfOffBits) ;
free (pbmi) ;
return hBitmap ;
}

Alas, this does not work. The documentation of
CreateDIBSection indicates that
"dwOffset [the final argument to the function] must be a multiple of the size of a
DWORD." Although the size of the information header is always a multiple of 4 and the size of
the color table is always a multiple of 4, the bitmap file header is not. It's 14 bytes.
So bmfh.bfOffBits is never a multiple of 4.

In Summary

If you have small DIBs and you need to frequently manipulate the pixel bits, you
can display them using SetDIBitsToDevice and
StretchDIBits. However, for larger DIBs,
this technique will encounter performance problems, particularly on 8-bit video displays
and under Windows NT.

You can convert a DIB to a DDB by using
CreateDIBitmap and SetDIBits.
Displaying the bitmap will now involve the speedy
BitBlt and StretchBlt functions. However,
you no longer have access to the device-independent pixel bits.

CreateDIBSection is a good compromise. Using the bitmap handle with
BitBlt and StretchBlt gives you better performance under Windows NT than using
SetDIBitsToDevice and StretchDIBits but with none of the drawbacks of the DDB. You still have access to
the DIB pixel bits.

In the next chapter, we'll wrap up our exploration of bitmaps after looking at
the Windows Palette Manager.

No comments:

Post a Comment