Win32 Series - The GDI Bitmap Object

 

http://www-user.tu-chemnitz.de/~heha/petzold/

 

The GDI Bitmap Object

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.

Creating a DDB

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 theGetDeviceCaps 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.

The Bitmap Bits

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 Memory Device Context

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.

Loading Bitmap Resources

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 <windows.h>

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

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PSTR szCmdLine, int iCmdShow)
 {
     static TCHAR szAppName [] = TEXT ("Bricks1") ;
     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  = NULL ;
     wndclass.lpszClassName = szAppName ;
     
     if (!RegisterClass (&wndclass))
     {
          MessageBox (NULL, TEXT ("This program requires Windows NT!"),
                      szAppName, MB_ICONERROR) ;
          return 0 ;
     }
     
     hwnd = CreateWindow (szAppName, TEXT ("LoadBitmap Demo"), 
                          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 ;
}

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
     static HBITMAP hBitmap ;
     static int     cxClient, cyClient, cxSource, cySource ;
     BITMAP         bitmap ;
     HDC            hdc, hdcMem ;
     HINSTANCE      hInstance ;
     int            x, y ;
     PAINTSTRUCT    ps ;
     
     switch (message)
     {
     case WM_CREATE:
          hInstance = ((LPCREATESTRUCT) lParam)->hInstance ;

          hBitmap = LoadBitmap (hInstance, TEXT ("Bricks")) ;

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

          cxSource = bitmap.bmWidth ;
          cySource = bitmap.bmHeight ;

          return 0 ;

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

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

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

          for (y = 0 ; y < cyClient ; y += cySource)
          for (x = 0 ; x < cxClient ; x += cxSource)
          {
               BitBlt (hdc, x, y, cxSource, cySource, hdcMem, 0, 0, SRCCOPY) ;
          }

          DeleteDC (hdcMem) ;
          EndPaint (hwnd, &ps) ;
          return 0 ;

     case WM_DESTROY:
          DeleteObject (hBitmap) ;
          PostQuitMessage (0) ;
          return 0 ;
     }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
}

 

BRICKS1.RC (excerpts)

//Microsoft Developer Studio generated resource script.
#include "resource.h"
#include "afxres.h"

/
// Bitmap

BRICKS                  BITMAP  DISCARDABLE     "Bricks.bmp"

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.

Click to view at full size.

Figure 14-6. The BRICKS1 display.

The Monochrome Bitmap Format

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:

Click to view at full size.

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 <windows.h>

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

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PSTR szCmdLine, int iCmdShow)
{
     static TCHAR szAppName [] = TEXT ("Bricks2") ;
     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  = NULL ;
     wndclass.lpszClassName = szAppName ;
     
     if (!RegisterClass (&wndclass))
     {
          MessageBox (NULL, TEXT ("This program requires Windows NT!"),
                      szAppName, MB_ICONERROR) ;
          return 0 ;
     }
     
     hwnd = CreateWindow (szAppName, TEXT ("CreateBitmap Demo"), 
                          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 ;
}

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
     static BITMAP  bitmap = { 0, 8, 8, 2, 1, 1 } ;
     static BYTE    bits [8][2] = { 0xFF, 0, 0x0C, 0, 0x0C, 0, 0x0C, 0,
                                    0xFF, 0, 0xC0, 0, 0xC0, 0, 0xC0, 0 } ;
     static HBITMAP hBitmap ;
     static int     cxClient, cyClient, cxSource, cySource ;
     HDC            hdc, hdcMem ;
     int            x, y ;
     PAINTSTRUCT    ps ;
     
     switch (message)
     {
     case WM_CREATE:
          bitmap.bmBits = bits ;
          hBitmap = CreateBitmapIndirect (&bitmap) ;
          cxSource = bitmap.bmWidth ;
          cySource = bitmap.bmHeight ;
          return 0 ;

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

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

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

          for (y = 0 ; y < cyClient ; y += cySource)
          for (x = 0 ; x < cxClient ; x += cxSource)
          {
               BitBlt (hdc, x, y, cxSource, cySource, hdcMem, 0, 0, SRCCOPY) ;
          }

          DeleteDC (hdcMem) ;
          EndPaint (hwnd, &ps) ;
          return 0 ;

     case WM_DESTROY:
          DeleteObject (hBitmap) ;
          PostQuitMessage (0) ;
          return 0 ;
     }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
}

 

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.

Brushes from Bitmaps

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 <windows.h>

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

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PSTR szCmdLine, int iCmdShow)
{
     static TCHAR szAppName [] = TEXT ("Bricks3") ;
     HBITMAP      hBitmap ;
     HBRUSH       hBrush ;
     HWND         hwnd ;
     MSG          msg ;
     WNDCLASS     wndclass ;

     hBitmap = LoadBitmap (hInstance, TEXT ("Bricks")) ;
     hBrush = CreatePatternBrush (hBitmap) ;
     DeleteObject (hBitmap) ;

     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 ; 
     wndclass.lpszMenuName  = NULL ;
     wndclass.lpszClassName = szAppName ;
     
     if (!RegisterClass (&wndclass))
     {
          MessageBox (NULL, TEXT ("This program requires Windows NT!"),
                      szAppName, MB_ICONERROR) ;
          return 0 ;
     }
     
     hwnd = CreateWindow (szAppName, TEXT ("CreatePatternBrush Demo"), 
                          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) ;
     }

     DeleteObject (hBrush) ;
     return msg.wParam ;
}

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
     switch (message)
     {
     case WM_DESTROY:
          PostQuitMessage (0) ;
          return 0 ;
     }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
}

 

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.

