What is GDI+ ?

If you have programmed under Windows you are familiar with the term GDI (Graphical Device Interface). GDI simplifies drawing by providing an interface to the hardware devices like screen or printer such that the programmers don’t need to bother about hardware details and their differences. The same program can work on different display adapters, printers, keyboards, etc. without modifying it.

.NET uses GDI+, an extension to GDI, which further simplifies drawing. GDI+ has added several new features like graphics paths, support to image file formats, image transformation, etc. GDI+ has also modified the programming model by introducing fundamental changes in the programming model used by GDI.

Changes In Programming Model

GDI uses an idea of Device Context (DC). Device Context is a structure that stores all the drawing related information viz. features of display device and attributes that decide the appearance of the drawing. Every device context is associated with a window. To draw on a window, one must first obtain a device context of that window. If we want to change any attribute, say, pen color we first select it in the device context by calling the SelectObject( ) method. Once selected all the drawing is done using this pen unless and until we select another pen in device context.

GDI+ works with ‘graphics context’ that plays similar role as device context. The graphics context is also associated with a particular window and contains information specifying how drawing would be displayed. However, unlike device context it does not contain information about pen, brush, font, etc. If we want to draw with new pen we simply have to pass an object of Pen class to the DrawLine( ) method (this method draws line on window). We can pass different Pen objects in each call to DrawLine( ) method to draw the lines in different colors. Thus GDI uses a stateful model, whereas, GDI+ uses a stateless model. The Graphics class encapsulates the graphics context. Not surprisingly, most of the drawing is done by calling methods of the Graphics class.

Working With GDI+

We would see how to draw text and graphics using GDI+ by writing a small program. Create a Windows Application. Windows programmers know that a window receives WM_PAINT message when it is to be painted. We need to handle this message if we want to do any painting in the window. In .NET to do this we can either override the virtual method OnPaint( ) of the Form class or write a handler for the Paint event. The base class implementation of OnPaint( ) invokes the Paint event handler through delegate. Hence we should write our code in the Paint event handler.

Add the Paint handler to the form. The Form1_Paint( ) handler would look like this.

private void Form1_Paint ( object sender, PaintEventArgs e )
{
}

The first parameter passed to the Form1_Paint( ) handler contains the reference to the object of a control that sends the event. The second parameter contains more information about the Paint event. We would first see how to display a string on the form. To display the string we would use DrawString( ) method of the Graphics class.

private void Form1_Paint ( object sender, PaintEventArgs e )
{

Graphics g = e.Graphics ;
Font myfont = new Font ( "Times New Roman", 60 ) ;
StringFormat f = new StringFormat( ) ;
f.Alignment = StringAlignment.Center ;
f.LineAlignment = StringAlignment.Center ;
g.DrawString ( "Hello!", myfont, Brushes.Blue, ClientRectangle, f ) ;

}

The Graphics property of the PaintEventArgs class contains reference to the Graphics object. We can use this reference for drawing. In a handler other than Paint event handler we can obtain the Graphics reference using the CreateGraphics( ) method of the Form class. The DrawString( ) method has several overloaded versions. We used one that allows us to display centrally aligned text in desired font and color. The first parameter passed to the DrawString( ) method is the string we wish to display. The second parameter is the font in which text would get displayed. We have created a font by passing the font name and font size to the constructor of the Font class. The text gets filled with the brush color specified as the third parameter. The fourth parameter specifies the surrounding rectangle. We have passed ClientRectangle property of Form class that contains a rectangle representing the client area of the form. To centrally align the text we have used the StringFormat class. The Alignment and LineAlignment properties of this class contain horizontal and vertical alignment of text respectively.

The Graphics class contains various methods to draw different shapes. This includes drawing rectangle, line, arc, bezier, curve, pie, etc. We would add the code in Form1_Paint( ) handler that draws rectangles in different pens and brushes. You would be able to draw other shapes on similar lines.

The following code draws a rectangle using green colored pen having line thickness of 3.

Pen p = new Pen ( Color.Green, 3 ) ;
g.DrawRectangle ( p, 20, 20, 150, 100 ) ;

The Pen class encapsulates various styles of pens like solid, dash, dash-dot, etc. We can change the style of pen using the DashStyle property of the Pen class. This is shown in the following statement.

p.DashStyle = DashStyle.Dash ;

If we want, we can specify custom pen style by using the DashPattern property. There are several other properties of the Pen class that allow us to specify the pen type (hatch fill, gradient fill, solid color, etc), cap style, join style, etc.

