Chapter 13  Bits, Blts, and Metafiles
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ

Bitmaps and metafiles represent two very different ways of storing pictorial
information. A bitmap is a complete digital representation of a picture.
Each pixel in the image corresponds to one or more bits in the bitmap.
Monochrome bitmaps require only one bit per pixel; color bitmaps require
additional bits to indicate the color of each pixel.

A metafile, on the other hand, stores pictorial information as a series of
records that correspond directly to GDI calls, such as MoveTo, Rectangle,
TextOut, and others that you encountered in Chapter 12. A metafile is thus a
description of a picture rather than a digital representation of it.

Both bitmaps and metafiles have their place in computer graphics. Bitmaps
are very often used for very complex images originating from the real world,
such as digitized photographs. Metafiles are more suitable for human- or
machine-generated images, such as architectural drawings. Both bitmaps and
metafiles can exist in memory or be stored on a disk as files, and both can
be transferred among Windows applications using the clipboard.

You can construct a bitmap "manually" using the SDKPAINT program included
with the Windows Software Development Kit. You can then include the bitmap
as a resource in a resource script file and load it into a program using the
LoadBitmap function. This was demonstrated in Chapter 8. In Chapter 9 we saw
how bitmaps can substitute for text in a menu. In Chapter 12 we constructed
small 8-by-8-pixel bitmaps to use for brushes.

Metafiles are more closely associated with Windows drawing programs (such as
Micrografx's Designer) and other CAD (computer-aided design) programs. The
user of these programs draws an image with lines, rectangles, ellipses,
text, and other graphics  primitives. Although these programs generally use
a private format for storing the picture in a file, they can usually
transfer the picture to the clipboard in the form of a metafile.

Windows 3 supports two different bitmap formats. The first format (sometimes
called the "device-dependent" format) originated with Windows 1. (I'll be
calling this the "old bitmap.") The second format is called the
"device-independent bitmap" (DIB) and is new in Windows 3. The DIB is an
extension of the bitmap format supported in the OS/2 1.1 Presentation
Manager, and the WINDOWS.H header file contains some structures for working
with OS/2 bitmaps.

Bitmaps have two major drawbacks. First, they are highly susceptible to
problems involving device dependence. Even the device-independent bitmap is
not entirely immune to these problems. The most obvious device dependency is
color. Displaying a color bitmap on a monochrome device is often
unsatisfactory. Another problem is that a bitmap implies a particular
resolution and aspect ratio of an image. Although bitmaps can be stretched
or compressed, this process generally involves duplicating or dropping rows
or columns of pixels and can lead to distortion in the scaled image. A
metafile can be scaled to any size without distortion.

The second major drawback of bitmaps is that they require a large amount of
storage space. For instance, a bitmap representation of an entire
640-by-480, 16-color VGA screen requires over 150 KB. Metafiles usually
require much less storage space than bitmaps. The storage space for a bitmap
is governed by the size of the image and number of colors it contains,
whereas the storage space for a metafile is governed by the complexity of
the image and the number of individual GDI instructions it contains.

One advantage of bitmaps over metafiles, however, is speed. Copying a bitmap
on a video display is usually much faster than rendering a metafile.

In the introduction to GDI in Chapter 11, I talked about two types of device
contexts that don't refer to real devices: the memory device context and the
metafile device context. We'll see how these work as we examine bitmaps and
metafiles and explore the ways we can create, use, and manipulate them. The
subject of bitmaps and metafiles will come up again in Chapter 16, "The
Clipboard." Text, bitmaps, and metafiles are the three primary forms of data
that can be shared by applications through the clipboard.

THE OLD BITMAP FORMAT

The old bitmap format that originated in Windows 1 is very limited and
highly dependent on the output device for which the bitmap is created. You
should use the DIB format rather than the old bitmap format for storing
bitmap files on disk. However, when you need a bitmap solely for use within
a program, working with the old bitmap format is much easier.

Creating Bitmaps in a Program

Windows includes four functions that let you create an old-style bitmap in
your program. They are:

hBitmap = CreateBitmap (cxWidth, cyHeight, nPlanes, nBitsPixel, lpBits) ;
hBitmap = CreateBitmapIndirect (&bitmap) ;
hBitmap = CreateCompatibleBitmap (hdc, cxWidth, cyHeight) ;
hBitmap = CreateDiscardableBitmap (hdc, cxWidth, cyHeight) ;

The cxWidth and cyHeight parameters are the width and the height of the
bitmap in pixels. In CreateBitmap, the nPlanes and nBitsPixel parameters are
the number of color planes and the number of color bits per pixel in the
bitmap. At least one of these parameters should be set to 1. If both
parameters are 1, the function creates a monochrome bitmap. (I'll discuss
how the color planes and color bits represent color shortly.)

In the CreateBitmap function, lpBits can be set to NULL if you are creating
an uninitialized bitmap. The resultant bitmap contains random data. In the
CreateCompatibleBitmap and CreateDiscardableBitmap functions, Windows uses
the device context referenced by hdc to obtain the number of color planes
and number of color bits per pixel. The bitmap created by these functions is
uninitialized.

CreateBitmapIndirect is similar to CreateBitmap except that it uses the
bitmap structure of type BITMAP to define the bitmap. The following table
shows the fields of this structure:


Field         Type   Description
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
bmType        short  Set to 0
bmWidth       short  Width of bitmap in pixels
bmHeight      short  Height of bitmap in scan lines
bmWidthBytes  short  Width of bitmap in bytes (must be even)
bmPlanes      BYTE   Number of color planes
bmBitsPixel   BYTE   Number of color bits per pixel
bmBits        LPSTR  Far pointer to array of bits


The bmWidthBytes field must be an even number--the lowest even number of
bytes required to store one scan line. The array of the bits referenced by
bmBits must be organized based on the bmWidthBytes field. If bm is a
structure variable of type BITMAP, you can calculate the bmWidthBytes field
by the following statement:

bm.bmWidthBytes = (bm.bmWidth * bm.bmBitsPixel + 15) / 16 * 2 ;

If Windows cannot create the bitmap (generally because not enough memory is
available), it will return a NULL. You should check the return value from
the bitmap creation functions, particularly if you're creating large
bitmaps.

The handle to the bitmap is not a handle to a global memory block, so don't
try to use the GlobalLock function on it. The handle is instead a local
handle to the GDI module's data segment. This handle references a small
local memory block in GDI that contains a second handle to a global memory
block containing the information in the BITMAP structure and the actual
bits.

Once you create a bitmap, you cannot change the size, the number of color
planes, or the number of color bits per pixel. You would have to create a
new bitmap and transfer the bits from the original bitmap to this new
bitmap. If you have a handle to a bitmap, you can get the size and color
organization using:

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

This copies the information about the bitmap into a structure (called bitmap
here) of type BITMAP. This function doesn't fill in the bmBits field. To get
access to the actual bits of the bitmap, you must call:

GetBitmapBits (hBitmap, dwCount, lpBits) ;

This copies dwCount bits into a character array referenced by the far
pointer lpBits. To ensure that all the bits of the bitmap are copied into
this array, you can calculate the dwCount parameter based on the fields of
the bitmap structure:

dwCount = (DWORD) bitmap.bmWidthBytes * bitmap.bmHeight *
                    bitmap.bmPlanes ;

You can also direct Windows to copy a character array containing the bitmap
bits back into an existing bitmap using the function:

SetBitmapBits (hBitmap, dwCount, lpBits) ;

Because bitmaps are GDI objects, you should delete any bitmap you create:

DeleteObject (hBitmap) ;


The Monochrome Bitmap Format

For a monochrome bitmap, the format of the bits is relatively simple and can
almost be derived directly from the image you want to create. For instance,
suppose you want to create a bitmap that looks like this:

You can write down a series of bits (0 for black and 1 for white) that
directly corresponds to this grid. Reading these bits from left to right,
you can then assign each group of 8 bits a hexadecimal byte. If the width of
the bitmap is not a multiple of 16, pad the bytes to the right with zeros to
get an even number of bytes:

0 1 0 1 0 0 0 1 0 1 1 1 0 1 1 1 0 0 0 1 = 51 77 10 00
0 1 0 1 0 1 1 1 0 1 1 1 0 1 1 1 0 1 0 1 = 57 77 50 00
0 0 0 1 0 0 0 1 0 1 1 1 0 1 1 1 0 1 0 1 = 11 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 the
following statement:

static BITMAP bitmap    = { 0, 20, 5, 4, 1, 1 } ;

and you can store the bits in a BYTE array:

static BYTE   byBits [] = { 0x51, 0x77, 0x10, 0x00,
                            0x57, 0x77, 0x50, 0x00,
                            0x11, 0x77, 0x50, 0x00,
                            0x57, 0x77, 0x50, 0x00,
                            0x51, 0x11, 0x10, 0x00 } ;

Creating the bitmap with CreateBitmapIndirect requires two statements:

bitmap.bmBits = (LPSTR) byBits ;

hBitmap = CreateBitmapIndirect (&bitmap) ;

Be careful when working with the pointer to byBits. It's OK to call
CreateBitmapIndirect right after you assign the far address to the bmBits
field, but this field can become invalid if Windows moves your local data
segment.

You may prefer the following statements, which avoid this problem:

hBitmap = CreateBitmapIndirect (&bitmap) ;

SetBitmapBits (hBitmap, (DWORD) sizeof byBits, byBits) ;

You can also avoid using the bitmap structure entirely and create the bitmap
in one statement:

hBitmap = CreateBitmap (20, 5, 1, 1, byBits) ;


The Color Bitmap Format

An old-style color bitmap is a little more complex and extremely device
dependent. A color bitmap is organized to facilitate the transfer of the
bits to a particular output device. Whether the bitmap is organized as a
series of color planes or as multiple color bits per pixel depends on the
device for which the bitmap is suitable.

Let's look first at a bitmap that has a bmBitsPixel field of 1 (which means
that it has 1 color bit per pixel) but a bmPlanes value greater than 1. A
color bitmap for the EGA or VGA is a good example. Windows uses the 4 color
planes of the EGA or VGA to display 16 colors, so bmPlanes is 4. The array
of bits begins with the top scan line. The color planes for each scan line
are stored sequentially--the red plane first, the green plane, the blue
plane, and the intensity plane. The bitmap then continues with the second
scan line.