Drawing on Bitmaps

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 <windows.h>
#include "resource.h"

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

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PSTR szCmdLine, int iCmdShow)
{
     static TCHAR szAppName [] = TEXT ("HelloBit") ;
     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 ("HelloBit"), 
                          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 ;
}

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
     static HBITMAP hBitmap ;
     static HDC     hdcMem ;
     static int     cxBitmap, cyBitmap, cxClient, cyClient, iSize = IDM_BIG ;
     static TCHAR * szText = TEXT (" Hello, world! ") ;
     HDC            hdc ;
     HMENU          hMenu ;
     int            x, y ;
     PAINTSTRUCT    ps ;
     SIZE           size ;
     
     switch (message)
     {
     case WM_CREATE:
          hdc = GetDC (hwnd) ;
          hdcMem  = CreateCompatibleDC (hdc) ;

          GetTextExtentPoint32 (hdc, szText, lstrlen (szText), &size) ;
          cxBitmap = size.cx ;
          cyBitmap = size.cy ;
          hBitmap = CreateCompatibleBitmap (hdc, cxBitmap, cyBitmap) ;

          ReleaseDC (hwnd, hdc) ;

          SelectObject (hdcMem, hBitmap) ;
          TextOut (hdcMem, 0, 0, szText, lstrlen (szText)) ;
          return 0 ;

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

     case WM_COMMAND:
          hMenu = GetMenu (hwnd) ;

          switch (LOWORD (wParam))
          {
          case IDM_BIG:
          case IDM_SMALL:
               CheckMenuItem (hMenu, iSize, MF_UNCHECKED) ;
               iSize = LOWORD (wParam) ;
               CheckMenuItem (hMenu, iSize, MF_CHECKED) ;
               InvalidateRect (hwnd, NULL, TRUE) ;
               break ;
          }
          return 0 ;

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

          switch (iSize)
          {
          case IDM_BIG:
               StretchBlt (hdc, 0, 0, cxClient, cyClient, 
                           hdcMem, 0, 0, cxBitmap, cyBitmap, SRCCOPY) ;
               break ;

          case IDM_SMALL:
               for (y = 0 ; y < cyClient ; y += cyBitmap)
               for (x = 0 ; x < cxClient ; x += cxBitmap)
               {
                    BitBlt (hdc, x, y, cxBitmap, cyBitmap, 
                            hdcMem, 0, 0, SRCCOPY) ;
               }
               break ;
          }

          EndPaint (hwnd, &ps) ;
          return 0 ;

     case WM_DESTROY:
          DeleteDC (hdcMem) ;
          DeleteObject (hBitmap) ;
          PostQuitMessage (0) ;
          return 0 ;
     }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
}

 

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 Shadow Bitmap

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 <windows.h>

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

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PSTR szCmdLine, int iCmdShow)
{
     static TCHAR szAppName [] = TEXT ("Sketch") ;
     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  = NULL ;
     wndclass.lpszClassName = szAppName ;
     
     if (!RegisterClass (&wndclass))

     {
          MessageBox (NULL, TEXT ("This program requires Windows NT!"),
                      szAppName, MB_ICONERROR) ;
          return 0 ;
     }
     
     hwnd = CreateWindow (szAppName, TEXT ("Sketch"), 
                          WS_OVERLAPPEDWINDOW, 
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          NULL, NULL, hInstance, NULL) ;

     if (hwnd == NULL)
     {
          MessageBox (NULL, TEXT ("Not enough memory to create bitmap!"),
                      szAppName, MB_ICONERROR) ;
          return 0 ;
     }

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

     while (GetMessage (&msg, NULL, 0, 0))
     {
          TranslateMessage (&msg) ;
          DispatchMessage (&msg) ;
     }
     return msg.wParam ;
}

