Chapter 12  Drawing Graphics
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ

Some theoretical discussions of computer graphics assume that you are
supplied with only two graphics primitives--a "write pixel" routine and a
"read pixel" routine. In theory, you can do anything you want with these two
functions. Drawing a line, for instance, simply requires that you call the
"write pixel" routine numerous times, adjusting the x- and y- coordinates
appropriately.

In reality, you can indeed do anything you want with only "write pixel" and
"read pixel" routines--if you don't mind waiting for the results. It is much
more efficient for a graphics system to do line drawing and other complex
graphics operations at the level of the device driver, which can have its
own optimized code to perform the operations. Moreover, as video display
technology becomes more sophisticated, the adapter boards will contain
graphics coprocessors that allow the video hardware itself to draw the
figures.

But of course, no graphics language would be complete without routines to
draw one pixel at a time, and that's where we'll begin. From there we'll
proceed to drawing lines, and then we'll tackle bounded areas.

DRAWING POINTS

You can draw a pixel of a particular color with the GDI SetPixel function:

rgbActualColor = SetPixel (hdc, x, y, rgbColor) ;

The rgbColor parameter is an unsigned long integer (32 bits) where the
lowest 3 bytes represent the density of red, green, and blue. You can
construct this color value using the RGB macro:

rgbColor = RGB (byRed, byGreen, byBlue) ;

(Chapters 5 and 6 contain a more extensive discussion of Windows' use of
color.)

Although x and y are logical coordinates, SetPixel colors only a single
physical pixel regardless of the mapping mode. Because SetPixel draws only a
single pixel, the use of a dithered color (a color that combines pixels of
various pure colors) is meaningless. For this reason, Windows translates the
rgbColor parameter to a pure nondithered color and returns that color.

SetPixel is almost never used in Windows programs, but that didn't prevent
us from using it in the CONNECT program in Chapter 4. You can obtain the
color of a particular pixel this way:

rgbColor = GetPixel (hdc, x, y) ;


DRAWING LINES

After drawing points, the next step up is drawing lines. Windows can draw
straight lines and elliptical lines. An elliptical line is a curved line on
the circumference of an ellipse. The three functions that draw lines are
LineTo (straight lines), PolyLine (series of connected lines), and Arc
(elliptical lines). Five attributes of the device context affect the
appearance of lines that you draw using these functions: current pen
position (for LineTo only), pen, background mode (for nonsolid pens),
background color (for the OPAQUE background mode), and drawing mode.

The LineTo function is one of the few GDI functions that does not include
the full dimensions of the object to be drawn. Instead, LineTo draws a line
from the current pen position defined in the device context up to (but not
including) the logical point specified in the LineTo function. In the
default device context, the current pen position is initially set at the
logical point (0, 0). If you call LineTo without first setting the current
pen position (or the viewport or window origin), it draws a line starting at
the upper left corner of the client area.

To draw a line from the logical point (xStart, yStart) to the logical point
(xEnd, yEnd), you first must use MoveTo to set the current pen position to
the point (xStart, yStart):

MoveTo (hdc, xStart, yStart) ;

MoveTo doesn't draw anything. It simply changes the current pen position.
You can then use LineTo to draw the line:

LineTo (hdc, xEnd, yEnd) ;

This draws the line up to (but not including) the point (xEnd, yEnd).
Following the LineTo call, the current pen position is set to (xEnd, yEnd).

LineTo is the only Windows function that uses the current pen position.
MoveTo and LineTo are the only functions that change it. You can obtain the
current pen position by calling:

dwPoint = GetCurrentPosition (hdc) ;

The dwPoint return value is an unsigned long (or doubleword) that contains
the x- coordinate in the low word and the y-coordinate in the high word. You
can use the LOWORD and HIWORD macros to extract the two coordinates, or you
can convert the value of dwPoint to a POINT structure using the MAKEPOINT
macro:

point = MAKEPOINT (dwPoint) ;

The following code draws a grid in the client area of a window, spacing the
lines 1 inch apart starting from the upper left corner. The variable hwnd is
assumed to be a handle to the window, hdc is a handle to the device context,
rect is a structure of type RECT, and x and y are short integers:

SetMapMode (hdc, MM_LOENGLISH) ;
GetClientRect (hwnd, &rect) ;
DPtoLP (hdc, (LPPOINT) &rect, 2) ;

for (x = 0 ; x < rect.right ; x += 100)
     {
     MoveTo (hdc, x, 0) ;
     LineTo (hdc, x, rect.bottom) ;
     }

for (y = 0 ; y > rect.bottom ; y -= 100)
     {
     MoveTo (hdc, 0, y) ;
     LineTo (hdc, rect.right, y) ;
     }

The dimensions of the client area are saved in the RECT structure called
rect and converted to logical points with DPtoLP. After the DPtoLP
conversion, rect.right is the width of the client area in units of 0.01
inch, and rect.bottom is the negative height of the client area. Notice that
y is decremented rather than incremented in the second for loop because the
MM_LOENGLISH mapping mode uses decreasing values of y as you move down the
display.

Although it may seem like a nuisance to be forced to use two functions to
draw a single line, the current pen position attribute comes in handy when
you want to draw a series of connected lines. For instance, you might want
to define an array of 5 points (10 values) that draw the outline of a
rectangle:

POINT pt [5] = { 100, 100, 200, 100, 200, 200,
                 100, 200, 100, 100 } ;

Notice that the last point is the same as the first. Now you need only use
MoveTo for the first point and LineTo for the successive points:

MoveTo (hdc, pt[0].x, pt[0].y) ;

for (i = 1 ; i < 5 ; i++)
     LineTo (hdc, pt[i].x, pt[i].y) ;

Because LineTo draws from the current point up to (but not including) the
point in the LineTo function, no coordinate gets written twice by this code.
While overwriting points is not a problem with a display, it might not look
good on a plotter or with some drawing modes (to be covered shortly).

When you have an array of points that you want connected with lines, you can
draw the lines more easily using the PolyLine function. This statement draws
the same rectangle as in the code shown above:

PolyLine (hdc, &pt, 5) ;

The last parameter is the number of points. We could also have represented
this value by (sizeofpt / sizeof (POINT)). PolyLine has the same effect as
an initial MoveTo function followed by multiple LineTo functions. However,
PolyLine doesn't use or change the current pen position.

The Arc function is a little more complex. Here's the general syntax:

Arc (hdc, xLeft, yTop, xRight, yBottom,
          xStart, yStart, xEnd, yEnd) ;

The Arc function draws a line on the circumference of an ellipse that is
bounded by a rectangle with the upper left corner at (xLeft, yTop) and the
lower right corner at (xRight, yBottom). The arc starts at the intersection
of the ellipse and the line connecting (xStart, yStart) with the center of
the ellipse. The arc is drawn counterclockwise around the circumference of
the ellipse and ends at the intersection of the ellipse and the line
connecting point (xEnd, yEnd) with the center of the ellipse. If you're
having trouble visualizing this, don't worry about it: I'll discuss the Arc
function in much more detail after we've covered rectangles and ellipses.

Using Stock Pens

When you call LineTo, PolyLine, or Arc, Windows uses the "pen" currently
selected in the device context to draw the line. The pen determines the
line's color, its width, and its style, which can be solid, dotted, or
dashed. The pen in the default device context is called BLACK_PEN. This pen
draws a solid black line with a width of one pixel regardless of the mapping
mode. BLACK_PEN is one of three "stock pens" that Windows provides. The
other two are WHITE_PEN and NULL_PEN. NULL_PEN is a pen that doesn't draw.
You can also create your own customized pens.

In your Windows programs, you refer to pens with a handle. WINDOWS.H
includes a type definition named HPEN, a handle to a pen. You can define a
variable (for instance, hPen) using this type definition:

HPEN hPen ;

You obtain the handle to one of the stock pens by a call to GetStockObject.
For instance, suppose you want to use the stock pen called WHITE_PEN. You
get the pen handle like this:

hPen = GetStockObject (WHITE_PEN) ;

Now you must make that pen the currently selected pen in the device context,
which requires a call to SelectObject:

SelectObject (hdc, hPen) ;

After this call, the lines you draw using LineTo, PolyLine, or Arc will use
WHITE_PEN until you select another pen into the device context or release
the device context.

Rather than explicitly defining an hPen variable, you can instead combine
the GetStockObject and SelectObject calls in one statement:

SelectObject (hdc, GetStockObject (WHITE_PEN)) ;

If you then want to return to using BLACK_PEN, you can get the handle to
that stock object and select it into the device context in one statement:

SelectObject (hdc, GetStockObject (BLACK_PEN)) ;

SelectObject returns the handle to the pen that had been previously selected
into the device context. If you start off with a fresh device context and
call:

hPen = SelectObject (hdc, GetStockObject (WHITE_PEN)) ;

then the current pen in the device context will be WHITE_PEN, and the
variable hPen will be the handle to BLACK_PEN. You can then select BLACK_PEN
into the device context by calling:

SelectObject (hdc, hPen) ;


Creating, Selecting, and Deleting Pens

Although the pens defined as stock objects are certainly convenient, you are
limited to only a solid black pen, a solid white pen, or no pen at all. If
you want to get fancier than that, you must create your own pens. Here's the
general procedure: You create a "logical pen," which is merely the
description of a pen, using the function CreatePen or CreatePenIndirect.
These functions return a handle to the logical pen. You select the pen into
the device context by calling SelectObject. You can then draw lines with
this new pen. Only  one pen can be selected into the device context at any
one time. After you release the device context (or after you select another
pen into the device context), you can delete the logical pen you've created
by calling DeleteObject. When you do so, the handle to the pen is no longer
valid.

A logical pen is a "GDI object." You create and use the pen, but the pen
doesn't belong to your program. The pen really belongs to the GDI module. A
pen is one of six GDI objects that you can create. The other five are
brushes, bitmaps, regions, fonts, and palettes.

This brings me to a very important point: Normally, Windows cleans up
thoroughly when a program terminates. The one big exception is for GDI
objects. When a program terminates, Windows doesn't automatically delete GDI
objects that the program has created. The program itself is responsible for
deleting GDI objects.

Three rules govern the use of GDI objects such as pens:

  þ   Delete all GDI objects that you create.

  þ   Don't delete GDI objects while they are selected in a valid device
      context.

  þ   Don't delete stock objects.

These are not unreasonable rules, but they can be a little tricky sometimes.
We'll run through some examples to get the hang of how the rules work.

The general syntax for the CreatePen function looks like this:

hPen = CreatePen (nPenStyle, nWidth, rgbColor) ;

The nPenStyle parameter determines whether the pen draws a solid line or a
line made up of dots or dashes. The parameter can be one of the following
identifiers defined in WINDOWS.H: PS_SOLID, PS_DASH, PS_DOT, PS_DASHDOT,
PS_DASHDOTDOT, PS_NULL, and PS_INSIDEFRAME. Figure 12-1 shows the kind of
line that each style produces.

  (Figure 12-1. may be found in the printed book.)

For the PS_SOLID, PS_NULL, and PS_INSIDEFRAME styles, the nWidth parameter
is the width of the pen in logical units. For instance, if the mapping mode
is MM_LOENGLISH, a pen with an nWidth of 10 will be 0.1 inch wide. When you
draw a line with a PS_SOLID or PS_NULL pen, the width of the pen will extend
0.05 inch on either side of the line. (The PS_INSIDEFRAME style is a little
different.) For the MM_ANISOTROPIC mapping mode, Windows uses logical units
on the x-axis to determine the physical width of the pen. An nWidth value of
0 directs Windows to use one physical unit (1 pixel) for the pen width. The
stock pens are 1 pixel wide. If you specify a dotted or dashed pen style
with a physical width greater than 1, Windows will use a solid pen instead.

The rgbColor parameter to CreatePen is an unsigned long integer specifying
the color of the pen. For all the pen styles except PS_INSIDEFRAME, when you
select the pen into the device context, Windows converts this parameter to
the nearest pure color that the device can represent. The PS_INSIDEFRAME
style is the only pen style that can use a dithered color, and then only
when the width is greater than 1. (The PS_INSIDEFRAME style has another
peculiarity, which I'll discuss later in this chapter in the section on the
"bounding box.")