A bitmap can also represent color as a multiple number of bits per pixel.
Suppose a device (such as the IBM 8514/A) can represent 256 colors using 8
color bits (1 byte) per pixel. For each scan line, the first byte represents
the color for the leftmost pixel, the second byte represents the color for
the next pixel, and so forth. The bmWidthBytes value in the BITMAP structure
reflects the increased byte width of each scan line, but the bmWidth value
is still the number of pixels per scan line.

Here's the catch: Nothing in the bitmap specifies how these multiple color
planes or multiple color bits correspond to actual display colors. A
particular color bitmap is suitable only for an output device with display
memory organized like the bitmap. For instance, suppose you have a device
that stores color information using 8 bits per pixel, but the 256 values are
interpreted by the device differently than on the 8514/A. This is perfectly
legitimate, but a bitmap created for the 8514/A would have incorrect colors
on this other device.

The device-independent bitmap solves this problem, as we'll see shortly.


The Dimensions of a Bitmap

Two other functions connected with bitmaps are the source of some confusion.
These are:

SetBitmapDimension (hBitmap, xDimension, yDimension) ;

and:

dwDimension = GetBitmapDimension (hBitmap) ;

The xDimension and yDimension values (encoded as the low and high words in
the dwDimension value returned from GetBitmapDimension) are the width and
height of the bitmap in units of 0.1 mm, which correspond to logical units
in the MM_LOMETRIC mapping mode. GDI itself doesn't use these dimensions.
They are part of neither the BITMAP structure nor the bitmap file format.
However, two cooperating applications could use these dimensions to aid in
the scaling of bitmaps that are exchanged through the clipboard or other
means.



THE DEVICE-INDEPENDENT BITMAP (DIB)

The device-independent bitmap (DIB) format is an extension of the bitmap
format introduced in the OS/2 1.1 Presentation Manager. It solves some of
the device dependencies of the old bitmap format by including a color table
that defines an RGB value for each color in the bitmap.

The device-independent bitmap has several other differences from the old
bitmap format: First, color is always represented by multiple color bits per
pixel and never as multiple color planes, despite how buffer memory is
organized on the output device. The number of color bits per pixel may be 1
(for monochrome bitmaps), 4 (16-color bitmaps), 8 (256 colors), or 24 (16
million colors). Second, the array of bits begins with the bottom row of
pixels rather than the top. Third, additional information is included in the
bitmap to indicate a resolution of the image. (This may help programs in
scaling a bitmap to a proper size.) Fourth, the bitmap data may be
compressed using a run-length-encoded (RLE) algorithm.

The DIB File

You can create a device-independent bitmap and save it to a disk file (with
the extension .BMP) in either the SDKPAINT program included in the Windows
Software Development Kit or the PAINTBRUSH program included in the retail
Windows product. The file begins with a file header section defined by the
BITMAPFILEHEADER structure. This structure has five fields:

Field        Size   Description
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
bfType       WORD   The bytes "BM" (for bitmap)

bfSize       DWORD  Total size of the file

bfReserved1  WORD   Set to 0

bfReserved2  WORD   Set to 0

bfOffBits    DWORD  Offset to the bitmap bits from the beginning of the
                    file


This is followed by another header defined by the BITMAPINFOHEADER
structure. This structure has 11 fields:


Field            Size   Description
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
biSize           DWORD  Size of the BITMAPINFOHEADER structure in bytes

biWidth          DWORD  Width of the bitmap in pixels

biHeight         DWORD  Height of the bitmap in pixels

biPlanes         WORD   Set to 1

biBitCount       WORD   Color bits per pixel (1, 4, 8, or 24)

biCompression    DWORD  Compression scheme (0 for none)

biSizeImage      DWORD  Size of bitmap bits in bytes (only required if
                        compression is used)

biXPelsPerMeter  DWORD  Horizontal resolution in pixels per meter

biYPelsPerMeter  DWORD  Vertical resolution in pixels per meter

biClrUsed        DWORD  Number of colors used in image

biClrImportant   DWORD  Number of important colors in image



All fields following the biBitCount field may be set to 0 for default
values.

If biClrUsed is set to 0 and the number of color bits per pixel is 1, 4, or
8, the BITMAPINFOHEADER structure is followed by a color table, which
consists of two or more RGBQUAD structures. The RGBQUAD structure defines an
RGB color value:

Field        Size  Description
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
rgbBlue      BYTE  Blue intensity
rgbGreen     BYTE  Green intensity
rgbRed       BYTE  Red intensity
rgbReserved  BYTE  Set to 0

The number of RGBQUAD structures is usually determined by the biBitCount
field: 2 RGBQUAD structures are required for 1 color bit, 16 for 4 color
bits, and 256 for 8 color bits. However, if the biClrUsed field is nonzero,
then the biClrUsed field contains the number of RGBQUAD structures in the
color table.

The color table is followed by the array of bits that define the bitmap
image. This array begins with the bottom row of pixels. Each row begins with
the leftmost pixels. Each pixel corresponds to 1, 4, 8, or 24 bits.

For a monochrome bitmap with 1 color bit per pixel, the first pixel in each
row is represented by the most significant bit of the first byte in each
row. If this bit is 0, the color of the pixel can be obtained from the first
RGBQUAD structure in the color table. If the bit is 1, the color is given by
the second RGBQUAD structure in the color table.

For a 16-color bitmap with 4 color bits per pixel, the first pixel in each
row is represented by the most significant four bits of the first byte in
each row. The color of each pixel is obtained by using the 4-bit value as an
index into the 16 entries in the color table.

For a 256-color bitmap, each byte corresponds to one pixel. The color of the
pixel is obtained by indexing the 256 entries in the color table by the
byte.

If the bitmap image contains 24 color bits per pixel, each set of three
bytes is an RGB value of the pixel. There is no color table (unless the
biClrUsed field in the BITMAPINFOHEADER structure is nonzero).

In each case, each row of the bitmap data contains a multiple of four bytes.
The row is padded on the right to ensure this.

The bitmap format supported in OS/2 1.1 and above is very similar. It begins
with a BITMAPFILEHEADER structure but is followed by a BITMAPCOREHEADER
structure. (You can determine if a bitmap file uses this format or the
Windows 3 format by examining the first field of this structure.) The color
table consists of RGBTRIPLE structures rather than RGBQUAD structures.


Creating a DIB

The CreateDIBitmap function creates a device-independent bitmap. You can use
this function in two different ways. The function call:

hBitmap = CreateDIBitmap (hdc, &bmih, 0L, NULL, NULL, 0) ;

creates an uninitialized bitmap. The second parameter is a pointer to an
initialized BITMAPINFOHEADER structure. The function call:

hBitmap = CreateDIBitmap (hdc, &bmih, CBM_INIT,
                          lpBits, &bmi, wUsage) ;

creates an initialized bitmap. The lpBits parameter is a pointer to the
array of bits. The fifth parameter is a pointer to an initialized BITMAPINFO
structure.

The BITMAPINFO structure is defined as follows:

typedef structure tagBITMAPINFO
     {
BITMAPINFOHEADER bmiHeader ;
     RGBQUAD          bmiColors[1] ;
     }
     BITMAPINFO ;

The first field is an initialized BITMAPINFOHEADER structure, and the second
field is an array of initialized RGBQUAD structures that define the color
table. Windows needs this color table when the bitmap is initialized to
properly interpret the bitmap image data and to perform any color
conversions required by the device.

Note that the RGBQUAD array contains only one element. To use this structure
you must allocate a memory block equal in size to the BITMAPINFOHEADER
structure plus enough RGBQUAD structures for the whole color table.

The wUsage parameter to CreateDIBitmap can be either DIB_RGB_COLORS (which
means that the color table contains RGB color values) or DIB_PAL_COLORS
(indicating that the color table is an array of 2-byte values that index a
palette).

Two functions are available to set and obtain the bits of the bitmap. The
first function sets the bits:

SetDIBits (hdc, hBitmap, nStart, nNum,
           lpBits, &bmi, wUsage) ;

The last three parameters are the same as in the CreateDIBitmap function.
The nStart parameter indicates the beginning scan line addressed by lpBits.
This can range from 0 (for the bottom scan line) to the height of the bitmap
in pixels minus 1 (for the top scan line). The nNum parameter indicates the
number of scan lines to set into the bitmap.

The GetDIBits function has identical parameters:

GetDIBits (hdc, hBitmap, nStart, nNum,
           lpBits, &bmi, wUsage) ;

But in this case lpBits points to a buffer to receive the bitmap bits. The
function sets the fields of the BITMAPINFO structure to indicate the
dimensions of the bitmap and the color table.

Like the old-style bitmap, a DIB can be deleted using DeleteObject.



THE MEMORY DEVICE CONTEXT

Two functions--SetDIBitsToDevice and StretchDIBits--allow you to render an
array of bits on an output device. However, if you have a handle to a
bitmap, there is no function to draw the bitmap on the display surface of a
device context. You'll search in vain for a function that looks like this:

DrawBitmap (hdc, hBitmap, xStart, yStart) ; // No such function!!!

This function would copy a bitmap to the device context represented by hdc
beginning at the logical point (xStart, yStart). We'll write our own
DrawBitmap function later in this chapter. First, however, you need to
become familiar with several concepts, starting with the memory device
context.

A memory device context is a device context that has a "display surface"
that exists only in memory. You can create a memory device context using the
function:

hdcMem = CreateCompatibleDC (hdc) ;

The hdc handle is a handle to an existing valid device context. The function
returns a handle to the memory device context. Upon creation of the memory
device context, all the attributes are set to the normal default values. You
can do almost anything you want with this memory device context. You can set
the attributes to nondefault values, obtain the current settings of the
attributes, and select pens, brushes, regions into it. And yes, you can even
draw on it. But it doesn't make much sense to do so just yet. Here's why.

