Chapter

6 Graphical Output: Pixels, Lines, and Polygons

There are three types of graphical figures that can be produced by a Windows program. They are, from the simplest to the most complex: points, lines, and closed (bounded) areas. Points (or pixels) can be used to construct any of the more complex figures. A series of lines can be used to construct polygons. A Windows program is most efficient, however, when you use the most appropriate drawing command for the task. In Windows, this means that you should use the command that most closely generates the object you wish to draw instead of drawing the figure as a series of more primitive requests.

When you issue a complex drawing request (for example, drawing a filled area), GDI cooperates with the device driver to carry out your request. If the device driver supports the drawing request directly, GDI passes it unchanged to the driver. The driver itself may have highly optimized code to perform the drawing request or, as graphics processors become more common on video display adapters, the video hardware itself may actually execute the request. When GDI determines that the device driver cannot support the drawing request as issued, GDI itself breaks the operation into a series of simpler requests that the device driver can process.

You take maximum advantage of Windows graphical device hardware when you allow it to worry about the complexities of actually performing the drawing. That said, let's look at the drawing functions provided by Windows from the simplest to the most complex. This chapter covers the following topics:

Getting and Setting the Color of a Pixel

The GetPixel function returns a COLORREF containing the RGB color value of the pixel identified by the logical coordinates specified by the X and Y parameters:

COLORREF rgbColor ;

HDC hdc ;

int X, Y ;

.

.

rgbColor = GetPixel (hdc, X, Y) ;

You can retrieve the color value of a pixel only when the logical coordinates identify a point within the current clipping region. The GetPixel function returns -1 when the point is outside the current clipping region.

You can set the color of a particular pixel by calling the SetPixel function:

COLORREF SetPixel(HDC hdc, int x, int y, COLORREF color);

This function sets the pixel at the point identified by the logical coordinates X and Y to the nearest approximation to the specified color. The color that is actually used to paint the pixel is returned by the SetPixel function as an RGB color value. When the specified point is outside the current clipping region, the function call is ignored and the function returns -1. Even though the multiple pixels can have the same logical coordinate (depending on the mapping mode), the SetPixel function changes only one pixel.

COLORREF rgbActualColor, rgbDesiredColor ;

HDC hdc ;

int X, Y ;

.

.

rgbActualColor = SetPixel (hdc, X, Y, rgbDesiredColor) ;

For faster pixel setting, you can use the SetPixelV function. This takes the same arguments as the SetPixel function but has a different result type:

BOOL SetPixelV(HDC hdc, int x, int y, COLORREF color);

This function returns a Boolean value describing whether it succeeded or failed; if it failed, you can get more information by calling GetLastError. Generally it will fail because the pixel is outside the clipping region. Because it does not need to return the actual color value used, it is generally faster.

Lines, Pens, and Drawing Modes

Drawing lines us somewhat more interesting than drawing pixels. You can draw straight lines and curved lines. You can draw lines that are solid and lines that are broken. A line can be one pixel wide or as wide as you like. You can specify the color of the line as well as the color of the gaps in a broken line. You also can specify what should happen as the line draws over existing information on the display.

To draw a line, you must specify two sets of information about the line: its location and its appearance. You specify the location of a line as the parameters to one of the line-drawing functions. There are 11 functions that draw a line:

The GDI Queue

In Windows NT 3.x, the GDI runs in its own thread. To enhance performance, GDI commands are "bat-ch-ed" into a queue. When the queue is filled, the commands to draw lines, fill figures, and the like are flush-ed from the queue and the requested graphics are actually drawn. This means that the return codes from GDI operations such as LineTo and MoveToEx do not indicate the success of the drawing, but only that the command has been successfully queued. This also means that normally you don't know when commands are being executed. However, any GDI call that does not return a BOOL result will force the queue to be flushed first. In addition, you can explicitly force the queue to be flushed by calling the GdiFlush function. This function will return TRUE if every queued command executed successfully and FALSE if any command failed. This is discussed in more detail on page 363. In Windows 95 and Windows NT 4.x, there is no GDI queue.

Drawing Lines

Many of the line drawing functions utilize or modify the current pen position, an attribute of the DC. The current position is a point in the logical coordinate space. You can explicitly set or read this value. The collection of functions that draw lines are given in Table 6.1. This table also shows if a line drawing function changes or utilizes the current pen position.

Not all of these are available in all versions of Win32. We indicate their availability in the table. Note that Win32s has only the basic Win16-equivalent drawing functions. Therefore you must be prepared to deal with the fact that not all versions of Win32 have the full suite of functions available. If you expect your program to run on all versions of Win32, you can use only the common subset of line drawing functions. You can also, with careful programming, take advantage of as many features as are available; for example, by falling back to simpler functions if you find yourself running on Win32s, but using full functionality if you find yourself running on Windows NT.
Table 6.1: Line drawing and pen creation functions
Win32s Win32 API 3 Win32 API 4 Batch Pen Position
Function Name Used Modified Description
3 Y AngleArc 3 3 Draws a line segment and an arc, starting at the current pen position.
3 3 3 Y Arc Draws a segment of an ellipse.
3 Y ArcTo 3 3 Draws a segment of an ellipse.
3 3 3 CreatePen Creates a pen for line drawing, given the pen parameters. Implies round end caps and round joins.
3 3 3 CreatePenIndirect Creates a pen for line drawing, given a reference to the pen parameters. Implies round end caps and round joins.
3 3 ExtCreatePen Creates a pen for line drawing. Allows specification of end caps and joins.
3 3 GetArcDirection Obtains the last value set by DC creation or the SetArcDirection call.
3 3 3 GetCurrentPositionEx 3 Returns the current pen position.
3 3 3 Y LineTo 3 3 Draws a line from the current pen position to the specified coordinate.
3 3 3 Y MoveToEx 3 3 Moves the pen to a specified coordinate; optionally returns the previous pen position.
3 3 Y PolyBezier 3 Draws one or more Bézier curves; leaves the pen at the starting point.
3 3 Y PolyBezierTo 3 3 Draws one or more Bézier curves; moves the pen to the endpoint of the last curve drawn.
3 Y PolyDraw 3 3 Draws a set of line segments and Bézier curves.
3 3 3 Y Polyline Draws a series of line segments.
3 3 Y PolylineTo 3 3 Draws a series of line segments starting at the current pen position; moves the current pen position to the endpoint of the last segment drawn.
3 3 Y PolyPolyLine Draws multiple series of connected lines.
3 3 N SetArcDirection Sets the drawing direction for arc and rectangle functions.
AD_COUNTERCLOCKWISE
Figure drawn counter-clockwise.
AD_CLOCKWISE
Figure drawn clock-wise.
3 3 3 N SetBkColor Sets the background color used to fill in the gaps in dashed or dotted lines. See SetBkMode.
3 3 3 N SetBkMode Sets the background mode. This determines what happens to the color in the gaps of dashed or dotted lines.
OPAQUE The color set via SetBkColor is used to fill in the gaps.
TRANSPARENT The existing color that is drawn over -remains in the gaps.
3 3 Y StrokePath Draws a line along an arbitrary path.
Although PolyDraw is not available as a Windows 95 API call, a function PolyDraw95 that simulates PolyDraw is described, with its complete source code, in the Microsoft Knowledge Base article Q135059.

The simplest line-drawing function is the LineTo function. This function draws a line from the logical coordinate specified by the current pen position attribute of the specified device context up to, but not including, the logical coordinate specified by the parameters of the LineTo function. A typical call looks like the -following:

BOOL Status ;

Status = LineTo (hdc, xEnd, yEnd) ;

The initial default setting for the current pen position attribute of a device context is the logical coordinate (0, 0). The preceding statement, issued on a device context with default values for its attributes, draws a line using the currently selected pen from the logical coordinate (0, 0) up to, but not including, the point (xEnd, yEnd). When all default settings for a device context (mapping mode, window, and viewport origin) are used, the line begins in the upper-left corner of the display surface (client area, window, screen, or device, depending on the type of device context). The LineTo function returns a nonzero value when the line is drawn and zero otherwise. After the line is drawn, the current pen position attribute of the device context is changed to the position (xEnd, yEnd).

Often you will want to start a line not at (0, 0) but at general coordinates such as (xBegin, yBegin). The function changes the current pen position attribute of a device context without drawing anything. The MoveToEx function returns the previous pen position in the specified POINT structure, if you provide one (if you don't need the previous value, just provide NULL as the parameter). The MoveToEx function returns a BOOL value that indicates the success or failure of the function call.

Therefore, to draw a line from the logical coordinates (xBegin, yBegin) up to, but not including, the logical coordinates (xEnd, yEnd), do the following:

POINT pt ;

MoveToEx (hdc, xBegin, yBegin, &pt) ;

LineTo (hdc, xEnd, yEnd) ;

The MoveToEx function returns a BOOL value that indicates the success or failure of the function call.

The AngleArc, ArcTo, LineTo, PolyBezier, PolyBezierTo, PolyDraw, and PolyLineTo functions are the only line drawing functions that use the current pen position attribute, as indicated in Table 6.1 (MoveToEx "uses" the pen position to return the previous value). The AngleArc, ArcTo, LineTo, MoveToEx, PolyBezierTo, PolyDraw, and PolyLineTo functions are the only line drawing functions that change the current pen position attribute of a device context. You can retrieve the current pen position without changing it by calling the GetCurrentPositionEx function:

POINT pt ;

GetCurrentPositionEx (hdc, &pt) ;

The GetCurrentPositionEx function returns the previous pen position in the specified POINT structure.

The separate functions for specifying the beginning and ending coordinates for a line are convenient when you are drawing a series of connected line segments. You call the MoveToEx function once to establish the beginning of the series of line segments, and then you call the LineTo function to draw each connected segment. This method is best when you don't know the coordinates of all the points ahead of time. As you determine the next point, all you need to do is use the LineTo function to draw to it. However, when you know all the points, there is an alternative way to draw a series of connected line segments.

The Polyline function draws a series of connected line segments. You pass it the address of an array of POINT structures and the number of points in the array (as well as the obligatory device context). It draws a line from the first point in the array through subsequent points in the array up to, but not including, the last point in the array. The Polyline function produces exactly the same output that would be produced by using the MoveToEx and LineTo functions to move to each point in the array and then drawing a line to the next point in the array when you use an ordinary (called "cosmetic") pen. The Polyline function, however, does not use or update the current pen position attribute of the device context. Since you know what the last pen position will be, namely, the last pair of coordinates, you can explicitly MoveToEx to that point if you need to. Alternatively, you can use the PolyLineTo function, which does update the current pen position. But unlike the Polyline function, in which the first point of the array is the first point of the polyline, PolylineTo specifies in the array the second point of the polyline; the first point is the current pen position.

If you are using a "geometric pen", however, the behavior of PolyLine and PolyLineTo are somewhat different. In Windows NT, the end cap and line join characteristics of the geometric pen come into play, and you will get lines with the selected style. In Windows 95, geometric pens are supported only when stroking a path, a topic we discuss starting on page 384. If you want geometric pen support on both platforms, you need to use PolyLine or PolyLineTo to establish a path and then stroke that path.

PolyDraw allows you to draw a complex figure composed of MoveToEx, LineTo, PolyBezierTo and CloseFigure operations, all in one function call. We will discuss it in detail in the section on paths, because we need to see how it relates to a path.

PolyLine, Polygon, PolyPolygon, and PolyDraw have one major advantage over drawing the individual lines to form the shape: If you draw the individual lines with LineTo, each line ends with a shape called an end cap, for example, a rounded end, which is the default. But if you are drawing thick lines, you may want the shapes to be smoothly joined. You will see how to control this when we talk about the Win32 pens in detail, in particular the ExtCreatePen function. But for now, be aware that these composite drawing functions really do have a potentially different effect than using the individual line drawing functions on the same set of point coordinates.

The following code draws a 10 ¥ 10 logical unit square centered around the origin. Of course, unless the origin is moved, only a quarter of the square is visible. Not all the braces are absolutely required. We put them in to make the association of the coordinates more apparent:

POINT apt [] = { {-5, -5},

{ 5, -5},

{ 5, 5},

{-5, 5},

{-5, -5} } ;

Polyline (hdc, apt, DIM(apt)) ;

You should note that both the LineTo, PolylineTo, Polyline, and the PolyDraw functions draw up to, but not including, the terminal point. Because they do draw the starting point, you can connect line segments without writing any point on the line more than once. This becomes quite important when using some drawing modes in which the result of drawing a line depends partially on the contents of the display before drawing the line. Drawing modes are discussed in depth beginning on page 293.

The next line-drawing function is the Arc function, one of the several functions that draw curved lines. The Arc function draws a line from a portion of the perimeter of an ellipse. The general form of a call to the function looks like this:

Arc (hdc, xUL, yUL, xLR, yLR, xBegin, yBegin, xEnd, yEnd) ;

The two points (xUL, yUL) and (xLR, yLR) specify the upper-left and lower-right corners of a bounding rectangle. GDI draws the arc within the bounding rectangle. It uses the convention that a bounding rectangle specifies an area that includes the upper and left coordinates of the rectangle but excludes the lower and right coordinates of the rectangle. It is important to keep this in mind to avoid being off by one unit.

One and only one ellipse can be inscribed in a given rectangle, so specifying a bounding rectangle uniquely determines an ellipse. When the rectangle is a square, the ellipse is a circle. After you've specified the location and size of the ellipse, all that remains is to specify where on the perimeter of the ellipse the line segment should begin and end.

Windows, however, does not require you to calculate the exact coordinates of the beginning and ending points on the perimeter of the ellipse. The logical coordinates (xBegin, yBegin) are used as one end of an imaginary line to the point at the logical coordinates ((xUL+xLR)/2, (yUL+yLR)/2), which is the center of the rectangle and, therefore, the center of the ellipse. Windows uses the point of intersection of this imaginary line and the perimeter of the ellipse as the actual starting point for the line segment. Another imaginary line from the logical coordinate (xEnd, yEnd) to the center of the ellipse is similarly used to determine the actual ending point for the line segment.

The net effect is that the beginning and ending points passed to the Arc function need only be near where you want the line segment to begin and end. They do not need to be exactly on the perimeter of the ellipse. Windows draws the arc from the beginning point to the ending point in a counterclockwise direction around the perimeter of the ellipse. Figure 6.1 shows a line drawn with the Arc function. We've drawn the bounding rectangle, the ellipse the arc is based on, and the imaginary lines using a dotted pen. We drew the arc itself using a wider, solid pen.

Another arc-drawing function, ArcTo, is available in Windows NT but not in Windows 95 or Win32s. This works just like the Arc function, except that it also uses the current pen position. It draws a straight line segment between the current position and the specified starting point of the arc and updates the current pen position to be the endpoint of the elliptical segment. This effect can be seen also in -Figure 6.2.

The AngleArc function draws both a line segment and an arc. It draws the line segment from the current pen position to the beginning of the arc. The arc itself is drawn along the perimeter of a circle (unlike the Arc and ArcTo functions, which can draw along an ellipse). The center of the circle and its radius are specified as input parameters to the function. Two angles are specified: the angle at which the arc starts and the sweep angle, the number of degrees counterclockwise that the arc traverses. Note that these latter two values, unlike most angle functions found in the C math library, are specified in degrees, not radians. The following call will draw an arc 50 logical units in radius, starting at the current point and rotating for 30 degrees clockwise starting at the horizontal positive axis:

POINT pt;

GetCurrentPointEx(hdc, &pt);

AngleArc(hdc, pt.x - 50, pt.y, 50, 0.0, -30.0);

The results are shown in Figure 6.3.

Win32 can draw compound curves using the PolyBezier and PolyBezierTo functions. These are not available in Win32s. They allow you to draw a complex curve by using a method known as a cubic Bézier splines. A Bézier spline, named after the person who first described and studied them,1 is a curve that is specified by four control points, which we can call p1, p2, p3, and p4. The curve starts at the x,y position defined by p1 and ends at the x,y position defined by p4. The PolyBezier and PolyBezierTo take the first control point, p1, as the current position of the pen. Therefore, for a simple spline, you need to define only three control points: the points p2, p3, and p4. The "Poly" part of the name comes from the property that allows you to specify more than one group of three control points. For example, given that p1 is already specified, you can specify p2, p3, and p4 to get a single Bézier curve. But if you specify three additional points, p5, p6, and p7, then the function will draw a second spline, starting at p4 and using the additional points to define the endpoint (p7) and control points (p5, p6). You can specify as many triples of points as you wish. The PolyBezier function draws the curve(s) based on the specified points but leaves the current pen position unchanged. The PolyBezierTo function draws the curves exactly as PolyBezier but moves the pen to the endpoint of the last Bézier curve drawn.

A Bézier cubic spline is controlled by the two internal points. The curve starts from the first point specified, p1, and is drawn tangent to a line drawn between p1 and p2. The curve ends at the point p4 and is tangent to a line drawn between p3 and p4. Some examples are shown in Figure 6.4. The WM_PAINT handler that drew them is shown in Listing 6.1.

Listing 6.1: The WM_PAINT handler for the Bézier example
typedef struct {

int caption; // string id of caption

BYTE style[4]; // coordinate style

POINT pt[4];

} bezier;

bezier b1 = { IDS_BEZIER_DOUBLE_CURVE,

{ DT_RIGHT | DT_TOP,

DT_CENTER | DT_BOTTOM,

DT_CENTER | DT_TOP,

DT_CENTER | DT_BOTTOM},

{ 30, 40,

50, 20,

70, 100,

80, 40}

};

bezier b2 = { IDS_BEZIER_CROSSOVER,

{DT_RIGHT | DT_TOP,

DT_LEFT | DT_VCENTER,

DT_RIGHT | DT_VCENTER,

DT_CENTER | DT_TOP},

{120, 80,

200, 50,

110, 40,

170, 80}

};

bezier b3 = { IDS_BEZIER_SIMPLE_CURVE,

{DT_CENTER | DT_TOP,

DT_CENTER | DT_BOTTOM,

DT_CENTER | DT_BOTTOM,

DT_CENTER | DT_TOP},

{220, 60,

240, 40,

260, 40,

280, 60}

};

bezier b4 = { IDS_BEZIER_COMPLEX_CURVE,

{DT_CENTER | DT_TOP,

DT_RIGHT | DT_BOTTOM,

DT_LEFT | DT_BOTTOM,

DT_CENTER | DT_TOP},

{300, 40,

330, 10,

370, 5,

360, 40}

};

/****************************************************************

* drawDot

* Inputs:

* HDC hdc: display context

* LPPOINT pt: Point at which to draw dot

* BYTE style: Style control

* LPSTR label: Optional label, or NULL

* Result: void

*

* Effect:

* Draws the dot and labels it according to the given text

****************************************************************/

static void drawDot(HDC hdc, LPPOINT pt, BYTE style, LPSTR label)

{

RECT r;

HFONT f;

SIZE size;

SIZE space;

TEXTMETRIC tm;

int height;

#define DOTSIZE 1

Ellipse(hdc, pt->x - DOTSIZE,

pt->y - DOTSIZE,

pt->x + DOTSIZE,

pt->y + DOTSIZE);

if(label != NULL)

{ /* has text */

// Create a nice small font

f = createCaptionFont(-5, _T("Arial"));

SelectFont(hdc, f);

// Determine where to place our text

GetTextExtentPoint32(hdc, _T(" "), 1, &space);

GetTextExtentPoint32(hdc, label, lstrlen(label), &size);

GetTextMetrics(hdc, &tm);

height = tm.tmHeight + tm.tmInternalLeading;

r.left = pt->x;

r.top = pt->y;

if(style & DT_LEFT)

r.left = pt->x + space.cx;

else

if(style & DT_CENTER)

r.left = pt->x - size.cx / 2 - space.cx;

else

if(style & DT_RIGHT)

r.left = pt->x - size.cx - 2 * space.cx;

if(style & DT_TOP)

r.top = pt->y + 16 * DOTSIZE;

else

if(style & DT_VCENTER)

r.top = pt->y - height;

else

if(style & DT_BOTTOM)

r.top = pt->y - 2 * height;

r.bottom = r.top + 2 * height;

r.right = r.left + size.cx + 2 * space.cx;

// FrameRect(hdc, &r, GetStockBrush(BLACK_BRUSH));

DrawText(hdc, label, -1, &r,

DT_CENTER | DT_VCENTER | DT_SINGLELINE);

DeleteFont(f);

} /* has text */

}

/****************************************************************

* drawBezierDot

* Inputs:

* HDC hdc: display context

* bezier * b: Bezier descriptor

* int i: Index of bezier point to describe

* Result: void

*

* Effect:

* Draws an illustrative dot using the currently selected

* pen and brush. Draws the pen coordinates using the style

* parameters

****************************************************************/

static void drawBezierDot(HDC hdc, bezier * b, int i)

{

TCHAR coords[30];

wsprintf(coords,_T("%d,%d"), b->pt[i].x, b->pt[i].y);

drawDot(hdc, &b->pt[i], b->style[i], coords);

}

/****************************************************************

* drawBezier

* Inputs:

* HDC hdc: DC to draw in

* bezier * b: Bezier point description

* Result: void

*

* Effect:

* Draws a bezier curve, showing all the control points

****************************************************************/

static void drawBezier(HDC hdc, HWND hwnd, bezier * b)

{

HPEN showpen = CreatePen(PS_SOLID, 1, RGB(192,192,192)); example

HPEN curvepen = CreatePen(PS_SOLID, 2, RGB(0,0,0));

SelectPen(hdc, showpen);SelectPen

Polyline(hdc, &b->pt[0], 4);

SelectPen(hdc, curvepen);

if(!PolyBezier(hdc, &b->pt[0], 4))

{ /* no beziers */

PostMessage(hwnd, UWM_ERROR, IDS_BEZIER_FAILED,

IDS_NOT_SUPPORTED);

} /* no beziers */

else

{ /* finish drawing */

SelectPen(hdc, GetStockPen(BLACK_PEN));

SelectBrush(hdc, GetStockBrush(BLACK_BRUSH));

drawBezierDot(hdc, b, 0);

drawBezierDot(hdc, b, 1);

drawBezierDot(hdc, b, 2);

drawBezierDot(hdc, b, 3);

} /* finish drawing */

DeletePen(showpen);

DeletePen(curvepen);

}

/****************************************************************

* scaleToWindow

* Inputs:

* HDC hdc: Display context

* HWND hwnd: Window handle

* int width: Width to scale

* int height: Height to scale

* Result: void

*

* Effect:

* Scales the window. Maintains isotropic scaling so the

* entire image, whose dimensions are given as input

* parameters, will fit in the resulting window.

* min(hwnd.width()/width, hwnd.height/height)

****************************************************************/

void scaleToWindow(HDC hdc, HWND hwnd, int width, int height)

{

RECT r;

GetClientRect(hwnd, &r);

float dx = (float)r.right / (float)width;

float dy = (float)r.bottom / (float) height;

float scale = min(dx, dy);

SetMapMode(hdc, MM_ISOTROPIC);

SetWindowExtEx(hdc, 1000, 1000, NULL);

SetViewportExtEx(hdc, (int)(1000.0f * scale),

(int)(1000.0f * scale),

NULL);

}

/****************************************************************

* bezier_OnPaint

****************************************************************/

static void bezier_OnPaint(HWND hwnd)

{

PAINTSTRUCT ps;

HDC hdc = BeginPaint(hwnd, &ps);

int restore = SaveDC(hdc);

SetGraphicsMode(hdc, GM_ADVANCED);Set

translate(hdc, 0.0f, 20.0f);

scaleToWindow(hdc, hwnd, 400, 120);

drawBezier(hdc, hwnd, &b1);

drawBezier(hdc, hwnd, &b2);

drawBezier(hdc, hwnd, &b3);

drawBezier(hdc, hwnd, &b4);

RestoreDC(hdc, restore);

EndPaint(hwnd, &ps);

}

Creating Pens

As mentioned previously in the chapter, the appearance of a line is determined by the pen you use when drawing the line. Windows draws a line using the pen that is currently selected into the device context when you call the LineTo, Polyline, Arc, ArcTo, and the other drawing functions shown in Table 6.1. The default pen is a Windows stock object called BLACK_PEN. This pen draws a black line that is 1 pixel wide regardless of the current mapping mode. Windows has two more stock pens: WHITE_PEN and NULL_PEN. A WHITE_PEN draws a white line that is 1 pixel wide regardless of the current mapping mode. A NULL_PEN has no ink and does not leave a mark when you draw with it. (You occasionally use a NULL_PEN when drawing a filled polygon such as a rectangle. Using a NULL_PEN results in a rectangle with its interior filled by a brush but with no surrounding border.)

You call the GetStockPen macro API function (defined in windowsx.h as a call to the GetStockObject function) to get a handle to one of the stock pens. After you have the handle to the pen, you can select it into a device context by calling the SelectPen macro API function (defined in windowsx.h as a call to the SelectObject function). For example, to use a white stock pen rather than the default black stock pen, you can do the following:

HPEN hpen ;

hpen = GetStockPen (WHITE_PEN) ;

SelectPen (hdc, hpen) ;

All lines drawn after selecting the white pen into the device context will be drawn in white. As long as you need only a 1-pixel-wide pen that can write in black, white, or invisible ink, you can stick with Windows's stock pens. Generally, though, you will want a little more variety in pens. To get it, you must abandon the stock pens and create your own.

Creating, using, and deleting your own pen is a five-step process:

1. Create a logical pen using the CreatePen, the CreatePenIndirect, or the ExtCreate-Pen function.

2. Select the logical pen into a device context by calling the SelectPen function.

3. Draw the lines using this pen by calling any of the line drawing functions, such as LineTo, Polyline, or Arc.

4. Select either the original pen or a stock pen into the device context by calling the SelectPen function or by using RestoreDC specifying a context that was saved before you did the SelectPen operation. This replaces your created pen.

5. Delete the logical pen by calling the DeletePen macro API function (defined in windowsx.h as a call to the DeleteObject function). You must not delete a pen while it is selected into a device context.

The easiest way to create a custom pen is to call the -CreatePen function or the CreatePenIndirect function. You pass the style, width, and color of the pen as parameters, and it returns a handle of type HPEN to a logical pen. The call looks like the following:

HPEN hpen ;

hpen = CreatePen (PenStyle, Width, Color) ;

The PenStyle parameter specifies whether a line drawn with the created pen will be solid, broken, or invisible. The windows.h header file defines seven symbolic names for pen styles: two for solid lines, four for broken lines, and one for invisible lines. Figure 6.5 shows each symbolic name and the pattern produced by each style. The line samples in Figure 6.5 are all 1 pixel wide. The PS_SOLID and PS_INSIDEFRAME styles are also shown with a rectangle drawn with the pen selected and then overlaid with the same rectangle drawn with the stock black pen to show how the two pens relate to a rectangle. You can study all these effects using the DC Explorer application of Chapter 5.

The Width parameter normally specifies the width of the pen in logical units. When this parameter is 0, GDI draws a line that is one device-unit wide (1-pixel wide). When this parameter is 1 or greater, GDI draws a line that is Width-logical-unit-wide with half the width on each side of the line. The actual width of the line in device units depends on the mapping mode that's in effect. GDI converts the width of a pen from logical units to device units using the x-axis scaling factor as determined by the mapping mode.

The Width parameter also can affect the style of the drawn line. GDI cannot draw dotted or dashed lines with a pen created from CreatePen or CreatePenIndirect unless the pen is 1 pixel wide. When the Width parameter specifies a logical width that translates to physical width greater than 1, GDI draws with a solid pen of that width even when you request a dotted or dashed pen style. You always get a null pen when you request one, regardless of the physical width of the pen. You cannot create a pen to draw dashed or dotted lines wider than 1 pixel using CreatePen; you must use ExtCreatePen to get these.

GDI also handles the width slightly differently in some situations when the pen style is PS_INSIDEFRAME. The PS_INSIDEFRAME style specifies that the mark left by a wide pen should be positioned differently than usual when drawing an arc, a chord, an ellipse, a rectangle, or a rounded rectangle. All these are figures that are drawn relative to a specified bounding rectangle. PS_INSIDEFRAME will not apply to path stroking or region outlining, topics we have not yet discussed (we do so starting on page 384).

Normally, wide pens distribute the mark equally on each side of the line. This causes part of the line to extend outside the bounding rectangle. When you use the PS_INSIDEFRAME style, GDI does not center the width around the line but offsets the pen and draws the line entirely inside the bounding rectangle. This is shown in Figure 6.5. The rectangles are drawn in a thick gray pen, and then another rectangle with identical coordinates is drawn with a 1-pixel pen. Note that for PS_SOLID, the thick pen surrounds the rectangle border, but for PS_INSIDEFRAME the thick pen is entirely within the rectangle border. A PS_INSIDEFRAME pen will also draw using dithered colors if necessary; all other pens will be rendered to the nearest pure color that is found in the current palette. If you need dithered colors, use ExtCreatePen.

The third parameter is a COLORREF value specifying the desired color of the logical pen; GDI can use a different actual color. When you select a logical pen into a device context and the pen style is not PS_INSIDEFRAME, GDI uses the nearest pure color that the device can represent for the pen. GDI will use a dithered color only for PS_INSIDEFRAME-style pens that have a physical width greater than 1.

The CreatePenIndirect function can also be used to create a logical pen. This function requires the same style, width, and color information as the CreatePen function; however, the information is passed to Windows a little differently. Instead of passing each item as a separate parameter, you fill in a LOGPEN structure and pass the address of the structure as the only parameter to the CreatePenIndirect function. The LOGPEN structure looks like the following:

typedef struct tagLOGPEN {

UINT lopnStyle ;

POINT lopnWidth ;

COLORREF lopnColor ;

} LOGPEN ;

The lopnStyle field holds the pen styles, which can be any one of the values listed in Figure 6.5. The lopnWidth field is a POINT structure. You must store the width of the pen in the x-coordinate field of the point. The y-coordinate value in the POINT structure is not used. The lopnColor field holds the desired color for the pen.

For example, a call to the CreatePenIndirect function to create a palette-relative, red, dashed, logical pen looks like the following:

HPEN hpen ;

LOGPEN logpen ;

logpen.lopnStyle = PS_DASH;

logpen.lopnWidth.x = 0 ;

logpen.lopnColor = PALETTERGB (255, 0, 0) ;

hpen = CreatePenIndirect (&logpen) ;

You also can ask Windows to copy information about an existing pen into a LOGPEN structure. The GetObject function can return information about an existing pen, brush, font, bitmap, or palette. The following code retrieves information about a pen given the handle to the pen:

LOGPEN logpen ;

GetObject ((HGDIOBJ) hpen, sizeof (logpen), (LPVOID) &logpen) ;

You may have noticed that we've been careful to call these pens logical pens. Like logical coordinates and logical units, a logical pen is a distinct entity from the physical pen used to draw into a device context. When you select a logical pen into a device context, only then are the physical width, style, and color actually determined. The same logical pen can produce differing results when used in two different device contexts.

It's worth repeating that pens (as well as brushes, bitmaps, regions, fonts, and palettes) are GDI objects. GDI objects are actually owned by GDI, and their space is managed by the GDI. A Windows program should explicitly delete all GDI objects it creates before terminating. It should also release each GDI object as soon as it is done with it. Failure to do so can cause a "resource leak". In Windows NT, this space is private to each running application and will only result in your application's becoming bulkier and bulkier as more GDI resources are unreclaimed. In Windows 95, the resources will be also be reclaimed when your program terminates, but while it is running, these resources consume shared GDI space. Although this space is very large, it is not unlimited. You can eventually fill it up, thereby causing Windows 95 to exhibit strange behavior. And in Win32s, you can lock up all of Windows when the very limited (64K) GDI space becomes glutted with objects.

Another good reason for having a good GDI-resource-deletion strategy deals with a number of third-party development tools that track resource allocation and deallocation. These tools will give you a post-mortem analysis of which resources you have allocated and failed to release. A not-uncommon scenario is that you allocate, say, 20 GDI objects at initialization and don't bother to delete them because you know they will be deleted anyway when the program term-inates. When you examine the post-mortem resource analysis, you see a large number of pens not deleted, so you ignore this part, knowing they were the initial pens. It turns out you have a re-source leak, which "leaks" one pen every time a certain operation is done. In your testing, you do that operation once. The difference between 20 and 21 unreleased pens is not easy to see and therefore might be missed. However, under actual operating conditions, this operation might be done hundreds of times, thus leaving hundreds of pens glutting up the GDI space. If you ar-range that all of your pens should be deleted, then any undeleted pen tells you immediately that you have a resource leak, which you can find and fix before the program goes into general use.

But you can't simply delete every pen you use. You must not delete a custom pen that is currently selected into a device context. You must take care to assure that each created pen is destroyed only after you're done using it. It is not sufficient to assume that because you have released a DC that the DC no longer exists and therefore the objects can be deleted. This is because you might have class-based or window-based DCs that continue to exist, as specified by the CS_CLASSDC or CS_OWNDC flags we discussed in Chapter 5.

You can create a pen of any style, width, and color you desire. Of course, the pen you're given by GDI occasionally will look a bit different than the one you expected. We already men-tioned most of the caveats. Being aware of those caveats will enable you to actually create a pen that draws exactly the way you expect-with three additions in the case of CreatePen.

You cannot specify the style of the end caps for a line written with a "fat pen" (a pen of width greater than 0) created by CreatePen or CreatePenIndirect. The end caps are always circles with a diameter equal to the pen width. Use ExtCreatePen if you need to control the end caps. The only way to get a flat side on the end of a fat line in Win32s is to adjust the clip-ping region so that the end of the line is clipped.

The color of the gaps in the line drawn by a dashed or a dotted pen isn't specified by the pen but by two other attributes of the device context in which the pen is selected: the background mode and the background color. When the background mode established by SetBkMode is TRANSPARENT, the gaps in a line are not filled in. The original background is left untouched. When the background mode is OPAQUE, the gaps in a line are filled with the color indicated by the background color attribute of the device context as established by the SetBkColor function.

Now that you have completely specified what the mark left by a pen looks like, there comes the process of transferring the "ink" to the drawing surface. Windows supports the obvious need to draw on a display, overwriting anything already there, but there are 15 other ways of copying the "ink" to the drawing surface. These are called the drawing modes.

Drawing Modes

Windows uses the drawing mode attribute of a device context to determine how to copy the "ink" from a pen onto the drawing surface of the device context. When you draw with a pen, the pixel values of the "ink" of the pen actually are combined with the pixel values of the display surface in a bitwise Boolean operation. The drawing mode specifies the particular Boolean operation to use.

The SetROP2 function changes the drawing mode of a device context:

int SetROP2 (HDC hdc, int DrawMode) ;

You can retrieve the current drawing mode by calling the GetROP2 function, as follows:

int DrawMode ;

DrawMode = GetROP2 (hdc) ;

The "ROP" stands for Raster OPeration, the term used to describe a bitwise Boolean operation on the pixels of a raster display device. The "2" in "ROP2" derives from the two operands: the pixels written by the pen and the pixels on the display surface. These are binary raster operations because two operands are used. Later you'll see ternary raster operations in which a third operand is involved and quaternary raster operation that involves four operands.

There are 16 ROP2 codes. This number is derived from the 16 possible outcomes of combining a pen and a monochrome display surface. The pen can be either black or white. Likewise, the display surface pixel can be either black or white. There are four possible combinations of the pen and the display surface: a white pen on a white surface, a white pen on a black surface, a black pen on a white surface, and a black pen on a black surface. You must describe to GDI what the dest-ination pixel's value should be for each of the four combinations of pen and destination. There are 16 such descriptions. One possible description would be as follows:

When the pen and the destination have the same color (that is, white and white or black and black), set the destination pixel to black. But when the pen is white and the destination is black, or vice versa, set the destination pixel to white. This is called the R2_XORPEN drawing mode. The symbolic names defined in windows.h for the raster operation codes are not assigned ar-bi-trary values. Instead, each symbolic name is assigned the decimal result value listed in Table 6.2, plus 1. This result value is produced by performing the Boolean operation listed in the right-most column on all four possible combinations of pen color and destination color. These symbolic names and their associated Boolean operations are listed in Table 6.2.

All possible combinations are represented in this table, but some are less useful than others. For example, R2_NOP derives its name from the fact that no matter what color of pen you use, the destination remains unchanged. The drawing operation performs no operation. The R2_BLACK and R2_WHITE raster operations always write black pixels and white pixels, respectively, to the destination no matter what the pen color or original color of the destination. One useful drawing mode is R2_NOT. This mode inverts the color of the destination pixels written by the pen. The pen color isn't used by the operation. Because the line is written in the inverse color of the destination, it is generally visible no matter what the destination color.

The default drawing mode for a device context is the R2_COPYPEN mode. As you can see in Table 6.2, the result of the Boolean operation on the pen and destination pixel is always the color of the pen, no matter what the destination color is. This copies the pen color to the destination surface. Basically, the pen overwrites anything already present. In the table, black is repre-sented by a 0, and white by a 1.
Table 6.2: Binary raster operation codes
Drawing Mode Pen(P) 1 1 0 0 Decimal Result Boolean Operation
Dest(D) 1 0 1 0
R2_BLACK 0 0 0 0 0 0
R2_NOTMERGEPEN 0 0 0 1 1 ~(P | D)
R2_MASKNOTPEN 0 0 1 0 2 ~P & D
R2_NOTCOPYPEN 0 0 1 1 3 ~P
R2_MASKPENNOT 0 1 0 0 4 P & ~D
R2_NOT 0 1 0 1 5 ~D
R2_XORPEN 0 1 1 0 6 P ^ D
R2_NOTMASKPEN 0 1 1 1 7 ~(P & D)
R2_MASKPEN 1 0 0 0 8 P & D
R2_NOTXORPEN 1 0 0 1 9 ~(P ^ D)
R2_NOPR2_NOP 1 0 1 0 10 D
R2_MERGENOTPEN 1 0 1 1 11 ~P | D
R2_COPYPEN 1 1 0 0 12 P (default)
R2_MERGEPENNOT 1 1 0 1 13 P | ~D
R2_MERGEPEN 1 1 1 0 14 P | D
R2_WHITE 1 1 1 1 15 1

A similar procedure handles color pens writing on a color surface. Rather than use ones and zeros as you do for a monochrome system, GDI uses RGB values to represent the colors of the pen and the destination. An RGB color value is a long integer containing red, green, and blue color fields, each 1 byte wide, representing the intensity from 0 to 255. The bitwise Boolean operation is per-form-ed on the two RGB color values. Here's an example in which a red pen is writing on a white destination surface using the R2_NOTMASKPEN operation. This operation sets the dest-ina-tion to the result of negating the logical AND of the pen color and the original destination color. The pen is red (0xFF0000), the destination is originally white (0xFFFFFF), and the result is (0x00FFFF), or full-intensity green and blue (cyan).

Some color devices, especially those with color palettes, don't assign the same meaning to the bits of a pixel's color value. You can calculate what the result of a raster operation will be, but the col-or corresponding to that bit pattern may not be defined.

GDI uses the raster operation code when a color value is a palette index (see Chapter 5) just like it does with a RGB color value; however, the result is less meaningful. Assume you per-form- the R2_NOTMASKPEN operation and have a pen using the color with palette-index 1 (a PALETTEINDEX (1) COLORREF value). This has the value 0x01000001. The destination is white, which is typically palette-index 255 (a PALETTEINDEX (255) COLORREF value). This has the value 0x010000FF. Logically ANDing the palette-indices 0x01 and 0xFF produces 0x01, and complementing that produces 0xFE. The color in palette-index 254 is the resulting color-whatever that color may be.

We wrote a set of Windows functions that draw the figure pictured in Figure 6.1. In that figure, we used the MM_ISOTROPIC mapping mode to draw dotted and solid lines using the MoveToEx, LineTo, and Arc line drawing functions and labeled the graph using the TextOut function. The graph automatically scales to the size of the window when the window is resized. All in all, it uses quite a number of the GDI functions that we have discussed so far in this chapter and in Chapter 5. You will find code very similar to this in the GDI Explorer. However, this code is somewhat simplified, since we use the same function in the Explorer to draw several of the other figures.

Listing 6.2: WM_PAINT handler for Arc-drawing example
#define MARKLENG 50

#define MARKGAP 10

static TCHAR xULLabel [] = _T("xUL") ;

static TCHAR yULLabel [] = _T("yUL") ;

static TCHAR xLRLabel [] = _T("xLR") ;

static TCHAR yLRLabel [] = _T("yLR") ;

static TCHAR xBeginLabel [] = _T("xBegin") ;

static TCHAR yBeginLabel [] = _T("yBegin") ;

static TCHAR xEndLabel [] = _T("xEnd") ;

static TCHAR yEndLabel [] = _T("yEnd") ;

static void

lines_OnPaint(HWND hwnd)

{

HDC hdc ;

HPEN hpenDot;

HPEN hpenThin;

HPEN hpenThick;

HPEN hpenOrig ;

int cxClient;

int cyClient ;

int xUL;

int yUL;

int xLR;

int yLR;

int xBegin;

int yBegin ;

int xEnd;

int yEnd ;

PAINTSTRUCT ps ;

RECT rect ;

SIZE size ;

/* Set the coordinates of the points */

/* used by the Arc function. */

xUL = -300 ; yUL = 200 ;

xLR = 300 ; yLR = -200 ;

xBegin = -150 ; yBegin = -250 ;

xEnd = 0 ; yEnd = 250 ;

/* Get the present size of the client area. */

GetClientRect (hwnd, &rect) ;

cxClient = rect.right ;

cyClient = rect.bottom ;

/* Get the device context and */

/* establish the coordinate mapping. */

hdc = BeginPaint (hwnd, &ps) ;

SetMapMode (hdc, MM_ISOTROPIC) ;Mapping mode:

SetViewportOrgEx (hdc, cxClient / 2, cyClient / 2, NULL) ;

SetWindowExtEx (hdc, 1000, 1000, NULL) ;

SetViewportExtEx (hdc, cxClient, -cyClient, NULL) ;

/* Create the drawing tools. */

hpenDot = CreatePen (PS_DOT, 0, RGB (0,0,0)) ;

hpenThin = CreatePen (PS_SOLID, 0, RGB (0,0,0)) ;

hpenThick = CreatePen (PS_SOLID, 5, RGB (0,0,0)) ;

/* Use a dotted pen to ... */

hpenOrig = SelectPen (hdc, hpenDot) ;

/* ...draw the rectangle and inscribed ellipse. */

Rectangle (hdc, xUL, yUL, xLR, yLR) ;

Ellipse (hdc, xUL, yUL, xLR, yLR) ;

/* Draw the imaginary lines from the begin and */

/* end points to the center of the rectangle. */

LineTo (hdc, xBegin, yBegin) ;

MoveToEx (hdc, 0, 0, NULL) ;

LineTo (hdc, xEnd, yEnd) ;

/* Use a solid thin pen to ... */

SelectPen (hdc, hpenThin) ;

/* Draw the point markers. */

MoveToEx (hdc, xUL - MARKGAP, yUL, NULL) ;

LineTo (hdc, xUL - MARKLENG, yUL) ;

MoveToEx (hdc, xUL, yUL + MARKGAP, NULL) ;

LineTo (hdc, xUL, yUL + MARKLENG) ;

MoveToEx (hdc, xLR + MARKGAP, yLR, NULL) ;

LineTo (hdc, xLR + MARKLENG, yLR) ;

MoveToEx (hdc, xLR, yLR - MARKGAP, NULL) ;

LineTo (hdc, xLR, yLR - MARKLENG) ;

MoveToEx (hdc, xBegin - MARKGAP, yBegin, NULL) ;

LineTo (hdc, xBegin - MARKLENG, yBegin) ;

MoveToEx (hdc, xBegin, yBegin - MARKGAP, NULL) ;

LineTo (hdc, xBegin, yBegin - MARKLENG) ;

MoveToEx (hdc, xEnd + MARKGAP, yEnd, NULL) ;

LineTo (hdc, xEnd + MARKLENG, yEnd) ;

MoveToEx (hdc, xEnd, yEnd + MARKGAP, NULL) ;

LineTo (hdc, xEnd, yEnd + MARKLENG) ;

/* Place labels by the markers. */

/* Draw the xUL label. */

GetTextExtentPoint32 (hdc, yULLabel,

lstrlen (yULLabel), &size) ;

TextOut (hdc,

xUL - 2 * MARKGAP - MARKLENG - size.cx,

yUL + size.cy / 2,

yULLabel, lstrlen (yULLabel)) ;

/* Draw the xUL label. */

GetTextExtentPoint32 (hdc, xULLabel,

lstrlen (xULLabel), &size) ;

TextOut (hdc,

xUL - size.cx / 2,

yUL + MARKGAP + MARKLENG + size.cy,

xULLabel, lstrlen (xULLabel)) ;

/* Draw the yLR label. */

GetTextExtentPoint32 (hdc, yLRLabel,

lstrlen (yLRLabel), &size) ;

TextOut (hdc,

xLR + 2 * MARKGAP + MARKLENG,

yLR + size.cy / 2,

yLRLabel, lstrlen (yxLRLabel)) ;

/* Draw the xLR label. */

GetTextExtentPoint32 (hdc, xLRLabel,

lstrlen (xLRLabel), &size) ;

TextOut (hdc,

xLR - size.cx / 2,

yLR - MARKGAP - MARKLENG,

xLRLabel, lstrlen (xLRLabel)) ;

/* Draw the yBegin label. */

GetTextExtentPoint32 (hdc, yBeginLabel,

lstrlen (yBeginLabel), &size) ;

TextOut (hdc,

xBegin - 2 * MARKGAP - MARKLENG - size.cx,

yBegin + size.cy / 2,

yBeginLabel, lstrlen (yBeginLabel)) ;

/* Draw the xBegin label. */

GetTextExtentPoint32 (hdc, xBeginLabel,

lstrlen (xBeginLabel), &size) ;

TextOut (hdc,

xBegin - size.cx / 2,

yBegin - MARKGAP - MARKLENG,

xBeginLabel, lstrlen (xBeginLabel)) ;

/* Draw the yEnd label. */

GetTextExtentPoint32 (hdc, yEndLabel,

lstrlen (yEndLabel), &size) ;

TextOut (hdc,

xEnd + 2 * MARKGAP + MARKLENG,

yEnd + size.cy / 2,

yEndLabel, lstrlen (yEndLabel)) ;

/* Draw the xEnd label. */

GetTextExtentPoint32 (hdc, xEndLabel,

lstrlen (xEndLabel), &size) ;

TextOut (hdc,

xEnd - size.cx / 2,

yEnd + MARKGAP + MARKLENG + size.cy,

xEndLabel, lstrlen (xEndLabel)) ;

/* Use a solid thick pen to ... */

SelectPen (hdc, hpenThick) ;

Arc (hdc, xUL, yUL, xLR, yLR,

xBegin, yBegin, xEnd, yEnd) ;

SelectPen (hdc, hpenOrig) ;

DeletePen (hpenDot) ;

DeletePen (hpenThin) ;

DeletePen (hpenThick) ;

EndPaint (hwnd, &ps) ;

}

More-complex Lines

The original GDI was specified when the dominant display devices were the Color Graphics Adapter (CGA, 320 pixels ¥ 200 pixels ¥ 2 bits color) and the Hercules (monochrome!) Graphics Adapter (720 pixels ¥ 350 pixels ¥ 1 bit color). These devices were limited to rather low resolution by modern standards. When Windows 95 was released, it was possible to get a 21-inch display and an adapter card that would run it at 1200 pixels ¥ 1600 pixels ¥ 24 bit color resolution . . . rather a lot better. One consequence of the higher resolution was that you really don't want to draw a 1-pixel line as a separator-it is almost too fine to see. So the recommended practice for drawing a line is to use an internal value Windows uses to determine the "best" visual resolution for a simple line. You normally should create your pen not as a 1-pixel pen, but by using the following value:

Width = GetSystemMetrics(SM_CXBORDER);

The value returned is the value Windows uses for drawing "window borders" and is adjusted by the display driver to be a value that produces a "satisfactory" line for a horizontal border. There is also a value SM_CYBORDER, which is the recommended width for a vertical border. If you want to be strictly precise, you should use the border width based on the desired orientation of your line. However, in the modern world the aspect ratio of most displays is 1:1 ("square" pixels), so the two are usually interchangeable. Since this line is often for visual marking, then even if the values are different either one would probably suffice. However, in a given mapping mode the actual size of a logical pen that displays this "standard line" will vary. Once having obtained the "best" size for a thin pen in device units, you must also use DPtoLP on the current DC to determine the number of logical pixels you must use for your pen. Remember that if you are using the anisotropic mapping mode, the "logical units" may no longer be "square", so you may even have to compute pen widths for different angles if you want lines of equal visual thickness!

If you change mapping modes, you will have to create a new pen:

HPEN getThinPen(HDC hdc)

{

POINT Width;

Width.x = GetSystemMetrics(SM_CXBORDER);

Width.y = 0; // unused, set to 0

DPtoLP(hdc, &Width, 1);

HPEN thinpen = CreatePen(PS_SOLID, Width.x, RGB(0,0,0);

return thinpen;

}

But this leads to a new and different problem: Such attributes as dotted and dashed lines are specified to work only for 1-pixel lines! This is one of the many limitations of normal Windows pens. Furthermore, additional line capabilities were needed, such as being able to control the shape of the end caps of lines, to get joined lines in a PolyLine to match nicely, and to specify application-specific dotted or dashed lines (the default dots and dashes were often insufficient for many applications, thus leading to "hand-drawn" dashed lines drawn as a series of individual LineTo operations, which are very inefficient to draw). And, while in the days of the 8-bit 8088 and the CGA the cost of computing all of these fancy features was prohibitive, today a graphics accelerator card often has all of these facilities "in the hardware". This removes the burden of doing it entirely from Windows.2 Finally, there is the limitation in a pen created by CreatePen and CreatePenIndirect that such a pen is rendered to the nearest "pure" color except for PS_INSIDEFRAME. Again, the increase in computational power and graphics displays makes this limitation seem irrelevant. Many people run cards supporting full 24-bit graphics whose video memory is larger than the entire main memory of a 286 running Windows 3.0! Thus the early engineering trade-offs made in the Windows design seem inappropriate for a modern Win32 platform.

All of this leads up to the much more sophisticated graphics interface supported by Win32. We cover many of the features in this chapter. The first we cover is the ExtCreatePen interface, which allows us to create very fancy pens that give us control over the patterns, joins, and end caps. These newer pens are distinguished from the older Win16-compatible pens by calling them geometric pens and calling the older pens cosmetic pens. A cosmetic pen is currently always 1 pixel wide and can be created by calling CreatePen or CreatePenIndirect specifying a width of 0 or by specifying PS_COSMETIC to ExtCreatePen and specifying a width of 1. The stock pens, obtained from GetStockObject, are all cosmetic pens. Note that cosmetic pens created by ExtCreatePen and which are not solid do not support OPAQUE background mode.

Cosmetic pens are more efficient for the GDI to draw than are geometric pens. They also are independent of any scaling factors that may apply. Hence, as the image is scaled the cosmetic pens always draw as 1-pixel-wide pens. This is not always right, and you have to decide what makes sense for your application. For example, as we pointed out, a 1-pixel-wide pen on a very high resolution display may be virtually impossible to see.

The ExtCreatePen call takes a pen style that is made up of four components. Each of these is specified by choosing an element from the appropriate category and using the bitwise OR operation to combine them into a single 32-bit style value. The values available are given in Table 6.3.
Table 6.3: ExtCreatePen style parameters
Style Code Meaning
Group 1: Pen Type
PS_GEOMETRIC A geometric pen.
PS_COSMETIC A cosmetic pen (the same as CreatePen/CreatePenIndirect with a width of 0. For ExtCreatePen, you must specify a width of 1 for a cosmetic pen).
Group 2: Pen Style
PS_ALTERNATE Pen sets every other pixel (PS_COSMETIC pens only).
PS_SOLID Pen is solid.
PS_DASH Pen is dashed. Not supported in Windows 95 for PS_GEOMETRIC pens.
PS_DOT Pen is dotted. Not supported in Windows 95 for PS_GEOMETRIC pens.
PS_DASHDOT Pen has alternating dashes and dots. Not supported in Windows 95 for PS_GEOMETRIC pens.
PS_DASHDOTDOT Pen has alternating dashes and double dots. Not supported in Windows 95 for PS_GEOMETRIC pens.
PS_NULL Pen is invisible.
PS_USERSTYLE Pen uses a styling array supplied by the user. Not supported in Windows 95.
PS_INSIDEFRAME Pen is solid. When this pen is used for any object that uses a bounding box, such as a rectangle or ellipse, the dimensions are adjusted so that the pen fits entirely within the bounding box. This style is only for geometric pens.
Group 3: End Cap Style (PS_GEOMETRIC only) Not supported in Windows 95 except when stroking paths.
PS_ENDCAP_ROUND End caps are round.
PS_ENDCAP_SQUARE End caps are square.
PS_ENDCAP_FLAT End caps are flat.
Group 4: Line Join Styles (PS_GEOMETRIC only) Not supported in Windows 95 except when stroking paths.
PS_JOIN_BEVEL Line joins are beveled.
PS_JOIN_MITER Line joins are mitered. See also SetMiterLimit.
PS_JOIN_ROUND Line joins are rounded.

The end cap style determines the relationship of the end of the ink to the end of the logical line. This is shown in Figure 6.6. The white line in each drawing represents the actual coordinates used to draw the line. This line is drawn on top of the thick lines using a 1-pixel-wide pen. The thick black or gray line shows the line drawn by the pen, given a LineTo operation that uses the same coordinates as the thin white pen but draws with the thicker pen. In each example, the two lines are drawn independently using LineTo. Note that the square endcaps and the flat endcaps, although superficially the same, bear different relationships to the coordinates to which the line is drawn.

The WM_PAINT handler for Figure 6.6 is shown in Listing 6.3.

Listing 6.3: WM_PAINT handler for end caps example
#define WIDTH 20

static void

drawPenExample(HWND hwnd, HDC hdc,

LPRECT r, HPEN horz,

HPEN vert, int caption,

BOOL angle)

{

HPEN whitepen =

GetStockPen(WHITE_PEN);

TEXTMETRIC tm;

RECT trect;

TCHAR text[50];

LoadString(GetWindowInstance(hwnd),

caption,

text, DIM(text));

BeginPath(hdc); // in case Windows 95

// Draw the top line in the horizontal pen

SelectPen(hdc, horz);

MoveToEx(hdc, r->left, r->top, NULL);

LineTo(hdc, r->right, r->top);

// Draw the downward line either vertical or at an

// angle depending on the `angle' option

SelectPen(hdc, vert);

LineTo(hdc, (angle ? r->left : r->right), r->bottom);

// Draw the thin white pen to show the actual LineTo

// coordinates

SelectPen(hdc, whitepen);

MoveToEx(hdc, r->left, r->top, NULL);

LineTo(hdc, r->right, r->top);

LineTo(hdc, (angle ? r->left : r->right), r->bottom);

EndPath(hdc);

StrokePath(hdc);

// define a rectangle for the text

trect.left = r->left;

trect.right = r->right;

trect.top = r->bottom + 2 * WIDTH;

GetTextMetrics(hdc, &tm);

trect.bottom = trect.top +

2 * (tm.tmHeight + tm.tmExternalLeading);

DrawText(hdc, text, -1, &trect, DT_CENTER | DT_VCENTER);

}

static void

endcaps_OnPaint(HWND hwnd)

{

// Define some parameters for the line drawing

#define X1 (1 * WIDTH)

#define Y1 (2 * WIDTH)

#define Y2 (6 * WIDTH)

#define R (3 * WIDTH)

#define Dx (5 * WIDTH)

#define Dy (8 * WIDTH)

// Define the bounding rectangles for the six

// illustrations

RECT r1 = {X1, Y1, X1 + R, Y1 + R};

RECT r2 = {X1 + Dx, Y1, X1 + R + Dx, Y1 + R};

RECT r3 = {X1 + 2 * Dx, Y1, X1 + R + 2 * Dx, Y1 + R};

RECT r4 = {X1, Y1 + Dy, X1 + R, Y1 + Dy + R};

RECT r5 = {X1 + Dx, Y1 + Dy, X1 + R + Dx, Y1 + Dy + R};

RECT r6 = {X1 + 2 * Dx, Y1 + Dy, X1 + R + 2 * Dx, Y1 + Dy + R};

// Define the brushes used by ExtCreatePen used for the

// filling

LOGBRUSH blackbrush = {BS_SOLID, RGB(0,0,0), 0 };

LOGBRUSH graybrush = {BS_SOLID, RGB(128,128,128), 0 };

// The pens we are going to use

HPEN roundblack =

ExtCreatePen(PS_GEOMETRIC | PS_ENDCAP_ROUND,

WIDTH, &blackbrush, 0, NULL);

HPEN squareblack =

ExtCreatePen(PS_GEOMETRIC | PS_ENDCAP_SQUARE,

WIDTH, &blackbrush, 0, NULL);

HPEN flatblack =

ExtCreatePen(PS_GEOMETRIC | PS_ENDCAP_FLAT,

WIDTH, &blackbrush, 0, NULL);

HPEN roundgray =

ExtCreatePen(PS_GEOMETRIC | PS_ENDCAP_ROUND,

WIDTH, &graybrush, 0, NULL);

HPEN squaregray =

ExtCreatePen(PS_GEOMETRIC | PS_ENDCAP_SQUARE,

WIDTH, &graybrush, 0, NULL);

HPEN flatgray =

ExtCreatePen(PS_GEOMETRIC | PS_ENDCAP_FLAT,

WIDTH, &graybrush, 0, NULL);

// Define a font to be used for the captions

HFONT font = createCaptionFont(-20, TimesFont) ;

PAINTSTRUCT ps;

HDC hdc;

int restore;

hdc = BeginPaint(hwnd, &ps);

if(roundblack == NULL)

{ /* no ExtCreatePen */

// We are probably running under Windows 95

PostMessage(hwnd, UWM_ERROR, IDS_EXTCREATEPEN_FAILED,

IDS_NOT_SUPPORTED);

EndPaint(hwnd, &ps);

DeleteFont(font);

return;

} /* no ExtCreatePen */

restore = SaveDC(hdc);

SelectFont(hdc, font);

drawPenExample(hwnd, hdc, &r1, roundblack, roundgray,

IDS_ROUND, FALSE);

drawPenExample(hwnd, hdc, &r2, squareblack, squaregray,

IDS_SQUARE, FALSE);

drawPenExample(hwnd, hdc, &r3, flatblack, flatgray,

IDS_FLAT, FALSE);

drawPenExample(hwnd, hdc, &r4, roundblack, roundgray,

IDS_ROUND, TRUE);

drawPenExample(hwnd, hdc, &r5, squareblack, squaregray,

IDS_SQUARE, TRUE);

drawPenExample(hwnd, hdc, &r6, flatblack, flatgray,

IDS_FLAT, TRUE);

RestoreDC(hdc, restore);

EndPaint(hwnd, &ps);

// Now that we know all our objects are deselected,

// we can safely delete them

DeletePen(roundblack);

DeletePen(squareblack);

DeletePen(flatblack);

DeletePen(roundgray);

DeletePen(squaregray);

DeletePen(flatgray);

DeleteFont(font);

}

In this example, three pens are created in each color, representing each of the kinds of end caps possible. With each pen, two drawings are made: one with the lines at right angles and one with the lines at a 45° angle. The six drawings are specified by providing the function drawPenExample with a pair of pens (one for the horizontal line, one for the downward line), a rectangle whose corners are used to determine the endpoints of the lines, a caption to be placed, and an option indicating whether the second line is to be drawn vertically or at an angle.

Note that because we have selected a font and a pen, rather than saving individual objects returned by SelectObject and then restoring them individually at the end of the function, we used the SaveDC and RestoreDC functions to reset the entire DC to its initial state. We use RestoreDC rather than depending on ReleaseDC to implicitly deselect our objects because in the presence of a class DC or private DC, the -ReleaseDC would have no effect, and we want our code to work reliably independent of such considerations.

You may ignore the BeginPath, EndPath, and StrokePath functions for the moment. On Windows NT, these are actually unnecessary. But on Windows 95, geometric pens have no effect except when these operations are used. We discuss paths more fully starting on page 384. We had to include them so that this code will work correctly on Windows 95.3

You may notice in Figure 6.6 that lines that meet with other than round end caps do not create a particularly good-looking result. This is why the default for lines from CreatePen (which was the only way to create a pen in Windows 3.1 and earlier versions) is round end caps. However, you may want to have a line that ends with square or flat end caps, but which in fact has several internal segments. This is why there are functions like PolyLine, PolyLineTo, PolyDraw, and the like. You can therefore specify a pen property of how two lines drawn with a single line drawing function are joined. In Windows 3.1, this made no difference: All lines ended with round end caps, and a multiline drawing was identical to a sequence of LineTo function calls. But round line joins are not the only possible style. Two others can be specified with a pen: miter and bevel.

A bevel join, shown in Figure 6.7, clips off the corners of the lines that would protrude and flattens the end. Compare the bevel join to the effect of simply drawing two lines with flat or square end caps at an angle, as shown in Figure 6.7. Note that the join style is independent of the end cap style. You can have round end caps and a bevel join. The bevel join works well at any angle.

The round join is no different in appearance from the result of drawing two independent lines with round end caps. But since the join style is independent of the end cap style, with an ExtCreatePen pen you can have a line with square end caps and a round join, which is not possible with ordinary pens created with CreatePen. With an ordinary pen, although drawing two lines would look as if they had a round join, it would also draw lines with round end caps, because that is the only end cap style -CreatePen supports. (These all presuppose that the drawing mode is something like R2_COPYPEN. It gets even more difficult to explain, or even accomplish, in other drawing modes.)

The miter join, also shown in Figure 6.7, has some very special properties. Note that it produces the effect of projecting the outer edges of the lines forming the angle until they meet. This produces a nice, sharp edge but doesn't work well for very small angles (and if you should happen to draw a pair of lines that started and ended at the same coordinate, that is, that overlapped, the projection would be parallel and could be extended to infinity!). So notice in Figure 6.7 that as we decrease the angle, the endpoint projects farther and farther out from the point where the two lines intersect. But if you follow the drawing horizontally, you see that the last example of the miter join looks remarkably like the bevel join. That is because the distance the miter would project exceeds the miter limit. The miter length is defined as the distance from the intersection of the "inner wall" of the line to the intersection of the "outer wall" of the line. The miter limit is the maximum allowed ratio of the miter length to the line width. These are illustrated in Figure 6.8. If your line exceeds the miter limit, the join style is changed to a bevel join. You can change the miter limit using the SetMiterLimit function. The previous setting of the miter limit can be obtained either by the GetMiterLimit function or by providing a non-NULL third parameter to the SetMiterLimit function:

FLOAT oldlimit;

SetMiterLimit(hdc, 8.0, &oldlimit);

is the same as

FLOAT oldlimit;

GetMiterLimit(hdc, &oldlimit);

SetMiterLimit(hdc, 8.0, NULL);

Like most Win32 GDI functions, both of these return a BOOL result: TRUE if they succeeded and FALSE if they failed. If you know the Windows 3.1 GDI, you will also note that this function takes a FLOAT value. We show other functions that take FLOAT values later in this chapter. Windows was originally designed for the 8088, which was a slow machine. A modern processor can perform a 32-bit floating point multiply in less time than an 8088 required for a 16-bit integer add, so Win32 is less reticent about using floating point -values.

The program that created the line join illustration of Figure 6.7 is shown in Listing 6.4.

Listing 6.4: WM_PAINT handler for showing line joins

#define JOIN_PEN_WIDTH 15

#define JOIN_SAMPLE_SIZE 100

#define JOIN_WIDTH (JOIN_SAMPLE_SIZE + 2 * JOIN_SAMPLE_SIZE/3)

#define JOIN_HEIGHT (JOIN_SAMPLE_SIZE + JOIN_SAMPLE_SIZE/4)

static void polyLineInRect(HDC hdc, LPRECT r, int adjust)

{

POINT pts[3];

pts[0].x = r->left;

pts[0].y = r->bottom;

pts[1].x = r->right;

pts[1].y = r->bottom;

pts[2].x = r->right;

pts[2].y = r->top - adjust;

Polyline(hdc, pts, DIM(pts));

}

static void

drawJoin(HWND hwnd, HDC hdc, int y, DWORD style, int caption)

{

LOGBRUSH blackbrush = {BS_SOLID, RGB(0,0,0), 0 };

HPEN pen = ExtCreatePen(PS_GEOMETRIC | PS_ENDCAP_FLAT |

style, JOIN_PEN_WIDTH, &blackbrush,

0, NULL);

int restore;

RECT r;

HFONT font = createCaptionFont(-20, TimesFont);

TCHAR text[50];

LoadString(GetWindowInstance(hwnd), caption,

text, DIM(text));

restore = SaveDC(hdc);

BeginPath(hdc);

SelectFont(hdc, font);

r.top = y;

r.bottom = r.top + JOIN_SAMPLE_SIZE;

r.left = 0;

r.right = r.left + JOIN_WIDTH;

DrawText(hdc, text, -1, &r,

DT_LEFT | DT_VCENTER | DT_SINGLELINE);

SelectPen(hdc, pen);

r.right = r.left + JOIN_SAMPLE_SIZE;

OffsetRect(&r, JOIN_WIDTH, 0);

polyLineInRect(hdc, &r, 0);

OffsetRect(&r, JOIN_WIDTH, 0);

polyLineInRect(hdc, &r, 0);

OffsetRect(&r, JOIN_WIDTH, 0);

polyLineInRect(hdc, &r, JOIN_SAMPLE_SIZE / 4 );

OffsetRect(&r, JOIN_WIDTH, 0);

polyLineInRect(hdc, &r, JOIN_SAMPLE_SIZE / 6 );

EndPath(hdc);

StrokePath(hdc);

RestoreDC(hdc, restore);

DeletePen(pen);

DeleteFont(font);

}

static void

join_OnPaint(HWND hwnd)

{

#define START 20

HDC hdc;

PAINTSTRUCT ps;

hdc = BeginPaint(hwnd, &ps);

drawJoin(hwnd, hdc, START + 0, PS_JOIN_BEVEL,

IDS_BEVEL_JOIN);

drawJoin(hwnd, hdc, START + JOIN_HEIGHT, PS_JOIN_MITER,

IDS_MITER_JOIN);

drawJoin(hwnd, hdc, START + 2 * JOIN_HEIGHT, PS_JOIN_ROUND,

IDS_ROUND_JOIN);

EndPaint(hwnd, &ps);

}

As with Listing 6.3, you can for the moment ignore the use of the BeginPath, EndPath, and StrokePath functions, which are necessary to make this work in Window 95. Or you can jump ahead to page 384.

Geometric pens support additional capability beyond ordinary CreatePen/CreatePenIndirect style pens. For example, you can specify that a bitmap be used for the pen. This is because the "color" of the pen is actually specified by giving a LOGBRUSH structure, which can take a bitmap to define the brush. You can create a pen that is halftoned or plaid or that even contains an image. Those of you who programmed Windows 3.x may remember that patterned brushes were limited to bitmaps of 8 ¥ 8 pixels. This limitation is removed in Windows NT. Unfortunately, it is still in Windows 95.

Drawing Filled Areas

Until now we've limited our drawing to text and lines. Windows also has functions to draw filled areas. In general, a filled area has a perimeter and an interior. Windows has functions to draw the following types of filled areas:

Drawing Rectangles and Ellipses

The Chord, DrawFocusRect, Ellipse, Pie, Rectangle, and RoundRect functions are quite similar to the Arc function discussed earlier in the chapter. All these functions require a bounding rectangle that specifies the position, shape, and size of their corresponding figure. The simplest object is the rectangle. The function call looks like the following:

BOOL Rectangle (HDC hdc, int xUL, int yUL, int xLR, int yLR) ;

The logical coordinates (xUL, yUL) and (xLR, yLR) specify the upper-left and lower-right corners, respectively, of the bounding rectangle that surrounds the desired rectangle. Because they are logical coordinates, the value of xUL isn't necessarily less than xLR. Similarly, yUL is not necessarily less than yLR. Do not rely on assumptions that are true when using the MM_TEXT mapping mode but may not be true when using other mapping modes. In addition, remember that the point (xLR, yLR) isn't on the perimeter of the drawn rectangle. In the default graphics mode (GM_COMPATIBLE), GDI draws up to, but not including, the right and bottom sides of the bounding rectangle. Figure 6.9 shows a rectangle drawn with the Rectangle function. The exterior is drawn with the selected pen (the default BLACK_PEN, in this case) and the interior is filled with the selected brush (a stock LTGRAY_BRUSH). In the extended graphics mode (GM_ADVANCED), GDI draws up to and including the right and bottom sides of the bounding rectangle. GM_ADVANCED mode is not available on Windows 95.

Exactly the same parameters are required by the Ellipse function to draw an ellipse. Figure 6.10 shows an ellipse drawn within the same bounding rectangle that was used for drawing the rectangle in Figure 6.9.

The Chord and Pie functions take exactly the same parameters as the Arc function:

Arc (HDC hdc,

int xUL, int yUL,

int xLR, int yLR,

int xBegin, int yBegin,

int xEnd, int yEnd) ;

Chord (HDC hdc,

int xUL, int yUL,

int xLR, int yLR,

int xBegin, int yBegin,

int xEnd, int yEnd) ;

Pie (HDC hdc, int xUL, int yUL, int xLR, int yLR,

int xBegin, int yBegin, int xEnd, int yEnd) ;

All three functions draw the same arc. The Arc function draws the specified arc and leaves the endpoints of the arc unconnected. The Arc function draws a curved line, not a filled area. The Chord function draws the specified arc and the chord connecting the two endpoints of the arc. It then paints the interior of the area with a brush. The Pie function draws the specified arc and a line from each of the endpoints of the arc to the center of the ellipse. It then paints the interior of the pie-shaped wedge with a brush.

We drew the chord in Figure 6.11 and the pie in Figure 6.12 by changing the Arc function call in Listing 6.2 to Chord and Pie. The pie wedge in Figure 6.12 looks a bit strange because the pie isn't circular. There are no additional Windows functions for drawing circles and squares because they are special cases of ellipses and rectangles. To draw a circle or a square, the physical distance between the horizontal coordinates of the bounding rectangle must be equal to the physical distance between the vertical coordinates.

All physical measurement mapping modes and the MM_ISOTROPIC mapping modes ensure that a logical unit maps to the same physical distance on each axis. When using one of these mapping modes, you need to ensure only that equals . When using the MM_TEXT and MM_ANISOTROPIC mapping modes, you must adjust the logical coordinates to account for the aspect ratio of the device pixels. (The obsolete EGA did not have "square" pixels. In fact, it had a 4:3 ratio on pixel dimensions. This meant that if you drew a "square", that is, a figure n units wide and n units high, it would be distorted. While most modern display units have square pixels, not all printers do. Some common inkjet printers offer 600 ¥ 300 resolution, for example). When using the MM_ANISOTROPIC mapping mode, you also must compensate for the scaling of the window to the viewport.

If you use a display or printer with nonsquare pixels, you must take extra care when drawing circles and squares to do it correctly. Pixels on a VGA have an aspect ratio of 1:1; that is, they are square. The following statement, drawing into a default common display context, draws what looks like a square on a VGA but might be a rectangle on other output devices:

Rectangle (hdc, 50, 50, 100, 100) ;

This is because of the implicit use of the MM_TEXT mapping mode in which logical units are equal to device units (or pixels). On a VGA with square pixels, everything looks correct, but on a display with rectangular pixels, a rectangle is drawn.

You can draw a rectangle with rounded corners with the RoundRect function. In many ways, this function is a cross between the Rectangle function and the Ellipse function. Like these, it draws a rectangle within a bounding rectangle. However, it rounds the corners of the rectangle to match one of the four quadrants of an ellipse that you also specify. The RoundRect function is defined as

BOOL RoundRect (HDC hdc, int xUL, int yUL, int xLR, int yLR,

int Width, int Height) ;

The Width and Height parameters specify the width and height in logical units of the ellipse used to draw the rounded corners. By varying the width and height parameters, you can generate a rectangle with no rounding at the corners (width and height equal to 0) or, at the other extreme, produce a rectangle with so much rounding at the corners that it actually becomes an ellipse (width equal to and height equal to). Figure 6.13 shows a rounded rectangle with the corner ellipse dimensions equal to one third of the corresponding rectangle sides.

Finally, four rectangle drawing functions require a pointer to a RECT structure. The first is the DrawFocusRect function. This function draws a rectangle in the style used to indicate that an object has the focus. This rather strange function doesn't work the way the others do. A call to the function is defined as

BOOL DrawFocusRect(HDC hdc, CONST RECT * rect);

and can be called as

RECT rect = { xUL, yUL, xLR, yLR };

DrawFocusRect (hdc, &rect) ;

The first difference is that the upper-left and lower-right corners of the bounding rectangle aren't specified by separate parameters to the DrawFocusRect function. Instead, you must pass a pointer to a RECT structure containing the coordinates. This actually is a better way to pass the parameters. The other functions should have been designed to accept a RECT structure. But because they weren't, changing in midstream leaves this call as the oddball.

The second difference is that neither the currently selected pen nor the currently selected brush are used to draw the focus rectangle. The DrawFocusRect function draws the border of the rectangle using a dotted pen and doesn't fill the interior of the rectangle.

The third difference is that the DrawFocusRect function assumes that the mapping mode is MM_TEXT and fails to work properly when other mapping modes are in effect. The Windows documentation, unfortunately, fails to mention this assumption. To use this function when you use other mapping modes, you must convert the logical coordinates of the bounding rectangle to device coordinates. You also must change the mapping mode back to MM_TEXT, reset, if necessary, the window and viewport origin back to (0, 0), and call the DrawFocusRect function. We used a slight modification of the code in Listing 6.2 to draw Figure 6.14. The following lines of code generated the focus rectangle in Figure 6.14:

int saved = SaveDC(hdc);

SetRect (&rect, xUL, yUL, xLR, yLR) ;

LPtoDP (hdc, (LPPOINT) &rect, 2) ;

SetMapMode (hdc, MM_TEXT) ;

SetViewportOrgEx (hdc, 0, 0, NULL) ;

DrawFocusRect (hdc, &rect) ;

RestoreDC(hdc, saved);

Another big difference is that DrawFocusRect draws using the R2_XORPEN raster operation. Calling this function a second time with the same bounding rectangle erases the rectangle from the display without disturbing the underlying image. The functions we've seen previously can do almost the same thing, but you would need to select a dotted pen, select a null brush, change the drawing mode to R2_XORPEN, draw the rectangle, and finally, remember to delete the dotted pen. Even then, this only approximates the appearance of a focus rectangle. A dotted pen draws dots that are spaced farther apart than the dots in a focus rectangle. You can use ExtCreatePen to create a PS_ALTERNATE-style geometric pen to get this effect.

You cannot scroll an area of the display that contains a rectangle drawn by this function. When you want to scroll the area containing a rectangle drawn by the DrawFocusRect function, you must draw the focus rectangle a second time (which removes it from the display), scroll the area, and then draw the focus rectangle once more at the new location.

The three other rectangle drawing functions are as follows:

BOOL FillRect (HDC hdc, LPRECT rect, HBRUSH brush) ;

BOOL FrameRect (HDC hdc, LPRECT rect, HBRUSH brush) ;

BOOL InvertRect (HDC hdc, LPRECT rect) ;

The FillRect function uses the specified brush, rather than the brush currently selected in the device context, to fill the rectangle specified by the logical coordinates in the RECT structure. It fills the top and left borders of the rectangle and up to, but not including, the right and bottom borders. It also assumes that the axes in the logical coordinate system have the default orientation. That is, it requires that the bottom field of the RECT structure be greater than top and that right be greater than left. When you invert an axis, such as switching to a Cartesian-style vertical axis, these assumptions are no longer true, and the FillRect function won't draw the rectangle.

BOOL fillCartesianRect(HDC hdc, LPRECT rect, HBRUSH brush)

{

RECT r = *rect; // make copy

int saved = SaveDC(hdc);

BOOL result;

LPtoDP(hdc, (LPPOINT)&r, 2);

SetMapMode(hdc, MM_TEXT);

SetViewportOrgEx (hdc, 0, 0, NULL) ;

result = FillRect(hdc, &r, brush);

RestoreDC(hdc, saved);

return result;

}

The FrameRect function uses the specified brush to draw a border around the rectangle specified by the logical coordinates in the RECT structure. The border is one logical unit wide and high. All the functions described previously use the current pen to draw the border. Because a brush is used, the border does not have to be a pure color. When a logical unit is greater than a device unit, the border is drawn using dithered colors. The FrameRect function will not draw the border when the axes do not have the default orientation.

The InvertRect function inverts the contents of the specified rectangle. This changes all black pixels to white and all white pixels to black. Other colored pixels are changed to their respective inverse colors. A given color may have different inverse colors on different displays. Inverting the rectangle a second time restores the pixels to their original values. The InvertRect makes the same assumptions about the orientation of the axes as do the FillRect and FrameRect functions.

We discuss the FillRgn, FillPath, and StrokeAndFillPath functions when we discuss regions and paths. They are included in this list for completeness.
Many of the functions listed here work properly only if the axes have the default orientation. That is, y-values increase downward and x-values increase rightward. Be careful if you are using one of the mapping modes that allows you to change axis orientation.

A number of functions ease the effort of working with rectangles. Unfortunately, some of them also assume that horizontal axis values increase to the right and that vertical axis values increase downward. They don't work correctly when you use logical coordinates in the physical measurement mapping modes. The functions may or may not work using logical coordinates in the MM_ISOTROPIC or MM_ANISOTROPIC mapping modes. It depends on the orientation of the axes.

BOOL CopyRect (LPRECT rectDest, CONST RECT * rectSrc) ;

The CopyRect function makes one rectangle equal to another rectangle. You can do this just as easily with a structure assignment statement. This does the same thing with inline code rather than a function call to -Windows:

rectDest = rectSrc ;

The InflateRect function adds the xDelta value to the right and the yDelta value to the bottom of a rectangle and subtracts the xDelta value from the left and the yDelta value from the top. Positive values increase the size of the rectangle, and negative values shrink the rectangle. Note that this approach assumes that positive horizontal coordinates increase to the right and positive vertical coordinates increase downward. Here is a typical call to the InflateRect function:

BOOL InflateRect (LPRECT rect, int xDelta, int yDelta) ;

The IntersectRect function sets the Dest structure to the largest rectangle contained in both Src1 and Src2. It doesn't work properly unless the axes have the default orientation:

BOOL IntersectRect (LPRECT Dest, CONST RECT * Src1,

CONST RECT * Src2) ;.

The OffsetRect function shifts the rectangle xOffset units horizontally and yOffset units vertically. The xOffset and yOffset parameters can be positive or negative, thus enabling you to shift the rectangle in any direction:

BOOL OffsetRect (LPRECT rect, int xOffset, int yOffset) ;

The SetRect function initializes the four fields of a RECT structure to the provided values:

BOOL SetRect (LPRECT rect, int xUL, int yUL, int xLR, int yLR) ;

The above statement is equivalent to the following:

rect.left = xUL ;

rect.top = yUL ;

rect.right = xLR ;

rect.bottom = yLR ;

The SetRectEmpty function sets all fields in a RECT structure to 0:

BOOL SetRectEmpty (LPRECT rect) ;

The UnionRect function creates the union of the Src1 and Src2 rectangles. It sets the Dest rectangle to the smallest rectangle that can enclose the two source rectangles, that is, the bounding box that encloses the two source rectangles. It requires that the default coordinate orientation be used:

BOOL UnionRect (LPRECT Dest, CONST RECT * Src1,

CONST RECT * Src2) ;

The IsRectEmpty function returns a nonzero value when the rect parameter is an empty rectangle. An empty rectangle is a rectangle with either top equal to bottom (zero height) or left equal to right (zero width), or both:

BOOL IsRectEmpty (LPRECT rect) ;

The PtInRect function returns a nonzero value when the point specified by the pt parameter is in the rectangle. A point is in a rectangle when it is within the four sides or on the left or top side. A point is not in a rectangle when it lies on the right or bottom side of the rectangle:

BOOL PtInRect (LPRECT rect, LPPOINT pt) ;

Drawing Polygons

Two functions for drawing filled areas with a border are the Polygon and PolyPolygon functions. The Polygon function takes the same arguments as does the Polyline function: a handle to a device context, a pointer to an array of POINT structures containing the logical coordinates of the vertices of the polygon, and the number of points in the array:

BOOL Polygon (HDC hdc, LPPOINT Points, int Count) ;

Unlike the Polyline function (which draws up to, but not including, the last point in the array), the Polygon function closes the polygon, if necessary, by drawing a line from the last vertex to the first. This is called "closing the figure", and you will see a generalization of it when we discuss paths (page 384). GDI draws the perimeter of the polygon with the selected pen and fills the interior of the polygon using the selected brush.

Unlike the simple rectangle and ellipse, a polygon can be a complex, overlapping figure. When a polygon is complex, the polygon-filling mode controls how GDI determines which points are interior points and therefore get filled. You get the polygon-filling mode with the GetPolyFillMode function and set it with the SetPolyFillMode function:

int PolyFillMode = GetPolyFillMode (hdc) ;

int PrevFillMode = SetPolyFillMode (hdc, PolyFillMode) ;

There are two polygon-filling modes: ALTERNATE (the default) and WINDING. When the polygon-filling mode is ALTERNATE, GDI fills every other enclosed area that is within the boundaries of the polygon. When the polygon-filling mode is WINDING, GDI computes a perimeter that encloses the polygon but does not overlap. This is the perimeter of the polygon drawn. The polygon itself can completely surround areas that aren't within the boundaries of the polygon. Those surrounded areas are not filled in either polygon-filling mode. In Figure 6.15, the left polygon was drawn with the WINDING polygon-filling mode. The right polygon was drawn with the ALTERNATE polygon-filling mode.

Listing 6.5 shows the WM_PAINT logic we used to draw the polygons in Figure 6.15. Notice that the same polygon is drawn twice. We inverted the orientation of both axes and changed from the default polygon--filling mode (ALTERNATE) to the WINDING polygon-filling mode before drawing the polygon the second time.

Listing 6.5: WM_PAINT logic for drawing Figure 6.15
POINT Points [] = {

{ 200, 400 },

{ 200, 0 },

{ 500, 0 },

{ 500, 300 },

{ 100, 300 },

{ 100, 200 },

{ 400, 200 },

{ 400, 100 },

{ 300, 100 },

{ 300, 400 }

} ;

static void

poly_OnPaint(HWND hwnd)

{

RECT rect;

int cxClient:

int cyClient;

HDC hdc;

PAINTSTRUCT ps;

HBRUSH hbr;

/* Get the present size of the client area. */

GetClientRect (hwnd, &rect) ;

cxClient = rect.right ;

cyClient = rect.bottom ;

/* Get the device context and */

/* establish the coordinate mapping. */

hdc = BeginPaint (hwnd, &ps) ;

SetMapMode (hdc, MM_ISOTROPIC) ;

SetViewportOrgEx (hdc, cxClient / 2, cyClient / 2, NULL) ;

SetWindowExtEx (hdc, 1000, 1000, NULL) ;

SetViewportExtEx (hdc, cxClient, -cyClient, NULL) ;

hbr = GetStockBrush (LTGRAY_BRUSH) ;

SelectBrush (hdc, hbr) ;

/* Draw the right polygon. */

Polygon (hdc, Points, DIM(Points)) ;

/* Invert the coordinate system axes. */

SetViewportExtEx (hdc, -cxClient, cyClient, NULL) ;

/* Change the polygon-filling mode. */

SetPolyFillMode (hdc, WINDING) ;

/* Draw the left polygon. */

Polygon (hdc, Points, DIM(Points)) ;

EndPaint (hwnd, &ps) ;

}

The PolyPolygon function draws a series of polygons. Unlike the Polygon function, polygons drawn by the PolyPolygon function are not automatically closed. That is, the PolyPolygon function does not draw a line from the last vertex to the first. GDI draws the perimeter of the polygon with the selected pen and fills the interior of the polygon using the selected brush. The interior of the polygon is filled in accordance with the polygon-filling mode, as is done for the Polygon function. Figure 6.16 shows the drawn polygons. In this case, we use the same points that would have drawn the two polygons shown in Figure 6.15, but we divide the points differently. You can use the GDI Explorer application to see all the variants of this. Select the PolyPolygon display and then use the right mouse button to change the properties of the drawing by selecting a different set of vertices. Selecting the 0th, 4th, and 13th vertices will generate the image shown in Figure 6.16. When the polygons are disjoint, they are drawn as if each is filled separately.

Listing 6.6 gives the WM_PAINT logic for drawing the polygons shown in Figure 6.16. Note that the same coordinates are used as for Figure 6.15, but the splits between the two points are different.

Listing 6.6: Code to draw the bizarre polygons of Figure 6.16
POINT Points [] = {

{ 200, 400 }, /* Right polygon */

{ 200, 0 },

{ 500, 0 },

{ 500, 300 },

{ 100, 300 },

{ 100, 200 },

{ 400, 200 },

{ 400, 100 },

{ 300, 100 },

{ 300, 400 },

{ -200, -400 }, /* Left polygon */

{ -200, 0 },

{ -500, 0 },

{ -500, -300 },

{ -100, -300 },

{ -100, -200 },

{ -400, -200 },

{ -400, -100 },

{ -300, -100 },

{ -300, -400 }

} ;

int PolyCounts [] = { 4, 9, 8 } ;

static void

polypoly_OnPaint(HWND hwnd)

{

RECT rect;

HDC hdc;

PAINTSTRUCT ps;

HBRUSH hbr;

int cxClient;

int cyClient;

/* Get the present size of the client area. */

GetClientRect (hwnd, &rect) ;

cxClient = rect.right ;

cyClient = rect.bottom ;

/* Get the device context and */

/* establish the coordinate mapping. */

hdc = BeginPaint (hwnd, &ps) ;

SetMapMode (hdc, MM_ISOTROPIC) ;

SetViewportOrgEx (hdc, cxClient / 2, cyClient / 2, NULL) ;

SetWindowExtEx (hdc, 1000, 1000, NULL) ;

SetViewportExtEx (hdc, cxClient, -cyClient, NULL) ;

hbr = GetStockBrush (LTGRAY_BRUSH) ;

SelectBrush (hdc, hbr) ;

/* Change the polygon-filling mode. */

SetPolyFillMode (hdc, ALTERNATE) ;

/* Draw the polygon. */

PolyPolygon (hdc, Points, PolyCounts, DIM(PolyCounts)) ;

EndPaint (hwnd, &ps) ;

return 0 ;

All polygons drawn by the PolyPolygon function use the polygon-filling mode in effect at the time of the call. You can't draw multiple polygons using different polygon-filling modes with a single function call. Figure 6.16, drawn by the algorithm in Listing 6.6, is drawn using the ALTERNATE polygon-filling mode. You can use the GDI Explorer to change the polygon fill mode using the right mouse button in the PolyPolygon display.

Creating and Using Stock and Custom Brushes

Until now, most of the filled figures have been filled using the default WHITE_BRUSH. Like the rest of the attributes of a device context, however, you can change the selected brush so that the interiors of filled figures can have whatever color and pattern you want. In Chapter 5, "Examining a Device Context in Depth", we covered brushes and how changing the brush origin affects how the pattern is applied to the interior of a figure. Now we show you how to use and create a brush. These functions are summarized in Table 6.5.
Table 6.5: Functions that create brushes
Function Explanation
CreateSolidBrush Creates a solid brush in a single color.
CreateHatchBrush Creates a brush using a predefined hatch pattern.
CreatePatternBrush Creates a brush using a bitmap pattern.
CreateDIBPatternBrush Creates a brush using a handle to a device-independent bitmap pattern. The interpretations of the colors are as follows:
DIB_PAL_COLORS The color table is an array of 16-bit indices into the logical palette.
DIB_RGB_COLORS The color table is an array of literal COLORREF RGB colors.
DIB_PAL_INDICES No color table is provided; the bitmap gives indices into the logical palette in the DC.
CreateDIBPatternBrushPt Creates a brush using a pointer to a device-independent bitmap pattern. The interpretations of the colors are as follows:
DIB_PAL_COLORS The color table is an array of 16-bit indices into the logical palette.
DIB_RGB_COLORS The color table is an array of literal COLORREF RGB colors.
DIB_PAL_INDICES No color table is provided; the bitmap gives indices into the logical palette in the DC.
CreateBrushIndirect Creates a brush from a LOGBRUSH specification.

You use a brush exactly the same as you do a pen. You select a brush into a device context by calling the SelectBrush macro API function. Windows defines the SelectBrush macro in windowsx.h as a call to the SelectObject function. It is defined as

#define SelectBrush(hdc, hbr)

((HBRUSH)SelectObject((hdc), (HGDIOBJ)(HBRUSH)(hbr)))

You should use the SelectBrush macro API rather than a call to the SelectObject function. The macro is more descriptive. You use it like this:

HBRUSH hbrush;

HBRUSH hbrPrevious ;

hbrPrevious = SelectBrush (hdc, hbrush) ;

The SelectBrush function returns the previously selected object. In the immediately preceding example, the SelectBrush function returns the previously selected brush. You must delete all the brushes you create. You need not be concerned about accidentally deleting stock objects; DeleteObject applied to a stock brush does nothing. A brush must not be selected into a device context when it is deleted, so you must select a different brush into the device context and then delete the created brush.

Windows provides the DeleteBrush macro to delete a brush. It is defined in windowsx.h as a call to the DeleteObject function. It looks like this:

#define DeleteBrush(hbr) DeleteObject((HGDIOBJ)(HBRUSH)(hbr))

Reselecting the previously selected brush always works, provided the previously selected brush was not also the current brush:

hbr = SelectBrush (hdc, hbrPrevious) ;

DeleteBrush (hbr) ;

Generally, you are selecting a previous brush that was replaced by a brush you have newly created. So you know that the previous brush cannot be the current brush, since the current brush is local to your procedure. But beware of this trap in the fully general case.

Stock Brushes

The easiest brushes to use are the stock brushes. The default brush in a device context is the stock object BLACK_BRUSH. Therefore the following code is equivalent to that just preceding:

DeleteBrush (SelectBrush (hdc, GetStockBrush (BLACK_BRUSH))) ;

Windows provides six different monochrome stock brushes, as listed in Table 6.6. Note that there are, for historical reasons, seven names for the six stock brushes.

You retrieve the handle to a stock brush like you do all stock objects: call the GetStockBrush function and pass the symbolic name of the desired stock brush. The Windows SDK defines the GetStockBrush macro API in windows.h. It looks like this:

#define GetStockBrush(i) ((HBRUSH)GetStockObject(i))

You can use the macro by writing something like this:

HBRUSH hbrush ;

hbrush = GetStockBrush (LTGRAY_BRUSH) ;

Custom Brushes

Creating a custom brush is a lot like creating a custom pen. A custom brush is a logical brush, just as a custom pen is a logical pen. So the appearance is not fully determined until the brush is selected into a device context. What happens when you select a logical brush into a device context depends on whether the brush is a solid brush or a brush that leaves a pattern. There are five functions you can use to create a custom brush: one to create a solid brush, three to create patterned brushes, and one that can create either type of brush.

Solid Brushes

The CreateSolidBrush function creates a logical brush that has the specified solid color:

HBRUSH CreateSolidBrush

(COLORREF Color) ;

The Color parameter is a COLORREF value that specifies the color of the brush. As such, it can be an RGB, a PALETTEINDEX, or a PALETTERGB color value. When you select a solid brush into a device context, GDI attempts to produce a color as close as possible to your specified logical color. When the device supports a pure color identical to your requested color, you get that color. However, when the device doesn't support your logical color exactly, GDI will approximate the color by creating an 8 ¥ 8 bitmap containing dithered pure colors. GDI then will use that bitmap for the brush. This is unlike ordinary ("cosmetic") pens in which GDI converts the specified color into the nearest pure color supported by the device.

Hatched Brushes

The CreateHatchBrush function creates a logical brush with a specified hatched pattern and color. The hatched pattern is produced by hatch marks-horizontal, vertical, and diagonal lines used to produce a "texture" to an area drawn by a brush. The CreateHatchBrush function call requires two parameters: the predefined hatch style name of the desired hatch style and a COLORREF value that specifies the color of the hatch marks. You create a hatched brush as follows:

hbrush = CreateHatchBrush(HatchStyle, Color);

The HatchStyle parameter can be one of the symbolic values shown in Figure 6.17.The color specified in the call to the -CreateHatchBrush function is the color of the hatch lines. GDI converts the specified color to the nearest pure color supported by the device. This differs from a solid brush, where dithered colors can be used. The color of the areas between the hatch lines is determined by the background mode attribute of the device context.

When the background mode is TRANSPARENT, GDI draws the hatch lines without changing the areas in between the lines. When the background mode is OPAQUE, GDI uses the background color (after converting it to a pure color) to fill in the areas between the hatches.

Bitmap Brushes

You aren't limited to using one of the six predefined hatch brushes. The CreatePatternBrush function creates a logical brush that has the pattern specified by the hBitmap parameter. A call to the function looks like the following:

hbrush = CreatePatternBrush (hbitmap) ;

When you create a brush based on a monochrome bitmap, the brush uses the current text and background colors. These are not necessarily black and white. Pixels represented in the bitmap by a 0 bit are drawn with the current text color, and pixels represented in the bitmap by a 1 bit are drawn with the current background color.

Because creating the bitmap can be a chore in itself, we will finish our examination of brush creation before we look at bitmaps.
Programmers who know the Win16 API may remember that the CreatePatternBrush API call created a brush that was only 8 ¥ 8, and in fact, used only the top-most, left-most 8 rows and columns no matter how large the bitmap was. A program that relied on this behavior in Win16 will probably create unexpected results in Win32. This is because there is no such restriction in Win32; the entire bitmap will be used.

An example of a pie chart created with a bitmap (in fact, our bitmap is the classic marble.bmp bitmap that comes with Windows) is shown in Figure 6.18. The accompanying code is in Listing 6.7, although the details of how a bitmap is created may not be apparent because we have not yet discussed that topic.

Listing 6.7: Code to fill using a bitmap
static BOOL

ids_GetTextExtentPoint32(HINSTANCE hinst, HDC hdc, int ids, LPSIZE size)

{

TCHAR text[256];

LoadString(hinst, ids, text, DIM(text));

return GetTextExtentPoint32 (hdc, text,

lstrlen(text), size);

}

static void

bmpbrush_OnPaint(HWND hwnd)

{

PAINTSTRUCT ps;

HDC hdc = BeginPaint(hwnd, &ps);

int xUL = -300;

int yUL = 200;

int xLR = 300;

int yLR = -200;

int xBegin = -250;

int yBegin = -100;

int xEnd = 0;

int yEnd = 250;

RECT rect;

RECT r;

#define BMP_CAPTION_OFFSET (-80)

HFONT font = createCaptionFont(-(abs(BMP_CAPTION_OFFSET)),

TimesFont);

HBRUSH brush;

HBITMAP bmp;

int save = SaveDC(hdc);

SIZE size;

// Get the device context

// and establish the coordinate mapping

GetClientRect(hwnd, &rect);

SetMapMode(hdc, MM_ISOTROPIC);

SetViewportOrgEx(hdc, rect.right / 2,

rect.bottom / 2, NULL);

SetWindowExtEx(hdc, 1000, 1000, NULL);

SetViewportExtEx(hdc, rect.right, - rect.bottom, NULL);

bmp = LoadBitmap(GetWindowInstance(hwnd),

MAKEINTRESOURCE(IDB_MARBLE));

brush = CreatePatternBrush(bmp);

// Do a drop-shadow 3-D effect here

#define BMP_SHADOW (-20)

SelectBrush(hdc, GetStockObject(BLACK_BRUSH));

Ellipse(hdc, xUL, yUL + BMP_SHADOW, xLR, yLR + BMP_SHADOW);

SelectBrush(hdc, GetStockObject(WHITE_BRUSH));

Ellipse(hdc, xUL, yUL, xLR, yLR);

SelectBrush(hdc, brush);

Pie(hdc, xUL, yUL, xLR, yLR, xBegin, yBegin, xEnd, yEnd);

r = rect;

DPtoLP(hdc, (LPPOINT)&r, 2);

r.top = yLR + BMP_CAPTION_OFFSET;

r.bottom = yLR + 2 * BMP_CAPTION_OFFSET;

SelectFont(hdc, font);

ids_GetTextExtentPoint32(GetWindowInstance(hwnd), hdc,

IDS_BMPBRUSH, &size);

r.bottom = r.top - size.cy;

ids_DrawText(GetWindowInstance(hwnd), hdc, IDS_BMPBRUSH, &r,

DT_CENTER);

RestoreDC(hdc, save);

DeleteObject(font);

DeleteObject(brush);

DeleteObject(bmp);

EndPaint(hwnd, &ps);

}

The helper function ids_GetTextExtentPoint32 simulates the effect of GetTextExtentPoint32, but it is passed an instance handle and a string ID instead of a string. The painting code first computes a scaled coordinate system with 0, 0 in the center of the client area. It loads a bitmap using LoadBitMap, which loads the "marble" bitmap we copied into our project and put into the resource file. The createCaptionFont helper function creates a font of a given size, which must be negative to indicate a desired point size. To ensure that the font value is always negative whether we place the caption above or below the pie chart, we pass in the negative of the absolute value of its separation. We use this value so that the font will always appear to be one "line" above or below the pie chart. After we draw a black ellipse slightly offset below a white ellipse (giving us a sort-of-3D effect), we draw our pie chart using the Pie function. To properly center the caption, we define a rectangle that is as wide as the window and use DPtoLP to convert the client coordinates we obtained from GetClientRect to logical co-ordinates in our new mapping. We then use the ids_GetTextExtentPoint32 function to compute the height so that we have a rectangle we can use for DrawText.

The CreateDIBPatternBrush and CreateDIBPatternBrushPt functions create a logical brush that has the pattern specified by a device-independent bitmap (DIB). Normally, color bitmaps are device-specific. They contain assumptions about the representation of a color on the device (for example, the number of color planes or adjacent pixels), as well as what pixel value represents which color. A device-independent bitmap specification is a device-independent recipe for describing the colors and image in a color bitmap.

A logical brush created by the CreateDIBPatternBrush or CreateDIBPatternBrushPt functions can be subsequently selected into a device context for any device that supports raster operations. The DIB specification is translated into the device-specific bitmap that is actually used on the device. You create a pattern brush with this function as follows:

hbrush = CreateDIBPatternBrush (hPackedDIB, Usage) ;

hbrush = CreateDIBPatternBrushPt (lpPackedDIB, Usage) ;

The hPackedDIB parameter to CreateDIBPatternBrush specifies a handle to a global memory block containing a packed DIB. Global memory is largely a vestige of the old Windows memory management methods. More modern usage will tend to use ordinary memory pointers, and you are more likely, in Win32, to use the CreateDIBPatternBrushPt function, which takes an ordinary memory pointer to a packed DIB. The Usage parameter specifies whether the bmiColors[] array of the BITMAPINFO structure of the bitmap contains explicit RGB values or indices into the currently realized logical palette. Device-independent bitmaps will be covered in greater depth in the following section, "Creating and Using Bitmaps".

The final function that can be used to create a brush is the CreateBrushIndirect function. It works very much like the CreatePenIndirect function. You pass a pointer to a structure, and the function creates the type of brush indicated by information in the structure. You can create any of the previously mentioned -custom brushes with the CreateBrushIndirect function. The only parameter to the function is a far pointer to a LOGBRUSH (logical brush) structure. A typical function call is as follows:

HBRUSH hbrush ;

LOGBRUSH logbrush ;

/* Fill in LOGBRUSH structure. */

hbrush = CreateBrushIndirect (&logbrush) ;

The LOGBRUSH structure has three fields and defines the style, color, and hatch style of the logical brush to create. It has three corresponding fields and is defined as follows:

typedef struct tagLOGBRUSH {

UINT lbStyle ;

COLORREF lbColor ;

int lbHatch ;

} LOGBRUSH ;

You set the lbStyle field to indicate the type of brush you want the CreateBrushIndirect function to create. The possible values are listed in Table 6.7.
Table 6.7: Logical brush types for the CreateBrushIndirect function
lbStyle lbColor lbHatch
BS_DIBPATTERN LOWORD (lbColor) con-tains either DIB_PAL_COLORS or DIB_RGB_COLORS Handle to global memory containing a packed device-independent bitmap (DIB). Cast to a HANDLE before using in a handle context; cast to an int to store it.
BS_DIBPATTERNPT LOWORD (lbColor) con-tains either DIB_PAL_COLORS or DIB_RGB_COLORS Pointer to memory containing a packed device-independent bitmap (DIB). Cast to a pointer before us-ing in a pointer context; cast to an int to store it.
BS_HATCHED Color of hatches. Hatch style as shown in Figure 6.17.
BS_HOLLOW Ignored. Ignored.
BS_PATTERN Ignored. Handle to a bitmap.
BS_SOLID Color of brush. Ignored.

The GetObject function works on brushes just as it does on pens. When you call the GetObject function and pass the handle to a logical brush as its parameter, it retrieves a LOGBRUSH structure that describes the brush. A typical call to GetObject for a logical brush is

LOGBRUSH logbrush ;

GetObject (hbrush, sizeof (logbrush), (LPSTR) &logbrush) ;

Note that how you interpret the lbHatch field depends on the value in the lbStyle field. You must ex-plicitly cast the int value to a handle (for BS_SOLID or BS_DIBPATTERN styles) or a pointer (for the BS_PATTERN style) to use it to access the bitmap.

Now that you've seen how to create and use a brush, we need to fill a gap: how to create the bitmap that describes a custom brush's color and pattern. Any bitmap can be used when creating a brush. So we don't restrict the following discussion to bitmaps used only to create a brush; we look at bit-maps in general.

Creating and Using Bitmaps

There are two fundamental types of bitmaps in Windows: device-specific bitmaps and device-independent bitmaps (DIBs). A device-specific bitmap is a pattern of bits stored in memory. The bits for a pixel are or-gan-ized in the same fashion as those on a particular output device. A bitmap for an output device that uses a different organization of bits for a pixel cannot be used on the first output device. Basically, the bits do not line up properly.

A DIB is also a pattern of bits stored in memory. However, the bits for a pix-el in a DIB are not necessarily organized in the same manner as bits for a pixel on the eventual output device. They could be organized in the same fashion, but they do not need to be. Instead, the bit-map is organized in a fashion that best describes the encoded image. Doing this takes into account the number of colors used in the bitmap, the actual colors used, and the data redundancy in the bit-map (used when selecting a compressed bitmap format).

The format of a device-specific bitmap is known (by implication) from the device on which it is used. The format of a DIB is specified by an accompanying DIB specification. A DIB spec-i-fi-ca-tion describes the -following:

Bitmap Functions

Table 6.8 lists the functions that create bitmaps as well as a few GDI drawing functions that haven't been presented. Once you've created a bitmap that is compatible with a specific device, you can select that bitmap into a memory device context for the device. You can then draw on the bitmap the same way you can draw on a device, by calling GDI drawing functions and specifying the device context.

For example, the LineTo and Polygon functions will draw a line and a polygon on a bitmap when you specify a memory device context. Other functions listed in Table 6.8 are often used to draw into a bitmap. The BitBlt function copies a rectangular area from one device context to another. When the device contexts are memory device contexts, this function copies part of one bitmap to another. You can also use it to copy part of a device's display surface, such as the pixels in a window, to a memory device context and thus to the pixels of a bitmap. The StretchBlt function works similarly to the BitBlt function but can also stretch or compress the copied bitmap. We look at some of the other "Blt" functions in more detail (See, for example, the description of the PatBlt function on page 340, the StretchBlt function on page 351, the PlgBlt function on page 353, and the MaskBlt function on 357 ). You also can use the "BLT Explorer" to see how these functions operate. This Explorer is a component of the GDI Explorer that is included on the CD-ROM that accompanies this book.
Table 6.8: Functions that operate on bitmaps
Function Description
BitBlt Copies a rectangular area from a source device context to a destination device context.
CreateBitmap Creates a device-specific memory bitmap. This function is sim-ilar to CreateDIBitmap.
CreateBitmapIndirect Creates a device-specific memory bitmap from a description in a data structure.
CreateCompatibleBitmap Creates a device-specific memory bitmap that is compatible with a specified device and that may be selected into a mem-ory device context associated with the device.
CreateDIBitmap Creates a device-specific memory bitmap from a device-in-de-pendent bitmap (DIB) specification. The pixels in the bitmap can be initialized when created, if desired. This function is sim-ilar to CreateBitmap.
CreateDIBSection Creates a device-independent bitmap that applications can write to -directly.
CreateDiscardableBitmap Creates a discardable device-specific memory bitmap that is compatible with a specified device and that may be selected into a memory device context associated with the device.
ExtFloodFill Fills an area of a display surface with the current brush. It can fill to a specified color boundary or fill an area that is a specified color.
FloodFill Fills an area of a display surface with the current brush. It fills only to a specified color boundary.
GetBitmapBits Retrieves the pixels from a specified bitmap. This function is similar to GetDIBits. It is present in Win32 for com-pat-i-bility with older Win16 that is being ported, but it is considered obsolete. You shouldn't use it for any new code.
GetBitmapDimensionEx Retrieves the height and width of a bitmap by updating a SIZE -structure.
GetDIBits Retrieves the pixels from a specified bitmap. This function is similar to GetBitmapBits, which it replaces in Win32 ap-pli-cations. It can also be used to retrieve bitmap dimensions.
GetPixel Gets the RGB color value for a pixel.
LoadBitmap Loads a bitmap from a resource file.
PatBlt Sets the pixels of a bitmap to a pattern.
MaskBlt Combines the color data for the source and destination using a bitmap mask and a pair of raster operations.
PlgBlt Transfers a block of bits, optionally masking color bits, to a region delimited by a parallelogram. The mask is specified by a monochrome bitmap.
SetBitmapBits Sets the pixels of a memory bitmap. This function is similar to SetDIBits. It is present in Win32 for compatibility with older Win16 code that is being ported, but it is considered ob-so-lete.
SetBitmapDimensionEx Sets the height and width of a bitmap and, optionally, updates a SIZE structure with the previous height and width.
SetDIBits Sets the pixels of a memory bitmap directly from a DIB.
SetDIBitsToDevice Draws on a device surface directly from a DIB.
SetPixel Sets a pixel to the specified COLORREF color or to the nearest matching color. Returns the actual color.
SetPixelV Sets a pixel to the specified COLORREF color or to the nearest matching color.
StretchBlt Copies a rectangular area from a source device context to a destination device context. The source bitmap is stretched or compressed, if necessary.
StretchDIBits Copies a rectangular area of a DIB to a different rectangular area on the device associated with the destination device con-text. The source bitmap is stretched or compressed, if nec-es-sary.

Creating a Bitmap

You create a bitmap by specifying the bitmap's width, height, and color format and, optionally, the initial value of the pixels. The phrase "create a bitmap" actually means "create a device-specific bitmap." The -device-specific bitmap can be created from either device-dependent data or device-independent data.

When you create a device-specific bitmap, GDI actually allocates space for the bitmap and, if the bitmap is initialized, copies the initial values for the pixels to the bitmap. You receive a handle to the bitmap of type HBITMAP. You do not have direct access to the memory containing the pixels of the bitmap. A device-specific bitmap is a GDI object. GDI objects are automatically released when an application terminates. But it is generally cleaner to delete all bitmaps you create. In particular, because bitmaps can be quite large, if you fail to release them early your application will gradually consume more and more memory and bog down the system. As always, you must not delete a bitmap while it is currently selected into a memory device context.

There are several ways to create a bitmap:

1. Use external bitmap files. These files are created by a program such the Paint application included with Microsoft Windows or by a tool such as the bitmap resource editor, which is part of most development environments (such as the Visual C++ Workbench).

Creating a Bitmap Resource from a Bitmap File

The easiest way to create a bitmap file is to use a program such as the Windows Paint program. The Paint program can produce either monochrome or color DIB (.bmp) files. Many development environments, such as Microsoft's Visual C++ environment, have bitmap editors as well, and you can use any commercial program, including photo processing programs, that can produce a DIB file.

You also can create your own bitmap file. A bitmap file is a file containing binary data that consists of three sec-tions. The file begins with a BITMAPFILEHEADER structure, which looks like the following:

typedef struct tagBITMAPFILEHEADER {

WORD bfType;

DWORD bfSize;

WORD bfReserved1;

WORD bfReserved2;

DWORD bfOffBits;

} BITMAPFILEHEADER;

The bfType field identifies the file as a DIB file. It must have the value "BM" (for BitMap, of course). Remember that multibyte integer values are stored in byte-swapped order on Intel processors. You must swap the bytes in an assignment so that they will be in the correct order in memory:

BITMAPFILEHEADER bmfh ;

bmfh.bfType = 0x4D42 ; /* "MB" is swapped to "BM" in memory. */

The bfSize field specifies the size of the file in DWORDs. Both the bfReserved1 and bfReserved2 fields are reserved and must be set to 0. The bfOffBits field specifies the offset (in bytes) from the BITMAPFILEHEADER to the actual bitmap in the file.

Either a BITMAPINFO structure or a BITMAPCOREINFO structure must immediately follow the BITMAPFILEHEADER structure in the DIB file.
A BITMAPCOREINFO structure defines the dimensions of the bitmap and the colors used in an OS/2 Presentation Manager version 1.x DIB. If you are running in an environment in which OS/2 is used or in which you might be importing OS/2 files, you need to be prepared to handle one of these. Win-dows can use either Windows DIBs or Presentation Manager DIBs. You can tell the difference between the two by the value of the first field in each structure. Because we're talking about Windows, we discuss only the Windows BITMAPINFO structure.

A BITMAPINFO structure defines the dimensions and color of a Windows DIB. It has the following definition:

typedef struct tagBITMAPINFO {

BITMAPINFOHEADER bmiHeader;

RGBQUAD bmiColors[1];

} BITMAPINFO;

The bmiHeader field specifies a BITMAPINFOHEADER structure that contains information about the dimensions and color format of a DIB. The bmiColors field specifies an array of RGBQUAD data structures that define the colors in the bitmap. A BITMAPINFOHEADER has the following fields:

typedef struct tagBITMAPINFOHEADER{

DWORD biSize;

LONG biWidth;

LONG biHeight;

WORD biPlanes;

WORD biBitCount

DWORD biCompression;

DWORD biSizeImage;

LONG biXPelsPerMeter;

LONG biYPelsPerMeter;

DWORD biClrUsed;

DWORD biClrImportant;

} BITMAPINFOHEADER;

The fields of a BITMAPINFOHEADER are described in Table 6.9.
Table 6.9: The fields of a BITMAPINFOHEADER structure
Field Description
biSize Size of a BITMAPINFOHEADER structure, in bytes.
biWidth Width of the bitmap, in pixels.
biHeight Height of the bitmap, in pixels.
biPlanes Number of planes for the target device. Must be 1.
biBitCount Number of bits per pixel. Must be 1, 4, 8, or 24.
biCompression Type of compression for a compressed bitmap. It can be one of the fol-lowing values:
BI_RGB Bitmap is not compressed.
BI_RLE8 Bitmap is run-length encoded and has 8 bits per pixel.
BI_RLE4 Bitmap is run-length encoded and has 4 bits per pixel.
biSizeImage Size of the image in bytes.
biXPelsPerMeter Horizontal resolution in pixels per meter of the target device for the bit-map. This is a suggested figure. You can use this number to locate a bit-map that best matches the device on which it will be used.
biYPelsPerMeter Vertical resolution in pixels per meter of the target device for the bit-map. This is a suggested figure. You can use this number to locate a bitmap that best matches the device on which it will be used.
biClrUsed Number of color indices in the color table actually used by the bitmap. When 0, the bitmap uses all the possible 2biBitCount color indices.
biClrImportant Number of color indices in the color table that hold colors important when displaying the bitmap. When 0, all colors are important.

The biSizeImage, biXPelsPerMeter, and biYPelsPerMeter are advisory. Many applications, for example, set all three fields to 0 and may even also set biClrUsed and biClrImportant to 0.

The bmiColors field, which is an array of RGBQUAD structures, immediately follows the BITMAPINFOHEADER structure. Each element of the bmiColors array defines the color of a pixel whose value equals the index of the element. That is, the element bmiColors[0] contains the color for pixel values of 0, bmiColors[1] contains the color for pixel values of 1, and so on. You should arrange colors in the array in descending order of importance. When a DIB is converted to a device-specific bitmap for a device that supports fewer colors than are used in the DIB, Windows uses the colors listed at the beginning of the array. Pixels using later, unsupported colors are displayed in the nearest matching color.

The bmiColors array is defined to have one entry in order to establish the location of the beginning of the array. You must, however, provide room for one entry for each possible color. When the biBitCount field of the BITMAPINFOHEADER structure is set to 1, the bitmap is a monochrome bitmap, so the bmiColors field must contain two entries. (Have you ever noticed that monochrome really should be called bichrome?) Each bit in the bitmap represents a pixel and may be clear (0) or set (1). When the bit is clear, the pixel has the color of the first entry in the bmiColors array; when the bit is set, the pixel has the color of the second entry in the array. Notice that a monochrome bitmap is not restricted to simply black and white but can have any two colors: the color in bmiColors[0] and the color in bmiColors[1].

When the biBitCount field of the BITMAPINFOHEADER structure is set to 4, the bitmap contains pixels, each of which can have one of 16 colors. Therefore the bmiColors field can contain up to 16 entries. You need to allocate room only for the number of entries used by pixel values in the bitmap. For example, when you use only 12 different pixel values, set the biClrUsed parameter to 12 and allocate only 12 entries in the bmiColors array.

When the biBitCount field of the BITMAPINFOHEADER structure is set to 8, the bitmap contains pixels, each of which can have one of 256 colors. Therefore the bmiColors field can contain up to 256 entries. As before, you need to allocate space only for the entries actually used.

When the biBitCount field of the BITMAPINFOHEADER structure is set to 24, the bitmap contains pixels, each of which can have one of 16,777,216 colors. In this case, the bmiColors field is not used. The 24 bits for each pixel directly encode the red, green, and blue intensities.

Normally, you specify the colors in the bmiColors array as red, green, and blue intensities, each ranging from 0 to 255. Each RGBQUAD array element looks like the following. The rgbReserved field must be set to 0.

typedef struct tagRGBQUAD {

BYTE rgbBlue;

BYTE rgbGreen;

BYTE rgbRed;

BYTE rgbReserved;

} RGBQUAD;
A DIB uses a different coordinate system than a device-specific bitmap. A DIB uses a Cartesian first-quadrant coordinate system. The origin of the bitmap is the lower-left corner of the bitmap. Horizontal pixel coordinates increase from left to right and vertical pixel coordinates increase from bottom to top.

A device-specific bitmap uses device coordinates. So its origin is the upper-left corner of the bitmap. Horizontal coordinates increase the same direction as a DIB (left to right), but vertical coordinates increase in the -opposite direction (top to bottom rather than bottom to top).

However, you also can allocate the bmiColors array as an array of WORDs rather than RGBQUAD structures. Each WORD entry represents an index into the currently selected and realized logical palette. When you use this format to specify the colors for a DIB, you must call the DIB functions with the Usage parameter set to DIB_PAL_COLORS. Typically, you don't use this format for a DIB stored in a file because the color palette may not have the proper colors when the DIB is next used.

The actual bitmap follows the BITMAPINFO structure. It does not have to be immediately contiguous because the location of the bitmap is specified by the bfOffBits parameter of the BITMAPFILEHEADER structure. The bits for each pixel are packed together.

The bitmap begins with the bottom row of pixels and at the left side. Each row must be a multiple of 4 bytes in length. The first pixel in a row of a monochrome bitmap is encoded in the most-significant bit of the first byte in the row. The first pixel in a row of a 16-color bitmap is encoded in the most-significant 4 bits of the first byte in the row. The first pixel in a row of a 256-color bitmap is encoded in all 8 bits of the first 3 bytes in the row (each pixel occupies 1 byte in this case). Twenty-four bits-per-pixel bitmaps use groups of 3 bytes for each pixel. The 3 bytes contain the red, green, and blue intensity values for the pixel.

As soon as you have a file containing a bitmap, you can add a BITMAPBITMAP resource statement to your application's resource script (.rc) file. You usually do this via whatever environment tool you are using. For example, in Visual C++ the act of creating a bitmap in the resource editor will include a reference to it in the resource script. If the bitmap was created by some external program, you need to generate a reference to it. The BITMAP statement associates a character-string identifier or an integer with the bitmap contained in the file. The resource compiler copies the bitmap from the bitmap file (typically, such a file uses the .bmp extension) into the compiled resource (.res) file. As the program is linked, the linker inserts the compiled resources into the executable (.exe) program file.

For example, suppose you've drawn a custom brush pattern using a bitmap editor application and saved it in a file called mybrush.bmp. If you use the built-in bitmap editor of your environment, the BITMAP statement will usually be inserted for you. If you import the file into your resource set, your environment tool will also add the BITMAP statement so that in your application's resource script file, you will find a BITMAP statement of the following form:

resID BITMAP filename

The resID field specifies either an integer value or a unique character string that identifies the resource. You use this value in your program to reference the bitmap. In the example proposed, let's use the following statement:

RoughTexture BITMAP mybrush.bmp

Be aware, however, that in looking at this statement you can't tell how to access the resource. For example, most environment tools will also create a definition

#define RoughTexture 17

so the effect is as if you had the resource:

17 BITMAP MYBRUSH.BMP

This is particularly important to understand how your environment resource tool handles this when you are creating a bitmap resource from an externally-created bitmap. If it creates a #define, you will need to use MAKEINTRESOURCE when you want to name the resource; if it doesn't create a #define, you will use the string name of the resource.

When you want to create a brush based on this pattern, you use the LoadBitmap or LoadImage function to load the bitmap from the executable file. You pass the function the instance handle of the module containing the desired bitmap and a parameter identifying the desired bitmap within that module. The function returns a handle to the created device-specific memory bitmap. In this example, you can load the bitmap one of two ways, depending on how you identified the bitmap on the BITMAP statement in the resource script file. You can load it either by name:

hbitmap = LoadBitmap (hInstance, _T("RoughTexture")) ;

or by integer identifier:

hbitmap = LoadBitmap (hInstance, MAKEINTRESOURCE (RoughTexture)) ;

It is critical that you use the correct form; the two are not interchangeable. Note that since you can't tell by looking at your .rc file which is the correct form, you have to either rely on your environment tool or read the appropriate .h file to determine which form to use. For example, in Visual C++ if you get a list of bitmaps or are displaying the bitmap in question, it will display the name as "RoughTexture" (quotes included) if you must use the string form and as RoughTexture (no quotes) if you must use the MAKEINTRESOURCE form. We point this out because it is a frequent source of problems, particularly for people either learning Windows for the first time or those moving to an integrated development environment such as those supported by Borland or Microsoft. The Visual C++ environment, if you are creating the bitmap with its built-in bitmap editor, will actually assign a name and create the #define. (It usually suggests a name starting with IDB_, a convention you may wish to maintain.) So you will find the following declarations in your files:

#define IDB_RoughTexture 17 // this is in RESOURCE.H

IDB_RoughTexture BITMAP filename // this is in your resource file
The LoadBitmap function will not load bitmaps using 256-color palettes. This is because Windows treats bitmaps loaded via LoadBitmap as 16-color bitmaps. To load bitmaps with higher resolution, you need to use the more general LoadResource call and extract the color information.

Once you have successfully done a LoadBitMap, you then can use the handle to the bitmap to create a logical brush that has that pattern by calling the CreatePatternBrush function:

hbrush = CreatePatternBrush (hbitmap) ;

Then you use this brush the way you use the others we've discussed. You select it into a device context, draw the filled object(s), select the default brush (or a stock brush) back into device context, and, finally, delete the brush and the bitmap:

hbrush = CreatePatternBrush (hbitmap) ;

DeleteBitmap (hbitmap) ;

SelectBrush (hdc, hbrush) ;

Rectangle (hdc, xUL, yUL, xLR, yLR) ;

SelectBrush (hdc, GetStockBrush (WHITE_BRUSH)) ;

DeleteBrush (brush) ;

As shown in this example, the bitmap can be deleted any time after you've used it to create the brush. You don't have to wait until the brush is deleted to delete the bitmap on which it's based.

Bitmaps are used for more than just brush patterns. An entire bitmap can be copied to a device surface as a complex picture or a large pattern, although somewhat indirectly. You create a compatible memory device con-text for the destination device, select the bitmap into the memory device context, and then copy the bitmap from the memory device context to the destination device context:

BITMAP bm ;

GetObject (hbitmap, sizeof (bm), (LPSTR) &bm) ;

hMemoryDC = CreateCompatibleDC (hdc, bm.bmWidth, bm.bmHeight) ;

SelectBitmap (hMemoryDC, hbitmap) ;

BitBlt (hdc, 0, 0, bm.bmWidth, bm.bmHeight,

hMemoryDC, 0, 0, SRCCOPY) ;

Creating and Drawing into a Blank Bitmap

Complex images may take quite a bit of work to create by hand. You may find it easier to create an uninitialized bitmap and use the drawing functions of the GDI to draw the bitmap you want. First, create the uninitialized bitmap by calling either the CreateBitmap, CreateBitmapIndirect, CreateCompatibleBitmap, or CreateDIBitmap functions. The functions look like the following:

HBITMAP CreateBitmap (int Width, int Height,

UINT Planes, UINT BitCount,

CONST VOID * lpBits) ;

HBITMAP CreateBitmapIndirect (CONST BITMAP * Bitmap) ;

HBITMAP CreateCompatibleBitmap (HDC hdc, int Width, int Height) ;

HBITMAP CreateDIBitmap (HDC hdc, CONST BITMAPINFOHEADER * lpInfoHeader,

DWORD Usage,

CONST VOID * lpInitBits,

CONST BITMAPINFO * lpInitInfo,

UINT Usage) ;
Those who know the Win16 API may notice that CreateDiscardableBitmap is missing from this set. This is because the concept of "discardable" storage was part of the Win16 memory management. Although the API call is retained in Win32, it is obsolete there and turns into a CreateCompatibleBitmap call.

Because all these functions return a device-specific bitmap, they must be provided with the format used by the device to represent colors and pixels. You specify this information explicitly when calling the CreateBitmap function. For a monochrome bitmap, this task is easy: Set both the Planes and BitCount parameters to 1. Setting the lpBits parameter to NULL specifies that the bitmap should not be initialized. A monochrome 8 ¥ 8 uninitialized bitmap used for a brush pattern can be created by the following:

hbitmap = CreateBitmap (8, 8, 1, 1, NULL) ;

The same approach can be used when calling the CreateBitmapIndirect function, but the information is passed via the fields of the BITMAP structure that is passed as the function parameter.

It's a bit more difficult to create a color bitmap using these two functions and still ensure that the bitmap is compatible with the desired device. You must get the device capabilities using the GetDeviceCaps function and create the bitmap accordingly. However, you can let Windows do the work by using either the CreateCompatibleBitmap or CreateDIBitmap functions.

You provide the handle of a device context to all three functions. Windows uses the handle to create a bitmap that has the same number of color planes and bits per pixel as the specified device.

The CreateCompatibleBitmap function is the function used most often to create an uninitialized color bitmap. It doesn't accept a parameter that specifies initialization data for the bitmap, and it creates the bitmap compatible with the specified device so that you don't have to worry about color planes and bits per pixel.

The CreateDIBitmap function also can be used to create an uninitialized bitmap, although it's much more useful when creating an initialized color bitmap, as you'll see later in the chapter. The following statements use the CreateDIBitmap function to create an uninitialized bitmap:

BITMAPINFOHEADER bmih ;

/* Initialize the BITMAPINFOHEADER here. */

hbitmap = CreateDIBitmap (hdc, &bmih, 0, NULL, NULL, 0) ;

After you have the handle to a device-specific bitmap, you can select it into a compatible memory device context using the SelectBitmap macro API function. Similar to the others you've seen, Windows defines it in windowsx.h this way:

#define SelectBitmap(hdc, hbm) \

((HBITMAP)SelectObject((hdc), (HGDIOBJ)(HBITMAP)(hbm)))

You can use the SelectBitmap macro in place of the SelectObject function to make your code more understandable:

SelectBitmap (hMemoryDC, hbitmap) ;

You can retrieve the current bitmap by using the GetCurrentObject function:

HBITMAP bm = (HBITMAP)GetCurrentObject(hdc, OBJ_BITMAP);

All drawing functions issued on the memory device context now draw on the newly created bitmap. It's important to note that uninitialized bitmaps are just that-completely uninitialized. In particular, the pixels in the bitmap aren't cleared to any value: 0, 1, white, or black. You'll probably want to start by clearing the entire bitmap to a known color. This is easiest done by using the PatBlt function. "PatBlt" stands for "Pattern Block transfer". The function call looks like this:

PatBlt (hdc, X, Y, Width, Height, Rop) ;

The PatBlt function creates a pattern on the specified device that is based on the selected brush and the pixels already on the device. When the specified device context refers to a memory device context, the pattern is applied to the selected bitmap. This function does not use a bounding rectangle to specify the area. Instead, the X and Y parameters specify one corner (in logical coordinates) of the rectangle that is to receive the pattern. The Width and Height parameters specify the width and height of the rectangle in logical units. Therefore the corner of the rectangle opposite the point (X, Y) is the point (X + Width, Y + Height).

The coordinates of any corner of the desired rectangle can be given as the X and Y parameters. The Width and Height parameters can be positive or negative. These all are logical values, and the mapping mode specifies the orientation of the axes. Therefore the upper-left corner of the rectangle can be at the point (X, Y) or (X + Width, Y) or (X, Y + Height) or (X + Width, Y + Height).

GDI determines the upper-left corner of the rectangle and copies the pattern to it. Like the area functions previously described, the top and left sides of the rectangle are included in the modified area. The right and bottom sides of the rectangle are outside the modified area.

GDI combines the colors of the pixels in the selected brush with the colors already present on the device according to a raster operation code that is similar, but not identical, to the raster operation code used when drawing lines. In general, raster operation codes describe the operation that should be applied to a source operand, a brush, and a destination operand. However, the Rop parameter to the PatBlt function cannot be an operation code that refers to a source operand. The valid operation codes for the PatBlt function that have common names are listed in Table 6.10. Any raster operation code that doesn't refer to a source operand, but only to a pattern and destination operand, can be used as the raster operation code for the PatBlt function. You must specify the ones that are not listed in Table 6.10 numerically.4 The hexadecimal values for all 16 PatBlt raster operation codes are give in Table 6.11.
Table 6.10: Raster operation codes for the PatBlt function
ROP Code Result
PATCOPY Copies the brush to the destination bitmap.
PATINVERT Combines the brush and the destination bitmap using the Boolean Exclusive OR (XOR) operation.
DSTINVERT Ignores the brush and inverts the destination bitmap using the Boolean bitwise NOT -operation.
BLACKNESS Ignores the brush and sets the destination bitmap to all bits 0. This sets the bitmap to all-black. (Technically, a pixel value of 0 doesn't necessarily mean black.)
WHITENESS Ignores the brush and sets the destination bitmap to all bits 1. This sets the bitmap to all-white. (Technically, a pixel value with all bits set to 1 doesn't necessarily mean white.)

Table 6.11: The Boolean operations performed by the raster operation codes usable by the PatBlt function
Pattern(P) 1 1 0 0 Decimal Result Boolean Operation ROP Code
Dest(D) 1 0 1 0
Name
BLACKNESS 0 0 0 0 0 D = 0 0x000042
- 0 0 0 1 1 D = ~(P | D) 0x0500A9
- 0 0 1 0 2 D = ~P & D 0x0A0329
- 0 0 1 1 3 D = ~P 0x0F0001
- 0 1 0 0 4 D = P & ~D 0x500325
DSTINVERT 0 1 0 1 5 D = ~D 0x550009
PATINVERT 0 1 1 0 6 D = P ^ D 0x5A0049
- 0 1 1 1 7 D = ~(P & D) 0x5F00E9
- 1 0 0 0 8 D = P & D 0xA000C9
- 1 0 0 1 9 D = ~(P ^ D) 0xA50065
- 1 0 1 0 10 D = D 0xAA0029
- 1 0 1 1 11 D = ~P | D 0xAF0229
PATCOPY 1 1 0 0 12 D = P 0xF00021
- 1 1 0 1 13 D = P | ~D 0xF50225
- 1 1 1 0 14 D = P | D 0xFA0089
WHITENESS 1 1 1 1 15 D = 1 0xFF0062

Finally, to stress the point again, bitmaps are GDI objects. You should delete them as soon as they are no longer needed, ideally before your application terminates.

Next we look at an example. We create an uninitialized 16 ¥ 32-pixel bitmap compatible with the display device, clear it to all-black, and draw a green X from corner to corner. The following code assumes that a common display context is retrieved by the GetDC function-particularly, that the MM_TEXT mapping mode is in effect:

HDC hdc ;

HDC hMemoryDC ;

HBITMAP hbitmap ;

HBITMAP hbitmapOrig ;

HPEN hpen ;

HPEN hpenOrig ;

hdc = GetDC (hwnd) ;

hMemoryDC = CreateCompatibleDC (hdc) ;

hbitmap = CreateCompatibleBitmap (hdc, 16, 32) ;

ReleaseDC (hwnd, hdc) ;

hbitmapOrig = SelectBitmap (hMemoryDC, hbitmap) ;

PatBlt (hMemoryDC, 0, 0, 16, 32, BLACKNESS) ;

hpen = CreatePen (PS_SOLID, 0, RGB (0, 255, 0)) ;

hpenOrig = SelectPen (hMemoryDC, hpen) ;

LineTo (hMemoryDC, 16, 32) ;

MoveToEx (hMemoryDC, 16, 0, NULL) ;

LineTo (hMemoryDC, 0, 32) ;

SelectBitmap (hMemoryDC, hbitmapOrig) ;

SelectPen (hMemoryDC, hpenOrig) ;

DeletePen (hpen) ;

DeleteDC (hMemoryDC) ;

/* Now you have the desired bitmap. */

/* When you no longer need the bitmap, delete it. */

DeleteBitmap (hbitmap) ;

Creating an Initialized Bitmap

Three functions can create an initialized bitmap: CreateBitmap, CreateBitmapIndirect, and CreateDIBitmap. The CreateBitmap and CreateBitmapIndirect functions are best for creating monochrome bitmaps. As described in the previous section, when creating a color bitmap using these functions, you must provide explicit information about the number of color planes and bits per pixel for the bitmap. This task is tedious and very device-dependent.

The CreateDIBitmap function enables you to specify the initial values of a color bitmap in a device-independent manner. A DIB specification is used to convert the DIB to a device-specific bitmap.

First we'll discuss creating initialized device-dependent bitmaps, and then we'll look at creating color bitmaps using DIB specifications. Let's create an initialized monochrome bitmap similar to the X described above. Because we lack green, we'll make it a white X on a black 32 ¥ 32-pixel background. The CreateBitmap function call will look like the following:

BYTE Bits [] = { 0x80, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x02,

0x20, 0x00, 0x00, 0x04, 0x10, 0x00, 0x00, 0x08,

0x08, 0x00, 0x00, 0x10, 0x04, 0x00, 0x00, 0x20,

0x02, 0x00, 0x00, 0x40, 0x01, 0x00, 0x00, 0x80,

0x00, 0x80, 0x01, 0x00, 0x00, 0x40, 0x02, 0x00,

0x00, 0x20, 0x04, 0x00, 0x00, 0x10, 0x08, 0x00,

0x00, 0x08, 0x10, 0x00, 0x00, 0x04, 0x20, 0x00,

0x00, 0x02, 0x40, 0x00, 0x00, 0x01, 0x80, 0x00,

0x00, 0x01, 0x80, 0x00, 0x00, 0x02, 0x40, 0x00,

0x00, 0x04, 0x20, 0x00, 0x00, 0x08, 0x10, 0x00,

0x00, 0x10, 0x08, 0x00, 0x00, 0x20, 0x04, 0x00,

0x00, 0x40, 0x02, 0x00, 0x00, 0x80, 0x01, 0x00,

0x01, 0x00, 0x00, 0x80, 0x02, 0x00, 0x00, 0x40,

0x04, 0x00, 0x00, 0x20, 0x08, 0x00, 0x00, 0x10,

0x10, 0x00, 0x00, 0x08, 0x20, 0x00, 0x00, 0x04,

0x40, 0x00, 0x00, 0x02, 0x80, 0x00, 0x00, 0x01 } ;

hbitmap = CreateBitmap (32, 32, 1, 1, Bits) ;

A couple of points aren't apparent from this example. Each scan line in the bitmap must be an even number of bytes long. Notice that this is different from a DIB. Each scan line in a DIB must be a multiple of 4 bytes in length. GDI assumes that a bitmap passed to the CreateBitmap function is composed of an array of short integer values. Scan lines are organized in a device-specific bitmap with the top line (line 0) at the beginning of the array, line 1 immediately following it, proceeding sequentially up to line Height-1. This, too, differs from a DIB. In a DIB, the bottom scan line comes first in the array, followed by the next line up, and so on. One bits represent white and 0 bits represent black. The left-most bit of a scan line is in the most significant bit of the first byte of data for the row.

You can set the bits of an already-created bitmap with the SetBitmapBits function. In this example, hbitmap is the handle of the bitmap whose bits you want to set and Count is a DWORD containing the number of bytes in the array pointed to by the lpBits parameter:

SetBitmapBits (hbitmap, Count, lpBits) ;

To set the X in a bitmap, we could do

SetBitmapBits (hbitmap, sizeof Bits, Bits) ;

We mentioned that each scan line of a device-specific bitmap must be an even number of bytes in length. Redefining the Bits array as an array of WORDs to ensure this necessity produces its own problems. For example, this bitmap isn't the same as the preceding one:

WORD Bits [] = { 0x8000, 0x0001,

0x4000, 0x0002,

0x2000, 0x0004,

0x1000, 0x0008,

.

.

.

The reason is that WORDs are stored in byte-swapped order on Intel processors. That is, the WORD 0x8000 is stored in memory as 0x00 followed by 0x80 in the next-higher memory location. We could, of course, swap the bytes in the definition of the constants (0x8000 to 0x0080), but then the pattern of the bitmap would become less obvious to those maintaining the source code.

You can use the CreateBitmapIndirect function to create the same bitmap with the following code. Notice the distinction between a BITMAP structure and an HBITMAP handle:

BITMAP bm ;

HBITMAP hbitmap ;

bm.bmType = 0 ;

bm.bmWidth = 32 ;

bm.bmHeight = 32 ;

bm.bmWidthBytes = 4 ;

bm.bmPlanes = 1 ;

bm.bmBitsPixel = 1 ;

bm.bmBits = (LPVOID) Bits ;

hbitmap = CreateBitmapIndirect (&bm) ;

A BITMAP is essentially a memory-based structure that contains some state information and a reference to the bits of the bitmap. However, Windows itself does not actually process this structure. Instead, it requires a copy of the bitmap description and the contents which is under its own control, and this copy is referenced by a bitmap handle, whose type is HBITMAP. Until you have converted a bitmap to a GDI object, Windows cannot do anything with it. Much of this is due to the original design of Windows for the 8088, in which memory management of the "virtual" memory was so critical. So, while Win32, in principle, should be able to reference a bitmap directly through the BITMAP structure, it in fact retains the same API and requires a bitmap handle.

Creating an Initialized Bitmap from a DIB

You also can use the CreateDIBitmap function to create the same monochrome bitmap. The CreateDIBitmap function creates a device-specific bitmap and initializes it with the image contained in a DIB. You must fill out a DIB specification to describe the size of the bitmap, the format of the image in the DIB, and the colors to use for pixel values. You can create the monochrome bitmap with the following code:

struct tagMonoDIBSpec {

BITMAPINFOHEADER I;

RGBQUAD Colors [2];

} mdib ;

mdib.ih.biSize = sizeof (mdib.ih) ;

mdib.ih.biWidth = 32 ;

mdib.ih.biHeight = 32 ;

mdib.ih.biPlanes = 1 ;

mdib.ih.biBitCount = 1 ;

mdib.ih.biCompression = 0 ;

mdib.ih.biSizeImage = 0 ;

mdib.ih.biXPelsPerMeter = 0 ;

mdib.ih.biYPelsPerMeter = 0 ;

mdib.ih.biClrUsed = 0;

mdib.ih.biClrImportant = 0 ;

mdib.Colors [0].rgbRed = 0 ; // Black (0, 0, 0)

mdib.Colors [0].rgbGreen = 0 ;

mdib.Colors [0].rgbBlue = 0 ;

mdib.Colors [1].rgbRed = 0xFF ; // White (255, 255, 255)

mdib.Colors [1].rgbGreen = 0xFF ;

mdib.Colors [1].rgbBlue = 0xFF ;

hdc = GetDC (hwnd) ;

hbitmap = CreateDIBitmap (hdc,

(LPBITMAPINFOHEADER) &mdib.ih,

CBM_INIT,

(LPVOID) Bits,

(LPBITMAPINFO) &mdib,

DIB_RGB_COLORS) ;

ReleaseDC (hwnd, hdc) ;

This last example requires some explanation. The BITMAPINFO structure mdib.ih specifies how the array of bits Bits is interpreted. It defines the array as a 32 ¥ 32-pixel array with each pixel being represented by 1 bit. One-bit pixels require two colors, so there are only two RGBQUAD entries. Each RGBQUAD entry contains the red, green, and blue intensities for a given pixel value. One advantage of this method is that you can change the bitmap to a green cross on a black background (like the one we created by drawing it) by simply changing the element in mdib.Colors[1] to green.

The process works as follows. Windows retrieves a number of bits from the Bits array based on the described bit planes and bits-per-pixel. It uses this pixel value to index into the RGBQUAD array, retrieving the color of the pixel in term of red, green, and blue intensities. This RGB color value is then converted to the device-specific pixel value that produces the equivalent color on the specified device. This process is a lot of work for a monochrome bitmap, but it is invaluable when dealing with color bitmaps.

We also cheated a little in the preceding example. The origin of a device-specific bitmap is the upper-left corner of the bitmap. The origin of a device-independent bitmap is the lower-left corner. This means you need to define the array of bytes starting with the bottom row of pixels rather than the top row when creating a device-independent bitmap. We used a symmetrical figure so that we didn't have to account for that difference. You also can define the bitmap data for a device-independent bitmap in a run-length-encoded (RLE) form to save space.

You also can use the SetDIBits function to copy a device-independent bitmap into a bitmap of your choice. It looks like this:

int SetDIBits (HDC hdc, HBITMAP hbitmap, UINT StartScan, UINT NumScans,

CONST VOID * lpBits,

CONST BITMAPINFO * lpBitsInfo,

UINT ColorUsage) ;

The parameter StartScan specifies the scan line number of the first row of pixels in the device-independent bitmap pointed to by lpBits. The NumScans parameter specifies how many scan lines are in the lpBits buffer. All NumScans scan lines are copied from the device-independent bitmap, converted to device-specific pixel values as described before, and stored into the bitmap identified by hbitmap. The lpBitsInfo parameter points to a BITMAPINFO structure that defines the format of the bits in the device-independent bitmap. The ColorUsage parameter specifies whether the colors in the RGBQUAD array entries are literal RGB values (as we used before) or 16-bit indices into the current realized logical palette.

Similarly, you can retrieve the pixels from a device-specific bitmap, convert them to a specified device-independent representation, and store the device-independent bitmap into a specified buffer. This is the purpose of the GetDIBits function:

int GetDIBits (HDC hdc, HBITMAP hbitmap,

UINT StartScan, UINT NumScans,

LPVOID * lpBits,

LPBITMAPINFO lpBitsInfo,

UINT ColorUsage) ;

There is another use of GetDIBits, which is obtaining the parameters of a bitmap given its handle. This is particularly useful for obtaining the dimensions of a bitmap loaded with the LoadBitmap function. This is discussed on page 337.

You can skip a step and write a DIB directly to the surface of a device using the SetDIBitsToDevice function. There is no function to copy a device-specific bitmap directly to a device surface. A device-specific bitmap must be selected into a compatible memory device context, and then the BitBlt function can copy all or part of the memory device context surface to the device surface. The SetDIBitsToDevice function looks like this:

int SetDIBitsToDevice (HDC hdc, int xDest, int yDest,

DWORD Width, DWORD Height,

int xSrc, int ySrc,

UINT StartScan, UINT NumScans,

CONST VOID * lpBits,

CONST BITMAPINFO lpBitsInfo,

UINT ColorUsage) ;

The hdc parameter identifies the destination device. The xSrc, ySrc, Width, and Height parameters describe a rectangular area in the source DIB. The xSrc and ySrc parameters are in device coordinates; Width and Height are in device units. Windows translates the device-independent pixel values to the appropriate device-dependent pixel values and copies the source rectangle to the destination-device surface. The rectangle is placed on device surface at the logical coordinates (xDest, yDest).

DIBs can be very large. You can reduce the amount of memory they require by keeping only a portion of the bitmap in memory at any given time. The StartScan and NumScans parameters identify the portion of the DIB that presently is in memory. The StartScan parameter identifies the number of the scan line located at the beginning of the bitmap in memory, that is, the scan line number of the first row of pixels pointed to by the lpBits parameter. The NumScans parameter specifies how many scan lines are in memory. To write a large bitmap to a device surface a small section at a time, you can repeatedly load a portion of a DIB into memory and call the SetDIBitsToDevice function with the StartScan and NumScans parameters set appropriately.

In Win16, it was sometimes necessary to use these techniques because it was not always possible to fit in memory at once all of a DIB or several DIBs requiring simultaneous access. In Win32, this is a far less important issue because the underlying paging system can handle the swapping. However, for very large images you might find yourself straining even Win32's available paging space. Or you may have a program so large that it is "thrashing" its own memory, that is, causing page faults and a page swap operation so often that the paging time dominates the execution time. Also, the time required to initially read in a very large DIB might be significant, particularly if only a small portion can be viewed at any one time. For example, you might be able to read in a "window's worth" of an image in a few seconds, but reading in the entire image might take minutes. When such performance bottlenecks arise, you might find it advantageous to consider using partial-DIB management. You also can take advantage of multithreading to bring in pieces of a bitmap "in the background". In this way, the user can begin to work on or see the pieces already present. We discuss multithreading in Chapter 18.

The lpBitsInfo parameter points to a BITMAPINFO structure that defines the format of the DIB. The Usage parameter is either DIB_RGB_COLORS or DIB_PAL_COLORS. The first indicates that colors are specified by RGB color values. The second indicates that colors are specified by indices into the currently realized color palette.

Finally, there is the CreateDIBSection function. This creates a DIB that an application can write to directly. We document this function here because the documentation in the Win32 API reference (at least in the editions we have) is incomplete and, in fact, incorrect.

HBITMAP CreateDIBSection( HDC hdc, CONST BITMAPINFO * pbitmap,

UINT usage, VOID ** ppbits,

HANDLE hSection, DWORD offset);

The hdc is a handle to a device context. If the usage parameter is DIB_PAL_COLORS, the logical palette of this device context is used to initialize the DIB's colors. The pbitmap reference is a pointer to a BITMAPINFO structure that specifies the DIB, including its dimensions and colors. The ppbits parameter (which does not appear in some versions of the Win32 documentation) is a pointer to a variable that receives a pointer to the bitmap's bits. The hSection value is optional and can be NULL. If it is not NULL, it must be a handle to a "mapping object" obtained from the CreateFileMapping function. The bitmap's bits will be located at offset bytes into the mapping object. You will be able to retrieve this handle by calling GetObject on the bitmap handle:

DIBSECTION as;

HANDLE section;

GetObject(hbitmap, sizeof DIBSECTION, &ds);

section = ds.dshSection;

If the hSection parameter is NULL, the system will allocate memory for the DIB and the offset parameter will be ignored. You will not be able to retrieve this handle using the above code; the ds.dshSection member will be NULL. The DIBSECTION structure, which is not otherwise documented in the Win32 reference, is

typedef struct tagDIBSECTION {

BITMAP dsBM;

BITMAPINFOHEADER dsBmih;

DWORD dsBitFields[3];

HANDLE dshSection;

DWORD dsOffset;

} DIBSECTION;

If you specify the hSection value when you create the DIB section, the offset parameter must be a multiple of sizeof(DWORD), since the bits will be aligned on double-word boundaries. This offset value will appear as the dsOffset field in the DIBSECTION. If the hSection value is NULL on creation, the dshSection value of the DIBSECTION will be NULL and the dsOffset value will have no meaning. If this function succeeds, it returns a handle to the newly-created bitmap; if it fails, it returns NULL.

If hSection is NULL, the system will free up the allocated storage when you call DeleteObject on the bitmap. If hSection is not NULL, you must close the hSection memory handle after calling DeleteObject.

For Windows NT, you have to guarantee that the GDI subsystem has completed drawing to the bitmap. You must call GdiFlush before accessing the bits of the bitmap via the pointer that was returned via the ppbits parameter.

One of the major useful purposes of the DIBSECTION is that it allows you to perform bitmap operations very quickly, primarily to expedite animation effects.

The BitBlt, StretchBlt, and StretchDIBits Functions

The BitBlt function (pronounced "bit blit") copies a rectangular block of bits from a source device context to a destination device context. The name is an abbreviation of "BIT BLock Transfer".5 The source and destination blocks must be the same width and height. The StretchBlt function also copies a rectangular block of bits from a source device context to a destination device context. However, the StretchBlt function can stretch or compress the source rectangle to fit into a larger or smaller destination rectangle.

Like the PatBlt function you saw earlier, these functions can do much more than simply copy bits from here to there. Technically, the BitBlt function provides a superset of the functionality of the PatBlt function. The StretchBlt function provides a superset of the functionality of the BitBlt function. The StretchDIBits function provides the same functionality as the StretchBlt function, but it does it on DIBs. In the next section, we discuss even more sophisticated Blt-class instructions: PlgBlt and MaskBlt.

The basic form of the BitBlt function call is

BOOL BitBlt (HDC hDestDC, int X, int Y, int Width, int Height,

HDC hSrcDC, int xSrc, int ySrc, DWORD Rop) ;

As you can see, this looks much like the PatBlt function discussed earlier in the chapter. The hDestDC parameter specifies the destination device context for the transfer. The X, Y, Width, and Height parameters are logical coordinates of one corner of a rectangle and the extent of the rectangle, horizontally and vertically. As with the PatBlt function, this information specifies two opposite corners of a rectangle. GDI uses this information and the mapping mode to determine the upper-left corner of the rectangle. It includes the top and left sides in the modified area. The bottom and right sides are not included in the default (GM_COMPATIBLE) mode but are included in the extended (GM_ADVANCED) mode.

The PatBlt function used two operands in the transfer: a brush (or pattern) and a rectangular area of pixels on the surface of a destination device context. The BitBlt function uses the same two parameters and adds a third: a rectangular area of pixels on the surface of a source device context. The hSrcDC parameter specifies the source device context. One of the source rectangle is at the (source device context) logical coordinate (xSrc, ySrc). The Width and Height parameters specify the logical width and height of the source rectangle, which has the same size as the destination rectangle.

The final parameter, Rop, specifies the raster operation code for the transfer. A raster operation code for the BitBlt and StretchBlt functions indicates the Boolean operation to use when combining a source bitmap, a pattern bitmap, and a destination bitmap. There are 256 raster operation codes.

A raster operation code (ROP) is a 32-bit value. The high-order 16-bits of the code identifies the Boolean operation and is a 0-extended, 8-bit value that ranges from 0 to 255. The low-order word is a bit-encoded operation code used by the device driver in executing the requested function. Only 15 of the 256 possible ternary raster operation codes have symbolic names defined in windows.h. We gave their symbolic names in Table 6.12, along with the Boolean operation they represent and the hexadecimal ROP code value. You can specify the other ROP values numerically. Or you can use the extrops.h header file-supplied with the GDI Explorer-which assigns names to all 256 ROP codes. You can also use the BLT Explorer component of the GDI Explorer to experiment with the entire set of ROP codes (although some are rather boring; for -example, ROP code 0x00AA0029, which copies the destination pixel to the destination, ignoring both the source and the brush pattern).
Table 6.12: The 15 ternary ROP codes with their symbolic names
Pixel Values
Pattern (P) 1 1 1 1 0 0 0 0
Source (S) 1 1 0 0 1 1 0 0 Boolean Operation ROP Code
Dest(D) 1 0 1 0 1 0 1 0
Name
BLACKNESS 0 0 0 0 0 0 0 0 0 0x000042
NOTSRCERASE 0 0 0 1 0 0 0 1 ~(S | D) 0x1100A6
NOTSRCCOPY 0 0 1 1 0 0 1 1 ~S 0x330008
SRCERASE 0 1 0 0 0 1 0 0 S & ~D 0x440328
DSTINVERT 0 1 0 1 0 1 0 1 ~D 0x550009
PATINVERT 0 1 0 1 1 0 1 0 P ^ D 0x5A0049
SRCINVERT 0 1 1 0 0 1 1 0 S ^ D 0x660046
SRCAND 1 0 0 0 1 0 0 0 S & D 0x8800C6
MERGEPAINT 1 0 1 1 1 0 1 1 ~S | D 0xBB0226
MERGECOPY 1 1 0 0 0 0 0 0 P & S 0xC000CA
SRCCOPY 1 1 0 0 1 1 0 0 S 0xCC0020
SRCPAINT 1 1 1 0 1 1 1 0 S | D 0xEE0086
PATCOPY 1 1 1 1 0 0 0 0 P 0xF00021
PATPAINT 1 1 1 1 1 0 1 1 P | ~S | D 0xFB0A09
WHITENESS 1 1 1 1 1 1 1 1 1 0xFF0062

Table 6.12 demonstrates how you can determine the proper ROP code for any possible Boolean operation. Notice how the organization of the pattern, source, and destination pixel values are arranged. The (0, 0, 0) combination is on the right, forming a column. The result of the chosen Boolean operation on the (0, 0, 0) bits is placed in the least-significant bit of the result. The (1, 1, 1) combination forms a column on the left, with the result placed in the most-significant bit of the result.

After you've written the result for each of the eight possible combinations of a pattern, source, and destination, you have the binary value for the high-order word of the ROP code. For example, the binary result of the WHITENESS operation is all 1s or, in hexadecimal, 0xFF. The ROP code for WHITENESS is 0xFF0062. The result of the SRCAND operation is the binary value "1 0 0 0 1 0 0 0" or, in hexadecimal, 0x88. The ROP code for SRCAND is 0x8800C6.

Now you can find the ROP code for any possible Boolean operation. Compute the result of your Boolean operation as described. The 8-bit resulting value is the Boolean operation index into the table of ROP codes listed in Appendix A. The high word of the 32-bit ROP code always equals the 0-extended, 8-bit value computed in the above manner.

One frequently used ROP code is SRCCOPY. The SRCCOPY ROP performs a straight copy of the source rectangle to the destination. A brush pattern isn't used, and the original contents of the destination do not matter.

Sixteen of the ROP codes ignore the source operand. These ROP codes were listed in Table 6.11. If you select one of those codes, then the hSrcDC, xSrc, and ySrc parameters to the BitBlt function are ignored. The BitBlt function is equivalent to the PatBlt function when it uses these ROP codes. In fact, in this case it would be better to call the PatBlt function instead. Calling PatBlt makes it clear that no source operand is involved. This is a software maintenance issue, not a functionality issue. Both functions produce the same result in this situation.

The StretchBlt Function

The StretchBlt function uses the same ROP codes as the BitBlt function but adds a twist. Rather than your assuming that the source and destination rectangles are the same size (as with the BitBlt function), you specify two additional parameters, SrcWidth and SrcHeight, that describe the width and height of the source bitmap:

BOOL StretchBlt (HDC hDestDC, int xDst, int yDst,

int DstWidth, int DstHeight,

HDC hSrcDC, int xSrc, int ySrc,

int SrcWidth, int SrcHeight,

DWORD Rop) ;

The StretchBlt function stretches or compresses the source bitmap in memory to match the size of the destination rectangle. It then combines the (possibly) stretched or compressed bitmap with the brush and the original pixel values from the destination device context according to the specified ROP code. The destination coordinates and extents are logical coordinates that are translated to device coordinates according to the mapping mode specified in the destination device context. Likewise, the source coordinates and extents are logical coordinates that are translated to device coordinates according to the mapping mode in the source device context.

The StretchBlt function duplicates rows and columns as necessary when stretching a bitmap. This preserves all the information present in the original bitmap, although it might introduce some distortion. It's more complicated to compress a bitmap without destroying the image. The stretching mode attribute of the destination device context determines the manner in which a bitmap is compressed. We discussed this function in detail in Chapter 5, "Examining a Device Context in Depth". However, there are basically four choices when using this function. The original names for these choices were rather strange and have been replaced in Win32 by more meaningful names. We mention them here because they are still available and may be used either in porting old Win16 code or by Win16 programmers who are familiar with them. Here are the mode names:

The StretchDIBits Function

The StretchDIBits function performs the same operations as the StretchBlt function, but the source bitmap is a DIB. The function has the following syntax:

BOOL StretchDIBits (HDC hdc, int xDst, int yDst,

int DstWidth, int DstHeight,

int xSrc, int ySrc,

int SrcWidth, int SrcHeight,

CONST VOID * lpBits,

CONST LPBITMAPINF BitsInfo,

UINT ColorUsage,

DWORD Rop) ;

This function transfers the DIB specified by the lpBits, lpBitsInfo, and ColorUsage parameters to the logical coordinates (xDst, yDst) in the destination device context. As it does, it stretches or compresses the bitmap if necessary and combines it with the brush selected in the destination device context and the original destination bitmap according to the ROP code Rop. The xSrc, ySrc, SrcWidth, and SrcHeight parameters define the size and position of the source rectangle relative to the origin of the DIB, which is the lower-left corner. The xDst, yDst, DstWidth, and DstHeight parameters define the size and position of the destination rectangle relative to the origin of the device context.

There is no specific function for a DIB that is equivalent to the PatBlt and BitBlt functions for device-specific bitmaps. You don't need one, as you can use the StretchDIBits function to perform those operations.

The MaskBlt and PlgBlt Functions

There are two powerful operations that allow you to combine the contents of bitmaps: MaskBlt and PlgBlt. MaskBlt allows you to take the contents of one bitmap and selectively combine it with the contents of another bitmap on a per-pixel basis using any two raster operations. PlgBlt ("PoLyGon BLock Transfer") allows you to copy the contents of one bitmap to another based on a mask (which is a little deceptive naming). It also lets you apply a shearing or rotation operation at the same time by specifying the destination as a parallelogram.
MaskBlt and PlgBlt are not available in Windows 95.

The PlgBlt function

The PlgBlt function is the somewhat simpler of the two, so we cover it first. The PlgBlt function takes 10 parameters:

BOOL PlgBlt(HDC dstDC, LPPOINT pt,

HDC srcDC, int xSrc, int ySrc,

int Width, int Height,

HBITMAP bm, int xMask, int yMask);

The dstDC parameter is the DC to which the bits are copied. The point parameter is a pointer to an array of three points, expressed in logical units, which define three of the points of the destination parallelogram. The first point, pt[0], is the upper-left corner of the parallelogram. The second point, pt[1], is the upper-right corner of the parallelogram. The third point, pt[2], is the lower-left corner of the parallelogram. The fourth point on the parallelogram, which we call pt3, is computed based on the width of the parallelogram as pt3.x = pt[2].x + pt[1].x - pt[0].x, and pt3.y = pt[2].y. The source rectangle is copied from the DC specified by the srcDC parameter, starting at the logical coordinates xSrc and ySrc. The point from (xSrc, ySrc) is copied to (pt[0].x, pt[0].y). The point from (xSrc + Width - 1, ySrc) is copied to (pt[1].x, pt[1].y). The point from (xSrc, ySrc + Height - 1) is copied to the point specified by (pt[2].x, pt[2].y). This is illustrated in Figure 6.19. The bm parameter is optional; a non-NULL value specifies a monochrome bitmap that is used to mask the colors of the source rectangle. In this case, the xMask and yMask parameters specify the starting coordinate of the top-left corner of the mask within the bitmap. If the mask rectangle is smaller than the source or destination rectangles, the function replicates the mask pattern. A 1 bit in the mask causes the color to be copied from the source to the destination. A 0 bit in the mask leaves the corresponding destination pixel unchanged; this is shown moreclearly in Figure 6.20. The source DC may have scaling, translation, or reflection transformations (we cover transformations starting on page 371), but not rotation or shearing. If the source and destination rectangles are not the same size, the stretching mode established by SetStretchBltMode in the destination DC will control how pixels are stretched or compressed. The destination coordinates are transformed according to the destination DC; the source coordinates are transformed according to the source DC.

Image Mask Destination Result

Figure 6.20: PlgBlt with a masking bitmap and no transformation

The result of the PlgBlt function is TRUE if the function succeeds and FALSE if the function fails. The function will fail if either the source or the destination DC has a rotation or shearing transformation or if the source DC is an enhanced metafile DC. It also will fail if the destination device does not support the PlgBlt function

function. You should confirm that PlgBlt is supported by checking the RC_BITBLT capability using the GetDeviceCaps function. Finally, the PlgBlt function will fail if the DCs represent incompatible devices. PlgBlt is not implemented on Windows 95.

For example, using the GDI Explorer application you can derive the information shown in Table 6.13. This set of multipliers was used to produce the images shown (multiply the width of the bitmap by the indicated factors to obtain the points shown). There was no masking bitmap in-volv-ed. Note that by using PlgBlt, we can get shearing and rotation effects on bitmaps.
Table 6.13: Multipliers for PlgBlt simple rotations
Rotation Image Polygon Points
pt[0] pt[1] pt[2]
x y x y x y
Original 0 0 1 0 0 1
90º clockwise 1 0 1 1 0 0
180º clockwise 1 1 0 1 1 0
270º clockwise 0 1 0 0 1 1
Horizontal shear 30º counterclockwise 0 0 1 0 sin 1
Vertical shear 30º clockwise 0 0 1 sin 0 1
Rotation 30º clockwise sin 0 1 + sin sin 0 cos
Mirrored Transformations
0º clockwise 1 0 0 0 1 1
90º clockwise 1 1 1 0 0 1
180º clockwise 0 1 1 1 0 0
270º clockwise 0 0 0 1 1 0
270º clockwise Vertical shear 30º clockwise 0 0 0 1 1 sin

The MaskBlt Function

The MaskBlt operation is complex to discuss, but simple once you see what it is doing. The basic idea of the MaskBlt is to copy pixels from one bitmap to another using a third monochrome bitmap as the mask. However, unlike PlgBlt, where a 1 bit in the mask means "copy to destination" and a 0 means "leave the destination unchanged", in MaskBlt the 1 bit means "copy with the foreground raster op" and a 0 means "copy with the background raster op". If we set the foreground raster op to copy the source and set the background raster op to copy the destination, we have exactly PlgBlt with the restriction that the copied bitmaps must be rectangular and the same size.

MaskBlt takes 12 parameters. This places it in the top 10 list of API functions with the most parameters. However, they are actually quite simple:

Miscellaneous Bitmap and Drawing Functions

A few functions don't fit into the previous discussions very well. We discuss them here.

Bitmap Functions

The GetBitmapDimensionEx returns the width and height of a bitmap by updating the specified SIZE structure. You use the GetBitmapDimensionEx function like this:

int Width ;

int Height ;

SIZE size ;

GetBitmapDimensionEx (hbitmap, &size) ;

Width = size.cx ;

Height = size.cy ;

This function is rather strange because it doesn't really return the bitmap's width and height. It actually returns the information that was set by the SetBitmapDimensionEx function. If no width and height were set by a prior call to the SetBitmapDimensionEx function, the GetBitmapDimensionEx function returns 0 for the width and height. If you need the real dimensions, use the GetDIBits function.

The following SetBitmapDimensionEx function call sets the bitmap dimension and returns the previous dimensions by updating the specified SIZE structure:

int Width ;

int Height ;

SIZE size ;

SetBitmapDimensionEx (hbitmap, Width, Height, &size) ;

You can use these functions to associate two integers with a bitmap. Windows does not use this information.

When you actually want the dimensions of the bitmap, use the GetDIBits function or call the GetObject function, passing it the handle to a bitmap. It will fill a buffer with a BITMAP structure containing the actual dimensions of the bitmap:

BITMAP bm ;

GetObject (hbitmap, sizeof bm, &bm) ;

Width = bm.bmWidth ;

Height = bm.bmHeight ;

The FloodFill and ExtFloodFill functions don't fit in anywhere else either. The FloodFill function fills an area of a device context surface with the current brush. You specify the logical coordinates of a point on the device surface and a color that bounds the area to be filled. The FloodFill function starts at the specified point and uses the current brush to paint in all directions on the device surface until the specified color boundary is reached. Here is the function syntax for the FloodFill function:

BOOL FloodFill (HDC hdc, int X, int Y, COLORREF Color) ;

Microsoft recommends that new code use the ExtFloodFill function. The ExtFloodFill function does everything the FloodFill function does and a bit more. The syntax is somewhat different:

ExtFloodFill (HDC hdc, int X, int Y, COLORREF Color, UINT FillType) ;

When the FillType parameter is FLOODFILLBORDER, the ExtFloodFill function starts at the logical point (X, Y) and paints with the current brush until it reaches the boundary color Color. This action is identical to the FloodFill function's.

When the FillType parameter is FLOODFILLSURFACE, only the adjacent areas that are the color Color are filled with the current brush. The ExtFloodFill function starts at the point (X, Y) and paints outwardly in all directions until a color other than Color is encountered.

Note that the flood fill operations depend on the actual boundary colors. Thus it is very difficult to use these in a general way to draw, for example, partially overlapping circles in two different interior colors but with the same border color, where you want the second circle drawn to be visually hiding the first one. To fill an arbitrary shape, you will more likely want to use the FillPath or StrokeAndFillPath operations. Note also that flood filling with a dithered color is likely to produce unexpected results because the halftoning effects will interfere with the "edge detection" of the logical boundary. So these functions are typically far less useful than you might expect.

ScrollDC

The ScrollDC function is invaluable when you want to scroll all or part of a window containing child windows and controls. Typically, in such a case you don't want the child windows and controls to move; you want just the surface of the window underneath them to move.

The ScrollDC function takes this into account. Because it is a GDI function, it performs clip-ping on the scroll region. The function call looks like the following:

BOOL ScrollDC (HDC hdc, int dx, int dy,

CONST RECT * ScrollRect,

CONST RECT * ClipRect,

HRGN UpdateRgn, LPRECT Update) ;

This function scrolls the device surface specified by the hdc parameter dx scroll units hor-i-zon-tal-ly and dy scroll units vertically. The ScrollRect parameter is a pointer to a RECT structure that specifies the area to scroll. You can restrict the scrolling to a smaller area within the pre-vious-ly specified rectangle by passing a smaller rectangle as the ClipRect parameter. It con-tains the coordinates of a clipping rectangle. All scrolling is done within the clipping rectangle.

The ScrollDC function returns information via the last two parameters. When you pass a han-dle to a region as the UpdateRgn parameter, GDI sets the region to the area uncovered by the scrolling process. When you pass a pointer to a RECT structure as the Update par-am-eter, GDI sets the rectangle to the client coordinates (independent of any mapping modes actually in effect in the DC) of the update rectangle. This is the smallest rect-angle that bounds the area needing repainting. It can be later passed to InvalidateRect to force -repainting.

The DrawEdge Function

The "3-D look" is pervasive in user interfaces today. Drawing those nice "3-D" edges, however, is a bit tricky, if you're doing it entirely with LineTo, that is. Of course, you could also use a bitmap, but doing this introduces the problem of mapping pixels in the bit map to the colors selected by the user in the Control Panel. This practice is recommended if you are following the GUI design guidelines. (You can accomplish this in some cases by using the LR_LOADMAP3DCOLORS option flag for the LoadImage function. But for many cases, having to create a bitmap and use LoadImage is technological overkill for what should be a simple task.) There is a composite operation, DrawEdge, that takes a rectangle and draws some portion of it using 3-D line effects. The results of three such drawings appear in Figure 6.22.

EDGE_SUNKEN
EDGE_RAISED
EDGE_ETCHED
Figure 6.22: DrawEdge effects

The DrawEdge function takes a DC, a rectangle specification, and flags to describe how the effects are to be produced:

BOOL DrawEdge(HDC hdc, LPRECT rect, UINT edge, UINT flags);

The rect parameter describes the rectangle in which the drawing takes place. The edge parameter can be any of the values given in Table 6.14. These are the values that were used to draw the illustrations of Figure 6.22.
Table 6.14: DrawEdge edge specifications
Code Meaning
Inner border flag: one of:
BDR_RAISEDINNER Raised inner edge.
BDR_SUNKENINNER Sunken inner edge.
Outer border flag, one of:
BDR_RAISEDOUTER Raised outer edge.
BDR_SUNKENOUTER Sunken outer edge.
Or, use one of the following values:
EDGE_BUMP BDR_RAISEDOUTER | BDR_SUNKENINNER
EDGE_ETCHED BDR_SUNKENOUTER | BDR_RAISEDINNER
EDGE_RAISED BDR_RAISED_OUTER | BDR_RAISEDINNER
EDGE_SUNKEN BDR_SUNKENOUTER | BDR_SUNKENINNER

The flags parameter describes which parts of the rectangle are to be drawn and chooses specific other effects. The flags values are given in Table 6.15.
Table 6.15: DrawEdge flags
Flag Value Meaning
Base edge flags: one or more of the following:
BF_TOP Draws the top of the rectangle.
BF_BOTTOM Draws the bottom of the rectangle.
BF_LEFT Draws the left of the rectangle.
BF_RIGHT Draws the right of the rectangle.
BF_DIAGONAL Draws a diagonal line (always at 45º no matter what the aspect ratio of the rectangle). Interpretation depends on other flag values.
Alternatively, you can use one of the following names:
BF_BOTTOMLEFT BF_BOTTOM | BF_LEFT
BF_BOTTOMRIGHT BF_BOTTOM | BF_RIGHT
BF_DIAGONAL_ENDBOTTOMLEFT BF_DIAGONAL | BF_BOTTOM | BF_LEFT
BF_DIAGONAL_ENDBOTTOMRIGHT BF_DIAGONAL | BF_BOTTOM | BF_RIGHT
BF_DIAGONAL_ENDTOPLEFT BF_DIAGONAL | BF_TOP | BF_LEFT
BF_DIAGONAL_ENDTOPRIGHT BF_DIAGONAL | BF_TOP | BF_RIGHT
BF_TOPLEFT BF_TOP | BF_LEFT
BF_TOPRIGHT BF_TOP | BF_RIGHT
BF_RECTBF_RECT BF_TOP | BF_LEFT | BF_BOTTOM | BF_RIGHT
One or more of the following flags for additional effects:
BF_ADJUST Adjust the rectangle to leave space for client area.
BF_FLAT Flat border.
BF_MIDDLE Fills in the area within the borders.
BF_MONO One-dimensional border.
BF_SOFT Soft buttons instead of tiles.

The illustrations in Figure 6.22 were done using the DrawEdge Explorer component of the GDI Explorer. This program allows you to directly see all the visual effects of all of the various flags and options.

One strange feature of the DrawEdge function is its use of the "diagonal" flag, which draws a diagonal line. This diagonal line is always a 45º line, no matter the size of the rectangle. It always is based on the left edge of the rectangle, that is, lines will run from top left to bottom right or from bottom left to top right. The nature of the diagonal line is also modified by the presence of BF_LEFT or BF_RIGHT flags in combination with BF_TOP and BF_BOTTOM. While you may think at first that a line that ends in the top left is the same as a line that ends in the bottom right, this is not true when 3-D effects depend on the virtual "light source". For example, when the style EDGE_RAISED is chosen, a BF_DIAGONAL_ENDTOPRIGHT draws a white line from the lower-left corner to the top-right corner, but a BF_DIAGONAL_ENDBOTTOMLEFT draws a black line from the top-right corner to the bottom-left corner.

The BF_SOFT, BF_FLAT and BF_MONO flags affect specific "3-D" aspects of how the lines are actually drawn. Figure 6.23 illustrates these effects. BF_FLAT uses no 3-D effects at all.

no modifiers BF_SOFT BF_FLAT BF_MONO
Basic style is EDGE_ETCHED with the BF_RECT flags set.
Figure 6.23: DrawEdge modifier effects

The BF_MIDDLE flag causes the area within the borders to be painted. The BF_ADJUST flag allows additional space for the client area of the rectangle. Since you will often want to use these borders on owner-draw objects such as buttons, you may want to reserve the client area. Using the BF_ADJUST presumably is to allow the size to be adjusted so that it lies outside the client area of the window whose client area is represented by the rectangle. We could not see a noticeable visual effect using the DrawEdge Explorer.

The GDI Batch Queue (in detail)

The final set of miscellaneous functions deals with the asynchronous nature of the GDI under Windows NT 3.x. Each thread has a buffer (called its "batch") in which GDI commands are accumulated. This means that if you are running multithreaded, not all GDI commands from one thread may have completed when control is transferred to another thread. Normally, this doesn't matter much. But if one thread is writing to a bitmap and another thread is reading from the bitmap, there is no guarantee that all of the operations have completed. The batch is flushed when any of the following occurs:

Region Functions

A region is a description of an area of a device surface. It is described by a combination of one or more ellipses and polygons. You use regions mainly for two purposes: to paint the area described by a region and to clip all output to the area described by a region. You can also use a region to test for the presence of the mouse in an irregularly-shaped area.

A region is not a simple data structure like a RECT structure. It is a GDI object like a bitmap and hence must be created. When you create a region, you receive a handle to the region of type HRGN. Once you have a handle to a region, you can use the handle to manipulate the region in a number of interesting ways. You can use it directly to draw the area described by the region. You can combine one region with another region and update a third region to refer to the newly described area. You also can select a region into a device context so that all output to the device context is clipped to the area described by the selected region. You can invalidate the screen area defined by the region, thereby forcing it to redraw, or revalidate a previously invalidated -region. You can retrieve the line, curve, or other segments of the region. You can fill or outline a region (in this sense, a region can act like a path for the restricted case of a closed polygon). You can ask if a specific coordinate is located within the region. inally, when you're done with a region, you should delete it just as you should all GDI objects. Just don't delete one while it is selected in a device context.

The region operations are given in Table 6.16
Table 6.16: Region operations
Operation Explanation
CombineRgn Combines two regions using one of the specified operations from Table 6.17.
CreateEllipticRgn Creates a region with an elliptical shape, given the four parameters defining the bounding rectangle.
CreateEllipticRgnIndirect Creates a region with an elliptical shape, given a reference to a RECT structure that defines the bounding rectangle.
CreatePolygonRgn Creates a region with a polygonal shape, given an array of points that define the polygon. Specifies a mode that determines which pixels are in the region.
CreatePolyPolygonRgn Creates one or more regions with polygonal shapes, given an array of points that define the polygons. Specifies a mode that determines which pixels are in the region.
CreateRectRgn Creates a rectangular region, given the four coordinates of the bounding rectangle.
CreateRectRgnIndirect Creates a rectangular region, given a reference to a RECT structure that defines the bounding rectangle.
CreateRoundRectRgn Creates a rectangular region with rounded corners, given the four coordinates defining the bounding rectangle and the dimensions of the rounding ellipse.
DeleteRgn Deletes a region.
EqualRgn Tests two regions for equality.
ExtCreateRgn Creates a region based on a transformation of an existing region. Windows 95 places some restrictions on the nature of the transformation matrix.
ExtSelectClipRgn Combines a region with the current clipping region.
FillRgn Fills a region with a given brush.
FrameRgn Frames a region by drawing a border using a specified brush.
GetClipRgn Returns a region handle to the current clipping region.
GetPolyFillMode Gets the polygon fill mode used by FillRgn.
GetRgnBox Gets the bounding box of a region.
GetRegionData Obtains the internal structure information about a region.
GetWindowRgn Obtains the window region established by SetWindowRgn.
InvalidateRgn Invalidates the region, eventually forcing a WM_PAINT message to be generated.
InvertRgn Inverts the colors in the region.
OffsetRgn Offsets the region.
PaintRgn Paints the region using the current brush from the DC.
PathToRegion Converts the current path in the DC to a region.
PtInRegion Tests to see if a given coordinate is contained in the region.
RectInRegion Tests to see if a given rectangle overlaps any part of the region.
SelectClipRgn Selects a given region as the current clipping region.
SetPolyFillMode Sets the polygon fill mode for FillRgn.
SetWindowRgn Sets the window region. This will be honored by the GDI; nothing outside the window region will be written. This allows you to construct nonrectangular windows.
ValidateRgn Validates the client area. This removes the area specified by the region from the current update region.

Several functions can be used to create a region:

Advanced Windows Graphics: Paths and Transformations

Win32 has graphical capabilities not available in 16-bit Windows: paths and transformations. Using paths you can build a complex clipping region from Bézier curves, ellipses, and rounded rectangles. But you can also create a clipping path from the path defining the outline of a text string; for example, you could clip a graphical layout by using the outline of the letters of your name. Transformations allow a Windows NT program to rotate, scale, reflect and shear-all in a single operation. They further allow the composition of these transformations to realize a single transformation that embodies all of its composite components. While shearing effects can be obtained using the PlgBlt operation, these effects apply only to bitmaps. Transformations apply to all graphics operators, which are handled as efficiently as possible. (Manipulating bitmaps can be very slow, and the effects when scaling comes into play can be quite ugly.)

Paths interact with the coordinate system by using coordinate transformations. Since coordinate transformations are a self-contained discussion (they don't require paths) but paths require an understanding of transformations, we first cover transformations. Note that in Windows 95, there are no transformations, except for a restricted form of the ExtCreateRegion function, so anything dealing with paths that also requires transformations means you can deal only with the "identity transformation".

Transformation Matrices

We have shown you how you can use SetViewportExtEx, SetViewportOrgEx, SetWindowExtEx, and SetWindowOrgEx to get scaling. Actually, this is the hard way to get scaling, but it was necessitated by the early days of the 8088. In those days, there was only the 8-bit data path to memory, the 16-bit processor architecture, no math coprocessor, and an unbelievable 4.77MHz clock.8 Using floating-point operations to manipulate the display would have been out of the question, even with a math coprocessor. Today, modern processors come with on-chip floating-point units and high-end processors have superscalar architecture (multiple instructions executed concurrently), parallel arithmetic units, and floating-point units so fast that a floating-point multiply can be done in a single machine clock cycle! So floating-point computation cost is almost an irrelevant consideration today. In Win32, we use these transformations to move from page space to device space; we talk about these spaces on page 372. But in Windows NT, we can use transformation matrices to manipulate our basic space.

To help you understand what a transformation matrix does, we look at some possible transformations. There is the operation of scaling, which changes the mapping mode to get a different cor-re-spon-dence between logical units and physical units. If you scale by the same amount in x and y, you have an isotropic mapping (almost like MM_ISOTROPIC), and if you scale differently, you have an anisotropic mapping (almost like MM_ANISOTROPIC). We say "almost" because you may have to scale using MM_ANISOTROPIC if you have nonsquare pixels just to get isotropic scaling in x and y, but that is a fine point. There is the operation of translation, which shifts the coordinates in the x and y positions. There is the operation of rotation, which rotates the coordinate system around its origin. Finally, there is the operation of shearing, which applies a linear transformation to each logical unit whose magnitude varies based on its distance from a reference point. These are all shown in Figure 6.25.

In the world of graphics, all of these transformations are captured in a simple model: the co-ord-inate transformation matrix.9 By doing matrix multiplication, you can easily combine two trans-formations, and when you have a composite matrix, you can quickly and easily apply it to any pair of points to get a new pair of points ¢. This composition gives you the ability to compute in one step the result of applying several individual transformations, without having to apply each sequentially

Windows deals with four coordinate spaces:

1. World coordinate space. In Windows NT, world coordinate space is 232 units high and wide. World coordinate space is used to rotate, shear, or reflect graphical output. Note that the limitation to 216 units in Window 95 is coupled to this; that is, in Windows 95, you cannot rotate or shear (or reflect) graphical output.

2. Page co-ord-inate space. This was called logical coordinate space in earlier versions of Win-dows. In Windows NT, it is 232 units high and wide and in Windows 95, 216 units high and wide.

3. Device coordinate space. In both Windows NT and Windows 95, this is 216 units high and wide.

4. Physical device coordinate space. This is limited to the range supported by a given window, screen, printer, or plotter.

When Windows transforms graphical output from its creation through its device rendering, it pro-gressively maps the output across each of these coordinate spaces. If you call the SetWorldTransform function, mapping starts in the world coordinate space; otherwise, it starts in the page space. The coordinates are then mapped to the device space under the mapping modes established by SetMapMode and finally to the physical device space. But if we do our scal-ing in world coordinates, we could use a simple mapping such as MM_ISOTROPIC or MM_TEXT to perform the next level of mapping.
The SetWorldTransform and all transform-related operations require that you place the DC in GM_ADVANCED mode using the SetGraphicsMode function. These are not available under Windows 95.

World-to-page space transformations are under the control of a transformation matrix. A trans-for-ma-tion matrix is a 3 ¥ 3 array of numbers. We won't belabor the details of matrix mul-ti-pli-cation quite yet (see page 375), but we will observe here that in 2D space, the third column of the transformation matrix is a set of constants (0, 0, 1), and these ultimately have no effect on the actual results computed. So we discard this last column, and we need only a 2 ¥ 3 array to specify the transformations:

becomes

and we can show that the under a transformation any point (x, y) is converted to a new point (x¢, y¢) quite simply as

Whenever two transformation matrices are composed by using matrix multiplication (which requires a consistent shape between the two matrices dimensions), the third row is provided implicitly as part of the operation.

A transformation is realized in the structure XFORM:

typedef struct tagXFORM

{

FLOAT eM11;

FLOAT eM12;

FLOAT eM21;

FLOAT eM22;

FLOAT eDx;

FLOAT eDy;

} XFORM, *LPXFORM;

Some simple transformation matrices are shown in
Table 6.19: Special cases of transformation matrices
Matrix Operation

Translation by an amount .

Scaling by an amount .

Rotation counterclockwise by an amount .

Horizontal shear by an amount k.
Table 6.19.

Remember that when you are taking the sine or cosine of an angle using the standard C math library, the angle is specified in radians for input to functions like sin and cos and for results from functions like asin and acos. There are 2 radians in a circle, so you can convert degrees to radians with the following spacemacros:

#define PI 3.141592f

#define deg_to_rad(n) \

((float)(((float)(n)) / (360.0f/(2.0f * PI)))

#define rad_to_deg(n) \

((float)((n) * (360.0f/(2.0f * PI)))

We can apply any combination of values here to get, in a single matrix, any amount of scaling, translation, rotation, and shearing. Furthermore, two matrices can be combined. A scaling matrix mscale and a translation matrix mtrans can be combined by using matrix multiplication to a single matrix that does the combination of scaling and translation. This is normally done with the CombineTransform function. Since we have used SetWorldTransform to establish a given transformation matrix, or we have inherited the default identity transformation, we can use ModifyWorldTransform to combine a transformation matrix with the current world transformation matrix in the DC, getting the effect of having performed GetWorldTransform, CombineTransform, and SetWorldTransform, all in one operation. So we have

XFORM m1 = { 1.0f, 0.0f,

0.0f, 1.0f,

12.0f, 24.0f}; // translation by 12 units in x and 24 units in y

XFORM m2 = {0.5f, 0.0f,

0.0f, 0.5f,

0.0f, 0.0f}; // scaling by 0.5 in x and y

XFORM m3;

CombineTransform(&m3, &m1, &m2);

SetWorldTransform(hdc, &m3);

which is the same as

SetWorldTransform(hdc, &m1);

ModifyWorldTransform(hdc, &m2, MWT_RIGHTMULTIPLY);

and this is the same as

SetWorldTransform(hdc, &m2) ;

ModifyWorldTransform(hdc, &m1, MWT_LEFTMULTIPLY);

Matrix multiplication is not commutative; therefore a left multiply and a right multiply of two matrices will produce two different answers.

Formally, matrix multiplication between two 3 ¥ 3 matrices is defined as10

Using our canonical representation of a matrix, with the last column having constant values, we get

This can be used to show how any two transformations compose under matrix multiplication.

To compute the transformed location of any point x, y given a transformation matrix, we add a third column of 1 to the point and make it a 1-¥-3 matrix and then apply matrix multiplication:

We can then discard the third element of the result matrix to be left with the equations

which are, not surprisingly, the same as the equations we gave on page 373.

The transformation matrix operations are shown in Table 6.20.
Table 6.20: Transformation matrix operations
Operation Explanation
CombineTransform Combine two transformation matrices M1 and M2 to produce a third one in the result parameter: M1 ¥ M2 result
GetWorldTransform Copy the current transformation matrix from the DC to the matrix referenced by the parameter: CTM result
ModifyWorldTransform Combine the parameter xform with the current transformation matrix according to the following options:
MWT_IDENTITY CTM (xform ignored)
MWT_LEFTMULTIPLY xform ¥ CTM CTM
MWT_RIGHTMULTIPLY CTM ¥ xform CTM
SetGraphicsMode Sets the graphics mode for the DC.
GM_COMPATIBLE Sets the graphics mode to be compatible with Windows 3.1 behavior.
GM_ADVANCED Enables SetWorldTransform and ModifyWorldTransform operations and makes other changes in graphics interpretation.
SetWorldTransform Sets the parameter as the world transformation in the DC: xform CTM

Before you can use the SetWorldTransform or ModifyWorldTransform calls, you must call SetGraphicsMode(hdc, GM_ADVANCED). This enables the advanced graphics features of Win32. The default mode for a DC is GM_COMPATIBLE, which disables the world coordinate transforms as well as having the effects summarized in Table 6.21. The GM_ADVANCED mode is not supported in Windows 95. Once you have set GM_ADVANCED mode, you cannot reset the DC to GM_COMPATIBLE mode unless the transformation matrix in the DC is the identity matrix.
Table 6.21: Comparison of GM_COMPATIBLE and GM_ADVANCED
GM_COMPATIBLE GM_ADVANCED
Font Differences
TrueType or vector font text is always written left to right and right-side up, independent of any graphics transformations that may be in effect to invert the axes. TrueType or vector font text fully conforms to the world-to-device transformation established in the DC.
Only the height of TrueType fonts is scaled. Height and width are both scaled.
Nonhorizontal text can be drawn only by creating a font with a nonzero escapement and orientation. Rotated text is drawn based on the world-to-device transformation and may be rotated, sheared, or reflected.
Raster fonts cannot be scaled. Raster fonts scale by integer multiples but are not particularly attractive.
Rectangle Differences
A rendering of a rectangle excludes the right and bottom edges. A rendering of a rectangle includes the right and bottom edges.
Arc Drawing Differences
An arc is drawn using the current arc direction set by SetArcDirection in the device space. Arcs are always drawn counterclockwise in logical space.
Arcs do not respect page-space-to-device-space transformations that would require a flip on either axis. Arcs fully respect the world-to-device transform that is set in the DC.

The WM_PAINT handler for a simple program that illustrates several transformations is shown in Listing 6.8. Its output is shown in Figure 6.25. When it is run under Windows 95, the rotation and shearing are not -available. Take note of how we detected this. If you want a program that runs reliably under both Windows 95 and Windows NT, you must deal with exactly these issues.

Listing 6.8: Translation, scaling, rotation, and shearing
typedef struct {

int caption;

XFORM x;

} xforms;

xforms xf[] = {

{ IDS_IDENTITY, { 1.0f, 0.0f,

0.0f, 1.0f,

0.0f, 0.0f}},

{ IDS_TRANSLATION, { 1.0f, 0.0f,

0.0f, 1.0f,

10.0f, 10.0f}},

#define COS30 0.86602540f

#define SIN30 0.50000000f

{ IDS_ROTATION30,

{ COS30, SIN30,

-SIN30, COS30,

0.0f, 0.0f}},

{ IDS_SCALING_HALF, { 0.5f, 0.0f,

0.0f, 0.5f,

0.0f, 0.0f}},

{ IDS_SHEARV, {1.0f, sin30,

0.0f, 1.0f,

0.0f, 0.0f}},

{ IDS_SHEARH, { 1.0f, 0.0f,

sin30, 1.0f,

0.0f, 0.0f}},

{ NULL /* END OF TABLE */ }

};

#define XFORM_SIZE 80

#define XFORM_OVERSHOOT (XFORM_SIZE / 10)

static void

drawTransform(HDC hdc, HWND errwnd, xforms * xf)

{

int orgdc;

HPEN axispen = CreatePen(PS_SOLID, 1, RGB(128,128,128));

RECT r;

int height;

TEXTMETRIC tm;

HFONT f;

TCHAR buffer[50]; // captions tend to be short

orgdc = SaveDC(hdc);

SetMapMode(hdc, MM_LOENGLISH);

SelectPen(hdc, axispen);

MoveToEx(hdc, 0, -XFORM_OVERSHOOT, NULL);

LineTo(hdc, 0, XFORM_SIZE + XFORM_OVERSHOOT);

MoveToEx(hdc, -XFORM_OVERSHOOT, 0, NULL);

LineTo(hdc, XFORM_SIZE + XFORM_OVERSHOOT, 0);

SelectPen(hdc, GetStockPen(BLACK_PEN));

SelectBrush(hdc, GetStockBrush(LTGRAY_BRUSH));

Rectangle(hdc, XFORM_SIZE, 0, 0, XFORM_SIZE/2);

f = createCaptionFont(-14, "Arial");

SelectFont(hdc, f);

GetTextMetrics(hdc, &tm);

height = tm.tmHeight + tm.tmInternalLeading;

SetRect(&r, -XFORM_OVERSHOOT, -2*XFORM_OVERSHOOT,

XFORM_SIZE + XFORM_OVERSHOOT,

2*height);

LoadString(GetWindowInstance(errwnd), xf->caption,

buffer, DIM(buffer));

DrawText(hdc, buffer, -1, &r, DT_CENTER | DT_CALCRECT);

DrawText(hdc, buffer, -1, &r, DT_CENTER );

FrameRect(hdc, &r, GetStockBrush(BLACK_BRUSH));

if(!ModifyWorldTransform(hdc, &xf->x, MWT_LEFTMULTIPLY))

{ /* failed */

PostMessage(errwnd, UWM_ERROR, IDS_XFORM_FAILED,

xf->caption);

} /* failed */

else

{ /* success */

SelectBrush(hdc, GetStockBrush(DKGRAY_BRUSH));

Rectangle(hdc, XFORM_SIZE, 0, 0, XFORM_SIZE/2);

} /* success */

RestoreDC(hdc, orgdc);

DeletePen(axispen);

DeleteFont(f);

}

static void

xform_OnPaint(HWND hwnd)

{

#define XFORM_OFFSET (1.5f * XFORM_SIZE)

HDC hdc;

PAINTSTRUCT ps;

int i;

XFORM initialize = {1.2f, 0.0f,

0.0f, 1.2f,

(float)(XFORM_OFFSET/2.0f),

(float) -XFORM_OFFSET};

XFORM translate = { 1.0f, 0.0f,

0.0f, 1.0f,

(float)(XFORM_OFFSET * 1.5f),0.0f};

int restore;

hdc = BeginPaint(hwnd, &ps);

restore = SaveDC(hdc);

if(!SetGraphicsMode(hdc, GM_ADVANCED))

{ /* no advanced mode */

PostMessage(hwnd, UWM_ERROR, IDS_NO_GM_ADVANCED,

IDS_NOT_SUPPORTED);

return;

} /* no advanced mode */

if(!ModifyWorldTransform(hdc, &initialize,

MWT_LEFTMULTIPLY))

{ /* failed transform */

PostMessage(hwnd, UWM_ERROR, IDS_XFORM_FAILED,

IDS_NOT_SUPPORTED);

return;

} /* failed transform */

for(i = 0; xf[i].caption != NULL; i++)

{ /* show each */

drawTransform(hdc, hwnd, &xf[i]);

ModifyWorldTransform(hdc, &translate,

MWT_RIGHTMULTIPLY);

} /* show each */

RestoreDC(hdc, restore);

EndPaint(hwnd, &ps);

}

It is worth pointing out some techniques we used in doing this, as they allow you to structure graphics operations in a more reasonable manner than do strictly ad hoc methods. For example, note that we never SetWorldTransform. Instead, we ModifyWorldTransform, even though we have called the SetGraphicsMode function to set the GM_ADVANCED mode. This is done deliberately. In particular, it allowed us to reuse the key part of the drawing handler as a separate function to draw the illustrations on a graphics metafile, where a different coordinate transformation was in effect. Since we es-tab-lish-ed the coordinate transform for the metafile outside the drawTransform handler, if the han-dler had done a SetWorldTransform it would have undone the metafile coordinate trans-formation. But by doing a ModifyWorldTransform, we will honor any existing trans-for-ma-tion.

Another technique used heavily here is the SaveDC call. Rather than generate a large number of variables, such as "oldPen", "oldBrush", "oldFont", and the like and having to remember to deselect the user-created objects from the DC before deleting them, we simple do a RestoreDC. This restores all the objects previously selected and implicitly deselects all of the new objects. You can SaveDC to any nesting and unwind a large number of them all at once with a single RestoreDC.

An important feature to note is that we seem always to create a font and select it into the DC. This is because the default font in a DC is the "System" font, which is not a TrueType font. The font obtained by GetStockFont(SYSTEM_FONT) is a raster font. Raster fonts can be scaled only by integer multiples and will not honor most of the operations that are done with a trans-formation matrix. Often, when a raster font is rendered to a printer, the effects of attempting to scale it are quite ugly and completely unsuitable. So be aware that all of the fonts we create are TrueType fonts.

Finally, note that we issue an error notification by using a PostMessage call. Why go through such an elaborate indirection when it is clear that a simple MessageBox call should suffice? The reason becomes obvious if you actually issue a MessageBox call, either at the PostMessage site or when the UWM_ERROR message is processed. The message box pops up, quite probably overlaying the image that was drawn. When you dismiss the message box, the in-val-i-dated area must be redrawn. Doing this will quite possibly cause another message box to pop up, whereupon the error is encountered again. By having the parent process the message, it can implement strategies such as writing the message to another window, writing it to a debug stream via the OutputDebugString function, or ignoring it. Generally you want to watch out for this sort of potential infinite loop when writing paint handlers.

A TrueType font can have any of the standard transformations applied to it. For example, you can apply a scaling or shearing transformation and draw text in a DC. This will produce the effect shown in Figure 6.26. The WM_PAINT handler that drew this is shown in Listing 6.9.

Listing 6.9: Drawing text with a transformation
static void

textxform_OnPaint(HWND hwnd)

{

HDC hdc;

PAINTSTRUCT ps;

int restore;

int localrestore;

XFORM shear = { 1.0f, 0.0f,

-1.6f, 1.3f,

0.0f, 0.0f};

#define SHEAR_X 50.0f

#define SHEAR_Y 100.0f

XFORM initialize = { 1.0f, 0.0f,

0.0f, 1.0f,

SHEAR_X, SHEAR_Y};

HFONT font;

TCHAR Message[128];

LoadString(GetWindowInstance(hwnd), IDS_TEXTSHEAR,

Message, DIM(Message));

hdc = BeginPaint(hwnd, &ps);

restore = SaveDC(hdc);

SetGraphicsMode(hdc, GM_ADVANCED);

if(!ModifyWorldTransform(hdc, &initialize,

MWT_LEFTMULTIPLY))

{ /* not implemented */

PostMessage(hwnd, UWM_ERROR, IDS_TRANSFORM_FAILED,

IDS_NOT_SUPPORTED);

EndPaint(hwnd, &ps);

return;

} /* not implemented */

SetBkMode(hdc, TRANSPARENT);

SetTextAlign(hdc, TA_BASELINE);

font = createCaptionFont(-50, "Times New Roman");

SelectFont(hdc, font);

//----------------------

localrestore = SaveDC(hdc);

SetTextColor(hdc, RGB(192,192,192));

if(ModifyWorldTransform(hdc, &shear, MWT_LEFTMULTIPLY))

{ /* success */

TextOut(hdc, 0, 0, Message, lstrlen(Message));

} /* success */

else

{ /* failure */

PostMessage(hwnd, UWM_ERROR, IDS_SHEAR_FAILED,

IDS_NOT_SUPPORTED);

EndPaint(hwnd, &ps);

return;

} /* failure */

RestoreDC(hdc, localrestore);

//----------------------

localrestore = SaveDC(hdc);

TextOut(hdc, 0, 0, Message, lstrlen(Message));

RestoreDC(hdc, localrestore);

//----------------------

RestoreDC(hdc, restore);

EndPaint(hwnd, &ps);

DeleteFont(font);

}

Regions Revisited: ExtCreateRegion

Now that we've discussed transformation matrices, we can now look at the ExtCreateRegion function. This function works in principle by applying a trans-for-ma-tion matrix to an existing region. However, it does not apply it directly to a region. Instead, you must obtain the region's representation via GetRegionData to first obtain a de-scrip-tion of the re-gion and then create a new region by using the region description to create a new region. Again, note that Windows 95 cannot do shearing or rotation, but in general the most interesting operations to do to a region are scaling and translation. Translation we already have by using OffsetRgn, so the major contribution of ExtCreateRegion is to allow us to scale a region. A combination of scaling and offset can be useful. For example, con-sider a contrived example: a program that wants to compute the "shadow" of a region, given an apparent light source. The light source is very far away and quite diffuse, but we want to simulate the sha-dowing by noting that the further one moves the object from the background where the shadow is cast, the smaller the shadow is relative to the size of the object casting the shadow. This can be ac-complished by the following code, which for now assumes there is a constant distance between the object and the background. In a real program, in which objects can appear at different dis-tances from the background, the scaling factor would be based on their apparent distance.

static void

shadow_rgn(HDC hdc, HRGN currentrgn)

{

HRGN newrgn ;

LPRGNDATA data ;

DWORD size ;

XFORM m ;

m.eM11 = MY_HSCALE_FACTOR; // factor for a fixed distance

m.eM12 = 0;

m.eM21 = MY_VSCALE_FACTOR;

m.eM22 = 0;

m.eDx = MY_HOFFSET;

m.eDy = MY_VOFFSET;

size = GetRegionData(hrgn, 0, NULL);

data = (LPRGNDATA)malloc(size);

// .. deal with malloc failure

GetRegionData(hrgn, size, data);

newrgn = ExtCreateRegion(&m, size, data);

// We may not have the region in the clipping region

ExtSelectClipRgn(hdc, newrgn, RGN_OR);

FillRgn(hdc, newrgn, ShadowBrush);

free(data);

Paths: Completely General Shapes for Drawing, Clipping, and Filling

A path is essentially a set of graphical objects represented by their outlines. A rectangle, an el-lipse, a rounded rectangle, and a line can certainly be components of a path. The graphic output function Polygon takes a sequence of points and draws a line segment between pairs. The no-tion of path separates the act of defining an outline from the act of drawing the outline. For ex-ample, we can construct a path from, say, 0, 0 to 0, 20, then to 20, 20, then to 20, 0, and finally back to 0, 0. This path delimits a square (assuming our mapping mode is isotropic and our device has square pixels). We could stroke it, which would draw the outline of a square, or we could fill it, which would give us a square of some specific color, or we could both stroke and fill it. The Rectangle function in effect constructs a path, then strokes it with the currently selected pen, and then fills it with the currently selected brush. So why do we need a more complex method to get a square painted?

The answer is that a path is really quite general. For example, we could start at 0, 0 and draw a line to 20, 20 and then specify three pairs of control points and do a PolyBezierTo function, fol-lowed by another line, an ArcTo, a sequence of line segments drawn by Polygon, and finally another PolyBezierTo whose terminal point was 0 ,0. Once we have constructed such a path, we can fill it or stroke it or both. If we are stroking a path, it does not need to form a contiguous line; it can have any number of MoveToEx calls embedded as well. Or, just to make life really interesting, we could convert the area defined by this path to a region, invalidate the region, and use the region in the resulting WM_PAINT handler to clip whatever we draw! The set of path-related operations is given in Table 6.22.
Table 6.22: Path operations
Function Description
AbortPath Closes and discards any paths in the DC. If there is an open path bracket in the DC, it is first closed and then the path is discarded.
BeginPathh Opens a path bracket in the current DC. All path-defining operations from -Table 6.23 will add to this path.
CloseFigure "Closes" the figure by drawing a line from the current position to the first point of the path being drawn. The line that is drawn is connected to the line that starts the figure by using the current line join style.
EndPath Closes the path bracket and selects the path into the DC.
FillPath Closes any open figure in the current path (using CloseFigure). Fills in the paths interior using the current brush from the DC and the prevailing polygon fill mode. The path is then discarded.
FlattenPath Converts any curves in the path currently in the DC to a sequence of line segments.
GetMiterLimit Returns the miter limit.
GetPolyFillMode Returns the current polygon fill mode.
GetPath Returns either the number of points in the path or the path and vertex data describing the currently selected path.
PathToRegion Closes any open path and then converts the path to a region. Whether or not a point is in the region depends on the prevailing polygon fill mode. The path itself is discarded.
PolyDraw Draws lines based on the results of GetPath.
SelectClipPath Selects the current path as a clipping region, combining the new region with any existing region using one of the region operations defined in Table 6.17. The path itself is discarded.
SetMiterLimit Sets the miter limit. When a path is stroked, the lines are joined with the current join mode of the pen selected into the DC. If this is PS_JOIN_MITER, the miter limit will be used to determine if a mitered or beveled corner will be produced.
SetPolyFillMode Sets the current polygon fill mode. The polygon fill mode is used to determine how FillPath determines what areas to fill in and how PathToRegion determines which points are in the region.
StrokePath Renders the existing closed path using the current pen in the DC. The path is then discarded.
StrokeAndFillPath Closes any open path and then strokes the outline of the path using the current pen and fills the figure using the current brush selected in the DC. The path is then discarded.
WidenPath Redefines the current path as being the area that would be painted if the path were drawn with StrokePath, using the currently selected pen in the DC. Flattens any curves using FlattenPath.

A path is initiated with the BeginPath function. This discards any existing path in the DC and prepares the DC to record path information. The path-defining operations shown in Table 6.23, when executed with an open path pending, will not actually draw into the DC. Instead, their effect will be realized by the addition of components to the path. A path is normally terminated by a CloseFigure call followed by an EndPath call. The CloseFigure call is not required if you do not plan to use the path for filling or clipping. However, in many cases this is exactly what you want it for. So you most often will find the CloseFigure call preceding the EndPath call. Once you have closed a figure, you don't have to call EndPath; you can start another figure. You might do this if you were trying to draw a doughnut-like shape. You would draw the outer figure, ending up with a CloseFigure call, and then draw the inner figure, ending with another CloseFigure call. If this was all you needed, you could then call EndPath.

There is a significant difference between calling CloseFigure to close off a figure and simply drawing the geometric shape. Consider drawing a simple form like an equilateral triangle. You might decide that it would look best if you created a pen with a mitered line join. But if you draw it in this way:

BeginPath(hdc) ;

MoveToEx(hdc, x0, y0) ;

LineTo(hdc, x1, y1) ; // draw line 1

LineTo(hdc, x2, y2) ; // draw line 2

LineTo(hdc, x0, y0) ; // draw line 3

EndPath(hdc) ;

StrokePath(hdc);

you will not see the desired effect. Lines 1 and 2 are joined with a mitered corner; lines 2 and 3 are joined with a mitered corner. But lines 3 and 1 are apparently not joined! This is because the line starts with the endcaps option selected for the pen, such as flat end caps. Line 3 ends with flat end caps. But if you draw line 3 by doing a CloseFigure instead of a LineTo, then the output will be correct. Windows will join the endpoint of line 2 to the endpoint of line 1 using a straight line and will make the join using the join of the pen.

BeginPath(hdc)

MoveToEx(hdc, x0, y0) ;

LineTo(hdc, x1, y1) ; // draw line 1

LineTo(hdc, x2, y2) ; // draw line 2

CloseFigure(hdc) ; // draw line 3

EndPath(hdc) ;

StrokePath(hdc);

Examples of this can be seen in Figure 6.27. There, we will show the path when ap-pro-priate with a dashed line. Figure 6.27(a) shows a path constructed by two LineTo operations. (To get it to show, we actually stroked it with a dotted pen.) Figure 6.27(b) shows the result of stroking the path with an ordinary stock black pen. Figure 6.27(c) shows a more complex result. We create a geometric pen using ExtCreatePen, specifying a fairly thick pen, round end caps, and mitered join. When we stroke the path with this pen, we get the solid drawing shown (to show the underlying path, we stroked the path a second time with a dotted white pen). If we call CloseFigure before stroking the path with an ordinary stock black pen, we get the result shown in Figure 6.27(d). If we stroke the path with the same cosmetic pen we used previously, we get the illustration of Figure 6.27(e). Note that in this case, all the joins are mitered. As indicated, we could simply draw that third line in, which when stroked with a stock black pen would give us the result in Figure 6.27(f). Looking at this, you will not see anything substantially different from Figure 6.27(d): for a 1-pixel stock pen, the results are in-dis-ting-uish-able. But if we use our thick geometric pen, we get Figure 6.27(g). There is a substantial dif-ference between the drawing of Figure 6.27(e) and Figure 6.27(g). You can see this difference by looking at the apex of the triangle. Figure 6.27(g) has a rounded top, whereas Figure 6.27(e) has a mi-ter-ed top. This is because Figure 6.27(g) does not have two joined lines at its top. The CloseFigure, however, joined the lines of Figure 6.27(e), so a miter was used to render the join. It is important to understand this difference.

You can use FillPath on the same path to get the result shown in Figure 6.27(h). The interior of the figure is filled with the current brush, which we have selected as a stock gray brush. You can also use StrokeAndFillPath to both stroke and fill the path, as shown in Figure 6.27(i). Note that stroked paths place the pen symmetrically around the line defining the path, as shown by the dotted white line. In the stroking of a path, there is no equivalent of the PS_INSIDEFRAME that would draw the pen "inside" the path. The PS_INSIDEFRAME style applies only to geometric figures drawn with a bounding box, such as Rectangle, Ellipse, and Arc. The code which draws this illustration is shown in Listing .

Listing 6.10: Code to draw the filled and stroked figures of Figure 6.27
#define STROKE_LENGTH 50

#define STROKE_X1 (2 * STROKE_LENGTH)

#define STROKE_Y1 (STROKE_LENGTH / 2)

#define STROKE_X2 (STROKE_X1 - STROKE_LENGTH)

#define STROKE_Y2 (STROKE_Y1 + STROKE_LENGTH)

#define STROKE_X3 (STROKE_X1 + STROKE_LENGTH)

#define STROKE_Y3 STROKE_Y2

#define STROKE_SPACING (2.7f * (float)STROKE_LENGTH)

#define STROKE_VERT_GAP (STROKE_LENGTH / 5)

#define OPEN_PATH 0

#define CLOSE_PATH 1

#define DRAW_PATH 2

#define STROKE_PEN_WIDTH 15

//==============================================================

HFONT createWeightedFont(int size, LPCSTR facecode, int weight)

{

return CreateFont(size, 0,0,0, weight, FALSE, FALSE, FALSE,

ANSI_CHARSET,

OUT_DEFAULT_PRECIS,

CLIP_DEFAULT_PRECIS,

PROOF_QUALITY,

VARIABLE_PITCH,

facecode);

}

//==============================================================

HFONT createCaptionFont(int size, LPCSTR facecode)

{

return createWeightedFont(size, facecode, FW_NORMAL);

}

/****************************************************************

* drawPath

* Inputs:

* HDC hDC: Display context

* int closed: Closing option, see Effect

* HINSTANCE hinst: Instance handle for string resource.

* Can be NULL if caption == 0

* int caption: Caption to display, or 0 for no caption

* Result: void

*

* Effect:

* Draws a path given the stroke parameters. There are

* three points used, which form an isoceles triangle. They

* are points p1, p2 and p3

* OPEN_PATH: p1->p2->p3

* CLOSE_PATH: p1->p2->p3->p1 via CloseFigure

* DRAW_PATH: p1->p2->p3->p1 by explicit line drawing

****************************************************************/

static void drawPath(HDC hdc, int closed, HINSTANCE hinst,

int caption)

{

RECT r;

BeginPath(hdc);

MoveToEx(hdc, STROKE_X1, STROKE_Y1, NULL);

LineTo(hdc, STROKE_X2, STROKE_Y2);

LineTo(hdc, STROKE_X3, STROKE_Y3);

// Based on the closed parameter, select the action

// following the second path segment

switch(closed)

{ /* what close option? */

case OPEN_PATH:

// do nothing. Path is open

break;

case CLOSE_PATH:

// Close the figure; let the GDI compute the path

// segment from p3 to p1

CloseFigure(hdc);

break;

case DRAW_PATH:

// Draw an explicit line from p3 to p1

LineTo(hdc, STROKE_X1, STROKE_Y1);

break;

} /* what close option? */

EndPath(hdc);

// If a caption is present, draw it.

if(caption != 0)

{ /* has caption */

TEXTMETRIC tm;

HFONT font = createCaptionFont(-14, _T("Arial"));

int saved = SaveDC(hdc);

TCHAR text[256];

SelectObject(hdc, font);

GetTextMetrics(hdc, &tm);

SetRect(&r, STROKE_X2, STROKE_Y2 + STROKE_VERT_GAP,

STROKE_X3, STROKE_Y2 + STROKE_VERT_GAP +

2 *(tm.tmHeight + tm.tmExternalLeading));

LoadString(hinst, caption, text, DIM(text));

DrawText(hdc, text, -1, &r, DT_CENTER | DT_WORDBREAK);

RestoreDC(hdc, saved);

DeleteObject(font);

} /* has caption */

}

/****************************************************************

* saf_APath

****************************************************************/

static void saf_APath(HWND hwnd, HDC hdc)

{

int horzsave = SaveDC(hdc);

HPEN pen;

drawPath(hdc, OPEN_PATH, GetWindowInstance(hwnd),

IDS_A_PATH);

pen = CreatePen(PS_DOT, 0, RGB(0,0,0));

SelectObject(hdc, pen);

StrokePath(hdc);

RestoreDC(hdc, horzsave);

DeleteObject(pen);

}

/****************************************************************

* saf_StrokedPath

****************************************************************/

static void saf_StrokedPath(HWND hwnd, HDC hdc)

{

int horzsave = SaveDC(hdc);

HPEN pen;

drawPath(hdc, OPEN_PATH, GetWindowInstance(hwnd),

IDS_STROKED_PATH);

pen = CreatePen(PS_SOLID, 0, RGB(0,0,0));

SelectObject(hdc, pen);

StrokePath(hdc);

RestoreDC(hdc, horzsave);

DeleteObject(pen);

}

/****************************************************************

* saf_StrokedPathWide

****************************************************************/

static void saf_StrokedPathWide(HWND hwnd, HDC hdc)

{

int horzsave = SaveDC(hdc);

int pathsave;

LOGBRUSH blackbrush = {BS_SOLID, RGB(0,0,0), 0 };

HPEN pen;

HPEN whpen;

drawPath(hdc, OPEN_PATH, GetWindowInstance(hwnd),

IDS_STROKED_WIDE);

pen = ExtCreatePen(PS_GEOMETRIC | PS_ENDCAP_ROUND |

PS_JOIN_MITER,

STROKE_PEN_WIDTH, &blackbrush, 0, NULL);

SelectObject(hdc, pen);

pathsave = SaveDC(hdc);

StrokePath(hdc);

RestoreDC(hdc, pathsave);

whpen = CreatePen(PS_DOT, 1, RGB(255,255,255));

SelectObject(hdc, whpen);

SetBkMode(hdc, TRANSPARENT);

StrokePath(hdc);

RestoreDC(hdc, horzsave);

DeleteObject(pen);

DeleteObject(whpen);

}

/****************************************************************

* saf_ClosedFigure

****************************************************************/

static void saf_ClosedFigure(HWND hwnd, HDC hdc)

{

int horzsave = SaveDC(hdc);

HPEN pen;

drawPath(hdc, CLOSE_PATH, GetWindowInstance(hwnd),

IDS_CLOSED);

pen = CreatePen(PS_SOLID, 0, RGB(0,0,0));

SelectObject(hdc, pen);

StrokePath(hdc);

RestoreDC(hdc, horzsave);

DeleteObject(pen);

}

/****************************************************************

* saf_ClosedFigureWide

****************************************************************/

static void saf_ClosedFigureWide(HWND hwnd, HDC hdc)

{

int horzsave = SaveDC(hdc);

int pathsave;

LOGBRUSH blackbrush = {BS_SOLID, RGB(0,0,0), 0 };

HPEN pen;

HPEN whpen;

drawPath(hdc, CLOSE_PATH, GetWindowInstance(hwnd),

IDS_CLOSED_WIDE);

pen = ExtCreatePen(PS_GEOMETRIC | PS_ENDCAP_ROUND |

PS_JOIN_MITER,

STROKE_PEN_WIDTH, &blackbrush, 0, NULL);

SelectObject(hdc, pen);

pathsave = SaveDC(hdc);

StrokePath(hdc);

RestoreDC(hdc, pathsave);

whpen = CreatePen(PS_DOT, 1, RGB(255,255,255));

SelectObject(hdc, whpen);

SetBkMode(hdc, TRANSPARENT);

StrokePath(hdc);

RestoreDC(hdc, horzsave);

DeleteObject(pen);

DeleteObject(whpen);

}

/****************************************************************

* saf_UnclosedFigure

****************************************************************/

static void saf_UnclosedFigure(HWND hwnd, HDC hdc)

{

int horzsave = SaveDC(hdc);

HPEN pen;

drawPath(hdc, DRAW_PATH, GetWindowInstance(hwnd),

IDS_UNCLOSED);

pen = CreatePen(PS_SOLID, 0, RGB(0,0,0));

SelectObject(hdc, pen);

StrokePath(hdc);

RestoreDC(hdc, horzsave);

DeleteObject(pen);

}

/****************************************************************

* saf_UnclosedWide

****************************************************************/

static void saf_UnclosedWide(HWND hwnd, HDC hdc)

{

int horzsave = SaveDC(hdc);

int pathsave;

LOGBRUSH blackbrush = {BS_SOLID, RGB(0,0,0), 0 };

HPEN pen;

HPEN whpen;

drawPath(hdc, DRAW_PATH, GetWindowInstance(hwnd),

IDS_UNCLOSED_WIDE);

pen = ExtCreatePen(PS_GEOMETRIC | PS_ENDCAP_ROUND |

PS_JOIN_MITER,

STROKE_PEN_WIDTH, &blackbrush, 0, NULL);

SelectObject(hdc, pen);

pathsave = SaveDC(hdc);

StrokePath(hdc);

RestoreDC(hdc, pathsave);

whpen = CreatePen(PS_DOT, 1, RGB(255,255,255));

SelectObject(hdc, whpen);

SetBkMode(hdc, TRANSPARENT);

StrokePath(hdc);

RestoreDC(hdc, horzsave);

DeleteObject(pen);

DeleteObject(whpen);

}

/****************************************************************

* saf_FilledPath

****************************************************************/

static void saf_FilledPath(HWND hwnd, HDC hdc)

{

int horzsave = SaveDC(hdc);

drawPath(hdc, CLOSE_PATH, GetWindowInstance(hwnd),

IDS_FILLED);

SelectObject(hdc, GetStockObject(LTGRAY_BRUSH));

FillPath(hdc);

RestoreDC(hdc, horzsave);

}

/****************************************************************

* saf_StrokedAndFilled

****************************************************************/

static void saf_StrokedAndFilled(HWND hwnd, HDC hdc)

{

int horzsave = SaveDC(hdc);

int pathsave;

LOGBRUSH blackbrush = {BS_SOLID, RGB(0,0,0), 0 };

HPEN pen;

HPEN whpen;

drawPath(hdc, CLOSE_PATH, GetWindowInstance(hwnd),

IDS_STROKED_AND_FILLED);

SelectObject(hdc, GetStockObject(LTGRAY_BRUSH));

pen = ExtCreatePen(PS_GEOMETRIC | PS_ENDCAP_ROUND |

PS_JOIN_MITER,

STROKE_PEN_WIDTH, &blackbrush, 0, NULL);

SelectObject(hdc, pen);

pathsave = SaveDC(hdc);

StrokeAndFillPath(hdc);

RestoreDC(hdc, pathsave);

whpen = CreatePen(PS_DOT, 1, RGB(255,255,255));

SelectObject(hdc, whpen);

SetBkMode(hdc, TRANSPARENT);

StrokePath(hdc);

RestoreDC(hdc, horzsave);

DeleteObject(pen);

DeleteObject(whpen);

}

static void strokeandfill_OnPaint(HWND hwnd)

{

XFORM ShiftRight = { 1.0f, 0.0f,

0.0f, 1.0f,

STROKE_SPACING, 0.0f};

XFORM ShiftDown = { 1.0f, 0.0f,

0.0f, 1.0f,

0.0f, STROKE_SPACING};

int vertsave;

PAINTSTRUCT ps;

HDC hdc = BeginPaint(hwnd, &ps);

SetGraphicsMode(hdc, GM_ADVANCED);

vertsave = SaveDC(hdc);

//==============================================================-

// ROW 1

//==============================================================-

saf_APath(hwnd, hdc);

ModifyWorldTransform(hdc, &ShiftRight, MWT_RIGHTMULTIPLY);

saf_StrokedPath(hwnd, hdc);

ModifyWorldTransform(hdc, &ShiftRight, MWT_RIGHTMULTIPLY);

saf_StrokedPathWide(hwnd, hdc);

ModifyWorldTransform(hdc, &ShiftRight, MWT_RIGHTMULTIPLY);

saf_ClosedFigure(hwnd, hdc);

ModifyWorldTransform(hdc, &ShiftRight, MWT_RIGHTMULTIPLY);

saf_ClosedFigureWide(hwnd, hdc);

//===============================================================

// Move down to the next row

//===============================================================

RestoreDC(hdc, vertsave);

ModifyWorldTransform(hdc, &ShiftDown, MWT_RIGHTMULTIPLY);

vertsave = SaveDC(hdc);

//===============================================================

// ROW 2

//===============================================================

saf_UnclosedFigure(hwnd, hdc);

ModifyWorldTransform(hdc, &ShiftRight, MWT_RIGHTMULTIPLY);

saf_UnclosedWide(hwnd, hdc);

ModifyWorldTransform(hdc, &ShiftRight, MWT_RIGHTMULTIPLY);

saf_FilledPath(hwnd, hdc);

ModifyWorldTransform(hdc, &ShiftRight, MWT_RIGHTMULTIPLY);

saf_StrokedAndFilled(hwnd, hdc);

ModifyWorldTransform(hdc, &ShiftRight, MWT_RIGHTMULTIPLY);

//===============================================================

RestoreDC(hdc, vertsave);

EndPaint(hwnd, &ps);

}

A useful technique is illustrated by the contents of one of the functions that draws a thick pen figure with a white dotted line. Let's look at it in some detail.

First we save the DC so that we can restore it when all the operations are finished. We also create a separate variable to hold another SaveDC result we will need later. Further, we create a black brush for creating a geometric pen and some variables to hold pens.

int horzsave = SaveDC(hdc);

int pathsave;

LOGBRUSH blackbrush = {BS_SOLID, RGB(0,0,0), 0 };

HPEN pen;

HPEN whpen;

We then call the generic drawPath function we wrote that draws a path; the OPEN_PATH flag we defined tells the function to not close the path:

drawPath(hdc, OPEN_PATH, GetWindowInstance(hwnd), IDS_WIDE_STROKED);

We now create our wide geometric pen and select it into the DC:

pen = ExtCreatePen(PS_GEOMETRIC | PS_ENDCAP_ROUND | PS_JOIN_MITER,

STROKE_PEN_WIDTH, &blackbrush, 0, NULL);

SelectObject(hdc, pen);

Here's a useful technique: We want to stroke the path twice, but stroking the path will remove the path from the DC. If we first save the DC, we can stroke the path. When we restore the DC, the saved path will be restored, and we can stroke it again. So we first stroke the path with our wide pen:

pathsave = SaveDC(hdc);

StrokePath(hdc);

RestoreDC(hdc, pathsave);

Next we create a dotted white pen for drawing the path itself, select it into the DC, and stroke the path a second time. Note that we set the background mode to be transparent so that the gaps in the pen are not filled, thereby leaving the background color (black) showing through:

whpen = CreatePen(PS_DOT, 1, RGB(255,255,255));

SelectObject(hdc, whpen);

SetBkMode(hdc, TRANSPARENT);

StrokePath(hdc);

Finally, we restore the DC to its original state and delete the objects we created:

RestoreDC(hdc, horzsave);

DeleteObject(pen);

DeleteObject(whpen);

Another example of a complex and interesting path is shown in Figure 6.28. This illustrates a symbol that is usually used in workflow diagrams to indicate a printer or a printed report. In this case, the word "Printer" appears in an outline form. This whole figure is drawn as a single path, text included. Then the path is stroked and filled. Here are some observations about this technique:

The left edge, bottom, and right edge are explicitly drawn, but the top is obtained by using the CloseFigure function.

The font is a TrueType font, not a raster font such as the System font.

After the initial MoveToEx, we do a LineTo, PolyBezierTo, LineTo, and CloseFigure call to get the box and then we do a TextOut to draw the text path. The path consists of a number of disjoint closed figures; letters such as P, i, and e have two closed figures for each letter.

The internal control points for the PolyBezierTo are 1/3 and 2/3 of the width of the object, with the first control point below the object and the second within it.

So that the text path is drawn properly, the background mode is set to TRANSPARENT and the polygon fill mode is set to ALTERNATE.

The figure scales itself to the size of the window by using a transformation matrix. This is all shown in Listing 6.11.

Listing 6.11: Code to draw the "Printer" box
static void

scale(HDC hdc, float xscale, float yscale)

{

XFORM scalefactor = { 1.0f, 0.0f,

0.0f, 1.0f,

0.0f, 0.0f }; // identity

scalefactor.eM11 = xscale;

scalefactor.eM22 = yscale;

ModifyWorldTransform(hdc, &scalefactor, MWT_LEFTMULTIPLY);

}

static void

translate(HDC hdc, float xdelta, float ydelta)

{

XFORM xlate = { 1.0f, 0.0f,

0.0f, 1.0f,

0.0f, 0.0f }; // identity

xlate.eDx = xdelta;

xlate.eDy = ydelta;

ModifyWorldTransform(hdc, &xlate, MWT_LEFTMULTIPLY);

}

#define COMPLEX_BASE 5 // base units for all dimensions

#define COMPLEX_X0 0

#define COMPLEX_Y0 0

#define COMPLEX_WIDTH (16 * COMPLEX_BASE)

#define COMPLEX_LEFT_HEIGHT (16 * COMPLEX_BASE)

#define COMPLEX_RIGHT_HEIGHT (12 * COMPLEX_BASE)

#define COMPLEX_X1 COMPLEX_X0

#define COMPLEX_Y1 (COMPLEX_Y0 + COMPLEX_LEFT_HEIGHT)

#define COMPLEX_X2 (COMPLEX_X0 + COMPLEX_WIDTH)

#define COMPLEX_Y2 (COMPLEX_Y0 + COMPLEX_RIGHT_HEIGHT)

#define COMPLEX_X3 (COMPLEX_X2)

#define COMPLEX_Y3 (COMPLEX_Y0)

#define COMPLEX_CAPTION_Y (COMPLEX_X0 + 3 * COMPLEX_BASE)

#define COMPLEX_OFFSET_Y1 (7 * COMPLEX_BASE)

#define COMPLEX_OFFSET_Y2 (5 * COMPLEX_BASE)

#define COMPLEX_FONTSIZE (-2 * COMPLEX_BASE)

#define COMPLEX_TOTAL_HEIGHT (1.5f * COMPLEX_Y1)

#define COMPLEX_DX 10.0f

#define COMPLEX_DY 10.0f

static void

complex_OnPaint(HWND hwnd)

{

PAINTSTRUCT ps;

HDC hdc = BeginPaint(hwnd, &ps);

int save = SaveDC(hdc);

POINT pt = {COMPLEX_X0, COMPLEX_CAPTION_Y };

SIZE size;

RECT client;

TCHAR caption[128];

HFONT font = createCaptionFont(COMPLEX_FONTSIZE, _T("Arial"));

POINT bottom[] = {

{COMPLEX_X1 + (COMPLEX_X2 - COMPLEX_X1) / 3,

COMPLEX_Y2 + COMPLEX_OFFSET_Y1},

{COMPLEX_X1 + 2 * (COMPLEX_X2 - COMPLEX_X1) / 3,

COMPLEX_Y2 - COMPLEX_OFFSET_Y2},

{COMPLEX_X2, COMPLEX_Y2}};

SetGraphicsMode(hdc, GM_ADVANCED);

// Compute the scaling so it is sized to the window

GetClientRect(hwnd, &client);

scale(hdc, (float)client.bottom / COMPLEX_TOTAL_HEIGHT,

(float)client.bottom / COMPLEX_TOTAL_HEIGHT);

translate(hdc, COMPLEX_DX, COMPLEX_DY);

LoadString(GetWindowInstance(hwnd), IDS_COMPLEX_CAPTION,

caption, DIM(caption));

BeginPath(hdc);

MoveToEx(hdc, COMPLEX_X0, COMPLEX_Y0, NULL);

LineTo(hdc, COMPLEX_X1, COMPLEX_Y1);

PolyBezierTo(hdc, bottom, 3);

LineTo(hdc, COMPLEX_X3, COMPLEX_Y3);

CloseFigure(hdc);

// Compute a point that centers the caption

SelectFont(hdc, font);

GetTextExtentPoint32(hdc, caption, lstrlen(caption), &size);

// Now center the rectangle horizontally

pt.x += ((COMPLEX_X2 - COMPLEX_X0) - size.cx) / 2;

SetBkMode(hdc, TRANSPARENT);

SetPolyFillMode(hdc, ALTERNATE);

TextOut(hdc, pt.x, pt.y, caption, lstrlen(caption));

EndPath(hdc);

SelectBrush(hdc, GetStockObject(LTGRAY_BRUSH));

StrokeAndFillPath(hdc);

RestoreDC(hdc, save);

DeleteObject(font);

EndPaint(hwnd, &ps);

}

We can convert any path to a clipping region. For example, we can draw a path by selecting a font of an appropriate size, calling BeginPath, drawing the text into the DC using TextOut (not DrawText), and calling EndPath. For this to work as expected, we must also draw the text in TRANSPARENT mode. We can then call StrokePath to get this text rendered as an outline if we wish, producing the effect shown in Figure 6.29. In this same DC, using a different font and without creating a path, we can draw some background text; this is illustrated in Figure 6.30. If, however, instead of stroking the path, we use SelectClipPath to establish the path as a clipping region, we get the result shown in Figure 6.31. The code to accomplish all this is shown in Listing 6.12.

Listing 6.12: Code to illustrate text clipping
/****************************************************************

* textclip_MakePath

****************************************************************/

static void textclip_MakePath(HWND hwnd, HDC hdc)

{

RECT r; // client rectangle

HFONT clipfont; // font used to define clipping region

HFONT oldfont;

TCHAR text[128];

LoadString(GetWindowInstance(hwnd), IDS_CLIPPER, text,

DIM(text)) ;

GetClientRect(hwnd, &r);

// Scale the string so that it will fill the client area

clipfont = createWeightedFont(r.bottom, TimesFont, FW_BOLD);

oldfont = SelectFont(hdc, clipfont);

// If we want the text itself to be the clipping region,

// we must set transparent mode.

BeginPath(hdc);

SetBkMode(hdc, TRANSPARENT);

TextOut(hdc, 0, 0, text, lstrlen(text));

EndPath(hdc);

SelectFont(hdc, oldfont);

DeleteObject(clipfont);

}

/****************************************************************

* textclip_DrawBackground

****************************************************************/

static void textclip_DrawBackground(HWND hwnd, HDC hdc)

{

RECT r; // client rectangle

HFONT bkgndfont; // font used to draw background words

HFONT oldfont; // previous font

POINT pt; // current output position

TEXTMETRIC tm; // for determining font height

int offset; // used to produce nicer appearing background

int line; // ...

TCHAR text[128];

int textlen;

GetClientRect(hwnd, &r);

// Create a background font so that the client area will

// contain BKGND_ROWS lines of text

#define BKGND_ROWS 30

bkgndfont = createCaptionFont(r.bottom / BKGND_ROWS,

_T("Times New Roman"));

// We want text output to update the current position

SetTextAlign(hdc, TA_UPDATECP);

// Set up for the background text drawing loops

oldfont = SelectFont(hdc, bkgndfont);

GetTextMetrics(hdc, &tm);

offset = 0;

line = 0;

MoveToEx(hdc, 0, 0, NULL);

GetCurrentPositionEx(hdc, &pt);

textlen = LoadString(GetWindowInstance(hwnd), IDS_BG,

text, DIM(text)) ;

while(pt.y < r.bottom)

{ /* output one row */

// Avoid boring columnar output: offset each line by one

// character of the string

offset = line;

// Now draw the row

while(pt.x < r.right)

{ /* output background word */

TextOut(hdc, 0, 0, &text[offset],

textlen - 1 - offset);

GetCurrentPositionEx(hdc, &pt);

offset = 0; // make sure full word draws next time

} /* output background word */

// Prepare for the next row

line = (line + 1) % (textlen - 1);

MoveToEx(hdc, 0, pt.y + tm.tmHeight, NULL);

GetCurrentPositionEx(hdc, &pt);

} /* output one row */

SelectFont(hdc, oldfont);

DeleteObject(oldfont);

}

/****************************************************************

* textclip1_OnPaint

****************************************************************/

static void

textclip1_OnPaint(HWND hwnd)

{

PAINTSTRUCT ps;

HDC hdc = BeginPaint(hwnd, &ps);

int restore = SaveDC(hdc);

textclip_MakePath(hwnd, hdc);

StrokePath(hdc);

// Clean up the DC and delete the resources created

RestoreDC(hdc, restore);

EndPaint(hwnd, &ps);

}

/****************************************************************

* textclip2_OnPaint

****************************************************************/

static void

textclip2_OnPaint(HWND hwnd, HDC hdc)

{

PAINTSTRUCT ps;

HDC hdc = BeginPaint(hwnd, &ps);

int restore = SaveDC(hdc);

textclip_MakePath(hwnd, hdc);

// Now set the path defined by the text to be the

// clipping path

StrokePath(hdc);

textclip_DrawBackground(hwnd, hdc);

// Clean up the DC and delete the resources created

RestoreDC(hdc, restore);

EndPaint(hwnd, &ps);

}

/****************************************************************

* textclip3_OnPaint

****************************************************************/

static void

textclip3_OnPaint(HWND hwnd, HDC hdc)

{

PAINTSTRUCT ps;

HDC hdc = BeginPaint(hwnd, &ps);

int restore = SaveDC(hdc);

textclip_MakePath(hwnd, hdc);

// Now set the path defined by the text to be the

// clipping path

SelectClipPath(hdc, RGN_COPY);

textclip_DrawBackground(hwnd, hdc);

// Clean up the DC and delete the resources created

RestoreDC(hdc, restore);

EndPaint(hwnd, &ps);

}

The code of Listing 6.12 contains three paint functions that share common functions. The function textclip_MakePath loads a string IDS_CLIPPER and uses it to create a path from a TextOut call. The function textclip_DrawBackground draws a set of lines all the way across the client area; the exact number of lines is fixed by a constant, but the height will be adjusted based on the client window size. The lines are a repeated sequence of IDS_BG strings (slightly offset on each successive line for a nice appearance). The function textclip1_OnPaint simply creates a path and strokes it (see Figure 6.29). The function textclip2_OnPaint creates the path and strokes it and then draws the background text (see Figure 6.30). Finally textclip3_OnPaint creates the path and sets it as the clipping region so that when the background text is drawn, it is clipped to the region, thus producing the result shown in Figure 6.31.

You can find out all the details of a path by using the GetPath function. Normally, you call this twice: once to determine how much space is required and again to obtain the path information. The GetPath function returns a set of points and a parallel array indicating what each point represents. So you have to allocate two arrays to hold the information that comes back.

int size;

LPPOINT points;

LPBYTE types;

size = GetPath(hdc, NULL, NULL, 0); // get number of points

points = (LPPOINT)malloc(size * sizeof(POINT));

types = (LPBYTE) malloc(size * sizeof(BYTE));

// ... write code here to deal with malloc failure...

GetPath(hdc, points, types, size);

PolyDraw(hdc, points, types, size);

This is exactly the same as the operation

StrokePath(hdc);

The meaning of each point is described by the value of its positionally corresponding type; so the meaning of points[3] is described by types[3]. The vertex types are given in Table 6.24.

The PT_CLOSEFIGURE bit is important to recognize. If you are trying to analyze the path in a program, you must mask out this value to compare the vertex type with one of the other values. For example,

if(types[i] == PT_LINETO)

will not necessarily give the correct test if you are not concerned with the PT_CLOSEFIGURE bit. The correct test to see if you have a LineTo operation is to do

if( (types[i] & ~ PT_CLOSEFIGURE) == PT_LINETO)

Note the careful use of parentheses! Without these parentheses, the test will always be false! (Do you understand why?)
Table 6.24: Vertex types for GetPath and PolyDraw
Vertex Type Meaning
PT_MOVETO The point starts a new figure.
PT_LINETO The previous point and this point are joined by a LineTo operation.
PT_BEZIERTO These points always appear in groups of three. The first of the three is the first control point of a PolyBezierTo function and the second of the three is the second control point of a PolyBezierTo function. The last point of the three is the ending point. Note that you may get several of these groups of three in a row.
PT_CLOSEFIGURE This is a flag that is combined with any of the above using a bitwise OR operation. When present, it indicates that the corresponding point is the last point in the figure. The figure should be closed with a CloseFigure.

The PolyDraw function does not exist in Windows 95, but it can be simulated. Sample code that simulates PolyDraw appears in the Microsoft Knowledge Base article Q135059.

If you have used the FlattenPath operation, there will be no curves in the path.

The function PathToRegion converts the area defined by the path to a region. The current value established by SetPolyFillMode determines if a point is inside or outside the region. Once you have a region, you can use it for clipping. (Or you could use it for filling, but you could have used FillPath and avoided the extra step!)

The GDI Explorer

The GDI Explorer application that is included on the CD-ROM was used to create all the illustrations in this chapter. In addition to the examples shown, the GDI explorer contains the BLT Explorer, a way that you can test out all of the BLT operations (except those not implemented on Windows 95) under a variety of conditions. The extended BLT operations are also made available for you to study. These are the operations normally designated only by specifying the hex number, since there are no symbols defined for them. They are even assigned symbolic names in the extrops.h file, which you are free to use in your own code. If you select the BLT Explorer, the ROP Finder tab on the dialog will let you find and identify each of the 256 raster operators.

What's Next?

We devote the last few chapters to discussing output from a Windows program. So it's about time we looked into input to a Windows program. In the next chapter, we start with the basics: input from the key-board, input from the mouse, and periodic timer message input.

Further Reading

Adobe Systems, PostScript Language Reference Manual, Second Ed. Addison-Wesley, 1990.

Ayres, Frank, Jr., Theory and Problems of Matrices, Schaum Publishing, 1962, part of the Schaum's Outline Series.

Foley, J. D. and van Dam, A., Fundamentals of Interactive Computer Graphics Addison-Wesley, 1982. ISBN 0-201-144-68-9.

The standard textbook on interactive computer graphics. Serious graphics programmers should all own it. Glassner, Andrew S. (ed.), Graphics Gems. Academic Press, 1990. ISBN 0-12-286166-3.

Newcomer, Joseph M. and Bruce Horn, "Undocumented Windows: The Windows `Region' Structure", in Dr. Dobb's Journal (18,3), (March 1993).

Newman, William N. and Sproull, Robert F. Principles of Interactive Computer Graphics (2nd edition) McGraw-Hill, 1979.

One of the early works on computer graphics, this book was one of the first to give detailed treatment of bitmap graphical interfaces. It is still a good reference for many of the basic techniques of graphical interaction.

1 According to Charles Petzold, in the article "GDI Comes of Age: Exploring the 32-bit Graphics of Windows NT" (Microsoft Systems Journal, 1992, #5, September), Pierre Bézier did this in the 1960s while working for Renault. He needed a way to model complex curved surfaces in the then-new CAD workstations that Renault was developing.

2 It would be surprising to find a modern processor less than 200 times faster than the 8088, so even if it had to compute all the pixels it is now realistic to do so.

3 You may notice, if you read the code for the GDI Explorer, that the code we use is slightly more complex than is shown here. The details are not important for this discussion and deal with producing nice illustrations rather than base functionality, so we omitted them from these examples.

4 You can also use the header file we provide, extrops.h, which defines the extended ROP codes. It uses the notation of Appendix A; for example, the code for "PDna" is 0x00500325 and is declared as the symbol "extrop_PDna". We use this file in the GDI Explorer.

5 For history buffs: The Digital PDP-6 computer (of the mid-1960s) had an instruction, "Block transfer", mnemonic "BLT", that copied a block of 36-bit words from one memory location to another. When an operation that transferred bits in graphics devices required a name, the name "BitBlt" was invented to designate a bit-block-transfer instead of a word-block-transfer. The name stuck.

6 The rather "clever" syntactic hack here of constructing the left-hand side of the assignment statement depends on your writing a BOOL-returning GDI function immediately after the #endif. A bit ugly, but clever. We leave it as an exercise for the reader to turn this into a nice pair of macros to handle the encapsulation of the GDI operation.

7 We looked at the internals of the region structure for Windows 3.1. See the article by Newcomer & Horn cited in "Further Reading".

8 Unbelievable in several ways. Those of us who worked on the timeshared mainframes and 1200-baud dialup modems of the day found the responsiveness of the original IBM PC a pleasant surprise compared to what we normally suffered through. Many hobbyists who had come up through the slow 8-bit hobby computer chips found the speed a delight; it was unbelievably fast. It is now unbelievable that we could accomplish as much as we did on a personal computer that was so slow. The machine I'm writing this on is approximately 200 times faster than that original IBM PC, and over 100 times faster than the $1,000,000 DECSystem-20 I used to use (it had a whopping 4MW of memory, roughly 16MB by modern standards, and supported 60 simultaneous users). - jmn

9 Transformation matrices are the core of many large graphics systems. They are well-documented in the literature, for example, Newman and Sproull's early work on graphics, Adobe Systems's PostScript Language or Foley and van Dam's book, all cited fully in "Further Reading."

10 OK. I admit it. I'm a PostScript developer, and I've been using transformation matrices for a decade, but to be absolutely sure about what I was writing here. I had it right for this book, I had to go back to my old college math book, Theory and Problems of Matrices (see "Further Reading"). A book I probably last used three decades ago. Frightening, isn't it, how time flies? - jmn

yourEmail@xyzcorp.com
Copyright © 1996, XYZ Corporation. All rights reserved.