void GetLargestDisplayMode (int * pcxBitmap, int * pcyBitmap)
{
     DEVMODE devmode ;
     int     iModeNum = 0 ;

     * pcxBitmap = * pcyBitmap = 0 ;

     ZeroMemory (&devmode, sizeof (DEVMODE)) ;
     devmode.dmSize = sizeof (DEVMODE) ;
     
     while (EnumDisplaySettings (NULL, iModeNum++, &devmode))
     {
          * pcxBitmap = max (* pcxBitmap, (int) devmode.dmPelsWidth) ;
          * pcyBitmap = max (* pcyBitmap, (int) devmode.dmPelsHeight) ;
     }
}

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
     static BOOL    fLeftButtonDown, fRightButtonDown ;
     static HBITMAP hBitmap ;
     static HDC     hdcMem ;
     static int     cxBitmap, cyBitmap, cxClient, cyClient, xMouse, yMouse ;
     HDC            hdc ;
     PAINTSTRUCT    ps ;
     
     switch (message)
     {
     case WM_CREATE:
          GetLargestDisplayMode (&cxBitmap, &cyBitmap) ;

          hdc = GetDC (hwnd) ;
          hBitmap = CreateCompatibleBitmap (hdc, cxBitmap, cyBitmap) ;
          hdcMem  = CreateCompatibleDC (hdc) ;
          ReleaseDC (hwnd, hdc) ;

          if (!hBitmap)       // no memory for bitmap
          {
               DeleteDC (hdcMem) ;
               return -1 ;
          }

          SelectObject (hdcMem, hBitmap) ;
          PatBlt (hdcMem, 0, 0, cxBitmap, cyBitmap, WHITENESS) ;
          return 0 ;

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

     case WM_LBUTTONDOWN:
          if (!fRightButtonDown)
               SetCapture (hwnd) ;

          xMouse = LOWORD (lParam) ;
          yMouse = HIWORD (lParam) ;
          fLeftButtonDown = TRUE ;
          return 0 ;

     case WM_LBUTTONUP:
          if (fLeftButtonDown)
               SetCapture (NULL) ;
          
          fLeftButtonDown = FALSE ;
          return 0 ;
          
     case WM_RBUTTONDOWN:
          if (!fLeftButtonDown)
               SetCapture (hwnd) ;
          
          xMouse = LOWORD (lParam) ;
          yMouse = HIWORD (lParam) ;
          fRightButtonDown = TRUE ;
          return 0 ;
          
     case WM_RBUTTONUP:
          if (fRightButtonDown) 
               SetCapture (NULL) ;
          
          fRightButtonDown = FALSE ;
          return 0 ;

     case WM_MOUSEMOVE:
          if (!fLeftButtonDown && !fRightButtonDown)
               return 0 ;

          hdc = GetDC (hwnd) ;

          SelectObject (hdc, 
               GetStockObject (fLeftButtonDown ? BLACK_PEN : WHITE_PEN)) ;

          SelectObject (hdcMem,
               GetStockObject (fLeftButtonDown ? BLACK_PEN : WHITE_PEN)) ;

          MoveToEx (hdc,    xMouse, yMouse, NULL) ;
          MoveToEx (hdcMem, xMouse, yMouse, NULL) ;

          xMouse = (short) LOWORD (lParam) ;
          yMouse = (short) HIWORD (lParam) ;

          LineTo (hdc,    xMouse, yMouse) ;
          LineTo (hdcMem, xMouse, yMouse) ;

          ReleaseDC (hwnd, hdc) ;
          return 0 ;

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

          BitBlt (hdc, 0, 0, cxClient, cyClient, hdcMem, 0, 0, SRCCOPY) ;

          EndPaint (hwnd, &ps) ;
          return 0 ;

     case WM_DESTROY:
          DeleteDC (hdcMem) ;
          DeleteObject (hBitmap) ;
          PostQuitMessage (0) ;
          return 0 ;
     }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
}

 

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!

Using Bitmaps in Menus

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 <windows.h>
#include "resource.h"

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
void    AddHelpToSys     (HINSTANCE, HWND) ;
HMENU   CreateMyMenu     (HINSTANCE) ;
HBITMAP StretchBitmap    (HBITMAP) ;
HBITMAP GetBitmapFont    (int) ;
void    DeleteAllBitmaps (HWND) ;

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

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  = NULL ;
     wndclass.lpszClassName = szAppName ;
     
     if (!RegisterClass (&wndclass))
     {
          MessageBox (NULL, TEXT ("This program requires Windows NT!"),
                      szAppName, MB_ICONERROR) ;
          return 0 ;
     }
     
     hwnd = CreateWindow (szAppName, TEXT ("Bitmap Menu Demonstration"),
                          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 ;
}

LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
     HMENU      hMenu ;
     static int iCurrentFont = IDM_FONT_COUR ;
     
     switch (iMsg)
     {
     case WM_CREATE:
          AddHelpToSys (((LPCREATESTRUCT) lParam)->hInstance, hwnd) ;
          hMenu = CreateMyMenu (((LPCREATESTRUCT) lParam)->hInstance) ;
          SetMenu (hwnd, hMenu) ;
          CheckMenuItem (hMenu, iCurrentFont, MF_CHECKED) ;
          return 0 ;
          
     case WM_SYSCOMMAND:
          switch (LOWORD (wParam))
          {
          case IDM_HELP:
               MessageBox (hwnd, TEXT ("Help not yet implemented!"),
                           szAppName, MB_OK | MB_ICONEXCLAMATION) ;
               return 0 ;
          }
          break ;
          
     case WM_COMMAND:
          switch (LOWORD (wParam))
          {
          case IDM_FILE_NEW:
          case IDM_FILE_OPEN:
          case IDM_FILE_SAVE:
          case IDM_FILE_SAVE_AS:
          case IDM_EDIT_UNDO:
          case IDM_EDIT_CUT:
          case IDM_EDIT_COPY:
          case IDM_EDIT_PASTE:
          case IDM_EDIT_CLEAR:
               MessageBeep (0) ;
               return 0 ;
                    
          case IDM_FONT_COUR:
          case IDM_FONT_ARIAL:
          case IDM_FONT_TIMES:
               hMenu = GetMenu (hwnd) ;
               CheckMenuItem (hMenu, iCurrentFont, MF_UNCHECKED) ;
               iCurrentFont = LOWORD (wParam) ;
               CheckMenuItem (hMenu, iCurrentFont, MF_CHECKED) ;
               return 0 ;
          }
          break ;
               
     case WM_DESTROY:
          DeleteAllBitmaps (hwnd) ;
          PostQuitMessage (0) ;
          return 0 ;
     }
     return DefWindowPr