When you first create a memory device context, it has a "display surface"
that contains exactly 1 monochrome pixel. That is a very small display
surface. (Don't rely on GetDeviceCaps to tell you this. The HORZSIZE,
VERTSIZE, HORZRES, VERTRES, BITSPIXEL, and PLANES values for hdcMem will all
be set to the values associated with the original hdc. If GetDeviceCaps
really returned the correct values associated with the memory device context
when it is first created, then the HORZRES, VERTRES, BITSPIXEL, and PLANES
indexes would all return 1.) What you need to do is make the display surface
of the memory device context larger. You do this by selecting a bitmap into
the device context:

SelectObject (hdcMem, hBitmap) ;

Now the display surface of hdcMem has the same width, height, and color
organization as the bitmap referenced by hBitmap. With the default window
and viewport origins, the logical point (0, 0) of the memory device context
corresponds to the upper left corner of the bitmap.

If the bitmap had some kind of picture on it, then that picture is now part
of the memory device context's display surface. Any changes you make to that
bitmap (for instance, by using SetBitmapBits to set a different array of
bits to the bitmap) are reflected in this display surface. Anything you draw
on the memory device context is actually drawn on the bitmap. In short, the
bitmap is the display surface of the memory device context.

Earlier I discussed the various functions to create bitmaps. One of them is:

hBitmap = CreateCompatibleBitmap (hdc, xWidth, yHeight) ;

If hdc is the handle to the normal device context for a screen or a printer,
then the number of color planes and number of bits per pixel of this bitmap
are the same as for the device. However, if hdc is the handle to a memory
device context (and no bitmap has yet been selected into the memory device
context), then CreateCompatibleBitmap returns a monochrome bitmap that is
xWidth pixels wide and yHeight pixels high.

A bitmap is one of six GDI objects. You saw in Chapter 12 how to use
SelectObject to select a pen, brush, or region into a device context, and in
Chapter 14 you'll learn how to use this function to select a font into a
device context. You can use SelectObject to select these four GDI objects
into a memory device context also. However, you cannot select a bitmap into
a normal device context, only into a memory device context.

When you're finished with the memory device context, you must delete it:

DeleteDC (hdcMem) ;

Well, you may say, this is all very nice, but we haven't yet solved the
problem of getting the bitmap on the display. All we've done is select it
into a memory device context. Now what? Now we have to learn how to "blt"
(pronounced "blit") the bits from one device context to another.


THE MIGHTY BLT

Graphics involves writing pixels to a display device. In Chapter 12 we
looked at the more refined ways of doing this, but for power-pixel
manipulation, nothing in Windows comes close to BitBlt and its two cousins,
PatBlt and StretchBlt. BitBlt (pronounced "bit blit") stands for "bit-block
transfer." BitBlt is a pixel-mover, or (more vividly) a raster-blaster. The
simple word "transfer" doesn't really do justice to BitBlt. It does more
than a transfer--it really does a logical combination of three sets of
pixels using 1 of 256 different types of raster operations.

The PatBlt Function

PatBlt ("pattern block transfer") is the simplest of the three "blt"
functions. It's really quite different from BitBlt and StretchBlt in that it
uses only one device context. But PatBlt is nonetheless a reasonable place
to begin.

In Chapter 12 you encountered the device context attribute called the
drawing mode. This attribute can be set to 1 of 16 binary raster operation
(ROP2) codes. When you draw a line, the drawing mode determines the type of
logical operation that Windows performs on the pixels of the pen and the
pixels of the device context destination. PatBlt is similar to the drawing
mode except that it alters a rectangular area of the device context
destination rather than merely a line. It performs a logical operation
involving the pixels in this rectangle and a "pattern." This "pattern" is
nothing new--pattern is simply another name for a brush. For this pattern,
PatBlt uses the brush currently selected in the device context.

The syntax of PatBlt is:

PatBlt (hdc, xDest, yDest, xWidth, yHeight, dwROP) ;

The xDest, yDest, xWidth, and yHeight parameters are in logical units. The
logical point (xDest, yDest) specifies the upper left corner of a rectangle.
The rectangle is xWidth units wide and yHeight units high. (See the section
entitled "Blt Coordinates," later in this chapter, for a more precise
definition of these values.) This is the rectangular area that PatBlt
alters. The logical operation that PatBlt performs on the brush and the
destination device context is determined by the dwROP parameter, which is a
doubleword (32-bit integer) ROP code--not one of the ROP2 codes used for the
drawing mode.

Windows has 256 ROP codes. These define all possible logical combinations of
a source display area, a destination display area, and a pattern (or brush).
The device driver for the video display supports all 256 raster operations
through the use of a "compiler" of sorts. This compiler uses the 32-bit ROP
code to create a set of machine-language instructions on the stack that can
carry out this logical operation on the pixels of the display; it then
executes these instructions. The high word of the 32-bit ROP code is a
number between 0 and 255. The low word is a number that assists the device
driver "compiler" in constructing the machine code for the logical
operation. Fifteen of the 256 ROP codes have names. If you want to use any
of the others, you'll have to look up the number in the table in the
Programmer's Reference included with the Windows Software Development Kit.

Because the PatBlt function uses only a destination device context and a
pattern (and not a source device context), it can accept only a subset of
these 256 ROP codes--that is, the 16 ROP codes that use only the destination
device context and a pattern. The 16 raster operations supported by PatBlt
are shown in the table below. You'll note that this is similar to the table
showing ROP2 codes on page 551 of Chapter 12.


Pattern (P):      1  1  0  0  Boolean    ROP
Destination (D):  1  0  1  0  Operation  Code    Name
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Result:           0  0  0  0  0          000042  BLACKNESS
                  0  0  0  1  ~(P|D)     0500A9
                  0  0  1  0  ~P & D     0A0329
                  0  0  1  1  ~P         0F0001
                  0  1  0  0  P & ~D     500325
                  0  1  0  1  ~D         550009  DSTINVERT
                  0  1  1  0  P ^ D      5A0049  PATINVERT
                  0  1  1  1  ~(P & D)   5F00E9
                  1  0  0  0  P & D      A000C9
                  1  0  0  1  ~(P ^ D)   A50065
                  1  0  1  0  D          AA0029
                  1  0  1  1  ~P|D       AF0229
                  1  1  0  0  P          F00021  PATCOPY
                  1  1  0  1  P|~D       F50225
                  1  1  1  0  P|D        FA0089
                  1  1  1  1  1          FF0062  WHITENESS


For a monochrome device context, a 1 bit corresponds to a white pixel and a
0 bit to a black pixel. Destinations and patterns that are either pure black
or pure white are the easiest to consider when you start thinking about
PatBlt. For instance, if you call:

PatBlt (hdc, xDest, yDest, xWidth, yHeight, 0x5F00E9L) ;

then the rectangular area that begins at the logical point (xDest, yDest)
and that is xWidth pixels wide and yHeight pixels high will be colored black
only if the destination was originally white and you had WHITE_BRUSH
selected in the device context. Otherwise, the destination will be colored
white. Of course, even in a monochrome device context, destinations and
brushes can be dithered combinations of black and white pixels. In this
case, Windows performs the logical combination on a pixel-by-pixel basis,
which can lead to some odd results. For instance, if your destination has
already been colored with GRAY- _BRUSH, and GRAY_BRUSH is also the current
brush selected in the device context, then:

PatBlt (hdc, xDest, yDest, xWidth, yHeight, PATINVERT) ;

will set the destination to either pure white or pure black, depending on
how the dithered pixels of the destination coincide with the dithered pixels
of the brush.

Color introduces more complexities. Windows performs the logical operation
for each color plane separately or each set of color bits separately,
depending on how the memory of the device is organized.

Some of the more common uses of PatBlt are shown below. If you want to draw
a black rectangle, you call:

PatBlt (hdc, xDest, yDest, xWidth, yHeight, BLACKNESS) ;

To draw a white rectangle, use:

PatBlt (hdc, xDest, yDest, xWidth, yHeight, WHITENESS) ;

The function:

PatBlt (hdc, xDest, yDest, xWidth, yHeight, DSTINVERT) ;

always inverts the colors of the rectangle. If WHITE_BRUSH is currently
selected in the device context, then the function:

PatBlt (hdc, xDest, yDest, xWidth, yHeight, PATINVERT) ;

also inverts the rectangle.

You'll recall that the FillRect function fills in a rectangular area with a
brush:

FillRect (hdc, &rect, hBrush) ;

The FillRect function is equivalent to the following code:

hBrush = SelectObject (hdc, hBrush) ;
PatBlt (hdc, rect.left, rect.top,
               rect.right - rect.left,
               rect.bottom - rect.top, PATCOPY) ;
SelectObject (hdc, hBrush) ;

In fact, this code (in more optimized assembly language) is what Windows
uses to execute the FillRect function. When you call:

InvertRect (hdc, &rect) ;

Windows translates it into the function:

PatBlt (hdc, rect.left, rect.top,
               rect.right - rect.left,
               rect.bottom - rect.top, DSTINVERT) ;


Blt Coordinates

When I introduced the syntax of the PatBlt function, I said that the point
(xDest, yDest) specifies the upper left corner of a rectangle and that this
rectangle is xWidth units wide and yHeight units high. Actually, although
that's also what the Windows documentation says, the statement is not
entirely accurate. Before we proceed any further, I need to clear up some
confusion concerning the blt functions and coordinates.

BitBlt, PatBlt, and StretchBlt are the only GDI drawing functions that
specify logical rectangular coordinates in terms of a logical width and
height measured from a single corner. All the other GDI drawing functions
that use rectangular bounding boxes require that coordinates be specified in
terms of an upper left corner and a lower right corner. For the MM_TEXT
mapping mode, the above description of the PatBlt parameters is accurate.
For the metric mapping modes, however, it's not. If you use positive values
of xWidth and yHeight, then the point (xDest, yDest) will be the lower left
corner of the rectangle. If you want (xDest, yDest) to be the upper left
corner of the rectangle, the yHeight parameter must be set to the negative
height of the rectangle.

To be more precise, the rectangle that PatBlt colors has a logical width
given by the absolute value of xWidth and a logical height given by the
absolute value of yHeight. These two parameters can be negative. The
rectangle is defined by two corners given by the logical points (xDest,
yDest) and (xDest + xWidth, yDest + yHeight). The upper left corner of the
rectangle is always included in the area that PatBlt modifies. The lower
right corner is outside the rectangle. Depending on the mapping mode and the
signs of the xWidth and yHeight parameters, the upper left corner of this
rectangle could be the point:

