Advanced Drawing


Visual Basic .NET Unleashed
By Paul Kimmel
Table of Contents
Chapter 17.  Programming with GDI+

Advanced Drawing

Truly advanced graphics applications are an art form. Video games like Half Life that render moving, jumping, and running images in a 3D world are excellent examples of the result of graphics programming in the hands of talented craftsmen.

Whether Visual Basic .NET will be used to write multiplayer Internet games remains to be seen. At TechEd the game Donkey.Net demonstrated that advanced 3D imagery could be created in VB. (Donkey.Net> used the Revolution3D graphics engine implemented in VB6, but the game is actually implemented in VB .NET.) At the time of this writing you could still download Donkey.Net from the Microsoft Web site.

This section will not teach you how to create visually complex results as demonstrated by games like Quake or Half Life, but you will learn more about GDI+ features in the System.Drawing.Drawing2D namespace that are available to help you get started. You will probably find some of these capabilities helpful in business programming, too.

Drawing Curves

The Graphics object defines a method for drawing curves based on multiple points. The DrawBezier and DrawBeziers methods take four points and an array of Points, respectively, including a starting and ending point and points in between that define the shape of the curve.

The Bzier drawing capabilities allow you to draw elaborate curves as long as you can define the points that describe the curve.


Bzier curves, or splines, were invented by Pierre Bzier for applications in the automotive industry. Bzier splines are curves based on four points and have found widespread use in CAD systems and defining the outline of fonts.

For our purposes, Bzier curves are curves defined by points.

Drawing Complex Curves, or Bzier Curves

Instead of contriving random curves to demonstrate the curve drawing capabilities of the Graphics class, the example in this section copies points described by a GraphicsPath and uses those points to render the image using DrawBeziers. Listing 17.6 demonstrates code that draws the curve shown in Figure 17.3. An overview follows the listing. (The GraphicsPath is covered in the next section.)

Figure 17.3. A Bzier curve drawn from a GraphicsPath initialized with Chr(174).


Listing 17.6 A complex array of points used to draw a moderately complex Bzier curve based on the symbol
  1:  Private Overloads Function GetAdjustedCount(_  2:  ByVal Size As Integer) As Integer  3:   4:  While ((Size - 4) Mod 3 <> 0)  5:  Size -= 1  6:  End While  7:   8:  Return Size  9:  End Function  10:   11:  Private Overloads Function GetAdjustedCount(_  12:  ByVal Source() As PointF) As Integer  13:  Return GetAdjustedCount(Source.GetUpperBound(0))  14:  End Function  15:   16:  Private Sub CopyPoints(ByVal Source() As PointF, _  17:  ByRef Target() As PointF)  18:   19:  ReDim Target(GetAdjustedCount(Source) - 1)  20:  Array.Copy(Source, Target, Target.Length)  21:  End Sub  22:   23:  Private Function GetPath2() As Drawing2D.GraphicsPath  24:  GetPath2 = New Drawing2D.GraphicsPath()  25:  GetPath2.AddString(Chr(174), FontFamily.GenericSerif, _  26:  FontStyle.Bold, 100, New PointF(50, 50), _  27:  StringFormat.GenericDefault)  28:  End Function  29:   30:  Private Sub DrawBezier(ByVal G As Graphics)  31:  Dim Points() As PointF  32:  CopyPoints(GetPath2.PathPoints, Points)  33:  G.DrawBeziers(Pens.Red, Points)  34:  End Sub 

DrawBeziers draws multiple curves. If you call Graphics.DrawBezier (singular), you will need to pass four points. If you call Graphics.DrawBeziers (plural), you will need to pass four points plus an additional number of points evenly divisible by 3 or you will get a System.ArgumentException error.


Depending on your keyboard's character set, you can get different results. If the code seems to work correctly but you get a blank screen, replace the Chr(174) with some alternate random numbers .

The first overloaded GetAdjustedCount method in Listing 17.6 drops a few points to make the equation (points 4) Mod 3 = True. The second overloaded GetAdjustedCount method takes an array of points and passes the size of the array to the original method on lines 1 to 9. (Having two GetAdjustedCount methods simply makes it more convenient to ask for the correct count in the main method, DrawBezier in lines 30 to 34.)


If you do need a temporary variable that is the same type as a function's return type, you can use the function name instead of introducing a new temporary variable (see the function GetPath2 from Listing 17.6).