You can also create a pen by setting up a structure of type LOGPEN ("logical
pen") and calling CreatePenIndirect. If your program uses a lot of different
pens that you can initialize in your source code, this method is more
efficient. First you define a structure variable of type LOGPEN--for
instance, logpen:

LOGPEN logpen ;

This structure has three members: lopnStyle (WORD) is the pen style,
lopnWidth (POINT) is the pen width in logical units, and lopnColor (DWORD)
is the pen color. The lopnWidth member is a structure of type POINT, but
Windows uses only the lopnWidth.x value for the pen width and ignores
lopnWidth.y. Then you create the pen by passing the address of the structure
to CreatePenIndirect:

hPen = CreatePenIndirect (&logpen) ;

You can also obtain the logical pen information for an existing pen. If you
already have a handle to a pen, you can copy the data that defines the
logical pen into a structure of type LOGPEN by using the GetObject call:

GetObject (hPen, sizeof (LOGPEN), (LPSTR) &logpen) ;

Note that the CreatePen and CreatePenIndirect functions do not require a
handle to a device context. These functions create logical pens that have no
connection with a device context until you call SelectObject. For instance,
you can use the same logical pen for several different devices, such as the
screen and a printer. Logical pens with a non- zero nWidth have a logical
width; they have a physical width only when you select the pen into a device
context, and then the physical width depends on the device context's mapping
mode.

Here's one method for creating, selecting, and deleting pens. Suppose your
program uses three pens--a black pen of width 1, a red pen of width 3, and a
black dotted pen. You can first define variables for storing the handles to
these pens:

static HPEN hPen1, hPen2, hPen3 ;

During processing of WM_CREATE, you can create the three pens:

hPen1 = CreatePen (PS_SOLID, 1, 0L) ;
hPen2 = CreatePen (PS_SOLID, 3, RGB (255, 0, 0)) ;
hPen3 = CreatePen (PS_DOT,   0, 0L) ;

During processing of WM_PAINT (or any other time you have a valid handle to
a device context), you can select one of these pens into the device context
and draw with it:

SelectObject (hdc, hPen2) ;
[LineTo, PolyLine, or Arc calls]

SelectObject (hdc, hPen1) ;
[other LineTo, PolyLine, or Arc calls]

During processing of WM_DESTROY, you can delete the three pens you created:

DeleteObject (hPen1) ;
DeleteObject (hPen2) ;
DeleteObject (hPen3) ;

This is the most straightforward method for creating, selecting, and
deleting pens, but it requires that the logical pens take up memory space
during the entire time your program is running. You might instead want to
create the pens during each WM_PAINT message and delete them after you call
EndPaint. (You can delete them before calling EndPaint, but you have to be
careful not to delete the pen currently selected in the device context.)

You might also want to create pens on the fly and combine the CreatePen and
the SelectObject calls in the same statement:

SelectObject (hdc, CreatePen (PS_DASH, 0, RGB (255, 0, 0))) ;

Now when you draw lines, you'll be using a red dashed pen. When you're
finished drawing the red dashed lines, you can delete the pen. Whoops! How
can you delete this pen when you haven't saved the pen handle? Recall that
SelectObject returns the handle to the pen previously selected in the device
context. So you can delete the pen by selecting the stock BLACK_PEN into the
device context and deleting the value returned from SelectObject:

DeleteObject (SelectObject (hdc, GetStockObject (BLACK_PEN))) ;

Here's another method. When you select a newly created pen into the device
context, save the handle to the pen that SelectObject returns:

hPen = SelectObject (hdc, CreatePen (PS_DASH, 0, RGB (255, 0, 0))) ;

What is hPen? If this is the first SelectObject call you've made since
obtaining the device context, hPen is a handle to the BLACK_PEN stock
object. You can now select that pen into the device context and delete the
pen you created (the handle returned from this second SelectObject call) in
one statement:

DeleteObject (SelectObject (hdc, hPen)) ;

If you delete a GDI object while it is selected in a device context and then
try to draw lines, Windows will respond with a fatal error because the
device context doesn't contain a valid pen. This is a fairly obvious bug to
track down. Failing to delete GDI objects that you create can be a more
difficult bug to discover, because the program will appear to work fine. If
your program creates the same logical pen for every WM_PAINT message, you
might want to cause the client area to be repainted over and over and check
to see if free memory starts to drop. The FREEMEM program shown in Chapter 5
can identify problems related to dropping memory. If HEAPWALK shows a lot of
small GDI segments after your program has terminated, some of them may be
GDI objects you have failed to delete properly.


Avoiding Device Dependencies

Pen widths will vary according to the resolution of the display. The stock
pens (and any pen created with a width of 0) are 1 pixel wide, which on a
high-resolution display can result in very thin lines.

If you're working in MM_TEXT, you might want to obtain the width of the
single- line window border by calling GetSystemMetrics with the SM_CXBORDER
and SM_CYBORDER indexes. These values are appropriate for pen widths. You
can also use one of the metric mapping modes and set specific physical
widths for the pens your program needs to create.

Pen colors are also susceptible to device dependencies. If you develop a
program on a color display and then run the program on a monochrome display,
you can be in for some unpleasant surprises. Except for the PS_INSIDEFRAME
style, Windows always uses pure colors for pens, and on a monochrome system,
pens are either black or white. For instance, on your color EGA, you might
be fond of magenta pens on a white background:

hPen = CreatePen (PS_SOLID, 1, RGB (255, 0, 255)) ;

But on a monochrome system, the pure color that is closest to magenta is
white, so the pen will be invisible. If you want to use colored pens, be
sure the sum of 2 times the red, 5 times the green, and 1 times the blue
values is less than 1920 (half the maximum sum of the three primaries) for
any pen that should default to black and greater than 1920 for any pen that
should default to white.


Filling In the Gaps

The use of dotted pens and dashed pens raises an interesting question: What
happens to the gaps between the dots and the dashes? The coloring of the
gaps depends on both the background mode and the background color attributes
defined in the device context. The default background mode is OPAQUE, which
means that Windows fills in the gaps with the background color, which by
default is white. This is consistent with the WHITE_BRUSH that many programs
use in the window class for erasing the background of the window.

You can change the background color that Windows uses to fill in the gaps by
calling:

SetBkColor (hdc, rgbColor) ;

As with the rgbColor value used for the pen color, Windows converts this
background color to a pure color. You can obtain the current background
color defined in the device context by calling GetBkColor.

You can also prevent Windows from filling in the gaps by changing the
background mode to TRANSPARENT:

SetBkMode (hdc, TRANSPARENT) ;

Windows will ignore the background color and will not fill in the gaps. You
can obtain the current background mode (either TRANSPARENT or OPAQUE) by
calling GetBkMode.


Drawing Modes

The appearance of lines drawn on the display is also affected by the drawing
mode defined in the device context. Imagine drawing a line that has a color
based not only on the color of the pen but also on the original color of the
display area where the line is drawn. Imagine a way in which you could use
the same pen to draw a black line on a white surface and a white line on a
black surface without knowing what color the surface is. Could such a
facility be useful to you? It's made possible by the drawing mode.

When Windows uses a pen to draw a line, it actually performs a bitwise
Boolean operation between the pixels of the pen and the pixels of the
destination display surface. Performing a bitwise Boolean operation with
pixels is called a "raster operation," or "ROP." Because drawing a line
involves only two pixel patterns (the pen and the destination), the Boolean
operation is called a "binary raster operation," or "ROP2." Windows defines
16 ROP2 codes that indicate how Windows combines the pen pixels and the
destination pixels. In the default device context, the drawing mode is
defined as R2_COPYPEN, which means that Windows simply copies the pixels of
the pen to the destination, which is how we normally think about pens. There
are 15 other ROP2 codes.

Where do these 16 different ROP2 codes come from? For illustration purposes,
let's assume a monochrome system. The destination color (the color of the
window's client area) can be either black (which we'll represent by a 0) or
white (1). The pen also can be either black or white. There are four
combinations of using a black or white pen to draw on a black or white
destination: a white pen on a white destination, a white pen on a black
destination, a black pen on a white destination, and a black pen on a black
destination.

What happens to the destination after you draw with the pen? One possibility
is that the line is always drawn as black regardless of the pen or
destination color: This drawing mode is indicated by the ROP2 code R2_BLACK.
Another possibility is that the line is drawn as black except when both the
pen and destination are black, in which case the line  is drawn as white.
Although this might be a little strange, Windows has a name for it: The
drawing mode is called R2_NOTMERGEPEN. Windows performs a bitwise OR
operation on the destination pixels and the pen pixels and then inverts that
result.

The table below shows all 16 ROP2 drawing modes. The table indicates how the
original pen (P) and destination (D) colors are combined for the resultant
destination color.

The column labeled "Boolean Operation" uses C notation to show how the
destination pixels and pen pixels are combined.


Pen (P):  1   1   0   0   Boolean
Destinat  1   0   1   0   Operatio  Drawing Mode
ion (D):                  n
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Results:  0   0   0   0   0         R2_BLACK

          0   0   0   1   ~(P | D)  R2_NOTMERGEPEN

          0   0   1   0   ~P & D    R2_MASKNOTPEN

          0   0   1   1   ~P        R2_NOTCOPYPEN

          0   1   0   0   P & ~D    R2_MASKPENNOT

          0   1   0   1   ~D        R2_NOT

          0   1   1   0   P ^ D     R2_XORPEN

          0   1   1   1   ~(P & D)  R2_NOTMASKPEN

          1   0   0   0   P & D     R2_MASKPEN

          1   0   0   1   ~(P ^ D)  R2_NOTXORPEN

          1   0   1   0   D         R2_NOP

          1   0   1   1   ~P | D    R2_MERGENOTPEN

          1   1   0   0   P         R2_COPYPEN (default)

          1   1   0   1   P | ~D    R2_MERGEPENNOT

          1   1   1   0   P | D     R2_MERGEPEN

          1   1   1   1   1         R2_WHITE



You can set a new drawing mode in the device context by:

SetROP2 (hdc, nDrawMode) ;

The nDrawMode parameter is one of the values listed in the "Drawing Mode"
column of the table. You can obtain the current drawing mode using the
function:

nDrawMode = GetROP2 (hdc) ;

The device context default is R2_COPYPEN, which simply transfers the pen
color to the destination. The R2_NOTCOPYPEN mode draws white if the pen
color is black and black if the pen color is white. The R2_BLACK mode always
draws black, regardless of the color of the pen or the background. Likewise,
the R2_WHITE mode always draws white. The R2_NOP mode is a "no operation":
It leaves the destination unchanged.

We started out using an example of a pure monochrome system. In reality, on
a monochrome display Windows can simulate various shades of gray by
dithering black and white pixels. When drawing a pen on a dithered
background, Windows simply performs the bitwise operation on a
pixel-by-pixel basis. The R2_NOT mode always inverts the destination, again
regardless of the color of the pen. This mode is useful when you don't know
the color of the background, because it guarantees that the pen will be
visible. (Well, almost guarantees--if the background is a 50 percent gray,
then the pen will be virtually invisible.)


The ROP2LOOK Program

The ROP2LOOK program, shown in Figure 12-2, lets you experiment with these
16 ROP2 codes.

 ROP2LOOK.MAK

#------------------------
# ROP2LOOK.MAK make file
#------------------------

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

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

rop2look.res : rop2look.rc
     rc -r rop2look.rc

 ROP2LOOK.C