(xDest, yDest)

or:

(xDest, yDest + yHeight)

or:

(xDest + xWidth, yDest)

or:

(xDest + xWidth, yDest + yHeight)

If you've set the mapping mode to MM_LOENGLISH and you want to use PatBlt on
the square inch at the upper left corner of the client area, you can use:

PatBlt (hdc, 0, 0, 100, -100, dwROP) ;

or:

PatBlt (hdc, 0, -100, 100, 100, dwROP) ;

or:

PatBlt (hdc, 100, 0, -100, -100, dwROP) ;

or:

PatBlt (hdc, 100, -100, -100, 100, dwROP) ;

The easiest way to set the correct parameters to PatBlt is to set xDest and
yDest to the upper left corner of the rectangle. If your mapping mode
defines y-coordinates as increasing as you move up the display, use a
negative value for the yHeight parameter. If your mapping mode defines
x-coordinates as increasing to the left (which is almost unheard of), use a
negative value for the xWidth parameter.


Transferring Bits with BitBlt

In one sense, BitBlt is a superset of PatBlt. It does everything PatBlt does
but also introduces a second device context into the logical operation.
Here's the general syntax:

BitBlt (hdcDest, xDest, yDest, xWidth, yHeight,
        hdcSrc,  xSrc,  ySrc,  dwROP) ;

The BitBlt call modifies the destination device context (whose handle is
hdcDest) within the rectangle defined by the logical point (xDest, yDest)
and the xWidth and yHeight parameters, both of which are in logical units.
These parameters define a rectangle as described in the previous section.
BitBlt also uses a rectangle in a source device context (whose handle is
SrcDC). This rectangle begins at the logical point (xSrc, ySrc) and is also
xWidth logical units wide and yHeight logical units high.

BitBlt performs a logical combination of three elements: the brush selected
in the destination device context, the pixels in the source device context
rectangle, and the pixels in the destination device context rectangle. The
result is written to the destination device context rectangle. You can use
any of the 256 ROP codes for the dwROP parameter to BitBlt. The 15 ROP codes
that have names are shown in the table below. If you need to use any of the
others, you can look them up in the Programmer's Reference.


Pattern (P):      1  1  1  1  0  0  0  0
Source (S):       1  1  0  0  1  1  0  0  Boolean    ROP
Destination (D):  1  0  1  0  1  0  1  0  Operation  Code    Name
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Result:           0  0  0  0  0  0  0  0  0          000042  BLACKNESS
                  0  0  0  1  0  0  0  1  ~ (S|D)    1100A6  NOTSRCERASE
                  0  0  1  1  0  0  1  1  ~ S        330008  NOTSRCCOPY
                  0  1  0  0  0  1  0  0  S & ~D     440328  SRCERASE
                  0  1  0  1  0  1  0  1  ~D         550009  DSTINVERT
                  0  1  0  1  1  0  1  0  P ^ D      5A0049  PATINVERT
                  0  1  1  0  0  1  1  0  S ^ D      660046  SRCINVERT
                  1  0  0  0  1  0  0  0  S & D      8800C6  SRCAND
                  1  0  1  1  1  0  1  1  ~S|D       BB0226  MERGEPAINT
                  1  1  0  0  0  0  0  0  P & S      C000CA  MERGECOPY
                  1  1  0  0  1  1  0  0  S          CC0020  SRCCOPY
                  1  1  1  0  1  1  1  0  S|D        EE0086  SRCPAINT
                  1  1  1  1  0  0  0  0  P          F00021  PATCOPY
                  1  1  1  1  1  0  1  1  P|~S|D     FB0A09  PATPAINT
                  1  1  1  1  1  1  1  1  1          FF0062  WHITENESS


Look at the eight 0's and 1's that show the result of the logical
combination. The two-digit hexadecimal number that corresponds to these bits
is the high word of the ROP code. If we can create a table of the result we
want from the pattern, source, and destination, we can easily determine the
ROP code from the table of ROP codes in the Programmer's Reference. We'll be
doing this a little later. If you use 1 of the 16 ROP codes shown in the
table on page 617, then you can use PatBlt instead of BitBlt, because you're
not referencing a source device context.

You can set hdcSrc and hdcDest to the same device context handle, in which
case BitBlt will perform a logical combination of the destination rectangle,
the source rectangle, and the current brush selected in the device context.
However, it's a little risky to do this in your client-area device context.
If part of the source rectangle is covered by another window, then Windows
will use the pixels of this other window as the source. Windows doesn't know
what's underneath that other window in your client area.

However, examples of the BitBlt function using the same device context for
the source and destination are the easiest to grasp. The function:

BitBlt (hdc, 100, 0, 50, 100, hdc, 0, 0, SRCCOPY) ;

copies the rectangle beginning at logical point (0, 0) that is 50 logical
units wide and 100 logical units high to the rectangular area beginning at
the logical point (100, 0).


The DrawBitmap Function

BitBlt becomes most valuable in working with bitmaps that have been selected
into a memory device context. When you perform a "bit-block transfer" from
the memory device context to a device context for your client area, the
bitmap selected in the memory device context is transferred to your client
area.

Earlier I mentioned a hypothetical DrawBitmap function that would draw a
bitmap on a display surface. Such a function would have the syntax:

DrawBitmap (hdc, hBitmap, xStart, yStart) ;

I promised we'd write a DrawBitmap function; here it is:

void DrawBitmap (HDC hdc, HBITMAP hBitmap,
                 short xStart, short yStart)
     {
     BITMAP    bm ;
     HDC       hdcMem ;
     DWORD     dwSize ;
     POINT     ptSize, ptOrg ;

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

     GetObject (hBitmap, sizeof (BITMAP), (LPSTR) &bm) ;

     ptSize.x = bm.bmWidth ;
     ptSize.y = bm.bmHeight ;
     DPtoLP (hdc, &ptSize, 1) ;

     ptOrg.x = 0 ;
     ptOrg.y = 0 ;
     DPtoLP (hdcMem, &ptOrg, 1) ;

     BitBlt (hdc, xStart, yStart, ptSize.x, ptSize.y,
             hdcMem, ptOrg.x, ptOrg.y, SRCCOPY) ;

     DeleteDC (hdcMem) ;
     }

I'm assuming here that you don't want the height or width of the bitmap
stretched or compressed in any way. That is, if your bitmap is 100 pixels
wide, you want it to cover a 100-pixel-wide rectangle of your client area
regardless of the mapping mode.

DrawBitmap first creates a memory device context using CreateCompatibleDC
and then selects the bitmap into it with SelectObject. The mapping mode of
the memory device context is set to the same mapping mode as the video
device context. Because BitBlt works with logical coordinates and logical
sizes and you don't want the bitmap stretched or compressed, the xWidth and
yHeight parameters to BitBlt must be logical units that correspond to the
physical pixel size of the bitmap. For this reason, DrawBitmap gets the
dimensions of the bitmap using GetObject and makes a POINT structure out of
the width and height. It then converts this point to logical coordinates.
This is done similarly for the origin of the bitmap--the point (0, 0) in
device coordinates.

Note that it doesn't matter what brush is currently selected in the
destination device context (hdc), because SRCCOPY doesn't use the brush.


Using Different ROP Codes

SRCCOPY is definitely the most popular dwROP parameter to BitBlt, and you
may be hard-pressed to find uses for the other 255 ROP codes. So I'll give
you a couple of examples in which other ROP codes show their stuff.

In the first example, you have a monochrome bitmap that you want to transfer
to the screen. However, you want to display the bitmap so that the black (0)
bits don't affect anything currently on the client area. Moreover, you want
all the white (1) bits to color the client area with a brush, perhaps a
colored brush created from CreateSolidBrush. How do you do it? I'll assume
that you're working in the MM_TEXT mapping mode and that you want to write
the bitmap starting at the point (xStart, yStart) in your client area. You
already have a handle to the monochrome bitmap (hBitmap) and a handle to the
colored brush (hBrush). You know the width and height of the bitmap and have
them stored in a BITMAP structure named bm. Here's the code:

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

BitBlt (hdc, xStart, yStart, bm.bmWidth, bm.bmHeight,
          hdcMem, 0, 0, 0xE20746L) ;

SelectObject (hdc, hBrush) ;
DeleteDC (hdcMem) ;

BitBlt performs a logical combination of a destination device context (hdc),
a source device context (hdcMem), and the brush currently selected in the
destination device context. So you create a memory device context, select
the bitmap into it, select the colored brush into your client-area display
context, and call BitBlt. Then you select the original brush into your
display device context and delete the memory device context.

The only puzzling part of this code is the ROP code 0xE20746. This ROP code
causes Windows to perform the logical operation:

  ((Destination ^ Pattern) & Source) ^ Destination

Still not obvious? Try this approach: Copy this part of the table on page
620:

ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Pattern:      1  1  1  1  0  0  0  0
Source:       1  1  0  0  1  1  0  0
Destination:  1  0  1  0  1  0  1  0
Result:       ?  ?  ?  ?  ?  ?  ?  ?

For every black bit in the bitmap (which will be selected into the source
memory device context), you want the destination device context to be
unchanged. This means that everywhere the Source is a 0, you want the Result
to be the same bit as the Destination:

ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Pattern:      1  1  1  1  0  0  0  0
Source:       1  1  0  0  1  1  0  0
Destination:  1  0  1  0  1  0  1  0
Result:       ?  ?  1  0  ?  ?  1  0

We're halfway there. For every white bit in the bitmap, you want the
destination device context to be colored with the pattern. The brush you
select in the destination device context is this pattern. So everywhere the
Source is 1, you want the Result to be the Pattern:

ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Pattern:      1  1  1  1  0  0  0  0
Source:       1  1  0  0  1  1  0  0
Destination:  1  0  1  0  1  0  1  0
Result:       1  1  1  0  0  0  1  0

This means that the high word of the ROP code is 0xE2. You can look that up
in the ROP table in Chapter 11 of the Programmer's Reference and find that
the full ROP code is 0xE20746.