CopyPoints copies the Source points into the Target points; Target points are passed ByRef because CopyPoints modifies the array on line 19. If we changed only the elements of the array, Target, we could pass the array ByVal. GetPath2 creates a GraphicsPath object and initializes it with the ASCII character 174. (We will discuss GraphicsPath in the next section.) Finally, my DrawBezier method creates an array of points, copies the points from the GraphicsPath.PathPoints, and uses the adjusted points to draw the curve in Figure 17.3.

GraphicsPath Class

The graphics engine maintains graphics as coordinates of geometric shapes in world-space. The GraphicsPath class is responsible for maintaining the coordinates describing the figures described by the GraphicsPath.

Paths are used to draw outlines, fill shapes, and create clipping regions . A path can contain any number of figures, referred to as subpaths. New figures are started when you create a GraphicsPath, call CloseFigure or Graphics.FillPath, or you explicitly call StartFigure.

As demonstrated in the preceding section, strings can be used to initialize graphics paths (see line 25 of Listing 17.6). Additionally, Bzier curves, simple curves, polygons, rectangles, ellipses, lines, and pies can be added to a GraphicsPath. When you've created a GraphicsPath object, you can get the points that describe the path or you can perform other operations on the image.


The PathPoints property is a read-only property that returns an array of PointF structures. Line 32 of Listing 17.6 uses the points returned from GraphicsPath.PathPoints to draw the Bzier curve shown in Figure 17.3.


The PointCount property is a read-only property that indicates the number of points in the GraphicsPath. You can also get the number of points from the Array.GetUpperBound method from the array PathPoints.


GraphicsPath.Reverse reverses the order of the points in the PathPoints array. Using reversed PathPoints to draw a Bzier curve does not result in a mirror image; rather, the points are drawn in reverse order. Listing 17.7 is excerpted from a C# example in the MSDN help files. The example reverses the path points for a line, ellipse, and arc and displays all of the path points in initial order and again after they have been reversed .

Listing 17.7 Reversing the order of PathPoints
  1:  Private Sub Reverse(ByVal G As Graphics)  2:  Dim Path As New Drawing2D.GraphicsPath()  3:  Path.AddLine(New Point(0, 0), New Point(100, 100))  4:  Path.AddEllipse(100, 100, 200, 250)  5:  Path.AddArc(300, 250, 100, 100, 0, 90)  6:   7:  DrawPoints(G, Path.PathPoints, 20)  8:  Path.Reverse()  9:  DrawPoints(G, Path.PathPoints, 150)  10:  End Sub  11:   12:  Private Sub DrawPoints(ByVal G As Graphics, _  13:  ByVal Points() As PointF, ByVal Offset As Integer)  14:   15:  Dim Y As Integer = 20  16:  Dim I As Integer  17:  Dim AFont As New Font("Arial", 8)  18:   19:  For I = 0 To Points.Length - 1  20:  G.DrawString(Points(I).X.ToString & _  21:  ", " & Points(I).Y.ToString, AFont, _  22:  Brushes.Black, Offset, Y)  23:  Y += 20  24:  Next  25:  End Sub 


The sample code was ported from a C# example in the help files. The code was translated line for line, demonstrating the very close match in capability between C# and Visual Basic .NET. Choosing Visual Basic over C#, or vice versa, is referred to as a lifestyle choice. The implication is that there are no practical technical reasons for choosing C# over VB .NET; program in the language you like.

The listing adds a line, ellipse, and arc to the graphics path on lines 2 through 5. Lines 7, 8, and 9 draw the point values, reverse the order of the points, and draw the point values in their new order. Call Graphics.DrawBezier to see that the same image results before and after the point order is changed.


The GraphicsPath.Transform method applies a matrix transform to the GraphicsPath and can be used to rotate, translate, skew, or scale a GraphicsPath.

To transform a GraphicsPath, create an instance of a Matrix object (found in the System.Drawing.Drawing2D namespace), invoke one of the transformation methods on the Matrix, and then call GraphicsPath.Transform(Matrix).

Creating a Matrix

The Matrix class has a default constructor that allows you to create an instance of the matrix without any parameters, or, you can constrain the matrix to a specific region by passing a rectangle and an array of points.

The following code demonstrates default construction of a Matrix.

 Dim MyMatrix As New Matrix 

With the Matrix object, we can invoke methods to describe the impact the matrix will have on the GraphicsPath when the matrix is passed to the Transform method.


Using the object from the preceding section, MyMatrix.Scale(2, 2) scales the matrix object by the X,Y vectors where X = 2 and Y = 2. Modifying DrawBezier from Listing 17.6 to incorporate the scale transform yields the code shown in Listing 17.8.

