Dennis Crain
Microsoft Developer Network Technology Group
Created: September 6, 1993
Click to open or copy the files in the W32PEN sample application for this technical article.
This article reviews the state of pens in Microsoft® Windows™ version 3.x and describes the improvements and features added to pens in Win32®. In Win32, pens are considered either cosmetic or geometric; the coordinate space in which the width of a pen is specified distinguishes the two types. The width of a cosmetic pen is specified in device units. With this version of Win32, the width cannot be greater than a single device unit. The width of a geometric pen is specified in logical units. Geometric pens also possess attributes not available in Windows 3.x: They can have patterns or hatches; they are scalable; and the end caps and joins can be specified by the user. Geometric pens offer features not found in cosmetic pens, but cosmetic pens draw faster. If speed is a consideration, cosmetic pens are a must. The accompanying sample illustrates the use of pens in Win32.
As brutal as it may sound, pens in Microsoft® Windows™ version 3.x did not leave much to the imagination. They provided such basic functionality as width, color, and styles limited to solids and combinations of dots and/or dashes. Colored pens looked great with solid colors, but dithering of colors was possible only with the PS_INSIDEFRAME style. Pen widths were unlimited, although pens with widths greater than a single pixel were always solid. The dot-dash styles were limited to pen widths of a single pixel only. Even though their functionality was limited, Windows 3.1 pens contributed a basic building block for the overall look of Windows 3.x. The three functions provided in Windows 3.1 to create or obtain pens were CreatePen, CreatePenIndirect, and GetStockObject.
Several changes took place in the design of Win32® pens. Following this overview I'll discuss each feature in more detail—with lots of pictures!
Pens are now categorized as cosmetic or geometric. Cosmetic pens are similar to the Windows 3.1 pens, although there are differences. The width of cosmetic pens is always specified in device units. In the current implementation of Win32, cosmetic pens are limited to a single device unit. The widths of geometric pens are specified in logical units. Geometric pens opened the door to creativity with pens. Geometric pens share all the attributes of brushes, thus permitting the use of patterns and dithers.
I remember being frustrated with Windows when I realized that lines drawn with pens having dot and/or dash patterns lost the pattern as the pen scaled upward, resulting in a wide pen. This has been nicely resolved with the use of two new pen styles. One style guarantees that only every other pixel is turned on. The other style lets the user define the spacing of the dashes and spaces. This style is great news for developers who feel confined by the same old dot, dash-dot, dash-dot-dot routine!
Many people liked using wide pens but did not like the rounded end caps that were the default in Windows 3.1. Not only were they the default, they were the only end caps you could have unless you engineered a clever workaround. In Win32, geometric pens have three types of end caps: square, round, or flat.
A join is the area on a line where line segments meet. Win32 lets you specify three types of joins: round, beveled, or mitered. This is best seen at the point in a path where the path takes a right angle to the previous direction.
Win32 pen widths may be changed by the current transformation. Win32 changes are controlled, unlike the seemingly random scaling changes incurred by Windows 3.1. More on this later.
In addition to CreatePen, CreatePenIndirect, and GetStockObject, Win32 includes ExtCreatePen. This function creates a logical cosmetic or geometric pen that has specific style, width, and brush attributes. The following function prototype gives you a glimpse of the features of Win32 pens:
HPEN ExtCreatePen(dwPenStyle, dwWidth, lplb, dwStyleCount, lpStyle)
DWORD dwPenStyle; //Pen style.
DWORD dwWidth; //Pen width,
CONST LOGBRUSH * lplb; //Address of struct for brush attributes.
DWORD dwStyleCount; //Length of array containing custom style bits.
CONST DWORD * lpStyle; //Optional array of custom style bits.
There are no changes to CreatePen or CreatePenIndirect between Windows 3.1 and Win32.
As mentioned earlier, Win32 introduces two types of pens, cosmetic and geometric. All pens may be categorized as one or the other but not both.
A cosmetic pen is essentially what you got when you created a pen in Windows 3.x using CreatePen with the nWidth parameter set to zero. The dimensions of a cosmetic pen are specified in device units. Cosmetic pens can feasibly vary in width, but they are currently limited to a single device unit (pixel).
To create a cosmetic pen, an application uses the CreatePen, CreatePenIndirect, or ExtCreatePen function. Pens created using CreatePen or CreatePenIndirect should specify the width of the pen as zero (if the coordinate system mapping is one-to-one, a pen width of 1 may be used). This will ensure that the pen is actually a cosmetic pen. When using widths greater than 1, these functions create geometric pens and you will not be able to take advantage of the advanced features offered by ExtCreatePen. The moral of the story is that it is wise to use ExtCreatePen to create all of your pens. To create a cosmetic pen with ExtCreatePen, you must specify the width as 1 and the style as PS_COSMETIC. If the width is not 1, ExtCreatePen will fail. There are three stock cosmetic pens available that should be familiar to you: BLACK_PEN, NULL_PEN, and WHITE_PEN. To retrieve one of these stock cosmetic pens, use the GetStockObject function.
There are some gains and some losses associated with cosmetic pens. On the plus side, cosmetic pens draw 3 to 10 times faster than geometric pens. Cosmetic pens are unaffected by transformations. This is especially useful if you are zooming in on a CAD drawing and don't want the widths of the lines to "grow." Cosmetic pens also include a new style, PS_ALTERNATE. When drawing lines using a cosmetic pen with the alternate style, you are guaranteed that only every other pixel will be used. More on this later.
On the downside, using cosmetic pens results in the loss of some very cool new features. You cannot take advantage of end-caps, joins, or patterns. Cosmetic pens created by ExtCreatePen don't support opaque mode.
Geometric pens open the door to all the new features of pens in Win32. Gone are the days of watching dashed lines become solid as their width exceeded 1 pixel. Wide lines with dots and dashes are a reality in Win32. One feature of geometric pens is their ability to utilize patterns (custom or predefined hatches) at widths greater than 1. Win32 pens may also possess a variety of end caps and joins.
The width of a geometric pen is specified in logical units. The width of lines drawn with a geometric pen can be changed depending on the current world transformation.
Geometric pens are created with the ExtCreatePen function. Pens created with CreatePen or CreatePenIndirect can also result in geometric pens, but they cannot take advantage of the new styles, end caps, and joins as geometric pens created with ExtCreatePen.
Win32 provides familiar and new pen styles.
The Windows 3.1 pen styles that we have come to know so well are also provided in Win32. Figure 1 (taken from the Win32 Programmer's Reference, Volume 1) illustrates these styles nicely.
Figure 1. Windows 3.1 pen styles included in Win32
You can create pens with these styles by using any pen creation function. However, there are a few idiosyncrasies worth noting. CreatePen and CreatePenIndirect are unchanged in Win32. This means that dot, dash, or dash-dot pen styles only work with pen widths equal to a single physical pixel. This applies to pens created with ExtCreatePen using the cosmetic pen type. If you wish to create a pen with a width greater than a single pixel and the dash-dot styles, you must use ExtCreatePen to create a geometric pen type.
When using ExtCreatePen to create cosmetic pens, you can use the PS_ALTERNATE style. This style sets every other pixel. PS_ALTERNATE cannot be used with CreatePen or CreatePenIndirect. Use it only when the pen width is specified as 1 and the pen type is PS_COSMETIC. This pen style can be dramatically different from the cosmetic pen obtained by specifying a style of PS_DOT. The dot pattern obtained by PS_DOT is not guaranteed to be every other pixel.
You can specify PS_USERSTYLE in the ExtCreatePen function. For this style, the user provides a "styling array" (see Figure 2 below). PS_USERSTYLE can be used with either geometric or cosmetic pens. In both cases, the styling array contains values that specify the length of the dashes and spaces in a broken line. If used with a cosmetic pen, the array consists of style units. A style unit is defined by a device driver. For example, a device driver may specify that a styling unit is 3 pixels (the typical value used by display drivers). If the styling array contained two values, {2,1}, the dashes would be 6 pixels and the spaces would be 3 pixels. Currently there is no way to determine the size of a driver's style unit. If you absolutely have to know the size, the application must draw a styled line in a bitmap and read it back to determine the size of the style units in the x and y directions.
If PS_USERSTYLE is used with a geometric pen, the styling array is composed of logical units versus style units.
Figure 2. Example of a line using a styling array
The default end cap in Windows 3.x was round. This is also the case in Win32. However, with Win32 geometric pens the end cap can be round, square, or flat. Figure 3 (taken from the Win32 Programmer's Reference, Volume 1) illustrates the three types of end caps. Only pens created using ExtCreatePen can take advantage of these end caps.
Figure 3. Win32 line end caps
The point at which segments of a line meet is referred to as the joint or join. Win32 provides three types of joins: round, beveled, and mitered. The default join is round, as it was in Windows 3.x. Figure 4 (taken from the Win32 Programmer's Reference, Volume 1) illustrates the joins. Remember, only pens created using ExtCreatePen can take advantage of these joins.
Figure 4. Win32 line joins
Geometric pens can have patterns! That is, geometric pens created with ExtCreatePen. This is a terrific improvement over Windows 3.x, where wide lines were limited to solid or dithered colors. Figure 5 (taken from the Win32 Programmer's Reference, Volume 1) shows lines created with the patterns that can be used with geometric pens.
Figure 5. Win32 pens patterns
The hatch and custom patterns should look familiar. They are the same as brushes created using one of the hatch patterns or device-independent bitmap (DIB) patterns. One of the parameters to ExtCreatePen is the address of a variable of type LOGBRUSH. This is the same LOGBRUSH structure used when creating brushes. It contains data that specifies the style, color, and pattern of the brush. A neat feature of the DIB patterns is that they are no longer confined to 8-by-8-pixels. My first thought was drawing bar charts using lines, instead of rectangles, with pictures of various commodities. You know, things like a small pig, or a bushel basket of wheat, or my favorite, an oil well (Figure 6).
Figure 6. My idea of a fun graph with pens versus rectangles
For more information regarding brushes, take a look at the Win32 Software Development Kit (SDK). Look specifically at the LOGBRUSH structure.
The shape of a pen can be changed before the line is stroked. Say what? That's right, you can transform a pen prior to drawing an object. Figure 7 shows two circles. The circle on the left was drawn without any transformation. The circle on the right was drawn after transforming the pen. The overall dimensions of the circles are the same. It is the pen that is transformed. Pretty cool, huh?
Figure 7. Circles demonstrating effect of transformation on pens
The code below shows how the circles were drawn. Note that a path is defined. Before the path is stroked, the transform value is changed. The net effect is that the circle remains a circle, but the pen becomes elliptical. By stretching the transform, you can make the pen become very thin. If you want to transform the circle and the pen, place the calls to SetMapMode and SetWindowExtEx before the creation of the path.
//
//Create a geometric pen. ExtCreatePen could be used also.
//
hpen = CreatePen(PS_SOLID, 20, RGB(255, 0, 0));
SelectObject(hdc, hpen);
//
//Create the path for a circle.
//
BeginPath(hdc);
Ellipse(hdc, 100, 100, 400, 400);
EndPath(hdc);
//
//This transform will only affect the pen shape!
//The pen shape will be elliptical.
//
SetMapMode(hdc, MM_ANISOTROPIC);
SetWindowExtEx(hdc, 1, 5, NULL);
//
//Draw the circle with the elliptical pen.
//
StrokePath(hdc);
The accompanying sample, W32PEN, illustrates many of the topics discussed in this article. Figure 8 shows the application window. The rectangle on the left shows the current end cap and join.
Figure 8. W32PEN application window
Clicking the left mouse button in the center square of the window brings up a dialog box (Figure 9) that allows you to change the color, type, style, join, end cap, and pattern of the pen.
Figure 9. Win32 Pen Attributes dialog box
The sample draws random polylines within a clipping region. Pens are created by using the ExtCreatePen function. As each pen is created, information regarding it is stored in a PENRECORD structure that is then stored as a node in a doubly linked list. The resulting linked list can be played into a metafile by using the File Save As menu option. Future versions of W32PEN will permit picking of the polylines and 2-D transformations of the various lines contained in each node.