Filling You In: Areas, Paths, and Clipping

by Kelvin R. Lawrence

Have you ever wondered how to draw pie charts? Create wide lines? Make clip paths? Then, you're in the right place! This article illustrates, with examples, how easily you can use areas and paths in your Presentation Manager (PM) graphics programs to create these effects and more.

What is an Area?
The term area describes a collection of drawing primitives, such as lines or arcs, that define a shape. To start defining an area, use the GpiBeginArea function. To finish defining an area and cause the area to be displayed, use the GpiEndArea function. More usefully, an area provides a quick and easy way to define a non-rectangular filled shape.

The following function, DrawSlice, uses an area definition to draw a segment, or slice, of a pie chart. Note that the area obeys the current fill pattern and color definitions. The function takes a presentation space handle and the center point of the pie chart as parameters.  /* Simple procedure to draw a 90 degree pie slice */ VOID DrawSlice( HPS hps, PPOINTL pptlCenter ) // Set the linetype and mix mode we want to use GpiSetLineType( hps, LINETYPE_SOLID ); GpiSetBackMix( hps, BM_OVERPAINT ); // Set the foreground and background colors GpiSetColor( hps, CLR_BLACK ); GpiSetBackColor( hps, CLR_BLUE ); // Choose a pattern to use for filling the area GpiSetPattern( hps,PATSYM_DIAG1 ); // Set the current position to our center point GpiSetCurrentPosition( hps, pptlCenter ); // Start defining our area. The BA_BOUNDARY flag says we want the // area boundary to be drawn. GpiBeginArea( hps, BA_BOUNDARY ); // Draw a partial arc with a 90 degree start angle, a 90 degree // angle of sweep and a size multiplier of 150. GpiPartialArc( hps             , pptlCenter               , MAKEFIXED(150,0)               , MAKEFIXED(90,0)               , MAKEFIXED(90,0)             ); // End our area definition and draw the area. GpiEndArea( hps ); }  ''Function 1. DrawSlice''

Given the above function, all it takes is an extra parameter and the addition of a loop and we can produce a function that draws whole pie charts. The following function, DrawPie, takes one extra parameter than the DrawSlice function. This parameter defines the number of pie slices required.  VOID DrawPie( HPS hps, LONG lSlices, PPOINTL pptlCenter ) { // Loop counter and size of each pie slice LONG i; LONG lSegSize = 360/lSlices ; // Set the linetype and mix mode we want to use GpiSetLineType( hps, LINETYPE_SOLID ); GpiSetBackMix( hps, BM_OVERPAINT ); // Loop around drawing each slice one at a time for ( i=0; i<lSlices; i++ ) {   // Set the current position to be the center point of the pie GpiSetCurrentPosition( hps, pptlCenter ); // Set our colors. Choose a different background color for each // slice. GpiSetColor( hps, CLR_BLACK ); GpiSetBackColor( hps, CLR_BLUE+i ); // Choose a different pattern for each slice GpiSetPattern( hps,PATSYM_DENSE1+i ); // Start the area definition for this slice GpiBeginArea( hps, BA_BOUNDARY ); // Draw a partial arc with the start angle and sweep angle // calculated above. GpiPartialArc( hps               , pptlCenter                 , MAKEFIXED(150,0)                 , MAKEFIXED(lSegSize*i,0)                 , MAKEFIXED(lSegSize, 0)               ); // End our area definition and draw the area. GpiEndArea( hps ); } }  ''Function 2. DrawPie''

The following illustrations show the output from Function 1 and Function 2, respectively.



''Figures 1 and 2. Pie slice versus pie''

So in just a few lines of code, we have defined a general purpose pie chart drawing function. If the area definition does not describe a closed figure, that is, if the last point of the area definition is not the same as the first, the system generates a closing line from the last point of the figure being defined to the first one. This sometimes is referred to as automatic closure of the figure.

GpiBeginArea Options
The GpiBeginArea function lets you specify flags to further define how the area should be drawn and filled. These flags are:
 * BA_BOUNDARY:Draw the area boundary lines
 * BA_NOBOUNDARY:Do not draw the area boundary lines
 * BA_ALTERNATE:Fill the area using an alternate mode fill
 * BA_WINDING:Fill the area using a winding mode fill

Winding/Alternate Fill
The two area filling options, BA_ALTERNATE and BA_WINDING, can best be described using an example. The following function, DrawStar, uses an area to draw a 6-pointed star filled in either alternate or winding mode depending on the second parameter passed, which should be either BA_ALTERNATE or BA_WINDING.  void DrawStar( HWND hwnd, ULONG lFlags ) { HPS hps; POINTL aptl1[3] = { 0,30,100,30,50,120 } ; POINTL aptl2[3] = { 50,0,100,90,0,90 }; hps = WinGetPS( hwnd ); GpiSetCurrentPosition( hps, aptl1 ); GpiSetColor( hps, CLR_RED ); GpiSetPattern( hps, PATSYM_DENSE4 ); GpiBeginArea( hps, BA_BOUNDARY | lFlags ); GpiPolyLine( hps, 3, apt11 ); GpiSetCurrentPosition( hps, aptl2 ); GpiPolyLine( hps, 3, aptl2 ); GpiEndArea( hps ); WinReleasePS( hps ); }  ''Function 3. DrawStar''

The following illustrations show the output of this function depending on the second parameter passed. You get the first illustration from passing BA_WINDING; the second from BA_ALTERNATE.



''Figures 3 and 4. Winding mode versus alternate mode''

As you can see, the star filled in BA_WINDING mode is completely filled in; the star filled in BA_ALTERNATE mode is only partially filled in. Think of winding mode as winding around the interior of the shape, thus filling it completely. For alternate mode, consider an imaginary line passing through the area from outside of the area on the left-hand side to the outside of the area on the right-hand side. Crossing a boundary line toggles the current fill state on or off. That is, every odd-numbered crossing turns on fill. This produces the effect shown in Figure 4.

What is a Path?
Like an area, a path provides a way of defining a shape using a collection of drawing primitives, but provides additional capabilities. Paths provide the ability to: Also, because paths are specified in World coordinates, you can apply all of the normal GPI transforms to path definitions. Because of this, once a path is displayed or used as a clippath, it must be recreated if it is needed again. The path is converted to device coordinates at the time it is used.
 * Draw complex shapes as outlines or filled areas
 * Define a complex, non-rectangular, clipping area
 * Draw widelines
 * Use outline fonts as fill areas and clip areas

The application start the path definition by issuing the GpiBeginPath function; the application ends the path definition by issuing the GpiEndPath function.

The following functions are used in the creation, modification, and manipulation of paths:
 * GpiBeginPath:Begins a path definition
 * GpiEndPath:Ends a path definition
 * GpiOutlinePath:Draws the outline of a path
 * GpiSetClipPath:Makes a path the current clip path
 * GpiFillPath:Draws a path with its interior filled
 * GpiModifyPath:Modifies the attributes of a path
 * GpiStrokePath:Modifies the attributes of a path
 * GpiCloseFigure:Closes a figure within a path
 * GpiPathToRegion:Converts a path to a region in device coordinates

Using a Path to Draw a Wideline
An example of an effective path use is drawing lines that are greater than 2 pels. Such lines are known as geometric wide lines. To draw a geometric wide line entails drawing a single pel wide line into a path and then modifying the attributes of the path before displaying it.

The following function, DrawWideLine, sets up a simple array of points for two lines that meet at 90 degrees to each other and then performs the necessary operations to produce a geometric wideline that is 100 pels wide. The 1 to 1 mapping with pels is only true because a cached PS, allocated using WinGetPS, is used. By default the page units are pels. If this were a PS using some other page units, clearly this relationship would not exist. The function would apply appropriate transformations to the wideline before it was drawn.

DrawWideLine creates a line with rounded ends and square joining points.  VOID DrawWideline2( HWND hwnd, PPOINTL pptlStart ) { POINTL aptl[2]; HPS   hps; hps = WinGetPS( hwnd ); // Arbitrarily define a line that is 200 long and 200 down from the // passed in start position. aptl[0].x = pptlStart->x + 200; aptl[0].y = pptlStart->y ; aptl[1].x = aptl[0].x; aptl[1].y = pptlStart->y - 200; // Set the mix mode, colors and patterns we want to use to fill the // wideline when it gets drawn. GpiSetBackMix( hps, BM_OVERPAINT ); GpiSetColor( hps, CLR_BLACK ); GpiSetBackColor( hps, CLR_BLUE ); GpiSetPattern( hps,PATSYM_DIAG4 ); // Set the current position to the passed in start position GpiSetCurrentPosition( hps, pptlStart ); // Start our path definition GpiBeginPath( hps, 1 ); // Draw our simple poly line into the path GpiPolyLine( hps, 2, aptl ); // End the path definition, note that nothing is drawn to the screen at // this time. GpiEndPath( hps); // Set the geometric line width to 100 GpiSetLineWidthGeom( hps, 100L ); // Set the line ending and line joining choices we require GpiSetLineEnd( hps, LINEEND_ROUND ); GpiSetLineJoin( hps, LINEJOIN_BEVEL ); // Update the path with this new information GpiModifyPath( hps, 1, MPATH_STROKE ); // Fill the wideline with the current pattern and colors, and display // the wideline we have created. GpiFillPath( hps, 1, FPATH_WINDING ); WinReleasePS( hwnd ); }  ''Function 4. DrawWideLine''

Line Ends/Line Joins
As shown in the DrawWideline function, a feature of widelines is that you can specify how to draw the line ending as well as the line joining points.

Figure 5 defines the various line endings and joinings.



''Figure 5. Illustrations of line ending and joinings''

Using a Path to Define a Complex Clip Area
Setting up a clipping area using a path, often referred to as a clip path, is quite simple. The following function, DrawClippedLines, sets up a triangular clip path in the middle of the client window of an application, then draws horizontal lines, the width of the window, from the bottom of the window to the top of the window. Only the parts of the lines which lie within the clip path appear. Everything else is not displayed (or clipped out).  VOID DrawClippedLines( HWND hwnd ) { HPS hps; RECTL rectl; LONG i;  POINTL pt1, pt2; POINTL aptlClip[4]; hps = WinGetPS( hwnd ); // Query our client window rectangle WinQueryWindowRect( hwnd, ); // Define a simple triangle to be the area we clip to. aptlClip[0].x = 0;             aptlClip[0].y = 0; aptlClip[1].x = rectl.xRight;  aptlClip[1].y = 0; aptlClip[2].x = rectl.xRight/2; aptlClip[2].y = rectl.yTop; aptlClip[3].x = 0;             aptlClip[3].y = 0; // Create the path containing our triangle outline as a poly line GpiSetCurrentPosition( hps, aptlClip ); GpiBeginPath( hps, 1 ); GpiPolyLine( hps, 4, aptlClip ); GpiEndPath( hps ); // Establish our path as the clip path. Note the path is destroyed at // this point. Ie we can no longer use it. GpiSetClipPath( hps, 1, SCP_ALTERNATE | SCP_AND ); // Draw some randomly colored lines all the way up the client window. // Note these lines will be clipped to our clip path. pt2.x = rectl.xRight; pt2.y = 0; pt1.x = 0; pt1.y = 0; for ( i=0; i < rectl.yTop; i++, pt1.y++, pt2.y++ ) {   GpiSetColor( hps, rand % CLR_PALEGRAY ); GpiSetCurrentPosition( hps, ); GpiLine( hps, ); } WinReleasePS( hps ); }  ''Function 6. DrawClippedLines''

Clip paths can be extremely complex and can contain more than just polylines. In fact, an interesting exercise to try is to use characters of an outline font to define a clipping area.

Look to a future issue of The Developer Connection News where I will cover this in detail in an article on text and advanced uses of outline font characters.

All the sample code is available on your accompanying Developer Connection CD-ROM.