oc (hwnd, iMsg, wParam, lParam) ;
}

/*----------------------------------------------------
   AddHelpToSys: Adds bitmap Help item to system menu
  ----------------------------------------------------*/

void AddHelpToSys (HINSTANCE hInstance, HWND hwnd)
{
     HBITMAP hBitmap ;
     HMENU   hMenu ;

     hMenu = GetSystemMenu (hwnd, FALSE);
     hBitmap = StretchBitmap (LoadBitmap (hInstance, TEXT ("BitmapHelp"))) ;
     AppendMenu (hMenu, MF_SEPARATOR, 0, NULL) ;
     AppendMenu (hMenu, MF_BITMAP, IDM_HELP, (PTSTR) (LONG) hBitmap) ;
}

/*----------------------------------------------
   CreateMyMenu: Assembles menu from components
  ----------------------------------------------*/

HMENU CreateMyMenu (HINSTANCE hInstance)
{
     HBITMAP hBitmap ;
     HMENU   hMenu, hMenuPopup ;
     int     i ;

     hMenu = CreateMenu () ;
     
     hMenuPopup = LoadMenu (hInstance, TEXT ("MenuFile")) ;
     hBitmap = StretchBitmap (LoadBitmap (hInstance, TEXT ("BitmapFile"))) ;
     AppendMenu (hMenu, MF_BITMAP | MF_POPUP, (int) hMenuPopup,
                        (PTSTR) (LONG) hBitmap) ;
     
     hMenuPopup = LoadMenu (hInstance, TEXT ("MenuEdit")) ;
     hBitmap = StretchBitmap (LoadBitmap (hInstance, TEXT ("BitmapEdit"))) ;
     AppendMenu (hMenu, MF_BITMAP | MF_POPUP, (int) hMenuPopup,
                        (PTSTR) (LONG) hBitmap) ;
     
     hMenuPopup = CreateMenu () ;
     
     for (i = 0 ; i < 3 ; i++)
     {
          hBitmap = GetBitmapFont (i) ;
          AppendMenu (hMenuPopup, MF_BITMAP, IDM_FONT_COUR + i, 
                                  (PTSTR) (LONG) hBitmap) ;
     }

     hBitmap = StretchBitmap (LoadBitmap (hInstance, TEXT ("BitmapFont"))) ;
     AppendMenu (hMenu, MF_BITMAP | MF_POPUP, (int) hMenuPopup,
                        (PTSTR) (LONG) hBitmap) ;
     return hMenu ;
}

/*----------------------------------------------------
   StretchBitmap: Scales bitmap to display resolution
  ----------------------------------------------------*/

HBITMAP StretchBitmap (HBITMAP hBitmap1)
{
     BITMAP     bm1, bm2 ;
     HBITMAP    hBitmap2 ;
     HDC        hdc, hdcMem1, hdcMem2 ;
     int        cxChar, cyChar ;

          // Get the width and height of a system font character

     cxChar = LOWORD (GetDialogBaseUnits ()) ;
     cyChar = HIWORD (GetDialogBaseUnits ()) ;

          // Create 2 memory DCs compatible with the display
     
     hdc = CreateIC (TEXT ("DISPLAY"), NULL, NULL, NULL) ;
     hdcMem1 = CreateCompatibleDC (hdc) ;
     hdcMem2 = CreateCompatibleDC (hdc) ;
     DeleteDC (hdc) ;

          // Get the dimensions of the bitmap to be stretched
     
     GetObject (hBitmap1, sizeof (BITMAP), (PTSTR) &bm1) ;

          // Scale these dimensions based on the system font size
     
     bm2 = bm1 ;
     bm2.bmWidth      = (cxChar * bm2.bmWidth)  / 4 ;
     bm2.bmHeight     = (cyChar * bm2.bmHeight) / 8 ;
     bm2.bmWidthBytes = ((bm2.bmWidth + 15) / 16) * 2 ;

          // Create a new bitmap of larger size
     
     hBitmap2 = CreateBitmapIndirect (&bm2) ;

          // Select the bitmaps in the memory DCs and do a StretchBlt
     
     SelectObject (hdcMem1, hBitmap1) ;
     SelectObject (hdcMem2, hBitmap2) ;
     StretchBlt (hdcMem2, 0, 0, bm2.bmWidth, bm2.bmHeight,
                 hdcMem1, 0, 0, bm1.bmWidth, bm1.bmHeight, SRCCOPY) ;

          // Clean up
     
     DeleteDC (hdcMem1) ;
     DeleteDC (hdcMem2) ;
     DeleteObject (hBitmap1) ;
     
     return hBitmap2 ;
}

/*------------------------------------------------
   GetBitmapFont: Creates bitmaps with font names
  ------------------------------------------------*/

