GDI+ is the successor to the GDI API, which has been available in Windows operating systems since the beginning. As GDI was a subsystem in previous versions of Windows, GDI+ is a subsystem within Windows XP that is responsible for displaying graphical information on screens and printers. Outside the functional similarities of the two APIs, GDI+ has an entirely new API that consists of a set of C++ classes for unmanaged code and a set of .NET classes for managed code. Each set of classes expose the entire set of GDI+ functionality. In addition to the new API that GDI+ has created, GDI+ also serves as a wrapper around the original GDI subsystem. Some functions have been improved and optimized, whereas functions that needed no improvements were simply wrapped with GDI+ methods instead.
Although GDI+ is a new feature of Windows XP, it is also available on Windows 2000 when you load the component updates that come as part of Visual Studio .NET. You may also download a redistributable file for Windows NT 4 Service Pack 6, Windows 98, and Windows Me.
Besides the new API interface and the aforementioned new features, GDI+ does away with the device context and the necessity to program with a device context. With GDI+ you, as a programmer, can focus on what you want to display and not be concerned about the details of a particular device. This goes a step beyond the GDI API, which provided a level of abstraction from the hardware layer but never delivered complete abstraction. If the functionality that you require can only be attained by using the older methods, you can do so because that is still supported.
As indicated earlier, GDI+ provides many new features that were not available in the GDI API. Many of these features were possible through advanced GDI techniques; however, many developers could not write the necessary code and resorted to buying third-party graphics libraries.
The first of the new features is gradient brushes. GDI+ now supports the use of linear and path gradient brushes. You can think of a brush much in the same way you think of a paintbrush. A brush can be any color (limited, of course, by the system bit depth) and, when used on the screen, paints certain objects using the color it is currently set to. Gradient brushes extend the single color brush by allowing you to start with one color, end with a different color, and interpolate all the color values in between those starting and ending colors. Gradient brushes can be used to draw lines, curves, and paths.
The linear gradient brush changes color gradually in a linear line as you move across the shape. For example, if you have a horizontal gradient brush, the color on the far-left side is the starting color, and the colors gradually change as you move from left to right, where the color used on the far right is your ending color. Because this lesson doesn't have color pictures, it is difficult to show an example; however, if you are familiar with gradients, this should make sense.
The path gradient is harder to explain and even harder to show without color pictures. When you fill a shape with a path gradient brush, you have several options on how the colors change as they relate to the shape. However, all the options are relative to the path that makes up the shape. For example, one option is to have a center color and an edge color so that the color changes from the edge color along the path to the center color as it moves away from the path.
A spline is a geometric object that defines a curve between two or more points in which the curve is influenced by some other value, such as another point or a tension value. A cardinal spline is a type of spline in which a series of points is defined and, given a tension value that controls the amount of line curvature, the points are connected so that no sharp edges occur, as shown in Figure 9.1.
The actual point indicators in the drawing were drawn separately for illustration purposes and are not part of the DrawCurve() implementation.
A path, as it relates to graphics, is a combination of lines and curves. Paths are extremely useful when it comes to drawing shapes and filling the interior portions of those shapes, and they can even be used to create clipping regions (regions in which no drawing is done). With GDI, a path that was created belonged to the device context. However, once the path was drawn, it was lost. With GDI+, paths are treated separately and therefore can be used over and over again to draw within GDI+.
Transformations in GDI+ are described with a Matrix object. The Matrix object provides easy transformations, such as rotations and scaling, for GDI+ objects that have a Transform() method. The Transform() method receives a Matrix object that defines the transformation or transformations to apply to the object every time it is drawn. For example, if a Path object has had the Transform() method called with a Matrix object, it is transformed every time it is drawn.
GDI+ expands the region functionality provided by GDI. A region in GDI could only have a translation transformation applied to it because its coordinates were stored relative to the actual device coordinates. GDI+, on the other hand, stores regions in world coordinates. This allows you to use any of the three transformations on this region: rotation, scaling, or translation. As mentioned earlier, these transformations are applied using the Matrix object.
A very nice feature built in to GDI+ is advanced alpha blending. The AlphaBlend() function was available in later versions of GDI; however, it was essentially a BitBlt() function with alpha-blending capabilities. Alpha blending in GDI+ is much more advanced, with the ability to create transparent colors that you can use to create brushes and pens. For example, instead of using the AlphaBlend() function on a bitmap to make it appear transparent, with GDI+ you can simply draw the portions of the image transparently by using brushes and pens that are transparent.
GDI+ also provides support for adjusting image colors. One or more colors in an image can be changed to other colors. You can also adjust a color's intensity relative to another color, adjust the brightness or contrast of all colors, and convert the image colors to grayscale. Recoloring is done by providing an ImageAttributes object when drawing images. The ImageAttributes object stores a color matrix and other information that determines how the colors are changed.
Color correction within GDI+ provides the ability to correct the displayed colors to represent the true colors as closely as possible. This feature is similar to the Image Color Matching (ICM) feature already found as part of the Windows Win32 API.
A feature most developers did without when developing with GDI was antialiasing (or the smoothing of images). The algorithms for antialiasing are somewhat complex to provide a good result. With GDI+, it is a simple option to turn on, and everything drawn is antialiased for the best presentation possible, given the resolution and color depth of the device.
In case you aren't familiar with antialiasing, it is a method of smoothing out the "jaggies" on graphic images. For example, on a diagonal line, you can see the jagged edges as the line passes from one pixel row to the next. With antialiasing, additional pixels are colored with variations of the true line color to produce an illusion of the line being smooth. This is a very common technique used in video games and is even used within Windows extensively to provide an easy-to-read user interface. Figure 9.2 provides an example of text drawn with and without antialiasing.
Metadata is often used within images to determine their features. For example, a digital camera may store camera settings with a picture in the metadata. GDI+ allows you to read and write metadata in image files. An application can then use the metadata to adjust itself based on image settings stored in the metadata.
Containers in GDI+ provide a useful way for beginning a section of drawing code that changes GDI+ properties specifically for the next block of drawing instructions. When those instructions are finished, ending the container restores GDI+ to its original state before the container was created. An application can also nest containers while drawing with GDI+. In other words, complex drawing logic can nest additional GDI+ settings by creating additional containers within a container.
For example, an application could draw a rectangle and then begin a container. Within that container, the application would set the clipping region to the inside of the rectangle. At this point, anything drawn is clipped to the rectangle. Once the drawing is done, the application can end the container and the clipping region, and any other settings made within the container are restored to previous settings.
This saves you from constantly writing code to set the GDI+ settings back to where they were when you are done drawing a portion of an image.
If you have written code using GDI, you are familiar with device contexts and how they store the information about the capabilities of a particular device. The device context is the basis for all GDI calls and is required for GDI to know the device with which you are working. Any time an application used GDI, it first started by retrieving a handle to the device context, which then was used as the first argument in each of the GDI functions that interacted with the device context to produce an image or change settings.
With GDI+ you don't have handles or device contexts. All the functionality is encapsulated within a C++ Graphics object that provides methods to perform GDI+ operations. The Graphics object is the main object within GDI+. However, there are several other supporting classes for pens, brushes, and so on.
The Graphics object, as the device context, is associated with a particular device. The object manages the attributes for the device and applies them as drawing occurs through the Graphics object.
The way pens, brushes, fonts, and so on are handled with GDI+ during the drawing operation is another difference in programming with GDI+. With GDI, you selected objects with the SelectObject() function into the device context and then performed the GDI operations that used the newly selected objects. With GDI+, you don't select objects into the Graphics object; instead, you simply pass them as parameters to the appropriate methods. There is one drawback to this method performance. Passing more parameters into GDI+ methods does have an impact on performance; however, for most applications it won't be noticeable.