Unlike GDI, GDI+ provides separate methods for rectangle and filled rectangle. To fill the rectangle we need to pass a Brush object. This is shown below.

HatchBrush hb = new HatchBrush ( HatchStyle.BackwardDiagonal,Color.Red, Color.Black ) ;
g.FillRectangle ( hb, 200, 20, 150, 100 ) ;

We have used hatch brush to fill the rectangle. The hatch brush is created using the HatchBrush class. We have mentioned the hatch style as BackwardDiagonal. The rectangle will get filled with the hatch brush in red and black color combination. Like the HatchBrush class there are several other classes used to fill the shapes with viz. SolidBrush, TextureBrush, and LinearGradientBrush. Gradient brush is something that was not available in GDI. Let us see how to use it.

LinearGradientBrush gb = new LinearGradientBrush ( ClientRectangle,
Color.BlanchedAlmond, Color.Aquamarine, 90 ) ;
g.FillRectangle ( gb, ClientRectangle ) ;

Here, we have created an object of the LinearGradientBrush class and passed to its constructor the rectangle to be filled, and two colors that form the gradient pattern. The last parameter specifies the angle from which we wish to draw. Specifying 90 would fill the window vertically.

Coordinates And Transformations

In the Graphics methods we specify coordinates in two-dimensional coordinate system. The system has origin at left-top corner and x and y axes point to right and down respectively. All the methods take coordinates in pixels. The coordinates passed to Graphics methods are world coordinates. When we pass world coordinates to a method, they firstly get translated into page coordinates (logical coordinates) and then into device coordinates (physical coordinates). Ultimately, the shape gets drawn in device coordinates. In both the page and device coordinate system the measure of unit is same i.e pixels. We can customize the coordinate system by shifting the origin to some other place in client area and by setting a different measure of unit. Let us see how this can be achieved. We would first draw a horizontal line having 1 inch of width. Here is the code to do this.

private void Form1_Paint ( object sender, PaintEventArgs e )
{

Graphics g = e.Graphics ;
g.PageUnit = GraphicsUnit.Inch ;
Pen p = new Pen ( Color.Green, 1 / g.DpiX ) ;
g.DrawLine ( p, 0, 0, 1, 0 ) ;

}

Here firstly we have set the PageUnit property to GraphicsUnit.Inch specifying that the unit of measure is an inch. We have created a Pen object and set its width to 1 / g.Dpix. The Dpix property of the Graphics class indicates a value, in dots per inch, for the horizontal resolution supported by this Graphics object. Note that this is necessary because now Pen object also assumes 1 unit = 1 inch. So, if we don’t set the pen width like this, a line with 1 inch pen width would get drawn. Next we drew a line having one unit measure, which happens to be an inch.

Let us now shift the origin to the center of the client area and draw the line again.

private void Form1_Paint ( object sender, PaintEventArgs e )
{

Graphics g = e.Graphics ;
g.PageUnit = GraphicsUnit.Inch ;
g.TranslateTransform ( ( ClientRectangle.Width / g.DpiX ) / 2,
( ClientRectangle.Height / g.DpiY ) / 2 ) ;
Pen p = new Pen ( Color.Green, 1 / g.DpiX ) ;
g.DrawLine ( p, 0, 0, 1, 0 ) ;

}

Here, after setting the unit to an inch using the PageUnit property, we have called the TranslateTransform( ) method to shift the origin to the center of the client area. This method maps the world coordinates to page coordinates and so the transformation is called world transformation. The x and y values we have passed to the TranslateTransform( ) method get added to every x and y values we pass to the Graphics methods. Finally, we have created a pen having proper width and drew the line.

GDI+ also allows us to orient the x and y axes’s direction to the specified angle. For this, it provides the RotateTransform( ) method. For example, if we call the RotateTransform( ) method before drawing the line as shown below,

g.RotateTransform ( 30 ) ;

then line would get displayed slanting downwards, 30 degrees below the base line. We can use this functionality of the RotateTransform( ) method to create an application like analog clock.

Disposing Graphics Objects

Whenever we open a file, we close it after we have finished working with the file. This is because a handle is associated with the file that remains open if we don’t close it explicitly. Similarly, GDI+ resources like pens, brushes, fonts need to be disposed of because they encapsulate GDI+ handles in them. To release the GDI+ resources, we can call the Dispose( ) method on every object that is to be released. For example, following statement would release the pen object represented by penobject using the Dispose( ) method.

penobject.Dispose( ) ;

We must also release the Graphics object obtained by calling the CreateGraphics( ) method.

Posted bySumedh at 10:23 PM  

0 comments:

Post a Comment