/*------------------------------------------
   ROP2LOOK.C -- ROP2 Demonstration 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[] = "Rop2Look" ;
     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  = szAppName ;
          wndclass.lpszClassName = szAppName ;

          RegisterClass (&wndclass) ;
          }

     hwnd = CreateWindow (szAppName, "ROP2 Demonstration 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 LOGPEN lpBlack = { PS_SOLID, 1, 1, RGB (  0,   0,   0) },
                   lpWhite = { PS_SOLID, 1, 1, RGB (255, 255, 255) } ;
     static short  nDrawingMode = R2_COPYPEN ;
     HDC           hdc ;
     HMENU         hMenu ;
     HPEN          hPenBlack, hPenWhite ;
     PAINTSTRUCT   ps ;
     RECT          rect ;
     short         i ;

     switch (message)
          {
          case WM_COMMAND :
               hMenu = GetMenu (hwnd) ;
               CheckMenuItem (hMenu, nDrawingMode, MF_UNCHECKED) ;
               nDrawingMode = wParam ;
               CheckMenuItem (hMenu, nDrawingMode, MF_CHECKED) ;
               InvalidateRect (hwnd, NULL, FALSE) ;
               return 0 ;

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

               hPenBlack = CreatePenIndirect (&lpBlack) ;
               hPenWhite = CreatePenIndirect (&lpWhite) ;

               SetMapMode (hdc, MM_ANISOTROPIC) ;
               GetClientRect (hwnd, &rect) ;
               SetViewportExt (hdc, rect.right, rect.bottom) ;
               SetWindowExt (hdc, 10, 4) ;

               for (i = 0 ; i < 10 ; i += 2)
                    {
                    SetRect (&rect, i, 0, i + 2, 4) ;
                    FillRect (hdc, &rect, GetStockObject (i / 2)) ;
                    }
               SetROP2 (hdc, nDrawingMode) ;

               SelectObject (hdc, hPenWhite) ;
               MoveTo (hdc, 1, 1) ;
               LineTo (hdc, 9, 1) ;

               SelectObject (hdc, hPenBlack) ;
               MoveTo (hdc, 1, 3) ;
               LineTo (hdc, 9, 3) ;

               EndPaint (hwnd, &ps) ;

               DeleteObject (hPenBlack) ;
               DeleteObject (hPenWhite) ;
               return 0 ;

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

 ROP2LOOK.RC

/*-----------------------------
   ROP2LOOK.RC resource script
  -----------------------------*/

Rop2Look MENU
     {
     POPUP "&Drawing Mode"
          {
          MENUITEM "0\tR2_BLACK",        1
          MENUITEM "1\tR2_NOTMERGEPEN",  2
          MENUITEM "2\tR2_MASKNOTPEN",   3
          MENUITEM "3\tR2_NOTCOPYPEN",   4
          MENUITEM "4\tR2_MASKPENNOT",   5
          MENUITEM "5\tR2_NOT",          6
          MENUITEM "6\tR2_XORPEN",       7
          MENUITEM "7\tR2_NOTMASKPEN",   8
          MENUITEM "8\tR2_MASKPEN",      9
          MENUITEM "9\tR2_NOTXORPEN",   10
          MENUITEM "A\tR2_NOP",         11
          MENUITEM "B\tR2_MERGENOTPEN", 12
          MENUITEM "C\tR2_COPYPEN",     13, CHECKED
          MENUITEM "D\tR2_MERGEPENNOT", 14
          MENUITEM "E\tR2_MERGEPEN",    15
          MENUITEM "F\tR2_WHITE",       16
          }
     }

 ROP2LOOK.DEF

;-------------------------------------
; ROP2LOOK.DEF module definition file
;-------------------------------------

NAME           ROP2LOOK

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

The program draws a background divided into five sections colored with the
white, light gray, gray, dark gray, and black stock brushes (a subject that
we'll get to soon). It then draws two very thick pens: a white pen on the
top and a black pen on the bottom. You can select one of the 16 ROP2 codes
from the menu. Figure 12-3 shows the white pen on the top and the black pen
on the bottom with the drawing mode set to R2_NOTMERGEPEN: The white pen
always displays as black, and the black pen inverts the destination.

ROP2LOOK uses initialized logical pen structures for the white and black
pens. You'll note that both these pens have a logical width of 1. Why do
they appear so thick? The program uses the MM_ANISOTROPIC mapping mode and
sets the width of the client area to 10 logical units and the height to 4
logical units. The pens are therefore one-tenth the width of the client
area.

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


ROP2 and Color

The drawing mode gets more interesting--and much more complex--when color is
introduced. Let's assume a display capable of eight pure colors (such as the
EGA and VGA in versions of Windows prior to version 3). The pen can be any
of these eight pure colors, and for simplicity's sake, let's restrict the
background to these colors also. The eight colors are combinations of the
bits in the red, green, and blue color planes, as shown in the following
table--a 1 means the color is illuminated, and a 0 means the color is off.

Red  Green  Blue  Pure Color
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
0    0      0     Black
0    0      1     Blue
0    1      0     Green
0    1      1     Cyan


Red  Green  Blue  Pure Color
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
1    0      0     Red
1    0      1     Magenta
1    1      0     Yellow
1    1      1     White

Each of the three color planes is affected separately by the raster
operation. For example, say you have a cyan background color and a magenta
pen color, and your drawing mode is R2_NOTMERGEPEN. What color will the pen
actually draw? For red, the pen is 1 (has red), and the destination is 0 (no
red). Looking at the ROP2 table on page 551, you see that the result is 0
(no red). For green, the pen is 0 (no green), and the destination is 1 (has
green), so the result is 0 (no green). For blue, the pen is 1 (has blue),
and the destination is 1 (has blue), so the result is 0 (no blue). Thus the
line has no red, no green, and no blue. The color will be black.

Let's take the R2_XORPEN drawing mode, which performs a bitwise exclusive OR
operation on each of the possible combinations in the three color planes.
The following table shows the resultant color for all combinations of the
eight destination colors and the eight pen colors.


                           PEN
                           COLOR
Destina  Black    Blue     Green    Cyan     Red      Magenta  Yellow   White
tion
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ


Black    Black    Blue     Green    Cyan     Red      Magenta  Yellow   White

Blue     Blue     Black    Cyan     Green    Magenta  Red      White    Yellow

Green    Green    Cyan     Black    Blue     Yellow   White    Red      Magenta

Cyan     Cyan     Green    Blue     Black    White    Yellow   Magenta  Red

Red      Red      Magenta  Yellow   White    Black    Blue     Green    Cyan

Magenta  Magenta  Red      White    Yellow   Blue     Black    Cyan     Green

Yellow   Yellow   White    Red      Magenta  Green    Cyan     Black    Blue

White    White    Yellow   Magenta  Red      Cyan     Green    Blue     Black



On certain devices (particularly on 256-color video boards), the bits that
define each pixel may not correspond to color in a consistent manner, and
the results of using some drawing modes are not well defined.

At the beginning of Chapter 11 I mentioned that Windows GDI is strong in the
area of raster operations. The drawing mode is one example of that. And if
you think that you'll probably never ever use some of these ROP2 codes, just
wait until you see the regular raster operation codes in Chapter 13--there
are more than 200 raster operations that you'll probably never use. But it's
nice to know that they're available.



DRAWING FILLED AREAS

Now let's take the next step up, from drawing lines to drawing figures.
Windows' six functions for drawing filled figures with borders are listed in
the chart below:


Function                          Figure
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Rectangle                         Rectangle with square corners

Ellipse                           Ellipse

RoundRect                         Rectangle with rounded corners

Chord                             Arc on the circumference of an ellipse
                                  with endpoints connected

by a chord

Pie                               Pie wedge on the circumference of an
                                  ellipse

Polygon                           Multisided figure

PolyPolygon                       Multiple multisided figures



Windows draws the outline of the figure with the current pen selected in the
device context. The current background mode, background color, and drawing
mode are all used for this outline, just as if Windows were drawing a line.
Everything we learned about lines also applies to the border around these
figures.

The figure is filled with the current brush selected in the device context.
By default, this is the stock object called WHITE_BRUSH, which means that
the interior will be drawn as white. Windows defines six stock brushes:
WHITE_BRUSH, LTGRAY_BRUSH, GRAY- _BRUSH, DKGRAY_BRUSH, BLACK_BRUSH, and
NULL_BRUSH (or HOLLOW_BRUSH). The first five of these brushes were used to
color the client area of ROP2LOOK.

You can select one of the stock brushes into your device context the same
way you select a stock pen. Windows defines HBRUSH to be a handle to a
brush, so you can first define a variable for the brush handle:

HBRUSH hBrush ;

You can get the handle to GRAY_BRUSH by calling GetStockObject:

hBrush = GetStockObject (GRAY_BRUSH) ;

You can select it into the device context by calling SelectObject:

SelectObject (hdc, hBrush) ;

Now when you draw one of these figures, the interior will be gray.

If you want to draw a figure without a border, select the NULL_PEN into the
device context:

SelectObject (hdc, GetStockObject (NULL_PEN)) ;

Or you can use the R2_NOP drawing mode:

SetROP2 (hdc, R2_NOP) ;

If you want to draw the outline of the figure but not fill in the interior,
select the NULL_BRUSH into the device context:

SelectObject (hdc, GetStockObject (NULL_BRUSH)) ;

You can also create customized brushes just as you can create customized
pens. We'll cover that topic shortly.

The Bounding Box

The Rectangle, Ellipse, RoundRect, Chord, and Pie functions (as well as the
Arc line-drawing function) are all similar in that they are built up from a
rectangular "bounding box." You define the coordinates of a box that
encloses the object--a bounding box--and Windows draws the object within
this box.

The simplest filled object is the rectangle:

Rectangle (hdc, xLeft, yTop, xRight, yBottom) ;

The point (xLeft, yTop) is the upper left corner of the rectangle, and
(xRight, yBottom) is the lower right corner; both points are expressed in
logical units. A figure drawn using the Rectangle function is shown in
Figure 12-4. In the MM_TEXT mapping mode, xRight must be greater than xLeft,
and yBottom must be greater than yTop. However, in all the other mapping
modes (except possibly MM_ISOTROPIC and MM_ANISOTROPIC), the value of
yBottom is less than that of yTop because the coordinates on the y-axis
increase as you move up.

Programmers who have worked with graphics before are accustomed to the
problem of being off by 1 pixel. Some graphics systems draw a figure to
encompass the right and bottom coordinates, and some draw figures up to (but
not including) the right and bottom coordinates. Windows uses the latter
approach, but there's an easier way to think about it.

  (Figure 12-4. may be found in the printed book.)

Consider the function call:

Rectangle (hdc, 1, 1, 5, 4) ;

I mentioned above that Windows draws the figure within a "bounding box." You
can think of the display as a grid where each pixel is within a grid cell.
The imaginary bounding box is drawn on the grid, and the rectangle is then
drawn within this bounding box. Here's how the figure would be drawn in the
MM_TEXT mapping mode:

The area separating the rectangle from the top and left of the client area
is 1 pixel wide. Windows uses the current brush to color the 2 pixels on the
inside of the rectangle.

For all pen styles except PS_INSIDEFRAME, if the pen used to draw the
outline is greater than 1 pixel wide, then the pen is centered on the border
so that part of the line may be outside the bounding box. For the
PS_INSIDEFRAME pen style, the entire line is drawn inside the bounding box.

Once you know how to draw a rectangle, then you also know how to draw an
ellipse, because it uses the same parameters:

Ellipse (hdc, xLeft, yTop, xRight, yBottom) ;

A figure drawn using the Ellipse function is shown (with the imaginary
bounding box) in Figure 12-5.

  (Figure 12-5. may be found in the printed book.)

Windows does not include "square" or "circle" functions. In all mapping
modes except MM_TEXT and MM_ANISOTROPIC, you can easily draw squares and
circles using the Rectangle and Ellipse functions by making the difference
between xLeft and xRight the same as the difference between yTop and
yBottom. In MM_TEXT, squares and circles are a little more difficult. You
have to call GetDeviceCaps with the ASPECTX and ASPECTY indexes and scale
the dimensions based on the aspect ratio of the pixels. In MM_ANISOTROPIC,
you also have to take into account the ratio of the window and viewport
extents.

The function to draw rectangles with rounded corners uses the same bounding
box as the Rectangle and Ellipse functions but includes two more parameters:

RoundRect (hdc, xLeft, yTop, xRight, yBottom,
               xCornerEllipse, yCornerEllipse) ;

A figure drawn using this function is shown in Figure 12-6.

Windows uses a small ellipse to draw the rounded corners. The width of this
ellipse is xCornerEllipse, and the height is yCornerEllipse, with both
points expressed in logical units. Imagine Windows splitting this small
ellipse into four quadrants and using one quadrant for each of the four
corners. The rounding of the corners is more pronounced for larger values of
xCornerEllipse and yCornerEllipse. If xCornerEllipse is equal to the
difference between xLeft and xRight and yCornerEllipse is equal to the
difference between yTop and yBottom, then the RoundRect function will draw
an ellipse.

The rounded rectangle shown in Figure 12-6 was drawn using the MM_TEXT
mapping mode with the corner ellipse dimensions calculated with these
formulas:

xCornerEllipse = (xRight - xLeft) / 4 ;
yCornerEllipse = (yBottom - yTop) / 4 ;

This is an easy approach, but the results admittedly don't look quite right,
because the rounding of the corners is more pronounced along the larger
rectangle dimension. To

  (Figure 12-6. may be found in the printed book.)

correct this problem, you'll probably want to make xCornerEllipse equal to
yCornerEllipse in real dimensions.

The Arc, Chord, and Pie functions all take identical parameters:

Arc (hdc, xLeft, yTop, xRight, yBottom,
          xStart, yStart, xEnd, yEnd) ;

Chord (hdc, xLeft, yTop, xRight, yBottom,
          xStart, yStart, xEnd, yEnd) ;

Pie (hdc, xLeft, yTop, xRight, yBottom,
          xStart, yStart, xEnd, yEnd) ;

A line drawn using the Arc function is shown in Figure 12-7; figures drawn
using the Chord and Pie functions are shown in Figures 12-8 and 12-9.

  (Figure 12-7. may be found in the printed book.)

  (Figure 12-8. may be found in the printed book.)

  (Figure 12-9. may be found in the printed book.)

Windows uses an imaginary line to connect (xStart, yStart) with the center
of the ellipse. At the point at which that line intersects the ellipse,
Windows begins drawing an arc in a counterclockwise direction around the
circumference of the ellipse. Windows also uses an imaginary line to connect
(xEnd, yEnd) with the center of the ellipse. At the point at which that line
intersects the ellipse, Windows stops drawing the arc.

For the Arc function, Windows is now finished, because the arc is an
elliptical line rather than a filled area. For the Chord function, Windows
connects the endpoints of the arc. For the Pie function, Windows connects
each endpoint of the arc with the center of the ellipse. The interiors of
the chord and pie-wedge figures are filled with the current brush.


The ARCS Program

You may wonder about this use of starting and ending positions in the Arc,
Chord, and Pie functions. Why not simply specify starting and ending points
on the circumference of the ellipse? Well, you can, but you would have to
figure out what those points are. Windows' method gets the job done without
requiring such precision.

You can experiment with arcs, chords, and pie wedges using the ARCS program,
shown in Figure 12-10 on the following page. The program draws the bounding
box and the ellipse using a dotted pen. Your menu choice determines whether
the program draws an arc, a chord, or a pie wedge. The line or figure is
drawn with a pen that is 3 pixels wide. The starting and ending points are
connected to the center of the ellipse with a normal black pen.

 ARCS.MAK

#--------------------
# ARCS.MAK make file
#--------------------

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

arcs.obj : arcs.c arcs.h
     cl -c -Gsw -Ow -W2 -Zp arcs.c

arcs.res : arcs.rc arcs.h
     rc -r arcs.rc

 ARCS.C

/*-------------------------------------------------------
   ARCS.C -- Demonstrates Drawing Arcs, Chords, and Pies
             (c) Charles Petzold, 1990
  -------------------------------------------------------*/

#include 
#include "arcs.h"

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