Listing 17.8 An excerpt from Listing 17.6 in which we modified DrawBezier to scale the Bzier by the vector 2, 2
  1:  Private Sub DrawBezier(ByVal G As Graphics)  2:   3:  Dim Points() As PointF  4:  Dim Path As Drawing2D.GraphicsPath = GetPath2()  5:  Dim AMatrix As New Drawing2D.Matrix()  6:   7:  AMatrix.Scale(2, 2)  8:  Path.Transform(AMatrix)  9:  CopyPoints(Path.PathPoints, Points)  10:  G.DrawBeziers(Pens.Red, Points)  11:   12:  End Sub 

The only modification from Listing 17.6, lines 30 through 34, is the introduction of a temporary Path to store the GraphicsPath object, allowing us to invoke the Transform method, the instantiation of the new matrix. Lines 7 and 8 scale the matrix and apply the matrix transform to the GraphicsPath object Path.

The end result of this subtle change is that the registered trademark symbol () will be displayed roughly twice as large as the original shown in Figure 17.3.


Matrix.Translate( X , Y ) applies the translation vector defined by X and Y to a GraphicsPath. The net result is that the world-space position of the graphics is moved. (Replace Scale on line 7 of Listing 17.8 with Translate to demonstrate the difference between scaling and translating.)


The Matrix.Invert method will invert a Matrix transform if the matrix is invertible. You can call Matrix.IsInvertible to determine whether a particular matrix instance is invertible.

Inserting AMatrix.Invert() between lines 7 and 8 of Listing 17.8 will invert the doubling in size of the matrix and halve the matrix instead, resulting in an image that is smaller than the original.


Rotating a matrix results in a clockwise rotation around the origin. For example, AMatrix.Rotate(10) will rotate the GraphicsPath object 10 degrees in a clockwise position around the origin.

To visualize the result, draw an imaginary line from 0, 0 (the upper-left corner) of the control that will display the image. Then, draw a vector 10 degrees down from the first line and move the center of the image to a point along the new vector a radial distance equal to the distance from the origin to the image's current location. The effect is an orbital path around the origin.


The GraphicsPath.Warp method applies a warp transform described by a parallelogram and a rectangle. Warp causes the effect that results from leaving vinyl LPs in the sun too long. (You remember LPs, don't you? They're the Long Playing records that predate audio cassettes and CDs.) Listing 17.9 draws a GraphicsPath object with a warped appearance.

Listing 17.9 Applying a warp transform to a GraphicsPath
  1:  Private Sub DrawBezier(ByVal G As Graphics)  2:   3:  Dim Points() As PointF  4:  Dim Path As Drawing2D.GraphicsPath = GetPath2()  5:  Dim AMatrix As New Drawing2D.Matrix()  6:   7:  Dim ARectangle = New RectangleF(0, 0, 100, 200)  8:  Dim AParallelogram() As PointF = {New PointF(200, 200), _  9:  New PointF(400, 250), New PointF(220, 400)}  10:   11:  Path.Warp(AParallelogram, ARectangle, AMatrix, _  12:  Drawing2D.WarpMode.Perspective, 0.5)  13:   14:  CopyPoints(Path.PathPoints, Points)  15:   16:  G.DrawBeziers(Pens.Red, Points)  17:   18:  End Sub 

DrawBezier in the example is again taken from the original in Listing 17.6. In the preceding example, a parallelogram defined by the three points on lines 8 and 9 and the rectangle on line 7 are used to define the warp. (If you omit the fourth point of the parallelogram, it is implied by the first three points.) The parallelogram, rectangle, and matrix are all passed to the GraphicsPath.Warp method resulting in a warped or stretched effect.


The GraphicsPath.Widen method takes a Pen or a Pen and a Matrix and adds an additional outline to the GraphicsPath.

Region Class

The Region class is a more powerful version of its cousin the Rectangle. Regions are composed of the interior part of a graphic described by paths and rectangles. Like GraphicsPath, regions are defined in world-space coordinates, making them scalable.

Regions, not rectangles, are used to define the drawing area of a window and are used to perform hit-testing . By assigning a Region object to the Region property, you can define shaped forms that are described by GraphicsPath objects. (The ShapedForm.sln from Chapter 15 demonstrates using a Region to create a custom shaped form, and the next section explores the topic of shaped forms in further detail.)

Shaped Forms