Perhaps at this point you discover that you mixed up the white and black
bits when you created the bitmap in SDKPAINT. That's easy to fix. It's
merely a different logical operation:

ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Pattern:      1  1  1  1  0  0  0  0
Source:       1  1  0  0  1  1  0  0
Destination:  1  0  1  0  1  0  1  0
Result:       1  0  1  1  1  0  0  0

Now the high word of the ROP code is 0xB8, and the entire ROP code is
0xB8074A, which performs the logical operation:

  ((Destination ^ Pattern) & Source) ^ Pattern

Here's the second example: Back in Chapter 8, I discussed the two bitmaps
that make up icons and cursors. The use of two bitmaps allows these figures
to be "transparent" in spots or to invert the display surface underneath.
For a monochrome icon or cursor, the two bitmaps are coded as follows:

ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Bitmap 1:  0      0      1       1

Bitmap 2:  0      1      0       1
Result:    Black  White  Screen  Inverse Screen

Windows selects Bitmap 1 into a memory device context and uses BitBlt with a
ROP code called SRCAND to transfer the bitmap to the display. This ROP code
performs the logical operation:

  Destination & Source

In Bitmap 1, the destination is left unchanged for 1 bits and set to 0 for 0
bits. Windows then selects Bitmap 2 into the device context and uses BitBlt
with SRCINVERT. The logical operation is:

  Destination ^ Source

In Bitmap 2, this leaves the destination unchanged for all 0 bits and
inverts the destination for all 1 bits.

Look at the first and second columns of the table: Bitmap 1 with SRCAND
blacks out the bits, and Bitmap 2 with SRCINVERT turns selected bits to
white by inverting the black bits. These operations set the black and white
bits that make up the icon or cursor. Now look at the third and fourth
columns of the table: Bitmap 1 with SRCAND leaves the display unchanged, and
Bitmap 2 with SRCINVERT inverts the colors of selected bits. These
operations let the icon or cursor be transparent or invert the underlying
colors.

Another example of the creative use of ROP codes accompanies the description
of the GrayString function in Chapter 14.


More Fun with Memory Device Contexts

We've been using memory device contexts to transfer an existing bitmap to
the display. You can also use memory device contexts to draw on the surface
of a bitmap. We did this in the GRAFMENU program in Chapter 9, when we used
the GetBitmapFont function to make menu items using bitmaps. First, you
create a memory device context:

hdcMem = CreateCompatibleDC (hdc) ;

Next, you create a bitmap of the desired size. If you want to create a
monochrome bitmap, you can make it compatible with hdcMem:

hBitmap = CreateCompatibleBitmap (hdcMem, xWidth, yHeight) ;

Or to make the bitmap have the same color organization as the video display,
you can make the bitmap compatible with hdc:

hBitmap = CreateCompatibleBitmap (hdc, xWidth, yHeight) ;

You can now select the bitmap into the memory device context:

SelectObject (hdcMem, hBitmap) ;

Now you can draw on this memory device context (and by extension, the
bitmap) using all the GDI functions we discussed in Chapters 11 and 12 and
more that you'll encounter in Chapter 14. When you first create the bitmap,
it contains random bits, so you may want to begin by using the PatBlt
function with a ROP code of WHITENESS or BLACKNESS to erase the background
of the memory device context.

When you're finished drawing on the memory device context, simply delete it:

DeleteDC (hdcMem) ;

Now you're left with a bitmap containing everything you drew on it while it
was selected in the memory device context. (We'll go through this process
again in the BOUNCE program, shown later in this chapter.)

The SCRAMBLE program, shown in Figure 13-1, uses a memory device context as
a temporary holding space for BitBlt operations that swap the contents of
two rectangles of the display.

 SCRAMBLE.MAK

#------------------------
# SCRAMBLE.MAK make file
#------------------------

scramble.exe : scramble.obj scramble.def
     link scramble, /align:16, NUL, /nod slibcew libw, scramble
     rc scramble.exe



scramble.obj : scramble.c
     cl -c -Gsw -Ow -W2 -Zp scramble.c

 SCRAMBLE.C

/*------------------------------------------------
   SCRAMBLE.C -- Scramble (and Unscramble) Screen
                 (c) Charles Petzold, 1990
  ------------------------------------------------*/

#include 
#include 
#define   NUM  200

long FAR PASCAL WndProc (HWND, WORD, WORD, LONG) ;

int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
                    LPSTR lpszCmdLine, int nCmdShow)
     {
     static short nKeep [NUM][4] ;
     HDC          hdc     = CreateDC ("DISPLAY", NULL, NULL, NULL) ;
     HDC          hdcMem  = CreateCompatibleDC (hdc) ;
     short        cxSize  = GetSystemMetrics (SM_CXSCREEN) / 10 ;
     short        cySize  = GetSystemMetrics (SM_CYSCREEN) / 10 ;
     HBITMAP      hBitmap = CreateCompatibleBitmap (hdc, cxSize, cySize) ;
     short        i, j, x1, y1, x2, y2 ;

     SelectObject (hdcMem, hBitmap) ;

     srand (LOWORD (GetCurrentTime ())) ;

     for (i = 0 ; i < 2 ; i++)
          for (j = 0 ; j < NUM ; j++)
               {
               if (i == 0)
                    {
                    nKeep [j] [0] = x1 = cxSize * (rand () % 10) ;
                    nKeep [j] [1] = y1 = cySize * (rand () % 10) ;
                    nKeep [j] [2] = x2 = cxSize * (rand () % 10) ;
                    nKeep [j] [3] = y2 = cySize * (rand () % 10) ;
                    }
               else
                    {
                    x1 = nKeep [NUM - 1 - j] [0] ;
                    y1 = nKeep [NUM - 1 - j] [1] ;
                    x2 = nKeep [NUM - 1 - j] [2] ;
                    y2 = nKeep [NUM - 1 - j] [3] ;
                    }
               BitBlt (hdcMem, 0, 0, cxSize, cySize, hdc,  x1, y1, SRCCOPY)
;
               BitBlt (hdc,  x1, y1, cxSize, cySize, hdc,  x2, y2, SRCCOPY)
;
               BitBlt (hdc,  x2, y2, cxSize, cySize, hdcMem, 0, 0, SRCCOPY)
;
               }
     return FALSE ;
     }

 SCRAMBLE.DEF

;-------------------------------------
; SCRAMBLE.DEF module definition file
;-------------------------------------

NAME           SCRAMBLE

DESCRIPTION    'Screen Scrambler (c) Charles Petzold, 1990'
EXETYPE        WINDOWS
STUB           'WINSTUB.EXE'
CODE           PRELOAD MOVEABLE DISCARDABLE
DATA           PRELOAD MOVEABLE MULTIPLE
HEAPSIZE       1024
STACKSIZE      8192

SCRAMBLE doesn't have a window function. In WinMain, it obtains a device
context for the entire screen:

hdc = CreateDC ("DISPLAY", NULL, NULL, NULL) ;

and also a memory device context:

hdcMem = CreateCompatibleDC (hdc) ;

Then it determines the dimensions of the full screen and divides them by 10:

xSize = GetSystemMetrics (SM_CXSCREEN) / 10 ;
ySize = GetSystemMetrics (SM_CYSCREEN) / 10 ;

The program uses these dimensions to create a bitmap:

hBitmap = CreateCompatibleBitmap (hdc, xSize, ySize) ;

and selects it into the memory device context:

SelectObject (hdcMem, hBitmap) ;

Using the normal C rand ("random number generator") function, SCRAMBLE finds
four random values that are multiples of the xSize and ySize values:

x1 = xSize * (rand () % 10) ;
y1 = ySize * (rand () % 10) ;
x2 = xSize * (rand () % 10) ;
y2 = ySize * (rand () % 10) ;

The program swaps two rectangular blocks of the display through the use of
three BitBlt functions. The first copies the rectangle beginning at point
(x1, y1) into the memory device context:

BitBlt (hdcMem, 0, 0, xSize, ySize, hdc, x1, y1, SRCCOPY) ;

The second copies the rectangle beginning at point (x2, y2) into the
location beginning at (x1, y1):

BitBlt (hdc, x1, y1, xSize, ySize, hdc, x2, y2, SRCCOPY) ;

The third copies the rectangle in the memory device context to the area
beginning at point (x2, y2):

BitBlt (hdc, x2, y2, xSize, ySize, hdcMem, 0, 0, SRCCOPY) ;

This process effectively swaps the contents of the two rectangles on the
display. SCRAMBLE does this 200 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 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), (LPSTR) &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) ;


Color Conversions

If the destination and source device contexts in the BitBlt call have
different color characteristics, Windows must convert the bitmap from one
color format to another. The best color conversion occurs when the source
bitmap is monochrome. Windows uses the text color and background color
attributes in the destination device context for this conversion:

Monochrome DC           Color DC
(Source)                (Destination)
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
0 (Black)               Text color (default is black)

1 (White)               Background color (default is white)


The background color attribute, which you encountered in Chapter 12, is the
color Windows uses to fill in the gaps in dotted and dashed lines and
between the hatches in hatched brushes. You can change the background color
with SetBkColor. The text color, which you'll encounter in Chapter 14,
determines the color of text. You can change this with SetTextColor. With
default settings, the monochrome bitmap simply turns into a black-and-white
bitmap on the color device context.

Translating a bitmap in a color source device context to a monochrome
destination device context is less satisfactory:

Color DC                   Monochrome DC
(Source)                   (Destination)
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Pixel != Background color  0 (Black)

Pixel == Background color  1 (White)


In this case Windows uses the background color of the source device context
to determine what color is translated to white. Every other color is
translated to black.

Here's another color-related problem: Windows needs to equate a particular
combination of color bits in the bitmap (either in different planes or in
the same plane) to the 24-bit color value of the background color. This
means that the color device context must refer to a real device or be a
memory device context based on a real device. For instance, suppose you have
a monochrome device driver. You create a memory device context based on the
screen device context and select a color bitmap into that memory device
context. You now try to transfer that bitmap to a monochrome device context.
It won't work, because Windows doesn't know how the multiple planes or
multiple bits per pixel in the memory device context bitmap relate to real
colors.


Mapping Mode Conversions

The BitBlt call requires different starting coordinates for the source and
destination device contexts, but it needs only one width and one height:

BitBlt (hdcDest, xDest, yDest, xWidth, yHeight,
        hdcSrc,  xSrc,  ySrc,  dwROP) ;

The xWidth and yHeight values are in logical units, and they apply to both
the rectangle in the source device context and the rectangle in the
destination device context. BitBlt must convert all coordinates and sizes to
device coordinates before calling on the driver file to perform the actual
operation. Because the xWidth and yHeight values are used for both the
source and destination device contexts, the values must be converted to
device units (pixels) separately for each device context.

When the source and destination device contexts are the same, or when both
device contexts use the MM_TEXT mapping mode, then the size of this
rectangle in device units will be the same in both device contexts. Windows
can then do a simple pixel-to-pixel transfer. However, when the size of the
rectangle in device units is different in the two device contexts, Windows
turns the job over to the more versatile StretchBlt function.


Stretching Bitmaps with StretchBlt

StretchBlt adds two parameters to the BitBlt call:

StretchBlt (hdcDest, xDest, yDest, xDestWidth, yDestHeight,
            hdcSrc,  xSrc,  ySrc,  xSrcWidth,  ySrcHeight, dwROP) ;

Because StretchBlt accepts different width and height parameters for the
source and destination rectangles, it allows you to stretch or compress a
bitmap in the source device context to fit a larger or smaller area in the
destination device context.

Just as BitBlt provides a superset of PatBlt's functionality, StretchBlt
expands on BitBlt by allowing you to specify the sizes of the source and
destination rectangles separately. As with PatBlt and BitBlt, all
coordinates and values in StretchBlt are in logical units. (We've already
used StretchBlt in two programs: The BLOWUP1 program in Chapter 4 used the
function to copy an area of the display into BLOWUP1's client area; the
GRAFMENU program in Chapter 9 used StretchBlt to expand the size of a bitmap
for use in a menu.)

StretchBlt also allows you to flip an image vertically or horizontally. If
the signs of xSrcWidth and xDestWidth (when converted to device units) are
different, then StretchBlt creates a mirror image: Left becomes right, and
right becomes left. If ySrcHeight and yDestHeight are different, then
StretchBlt turns the image upside down. You can verify this with the BLOWUP1
program by capturing the image starting at the upper right corner (a
negative width), the lower left corner (a negative height), or the lower
right corner (a negative height and width).

If you've experimented with BLOWUP1, you've probably discovered that
StretchBlt can be slow, particularly when it works with a large bitmap.
StretchBlt also has some problems related to the inherent difficulties of
scaling bitmaps. When expanding a bitmap, StretchBlt must duplicate rows or
columns of pixels. If the expansion is not an integral multiple, then the
process can result in some distortion of the image.

When shrinking a bitmap, StretchBlt must combine two or more rows or columns
of pixels into a single row or column. It does this in one of three ways,
depending on the stretching mode attribute in the device context. You can
use the SetStretchBltMode function to change this attribute:

SetStretchBltMode (hdc, nMode) ;

The value of nMode can be one of the following:

  þ   BLACKONWHITE (default)--If two or more pixels have to be combined into
      one pixel, StretchBlt performs a logical AND operation on the pixels.
      The resulting pixel is white only if all the original pixels are
      white, which in practice means that black pixels predominate over
      white pixels.

  þ   WHITEONBLACK--If two or more pixels have to be combined into one
      pixel, StretchBlt performs a logical OR operation. The resulting pixel
      is black only if all the original pixels are black, which means that
      white pixels predominate.

  þ   COLORONCOLOR--StretchBlt simply eliminates rows or columns of pixels
      without doing any logical combination. This is often the best approach
      for color bitmaps, because the other two modes can cause color
      distortions.


Animation

I mentioned at the beginning of Chapter 11 that GDI supports only static
pictures. Although it's true that Windows has no traditional animation
support (such as the ability to flip video pages, check for vertical retrace
of the video signal, or construct rotatable sprites), that doesn't mean that
we can't move images around on the display. Yes, it's time for the bouncing
ball program. The BOUNCE program, shown in Figure 13-2 beginning on the
following page, constructs a ball that bounces around in the window's client
area. The program uses the timer to pace the ball; it draws the ball with a
simple "bit-block transfer" from a memory device context.

 BOUNCE.MAK

#----------------------
# BOUNCE.MAK make file
#----------------------

bounce.exe : bounce.obj bounce.def
     link bounce, /align:16, NUL, /nod slibcew libw, bounce
     rc bounce.exe

bounce.obj : bounce.c
     cl -c -Gsw -Ow -W2 -Zp bounce.c

 BOUNCE.C

/*---------------------------------------
   BOUNCE.C -- Bouncing Ball Program
               (c) Charles Petzold, 1990
  ---------------------------------------*/

#include 

long FAR PASCAL WndProc (HWND, WORD, WORD, LONG) ;

int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
                    LPSTR lpszCmdLine, int nCmdShow)
     {
     static char szAppName [] = "Bounce" ;
     HWND        hwnd ;
     MSG         msg ;
     WNDCLASS    wndclass ;

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

          RegisterClass (&wndclass) ;
          }



     hwnd = CreateWindow (szAppName, "Bouncing Ball",
                          WS_OVERLAPPEDWINDOW,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          NULL, NULL, hInstance, NULL) ;

     if (!SetTimer (hwnd, 1, 50, NULL))
          {
          MessageBox (hwnd, "Too many clocks or timers!",
                      szAppName, MB_ICONEXCLAMATION | MB_OK) ;
          return FALSE ;
          }

     ShowWindow (hwnd, nCmdShow) ;
     UpdateWindow (hwnd) ;

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

long FAR PASCAL WndProc (HWND hwnd, WORD message, WORD wParam, LONG lParam)
     {
     static HANDLE hBitmap ;
     static short  cxClient, cyClient, xCenter, yCenter, cxTotal, cyTotal,
                   cxRadius, cyRadius, cxMove, cyMove, xPixel, yPixel ;
     HBRUSH        hBrush ;
     HDC           hdc, hdcMem ;
     short         nScale ;

     switch (message)
          {
          case WM_CREATE :
               hdc = GetDC (hwnd) ;
               xPixel = GetDeviceCaps (hdc, ASPECTX) ;
               yPixel = GetDeviceCaps (hdc, ASPECTY) ;
               ReleaseDC (hwnd, hdc) ;
               return 0 ;

          case WM_SIZE :
               xCenter = (cxClient = LOWORD (lParam)) / 2 ;
               yCenter = (cyClient = HIWORD (lParam)) / 2 ;

               nScale = min (cxClient * xPixel, cyClient * yPixel) / 16 ;

               cxRadius = nScale / xPixel ;
               cyRadius = nScale / yPixel ;
               cxMove = max (1, cxRadius / 4) ;
               cyMove = max (1, cyRadius / 4) ;

               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, 1) ;
               PostQuitMessage (0) ;
               return 0 ;
          }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
     }

 BOUNCE.DEF

;-----------------------------------
; BOUNCE.DEF module definition file
;-----------------------------------

NAME           BOUNCE

DESCRIPTION    'Bouncing Ball Program (c) Charles Petzold, 1990'
EXETYPE        WINDOWS
STUB           'WINSTUB.EXE'
CODE           PRELOAD MOVEABLE DISCARDABLE
DATA           PRELOAD MOVEABLE MULTIPLE
HEAPSIZE       1024
STACKSIZE      8192
EXPORTS        WndProc

BOUNCE reconstructs the ball whenever the program gets a WM_SIZE message,
the diameter of the ball being 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 quarter of the ball's
radius.

After the bitmap is selected into a memory device context, it is colored
white:

Rectangle (hdcMem, -1, -1, xTotal + 1, yTotal + 1) ;

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 simple BitBlt call using the ROP code of SRCCOPY:

BitBlt (hdc, xCenter - xTotal / 2,
             yCenter - yTotal / 2, xTotal, yTotal,
             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.



METAFILES

A metafile is a collection of GDI functions that are encoded in a binary
form. You create a metafile by first creating a metafile device context. You
can then use most of the GDI drawing functions to draw on this metafile
device context. These GDI calls don't really draw on anything, however.
Instead, they are stored within the metafile. When you close the metafile
device context, you get back a handle to the metafile. You can then "play"
this metafile on a real device context and execute the GDI functions in the
metafile.

Metafiles are used most often for sharing pictures between programs through
the clipboard. Because metafiles describe a picture as a collection of GDI
calls, they take up much less space and are more device independent than
bitmaps. I'll begin the discussion of metafiles with some simple examples,
and then I'll take up the more theoretical considerations.

Simple Use of Memory Metafiles

Suppose your company's logo consists of a rectangle with lines drawn between
the opposing corners and a blue circle in the center. You need to draw this
logo often on the client area of your programs' windows and on the printer.
Let's make that logo a metafile.

We'll begin by defining a few necessary variables:

static HANDLE  hmf ;
HANDLE         hBrush ;
HDC            hdcMeta ;

During processing of the WM_CREATE message, you can create the metafile. You
call CreateMetaFile to obtain a handle to a metafile device context:

hMetaDC = CreateMetaFile (NULL) ;

The NULL parameter indicates that this will be a "memory" metafile; that is,
the metafile will be stored in memory rather than as a disk file.

You can now draw your logo on this metafile device context. You decide you
want it to be 100 units high and 100 units wide:

Rectangle (hdcMeta, 0, 0, 100, 100) ;
MoveTo (hdcMeta, 0, 0) ;
LineTo (hdcMeta, 100, 100) ;
MoveTo (hdcMeta, 0, 100) ;
LineTo (hdcMeta, 100, 0) ;

hBrush = CreateSolidBrush (RGB (0, 0, 255)) ;
SelectObject (hdcMeta, hBrush) ;
Ellipse (hdcMeta, 20, 20, 80, 80) ;

When you're finished drawing, you close the metafile device context by
calling CloseMetaFile, which returns a handle to the metafile:

hmf = CloseMetaFile (hdcMeta) ;

Now you can delete the brush you created:

DeleteObject (hBrush) ;

You're done creating the metafile. The hmf variable is defined as static, so
it will remain in existence during other messages.

You drew the logo with a height and width of 100. Are these logical units or
device units? At this point, they are neither: They are simply units. They
will take on meaning only when you play the metafile. Let's do so. During
processing of your WM_PAINT message, you may want to fill up the client area
with 100 logos, and you don't care whether they get stretched out somewhat.
We'll assume that you've obtained values of cxClient and cyClient,
representing the width and height of the client area, and that you've
defined some of the other variables used in this code.

You obtain a handle to the client-area device context and set a mapping mode
of MM_ANISOTROPIC with 1000 logical units horizontally and vertically:

hdc = BeginPaint (hwnd, &ps) ;

SetMapMode (hdc, MM_ANISOTROPIC) ;
SetWindowExt (hdc, 1000, 1000) ;
SetViewportExt (hdc, cxClient, cyClient) ;

Now you can "play the metafile" 100 times by calling PlayMetaFile, each time
changing the window origin to move the metafile to a new position:

for (x = 0 ; x < 10 ; x++)
     for (y = 0 ; y < 10 ; y++)
          {
          SetWindowOrg (hdc, -100 * x, -100 * y) ;
          PlayMetaFile (hdc, hmf) ;
          }

In calling PlayMetaFile, you're in effect repeating all the calls that you
made between CreateMetaFile and CloseMetaFile when you originally created
the metafile during the WM_CREATE message. The results are shown in Figure
13-3.

When you're finished processing the WM_PAINT message, you can end it
normally:

EndPaint (hwnd, &ps) ;

One task remains. When you create a metafile, the handle is really the
property of the GDI module, and you must explicitly delete it with
DeleteMetaFile before you terminate the program. You can do this during
processing of the WM_DESTROY message:

case WM_DESTROY :
     DeleteMetaFile (hmf) ;
     PostQuitMessage (0) ;
     return 0 ;

  (Figure 13-3. may be found in the printed book.)


Storing Metafiles on Disk

In the above example, the NULL parameter to CreateMetaFile meant that we
wanted to create a metafile stored in memory. We can also create a metafile
stored on the disk as a normal file. This method is preferred for large
metafiles because it uses less memory space. Windows has to maintain a
relatively small area in memory to store the name of the file containing the
metafile. On the other hand, a metafile stored on disk requires a disk
access every time you play it.

Let's use the example of the company logo again. In addition to the
variables shown above, you'll need a variable to store the filename of the
metafile:

static char szFileName [80] ;

In this example, we'll use a temporary file. During processing of the
WM_CREATE message, you can create a filename for a temporary file using the
Windows GetTempFileName function:

GetTempFileName (0, MF, 0, szFileName) ;

Windows first checks the TEMP variable in the MS-DOS environment to select a
disk and subdirectory for this file. If there is no TEMP variable in the
MS-DOS environment, Windows uses the root directory of the first fixed disk.
The filename begins with a tilde (~) followed by the characters we've
specified in the GetTempFileName function (MF) and a unique number; the
extension is .TMP. On return from the call, the filename is stored in the
szFileName array.

We create the metafile device context using this filename:

hMetaDC = CreateMetaFile (szFileName) ;

We can write to this device context just as we did in the original example
and then close the metafile device context to get the metafile handle:

hmf = CloseMetaFile (hdcMeta) ;

The processing of the WM_PAINT message is the same as in the original
example. However, during processing of the WM_DESTROY message, you'll have
to add something. The statement:

DeleteMetaFile (hmf) ;

deletes the area of memory that references the metafile handle to the disk
file, but the disk file still exists. You should also delete that file using
the normal C function:

unlink (szFileName) ;

Here's another way to use disk-based metafiles. This method doesn't require
that you maintain hmf as a static variable. First, you get a temporary
filename and create the metafile device context as before:

GetTempFileName (0, MF, 0, szFileName) ;
hdcMeta = CreateMetaFile (szFileName) ;

Now you draw on the metafile device context. When you're finished, you can
close the device context and get a handle to the metafile:

hmf = CloseMetaFile (hdcMeta) ;

But now you also delete the metafile:

DeleteMetaFile (hmf) ;

Do we really want to do this? We might. Deleting a disk-based metafile
invalidates the metafile handle, freeing the memory required for the
metafile but leaving the disk file intact. During processing of the WM_PAINT
message, you can get a metafile handle to this disk file by calling
GetMetaFile:

hmf = GetMetaFile (szFileName) ;

Now you can play this metafile just as before. When processing of the
WM_PAINT message is over, you can delete the metafile handle:

DeleteMetaFile (hmf) ;

When it comes time to process the WM_DESTROY message, you don't have to
delete the metafile, because it was deleted at the end of the WM_CREATE
message and at the end of each WM_PAINT message. But you still should delete
the disk file:

unlink (szFileName) ;


Using Preexisting Metafiles

What we've done in the last example above seems to imply that we can create
a disk-based metafile in one program and then use it in another program by
calling GetMetaFile. We can. The MFCREATE ("metafile create") program, shown
in Figure 13-4, is the shortest Windows program in this book. All it does is
create a disk-based metafile with the name MYLOGO.WMF. The .WMF extension
stands for "Windows metafile" and is the customary extension for a metafile
stored as a disk file.

 MFCREATE.MAK

#------------------------
# MFCREATE.MAK make file
#------------------------

mfcreate.exe : mfcreate.obj mfcreate.def
     link mfcreate, /align:16, NUL, /nod slibcew libw, mfcreate
     rc mfcreate.exe

mfcreate.obj : mfcreate.c
     cl -c -Gsw -Ow -W2 -Zp mfcreate.c


 MFCREATE.C

/*-----------------------------------------
   MFCREATE.C -- Metafile Creation Program
                 (c) Charles Petzold, 1990
  -----------------------------------------*/

#include 

int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
                    LPSTR lpszCmdLine, int nCmdShow)
     {
     HBRUSH hBrush  = CreateSolidBrush (RGB (0, 0, 255)) ;
     HDC    hdcMeta = CreateMetaFile ("MYLOGO.WMF") ;

     Rectangle (hdcMeta, 0, 0, 100, 100) ;
     MoveTo (hdcMeta, 0, 0) ;
     LineTo (hdcMeta, 100, 100) ;
     MoveTo (hdcMeta, 0, 100) ;
     LineTo (hdcMeta, 100, 0) ;
     SelectObject (hdcMeta, hBrush) ;
     Ellipse (hdcMeta, 20, 20, 80, 80) ;

     DeleteMetaFile (CloseMetaFile (hdcMeta)) ;
     DeleteObject (hBrush) ;

     MessageBeep (0) ;

     return FALSE ;
     }

 MFCREATE.DEF

;-------------------------------------
; MFCREATE.DEF module definition file
;-------------------------------------

NAME           MFCREATE

DESCRIPTION    'Metafile Creation Program (c) Charles Petzold, 1990'
EXETYPE        WINDOWS
STUB           'WINSTUB.EXE'
CODE           PRELOAD MOVEABLE DISCARDABLE
DATA           PRELOAD MOVEABLE MULTIPLE
HEAPSIZE       1024
STACKSIZE      8192

In the WinMain function, MFCREATE creates a metafile device context using
the filename MYLOGO.WMF:

hMetaDC = CreateMetaFile ("MYLOGO.WMF") ;

It then draws on this device context. When it's finished, it closes the
metafile device context and deletes the metafile handle in one statement:

DeleteMetaFile (CloseMetaFile (hdcMeta)) ;

The program beeps to indicate that it's finished and then exits WinMain.

Now you can use this metafile in another program. Here's the entire WM_PAINT
logic. All you need to obtain the handle to the disk-based metafile is
GetMetaFile. When you're done with the metafile, you call DeleteMetaFile:

hdc = BeginPaint (hwnd, &ps) ;

SetMapMode (hdc, MM_ANISOTROPIC) ;
SetWindowExt (hdc, 1000, 1000) ;
SetViewportExt (hdc, xClient, yClient) ;

hmf = GetMetaFile ("MYLOGO.WMF") ;

for (x = 0 ; x < 10 ; x++)
     for (y = 0 ; y < 10 ; y++)
          {
          SetWindowOrg (hdc, -100 * x, -100 * y) ;
          PlayMetaFile (hdc, hmf) ;
          }

DeleteMetaFile (hmf) ;
EndPaint (hwnd, &ps) ;

Alternatively, you can define hmf as a static variable and call GetMetaFile
once during processing of WM_CREATE and call DeleteMetaFile during
processing of WM_DESTROY. Of course, this approach has some problems. The
code assumes that MYLOGO.WMF is in the current directory or a directory
listed in the PATH environment variable. If the file isn't in the current
directory when you call GetMetaFile, Windows will display a message box
asking the user to insert the MYLOGO.WMF disk in drive A. (The usual
response of a user  to a message box of this sort is "Huh?") You should
search for the file before you call GetMetaFile.

Now let's try another approach.


Using Metafiles as Resources

In Chapter 8 you encountered a "user-defined resource," which in that case
was a block of text. Now let's transform a metafile into a user-defined
resource. MYLOGO.WMF will then become part of the .EXE file for the program
that needs it. The program MFRESORC ("metafile resource"), shown in Figure
13-5, accomplishes this using the MYLOGO.WMF metafile created by MFCREATE.

 MFRESORC.MAK

#------------------------
# MFRESORC.MAK make file
#------------------------

mfresorc.exe : mfresorc.obj mfresorc.def mfresorc.res
     link mfresorc, /align:16, NUL, /nod slibcew libw, mfresorc
     rc mfresorc.res

mfresorc.obj : mfresorc.c
     cl -c -Gsw -Ow -W2 -Zp mfresorc.c

mfresorc.res : mfresorc.rc mylogo.wmf
     rc -r mfresorc.rc

 MFRESORC.C

/*-----------------------------------------
   MFRESORC.C -- Metafile Resource Program
                 (c) Charles Petzold, 1990
  -----------------------------------------*/

#include 

long FAR PASCAL WndProc (HWND, WORD, WORD, LONG) ;

