http://www-user.tu-chemnitz.de/~heha/petzold/
I mentioned earlier in this chapter that Windows has supported a GDI bitmap object since version 1.0. Because of the introduction of the device-independent bitmap in Windows 3.0, the GDI Bitmap Object is sometimes now also known as the device-dependent bitmap, or DDB. I will tend not to use the full, spelled-out term device-dependent bitmap because at a quick glance the words can be confused with device-independent bitmap. The abbreviation DDB is better because it is more easily visually distinguished from DIB.
The existence of two different types of bitmaps has created much confusion for programmers first coming to Windows in the version 3.0 and later days. Many veteran Windows programmers also have problems understanding the precise relationship between the DIB and the DDB. (I'm afraid the Windows 3.0 version of this book did not help clarify this subject.) Yes, the DIB and DDB are related in some ways: DIBs can be converted to DDBs and vice versa (although with some loss of information). Yet the DIB and the DDB are not interchangeable and are not simply alternative methods for representing the same visual data.
It would certainly be convenient if we could assume that DIBs have made DDBs obsolete. Yet that is not the case. The DDB still plays a very important role in Windows, particularly if you care about performance.
The DDB is one of several graphics objects (including pens, brushes, fonts, metafiles, and palettes) defined in the Windows Graphics Device Interface. These graphics objects are stored internally in the GDI module and referred to by application programs with numerical handles. You store a handle to a DDB in a variable of type HBITMAP ("handle to a bitmap"). For example,
HBITMAP hBitmap ;
You then obtain the handle by calling one of the DDB-creation functions: for example,CreateBitmap. These functions allocate and initialize some memory in GDI memory to store information about the bitmap as well as the actual bitmap bits. The application program does not have direct access to this memory. The bitmap is independent of any device context. When the program is finished using the bitmap, it should be deleted:
DeleteObject (hBitmap) ;
You could do this when the program is terminating if you're using the DDB throughout the time the program is running.
The CreateBitmap function looks like this:
hBitmap = CreateBitmap (cx, cy, cPlanes, cBitsPixel, bits) ;
The first two arguments are the width and height of the bitmap in pixels. The third argument is the number of color planes and the fourth argument is the number of bits per pixel. The fifth argument points to an array of bits organized in accordance with the specified color format. You can set the last argument to NULL if you do not want to initialize the DDB with the pixel bits. The pixel bits can be set later.
When you use this function, Windows will let you create any bizarre type of GDI bitmap object you'd like. For example, suppose you want a bitmap with a width of 7 pixels, a height of 9 pixels, 5 color planes, and 3 bits per pixel. Just do it like so,
hBitmap = CreateBitmap (7, 9, 5, 3, NULL) ;
and Windows will gladly give you a valid bitmap handle.
What happens during this function call is that Windows saves the information you've passed to the function and allocates memory for the pixel bits. A rough calculation indicates that this bitmap requires 7 times 9 times 5 times 3, or 945 bits, which is 118 bytes and change.
However, when Windows allocates memory for the bitmap, each row of pixels has an even number of bytes. Thus,
iWidthBytes = 2 * ((cx * cBitsPixel + 15) / 16) ;
or, as a C programmer might tend to write it,
iWidthBytes = (cx * cBitsPixel + 15) & ~15) >> 3 ;
The memory allocated for the DDB is therefore
iBitmapBytes = cy * cPlanes * iWidthBytes ;
In our example, iWidthBytes is 4 bytes, and iBitmapBytes is 180 bytes.
Now, what does it mean to have a bitmap with 5 color planes and 3 color bits per pixel? Not a whole heck of a lot. It doesn't even mean enough to call it an academic exercise. You have caused GDI to allocate some internal memory, and this memory has a specific organization, but it doesn't mean anything, and you can't do anything useful with this bitmap.
In reality, you will call CreateBitmap with only two types of arguments:
cPlanes and cBitsPixel both equal to 1 (indicating a monochrome bitmap); or cPlanes and cBitsPixel equal to the values for a particular device context, which you can obtain from the GetDeviceCaps function by using the PLANES and BITSPIXEL indices.In a much "realer" reality, you will call CreateBitmap only for the first case. For the second case, you can simplify things using CreateCompatibleBitmap:
hBitmap = CreateCompatibleBitmap (hdc, cx, cy) ;
This function creates a bitmap compatible with the device whose device context handle is given by the first parameter. CreateCompatibleBitmap uses the device context handle to obtain the GetDeviceCaps information that it then passes to CreateBitmap. Aside from having the same memory organization as a real device context, the DDB is not otherwise associated with the device context.
The CreateDiscardableBitmap function has the same parameters as CreateCompatibleBitmap and is functionally equivalent to it. In earlier versions of Windows, CreateDiscardableBitmap created a bitmap that Windows could discard from memory if memory got low. The program would then have to regenerate the bitmap data.
The third bitmap-creation function is CreateBitmapIndirect,
hBitmap CreateBitmapIndirect (&bitmap) ;
where bitmap is a structure of type BITMAP. The BITMAP structure is defined like so:
typedef struct _tagBITMAP { LONG bmType ; // set to 0 LONG bmWidth ; // width in pixels LONG bmHeight ; // height in pixels LONG bmWidthBytes ; // width of row in bytes WORD bmPlanes ; // number of color planes WORD bmBitsPixel ; // number of bits per pixel LPVOID bmBits ; // pointer to pixel bits } BITMAP, * PBITMAP ;
When calling the CreateBitmapIndirect function, you don't need to set the bmWidthBytes field. Windows will calculate that for you. You can also set the bmBits field to NULL or to the address of pixel bits to initialize the bitmap.
The BITMAP structure is also used in the GetObject function. First define a structure of type BITMAP,
BITMAP bitmap ;
and call the function like so:
GetObject (hBitmap, sizeof (BITMAP), &bitmap) ;
Windows will fill in the fields of the BITMAP structure with information about the bitmap. However, thebmBits field will be equal to NULL.
You should eventually destroy any bitmap that you create in a program with a call toDeleteObject.
When you create a device-dependent GDI bitmap object by using CreateBitmap orCreateBitmapIndirect, you can specify a pointer to the bitmap pixel bits. Or you can leave the bitmap uninitialized. Windows also supplies two functions to get and set the pixel bits after the bitmap has been created.
To set the pixel bits, call
SetBitmapBits (hBitmap, cBytes, &bits) ;
The GetBitmapBits function has the same syntax:
GetBitmapBits (hBitmap, cBytes, &bits) ;
In both functions, cBytes indicates the number of bytes to copy and bits is a buffer of at least cBytes size.
The pixel bits in DDBs are arranged beginning with the top row. As I mentioned earlier, each row has an even number of bytes. Beyond that, there's not too much to say. If the bitmap is monochrome, which means it has 1 plane and 1 bit per pixel, then each pixel is either 1 or 0. The leftmost pixel in each row is the most significant bit of the first byte in the row. We'll make a monochrome DDB later in this chapter after we figure out how to display them.
For nonmonochrome bitmaps, you should avoid situations where you need to know what the pixel bits mean. For example, suppose Windows is running on an 8-bit VGA. You callCreateCompatibleBitmap. You can determine from GetDeviceCaps that you're dealing with a device that has 1 color plane and 8 bits per pixel. Each pixel is stored in 1 byte. But what does a pixel of value 0x37 mean? It obviously refers to some color, but what color?
The pixel actually doesn't refer to any fixed specific color. It's just a value. DDBs do not have a color table. The essential question is: what color is the pixel when the DDB gets displayed on the screen? It has to be some color, so what is it? The displayed pixel will be the RGB color referenced by an index value of 0x37 in the palette lookup table on the video board. Now that's device dependence for you.
However, do not assume that the nonmonochrome DDB is useless just because we don't know what the pixel values mean. We'll see shortly how useful they can be. And in the next chapter we'll see how the SetBitmapBits and GetBitmapBits functions have been superseded by the more useful SetDIBits and GetDIBits functions.
So, the basic rule is this: you will not be using CreateBitmap or CreateBitmapIndirect or SetBitmapBits to set the bits of a color DDB. You can safely set the bits of only a monochrome DDB. (The exception to this rule is if you get the bits from another DDB of the same format through a call to GetBitmapBits.)
Before we move on, let me just mention the SetBitmapDimensionEx and GetBitmapDimensionEx functions. These functions let you set (and obtain) a metrical dimension of a bitmap in 0.1 millimeter units. This information is stored in GDI along with the bitmap definition, but it's not used for anything. It's just a tag that you can use to associate a metrical dimension with a DDB.
The next concept we must tackle is that of the memory device context. You need a memory device context to use a GDI bitmap object.
Normally, a device context refers to a particular graphics output device (such as a video display or a printer) together with its device driver. A memory device context exists only in memory. It is not a real graphics output device, but is said to be "compatible" with a particular real device.
To create a memory device context, you must first have a device context handle for a real device. If it'shdc, you create a memory device context like so:
hdcMem = CreateCompatibleDC (hdc) ;
Usually the function call is even simpler than this. If you specify NULL as the argument, Windows will create a memory device context compatible with the video display. Any memory device context that an application creates should eventually be destroyed with a call to DeleteDC.
The memory device context has a display surface just like a real raster device. However, this display surface is initially very small—it's monochrome, 1 pixel wide and 1 pixel high. The display surface is just a single bit.
You can't do much with a 1-bit display surface, of course, so the only practical next step is to make the display surface larger. You do this by selecting a GDI bitmap object into the memory device context, like so:
SelectObject (hdcMem, hBitmap) ;
This is the same function you use for selecting pens, brushes, fonts, regions, and palettes into device contexts. However, the memory device context is the only type of device context into which you can select a bitmap. (You can also select other GDI objects into a memory device context if you need to.)
SelectObject will work only if the bitmap you select into the memory device context is either monochrome or has the same color organization as the device with which the memory device context is compatible. That's why creating a bizarre DDB (for example, with 5 planes and 3 bits per pixel) is not useful.
Now get this: Following the SelectObject call, the DDB is the display surface of the memory device context. You can do almost anything with this memory device context that you can do with a real device context. For example, if you use GDI drawing functions to draw on the memory device context, the images are drawn on the bitmap. This can be very useful. You can also callBitBlt using the memory device context as a source and the video device context as a destination. This is how you can draw a bitmap on the display. And you can callBitBlt using the video device context as a source and a memory device context as a destination to copy something from the screen to a bitmap. We'll be looking at all these possibilities.
Besides the various bitmap creation functions, another way to get a handle to a GDI bitmap object is through theLoadBitmap function. With this function, you don't have to worry about bitmap formats. You simply create a bitmap as a resource in your program, similar to the way you create icons or mouse cursors. The LoadBitmap function has the same syntax as LoadIcon and LoadCursor:
hBitmap = LoadBitmap (hInstance, szBitmapName) ;
The first argument can be NULL if you want to load a system bitmap. These are the various bitmaps used for little parts of the Windows visual interface such as the close box and check marks, with identifiers beginning with the letters OBM. The second argument can use the MAKEINTRESOURCE macro if the bitmap is associated with an integer identifier rather than a name. All bitmaps loaded by LoadBitmap should eventually be deleted using DeleteObject.
If the bitmap resource is monochrome, the handle returned from LoadBitmap will reference a monochrome bitmap object. If the bitmap resource is not monochrome, then the handle returned fromLoadBitmap will reference a GDI bitmap object with a color organization the same as the video display on which the program is running. Thus, the bitmap is always compatible with the video display and can always be selected into a memory device context compatible with the video display. Don't worry right now about any color conversions that may have gone on behind the scenes during the LoadBitmap call. We'll understand how this works after the next chapter.
The BRICKS1 program shown in Figure 14-5 shows how to load a small monochrome bitmap resource. This bitmap doesn't exactly look like a brick by itself but when repeated horizontally and vertically resembles a wall of bricks.
Figure 14-5. The BRICKS1 program.
BRICKS1.C/*---------------------------------------- BRICKS1.C -- LoadBitmap Demonstration (c) Charles Petzold, 1998 ----------------------------------------*/ #include |
BRICKS1.RC (excerpts)//Microsoft Developer Studio generated resource script. #include "resource.h" #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// // Bitmap BRICKS BITMAP DISCARDABLE "Bricks.bmp" |
When creating the bitmap in Visual C++ Developer Studio, specify that the bitmap's height and width are 8 pixels, that it's monochrome, and that it has a name of "Bricks". The BRICKS1 program loads the bitmap during the WM_CREATE message and uses GetObject to determine its pixel dimensions (so that the program will still work if the bitmap isn't 8 pixels square). BRICKS1 later deletes the bitmap handle during the WM_DESTROY message.
During the WM_PAINT message, BRICKS1 creates a memory device context compatible with the display and selects the bitmap into it. Then it's just a series of BitBlt calls from the memory device context to the client area device context. The memory device context handle is then deleted. The program is shown running in Figure 14-6.
By the way, the BRICKS.BMP file that Developer Studio creates is a device-independent bitmap. You may want to try creating a color BRICKS.BMP file in Developer Studio (of whatever color format you choose) and assure yourself that everything works just fine.
We've seen that DIBs can be converted to GDI bitmap objects that are compatible with the video display. We'll see how this works in the next chapter.
Figure 14-6. The BRICKS1 display.
If you're working with small monochrome images, you don't have to create them as resources. Unlike color bitmap objects, the format of monochrome bits is relatively simple and can almost be derived directly from the image you want to create. For instance, suppose you want to create a bitmap that looks like this:
You can write down a series of bits (0 for black and 1 for white) that directly corresponds to this grid. Reading these bits from left to right, you can then assign each group of 8 bits a hexadecimal byte. If the width of the bitmap is not a multiple of 16, pad the bytes to the right with zeros to get an even number of bytes:
0 1 0 1 0 0 0 1 0 1 1 1 0 1 1 1 0 0 0 1 = 51 77 10 00
0 1 0 1 0 1 1 1 0 1 1 1 0 1 1 1 0 1 0 1 = 57 77 50 00
0 0 0 1 0 0 1 1 0 1 1 1 0 1 1 1 0 1 0 1 = 13 77 50 00
0 1 0 1 0 1 1 1 0 1 1 1 0 1 1 1 0 1 0 1 = 57 77 50 00
0 1 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 = 51 11 10 00
The width in pixels is 20, the height in scan lines is 5, and the width in bytes is 4. You can set up a BITMAP structure for this bitmap with this statement,
static BITMAP bitmap = { 0, 20, 5, 4, 1, 1 } ;
and you can store the bits in a BYTE array:
static BYTE bits [] = { 0x51, 0x77, 0x10, 0x00, 0x57, 0x77, 0x50, 0x00, 0x13, 0x77, 0x50, 0x00, 0x57, 0x77, 0x50, 0x00, 0x51, 0x11, 0x10, 0x00 } ;
Creating the bitmap with CreateBitmapIndirect requires two statements:
bitmap.bmBits = (PSTR) bits ; hBitmap = CreateBitmapIndirect (&bitmap) ;
Another approach is
hBitmap = CreateBitmapIndirect (&bitmap) ; SetBitmapBits (hBitmap, sizeof bits, bits) ;
You can also create the bitmap in one statement:
hBitmap = CreateBitmap (20, 5, 1, 1, bits) ;
The BRICKS2 program shown in Figure 14-7 uses this technique to create the bricks bitmap directly without requiring a resource.
Figure 14-7. The BRICKS2 program.
BRICKS2.C/*----------------------------------------- BRICKS2.C -- CreateBitmap Demonstration (c) Charles Petzold, 1998 -----------------------------------------*/ #include |
You may be tempted to try something similar with a color bitmap. For example, if your video display is running in a 256-color mode, you can use the table shown earlier in this chapter to define each pixel for a color brick. However, this code will not work when the program runs under any other video mode. Dealing with color bitmaps in a device-independent manner requires use of the DIB discussed in the next chapter.
The final entry in the BRICKS series is BRICKS3, shown in Figure 14-8. At first glance this program might provoke the reaction "Where's the code?"
Figure 14-8. The BRICKS3 program.
BRICKS3.C/*----------------------------------------------- BRICKS3.C -- CreatePatternBrush Demonstration (c) Charles Petzold, 1998 -----------------------------------------------*/ #include |
BRICKS3.RC (excerpts)//Microsoft Developer Studio generated resource script. #include "resource.h" #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// // Bitmap BRICKS BITMAP DISCARDABLE "Bricks.bmp" |
This program uses the same BRICKS.BMP file as BRICKS1, and the window looks the same.
As you can see, the window procedure doesn't do much of anything. BRICKS3 actually uses the bricks pattern as the window class background brush, which is defined in thehbrBackground field of the WNDCLASS structure.
As you may have guessed by now, GDI brushes are tiny bitmaps, usually 8 pixels square. You can make a brush out of a bitmap by calling CreatePatternBrush or by calling CreateBrushIndirect with the lbStyle field of the LOGBRUSH structure set to BS_PATTERN. The bitmap must be least 8 pixels wide and 8 pixels high. If it's larger, Windows 98 uses only the upper left corner of the bitmap for the brush. Windows NT, on the other hand, doesn't have that restriction and will use the whole bitmap.
Remember that brushes and bitmaps are both GDI objects and you should delete any that you create in your program before the program terminates. When you create a brush based on a bitmap, Windows makes a copy of the bitmap bits for use when drawing with the brush. You can delete the bitmap immediately after calling CreatePatternBrush (or CreateBrushIndirect) without affecting the brush. Similarly, you can delete the brush without affecting the original bitmap you selected into it. Notice that BRICKS3 deletes the bitmap after creating the brush and deletes the brush before terminating the program.
We've been using bitmaps as a source for drawing on our windows. This requires selecting the bitmap into a memory device context and calling BitBlt or StretchBlt. You can also use the handle to the memory device context as the first argument to virtually all the GDI function calls. The memory device context behaves the same as a real device context except that the display surface is the bitmap.
The HELLOBIT program shown in Figure 14-9 shows this technique. The program displays the text string "Hello, world!" on a small bitmap and then does a BitBlt or a StretchBlt (based on a menu selection) from the bitmap to the program's client area.
Figure 14-9. The HELLOBIT program.
HELLOBIT.C/*----------------------------------------- HELLOBIT.C -- Bitmap Demonstration (c) Charles Petzold, 1998 -----------------------------------------*/ #include |
HELLOBIT.RC (excerpts)//Microsoft Developer Studio generated resource script. #include "resource.h" #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// // Menu HELLOBIT MENU DISCARDABLE BEGIN POPUP "&Size" BEGIN MENUITEM "&Big", IDM_BIG, CHECKED MENUITEM "&Small", IDM_SMALL END END |
RESOURCE.H (excerpts)// Microsoft Developer Studio generated include file. // Used by HelloBit.rc #define IDM_BIG 40001 #define IDM_SMALL 40002 |
The program begins by determining the pixel dimensions of the text string through a call toGetTextExtentPoint32. These dimensions become the size of a bitmap compatible with the video display. Once this bitmap is selected into a memory device context (also compatible with the video display), a call to TextOut puts the text on the bitmap. The memory device context is retained throughout the duration of the program. While processing the WM_DESTROY message, HELLOBIT deletes both the bitmap and the memory device context.
A menu selection in HELLOBIT allows you to display the bitmap at actual size repeated horizontally and vertically in the client area or stretched to the size of the client area as shown in Figure 14-10. As you can see, this is not a good way to display text of large point sizes! It's just a magnified version of the smaller font, with all the jaggies magnified as well.
Figure 14-10. The HELLOBIT display.
You may wonder if a program such as HELLOBIT needs to process the WM_DISPLAYCHANGE message. An application receives this message whenever the user (or another application) changes the video display size or color depth. It could be that a change to the color depth would cause the memory device context and the video device context to become incompatible. Well, that doesn't happen because Windows automatically changes the color resolution of the memory device context when the video mode is changed. The bitmap selected into the memory device context remains the same, but that doesn't seem to cause any problems.
The technique of drawing on a memory device context (and hence a bitmap) is the key to implementing a "shadow bitmap." This is a bitmap that contains everything displayed in the window's client area. WM_PAINT message processing thus reduces to a simple BitBlt.
Shadow bitmaps are most useful in paint programs. The SKETCH program shown in Figure 14-11 is not exactly the most sophisticated paint program around, but it's a start.
Figure 14-11. The SKETCH program.
SKETCH.C/*----------------------------------------- SKETCH.C -- Shadow Bitmap Demonstration (c) Charles Petzold, 1998 -----------------------------------------*/ #include |
To draw lines in SKETCH, you press the left mouse button and move the mouse. To erase (or more precisely, to draw white lines), you press the right mouse button and move the mouse. To clear the entire window, you…well, you have to end the program, load it again, and start all over. Figure 14-12 shows the SKETCH program paying homage to the early advertisements for the Apple Macintosh.
Figure 14-12. The SKETCH display.
How large should the shadow bitmap be? In this program, it should be large enough to encompass the entire client area of a maximized window. This is easy enough to calculate fromGetSystemMetrics information, but what happens if the user changes the display settings and makes the display, and hence the maximum window size, larger? SKETCH implements a brute force solution to this problem with the help of the EnumDisplaySettings function. This function uses a DEVMODE structure to return information on all the available video display modes. Set the second argument to EnumDisplaySettings to 0 the first time you call the function, and increase the value for each subsequent call. When EnumDisplaySettings returns FALSE, you're finished.
With that information, SKETCH will create a shadow bitmap that can have more than four times the surface area of the current video display mode and require multiple megabytes of memory. For this reason, SKETCH checks to see if the bitmap has been created and returns -1 from WM_CREATE to indicate an error if it has not.
SKETCH captures the mouse when the left or right mouse button is pressed and draws lines on both the memory device context and the device context for the client area during the WM_MOUSEMOVE message. If the drawing logic were any more complex, you'd probably want to implement it in a function that the program calls twice—once for the video device context and again for the memory device context.
Here's an interesting experiment: Make the SKETCH window less than the size of the full screen. With the left mouse button depressed, draw something and let the mouse pass outside the window at the right and bottom. Because SKETCH captures the mouse, it continues to receive and process WM_MOUSEMOVE messages. Now expand the window. You'll discover that the shadow bitmap includes the drawing you did outside SKETCH's window!
You can also use bitmaps to display items in menus. If you immediately recoiled at the thought of pictures of file folders, paste jars, and trash cans in a menu, don't think of pictures. Think instead of how useful menu bitmaps might be for a drawing program. Think of using different fonts and font sizes, line widths, hatch patterns, and colors in your menus.
The sample program that demonstrates graphical menu items is called GRAFMENU. The top-level menu of this program is shown in Figure 14-13 below. The enlarged block letters are obtained from 40-by-16-pixel monochrome bitmap files created in Visual C++ Developer Studio. Choosing FONT from the menu invokes a popup containing three options—Courier New, Arial, and Times New Roman. These are the standard Windows TrueType fonts, and each is displayed in its respective font, as you can see in Figure 14-14 below. These bitmaps were created in the program using a memory device context.
Figure 14-13. The GRAFMENU program's top-level menu.
Figure 14-14. The GRAFMENU program's popup FONT menu.
Finally, when you pull down the system menu, you see that you have access to some "help" information, with the word "Help" perhaps mirroring the desperation of a new user. (See Figure 14-15.) This 64-by-64-pixel monochrome bitmap was created in Developer Studio.
Figure 14-15. The GRAFMENU program's system menu.
The GRAFMENU program, including the four bitmaps created in Developer Studio, is shown in Figure 14-16.
Figure 14-16. The GRAFMENU program.
GRAFMENU.C/*---------------------------------------------- GRAFMENU.C -- Demonstrates Bitmap Menu Items (c) Charles Petzold, 1998 ----------------------------------------------*/ #include |
GRAFMENU.RC (excerpts)//Microsoft Developer Studio generated resource script. #include "resource.h" #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// // Menu MENUFILE MENU DISCARDABLE BEGIN MENUITEM "&New", IDM_FILE_NEW MENUITEM "&Open...", IDM_FILE_OPEN MENUITEM "&Save", IDM_FILE_SAVE MENUITEM "Save &As...", IDM_FILE_SAVE_AS END MENUEDIT MENU DISCARDABLE BEGIN MENUITEM "&Undo", IDM_EDIT_UNDO MENUITEM SEPARATOR MENUITEM "Cu&t", IDM_EDIT_CUT MENUITEM "&Copy", IDM_EDIT_COPY MENUITEM "&Paste", IDM_EDIT_PASTE MENUITEM "De&lete", IDM_EDIT_CLEAR END ///////////////////////////////////////////////////////////////////////////// // Bitmap BITMAPFONT BITMAP DISCARDABLE "Fontlabl.bmp" BITMAPHELP BITMAP DISCARDABLE "Bighelp.bmp" BITMAPEDIT BITMAP DISCARDABLE "Editlabl.bmp" BITMAPFILE BITMAP DISCARDABLE "Filelabl.bmp" |
RESOURCE.H (excerpts// Microsoft Developer Studio generated include file. // Used by GrafMenu.rc #define IDM_FONT_COUR 101 #define IDM_FONT_ARIAL 102 #define IDM_FONT_TIMES 103 #define IDM_HELP 104 #define IDM_EDIT_UNDO 40005 #define IDM_EDIT_CUT 40006 #define IDM_EDIT_COPY 40007 #define IDM_EDIT_PASTE 40008 #define IDM_EDIT_CLEAR 40009 #define IDM_FILE_NEW 40010 #define IDM_FILE_OPEN 40011 #define IDM_FILE_SAVE 40012 #define IDM_FILE_SAVE_AS 40013 |
EDITLABL.BMP
FILELABL.BMP
FONTLABL.BMP
BIGHELP.BMP
To insert a bitmap into a menu, you use AppendMenu or InsertMenu. The bitmap can come from one of two places. You can create a bitmap in Visual C++ Developer Studio, include the bitmap file in your resource script, and within the program use LoadBitmap to load the bitmap resource into memory. You then call AppendMenu or InsertMenu to attach it to the menu. There's a problem with this approach, however. The bitmap might not be suitable for all types of video resolutions and aspect ratios; you probably want to stretch the loaded bitmap to account for this. Alternatively, you can create the bitmap right in the program, select it into a memory device context, draw on it, and then attach it to the menu.
The GetBitmapFont function in GRAFMENU takes a parameter of 0, 1, or 2 and returns a handle to a bitmap. This bitmap contains the string "Courier New," "Arial," or "Times New Roman" in the appropriate font and about twice the size of the normal system font. Let's see how GetBitmapFont does it. (The code that follows is not the same as that in the GRAFMENU.C file. For purposes of clarity, I've replaced references to the szFaceName array with the values appropriate for the Arial font.)
The first steps are to determine the size of the system font by using the TEXTMETRIC structure and to create a memory device context compatible with the screen:
hdc = CreateIC (TEXT ("DISPLAY"), NULL, NULL, NULL) ; GetTextMetrics (hdc, &tm) ; hdcMem = CreateCompatibleDC (hdc) ;
The CreateFont function creates a logical font that is twice the height of the system font and has a facename of "Arial":
hFont = CreateFont (2 * tm.tmHeight, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, TEXT ("Arial")) ;
This font is selected in the memory device context and the default font handle is saved:
hFont = (HFONT) SelectObject (hdcMem, hFont) ;
Now when we write some text to the memory device context, Windows will use the TrueType Arial font selected into the device context.
But this memory device context initially has a one-pixel monochrome device surface. We have to create a bitmap large enough for the text we want to display on it. You can obtain the dimensions of the text through GetTextExtentPoint32 and create a bitmap based on these dimensions withCreateBitmap:
GetTextExtentPoint32 (hdcMem, TEXT ("Arial"), 5, &size) ; hBitmap = CreateBitmap (size.cx, size.cy, 1, 1, NULL) ; SelectObject (hdcMem, hBitmap) ;
This device context now has a monochrome display surface exactly the size of the text. Now all we have to do is write the text to it:
TextOut (hdcMem, 0, 0, TEXT ("Arial"), 5) ;
We're finished, except for cleaning up. To do so, we select the system font (with handlehFont) back into the device context by using SelectObject, and we delete the previous font handle that SelectObject returns, which is the handle to the Arial font:
DeleteObject (SelectObject (hdcMem, hFont)) ;
Now we can also delete the two device contexts:
DeleteDC (hdcMem) ; DeleteDC (hdc) ;
We're left with a bitmap that has the text "Arial" in an Arial font.
The memory device context also comes to the rescue when we need to scale fonts to a different display resolution or aspect ratio. I created the four bitmaps used in GRAFMENU to be the correct size for a display that has a system font height of 8 pixels and width of 4 pixels. For other system font dimensions, the bitmap has to be stretched. This is done in GRAFMENU'sStretchBitmap function.
The first step is to get the device context for the screen, obtain the text metrics for the system font, and create two memory device contexts:
hdc = CreateIC (TEXT ("DISPLAY"), NULL, NULL, NULL) ; GetTextMetrics (hdc, &tm) ; hdcMem1 = CreateCompatibleDC (hdc) ; hdcMem2 = CreateCompatibleDC (hdc) ; DeleteDC (hdc) ;
The bitmap handle passed to the function is hBitmap1. The program can obtain the dimensions of this bitmap using GetObject:
GetObject (hBitmap1, sizeof (BITMAP), (PSTR) &bm1) ;
This copies the dimensions into a structure bm1 of type BITMAP. The structure bm2 is set equal to bm1, and then certain fields are modified based on the system font dimensions:
bm2 = bm1 ; bm2.bmWidth = (tm.tmAveCharWidth * bm2.bmWidth) / 4 ; bm2.bmHeight = (tm.tmHeight * bm2.bmHeight) / 8 ; bm2.bmWidthBytes = ((bm2.bmWidth + 15) / 16) * 2 ;
Next a new bitmap with handle hBitmap2 can be created that is based on the altered dimensions:
hBitmap2 = CreateBitmapIndirect (&bm2) ;
You can then select these two bitmaps into the two memory device contexts:
SelectObject (hdcMem1, hBitmap1) ; SelectObject (hdcMem2, hBitmap2) ;
We want to copy the first bitmap to the second bitmap and stretch it in the process. This involves theStretchBlt call:
StretchBlt (hdcMem2, 0, 0, bm2.bmWidth, bm2.bmHeight, hdcMem1, 0, 0, bm1.bmWidth, bm1.bmHeight, SRCCOPY) ;
Now the second bitmap has the properly scaled bitmap. We'll use that one in the menu. As you can see below, cleanup is simple.
DeleteDC (hdcMem1) ; DeleteDC (hdcMem2) ; DeleteObject (hBitmap1) ;
The CreateMyMenu function in GRAFMENU uses the StretchBitmap andGetBitmapFont functions when constructing the menu. GRAFMENU has two menus already defined in the resource script. These will become popups for the File and Edit options. The function begins by obtaining a handle to an empty menu:
hMenu = CreateMenu () ;
The popup menu for File (containing the four options New, Open, Save, and Save As) is loaded from the resource script:
hMenuPopup = LoadMenu (hInstance, TEXT ("MenuFile")) ;
The bitmap containing the word "FILE" is also loaded from the resource script and stretched usingStretchBitmap:
hBitmapFile = StretchBitmap (LoadBitmap (hInstance, TEXT ("BitmapFile"))) ;
The bitmap handle and popup menu handle become arguments to the AppendMenu call:
AppendMenu (hMenu, MF_BITMAP ¦ MF_POPUP, hMenuPopup, (PTSTR) (LONG) hBitmapFile) ;
The same procedure is followed for the Edit menu:
hMenuPopup = LoadMenu (hInstance, TEXT ("MenuEdit")) ; hBitmapEdit = StretchBitmap (LoadBitmap (hInstance, TEXT ("BitmapEdit"))) ; AppendMenu (hMenu, MF_BITMAP ¦ MF_POPUP, hMenuPopup, (PTSTR) (LONG) hBitmapEdit) ;
The popup menu for the three fonts is constructed from calls to the GetBitmapFont function:
hMenuPopup = CreateMenu () ; for (i = 0 ; i < 3 ; i++) { hBitmapPopFont [i] = GetBitmapFont (i) ; AppendMenu (hMenuPopup, MF_BITMAP, IDM_FONT_COUR + i, (PTSTR) (LONG) hMenuPopupFont [i]) ; }
The popup is then added to the menu:
hBitmapFont = StretchBitmap (LoadBitmap (hInstance, "BitmapFont")) ; AppendMenu (hMenu, MF_BITMAP ¦ MF_POPUP, hMenuPopup, (PTSTR) (LONG) hBitmapFont) ;
The window menu is now complete, and WndProc makes it the window's menu by a call toSetMenu.
GRAFMENU also alters the system menu in the AddHelpToSys function. The function first obtains a handle to the system menu:
hMenu = GetSystemMenu (hwnd, FALSE) ;
This loads the "Help" bitmap and stretches it to an appropriate size:
hBitmapHelp = StretchBitmap (LoadBitmap (hInstance, TEXT ("BitmapHelp"))) ;
This adds a separator bar and the stretched bitmap to the system menu:
AppendMenu (hMenu, MF_SEPARATOR, 0, NULL) ; AppendMenu (hMenu, MF_BITMAP, IDM_HELP, (PTSTR)(LONG) hBitmapHelp) ;
GRAFMENU devotes a whole function to cleaning up and deleting all the bitmaps before the program terminates.
A couple of miscellaneous notes on using bitmap in menus now follow.
In a top-level menu, Windows adjusts the menu bar height to accommodate the tallest bitmap. Other bitmaps (or character strings) are aligned at the top of the menu bar. The size of the menu bar obtained from GetSystemMetrics with the SM_CYMENU constant is no longer valid after you put bitmaps in a top-level menu.
As you can see from playing with GRAFMENU, you can use check marks with bitmapped menu items in popups, but the check mark is of normal size. If that bothers you, you can create a customized check mark and use SetMenuItemBitmaps.
Another approach to using nontext (or text in a font other than the system font) on a menu is the "owner-draw" menu.
The keyboard interface to menus is another problem. When the menu contains text, Windows automatically adds a keyboard interface. You can select a menu item using the Alt key in combination with a letter of the character string. But once you put a bitmap in a menu, you've eliminated that keyboard interface. Even if the bitmap says something, Windows doesn't know about it.
This is where the WM_MENUCHAR message comes in handy. Windows sends a WM_MENUCHAR message to your window procedure when you press Alt with a character key that does not correspond to a menu item. GRAFMENU would need to intercept WM_MENUCHAR messages and check the value of wParam (the ASCII character of the pressed key). If this corresponds to a menu item, it would have to return a double word to Windows, where the high word is set to 2 and the low word is set to the index of the menu item we want associated with that key. Windows does the rest.
Bitmaps are always rectangular, but they needn't be displayed like that. For example, suppose you have a rectangular bitmap image that you want to be displayed as an ellipse.
At first, this sounds simple. You just load the image into Visual C++ Developer Studio or the Windows Paint program (or a more expensive application) and you start drawing around the outside of the image with a white pen. You're left with an elliptical image with everything outside the ellipse painted white. This will work—but only when you display the bitmap on a white background. If you display it on any other color background, you'll have an elliptical image on top of a white rectangle on top of a colored background. That's no good.
There's a very common technique to solve problems like this. The technique involves a "mask" bitmap and some raster operations. A mask is a monochrome bitmap of the same dimensions as the rectangular bitmap image you want to display. Each mask pixel corresponds with a pixel of the bitmap image. The mask pixels are 1 (white) wherever the original bitmap pixel is to be displayed, and 0 (black) wherever you want to preserve the destination background. (Or the mask bitmap can be opposite this, with some corresponding changes to the raster operations you use.)
Let's see how this works in real life in the BITMASK program shown in Figure 14-17.
Figure 14-17. The BITMASK program.
BITMASK.C/*------------------------------------------- BITMASK.C -- Bitmap Masking Demonstration (c) Charles Petzold, 1998 -------------------------------------------*/ #include |
BITMASK.RC// Microsoft Developer Studio generated resource script. #include "resource.h" #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// // Bitmap MATTHEW BITMAP DISCARDABLE "matthew.bmp" |
The MATTHEW.BMP file referred to in the resource script is a digitized black-and-white photograph of a nephew of mine. It's 200 pixels wide, 320 pixels high, and has 8 bits per pixel. However, BITMASK is written so that this file can be just about anything.
Notice that BITMASK colors its window background with a light gray brush. This is to assure ourselves that we're properly masking the bitmap and not just coloring part of it white.
Let's look at WM_CREATE processing. BITMASK uses the LoadBitmap function to obtain a handle to the original image in the variable hBitmapImag. The GetObject function obtains the bitmap width and height. The bitmap handle is then selected in a memory device context whose handle is hdcMemImag.
Next the program creates a monochrome bitmap the same size as the original image. The handle is stored inhBitmapMask and selected into a memory device context whose handle is hdcMemMask. The mask bitmap is colored with a black background and a white ellipse by using GDI functions on the memory device context:
SelectObject (hdcMemMask, GetStockObject (BLACK_BRUSH)) ; Rectangle (hdcMemMask, 0, 0, cxBitmap, cyBitmap) ; SelectObject (hdcMemMask, GetStockObject (WHITE_BRUSH)) ; Ellipse (hdcMemMask, 0, 0, cxBitmap, cyBitmap) ;
Because this is a monochrome bitmap, the black area is really 0 bits and the white area is really 1 bits.
Then a BitBlt call alters the original image by using this mask:
BitBlt (hdcMemImag, 0, 0, cxBitmap, cyBitmap, hdcMemMask, 0, 0, SRCAND) ;
The SRCAND raster operation performs a bitwise AND operation between the bits of the source (the mask bitmap) and the bits of the destination (the original image). Wherever the mask bitmap is white, the destination is preserved. Wherever the mask bitmap is black, the destination becomes black as well. An elliptical area in the original image is now surrounded by black.
Now let's look at WM_PAINT processing. Both the altered image bitmap and the mask bitmap are selected into memory device contexts. Two BitBlt calls perform the magic. The first does a BitBlt of the mask bitmap on the window:
BitBlt (hdc, x, y, cxBitmap, cyBitmap, hdcMemMask, 0, 0, 0x220326) ;
This uses a raster operation for which there is no name. The logical operation is D & ~S. Recall that the source—the mask bitmap—is a white ellipse (1 bits) surrounded by black (0 bits). The raster operation inverts the source so that it's a black ellipse surrounded by white. The raster operation then performs a bitwise AND of this inverted source with the destination—the surface of the window. When the destination is ANDed with 1 bits, it remains unchanged. When ANDed with 0 bits, the destination becomes black. Thus, this BitBlt operation draws a black ellipse in the window.
The second BitBlt call draws the image bitmap on the window:
BitBlt (hdc, x, y, cxBitmap, cyBitmap, hdcMemImag, 0, 0, SRCPAINT) ;
The raster operation performs a bitwise OR operation between the source and the destination. The outside of the source bitmap is black, so it leaves the destination unchanged. Within the ellipse, the destination is black, so the image is copied unchanged. The result is shown in Figure 14-18.
A few notes:
You may need a mask that is quite complex—for example, one that blots out the whole background of the original image. You'll probably need to create this manually in a paint program and save it to a file.
Figure 14-18. The BITMASK display.
If you're writing applications specifically for Windows NT, you can use the MaskBlt function to do something similar to the MASKBIT program with fewer function calls. Windows NT also includes anotherBitBlt-like function not supported under Windows 98. This is the PlgBlt ("parallelogram blt") function that lets you rotate or skew bitmap images.
Finally, if you run BITMASK on your machine and you see only black, white, and a couple of gray shades, it's because you're running in a 16-color or 256-color video mode. With the 16-color mode, there's not much you can do to improve things, but an application running in a 256-color mode can alter the color palette to display shades of gray. You'll find out how inChapter 16.
Because the display of small bitmaps is quite fast, you can use bitmaps in combination with the Windows timer for some rudimentary animation.
Yes, it's time for the bouncing ball program.
The BOUNCE program, shown in Figure 14-19 below, constructs a ball that bounces around in the window's client area. The program uses the timer to pace the ball. The ball itself is a bitmap. The program first creates the ball by creating the bitmap, selecting it into a memory device context, and then making simple GDI function calls. The program draws the bitmapped ball on the display using a BitBlt from a memory device context.
Figure 14-19. The BOUNCE program.
BOUNCE.C/*--------------------------------------- BOUNCE.C -- Bouncing Ball Program (c) Charles Petzold, 1998 ---------------------------------------*/ #include |
BOUNCE reconstructs the ball whenever the program gets a WM_SIZE message. This requires a memory device context compatible with the video display:
hdcMem = CreateCompatibleDC (hdc) ;
The diameter of the ball is set at one-sixteenth of either the height or the width of the client area, whichever is shorter. However, the program constructs a bitmap that is larger than the ball: On each of its four sides, the bitmap extends beyond the ball's dimensions by one-half of the ball's radius:
hBitmap = CreateCompatibleBitmap (hdc, cxTotal, cyTotal) ;
After the bitmap is selected into a memory device context, the entire bitmap is colored white for the background:
Rectangle (hdcMem, -1, -1, xTotal + 1, yTotal + 1) ;
Those odd coordinates cause the rectangle boundary to be painted outside the bitmap. A diagonally hatched brush is selected into the memory device context, and the ball is drawn in the center of the bitmap:
Ellipse (hdcMem, xMove, yMove, xTotal - xMove, yTotal - yMove) ;
The margins around the edges of the ball effectively erase the previous image of the ball when the ball is moved. Redrawing the ball at another position requires only a simpleBitBlt call using the ROP code of SRCCOPY:
BitBlt (hdc, xCenter - cxTotal / 2, yCenter - cyTotal / 2, cxTotal, cyTotal, hdcMem, 0, 0, SRCCOPY) ;
BOUNCE demonstrates the simplest way to move an image around the display, but this approach isn't satisfactory for general purposes. If you're interested in animation, you'll want to explore some of the other ROP codes (such as SRCINVERT) that perform an exclusive OR operation on the source and destination. Other techniques for animation involve the Windows palette (and the AnimatePalette function) and the CreateDIBSection function. For more sophisticated animation, you may need to abandon GDI and explore the DirectX interface.
The SCRAMBLE program, shown in Figure 14-20 beginning below, is very rude and I probably shouldn't be showing it to you. But it demonstrates some interesting techniques and uses a memory device context as a temporary holding space for BitBlt operations that swap the contents of pairs of display rectangles.
Figure 14-20. The SCRAMBLE program.
SCRAMBLE.C/*------------------------------------------------ SCRAMBLE.C -- Scramble (and Unscramble) Screen (c) Charles Petzold, 1998 ------------------------------------------------*/ #include |
SCRAMBLE doesn't have a window procedure. In WinMain, it first calls LockWindowUpdate with the desktop window handle. This function temporarily prevents any other program from updating the screen. SCRAMBLE then obtains a device context for the entire screen by callingGetDCEx with a DCX_LOCKWINDOWUPDATE argument. This lets SCRAMBLE write on the screen when no other program can.
SCRAMBLE then determines the dimensions of the full screen and divides them by 10. The program uses these dimensions (named cx and cy) to create a bitmap and then selects the bitmap into the memory device context.
Using the C rand function, SCRAMBLE calculates four random values (two coordinate points) that are multiples of the cx and cy values. The program swaps two rectangular blocks of the display through the use of three BitBlt functions. The first copies the rectangle beginning at the first coordinate point to the memory device context. The secondBitBlt copies the rectangle beginning at the second point to the location beginning at the first point. The third copies the rectangle in the memory device context to the area beginning at second coordinate point.
This process effectively swaps the contents of the two rectangles on the display. SCRAMBLE does this 300 times, after which the screen should be thoroughly scrambled. But do not fear, because SCRAMBLE keeps track of this mess and then unscrambles the screen, returning it to normal (and unlocking the screen) before exiting.
You can also use memory device contexts to copy the contents of one bitmap to another. For instance, suppose you want to create a bitmap that contains only the upper left quadrant of another bitmap. If the original bitmap has the handle hBitmap, you can copy the dimensions into a structure of type BITMAP,
GetObject (hBitmap, sizeof (BITMAP), &bm) ;
and create a new uninitialized bitmap of one-quarter the size:
hBitmap2 = CreateBitmap (bm.bmWidth / 2, bm.bmHeight / 2, bm.bmPlanes, bm.bmBitsPixel, NULL) ;
Now create two memory device contexts and select the original bitmap and the new bitmap into them:
hdcMem1 = CreateCompatibleDC (hdc) ; hdcMem2 = CreateCompatibleDC (hdc) ; SelectObject (hdcMem1, hBitmap) ; SelectObject (hdcMem2, hBitmap2) ;
Finally, copy the upper left quadrant of the first bitmap to the second:
BitBlt (hdcMem2, 0, 0, bm.bmWidth / 2, bm.bmHeight / 2, hdcMem1, 0, 0, SRCCOPY) ;
You're done, except for cleaning up:
DeleteDC (hdcMem1) ; DeleteDC (hdcMem2) ; DeleteObject (hBitmap) ;
The BLOWUP.C program, shown in Figure 14-21, also uses window update locking to display a capture rectangle outside the border of the program's window. This program lets you use the mouse to block out any rectangular area of the screen. BLOWUP then copies the contents of that rectangular area to a bitmap. During the WM_PAINT message, the bitmap is copied to the program's client area and stretched or compressed, if necessary. (See Figure 14-22)
Figure 14-21. The BLOWUP program.
BLOWUP.C/*--------------------------------------- BLOWUP.C -- Video Magnifier Program (c) Charles Petzold, 1998 ---------------------------------------*/ #include |
BLOWUP.RC (excerpts)//Microsoft Developer Studio generated resource script. #include "resource.h" #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// // Menu BLOWUP MENU DISCARDABLE BEGIN POPUP "&Edit" BEGIN MENUITEM "Cu&t\tCtrl+X", IDM_EDIT_CUT MENUITEM "&Copy\tCtrl+C", IDM_EDIT_COPY MENUITEM "&Paste\tCtrl+V", IDM_EDIT_PASTE MENUITEM "De&lete\tDelete", IDM_EDIT_DELETE END END ///////////////////////////////////////////////////////////////////////////// // Accelerator BLOWUP ACCELERATORS DISCARDABLE BEGIN "C", IDM_EDIT_COPY, VIRTKEY, CONTROL, NOINVERT "V", IDM_EDIT_PASTE, VIRTKEY, CONTROL, NOINVERT VK_DELETE, IDM_EDIT_DELETE, VIRTKEY, NOINVERT "X", IDM_EDIT_CUT, VIRTKEY, CONTROL, NOINVERT END |
RESOURCE.H (excerpts)// Microsoft Developer Studio generated include file. // Used by Blowup.rc #define IDM_EDIT_CUT 40001 #define IDM_EDIT_COPY 40002 #define IDM_EDIT_PASTE 40003 #define IDM_EDIT_DELETE 40004 |
Figure 14-22. A sample BLOWUP display.
Because of restrictions on mouse capturing, using BLOWUP is a little complicated at first and takes some getting used to. Here's how to use the program:
Press the left mouse button in BLOWUP's client area, and keep the left button held down. The mouse cursor changes to a crosshair.
Still holding the left button down, move the mouse cursor anywhere on the screen. Position the mouse cursor at the upper left corner of the rectangular area you want to capture.
Still holding the left button down, press the right mouse button and drag the mouse to the lower right corner of the rectangular area you want to capture. Release the left and right mouse buttons. (The order in which you release the buttons doesn't matter.)
The mouse cursor changes back to an arrow, and the area that you blocked out is copied to BLOWUP's client area and compressed or expanded appropriately.
If you block out a rectangle by moving from the upper right corner to the lower left corner, BLOWUP displays a mirror image. If you move from the lower left to the upper right, BLOWUP displays an upside-down image. And if you move from the upper right to the upper left, the program combines the two effects.
BLOWUP also contains logic to copy the bitmap to the clipboard, and to copy any bitmap in the clipboard to the program. BLOWUP processes the WM_INITMENUPOPUP message to enable or disable the various items on its Edit menu and the WM_COMMAND message to process these menu items. The structure of this code should look familiar because it is essentially the same as that shown inChapter 12 to copy and paste text items.
For bitmaps, however, the clipboard items are not global handles but bitmap handles. When you use the CF_BITMAP, the GetClipboardData function returns an HBITMAP object and the SetClipboardData function accepts an HBITMAP object. If you want to transfer a bitmap to the clipboard but still have a copy of it for use by the program itself, you must make a copy of the bitmap. Similarly, if you paste a bitmap from the clipboard, you should also make a copy. TheCopyBitmap function in BLOWUP does this by obtaining a BITMAP structure of the existing bitmap and using this structure in the CreateBitmapIndirect function to create a new bitmap. (The Src andDst suffixes on the variable names stand for "source" and "destination.") Both bitmaps are selected into memory device contexts and the bitmap bits transferred with a call to BitBlt. (Alternatively, to copy the bits, you can allocate a block of memory the size of the bitmap and call GetBitmapBits for the source bitmap and SetBitmapBits for the destination bitmap.)
I find BLOWUP to be very useful for examining the multitude of little bitmaps and pictures that are scattered throughout Windows and its applications.