int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
                    LPSTR lpszCmdLine, int nCmdShow)
     {
     static char szAppName[] = "Arcs" ;
     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  = szAppName ;
          wndclass.lpszClassName = szAppName ;



          RegisterClass (&wndclass) ;
          }

     hwnd = CreateWindow (szAppName, "Arcs, Chords, and Pies",
                          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 short cxClient, cyClient, x1, x2, x3, x4, y1, y2, y3, y4,
                  nFigure = IDM_ARC ;
     HDC          hdc ;
     HMENU        hMenu ;
     HPEN         hPen ;
     PAINTSTRUCT  ps ;
     short        x, y ;

     switch (message)
          {
          case WM_SIZE :
               x3 = y3 = 0 ;
               x4 = cxClient = LOWORD (lParam) ;
               y4 = cyClient = HIWORD (lParam) ;
               x2 = 3 * (x1 = cxClient / 4) ;
               y2 = 3 * (y1 = cyClient / 4) ;
               return 0 ;

          case WM_COMMAND :
               switch (wParam)
                    {
                    case IDM_ARC :
                    case IDM_CHORD :
                    case IDM_PIE :
                         hMenu = GetMenu (hwnd) ;
                         CheckMenuItem (hMenu, nFigure, MF_UNCHECKED) ;
                         CheckMenuItem (hMenu, nFigure = wParam, MF_CHECKED)
;
                         InvalidateRect (hwnd, NULL, FALSE) ;
                         return 0 ;
                    }
               break ;

          case WM_LBUTTONDOWN :
               if (!(wParam & MK_SHIFT))
                    {
                    x3 = LOWORD (lParam) ;
                    y3 = HIWORD (lParam) ;
                    InvalidateRect (hwnd, NULL, TRUE) ;
                    return 0 ;
                    }
                                        // fall through for MK_SHIFT
          case WM_RBUTTONDOWN :
               x4 = LOWORD (lParam) ;
               y4 = HIWORD (lParam) ;
               InvalidateRect (hwnd, NULL, TRUE) ;
               return 0 ;

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

               hPen = SelectObject (hdc, CreatePen (PS_DOT, 1, 0L)) ;
               Rectangle (hdc, x1, y1, x2, y2) ;
               Ellipse   (hdc, x1, y1, x2, y2) ;

               DeleteObject (SelectObject (hdc, CreatePen (PS_SOLID, 3,
0L))) ;

               switch (nFigure)
                    {
                    case IDM_ARC :
                         Arc (hdc, x1, y1, x2, y2, x3, y3, x4, y4) ;
                         break ;

                    case IDM_CHORD :
                         Chord (hdc, x1, y1, x2, y2, x3, y3, x4, y4) ;
                         break ;

                    case IDM_PIE :
                         Pie (hdc, x1, y1, x2, y2, x3, y3, x4, y4) ;
                         break ;
                    }

               DeleteObject (SelectObject (hdc, hPen)) ;
               MoveTo (hdc, x3, y3) ;
               LineTo (hdc, cxClient / 2, cyClient / 2) ;
               LineTo (hdc, x4, y4) ;

               EndPaint (hwnd, &ps) ;
               return 0 ;

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

 ARCS.RC

/*-------------------------
   ARCS.RC resource script
  -------------------------*/

#include "arcs.h"

Arcs MENU
     {
     POPUP "&Options"
          {
          MENUITEM "&Arc",    IDM_ARC,  CHECKED
          MENUITEM "&Chord",  IDM_CHORD
          MENUITEM "&Pie",    IDM_PIE
          }
     }

 ARCS.H

/*--------------------
   ARCS.H header file
  --------------------*/

#define IDM_ARC     1
#define IDM_CHORD   2
#define IDM_PIE     3

 ARCS.DEF

;---------------------------------
; ARCS.DEF module definition file
;---------------------------------

NAME           ARCS

DESCRIPTION    'Arc, Chord, and Pie Drawing Program (c) Charles Petzold,
1990'
EXETYPE        WINDOWS
STUB           'WINSTUB.EXE'
CODE           PRELOAD MOVEABLE DISCARDABLE
DATA           PRELOADMOVEABLE MULTIPLE
HEAPSIZE       1024
STACKSIZE      8192
EXPORTS        WndProc

When you click on the client area using the left mouse button, ARCS uses
that point as the starting point, which is the point (x3, y3) in the
program. Clicking on the client area with the right mouse button sets the
ending point, the point (x4, y4). Users with a one-button mouse can hold
down the Shift key and click the mouse to set the ending point.

ARCS also shows some typical pen-handle manipulation. After the program gets
the device context by calling BeginPaint, it creates a dotted pen and
selects it into the device context:

hPen = SelectObject (hdc, CreatePen (PS_DOT, 1, 0L)) ;

The hPen handle returned from SelectObject is a handle to the stock
BLACK_PEN.

When ARCS needs to draw the arc, chord, or pie wedge, it creates a
3-pixel-wide pen and selects that into the device context:

DeleteObject (SelectObject (hdc, CreatePen (PS_SOLID, 3, 0L))) ;

The pen handle returned from SelectObject, which is the handle to the dotted
pen, is then deleted using DeleteObject.

When ARCS needs to draw the lines connecting the starting and ending points
with the center of the ellipse, it selects hPen--the handle to the stock
BLACK_PEN--into the device context and deletes the 3-pixel-wide pen returned
from SelectObject:

DeleteObject (SelectObject (hdc, hPen)) ;

Now the two pens that were created have been deleted. The pen currently
selected in the device context is a stock pen, and the device context can be
released.


The Trigonometry of Pie Charts

If you use the Pie function to create pie charts, the size of each pie wedge
will be based on the relative sizes of data items. This involves calculating
starting and ending points of the pie figures that are derived from the
internal angle of the pie wedge.

It's time for a trigonometry refresher. In a Cartesian coordinate system
(with x increasing to the right and y increasing as it moves up), we can
draw a triangle like this:

The relationship between each of the three sides of the triangle and the
angle à is given by the formulas:

  sin (à) = y/r

  cos (à) = x/r

  tan (à) = y/x

If you know the angle à (which in a pie chart will be a fraction of a
circle) and r (which will be the radius of the circle), you can determine
that the point (x, y) is equal to:

  (r * cos (à), r * sin (à))

In the C library functions sin and cos, angles are specified in terms of
radians. There are  2 * PI radians in 360 degrees.

Let's try drawing a pie chart that uses five numbers. For convenience, we'll
set this condition with a #define statement:

#define NUM 5

In a real program, that would be a variable.

You'll also find it convenient to define an identifier called TWO_PI that is
the number of radians in a circle:

#define TWO_PI (2.0 * 3.14159)

Next you define some variables:

static short   nValues [NUM] = { 3, 5, 2, 7, 4 };
short          i, nSum [NUM + 1] ;

The initialized values in nValues are the data we'll be graphing. In a real
program, these values would be variables. The nSum array is set to the
accumulated sum of the data values where the first element of the array is
set to 0:

nSum [0] = 0 ;
for (i = 0 ; i < NUM ; i++)
     nSum [i + 1] = nSum [i] + nValues [i] ;

The array element nSum [NUM] is the sum of the five values.

Now we are ready to start drawing the pie chart. Set the mapping mode to
MM_ISOTROPIC, which is the mode in which you can most easily draw a circle:

SetMapMode (hdc, MM_ISOTROPIC) ;
SetWindowExt (hdc, 400, 400) ;
SetViewportExt (hdc, xClient, -yClient) ;
SetViewportOrg (hdc, xClient / 2, yClient / 2) ;

The logical point (0, 0) is the center of the client area, and the x- and
y-coordinates define a normal Cartesian coordinate system.

Our pie has a radius of 100 logical units. Here's the code to paint the five
pie segments:

for (i = 0 ; i < NUM ; i++)
     Pie (hdc, -100, 100, 100, -100,
          (short) (100.0 * cos (TWO_PI * nSum [i]     / nSum [NUM])),
          (short) (100.0 * sin (TWO_PI * nSum [i]     / nSum [NUM])),
          (short) (100.0 * cos (TWO_PI * nSum [i + 1] / nSum [NUM])),
          (short) (100.0 * sin (TWO_PI * nSum [i + 1] / nSum [NUM]))) ;

The pie chart produced from this code is shown in Figure 12-11. The first
pie wedge is at the right of the pie chart, just above the x-axis. The other
pie wedges are drawn in a counterclockwise direction.

  (Figure 12-11. may be found in the printed book.)

The values:

TWO_PI * nSum [i] / nSum [NUM]

and:

TWO_PI * nSum [i + 1] / nSum [NUM]

are ratios of the accumulated sum of the items to the total sum of the items
converted to angles that are measured counterclockwise from the horizontal.
The second formula includes the item that the particular pie wedge
represents; the first does not. By taking the cosine and sine of these
angles and multiplying by 100, we're calculating the starting and ending
points on the circle.


The Polygon Function and the Polygon Filling Mode

Polygon is the sixth function for drawing a bordered and filled figure. The
function call is similar to the PolyLine function:

Polygon (hdc, lpPoints, nCount) ;

The lpPoints parameter is a far pointer to an array of POINT structures (in
logical coordinates), and nCount is the number of points. If the last point
in this array is different from the first point, Windows adds another line
that connects the last point with the first point. (This does not happen
with the PolyLine function.)

Windows fills this bounded area with the current brush in one of two ways,
depending on the current polygon filling mode defined in the device context.
By default, the polygon filling mode is ALTERNATE, which means that Windows
fills in only those interiors accessible from the outside of the polygon by
crossing an odd number of lines (1, 3, 5, and so forth). The other interiors
are not filled. You can also set the polygon filling mode to WINDING, in
which case Windows fills in all the interior areas. The two polygon filling
modes are most simply demonstrated with a five-pointed star. In Figure 12-12
on the following page, the star on the left was drawn with the ALTERNATE
mode, and the star on the right was drawn with the WINDING mode. Both
figures were drawn with an array of points defined like this:

static POINT pt [] =
          { -59, -81, 0, 100, 59, -81, -95, 31, 95, 31 } ;

The five points of the star were manually calculated from trigonometric
tables. The WM_PAINT logic looks like this:

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

     hPen = CreatePen (PS_SOLID, 3, 0L) ;
     SelectObject (hdc, hPen) ;
     SelectObject (hdc, GetStockObject (LTGRAY_BRUSH)) ;

     SetMapMode (hdc, MM_ISOTROPIC) ;
     SetWindowExt (hdc, 440, -220) ;
     SetViewportExt (hdc, xClient, yClient) ;
     SetWindowOrg (hdc, -110, 110) ;

     SetPolyFillMode (hdc, ALTERNATE) ;
     Polygon (hdc, pt, sizeof (pt) / sizeof (POINT)) ;

     SetWindowOrg (hdc, -330, 110) ;

     SetPolyFillMode (hdc, WINDING) ;
     Polygon (hdc, pt, sizeof (pt) / sizeof (POINT)) ;

     EndPaint (hwnd, &ps) ;
     DeleteObject (hPen) ;
     break ;

The PolyPolygon function draws multiple polygons.

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


Brushing the Interior

The interiors of the Rectangle, RoundRect, Ellipse, Chord, Pie, Polygon, and
PolyPolygon figures are filled in with the current brush (also sometimes
called a "pattern") selected in the device context. A brush is an 8-by-8
bitmap that is repeated horizontally and vertically to fill the area.

When Windows uses dithering to display more colors than are normally
available on a display, it actually uses a brush for the color. On a
monochrome system, Windows can use dithering of black and white pixels to
create 64 different shades of gray. More precisely, Windows can create 64
different monochrome brushes. For pure black, all bits in the 8-by-8 bitmap
are 0. One bit out of the 64 is made 1 (that is, white) for the first gray
shade, two bits are white for the second gray shade, and so on, until all
bits in the 8-by-8 bitmap are 1 for pure white. On a color video system,
dithered colors are also bitmaps, and a much wider range of color is
available.

We've already used stock brushes. Windows also has four functions that let
you create logical brushes. You select the brush into the device context
with SelectObject. Like logical pens, logical brushes are GDI objects. Any
brush that you create must be deleted, but it must not be deleted while it
is selected in the device context.

Here's the first function to create a logical brush:

hBrush = CreateSolidBrush (rgbColor) ;

The word Solid in this function doesn't really mean that the brush is a pure
color. When you select the brush into the device context, Windows creates an
8-by-8 bitmap for a dithered color and uses that bitmap for the brush. We
used CreateSolidBrush in the COLORS1 program in Chapter 6. The brush was
used as the background color defined in the window class structure.

You can also create a brush with "hatch marks" made up of horizontal,
vertical, or diagonal lines. Brushes of this style are most commonly used
for coloring the interiors of bar graphs and when drawing to plotters. The
function for creating a hatch brush is:

hBrush = CreateHatchBrush (nHatchStyle, rgbColor) ;

The nHatchStyle parameter describes the appearance of the hatch marks. The
parameter can be one of the following styles: HS_HORIZONTAL, HS_VERTICAL,
HS_FDIAGONAL, HS_BDIAGONAL, HS_CROSS, and HS_DIAGCROSS. Figure 12-13 shows
the kind of hatch marks that each of these styles produces.

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

The rgbColor parameter of CreateHatchBrush is the color of the hatch lines.
When you select the brush into a device context, Windows converts this color
to the nearest pure color. The area between the hatch lines is colored based
on the background mode and background color defined in the device context.
If the background mode is OPAQUE, the background color (which is also
converted to a pure color) is used to fill in the spaces between the lines.
In this case, neither the hatch lines nor the fill color can be a dithered
color. If the background mode is TRANSPARENT, Windows draws the hatch lines
without filling in the area between them.

Earlier I discussed the problems that you can encounter with pen colors when
you develop a program on a color display and later run the program on a
monochrome display. You should beware of the same problems when you choose
colors for hatch marks and the brush background. Colored hatch marks that
look fine on a color display may disappear into the background when
displayed in monochrome.

Because brushes are always 8-by-8 bitmaps, the appearance of hatch brushes
will also vary according to the resolution of the device on which they are
displayed. Each of the hatch marks shown in Figure 12-13 was drawn in a
32-by-16-pixel rectangle, which means that the 8-by-8 bitmap was repeated 4
times horizontally and 2 times vertically. On a 300-dots-per-inch laser
printer, the same 32-by-16-pixel rectangle would occupy an area about 1/9
inch wide and 1/19 inch high.

You can also create your own brushes based on bitmaps using
CreatePatternBrush:

hBrush = CreatePatternBrush (hBitmap) ;

This function was discussed in Chapter 9. The hBitmap parameter is a handle
to an 8-by-8 bitmap. How you get this bitmap handle is covered in the next
section of this chapter.

Windows also includes a function that encompasses the three other functions
for creating brushes (CreateSolidBrush, CreateHatchBrush, and
CreatePatternBrush):

hBrush = CreateBrushIndirect (&logbrush) ;

The variable logbrush is a structure of type LOGBRUSH ("logical brush"). The
three fields of this structure are shown below. The value of the lbStyle
field determines how Windows interprets the other two fields:

lbStyle (WORD)  lbColor (DWORD)   lbHatch (short)
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
BS_SOLID        Color of brush    Ignored
BS_HOLLOW       Ignored           Ignored
BS_HATCHED      Color of hatches  Hatch brush style
BS_PATTERN      Ignored           Handle to bitmap

Earlier we used SelectObject to select a logical pen into a device context,
DeleteObject to delete a logical pen, and GetObject to get information about
a logical pen. You can use these same three functions with brushes. Once you
have a handle to a brush, you can select the brush into a device context
using SelectObject:

SelectObject (hdc, hBrush) ;

You can later delete a created brush with the DeleteObject function:

DeleteObject (hBrush) ;

Do not delete a brush that is currently selected into a device context,
however. If you need to obtain information about a brush, you can call
GetObject:

GetObject (hBrush, sizeof (LOGBRUSH), (LPSTR) &logbrush) ;

where logbrush is a structure of type LOGBRUSH.


Brushes and Bitmaps

When you use the CreatePatternBrush or CreateBrushIndirect function with the
lbStyle field set to BS_PATTERN, you first need a handle to a bitmap. The
bitmap must be least 8 pixels wide and 8 pixels high. If it's larger,
Windows uses only the upper left corner of the bitmap for the brush.

Because brushes and bitmaps are GDI objects, you must delete any that you
create in your program before the program terminates. When you create a
brush based on a bitmap, Windows makes a copy of the bitmap bits for use
when drawing with the brush. You can delete the bitmap immediately after
calling CreatePatternBrush (or CreateBrushIndirect) without affecting the
brush. Similarly, you can delete the brush without affecting the bitmap.

One method of getting a handle to a bitmap was discussed in Chapter 8. You
can use SDKPAINT to create a bitmap file (with the extension .BMP), include
that filename in a BITMAP statement in the resource script, and then load
the bitmap into your program. The LoadBitmap function returns a handle of
type HBITMAP:

hBitmap = LoadBitmap (hInstance, lpszBitmap) ;

The variable lpszBitmap is the name of the bitmap in the resource script
file.

The second method of getting a handle to a bitmap is to use this function:

hBitmap = CreateBitmap (nWidth, nHeight, nPlanes, nBitsPixel, lpBits) ;

To create a bitmap to use for a brush, nWidth and nHeight should both be set
to 8. If you want a monochrome bitmap, nPlanes and nBitsPixel should both be
set to 1. The variable lpBits is a long pointer to an array of bytes
containing the pixel pattern of the bitmap. You can set this parameter to
NULL if you want to create an uninitialized bitmap, in which case the bitmap
will contain random data.

The third method of getting a handle to a bitmap is this:

hBitmap = CreateCompatibleBitmap (hdc, nWidth, nHeight) ;

This creates a bitmap with the same number of color planes and the same
number of color bits per pixel as the device context indicated by hdc. (The
only reason this function requires hdc is to get this color information.)
The bitmap initially contains random data.

The final method for getting a handle to a bitmap requires a pointer to a
structure (here named bitmap) of type BITMAP:

hBitmap = CreateBitmapIndirect (&bitmap) ;

Watch out for the difference between the types HBITMAP and BITMAP. HBITMAP
is a handle to a bitmap. BITMAP is a structure that describes the bitmap.
The BITMAP structure has seven fields, as described in the following list.
Five of them are similar to the parameters of the CreateBitmap function.


ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
bmType                            Set to 0

bmWidth                           Width of bitmap in pixels

bmHeight                          Height of bitmap in pixels

bmWidthBytes                      Number of bytes in each raster line

bmPlanes                          Number of color planes

bmBitsPixel                       Number of adjacent color bits per pixel

bmBits                            Far pointer to an array of bytes
                                  containing the bitmap pattern



When you have a handle to a bitmap, you can use GetObject to obtain
information about the bitmap:

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

where bitmap is a structure of type BITMAP. However, GetObject does not copy
a valid far pointer into the bmBits field. To get the actual bits that make
up the bitmap, you can call:

GetBitmapBits (hBitmap, dwCount, lpBits) ;

GetBitmapBits copies dwCount number of bytes into an array whose address is
lpBits. You can also set the bits of a bitmap using the SetBitmapBits
function:

SetBitmapBits (hBitmap, dwCount, lpBits) ;

The bmBits field of the BITMAP structure and the lpBits parameter of the
CreateBitmap, SetBitmapBits, and GetBitmapBits functions are far pointers to
an array of bytes that define the bitmap pattern. The array of bytes begins
with the top scan line. Color bitmaps may be organized with multiple bits
per pixel or multiple color planes per scan line. (I'll discuss this more in
the next chapter.) If you wish to use a bitmap as a brush and be assured
that it will work on all devices, use a monochrome bitmap.

The total size of this array of bytes is equal to (using the fields of the
logical bitmap structure):

  bmPlanes * bmHeight * bmWidthBytes

The bmWidthBytes field is the width of each scan line in bytes. This value
must be the even number equal to or the next even number greater than:

  bmWidth * bmBitsPixel / 8

In other words, each scan line is padded with bytes if necessary to get an
integral number of words. The scan line is padded at the right because the
bytes are arranged from left to right. The most significant bit of the first
byte is the leftmost pixel.


Creating and Using Bitmap Brushes

We've covered the background information you need to create and use brushes
based on bitmaps. Now let's put that information to work. Let's say that you
want to draw a rectangle filled in with a brush that looks like little
bricks, as shown in Figure 12-14.

  (Figure 12-14. may be found in the printed book.)

The bitmap you need has a pixel pattern that looks like this:

This is a monochrome bitmap with a height and width of 8. Here are three
methods you can use to create a brush based on this bitmap.

Method one

Create an 8-by-8 monochrome bitmap in SDKPAINT that resembles the diagram
shown above and save it under the name BRICK.BMP. In your resource script
file, include a statement identifying this file as a bitmap and naming it
"Brick":

Brick BITMAP brick.bmp

Within your program, define variables of type HBITMAP (handle to a bitmap)
and HBRUSH (handle to a brush):

HBITMAP hBitmap ;
HBRUSH  hBrush ;

These two handles are set by the following statements:

hBitmap = LoadBitmap (hInstance, "Brick") ;
hBrush  = CreatePatternBrush (hBitmap) ;

When you have a valid device context, select the brush into the device
context and display the rectangle:

SelectObject (hdc, hBrush) ;
Rectangle (hdc, xLeft, yTop, xRight, yBottom) ;

When you release the device context, delete the brush and bitmap:

DeleteObject (hBrush) ;
DeleteObject (hBitmap) ;

You don't have to wait until you release the device context to delete the
bitmap--you can do so anytime after you create the brush based on the
bitmap.


Method two

This method defines the bitmap pixels within the program as an array of
eight unsigned integers. Each integer corresponds to a scan line in the
bitmap pattern. A 1 bit is used for white and a 0 bit for black:

HBITMAP hBitmap ;
HBRUSH  hBrush ;
static  WORD wBrickBits [] =
     { 0xFF, 0x0C, 0x0C, 0x0C, 0xFF, 0xC0, 0xC0, 0xC0 } ;

The bitmap is created using CreateBitmap by referencing this array of
integers:

hBitmap = CreateBitmap (8, 8, 1, 1, (LPSTR) wBrickBits) ;
hBrush = CreatePatternBrush (hBitmap) ;

SelectObject (hdc, hBrush) ;
Rectangle (hdc, xLeft, yTop, xRight, yBottom) ;

After you're finished with the brush and the bitmap (and the brush is no
longer selected in a valid device context), you delete the bitmap and the
brush:

DeleteObject (hBrush) ;
DeleteObject (hBitmap) ;

Rather than using an array of integers for the bitmap's bits, you can use
instead an array of unsigned characters. Because each scan line must contain
an even number of bytes, however, you have to insert a 0 after each byte:

static unsigned char cBrickBits [] =
          { 0xFF, 0, 0x0C, 0, 0x0C, 0, 0x0C, 0,
            0xFF, 0, 0xC0, 0, 0xC0, 0, 0xC0, 0 } ;


Method three

This method is similar to the second method except that you use the logical
bitmap and logical brush structures to create the bitmap and the brush.
Begin by defining these variables:

HBITMAP hBitmap ;
HBRUSH  hBrush ;
static  BITMAP   bitmap   = { 0, 8, 8, 2, 1, 1 } ;
static  LOGBRUSH logbrush = { BS_PATTERN, 0L } ;
static  WORD wBrickBits [] =
     { 0xFF, 0x0C, 0x0C, 0x0C, 0xFF, 0xC0, 0xC0, 0xC0 } ;

The last field of the logical bitmap structure remains uninitialized. This
field must contain a far pointer to the array of bytes that define the
bitmap pattern. Do not initialize the structure with this pointer, however.
Instead use an assignment statement:

bitmap.bmBits = (LPSTR) wBrickBits ;

Watch out when you assign a far address of a local data item to the field of
a structure. If the program's data segment is moved in memory (as it can be
following a GetMessage, LocalAlloc, or LocalReAlloc call), that far address
can become invalid. A good rule is to make this assignment immediately
before using the structure. After the assignment, you use this structure to
create the bitmap:

hBitmap = CreateBitmapIndirect (&bitmap) ;

Now that you have the handle to a bitmap, you can use the
CreateBrushIndirect function to create the brush:

logbrush.lbHatch = hBitmap ;
hBrush = CreateBrushIndirect (&logbrush) ;

SelectObject (hdc, hBrush) ;
Rectangle (hdc, xLeft, yTop, xRight, yBottom) ;

Later on, delete both the brush and the bitmap:

DeleteObject (hBrush) ;
DeleteObject (hBitmap) ;



Brush Alignment

When Windows fills in an area with a brush, it repeats the 8-by-8 bitmap
both horizontally and vertically. This brush's appearance can vary slightly,
depending on how Windows aligns the upper left corner of the bitmap with the
display surface. The attribute in the device context that determines this
alignment is called the "brush origin." This attribute is always expressed
in terms of screen coordinates. If you obtain a device context using
BeginPaint or GetDC, the brush origin is initially set to the upper left
corner of your window's client area. If the client area begins 30 pixels
from the left of the screen and 20 pixels from the top of the screen, the
brush origin is set to the point (30, 20).

Whenever Windows uses a brush within the client area, the upper left corner
of the brush's bitmap coincides with those client-area device points where
both x and y are multiples of 8. Let's take an example involving a hatch
brush with style HS_FDIAGONAL, which looks like this:

In MM_TEXT mode, when you draw a rectangle that is filled in with the
HS_FDIAGONAL brush, the downward hatch line will intersect the upper left
corner of the rectangle if this corner is at the logical point (8, 8).
However, the hatch line will be aligned 4 pixels from the top of the corner
if the rectangle begins at the logical point (8, 4).

In most cases, adjusting the brush origin is an unnecessary refinement to
your drawing, but sometimes you'll want to do it. The process involves three
steps:

  1.  Call UnrealizeObject for the brush. (Do not call UnrealizeObject for
      stock brushes.)

  2.  Set the brush origin with SetBrushOrg, remembering to use screen
      coordinates.

      3.

Select the brush into the device context using SelectObject.

One situation in which you'll need to change the brush origin is when you
want the background of a child window to blend in with the background of its
parent window. When you obtain a handle to the device context for the child
window, the brush origin will be the upper left corner of the child window.
You need to change the brush origin to be the upper left corner of the
parent window. (We did this when coloring child window controls in Chapter
6.) In this case, you're changing the brush origin in one device context so
that it coincides with the brush origin in another device context.

You'll also need to change brush origins when you draw several figures
sharing the same device context but you don't want the brushes to coincide.
For instance, suppose you want to use brushes to color the bars of a bar
chart. If you didn't care about brush alignment, you might draw each of the
bars using a function that looks like this:

void DrawBar1 (hdc, xLeft, yTop, xRight, yBottom, hBrush)
     HDC       hdc ;
     short     xLeft, yTop, xRight, yBottom ;
     HBRUSH    hBrush ;
     {
     HBRUSH    hBrushOriginal ;

     hBrushOriginal = SelectObject (hdc, hBrush) ;
     Rectangle (hdc, xLeft, yTop, xRight, yBottom) ;
     SelectObject (hdc, hBrushOriginal) ;
     }

This function simply selects the brush handle passed as a function parameter
(saving the handle to the brush originally selected), draws a rectangle, and
then selects the original brush back into the device context. If a program
used this routine to draw several adjacent bars that it filled in with the
HS_FDIAGONAL brush, the result would look like Figure 12-15.

Notice that the hatch lines for all three bars align, which has the
unfortunate effect of drawing the eye downward from left to right.

  (Figure 12-15. may be found in the printed book.)

To avoid this effect, you need to align the brush with each bar
individually, which you can accomplish with the following function:

void DrawBar2 (hwnd, hdc, xLeft, yTop, xRight, yBottom, hBrush)
     HWND      hwnd ;
     HDC       hdc ;
     short     xLeft, yTop, xRight, yBottom ;
     HBRUSH    hBrush ;
     {
     HBRUSH    hBrushOriginal ;
     POINT     pt ;

     UnrealizeObject (hBrush) ;

     pt.x = xLeft ;
     pt.y = yTop ;
     LPtoDP (hdc, &pt, 1) ;
     ClientToScreen (hwnd, &pt) ;

     SetBrushOrg (hdc, pt.x, pt.y) ;

     hBrushOriginal = SelectObject (hdc, hBrush) ;
     Rectangle (hdc, xLeft, yTop, xRight, yBottom) ;
     SelectObject (hdc, hBrushOriginal) ;
     }

You "unrealize" the brush by calling UnrealizeObject and then set the brush
origin to the upper left corner of the bar that's being drawn. This requires
translating the upper left corner of the bar to client-area coordinates
using LPtoDP and then to screen coordinates using ClientToScreen. The result
is shown in Figure 12-16. Now the hatch lines begin in the upper left corner
of each bar and do not align from one bar to the next.

  (Figure 12-16. may be found in the printed book.)



RECTANGLES, REGIONS, AND CLIPPING

Windows includes several additional drawing functions that work with RECT
(rectangle) structures and "regions." A region is an area of the screen that
is a combination of rectangles, other polygons, and ellipses.

Working with Rectangles

These three drawing functions require a pointer to a rectangle structure:

FillRect (hdc, &rect, hBrush) ;
FrameRect (hdc, &rect, hBrush) ;
InvertRect (hdc, &rect) ;

In these functions, the rect parameter is a structure of type RECT with four
fields: left, top, right, and bottom. The coordinates in this structure are
treated as logical coordinates.

FillRect fills the rectangle (up to but not including the right and bottom
coordinate) with the specified brush. This function doesn't require that you
first select the brush into the device context. We used the FillRect
function in the ROP2LOOK program earlier in this chapter to color the
background with five stock brushes.

FrameRect uses the brush to draw a rectangular frame, but it does not fill
in the rectangle. Using a brush to draw a frame may seem a little strange,
because with the functions that you've seen so far (such as Rectangle), the
border is drawn with the current pen. FrameRect allows you to draw a
rectangular frame that isn't necessarily a pure color. This frame is one
logical unit wide. If logical units are larger than device units, then the
frame will be 2 or more pixels wide.

InvertRect inverts all the pixels in the rectangle, turning ones to zeros
and zeros to ones. This function turns a white area to black, a black area
to white, and a green area to magenta.

Windows also includes nine functions that allow you to manipulate RECT
structures easily and cleanly. For instance, to set the four fields of a
RECT structure to particular values, you would conventionally use code that
looks like this:

rect.left   = xLeft ;
rect.top    = xTop ;
rect.right  = xRight ;
rect.bottom = xBottom ;

By calling the SetRect function, however, you can achieve the same result
with a single line:

SetRect (&rect, xLeft, yTop, xRight, yBottom) ;

The other eight functions can also come in handy when you want to do one of
the following:

  þ   Move a rectangle a number of units along the x and y axes:

      OffsetRect (&rect, x, y) ;

  þ   Increase or decrease the size of a rectangle:

      InflateRect (&rect, x, y) ;

  þ   Set the fields of a rectangle equal to 0:

      SetRectEmpty (&rect) ;

  þ   Copy one rectangle to another:

      CopyRect (&DestRect, &SrcRect) ;

  þ   Obtain the intersection of two rectangles:

      IntersectRect (&DestRect, &SrcRect1, &SrcRect2) ;

  þ   Obtain the union of two rectangles:

      UnionRect (&DestRect, &SrcRect1, &SrcRect2) ;

  þ   Determine if a rectangle is empty:

      bEmpty = IsRectEmpty (&rect) ;

  þ   Determine if a point is in a rectangle:

      bInRect = PtInRect (&rect, point) ;

      In most cases, the equivalent code for these functions is simple.
      Sometimes, you'll find that using one of these functions actually
      increases the size of your .EXE file. In some instances, in fact,
      equivalent code even takes up less space in your source code file. For
      example, you can duplicate the CopyRect function call with:

      DestRect = SrcRect ;


Creating and Painting Regions

A region is a description of an area of the display that is a combination of
rectangles, other polygons, and ellipses. You can use regions for drawing or
for clipping. You use a region for clipping by selecting the region into the
device context.

Like pens, brushes, and bitmaps, regions are GDI objects. (The fifth and
final type of GDI object is the logical font, which we'll look at in Chapter
15.) You must treat regions the same way you treat the other GDI objects:
Delete any regions that you create, but don't delete a region while it is
selected in the device context.

When you create a region, Windows returns a handle to the region of type
HRGN. The simplest type of region describes a rectangle. You can create a
rectangular region in one of two ways:

hRgn = CreateRectRgn (xLeft, yTop, xRight, yBottom) ;

or:

hRgn = CreateRectRgnIndirect (&rect) ;

Regions are always expressed in terms of device coordinates.

You can also create elliptical regions using:

hRgn = CreateEllipticRgn (xLeft, yTop, xRight, yBottom) ;

or:

hRgn = CreateEllipticRgnIndirect (&rect) ;

The CreatRoundRectRgn creates a rectangular region with rounded corners.

Creating a polygonal region is similar to using the Polygon function:

hRgn = CreatePolygonRgn (&point, nCount, nPolyFillMode) ;

The point parameter is an array of structures of type POINT, nCount is the
number of points, and nPolyFillMode is either ALTERNATE or WINDING. You can
also create multiple polygonal regions using CreatePolyPolygonRgn.

So what, you say? What makes these regions so special? Here's the function
that unleashes the power of regions:

nRgnType = CombineRgn (hDestRgn, hSrcRgn1, hSrcRgn2, nCombine) ;

This combines two source regions (hSrcRgn1 and hSrcRgn2) and causes the
destination region handle (hDestRgn) to refer to that combined region. All
three region handles must be valid, but the region previously described by
hDestRgn is destroyed. (When you use this function, you might want to make
hDestRgn refer initially to a small rectangular region.)

The nCombine parameter describes how the hSrcRgn1 and hSrcRgn2 regions are
to be combined:

nCombine Value  New Region
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
RGN_AND         Overlapping area of the two source regions
RGN_OR          All the two source regions
RGN_XOR         All the two source regions excluding the overlapping area
RGN_DIFF        All of hSrcRgn1 not in hSrcRgn2
RGN_COPY        The hSrcRgn1 made the same as hSrcRgn2

The nRgnType value returned from CombineRgn is one of the following:
NULLREGION, indicating an empty region; SIMPLEREGION, indicating a simple
rectangle, ellipse, or polygon; COMPLEXREGION, indicating a combination of
rectangles, ellipses, or polygons; and ERROR, meaning that an error has
occurred.

Once you have a handle to a region, you can use it with four drawing
functions:

FillRgn (hdc, hRgn, hBrush) ;
FrameRgn (hdc, hRgn, hBrush, xFrame, yFrame) ;
InvertRgn (hdc, hRgn) ;
PaintRgn (hdc, hRgn) ;

The FillRgn, FrameRgn, and InvertRgn functions are similar to the FillRect,
FrameRect, and InvertRect functions. The xFrame and yFrame parameters to
FrameRgn are the width and height of the frame to be painted around the
region. Although regions always use device coordinates, these two parameters
are specified in terms of logical units. The PaintRgn function fills in the
region with the brush currently selected in the device context.

When you are finished with a region, you can delete it using the same
function that deletes other GDI objects:

DeleteObject (hRgn) ;


Clipping with Rectangles and Regions

Regions can also play a role in clipping. (I discussed clipping in Chapter 2
when discussing the various SYSMETS programs.) The InvalidateRect function
invalidates a rectangular area of the display and generates a WM_PAINT
message. Often we use the InvalidateRect function to erase the client area
and generate a WM_PAINT message:

InvalidateRect (hwnd, NULL, TRUE) ;

You can obtain the coordinates of the invalid rectangle by calling
GetUpdateRect, and you can validate a rectangle of the client area using the
ValidateRect function. When you receive a WM_PAINT message, the coordinates
of the invalid rectangle are available from the PAINTSTRUCT structure that
is filled in by the BeginPaint function. This invalid rectangle also defines
a "clipping region." You cannot paint outside the clipping region.

Windows has two functions similar to InvalidateRect and ValidateRect that
work with regions rather than rectangles:

InvalidateRgn (hwnd, hRgn, bErase) ;

and:

ValidateRgn (hwnd, hRgn) ;

However, when you receive a WM_PAINT message as a result of an invalid
region, the clipping region will still be a rectangle that encompasses the
invalid region.

If you want a nonrectangular clipping region, you can select a region into
the device context using either:

SelectObject (hdc, hRgn) ;

or:

SelectClipRgn (hdc, hRgn) ;

SelectObject returns a handle to the previous clipping region selected in
the device context, whereas SelectClipRgn returns an nRgnType value like
CombineRgn. Windows also includes several functions to manipulate this
clipping region, such as ExcludeClipRect to exclude a rectangle from the
clipping region, IntersectClipRect to create a new clipping region that is
the intersection of the previous clipping region and a rectangle, and
OffsetClipRgn to move a clipping region to another part of the client area.


The CLOVER Program

The CLOVER program forms a region out of four ellipses, selects this region
into the device context, and then draws a series of lines emanating from the
center of the window's client area. The lines appear only in the area
defined by the region. The resulting display is shown in Figure 12-17.

To draw this graphic by conventional methods, you would have to calculate
the end point of each line based on formulas involving the circumference of
an ellipse. By using a complex clipping region, you can draw the lines and
let Windows determine the end points. The CLOVER program is shown in Figure
12-18, beginning on the following page.

  (Figure 12-17. may be found in the printed book.)

 CLOVER.MAK

#----------------------
# CLOVER.MAK make file
#----------------------

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

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

 CLOVER.C

/*--------------------------------------------------
   CLOVER.C -- Clover Drawing Program Using Regions
               (c) Charles Petzold, 1990
  --------------------------------------------------*/

#include 
#include 
#define TWO_PI (2.0 * 3.14159)

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

int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
                    LPSTR lpszCmdLine, int nCmdShow)
     {
     static char szAppName[] = "Clover" ;
     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, "Draw a Clover",
                          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 HRGN  hRgnClip ;
     static short cxClient, cyClient ;
     double       fAngle, fRadius ;
     HCURSOR      hCursor ;
     HDC          hdc ;
     HRGN         hRgnTemp [6] ;
     PAINTSTRUCT  ps ;
     short        i ;

     switch (message)
          {
          case WM_SIZE :
               cxClient = LOWORD (lParam) ;
               cyClient = HIWORD (lParam) ;

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

               if (hRgnClip)
                    DeleteObject (hRgnClip) ;

               hRgnTemp [0] = CreateEllipticRgn (0, cyClient / 3,
                                        cxClient / 2, 2 * cyClient / 3) ;
               hRgnTemp [1] = CreateEllipticRgn (cxClient / 2, cyClient / 3,
                                        cxClient, 2 * cyClient / 3) ;
               hRgnTemp [2] = CreateEllipticRgn (cxClient / 3, 0,
                                        2 * cxClient / 3, cyClient / 2) ;
               hRgnTemp [3] = CreateEllipticRgn (cxClient / 3, cyClient / 2,
                                        2 * cxClient / 3, cyClient) ;
               hRgnTemp [4] = CreateRectRgn (0, 0, 1, 1) ;
               hRgnTemp [5] = CreateRectRgn (0, 0, 1, 1) ;
               hRgnClip     = CreateRectRgn (0, 0, 1, 1) ;

               CombineRgn (hRgnTemp [4], hRgnTemp [0], hRgnTemp [1], RGN_OR)
;
               CombineRgn (hRgnTemp [5], hRgnTemp [2], hRgnTemp [3], RGN_OR)
;
               CombineRgn (hRgnClip,     hRgnTemp [4], hRgnTemp [5],
RGN_XOR) ;

               for (i = 0 ; i < 6 ; i++)
                    DeleteObject (hRgnTemp [i]) ;

               SetCursor (hCursor) ;
               ShowCursor (FALSE) ;
               return 0 ;

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

               SetViewportOrg (hdc, cxClient / 2, cyClient / 2) ;
               SelectClipRgn (hdc, hRgnClip) ;

               fRadius = hypot (cxClient / 2.0, cyClient / 2.0) ;

               for (fAngle = 0.0 ; fAngle < TWO_PI ; fAngle += TWO_PI / 360)
                    {
                    MoveTo (hdc, 0, 0) ;
                    LineTo (hdc, (short) ( fRadius * cos (fAngle) + 0.5),
                                 (short) (-fRadius * sin (fAngle) + 0.5)) ;
                    }
               EndPaint (hwnd, &ps) ;
               return 0 ;

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

 CLOVER.DEF

;-----------------------------------
; CLOVER.DEF module definition file
;-----------------------------------

NAME           CLOVER

DESCRIPTION    'Clover Drawing Using Regions (c) Charles Petzold, 1990'
EXETYPE        WINDOWS
STUB           'WINSTUB.EXE'
CODE           PRELOAD MOVEABLE DISCARDABLE
DATA           PRELOAD MOVEABLE MULTIPLE
HEAPSIZE       1024
STACKSIZE      8192
EXPORTS        WndProc

Because regions always use device coordinates, the CLOVER program has to
re-create the region every time it receives a WM_SIZE message. This takes
several seconds. CLOVER begins by creating four elliptical regions that are
stored as the first four elements of the hRgnTemp array. Then the program
creates three "dummy" regions:

hRgnTemp [4] = CreateRectRgn (0, 0, 1, 1) ;
hRgnTemp [5] = CreateRectRgn (0, 0, 1, 1) ;
hRgnClip     = CreateRectRgn (0, 0, 1, 1) ;

The two elliptical regions at the left and right of the client area are
combined:

CombineRgn (hRgnTemp [4], hRgnTemp [0], hRgnTemp [1], RGN_OR) ;

Similarly, the two elliptical regions at the top and bottom of the client
area are combined:

CombineRgn (hRgnTemp [5], hRgnTemp [2], hRgnTemp [3], RGN_OR) ;

Finally, these two combined regions are in turn combined into hRgnClip:

CombineRgn (hRgnClip, hRgnTemp [4], hRgnTemp [5], RGN_XOR) ;

The RGN_XOR identifier is used to exclude overlapping areas from the
resultant region. Finally, the six temporary regions are deleted:

for (i = 0 ; i < 6 ; i++)
     DeleteObject (hRgnTemp [i]) ;

The WM_PAINT processing is simple, considering the results. The viewport
origin is set to the center of the client area (to make the line drawing
easier), and the region created during the WM_SIZE message is selected as
the device context's clipping region:

SetViewportOrg (hdc, xClient / 2, yClient / 2) ;
SelectClipRgn (hdc, hRgnClip) ;

Now all that's left is drawing the lines--360 of them, spaced 1 degree
apart. The length of each line is the variable fRadius, which is the
distance from the center to the corner of the client area:

fRadius = hypot (xClient / 2.0, yClient / 2.0) ;

for (fAngle = 0.0 ; fAngle < TWO_PI ; fAngle += TWO_PI / 360)
     {
     MoveTo (hdc, 0, 0) ;
     LineTo (hdc, (short) ( fRadius * cos (fAngle) + 0.5),
                  (short) (-fRadius * sin (fAngle) + 0.5)) ;
     }

During processing of WM_DESTROY, the region is deleted:

DeleteObject (hRgnClip) ;



SOME MISCELLANEOUS GDI FUNCTIONS

A few additional drawing functions don't fit into any convenient categories.
These include FloodFill, DrawIcon, ScrollWindow, ScrollDC, LineDDA, and
LineProc.

FloodFill fills in an area with the current brush. The syntax is:

FloodFill (hdc, xStart, yStart, rgbColor) ;

This is the function that the Windows PAINTBRUSH program calls when you use
the "paint roller" icon. Starting at the logical point (xStart, yStart), the
function fills in an area until it encounters a boundary of rgbColor.

FloodFill doesn't work if the point (xStart, yStart) is rgbColor itself or
if the point is outside the clipping region. If you use FloodFill during
normal repainting of your client area, you might want to invalidate the
entire client area before calling BeginPaint to be sure that (xStart,
yStart) is in the clipping region.

The ExtFloodFill function has the following syntax:

ExtFloodFill (hdc, xStart, yStart, rgbColor, wFill) ;

ExtFloodFill is an extended version of FloodFill that (depending on the last
parameter) can fill to a boundary or over a surface the color of rgbColor.

DrawIcon draws an icon on the display:

DrawIcon (hdc, xStart, yStart, hIcon) ;

This function works only in the MM_TEXT mapping mode. (The DrawIcon function
appears in Chapter 9.)

Both ScrollWindow and ScrollDC scroll part of the window's client area. We
used ScrollWindow in the SYSMETS3 program in Chapter 2. It's not a GDI
function, so it always uses device units. The general syntax is:

ScrollWindow (hwnd, xScroll, yScroll, &rectScroll, &rectClip) ;

Note that the first parameter is a handle to a window rather than to a
device context. The section of the client area indicated by the rectScroll
rectangle structure is shifted right xScroll pixels and down yScroll pixels.
These two parameters can be negative if you use the function to scroll to
the left or up.

Only the area within the rectClip rectangle is affected by this scrolling,
so it makes sense to set rectClip only to be the same rectangle as
rectScroll, a larger rectangle, or NULL. If you want the area within
rectScroll to be scrolled without affecting anything outside the rectangle,
set rectClip to equal rectScroll. If it is acceptable for the area within
rectScroll to be shifted outside the rectangle, set rectClip to NULL. The
area uncovered by the scrolling is invalidated and generates a WM_PAINT
message. Because ScrollWindow is not a GDI function, it can't perform
clipping and will also scroll dialog boxes or child windows that may be
covering the surface of the client area.

ScrollDC is a GDI function and works differently. The syntax is:

ScrollDC (hdc, xScroll, yScroll, &rectScroll, &rectClip,
               hRgnUpdate, &rectUpdate) ;

When not set to NULL, the last two parameters return information to the
program. If you pass a region handle to Windows in the sixth parameter,
Windows sets the region to the invalid region uncovered by the scroll. If
you pass a pointer to a RECT structure in the last parameter, Windows sets
the structure to indicate the invalid rectangle uncovered by the scroll.

The LineDDA function calculates all the points on a straight line that
connects two given points. (DDA stands for "digital differential analyzer.")
The syntax is:

LineDDA (xStart, yStart, xEnd, yEnd, lpfnLineProc, lpData) ;

LineDDA requires a call-back function of the form:

void FAR PASCAL LineProc (x, y, lpData)
     short     x, y ;
     LPSTR     lpData ;
     {
[other program lines]
     }

LineProc must be included in the EXPORTS section of your module definition
(.DEF) file. The lpfnLineProc parameter you pass to LineDDA must be the
result of a MakeProcInstance call:

lpfnLineProc = MakeProcInstance (LineProc, hInstance) ;

When you call LineDDA, Windows calls LineProc once for each point on the
line connecting (xStart, yStart) and (xEnd, yEnd). The point is indicated by
the x and y parameters to LineProc. LineProc also receives the far pointer
to programmer-defined data that you supply in the LineDDA call.

Note that LineDDA does not require a handle to a device context. The
function is simply a line calculation algorithm that gives your program each
point it calculates. What you do with these points is up to you. In the
LINEDDA program, shown in Figure 12-19, I chose to draw a rectangle and
connect the corners of the rectangle with the corners of the client area.
Not satisfied with the mundane dotted and dashed line styles that Windows
allows one to use with more ease, I chose to draw the lines as a series of
tiny ellipses. The result is shown in Figure 12-20 on page 597.

 LINEDDA.MAK

#-----------------------
# LINEDDA.MAK make file
#-----------------------

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

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


 LINEDDA.C

/*----------------------------------------
   LINEDDA.C -- LineDDA Demonstration
                (c) Charles Petzold, 1990
  ----------------------------------------*/

#include 

long FAR PASCAL WndProc (HWND, WORD, WORD, LONG) ;
void FAR PASCAL LineProc (short, short, LPSTR) ;

HANDLE hInst ;

int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
                    LPSTR lpszCmdLine, int nCmdShow)
     {
     static char szAppName[] = "LineDDA" ;
     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) ;
          }

     hInst = hInstance ;

     hwnd = CreateWindow (szAppName, "LineDDA Demonstration",
                          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 FARPROC lpfnLineProc ;
     static short   cxClient, cyClient, xL, xR, yT, yB ;
     HDC            hdc ;
     PAINTSTRUCT    ps ;

     switch (message)
          {
          case WM_CREATE :
               lpfnLineProc = MakeProcInstance (LineProc, hInst) ;
               return 0 ;

;FC

          case WM_SIZE :
               xR = 3 * (xL = (cxClient = LOWORD (lParam)) / 4) ;
               yB = 3 * (yT = (cyClient = HIWORD (lParam)) / 4) ;
               return 0 ;

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

               LineDDA (xL, yT, xR, yT, lpfnLineProc, (LPSTR) &hdc) ;
               LineDDA (xR, yT, xR, yB, lpfnLineProc, (LPSTR) &hdc) ;
               LineDDA (xR, yB, xL, yB, lpfnLineProc, (LPSTR) &hdc) ;
               LineDDA (xL, yB, xL, yT, lpfnLineProc, (LPSTR) &hdc) ;

               LineDDA (0,       0,       xL, yT, lpfnLineProc, (LPSTR)
&hdc) ;
               LineDDA (cxClient, 0,       xR, yT, lpfnLineProc, (LPSTR)
&hdc) ;
               LineDDA (cxClient, cyClient, xR, yB, lpfnLineProc, (LPSTR)
&hdc) ;
               LineDDA (0,       cyClient, xL, yB, lpfnLineProc, (LPSTR)
&hdc) ;

               EndPaint (hwnd, &ps) ;
               return 0 ;

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

void FAR PASCAL LineProc (short x, short y, LPSTR lpData)
     {
     static short nCounter = 0 ;

     if (nCounter == 2)
          Ellipse (* (HDC far *) lpData, x - 2, y - 2, x + 3, y + 3) ;

     nCounter = (nCounter + 1) % 4 ;
     }

 LINEDDA.DEF

;------------------------------------
; LINEDDA.DEF module definition file
;------------------------------------
NAME           LINEDDA

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

  (Figure 12-20. may be found in the printed book.)

The LineDDA function is called eight times during processing of the WM_PAINT
message, once for each of the eight lines. The lpData parameter is the
address of the handle to the device context.

The LineProc function is short:

void FAR PASCAL LineProc (x, y, lpData)
     short        x, y ;
     LPSTR        lpData ;
     {
     static short nCounter = 0 ;

     if (nCounter == 2)
          Ellipse (* (HDC far *) lpData, x - 2, y - 2, x + 3, y + 3) ;

     nCounter = (nCounter + 1) % 4 ;
     }

Note that the nCounter variable is defined as static so that its value is
preserved between LineProc calls. It cycles through the values 0, 1, 2, and
3. When the value is 2, LineProc draws an ellipse centered on the point.


PROGRAMS THAT DRAW FOREVER

A fun program in any graphics system is one that runs "forever," simply
drawing a hypnotic series of rectangles with random sizes and colors. You
can create such a program in Windows, but it's not quite as easy as it first
seems. By now you should know that this is definitely not the way to do it:

case WM_PAINT :           // Very, very bad code !!!

     hdc = BeginPaint (hwnd, &ps) ;

     while (TRUE)
          {
          xLeft   = rand () % xClient ;
          xRight  = rand () % xClient ;
          yTop    = rand () % yClient ;
          yBottom = rand () % yClient ;
          nRed    = rand () & 255 ;
          nGreen  = rand () & 255 ;
          nBlue   = rand () & 255 ;

          hdc = GetDC (hwnd) ;
          hBrush = CreateSolidBrush (RGB (nRed, nGreen, nBlue)) ;
          SelectObject (hdc, hBrush) ;

          Rectangle (hdc, min (xLeft, xRight), min (yTop, yBottom),
                          max (xLeft, xRight), max (yTop, yBottom)) ;
          }
     EndPaint (hwnd, &ps) ;
     return 0 ;

Sure, this will work, but nothing else will. Because Windows is a
nonpreemptive multitasking environment, a program can't enter into an
infinite loop like this. This loop will stop all other processing in the
system.

One acceptable alternative is setting a Windows timer to send WM_TIMER
messages to your window function. For each WM_TIMER message, you obtain a
device context with GetDC, draw a random rectangle, and then release the
device context with ReleaseDC. But that takes some of the fun out of the
program, because the program can't draw the random rectangles as quickly as
possible. It must wait for each WM_TIMER message.

There must be plenty of "dead time" in Windows, time during which all the
message queues are empty and Windows is just sitting in a little loop
waiting for keyboard or mouse input. Couldn't we somehow get control during
that dead time and draw the rectangles, relinquishing control only when a
message is added to a program's message queue? That's one of the purposes of
the PeekMessage function. Here's one example of a PeekMessage call:

PeekMessage (&msg, NULL, 0, 0, PM_REMOVE) ;

The first four parameters (a pointer to a MSG structure, a window handle,
and two values indicating a message range) are identical to those of
GetMessage. Setting the second, third, and fourth parameters to NULL or 0
indicates that we want PeekMessage to return all messages for all windows in
the program. Like GetMessage, PeekMessage effectively yields control to
other programs if messages are waiting in the other programs' message
queues. Like GetMessage, PeekMessage returns messages only for window
functions in the program that makes the function call.

The last parameter to PeekMessage is set to PM_REMOVE if the message is to
be removed from the message queue. You can set it to PM_NOREMOVE if the
message isn't to be removed. This is why PeekMessage is a "peek" rather than
a "get"--it allows a program to check the next message in the program's
queue without actually removing it. GetMessage doesn't return control to a
program unless it retrieves a message from the program's message queue. But
PeekMessage will return under two conditions:

  þ   When there's a message in the program's message queue, in which case
      the return value of PeekMessage is TRUE (nonzero).

  þ   When there are no messages in the message queue of any program running
      under Windows, in which case the return value of PeekMessage is FALSE
      (0).

A message loop that uses PeekMessage rather than GetMessage essentially says
to Windows, "Let other programs run for a little while, but once they've
emptied their message queues, return control to me--I'm not finished with my
work." If two or more programs are running that use a PeekMessage loop to
retrieve messages, Windows uses a round-robin approach, returning control
sequentially to each program waiting with a PeekMessage call.

This allows us to replace the normal message loop, which looks like this:

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

with an alternative message loop like this:

while (TRUE)
     {
     if (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
          {
          if (msg.message == WM_QUIT)
               break ;

          TranslateMessage (&msg) ;
          DispatchMessage (&msg) ;
          }
     else
          {
[other program lines to do some work]
          }
     }
return msg.wParam ;

Notice that the WM_QUIT message is explicitly checked. You don't have to do
this in a normal message loop, because the return value of GetMessage is 0
when it retrieves a  WM_QUIT message. But PeekMessage uses its return value
to indicate whether a message was retrieved, so the check of WM_QUIT is
required.

If the return value of PeekMessage is TRUE, the message is processed
normally. If the value is FALSE, the program can do some work (such as
displaying yet another random rectangle) before returning control to
Windows.

(Although the Windows documentation notes that you can't use PeekMessage to
remove WM_PAINT messages from the message queue, this isn't really a
problem. After all, GetMessage doesn't remove WM_PAINT messages from the
queue either. The only way to

remove a WM_PAINT message from the queue is to validate the invalid regions
of the window's client area, which you can do with ValidateRect,
ValidateRgn, or a BeginPaint and EndPaint pair. If you process a WM_PAINT
message normally after retrieving it from the queue with PeekMessage, you'll
have no problems. What you can't do is use code like this to empty your
message queue of all messages:

while (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE)) ;

This statement removes and discards all messages from your message queue
except  WM_PAINT. If a WM_PAINT message is in the queue, you'll be stuck
inside the while loop forever.)

The Windows TERMINAL program uses a PeekMessage loop when it receives input
from a communications line. This allows TERMINAL to check "continuously" for
incoming data. The PRINT MANAGER program uses this technique to print on a
printer or plotter and--as you'll see in Chapter 15--a Windows program that
prints also includes a function with a PeekMessage loop. Armed with the
PeekMessage function, we can now write a program that relentlessly displays
random rectangles. The program, called RANDRECT, is shown in Figure 12-21.

 RANDRECT.MAK

#------------------------
# RANDRECT.MAK make file
#------------------------

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

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

 RANDRECT.C

/*------------------------------------------
   RANDRECT.C -- Displays Random Rectangles
                 (c) Charles Petzold, 1990
  ------------------------------------------*/

#include 
#include 

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

short cxClient, cyClient ;

int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
                    LPSTR lpszCmdLine, int nCmdShow)
     {
     static char szAppName[] = "RandRect" ;
     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, "Random Rectangles",
                          WS_OVERLAPPEDWINDOW,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          NULL, NULL, hInstance, NULL) ;

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

     while (TRUE)
          {
          if (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
               {
               if (msg.message == WM_QUIT)
                    break ;

               TranslateMessage (&msg) ;
               DispatchMessage (&msg) ;
               }
          else
               DrawRectangle (hwnd) ;
          }
     return msg.wParam ;
     }
long FAR PASCAL WndProc (HWND hwnd, WORD message, WORD wParam, LONG lParam)
     {
     switch (message)
          {
          case WM_SIZE :
               cxClient = LOWORD (lParam) ;
               cyClient = HIWORD (lParam) ;
               return 0 ;

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

void DrawRectangle (HWND hwnd)
     {
     HBRUSH hBrush ;
     HDC    hdc ;
     short  xLeft, xRight, yTop, yBottom, nRed, nGreen, nBlue ;

     xLeft   = rand () % cxClient ;
     xRight  = rand () % cxClient ;
     yTop    = rand () % cyClient ;
     yBottom = rand () % cyClient ;
     nRed    = rand () & 255 ;
     nGreen  = rand () & 255 ;
     nBlue   = rand () & 255 ;

     hdc = GetDC (hwnd) ;
     hBrush = CreateSolidBrush (RGB (nRed, nGreen, nBlue)) ;
     SelectObject (hdc, hBrush) ;

     Rectangle (hdc, min (xLeft, xRight), min (yTop, yBottom),
                     max (xLeft, xRight), max (yTop, yBottom)) ;

     ReleaseDC (hwnd, hdc) ;
     DeleteObject (hBrush) ;
     }

 RANDRECT.DEF

;-------------------------------------
; RANDRECT.DEF module definition file
;-------------------------------------

NAME           RANDRECT

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