HBITMAP GetBitmapFont (int i)
{
     static TCHAR  * szFaceName[3] = { TEXT ("Courier New"), TEXT ("Arial"), 
                                       TEXT ("Times New Roman") } ;
     HBITMAP         hBitmap ;
     HDC             hdc, hdcMem ;
     HFONT           hFont ;
     SIZE            size ;
     TEXTMETRIC      tm ;
     
     hdc = CreateIC (TEXT ("DISPLAY"), NULL, NULL, NULL) ;
     GetTextMetrics (hdc, &tm) ;
     
     hdcMem = CreateCompatibleDC (hdc) ;
     hFont  = CreateFont (2 * tm.tmHeight, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                          szFaceName[i]) ;

     hFont = (HFONT) SelectObject (hdcMem, hFont) ;
     GetTextExtentPoint32 (hdcMem, szFaceName[i], 
                           lstrlen (szFaceName[i]), &size);
     
     hBitmap = CreateBitmap (size.cx, size.cy, 1, 1, NULL) ;
     SelectObject (hdcMem, hBitmap) ;
     
     TextOut (hdcMem, 0, 0, szFaceName[i], lstrlen (szFaceName[i])) ;
     
     DeleteObject (SelectObject (hdcMem, hFont)) ;
     DeleteDC (hdcMem) ;
     DeleteDC (hdc) ;
     
     return hBitmap ;
}

/*------------------------------------------------------- 
   DeleteAllBitmaps: Deletes all the bitmaps in the menu
  -------------------------------------------------------*/

void DeleteAllBitmaps (HWND hwnd)
{
     HMENU        hMenu ;
     int          i ;
     MENUITEMINFO mii = { sizeof (MENUITEMINFO), MIIM_SUBMENU | MIIM_TYPE } ;

          // Delete Help bitmap on system menu

     hMenu = GetSystemMenu (hwnd, FALSE);
     GetMenuItemInfo (hMenu, IDM_HELP, FALSE, &mii) ;
     DeleteObject ((HBITMAP) mii.dwTypeData) ;

          // Delete top-level menu bitmaps

     hMenu = GetMenu (hwnd) ;

     for (i = 0 ; i < 3 ; i++)
     {
          GetMenuItemInfo (hMenu, i, TRUE, &mii) ;
          DeleteObject ((HBITMAP) mii.dwTypeData) ;
     }

          // Delete bitmap items on Font menu

     hMenu = mii.hSubMenu ;;

     for (i = 0 ; i < 3 ; i++)
     {
          GetMenuItemInfo (hMenu, i, TRUE, &mii) ;
          DeleteObject ((HBITMAP) mii.dwTypeData) ;
     }
}

 

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.

Nonrectangular Bitmap Images

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 <windows.h>

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

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PSTR szCmdLine, int iCmdShow)
{
     static TCHAR szAppName [] = TEXT ("BitMask") ;
     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 (LTGRAY_BRUSH) ;
     wndclass.lpszMenuName  = NULL ;
     wndclass.lpszClassName = szAppName ;
     
     if (!RegisterClass (&wndclass))
     {

          MessageBox (NULL, TEXT ("This program requires Windows NT!"),
                      szAppName, MB_ICONERROR) ;
          return 0 ;
     }
     
     hwnd = CreateWindow (szAppName, TEXT ("Bitmap Masking Demo"), 
                          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 ;
}

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
     static HBITMAP   hBitmapImag, hBitmapMask ;
     static HINSTANCE hInstance ;
     static int       cxClient, cyClient, cxBitmap, cyBitmap ;
     BITMAP           bitmap ;
     HDC              hdc, hdcMemImag, hdcMemMask ;
     int              x, y ;
     PAINTSTRUCT      ps ;
     
     switch (message)
     {
     case WM_CREATE:
          hInstance = ((LPCREATESTRUCT) lParam)->hInstance ;

               // Load the original image and get its size

          hBitmapImag = LoadBitmap (hInstance, TEXT ("Matthew")) ;
          GetObject (hBitmapImag, sizeof (BITMAP), &bitmap) ;
          cxBitmap = bitmap.bmWidth ;
          cyBitmap = bitmap.bmHeight ;

               // Select the original image into a memory DC
          hdcMemImag  = CreateCompatibleDC (NULL) ;
          SelectObject (hdcMemImag, hBitmapImag) ;

               // Create the monochrome mask bitmap and memory DC

          hBitmapMask = CreateBitmap (cxBitmap, cyBitmap, 1, 1, NULL) ;
          hdcMemMask = CreateCompatibleDC (NULL) ;
          SelectObject (hdcMemMask, hBitmapMask) ;

               // Color the mask bitmap black with a white ellipse
          
          SelectObject (hdcMemMask, GetStockObject (BLACK_BRUSH)) ;
          Rectangle (hdcMemMask, 0, 0, cxBitmap, cyBitmap) ;
          SelectObject (hdcMemMask, GetStockObject (WHITE_BRUSH)) ;
          Ellipse (hdcMemMask, 0, 0, cxBitmap, cyBitmap) ;

               // Mask the original image

          BitBlt (hdcMemImag, 0, 0, cxBitmap, cyBitmap, 
                  hdcMemMask, 0, 0, SRCAND) ;

          DeleteDC (hdcMemImag) ;
          DeleteDC (hdcMemMask) ;
          return 0 ;

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

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

               // Select bitmaps into memory DCs

          hdcMemImag = CreateCompatibleDC (hdc) ;
          SelectObject (hdcMemImag, hBitmapImag) ;

          hdcMemMask = CreateCompatibleDC (hdc) ;
          SelectObject (hdcMemMask, hBitmapMask) ;

               // Center image

          x = (cxClient - cxBitmap) / 2 ;
          y = (cyClient - cyBitmap) / 2 ;
               
               // Do the bitblts

          BitBlt (hdc, x, y, cxBitmap, cyBitmap, hdcMemMask, 0, 0, 0x220326) ;
          BitBlt (hdc, x, y, cxBitmap, cyBitmap, hdcMemImag, 0, 0, SRCPAINT) ;

          DeleteDC (hdcMemImag) ;
          DeleteDC (hdcMemMask) ;
          EndPaint (hwnd, &ps) ;
          return 0 ;

     case WM_DESTROY:
          DeleteObject (hBitmapImag) ;
          DeleteObject (hBitmapMask) ;
          PostQuitMessage (0) ;
          return 0 ;
     }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
}

 

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.

Some Simple Animation

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 <windows.h>
#define ID_TIMER    1

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

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PSTR szCmdLine, int iCmdShow)
{
     static TCHAR szAppName[] = TEXT ("Bounce") ;
     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  = NULL ;
     wndclass.lpszClassName = szAppName ;
     
     if (!RegisterClass (&wndclass))
     {
          MessageBox (NULL, TEXT ("This program requires Windows NT!"),
                      szAppName, MB_ICONERROR) ;
          return 0 ;
     }
     
     hwnd = CreateWindow (szAppName, TEXT ("Bouncing Ball"),
                          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 ;
}

LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
     static HBITMAP hBitmap ;
     static int     cxClient, cyClient, xCenter, yCenter, cxTotal, cyTotal,
                    cxRadius, cyRadius, cxMove, cyMove, xPixel, yPixel ;
     HBRUSH         hBrush ;
     HDC            hdc, hdcMem ;
     int            iScale ;
     
     switch (iMsg)
     {
     case WM_CREATE:
          hdc = GetDC (hwnd) ;
          xPixel = GetDeviceCaps (hdc, ASPECTX) ;
          yPixel = GetDeviceCaps (hdc, ASPECTY) ;
          ReleaseDC (hwnd, hdc) ;
          
          SetTimer (hwnd, ID_TIMER, 50, NULL) ;
          return 0 ;
          
     case WM_SIZE:
          xCenter = (cxClient = LOWORD (lParam)) / 2 ;
          yCenter = (cyClient = HIWORD (lParam)) / 2 ;
          
          iScale = min (cxClient * xPixel, cyClient * yPixel) / 16 ;
          
          cxRadius = iScale / xPixel ;
          cyRadius = iScale / yPixel ;
          
          cxMove = max (1, cxRadius / 2) ;
          cyMove = max (1, cyRadius / 2) ;
          
          cxTotal = 2 * (cxRadius + cxMove) ;
          cyTotal = 2 * (cyRadius + cyMove) ;
          
          if (hBitmap)
               DeleteObject (hBitmap) ;
          hdc = GetDC (hwnd) ;
          hdcMem = CreateCompatibleDC (hdc) ;
          hBitmap = CreateCompatibleBitmap (hdc, cxTotal, cyTotal) ;
          ReleaseDC (hwnd, hdc) ;
          
          SelectObject (hdcMem, hBitmap) ;
          Rectangle (hdcMem, -1, -1, cxTotal + 1, cyTotal + 1) ;
          
          hBrush = CreateHatchBrush (HS_DIAGCROSS, 0L) ;
          SelectObject (hdcMem, hBrush) ;
          SetBkColor (hdcMem, RGB (255, 0, 255)) ;
          Ellipse (hdcMem, cxMove, cyMove, cxTotal - cxMove, cyTotal - cyMove) ;
          DeleteDC (hdcMem) ;
          DeleteObject (hBrush) ;
          return 0 ;
          
     case WM_TIMER:
          if (!hBitmap)
               break ;
          
          hdc = GetDC (hwnd) ;
          hdcMem = CreateCompatibleDC (hdc) ;
          SelectObject (hdcMem, hBitmap) ;
          
          BitBlt (hdc, xCenter - cxTotal / 2,
                       yCenter - cyTotal / 2, cxTotal, cyTotal,
                  hdcMem, 0, 0, SRCCOPY) ;
          
          ReleaseDC (hwnd, hdc) ;
          DeleteDC (hdcMem) ;
          
          xCenter += cxMove ;
          yCenter += cyMove ;
          
          if ((xCenter + cxRadius >= cxClient) || (xCenter - cxRadius <= 0))
               cxMove = -cxMove ;
          
          if ((yCenter + cyRadius >= cyClient) || (yCenter - cyRadius <= 0))
               cyMove = -cyMove ;
          
          return 0 ;
          
     case WM_DESTROY:
          if (hBitmap)
               DeleteObject (hBitmap) ;
          
          KillTimer (hwnd, ID_TIMER) ;
          PostQuitMessage (0) ;
          return 0 ;
     }
     return DefWindowProc (hwnd, iMsg, wParam, lParam) ;
}

 

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.

Bitmaps Outside the Window

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 <windows.h>

