The next step up from drawing lines is filling enclosed areas. Windows' seven functions for drawing filled areas with borders are listed in the table below.
Function | Figure |
Rectangle | Rectangle with square corners |
Ellipse | Ellipse |
RoundRect | Rectangle with rounded corners |
Chord | Arc on the circumference of an ellipse with endpoints connected by a chord |
Pie | Pie wedge defined by the circumference of an ellipse |
Polygon | Multisided figure |
PolyPolygon | Multiple multisided figures |
Windows draws the outline of the figure with the current pen selected in the device context. The current background mode, background color, and drawing mode are all used for this outline, just as if Windows were drawing a line. Everything we learned about lines also applies to the borders around these figures.
The figure is filled with the current brush selected in the device context. By default, this is the stock object called WHITE_BRUSH, which means that the interior will be drawn as white. Windows defines six stock brushes: WHITE_BRUSH, LTGRAY_BRUSH, GRAY_BRUSH, DKGRAY_BRUSH, BLACK_BRUSH, and NULL_BRUSH (or HOLLOW_BRUSH). You can select one of the stock brushes into the device context the same way you select a stock pen. Windows defines HBRUSH to be a handle to a brush, so you can first define a variable for the brush handle:
HBRUSH hBrush ;
You can get the handle to the GRAY_BRUSH by calling GetStockObject:
hBrush = GetStockObject (GRAY_BRUSH) ;
You can select it into the device context by calling SelectObject:
SelectObject (hdc, hBrush) ;
Now when you draw one of the figures listed above, the interior will be gray.
To draw a figure without a border, select the NULL_PEN into the device context:
SelectObject (hdc, GetStockObject (NULL_PEN)) ;
If you want to draw the outline of the figure without filling in the interior, select the NULL_BRUSH into the device context:
SelectObject (hdc, GetStockobject (NULL_BRUSH) ;
You can also create customized brushes just as you can create customized pens. We'll cover that topic shortly.
I've already discussed the first five area-filling functions. Polygon is the sixth function for drawing a bordered and filled figure. The function call is similar to the Polyline function:
Polygon (hdc, apt, iCount) ;
The apt argument is an array of POINT structures, and iCount is the number of points. If the last point in this array is different from the first point, Windows adds another line that connects the last point with the first point. (This does not happen with the Polyline function.) The PolyPolygon function looks like this:
PolyPolygon (hdc, apt, aiCounts, iPolyCount) ;
The function draws multiple polygons. The number of polygons it draws is given as the last argument. For each polygon, the aiCounts array gives the number of points in the polygon. The apt array has all the points for all the polygons. Aside from the return value, PolyPolygon is functionally equivalent to the following code:
for (i = 0, iAccum = 0 ; i < iPolyCount ; i++) { Polygon (hdc, apt + iAccum, aiCounts[i]) ; iAccum += aiCounts[i] ; }
For both Polygon and PolyPolygon, Windows fills the bounded area with the current brush defined in the device context. How the interior is filled depends on the polygon-filling mode, which you can set using the SetPolyFillMode function:
SetPolyFillMode (hdc, iMode) ;
By default, the polygon-filling mode is ALTERNATE, but you can set it to WINDING. The difference between the two modes is shown in Figure 5-19.
Figure 5-19. Figures drawn with the two polygon-filling modes: ALTERNATE (left) and WINDING (right).
At first, the difference between alternate and winding modes seems rather simple. For alternate mode, you can imagine a line drawn from a point in an enclosed area to infinity. The enclosed area is filled only if that imaginary line crosses an odd number of boundary lines. This is why the points of the star are filled but the center is not.
The example of the five-pointed star makes winding mode seem simpler than it actually is. When you're drawing a single polygon, in most cases winding mode will cause all enclosed areas to be filled. But there are exceptions.
To determine whether an enclosed area is filled in winding mode, you again imagine a line drawn from a point in that area to infinity. If the imaginary line crosses an odd number of boundary lines, the area is filled, just as in alternate mode. If the imaginary line crosses an even number of boundary lines, the area can either be filled or not filled. The area is filled if the number of boundary lines going in one direction (relative to the imaginary line) is not equal to the number of boundary lines going in the other direction.
For example, consider the object shown in Figure 5-20. The arrows on the lines indicate the direction in which the lines are drawn. Both winding mode and alternate mode will fill the three enclosed L-shaped areas numbered 1 through 3. The two smaller interior areas, numbered 4 and 5, will not be filled in alternate mode. But in winding mode, area number 5 is filled because you must cross two lines going in the same direction to get from the inside of that area to the outside of the figure. Area number 4 is not filled. You must again cross two lines, but the two lines go in opposite directions.
If you doubt that Windows is clever enough to do this, the ALTWIND program in Figure 5-21 demonstrates that it is.
Figure 5-20. A figure in which winding mode does not fill all interior areas.
Figure 5-21. The ALTWIND program.
ALTWIND.C/*----------------------------------------------- ALTWIND.C -- Alternate and Winding Fill Modes (c) Charles Petzold, 1998 -----------------------------------------------*/ #include <windows.h> LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT ("AltWind") ; HWND hwnd ; MSG msg ; WNDCLASS wndclass ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = szAppName ; if (!RegisterClass (&wndclass)) { MessageBox (NULL, TEXT ("Program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; } hwnd = CreateWindow (szAppName, TEXT ("Alternate and Winding Fill Modes"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { static POINT aptFigure [10] = { 10,70, 50,70, 50,10, 90,10, 90,50, 30,50, 30,90, 70,90, 70,30, 10,30 }; static int cxClient, cyClient ; HDC hdc ; int i ; PAINTSTRUCT ps ; POINT apt[10] ; switch (message) { case WM_SIZE: cxClient = LOWORD (lParam) ; cyClient = HIWORD (lParam) ; return 0 ; case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; SelectObject (hdc, GetStockObject (GRAY_BRUSH)) ; for (i = 0 ; i < 10 ; i++) { apt[i].x = cxClient * aptFigure[i].x / 200 ; apt[i].y = cyClient * aptFigure[i].y / 100 ; } SetPolyFillMode (hdc, ALTERNATE) ; Polygon (hdc, apt, 10) ; for (i = 0 ; i < 10 ; i++) { apt[i].x += cxClient / 2 ; } SetPolyFillMode (hdc, WINDING) ; Polygon (hdc, apt, 10) ; EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY: PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; } |
The coordinates of the figure—scaled to an arbitrary 100-unit-by-100-unit area—are stored in the aptFigure array. These coordinates are scaled based on the width and height of the client area. The program displays the figure twice, once using the ALTERNATE filling mode and then using WINDING. The results are shown in Figure 5-22.
Figure 5-22. The ALTWIND display.
The interiors of the Rectangle, RoundRect, Ellipse, Chord, Pie, Polygon, and PolyPolygon figures are filled with the current brush (sometimes also called a "pattern") selected in the device context. A brush is a small 8-pixel-by-8-pixel bitmap that is repeated horizontally and vertically to fill the area.
When Windows uses dithering to display more colors than are normally available on a display, it actually uses a brush for the color. On a monochrome system, Windows can use dithering of black and white pixels to create 64 different shades of gray. More precisely, Windows can create 64 different monochrome brushes. For pure black, all bits in the 8-by-8 bitmap are 0. One bit out of the 64 is made 1 (that is, white) for the first gray shade, two bits are white for the second gray shade, and so on, until all bits in the 8-by-8 bitmap are 1 for pure white. With a 16-color or 256-color video system, dithered colors are also brushes and Windows can display a much wider range of color than would normally be available.
Windows has five functions that let you create logical brushes. You select the brush into the device context with SelectObject. Like logical pens, logical brushes are GDI objects. Any brush that you create must be deleted, but it must not be deleted while it is selected in a device context.
Here's the first function to create a logical brush:
hBrush = CreateSolidBrush (crColor) ;
The word Solid in this function doesn't really mean that the brush is a pure color. When you select the brush into the device context, Windows may create a dithered bitmap and use that for the brush.
You can also create a brush with "hatch marks" made up of horizontal, vertical, or diagonal lines. Brushes of this style are most commonly used for coloring the interiors of bar graphs and when drawing to plotters. The function for creating a hatch brush is
hBrush = CreateHatchBrush (iHatchStyle, crColor) ;
The iHatchStyle argument describes the appearance of the hatch marks. Figure 5-23 shows the six available hatch style constants and what they look like.
Figure 5-23. The six hatch brush styles.
The crColor argument to CreateHatchBrush specifies the color of the hatch lines. When you select the brush into a device context, Windows converts this color to the nearest pure color available on the display. The area between the hatch lines is colored based on the current background mode and the background color. If the background mode is OPAQUE, the background color (which is also converted to a pure color) is used to fill in the spaces between the lines. If the background mode is TRANSPARENT, Windows draws the hatch lines without filling in the area between them.
You can also create your own brushes based on bitmaps using CreatePatternBrush and CreateDIBPatternBrushPt.
The fifth function for creating a logical brush encompasses the other four functions:
hBrush = CreateBrushIndirect (&logbrush) ;
The logbrush variable is a structure of type LOGBRUSH ("logical brush"). The three fields of this structure are shown below. The value of the lbStyle field determines how Windows interprets the other two fields:
lbStyle (UINT) | lbColor (COLORREF) | lbHatch (LONG) |
BS_SOLID | Color of brush | Ignored |
BS_HOLLOW | Ignored | Ignored |
BS_HATCHED | Color of hatches | Hatch brush style |
BS_PATTERN | Ignored | Handle to bitmap |
BS_DIBPATTERNPT | Ignored | Pointer to DIB |
Earlier we used SelectObject to select a logical pen into a device context, DeleteObject to delete a logical pen, and GetObject to get information about a logical pen. You can use these same three functions with brushes. Once you have a handle to a brush, you can select the brush into a device context using SelectObject:
SelectObject (hdc, hBrush) ;
You can later delete a created brush with the DeleteObject function:
DeleteObject (hBrush) ;
Do not delete a brush that is currently selected in a device context.
If you need to obtain information about a brush, you can call GetObject,
GetObject (hBrush, sizeof (LOGBRUSH), (LPVOID) &logbrush) ;
where logbrush is a structure of type LOGBRUSH.