int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
                    LPSTR lpszCmdLine, int nCmdShow)
     {
     static char szAppName [] = "MFResorc" ;
     HWND        hwnd ;
     MSG         msg ;
     WNDCLASS    wndclass ;

     if (!hPrevInstance)
          {
          wndclass.style         = CS_HREDRAW | CS_VREDRAW ;
          wndclass.lpfnWndProc   = WndProc ;
          wndclass.cbClsExtra    = 0 ;
          wndclass.cbWndExtra    = 0 ;

          wndclass.hInstance     = hInstance ;
          wndclass.hIcon         = NULL ;
          wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
          wndclass.hbrBackground = GetStockObject (WHITE_BRUSH) ;
          wndclass.lpszMenuName  = NULL ;
          wndclass.lpszClassName = szAppName ;

          RegisterClass (&wndclass) ;
          }

     hwnd = CreateWindow (szAppName, "Metafile Resource Program",
                          WS_OVERLAPPEDWINDOW,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          NULL, NULL, hInstance, NULL) ;

     ShowWindow (hwnd, nCmdShow) ;
     UpdateWindow (hwnd) ;

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

long FAR PASCAL WndProc (HWND hwnd, WORD message, WORD wParam, LONG lParam)
     {
     static HANDLE hmf ;
     static short  cxClient, cyClient ;
     HANDLE        hInstance, hResource ;
     HDC           hdc ;
     PAINTSTRUCT   ps ;
     short         x, y ;

     switch (message)
          {
          case WM_CREATE :
               hInstance = ((LPCREATESTRUCT) lParam) -> hInstance ;
               hResource = LoadResource (hInstance,
                           FindResource (hInstance, "MyLogo", "METAFILE")) ;

               LockResource (hResource) ;
               hmf = SetMetaFileBits (hResource) ;
               UnlockResource (hResource) ;
               return 0 ;
          case WM_SIZE :
               cxClient = LOWORD (lParam) ;
               cyClient = HIWORD (lParam) ;
               return 0 ;

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

               SetMapMode (hdc, MM_ANISOTROPIC) ;
               SetWindowExt (hdc, 1000, 1000) ;
               SetViewportExt (hdc, cxClient, cyClient) ;

               for (x = 0 ; x < 10 ; x++)
                    for (y = 0 ; y < 10 ; y++)
                         {
                         SetWindowOrg (hdc, -100 * x, -100 * y) ;
                         PlayMetaFile (hdc, hmf) ;
                         }

               EndPaint (hwnd, &ps) ;
               return 0 ;

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

 MFRESORC.RC

/*-----------------------------
   MFRESORC.RC resource script
  -----------------------------*/

MyLogo METAFILE "mylogo.wmf"

The resource script is only one line. METAFILE may look like a normal
resource script keyword such as MENU or DIALOG, but it isn't. We're defining
this resource type. The name we give to this particular resource of the
METAFILE type is "MyLogo."

 MFRESORC.DEF

;-------------------------------------
; MFRESORC.DEF module definition file
;-------------------------------------

NAME           MFRESORC

DESCRIPTION    'Metafile Resource Program (c) Charles Petzold, 1990'
EXETYPE        WINDOWS
STUB           'WINSTUB.EXE'
CODE           PRELOAD MOVEABLE DISCARDABLE
DATA           PRELOAD MOVEABLE MULTIPLE
HEAPSIZE       1024
STACKSIZE      8192
EXPORTS        WndProc

During processing of the WM_CREATE message, MFRESORC must first call
FindResource and LoadResource to obtain a handle to the resource:

hResource = LoadResource (hInstance,
            FindResource (hInstance, "MyLogo", "METAFILE")) ;

Then the resource is locked:

LockResource (hResource) ;

Normally, you would lock a resource to obtain a pointer to the memory block.
However, LockResource also performs the chore of actually loading the
resource into memory. That's all we need to do. Now we can convert this
global memory block to a metafile using SetMetaFileBits, and the resource
can be unlocked:

hmf = SetMetaFileBits (hResource) ;

GlobalUnlock (hResource) ;

SetMetaFileBits has a companion function, GetMetaFileBits, that converts a
metafile handle to a global memory handle. GetMetaFileBits can be used only
with a memory metafile.

The metafile that we loaded as a resource is a memory metafile. If you'd
prefer to use a disk-based metafile, you can copy it. This is the code you
would use following the GlobalUnlock statement:

GetTempFileName (0, MF, 0, szFileName) ;
hmf2 = CopyMetaFile (hmf, szFileName) ;
DeleteMetaFile (hmf) ;
hmf = hmf2 ;

The hmf2 handle need not be defined as a static variable. The MFRESORC
program shows the deletion of the metafile handle during processing of
WM_DESTROY:

DeleteMetaFile (hmf) ;

You should also delete the temporary file:

unlink (szFileName) ;

The CopyMetaFile function can also be used to copy an existing metafile to a
memory metafile:

hmf2 = CopyMetaFile (hmf, NULL) ;


Looking at Metafiles

You can get a good idea of what a metafile is and is not by dumping out the
contents of MYLOGO.WMF. The metafile begins with an 18-byte header record.
This is followed by a series of metafile records, each of which contains
three or more 2-byte words. The first word is the number of words in the
record, including the first; the second word is 0; the third word is a code
that indicates the GDI call that the record represents.

These codes are documented in Chapter 9 of the Programmer's Reference and in
WINDOWS.H with identifiers that begin with the word META. The low byte of
this word identifies the particular GDI call; the high byte is generally the
number of words that are parameters to the call. The words that follow this
code are the actual parameters to the call in reverse order, excluding the
hdc parameter. For instance, the GDI call:

Rectangle (hdcMeta, 0, 0, 100, 100) ;

shows up in the metafile as a seven-word record. In hexadecimal, these words
are:

  0007 0000 041B 0064 0064 0000 0000

The 041B word means that the call is Rectangle with four parameters,
excluding the initial hdc. The parameters follow.

The only real exception to this rule is the SelectObject call. Windows must
save the object that the function is selecting into the metafile device
context. For instance, the call:

hBrush = CreateSolidBrush (RGB (0, 0, 255)) ;

doesn't affect the metafile device context at all. You can even make this
call before you create the metafile device context with CreateMetaFile. When
you select that brush into the metafile device context, however, two records
must be generated. The first is a record for CreateBrushIndirect:

  0007 0000 FC02 0000 0000 00FF 0000

The four words following the identifying code FC02 are the elements of the
LOGBRUSH structure in the same order as the structure (lbStyle first).

The call:

SelectObject (hdcMeta, hBrush) ;

is coded in this record:

  0004 0000 012D 0000

The single parameter 0000 indicates that it's a handle to the first object
created in the metafile. Any following SelectObject calls with other GDI
objects will have sequentially increasing parameters.

For a memory-based metafile, these records are stored in a global memory
block. (You can obtain the records using the EnumMetaFile function.) For a
disk-based metafile, the records are stored in a disk file. The handle to a
disk-based metafile references a small global memory block that contains the
drive, the directory, and the name of the file.


Metafile Dos and Don'ts

When you play the metafile, Windows breaks down the metafile into records
and executes the appropriate functions using the parameters in each record.
From the format of the metafile records, some facts should be fairly obvious
(and some not so obvious).

The metafile device context is not a true device context. It doesn't
correspond to an actual device or even to a block of memory like the memory
device context. It's simply a repository for GDI calls you make using the
hdcMeta device context handle.

The metafile device context doesn't have any default device context
attributes. It uses whatever device context attributes are in effect when
you play the metafile.

All parameters enter the metafile device context as numbers. For instance,
if the width and height of your client area are stored in xClient and
yClient, and you call:

Rectangle (hdcMeta, 0, 0, xClient / 2, yClient / 2) ;

then the actual calculated values of xClient / 2 and yClient / 2 will enter
the metafile. If you later play back that metafile, it will draw a rectangle
based on these calculated values regardless of the current size of the
client area.

If you change the mapping mode of your screen device context before you play
the metafile, the coordinates in the metafile will be interpreted based on
the newly chosen mapping mode (unless the metafile itself changes the
mapping mode).

The only calls that go into the metafile are those that take a handle to a
device context as the first parameter. Many GDI calls are not allowed in a
metafile. It's easier to say what functions cannot be used with a metafile
device context, because they fall into several categories:

  þ   Any function that begins with the word Get, including GetDeviceCaps
      and GetTextMetrics. The metafile can do nothing with the information
      that these functions return.

  þ   Any other function designed to return information to the program:
      RectVisible, PtVisible, EnumFonts, EnumObjects, DPtoLP, and LPtoDP.
      The Escape function (which you'll encounter in Chapter 16) is
      supported only for calls that don't return data.

  þ   Any function that treats the metafile device context as if it were an
      actual device context: ReleaseDC, DeleteDC, CreateCompatibleDC,
      CreateCompatibleBitmap, CreateDiscardableBitmap, and PlayMetaFile.

  þ   Some of the more complex GDI functions: GrayString, DrawIcon, and
      SetBrushOrg.

  þ   Two functions that require handles to brushes: FillRect and FrameRect.

As I indicated above, SelectObject works a little differently for metafile
device contexts. First, it doesn't return the handle of the object
previously selected in the device context. When you use SelectObject with a
metafile device context, the function returns a nonzero if it is successful
and 0 otherwise. You can't use the construction:

DeleteObject (SelectObject (hdcMeta,  . . . )) ; // WRONG !!!

For SelectObject, the metafile also stores a description of the logical
object that you are selecting into the device context. When you play the
metafile, Windows starts with the pen, brush, font, and region currently
selected in the device context. For SelectObject calls, it creates the
indicated object and selects it into the device context but saves the
original object. When it is done playing the metafile, Windows restores the
original objects and deletes all the objects it created to play the
metafile.

When you play a metafile, it uses the device context attributes currently in
effect. The metafile can change any of these attributes, including the
mapping mode, the text color, the drawing mode, and so forth. These changes
remain in effect for the device context af- ter the metafile has finished
playing. If you want to retain your original device context attributes after
the metafile has finished playing, call:

SaveDC (hdc) ;

before you play the metafile and:

RestoreDC (hdc, -1) ;

after the PlayMetaFile call. The metafile itself can also save and restore
the device context while it is playing. Each SaveDC call must be balanced by
a RestoreDC call with a -1 parameter.

One of the purposes of metafiles is to provide a format for
device-independent pictures that can be shared by applications. There are
some other considerations involved with using metafiles with the clipboard
that will be discussed in Chapter 16.