#define NUM 300

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

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PSTR szCmdLine, int iCmdShow)
{
     static int iKeep [NUM][4] ;
     HDC        hdcScr, hdcMem ;
     int        cx, cy ;
     HBITMAP    hBitmap ;
     HWND       hwnd ;
     int        i, j, x1, y1, x2, y2 ;

     if (LockWindowUpdate (hwnd = GetDesktopWindow ()))
     {
          hdcScr  = GetDCEx (hwnd, NULL, DCX_CACHE | DCX_LOCKWINDOWUPDATE) ;
          hdcMem  = CreateCompatibleDC (hdcScr) ;
          cx      = GetSystemMetrics (SM_CXSCREEN) / 10 ;
          cy      = GetSystemMetrics (SM_CYSCREEN) / 10 ;
          hBitmap = CreateCompatibleBitmap (hdcScr, cx, cy) ;
         
          SelectObject (hdcMem, hBitmap) ;

          srand ((int) GetCurrentTime ()) ;
          
          for (i = 0 ; i < 2   ; i++)
          for (j = 0 ; j < NUM ; j++)
          {
               if (i == 0)
               {
                    iKeep [j] [0] = x1 = cx * (rand () % 10) ;
                    iKeep [j] [1] = y1 = cy * (rand () % 10) ;

                    iKeep [j] [2] = x2 = cx * (rand () % 10) ;
                    iKeep [j] [3] = y2 = cy * (rand () % 10) ;
               }
               else
               {
                    x1 = iKeep [NUM - 1 - j] [0] ;
                    y1 = iKeep [NUM - 1 - j] [1] ;
                    x2 = iKeep [NUM - 1 - j] [2] ;
                    y2 = iKeep [NUM - 1 - j] [3] ;
               }
               BitBlt (hdcMem,  0,  0, cx, cy, hdcScr, x1, y1, SRCCOPY) ;
               BitBlt (hdcScr, x1, y1, cx, cy, hdcScr, x2, y2, SRCCOPY) ;
               BitBlt (hdcScr, x2, y2, cx, cy, hdcMem,  0,  0, SRCCOPY) ;
                    
               Sleep (10) ;
          }
               
          DeleteDC (hdcMem) ;
          ReleaseDC (hwnd, hdcScr) ;
          DeleteObject (hBitmap) ;
             
          LockWindowUpdate (NULL) ;
     }
     return FALSE ;
}

 

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 <windows.h>
#include <stdlib.h>      // for abs definition
#include "resource.h"


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

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PSTR szCmdLine, int iCmdShow)
{
     static TCHAR szAppName [] = TEXT ("Blowup") ;
     HACCEL       hAccel ;
     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 ("Blow-Up Mouse Demo"), 
                          WS_OVERLAPPEDWINDOW, 
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          NULL, NULL, hInstance, NULL) ;

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

     hAccel = LoadAccelerators (hInstance, szAppName) ;

     while (GetMessage (&msg, NULL, 0, 0))
     {
          if (!TranslateAccelerator (hwnd, hAccel, &msg))
          {
               TranslateMessage (&msg) ;
               DispatchMessage (&msg) ;
          }
     }
     return msg.wParam ;
}

void InvertBlock (HWND hwndScr, HWND hwnd, POINT ptBeg, POINT ptEnd)
{
     HDC hdc ;

     hdc = GetDCEx (hwndScr, NULL, DCX_CACHE | DCX_LOCKWINDOWUPDATE) ;
     ClientToScreen (hwnd, &ptBeg) ;
     ClientToScreen (hwnd, &ptEnd) ;
     PatBlt (hdc, ptBeg.x, ptBeg.y, ptEnd.x - ptBeg.x, ptEnd.y - ptBeg.y,
             DSTINVERT) ;
     ReleaseDC (hwndScr, hdc) ;
}

