The Structure of GDI
From the programmer's perspective, GDI consists of several hundred function calls and some associated data types, macros, and structures. But before we begin looking at some of these functions in detail, let's step back and get a feel for the overall structure of GDI.
The GDI Philosophy
Graphics in Windows 98 and Microsoft Windows NT is handled primarily by functions exported from the dynamic-link library GDI32.DLL. In Windows 98, this GDI32.DLL makes use of the 16-bit GDI.EXE dynamic-link library for the actual implementation of many of the functions. In Windows NT, GDI.EXE is used only for 16-bit programs.
These dynamic-link libraries call routines in device drivers for the video display and any printers you may have set up. The video driver accesses the hardware of the video display, and the printer driver converts GDI commands into codes or commands that the various printers understand. Obviously, different video display adapters and printers require different device drivers.
A wide variety of display devices can be attached to PC compatibles. One of the primary goals of GDI is to support device-independent graphics. Windows programs should be able to run without problems on any graphics output device that Windows supports. GDI accomplishes this goal by providing facilities to insulate your programs from the particular characteristics of different output devices.
The world of graphics output devices is divided into two broad groups: raster devices and vector devices. Most PC output devices are raster devices, which means that they represent images as a rectangular pattern of dots. This category includes video display adapters, dot-matrix printers, and laser printers. Vector devices, which draw images using lines, are generally limited these days to plotters.
Much of traditional computer graphics programming (the type you'll find in older books) is based solely on vectors. This means that a program using a vector graphics system is a level of abstraction away from the hardware. The output device uses pixels for a graphics representation, but the program doesn't talk to the interface in terms of pixels. While you can certainly use the Windows GDI as a high-level vector drawing system, you can also use it for relatively low-level pixel manipulation.
In this respect, Windows GDI is to traditional graphics interface languages what C is to other programming languages. C is well known for its high degree of portability among different operating systems and environments. Yet C is also well known for allowing a programmer to perform low-level system functions that are often impossible in other high-level languages. Just as C is sometimes thought of as a "high-level assembly language," you can think of GDI as a high-level interface to the hardware of the graphics device.
As you've seen, by default Windows uses a coordinate system based on pixels. Most traditional graphics languages use a "virtual" coordinate system with horizontal and vertical axes that range (for instance) from 0 to 32,767. Although some graphics languages don't let you use pixel coordinates, Windows GDI lets you use either system (as well as additional coordinate systems based on physical measurements). You can use a virtual coordinate system and keep your program distanced from the hardware, or you can use the device coordinate system and snuggle right up to the hardware.
Some programmers think that when you're working in terms of pixels, you've abandoned device independence. We've already seen in the last chapter that this is not necessarily the case. The trick is to use the pixels in a device-independent manner. This requires that the graphics interface language provide facilities for a program to determine the hardware characteristics of the device and make appropriate adjustments. For example, in the SYSMETS programs we used the pixel size of a standard system font character to space text on the screen. This approach allowed the programs to adjust to different display adapters with different resolutions, text sizes, and aspect ratios. You'll see other methods in this chapter for determining display sizes.
In the early days, many users ran Windows with a monochrome display. Even in more recent years, laptop users were restricted to gray shades. For this reason, GDI was constructed so that you can write a program without worrying much about color—that is, Windows can convert colors to gray shades. Even today, video displays used with Windows 98 have different color capabilities (16 color, 256 color, "high color," and "true color"). Although ink-jet printers have brought low-cost hard-copy color to the masses, many users still prefer their black-only laser printers for high-quality output. It is possible to use these devices blindly, but your program can also determine how many colors are available on the particular display device and take best advantage of the hardware.
Of course, just as you can write C programs that have subtle portability problems when they run on other computers, you can also inadvertently let device dependencies creep into your Windows programs. That's part of the price of not being fully insulated from the hardware. You should also be aware of the limitations of Windows GDI. Although you can certainly move graphics objects around the display, GDI is generally a static display system with only limited animation support. If you need to write sophisticated animations for games, you should explore Microsoft DirectX, which provides the support you'll need.
The GDI Function Calls
The several hundred function calls that comprise GDI can be classified in several broad groups:
- Functions that get (or create) and release (or destroy) a device context As we saw in earlier chapters, you need a handle to a device context in order to draw. The BeginPaint and EndPaint functions (although technically a part of the USER module rather than the GDI module) let you do this during the WM_PAINT message, and GetDC and ReleaseDC functions let you do this during other messages. We'll examine some other functions regarding device contexts shortly.
- Functions that obtain information about the device context In the SYSMETS programs in Chapter 4, we used the GetTextMetrics function to obtain information about the dimensions of the font currently selected in the device context. Later in this chapter, we'll look at the DEVCAPS1 program, which obtains other, more general, device context information.
- Functions that draw something Obviously, once all the preliminaries are out of the way, this is the really important stuff. In the last chapter, we used the TextOut function to display some text in the client area of the window. As we'll see, other GDI functions let us draw lines and filled areas. In Chapters 14 and 15, we'll also see how to draw bit-mapped images.
- Functions that set and get attributes of the device context An "attribute" of the device context determines various details regarding how the drawing functions work. For example, you can use SetTextColor to specify the color of any text you draw using TextOut or other text output functions. In the SYSMETS programs in Chapter 4, we used SetTextAlign to tell GDI that the starting position of the text string in the TextOut function should be the right side of the string rather than the left, which is the default. All attributes of the device context have default values that are set when the device context is obtained. For all Set functions, there are Get functions that let you obtain the current device context attributes.
- Functions that work with GDI "objects" Here's where GDI gets a bit messy. First an example: By default, any lines you draw using GDI are solid and of a standard width. You may wish to draw thicker lines or use lines composed of a series of dots or dashes. The line width and this line style are not attributes of the device context. Instead, they are characteristics of a "logical pen." You can think of a pen as a collection of bundled attributes. You create a logical pen by specifying these characteristics in the CreatePen, CreatePenIndirect, or ExtCreatePen function. Although these functions are considered to be part of GDI, unlike most GDI functions they do not require a handle to a device context. The functions return a handle to a logical pen. To use this pen, you "select" the pen handle into the device context. The current pen selected in the device context is considered an attribute of the device context. From then on, whatever lines you draw use this pen. Later on, you deselect the pen object from the device context and destroy the object. Destroying the pen is necessary because the pen definition occupies allocated memory space. Besides pens, you also use GDI objects for creating brushes that fill enclosed areas, for fonts, for bitmaps, and for other aspects of GDI.
The GDI Primitives
The types of graphics you display on the screen or the printer can themselves be divided into several categories, which are called "primitives." These are:
- Lines and curves Lines are the foundation of any vector graphics drawing system. GDI supports straight lines, rectangles, ellipses (including that subset of ellipses known as circles), "arcs" that are partial curves on the circumference of an ellipse, and Bezier splines, all of which I'll discuss in this chapter. If you need to draw a different type of curve, you can draw it as a polyline, which is a series of very short lines that define a curve. GDI draws lines using the current pen selected in the device context.
- Filled areas Whenever a series of lines or curves encloses an area, you can cause that area to be filled with the current GDI brush object. This brush can be a solid color, a pattern (which can be a series of horizontal, vertical, or diagonal hatch marks), or a bitmapped image that is repeated vertically or horizontally within the area.
- Bitmaps A bitmap is a rectangular array of bits that correspond to the pixels of a display device. The bitmap is the fundamental tool of raster graphics. Bitmaps are generally used for displaying complex (often real-world) images on the video display or printer. Bitmaps are also used for displaying small images that must be drawn very quickly, such as icons, mouse cursors, and buttons that appear in application toolbars. GDI supports two types of bitmaps—the old (although still quite useful) "device-dependent" bitmap, which is a GDI object, and the newer (as of Windows 3.0) "device-independent" bitmap (or DIB), which can be stored in disk files. I'll discuss bitmaps in Chapters 14 and 15.
- Text Text is not quite as mathematical as other aspects of computer graphics; instead it is bound to hundreds of years of traditional typography, which many typographers and other observers appreciate as an art. For this reason, text is often the most complex part of any computer graphics system, but it is also (assuming literacy remains the norm) the most important. Data structures used for defining GDI font objects and for obtaining font information are among the largest in Windows. Beginning with Windows 3.1, GDI began supporting TrueType fonts, which are based on filled outlines that can be manipulated with other GDI functions. Windows 98 continues to support the older bitmap-based fonts for compatibility and small memory requirements. I'll discuss fonts in Chapter 17.
Other aspects of GDI are not so easily classifiable. These are:
- Mapping modes and transforms Although by default you draw in units of pixels, you are not limited to doing that. The GDI mapping modes allow you to draw in units of inches (or rather, fractions of inches), millimeters, or anything you want. In addition, Windows NT supports a traditional "world transform" expressed as a 3-by-3 matrix. This allows for skewing and rotation of graphics objects. The world transform is not supported under Windows 98.
- Metafiles A metafile is a collection of GDI commands stored in a binary form. Metafiles are used primarily to transfer representations of vector graphic drawings through the clipboard. I'll discuss metafiles in Chapter 18.
- Regions A region is a complex area of any shape and is generally defined as a Boolean combination of simpler regions. More complex regions can be stored internally in GDI as a series of scan lines derived from the original definition of the region. You can use regions for outlining, filling, and clipping.
- Paths A path is a collection of straight lines and curves stored internally in GDI. Paths can be used for drawing, filling, and clipping. Paths can also be converted to regions.
- Clipping Drawing can be restricted to a particular section of the client area. This is known as clipping. The clipping area can be rectangular or nonrectangular, generally specified as a region or a path.
- Palettes The use of a customized palette is generally restricted to displays that show 256 colors. Windows reserves only 20 of these colors for use by the system. You can alter the other 236 colors to accurately display the colors of real-world images stored in bitmaps. I'll discuss palettes in Chapter 16.
- Printing Although this chapter is restricted to the video display, almost everything you learn here can be applied to printing. I discuss printing in Chapter 13.