GDI+ is the subsystem of the Microsoft Windows XP operating system that is responsible for displaying information on screens and printers. GDI+ is an application programming interface (API) that is exposed through a set of C++ classes.
As its name suggests, GDI+ is the successor to GDI, the graphics device interface included with earlier versions of Windows. Windows XP supports GDI for compatibility with existing applications, but programmers of new applications should use GDI+ for all their graphics needs because GDI+ optimizes many of the capabilities of GDI and also provides additional features.
A graphics device interface, such as GDI+, allows application programmers to display information on a screen or printer without having to be concerned about the details of a particular display device. The application programmer makes calls to methods provided by GDI+ classes and those methods in turn make the appropriate calls to specific device drivers. GDI+ insulates the application from the graphics hardware, and it is this insulation that allows developers to create device-independent applications.
GDI+ expands on GDI by providing linear gradient and path gradient brushes for filling shapes, paths, and regions. Gradient brushes can also be used to draw lines, curves, and paths. When you fill a shape with a linear gradient brush, the color gradually changes as you move across the shape. For example, suppose you create a horizontal gradient brush by specifying blue at the left edge of a shape and green at the right edge. When you fill that shape with the horizontal gradient brush, it will gradually change from blue to green as you move from its left edge to its right edge. Similarly, a shape filled with a vertical gradient brush will change color as you move from top to bottom. The following illustration shows an ellipse filled with a horizontal gradient brush and a region filled with a diagonal gradient brush.
When you fill a shape with a path gradient brush, you have a variety of options for specifying how the colors change as you move from one portion of the shape to another. One option is to have a center color and a boundary color so that the pixels change gradually from one color to the other as you move from the middle of the shape towards the outer edges. The following illustration shows a path (created from a pair of Bézier splines) filled with a path gradient brush.
GDI+ supports cardinal splines, which are not supported in GDI. A cardinal spline is a sequence of individual curves joined to form a larger curve. The spline is specified by an array of points and passes through each point in that array. A cardinal spline passes smoothly (no sharp corners) through each point in the array and thus is more refined than a path created by connecting straight lines. The following illustration shows two paths, one created by connecting straight lines and one created as a cardinal spline.
Persistent Path Objects
In GDI, a path belongs to a device context, and the path is destroyed as it is drawn. With GDI+, drawing is performed by a Graphics object, and you can create and maintain several Path objects that are separate from the Graphics object. A Path object is not destroyed by the drawing action, so you can use the same Path object to draw a path several times.
Transformations and the Matrix Object
GDI+ provides the Matrix object, a powerful tool that makes transformations (rotations, translations, and so on) easy and flexible. A matrix object works in conjunction with the objects that are transformed. For example, a Path object has a Transform method that receives the address of a Matrix object as an argument. A single 3×3 matrix can store one transformation or a sequence of transformations. The following illustration shows a path before and after a sequence of two transformations (first scale, then rotate).
GDI+ expands greatly on GDI with its support for regions. In GDI, regions are stored in device coordinates, and the only transformation that can be applied to a region is a translation. GDI+ stores regions in world coordinates and allows a region to undergo any transformation (scaling, for example) that can be stored in a transformation matrix. The following illustration shows a region before and after a sequence of three transformations: scale, rotate, and translate.
Note that in the previous figure, you can see the untransformed region (filled with red) through the transformed region (filled with a hatch brush). This is made possible by alpha blending, which is supported by GDI+. With alpha blending, you can specify the transparency of a fill color. A transparent color is blended with the background color—the more transparent you make a fill color, the more the background shows through. The following illustration shows four ellipses that are filled with the same color (red) at different transparency levels.
Support for Multiple Image Formats
GDI+ provides the Image, Bitmap, and Metafile classes, which allow you to load, save and manipulate images in a variety of formats. The following formats are supported:
Device Contexts, Handles, and Graphics Objects
If you have written programs using GDI (the graphics device interface included in previous versions of Windows), you are familiar with the idea of a device context (DC). A device context is a structure used by Windows to store information about the capabilities of a particular display device and attributes that specify how items will be drawn on that device. A device context for a video display is also associated with a particular window on the display. First you obtain a handle to a device context (HDC), and then you pass that handle as an argument to GDI functions that actually do the drawing. You also pass the handle as an argument to GDI functions that obtain or set the attributes of the device context.
When you use GDI+, you don't have to be as concerned with handles and device contexts as you do when you use GDI. You simply create a Graphics object and then invoke its methods in the familiar object-oriented style—myGraphicsObject.DrawLine(parameters). The Graphics object is at the core of GDI+ just as the device context is at the core of GDI. The device context and the Graphics object play similar roles, but there are some fundamental differences between the handle-based programming model used with device contexts (GDI) and the object-oriented model used with Graphics objects (GDI+).
The Graphics object, like the device context, is associated with a particular window on the screen and contains attributes (for example, smoothing mode and text rendering hint) that specify how items are to be drawn. However, the Graphics object is not tied to a pen, brush, path, image, or font as a device context is. For example, in GDI, before you can use a device context to draw a line, you must call SelectObject to associate a pen object with the device context. This is referred to as selecting the pen into the device context. All lines drawn in the device context will use that pen until you select a different pen. With GDI+, you pass a Pen object as an argument to the DrawLine method of the Graphics class. You can use a different Pen object in each of a series of DrawLine calls without having to associate a given Pen object with a Graphics object.
Two Ways to Draw a Line
The following two examples each draw a red line of width 3 from location (20, 10) to location (200,100). The first example calls GDI, and the second calls GDI+ through the C++ class interface.
Drawing a line with GDI
To draw a line with GDI, you need two objects: a device context and a pen. You get a handle to a device context by calling BeginPaint, and you get a handle to a pen by calling CreatePen. Next, you call SelectObject to select the pen into the device context. You set the pen position to (20, 10) by calling MoveToEx and then draw a line from that pen position to (200, 100) by calling LineTo. Note that MoveToEx and LineTo both receive hdc (the handle to the device context) as an argument.
HDC hdc; PAINTSTRUCT ps; HPEN hPen; HPEN hPenOld; hdc = BeginPaint(hWnd, &ps); hPen = CreatePen(PS_SOLID, 3, RGB(255, 0, 0)); hPenOld = (HPEN)SelectObject(hdc, hPen); MoveToEx(hdc, 20, 10, NULL); LineTo(hdc, 200, 100); SelectObject(hdc, hPenOld); DeleteObject(hPen); EndPaint(hWnd, &ps);
Drawing a line with GDI+ and the C++ class interface
To draw a line with GDI+ and the C++ class interface, you need a Graphics object and a Pen object. Note that you don't ask Windows for handles to these objects. Instead, you use constructors to create an instance of the Graphics class (a Graphics object) and an instance of the Pen class (a Pen object). Drawing a line involves calling the DrawLine method of the Graphics class. The first parameter of the DrawLine method is a pointer to your Pen object. This is a simpler and more flexible scheme than selecting a pen into a device context as shown in the preceding GDI example.
HDC hdc; PAINTSTRUCT ps; Pen* myPen; Graphics* myGraphics; hdc = BeginPaint(hWnd, &ps); myPen = new Pen(Color(255, 255, 0, 0), 3); myGraphics = new Graphics(hdc); myGraphics->DrawLine(myPen, 20, 10, 200, 100); delete myGraphics; delete myPen; EndPaint(hWnd, &ps);
Pens, Brushes, Paths, Images, and Fonts as Parameters
The preceding examples show that Pen objects can be created and maintained separately from the Graphics object, which supplies the drawing methods. Brush, Path, Image, and Font objects can also be created and maintained separately from the Graphics object. Many of the drawing methods provided by the Graphics class receive a Brush, Path, Image, or Font object as an argument. For example, the address of a Brush object is passed as an argument to the FillRectangle method, and the address of a Path object is passed as an argument to the DrawPath method. Similarly, addresses of Image and Font objects are passed to the DrawImage and DrawString methods. This is in contrast to GDI where you select a brush, path, image, or font into the device context and then pass a handle to the device context as an argument to a drawing function.
Many of the GDI+ methods are overloaded; that is, several methods share the same name but have different parameter lists. For example, the DrawLine method of the Graphics class comes in the following forms:
Status DrawLine(IN const Pen* pen, IN REAL x1, IN REAL y1, IN REAL x2, IN REAL y2); Status DrawLine(IN const Pen* pen, IN const PointF& pt1, IN const PointF& pt2); Status DrawLine(IN const Pen* pen, IN INT x1, IN INT y1, IN INT x2, IN INT y2); Status DrawLine(IN const Pen* pen, IN const Point& pt1, IN const Point& pt2);
All four of the DrawLine variations above receive a pointer to a Pen object, the coordinates of the starting point, and the coordinates of the ending point. The first two variations receive the coordinates as floating point numbers, and the last two variations receive the coordinates as integers. The first and third variations receive the coordinates as a list of four separate numbers, while the second and fourth variations receive the coordinates as a pair of Point (or PointF) objects.
No More Current Position
Note that in the DrawLine methods shown previously both the starting point and the ending point of the line are received as arguments. This is a departure from the GDI scheme where you call
MoveToEx(hdc, x1, y1, NULL) to set the current pen position followed by
LineTo(hdc, x2, y2) to draw a line starting at (x1, y1) and ending at (x2, y2). GDI+ as a whole has abandoned the notion of current position.
Separate Methods for Draw and Fill
GDI+ is more flexible than GDI when it comes to drawing the outlines and filling the interiors of shapes like rectangles. GDI has a Rectangle function that draws the outline and fills the interior of a rectangle all in one step. The outline is drawn with the currently selected pen, and the interior is filled with the currently selected brush.
hBrush = CreateHatchBrush(HS_CROSS, RGB(0, 0, 255)); hPen = CreatePen(PS_SOLID, 3, RGB(255, 0, 0)); SelectObject(hdc, hBrush); SelectObject(hdc, hPen); Rectangle(hdc, 100, 50, 200, 80);
GDI+ has separate methods for drawing the outline and filling the interior of a rectangle. The DrawRectangle method of the Graphics class has the address of a Pen object as one of its parameters, and the FillRectangle method has the address of a Brush object as one of its parameters.
HatchBrush* myHatchBrush = new HatchBrush( HatchStyleCross, Color(255, 0, 255, 0), Color(255, 0, 0, 255)); Pen* myPen = new Pen(Color(255, 255, 0, 0), 3); myGraphics.FillRectangle(myHatchBrush, 100, 50, 100, 30); myGraphics.DrawRectangle(myPen, 100, 50, 100, 30);
Note that the FillRectangle and DrawRectangle methods in GDI+ receive arguments that specify the rectangle's left edge, top, width, and height. This is in contrast to the GDI Rectangle function, which takes arguments that specify the rectangle's left edge, right edge, top, and bottom. Also note that the constructor for the Color class in GDI+ has four parameters. The last three parameters are the usual red, green, and blue values; the first parameter is the alpha value, which specifies the extent to which the color being drawn is blended with the background color.
GDI provides several functions for creating regions: CreateRectRgn, CreateEllpticRgn, CreateRoundRectRgn, CreatePolygonRgn, and CreatePolyPolygonRgn. You might expect the Region class in GDI+ to have analogous constructors that take rectangles, ellipses, rounded rectangles, and polygons as arguments, but that is not the case. The Region class in GDI+ provides a constructor that receives a Rect object reference and another constructor that receives the address of a Path object. If you want to construct a region based on an ellipse, rounded rectangle, or polygon, you can easily do so by creating a Path object (that contains an ellipse, for example) and then passing the address of that Path object to a Region constructor.
GDI+ makes it easy to form complex regions by combining shapes and paths. The Region class has Union and Intersect methods that you can use to augment an existing region with a path or another region. One nice feature of the GDI+ scheme is that a Path object is not destroyed when it is passed as an argument to a Region constructor. In GDI, you can convert a path to a region with the PathToRegion function, but the path is destroyed in the process. Also, a Path object is not destroyed when its address is passed as an argument to a Union or Intersect method, so you can use a given path as a building block for several separate regions. This is shown in the following example. Assume that onePath is a pointer to a Path object (simple or complex) that has already been initialized.
Region region1(rect1); Region region2(rect2); region1.Union(onePath); region2.Intersect(onePath);