HBITMAP CopyBitmap (HBITMAP hBitmapSrc)
{
     BITMAP  bitmap ;
     HBITMAP hBitmapDst ;
     HDC     hdcSrc, hdcDst ;

     GetObject (hBitmapSrc, sizeof (BITMAP), &bitmap) ;
     hBitmapDst = CreateBitmapIndirect (&bitmap) ;

     hdcSrc = CreateCompatibleDC (NULL) ;
     hdcDst = CreateCompatibleDC (NULL) ;

     SelectObject (hdcSrc, hBitmapSrc) ;
     SelectObject (hdcDst, hBitmapDst) ;

     BitBlt (hdcDst, 0, 0, bitmap.bmWidth, bitmap.bmHeight,
             hdcSrc, 0, 0, SRCCOPY) ;

     DeleteDC (hdcSrc) ;
     DeleteDC (hdcDst) ;

     return hBitmapDst ;
}

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
     static BOOL    bCapturing, bBlocking ;
     static HBITMAP hBitmap ;
     static HWND    hwndScr ;
     static POINT   ptBeg, ptEnd ;
     BITMAP         bm ;
     HBITMAP        hBitmapClip ;
     HDC            hdc, hdcMem ;
     int            iEnable ;
     PAINTSTRUCT    ps ;
     RECT           rect ;

     switch (message)
     {
     case WM_LBUTTONDOWN:
          if (!bCapturing)
          {
               if (LockWindowUpdate (hwndScr = GetDesktopWindow ()))
               {
                    bCapturing = TRUE ;
                    SetCapture (hwnd) ;
                    SetCursor (LoadCursor (NULL, IDC_CROSS)) ;
               }
               else
                    MessageBeep (0) ;
          }
          return 0 ;

     case WM_RBUTTONDOWN:
          if (bCapturing)
          {
               bBlocking = TRUE ;
               ptBeg.x = LOWORD (lParam) ;
               ptBeg.y = HIWORD (lParam) ;
               ptEnd = ptBeg ;
               InvertBlock (hwndScr, hwnd, ptBeg, ptEnd) ;
          }
          return 0 ;

     case WM_MOUSEMOVE:
          if (bBlocking)
          {
               InvertBlock (hwndScr, hwnd, ptBeg, ptEnd) ;
               ptEnd.x = LOWORD (lParam) ;
               ptEnd.y = HIWORD (lParam) ;
               InvertBlock (hwndScr, hwnd, ptBeg, ptEnd) ;
          }
          return 0 ;

     case WM_LBUTTONUP:
     case WM_RBUTTONUP:
          if (bBlocking)
          {
               InvertBlock (hwndScr, hwnd, ptBeg, ptEnd) ;
               ptEnd.x = LOWORD (lParam) ;
               ptEnd.y = HIWORD (lParam) ;

               if (hBitmap)
               {
                    DeleteObject (hBitmap) ;
                    hBitmap = NULL ;
               }

               hdc = GetDC (hwnd) ;
               hdcMem = CreateCompatibleDC (hdc) ;
               hBitmap = CreateCompatibleBitmap (hdc, 
                                   abs (ptEnd.x - ptBeg.x),
                                   abs (ptEnd.y - ptBeg.y)) ;

               SelectObject (hdcMem, hBitmap) ;

               StretchBlt (hdcMem, 0, 0, abs (ptEnd.x - ptBeg.x),
                                         abs (ptEnd.y - ptBeg.y), 
                           hdc, ptBeg.x, ptBeg.y, ptEnd.x - ptBeg.x, 
                                                  ptEnd.y - ptBeg.y, SRCCOPY) ;

               DeleteDC (hdcMem) ;
               ReleaseDC (hwnd, hdc) ;
               InvalidateRect (hwnd, NULL, TRUE) ;
          }
          if (bBlocking || bCapturing)
          {
               bBlocking = bCapturing = FALSE ;
               SetCursor (LoadCursor (NULL, IDC_ARROW)) ;
               ReleaseCapture () ;
               LockWindowUpdate (NULL) ;
          }
          return 0 ;

     case WM_INITMENUPOPUP:
          iEnable = IsClipboardFormatAvailable (CF_BITMAP) ? 
                              MF_ENABLED : MF_GRAYED ;

          EnableMenuItem ((HMENU) wParam, IDM_EDIT_PASTE, iEnable) ;

          iEnable = hBitmap ? MF_ENABLED : MF_GRAYED ;

          EnableMenuItem ((HMENU) wParam, IDM_EDIT_CUT,    iEnable) ;
          EnableMenuItem ((HMENU) wParam, IDM_EDIT_COPY,   iEnable) ;
          EnableMenuItem ((HMENU) wParam, IDM_EDIT_DELETE, iEnable) ;
          return 0 ;

     case WM_COMMAND:
          switch (LOWORD (wParam))
          {
          case IDM_EDIT_CUT:
          case IDM_EDIT_COPY:
               if (hBitmap)
               {
                    hBitmapClip = CopyBitmap (hBitmap) ;
                    OpenClipboard (hwnd) ;
                    EmptyClipboard () ;
                    SetClipboardData (CF_BITMAP, hBitmapClip) ;
               }
               if (LOWORD (wParam) == IDM_EDIT_COPY)
                    return 0 ;
                                        // fall through for IDM_EDIT_CUT
          case IDM_EDIT_DELETE:
               if (hBitmap)
               {
                    DeleteObject (hBitmap) ;
                    hBitmap = NULL ;
               }
               InvalidateRect (hwnd, NULL, TRUE) ;
               return 0 ;

          case IDM_EDIT_PASTE:
               if (hBitmap)
               {
                    DeleteObject (hBitmap) ;
                    hBitmap = NULL ;
               }
               OpenClipboard (hwnd) ;
               hBitmapClip = GetClipboardData (CF_BITMAP) ;

               if (hBitmapClip)
                    hBitmap = CopyBitmap (hBitmapClip) ;

               CloseClipboard () ;
               InvalidateRect (hwnd, NULL, TRUE) ;
               return 0 ;
          }
          break ;
     case WM_PAINT:
          hdc = BeginPaint (hwnd, &ps) ;

          if (hBitmap)
          {
               GetClientRect (hwnd, &rect) ;

               hdcMem = CreateCompatibleDC (hdc) ;
               SelectObject (hdcMem, hBitmap) ;
               GetObject (hBitmap, sizeof (BITMAP), (PSTR) &bm) ;
               SetStretchBltMode (hdc, COLORONCOLOR) ;

               StretchBlt (hdc,    0, 0, rect.right, rect.bottom,
                           hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, 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) ;
}

 

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:

  1.       Press the left mouse button in BLOWUP's client area, and keep the left  button held down. The mouse cursor changes to a crosshair.

  2.         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.

  3.      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.

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值