http://www-user.tu-chemnitz.de/~heha/petzold/
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.
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 thehdc 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 inSetDIBitsToDevice. 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 ofSetDIBitsToDevice, 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 becauseSetDIBitsToDevice 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 theCreateDIBitmap 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.
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 usingGetDIBits, 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 functiondoes come in handy is when you're doing screen captures, such as what the BLOWUP program did inChapter 14. I won't be demonstrating this function, but some information is available in Knowledge Base article Q80080.
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. WhatCreateDIBSection 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 thatCreateDIBSection 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, thehdc 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 fromCreateDIBitmap 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 secondReadFile 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 setspBits 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:
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 thepBits 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 useCreateDIBSection, hold onto it. There seems to be no way to later obtain the bits pointer after theCreateDIBSection call.
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 thebmWidthBytes 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.
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. Sobmfh.bfOffBits is never a multiple of 4.
If you have small DIBs and you need to frequently manipulate the pixel bits, you can display them usingSetDIBitsToDevice 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.