John Percival demonstrates how to use the API to create shaped forms in an article "Creating Odd Shaped Forms" published on by creating a form in the shape of a smiley face. To Mr. Percival's credit, the project imports a dozen or so API methods and completes the task in an additional 50 to 100 lines of code, an example of mental gymnastics.

What a good object-oriented framework does for us is introduce new things and make old things easier. By incorporating spurious API features into a coherent framework, capabilities are organized by utility and consequently easier to use.

Listing 17.10 demonstrates code that creates a smiley face form (see Figure 17.4). The form is similar to the example in the example by John Percival. In the VB6 example, when the API functions are included, the code is all about drawing the shapes and defining the regions that describe the form. For this reason the code in the VB6 example (not provided, but available online at is similar to the VB .NET example in Listing 17.10. The biggest difference is in the tenor of the code.

Figure 17.4. A smiley face form uses clipping regions to define the boundaries of the form.


Listing 17.10 A form based on simple line, curve, and polygon regions
  1:  Public Class Form1  2:  Inherits System.Windows.Forms.Form  3:   4:  [Windows Form Designer generated code]  5:   6:  Private Sub AddOutline(ByVal Face As Region)  7:   8:  Dim OutlinePath As New Drawing2D.GraphicsPath()  9:  OutlinePath.AddEllipse(5, 90, 195, 230)  10:  Dim Outline As New Region(OutlinePath)  11:   12:  Dim InlinePath As New Drawing2D.GraphicsPath()  13:  InlinePath.AddEllipse(10, 95, 185, 220)  14:  Dim Inline As New Region(InlinePath)  15:   16:  Dim Ring As New Region()  17:  Outline.Xor(Inline)  18:  Face.Intersect(Outline)  19:   20:  End Sub  21:   22:  Private Sub AddEyes(ByVal Face As Region)  23:   24:  Dim LeftEyePath As New Drawing2D.GraphicsPath()  25:  LeftEyePath.AddEllipse(10, 100, 85, 40)  26:   27:  Dim RightEyePath As New Drawing2D.GraphicsPath()  28:  RightEyePath.AddEllipse(105, 100, 85, 40)  29:   30:  Dim LeftEye As New Region(LeftEyePath)  31:  Dim RightEye As New Region(RightEyePath)  32:  Face.Union(LeftEye)  33:  Face.Union(RightEye)  34:   35:  End Sub  36:   37:  Private Sub AddNose(ByVal Face As Region)  38:  Dim Nose(2) As PointF  39:  Nose(0).X = 100  40:  Nose(0).Y = 120  41:  Nose(1).X = 80  42:  Nose(1).Y = 180  43:  Nose(2).X = 120  44:  Nose(2).Y = 180  45:   46:  Dim NoseRegionPath As New Drawing2D.GraphicsPath()  47:  NoseRegionPath.AddPolygon(Nose)  48:  Dim NoseRegion As New Region(NoseRegionPath)  49:  Face.Union(NoseRegion)  50:   51:  End Sub  52:   53:  Private Sub AddLips(ByVal Face As Region)  54:   55:  Dim TopLipPath As New Drawing2D.GraphicsPath()  56:  TopLipPath.AddArc(50, 250, 100, 50, 0, 180)  57:  Dim TopLip As New Region(TopLipPath)  58:  Face.Union(TopLip)  59:   60:  End Sub  61:   62:  Private Sub DrawFace()  63:  Dim Face As New Region()  64:  AddOutline(Face)  65:  AddEyes(Face)  66:  AddNose(Face)  67:  AddLips(Face)  68:  Region = Face  69:  End Sub  70:   71:  Private Sub Form1_Load(ByVal sender As System.Object, _  72:  ByVal e As System.EventArgs) Handles MyBase.Load  73:  BackColor = Color.Salmon  74:  DrawFace()  75:  End Sub  76:   77:  Private Sub MenuItem1_Click(ByVal sender As System.Object, _  78:  ByVal e As System.EventArgs) Handles MenuItem1.Click  79:  Application.Exit()  80:  End Sub  81:   82:  End Class 


No special technique was used to contrive the regions. Percival's code was used as a starting point reference for the general appearance, and trial and error was used to create the image shown in Figure 17.4. For advanced images, a more advanced approach needs to be devised.

When the main form loads, the BackColor is set to Color.Salmon and DrawFace is called. The application does more than draw the face; the form actually is the face. Thus if you click on a visible part of the face (see Figure 17.4), you are interacting with the form; otherwise , you are interacting with whatever appears behind the form.

The DrawFace method adds an outline region, eyes, nose, and lips to a Region object, using variations of unions and intersections. The basic process is to create a GraphicsPath, add some points to the path, and construct a Region, adding the GraphicsPath object to the Region. For example, AddNose on lines 37 to 51 creates a GraphicsPath object and adds a polygon describing the nose and adds the polygon to the nose region. The NoseRegion is then added to the Region for the entire Face with a Union operation, Face.Union(NoseRegion).

To demonstrate that the face is now the form, a ContextMenu is associated with the form's ContextMenu property. If you right-click over a filled-in area of the smiley face, the context menu is displayed. Click anywhere else and you are clicking on something unrelated to the form.

Icon Class

The Icon class is in the System.Drawing namespace. Icon makes it easier to load an icon from an external file, a stream, or use an existing icon as a template. Dim AnIcon As New Icon("my.ico") will create an instance of a new icon and load it from a file named my.ico in the current directory.

Icon has properties that are self-explanatory: Width, Height, Size, and Handle. Additional useful methods include Save, which allows you to save the icon to a Stream, and ToBitmap, which converts the icon to a Bitmap object.

Imaging Namespace

The System.Drawing.Imaging namespace contains classes for managing pixel-based pages, such as JPEG and BMP files that might not be suitable for representation using vector-based graphics.

The System.Drawing namespace contains classes for managing primitives, like lines, curves, and figures, as we have seen. The System.Drawing.Imaging namespace contains classes for managing images that cannot be represented using vector-based primitives, such as photographs.

Listing 17.11 demonstrates how various bitmapped images can be loaded and displayed using the System.Drawing.Imaging.Bitmap class. The images demonstrated include ICO, WMF, JPG, BMP, GIF, and EMF files.

Listing 17.11 Bitmapped images supported by classes in the System.Drawing.Imaging namespace
  1:  Imports System.Drawing.Drawing2D  2:  Imports System.Drawing.Imaging  3:   4:  Public Class Form1  5:  Inherits System.Windows.Forms.Form  6:   7:  [Windows Form Designer generated code]  8:   9:  Private AnImage As Bitmap  10:   11:  Private Sub Form1_Paint(ByVal sender As Object, _  12:  ByVal e As System.Windows.Forms.PaintEventArgs) _  13:  Handles MyBase.Paint  14:   15:  If (AnImage Is Nothing) Then Exit Sub  16:  e.Graphics.DrawImage(AnImage, 10, 10)  17:   18:  End Sub  19:   20:   21:  Private Sub Form1_Load(ByVal sender As System.Object, _  22:  ByVal e As System.EventArgs) Handles MyBase.Load  23:   24:  ComboBox1.Text = vbNullString  25:  Dim Files As New IO.DirectoryInfo("..\ Images\ ")  26:  Dim FileInfos() As IO.FileInfo = Files.GetFiles("*.*")  27:   28:  Dim Info As IO.FileInfo  29:   30:  For Each Info In FileInfos  31:  ComboBox1.Items.Add(Info.FullName)  32:  Next  33:  End Sub  34:   35:  Private Sub ComboBox1_SelectedIndexChanged(_  36:  ByVal sender As System.Object, _  37:  ByVal e As System.EventArgs) _  38:  Handles ComboBox1.SelectedIndexChanged  39:   40:  Try  41:  AnImage = New Bitmap(ComboBox1.Text)  42:  Invalidate()  43:  Catch Except As System.Exception  44:  MsgBox(Except.Message)  45:  End Try  46:  End Sub  47:   48:  Private Sub Button1_Click(ByVal sender As System.Object, _  49:  ByVal e As System.EventArgs) Handles Button1.Click  50:   51:  If (AnImage Is Nothing) Then Exit Sub  52:  AnImage.RotateFlip(RotateFlipType.Rotate90FlipX)  53:  Invalidate()  54:  End Sub  55:  End Class 

The example is straightforward. The Load event loads all of the files in the Images subdirectory into the combo box using System.IO capabilities (lines 21 to 33). Each time the user selects an image file from the combo box, the image is updated, the form is invalidated, and the new image is painted on the form.

The sample images demonstrate that the Bitmap class is capable of working with a wide variety of images; notice that there is no special code for managing any particular kind of compressed image.

Further, VB .NET provides support for managing custom images via the Encoder and Decoder classes. You can use the Encoder and Decoder objects to extend GDI+ to support custom image formats.


Visual BasicR. NET Unleashed
Visual BasicR. NET Unleashed
Year: 2001
Pages: 222 © 2008-2017.
If you may any questions please contact us: