Creating Your Own Drawing Application in Visual Basic.NET, Part 3

In the previous two parts (Part 1, Part 2) of this article series, you have created the Drawing Application Framework, enhanced it by adding a marquee while drawing, and extended the color filling capabilities. This part (Part 3) was probably the most fun to create! What I will cover here is the following:

  • Drawing of Odd shapes, such as:
    • Hearts
    • Spirals
    • Rounded Rectangles
    • Stars
    • Lines
  • Filling these shapes with the following Brushes:
    • Horizontal Hatch Brush
    • Vertical hatch Brush
    • Zig Zag Hatch Brush
    • Wave Hatch Brush
    • Plaid Hatch Brush
    • Linear Gradient Brush
    • Path Gradient Brush
    • Texture
    • Solid Brush

Now, get started!

Giving the Triangle Button a Face Lift

From the beginning, I wasn't too fond of the Triangle button; I envisioned it to be much more useful than what it was. My whole plan for this (the Triangle button) was to be able to draw shapes like Hearts, Spirals, and Rounded Rectangles—uncommon shapes—and to have one button control all the odd shapes.

Step 1: Rename All the Objects to More Appropriate Names

On frmTools, rename the Triangle button to butCOddShapes and change its Text property to Odds. Comment out the butCTriangle_Click event handler completely, so that you can start over with butCOddShapes' events. Create a Paint event for butCOddShapes that looks like the following:

Private Sub butCOddShapes_Paint(ByVal sender As System.Object, _
   ByVal e As System.Windows.Forms.PaintEventArgs) _
   Handles butCOddShapes.Paint

   'Declare A GraphicsPath Object, Which Is Used To Draw The Shape
   'Of The Button
   Dim StarPath As System.Drawing.Drawing2D.GraphicsPath = _
      New System.Drawing.Drawing2D.GraphicsPath

   'Create A Star Path
   Dim pSPoints As Point() = { _
      New Point(30, 0), _
      New Point(40, 20), _
      New Point(60, 20), _
      New Point(42, 30), _
      New Point(60, 60), _
      New Point(30, 40), _
      New Point(0, 60), _
      New Point(17, 30), _
      New Point(0, 20), _
      New Point(20, 20)}    ' star shape + changed caption to Odds,
                            ' center middle aligned

   'Add The Point Array Object To The GraphicsPath Object.
   StarPath.AddLines(pSPoints)

   'Size Of The Button
   butCOddShapes.Size = New System.Drawing.Size(60, 60)

   If blnTraingleClicked Then
      'If The Button Is Selected To Draw, Change The Color
      butCOddShapes.BackColor = Color.DeepSkyBlue
   Else
      'If The Button Is Not Selected To Draw With, Change Back To
      'Original Color
      butCOddShapes.BackColor = Color.Aquamarine
   End If

   'Create The Triangular Shaped Button, Based On The Graphics Path
   butCOddShapes.Region = New Region(StarPath)

   'Release All Resources Owned By The Graphics Path Object
   StarPath.Dispose()

End Sub

Once run, the code above will create a Star shaped button with five points.

In frmTools_Load, include a ToolTip for your new button:

tipCanvas.SetToolTip(butCOddShapes, "Draw An Odd Shape")

Step 2: Add the Odd Shapes Menu and Their Associated Event Handlers

Add a Context Menu named cmCanvasShapes to frmTools. Add the following Menu Items to cmCanvasShapes:

Menu Item Name Text
mnuTriangle Triangle
mnuHeart Heart
mnuRoundRect Rounded Rectangle
mnuLine Line
mnuSpiral Spiral

After adding the new Context Menu, you have to Select the Context Menu property on butCOddShapes, and set it to cmCanvasShapes. Add a butCOddShapes_MouseDown event to frmTools in the code window, and type the following:

Private Sub butCOddShapes_MouseDown(ByVal sender As Object, _
   ByVal e As System.Windows.Forms.MouseEventArgs) _
   Handles butCOddShapes.MouseDown
   If e.Button = MouseButtons.Left And e.X > 28 And e.Y > 28 Then
       butCOddShapes.ContextMenu.Show(butCOddShapes, _
                                      New Point(e.X, e.Y))
   End If
End Sub

This will enable you to get a menu showing once you click on the bottom right point of the star.

Now, add all the event handlers, to set which Tool was selected, to True. This is the same principle as all the other buttons:

Private Sub mnuTriangle_Click(ByVal sender As Object, _
                              ByVal e As System.EventArgs) _
   Handles mnuTriangle.Click
   'Set All Boolean Flags Of Tools Click To False, Except For The
   'Current One : Triangle
   blnCircleClicked   = False
   blnSquareClicked   = False
   blnDrawClicked     = False
   blnEraserClicked   = False
   blnTraingleClicked = True

   blnHeartClicked     = False
   blnRoundRectClicked = False
   blnLineClicked      = False
   blnSpiralClicked    = False

   'Refresh/Repaint The Buttons To Indicate Current Selection State
   butCCircle.Refresh()
   butCSquare.Refresh()
   butCOddShapes.Refresh()
End Sub

Private Sub mnuHeart_Click(ByVal sender As Object, _
   ByVal e As System.EventArgs) Handles mnuHeart.Click
   'Set All Boolean Flags Of Tools Click To False, Except For The
   'Current One: Triangle
   blnCircleClicked   = False
   blnSquareClicked   = False
   blnDrawClicked     = False
   blnEraserClicked   = False
   blnTraingleClicked = False

   blnHeartClicked     = True
   blnRoundRectClicked = False
   blnLineClicked      = False
   blnSpiralClicked    = False

End Sub

Private Sub mnuRoundRect_Click(ByVal sender As Object, _
   ByVal e As System.EventArgs) Handles mnuRoundRect.Click
   'Set All Boolean Flags Of Tools Click To False, Except For The
   'Current One: Triangle
   blnCircleClicked   = False
   blnSquareClicked   = False
   blnDrawClicked     = False
   blnEraserClicked   = False
   blnTraingleClicked = False

   blnHeartClicked     = False
   blnRoundRectClicked = True
   blnLineClicked      = False
   blnSpiralClicked    = False
End Sub

Private Sub mnuLine_Click(ByVal sender As Object, _
   ByVal e As System.EventArgs) Handles mnuLine.Click
   'Set All Boolean Flags Of Tools Click To False, Except For The
   'Current One: Triangle
   blnCircleClicked   = False
   blnSquareClicked   = False
   blnDrawClicked     = False
   blnEraserClicked   = False
   blnTraingleClicked = False

   blnHeartClicked     = False
   blnRoundRectClicked = False
   blnLineClicked      = True
   blnSpiralClicked    = False
End Sub


Private Sub mnuSpiral_Click(ByVal sender As Object, _
   ByVal e As System.EventArgs) Handles mnuSpiral.Click
   'Set All Boolean Flags Of Tools Click To False, Except For The
   'Current One: Triangle
   blnCircleClicked   = False
   blnSquareClicked   = False
   blnDrawClicked     = False
   blnEraserClicked   = False
   blnTraingleClicked = False

   blnHeartClicked     = False
   blnRoundRectClicked = False
   blnLineClicked      = False
   blnSpiralClicked    = True
End Sub

Also, make sure that the Circle button and the Square button, Pencil button and the Eraser button also know about these new tools:

Private Sub butCCircle_Click(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles butCCircle.Click

   'Set All Boolean Flags Of Tools Click To False, Except For The
   'Current One: Circle
   blnTraingleClicked = False
   blnSquareClicked   = False
   blnDrawClicked     = False
   blnEraserClicked   = False
   blnCircleClicked   = True

   blnHeartClicked     = False
   blnRoundRectClicked = False
   blnLineClicked      = False
   blnSpiralClicked    = False

   'Refresh/Repaint The Buttons, To Indicate Current Selection State
   butCCircle.Refresh()
   butCSquare.Refresh()
   butCOddShapes.Refresh()

End Sub

Private Sub butCSquare_Click(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles butCSquare.Click

   'Set All Boolean Flags Of Tools Click To False, Except For The
   'Current One: Square
   blnCircleClicked   = False
   blnTraingleClicked = False
   blnDrawClicked     = False
   blnEraserClicked   = False
   blnSquareClicked   = True

   blnHeartClicked     = False
   blnRoundRectClicked = False
   blnLineClicked      = False
   blnSpiralClicked    = False

   'Refresh/Repaint The Buttons, To Indicate Current Selection State

   butCCircle.Refresh()
   butCSquare.Refresh()
   butCOddShapes.Refresh()

End Sub

Private Sub butCPencil_Click(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles butCPencil.Click

   'Set All Boolean Flags Of Tools Click To False, Except For The
   'Current One: Pencil
   blnCircleClicked   = False
   blnTraingleClicked = False
   blnSquareClicked   = False
   blnDrawClicked     = True

   blnHeartClicked     = False
   blnRoundRectClicked = False
   blnLineClicked      = False
   blnSpiralClicked    = False

   'Refresh/Repaint The Buttons, To Indicate Current Selection State
   butCCircle.Refresh()
   butCSquare.Refresh()
   butCOddShapes.Refresh()

End Sub


Private Sub butCEraser_Click(ByVal sender As Object, _
   ByVal e As System.EventArgs) Handles butCEraser.Click

   'Set All Boolean Flags Of Tools Click To False, Except For The
   'Current One: Eraser
   blnCircleClicked   = False
   blnTraingleClicked = False
   blnSquareClicked   = False
   blnDrawClicked     = False
   blnEraserClicked   = True

   blnHeartClicked     = False
   blnRoundRectClicked = False
   blnLineClicked      = False
   blnSpiralClicked    = False

   'Refresh/Repaint The Buttons, To Indicate Current Selection State
   butCCircle.Refresh()
   butCSquare.Refresh()
   butCOddShapes.Refresh()

End Sub

Step 3: Drawing the Actual Shapes

Before you draw anything, you should declare the pen objects for each of your new shapes, at the top of picCDraw_MouseUp.

Dim pHeartPen     As New Pen(Color.Black, 3)
Dim pRoundRectPen As New Pen(Color.Black, 3)
Dim pLinePen      As New Pen(Color.Black, 3)
Dim pSpiralPen    As New Pen(Color.Black, 3)

Drawing the Heart

The first shape you are going to draw is the Heart because you already have the working code that draws your Triangle.

To draw your heart, you will need to add Point variables that keep track of the starting point and ending point of your Heart. Declare them in the Declarations section of frmCanvas.

'Start Point For Heart
Private ptHeartStartPoint As System.Drawing.Point

'End Point For Heart
Private ptHeartEndPoint As System.Drawing.Point

At the bottom of picCDraw_MouseDown, set the Start Point's x & Y equals to the current x & y points:

ptHeartStartPoint.X = e.X 'Heart Start Point - X

ptHeartStartPoint.Y = e.Y 'Heart Start Point - Y

In picCDraw_MouseUp, just above the starting IF block, add these lines to set the Heart's ending points equals to the current ending X & Y points

ptHeartEndPoint.X = e.X 'Heart End Point - X

ptHeartEndPoint.Y = e.Y 'Heart End Point - Y

Still in picCDraw_MouseUp, add another If block after the Triangle's If block, to determine whether the Heart Tool was selected. Then, draw the Heart shape.

'Determine If The Heart Tool Has Been Clicked
If blnHeartClicked Then

   'Yes, It Has Been Clicked, Set The Pen's Color To Selected Color
   pHeartPen.Color = cColor

   If blnOutlineDrawClicked Then
      'Draw The Heart With The Current Starting, And Ending Values
      Dim HeartWidth As Integer = _
         Math.Max(ptHeartEndPoint.X, ptHeartStartPoint.X) - _
         Math.Min(ptHeartEndPoint.X, ptHeartStartPoint.X)
      Dim HeartHeight As Integer = _
         Math.Max(ptHeartEndPoint.Y, ptHeartStartPoint.Y) - _
         Math.Min(ptHeartEndPoint.Y, ptHeartStartPoint.Y)
      Dim HeartAvg As Integer = CInt((HeartWidth + HeartHeight) / 2)
      If HeartAvg > 0 Then
         Dim HeartTopLeft As Point = ptHeartStartPoint
         If ptHeartEndPoint.X < ptHeartStartPoint.X Then
            HeartTopLeft.X = ptHeartEndPoint.X
         End If
         If ptHeartEndPoint.Y < ptHeartStartPoint.Y Then
            HeartTopLeft.Y = ptHeartEndPoint.Y
         End If

         Dim HeartRadius As Integer = CInt(HeartAvg / 2)
         Dim HeartTopLeftSquare As New Rectangle(HeartTopLeft.X, _
            HeartTopLeft.Y, HeartRadius, HeartRadius)
         Dim HeartTopRightSquare As New Rectangle(HeartTopLeft.X + _
            HeartRadius, HeartTopLeft.Y, HeartRadius, HeartRadius)

         gCanvas.DrawArc(pHeartPen, HeartTopLeftSquare, 135.0F, 225.0F)
         gCanvas.DrawArc(pHeartPen, HeartTopRightSquare, 180.0F, 225.0F)
         gCanvas.DrawLine(pHeartPen, CInt(HeartTopLeft.X + _
            HeartRadius / 2 - Math.Sin(45 / 180 * Math.PI) * _
            HeartRadius / 2), _
            CInt(HeartTopLeft.Y + HeartRadius / 2 + _
               Math.Sin(45 / 180 * Math.PI) * HeartRadius / 2), _
               HeartTopLeft.X + HeartRadius, HeartTopLeft.Y + HeartAvg)
         gCanvas.DrawLine(pHeartPen, CInt(HeartTopLeft.X + _
            HeartAvg - (1 - Math.Sin(45 / 180 * Math.PI)) * _
            HeartRadius / 2), _
            CInt(HeartTopLeft.Y + HeartRadius / 2 + _
               Math.Sin(45 / 180 * Math.PI) * HeartRadius / 2), _
               HeartTopLeft.X + HeartRadius, HeartTopLeft.Y + HeartAvg)
      End If
   End If

At the bottom of picCDraw_MouseUp, dispose of your Heart pen.

pHeartPen.Dispose()

Drawing the Rounded Rectangle

In picCDraw_MouseUp, add the following If block, right after the Heart section you've just created:

'Determine If The RoundRect Tool Has Been Clicked
If blnRoundRectClicked Then

   'Yes, It Has Been Clicked, Set The Pen's Color To Selected Color
   pRoundRectPen.Color = cColor

   If blnOutlineDrawClicked Then
      'Draw The RoundRect With The Current Starting And Ending Values
      Dim RectX As Integer      = Math.Min(sStartX, sEndX)
      Dim RectY As Integer      = Math.Min(sStartY, sEndY)
      Dim RectWidth As Integer  = Math.Abs(sStartX - sEndX)
      Dim RectHeight As Integer = Math.Abs(sStartY - sEndY)
      Dim RectRadius As Integer = 15
      Dim pRRPath As New System.Drawing.Drawing2D.GraphicsPath

      If RectRadius > Rectwidth / 2 OrElse RectRadius > _
         RectHeight / 2 Then RectRadius = RectHeight / 2

      pRRPath.StartFigure()

      pRRPath.AddArc(RectX, RectY, RectRadius * 2, _
                     RectRadius * 2, 180, 90)
      pRRPath.AddArc(RectX + RectWidth - RectRadius * 2, RectY, _
                     RectRadius * 2, RectRadius * 2, 270, 90)
      pRRPath.AddArc(RectX + RectWidth - RectRadius * 2, RectY + _
                     RectHeight - RectRadius * 2, RectRadius * 2, _
                     RectRadius * 2, 0, 90)
      pRRPath.AddArc(RectX, RectY + RectHeight - RectRadius * 2, _
                     RectRadius * 2, RectRadius * 2, 90, 90)
      pRRPath.CloseFigure()

      gCanvas.DrawPath(pRoundRectPen, pRRPath)
   End If

At the bottom of picCDraw_MouseUp, dispose of the RoundRect Pen object:

pRoundRectPen.Dispose()

Drawing the Line

Underneath the code you entered to draw the Rounded Rectangle, add the following If block:

'Determine If The Line Tool Has Been Clicked
If blnLineClicked Then

   'Yes, It Has Been Clicked, Set The Pen's Color To Selected Color
   pLinePen.Color = cColor


   If blnOutlineDrawClicked Then
      'Draw The Line With The Current Starting, And Ending Values
      gCanvas.DrawLine(pLinePen, sStartX, sStartY, sEndX, sEndY)
   End If

End If


Dispose of the Line Pen as well:

pLinePen.Dispose()

Experimenting with The Line Tool

Drawing the Spiral

Inside picCDraw_MouseUp, just underneath the code to create the Line, add the following to draw your Spiral.

'Determine If The Spiral Tool Has Been Clicked
If blnSpiralClicked Then

   'Yes, It Has Been Clicked, Set The Pen's Color To Selected Color
   pSpiralPen.Color = cColor

   If blnOutlineDrawClicked Then
      'Draw The Spiral With The Current Starting, And Ending Values
      Dim SpiralPI          As Double = 3.14159265358979
      Dim SpiralOrientation As Double = 3.356987413    'orientation
      Dim SpiralWidth       As Integer
      Dim SpiralX           As Integer
      Dim SpiralHeight      As Integer
      Dim SpiralY           As Integer

      'location of spiral
      Dim SpiralRect As New Rectangle(sStartX, sStartY, sEndX, sEndY)

      SpiralWidth  = SpiralRect.Width
      SpiralHeight = SpiralRect.Height

      Dim SpiralAAng  As Single
      Dim SpiralBAng  As Single
      Dim SpiralLoop  As Long
      Dim SpiralAngle As Double

      SpiralAAng = 0.15
      SpiralBAng = 0.15

      For SpiralLoop = 0 To 8000    ' size of spiral
         SpiralAngle = (SpiralPI / 720) * SpiralLoop
         SpiralX = SpiralWidth + (SpiralAAng * _
            (System.Math.Cos(SpiralAngle)) * _
            (SpiralOrientation ^ (SpiralBAng * SpiralAngle)))
         SpiralY = SpiralHeight - (SpiralAAng * _
            (System.Math.Sin(SpiralAngle)) * _
            (SpiralOrientation ^ (SpiralBAng * SpiralAngle)))
         'the higher the + number the thicker the lines
         gCanvas.DrawLine(pSpiralPen, SpiralX, SpiralY, _
         SpiralX + 5, SpiralY + 5)
      Next SpiralLoop
   End If

End If

Dispose of the Spiral pen.

pSpiralPen.Dispose()

You are now ready to Build and run Canvas. Once Canvas is run, you will immediately notice the newly created "Star" button, instead of the old Triangle, on the Tools Window. When you click on the bottom right point of the star, a menu will appear listing all of the Odd shapes you can draw. It should list Triangle, Heart, Rounded Rectangle, Line, and Spiral. Feel free to experiment drawing with your new shapes. The Line and the Spiral shapes only work in Outline Draw mode; the rest you can draw in Outline Mode, or Full Fill Draw mode.

Move on now, to filling these shapes with color.

Creating Your Own Drawing Application in Visual Basic.NET, Part 3

Filling Your Shapes

In case you haven't been following this article series, in Part 2 you enhanced your application to be able to fill already drawn objects/shapes with solid colors. The problem you are faced with now is that you have added more shapes; and you must change your methodology of filling the shapes with colors by adding new filling styles. In this section, you are going to work with the following:

Hatch Brushes

  1. Horizontal
  2. Vertical
  3. Zig Zag
  4. Weave
  5. Plaid

Gradients

  1. Linear
  2. Path

Textures

And your now-familiar Solid brush.

modCanvas Changes

To handle which Fill tool has been selected, I've felt it best to create an Enum called BrushType, which contains all your filling options.

In modCanvas, add the following Enum:

Public Enum BrushType
   'Hatch
   brHorizontal = 1
   brVertical   = 2
   brZigZag     = 3
   brWeave      = 4
   brPlaid      = 5
   'Gradient
   brLinear     = 6
   brPath       = 7
   'Texture
   brTexture    = 8
   'Solid
   brSolid      = 9
End Enum

Add the following two variables. TBrushType will be used to determine which Brush was selected, and strTextureFile will store the physical location of your Texture file, when you fill your objects with pictures:

'Which Brush is Selected?
Public TBrushType As BrushType

'The Texture/Picture File To Draw With
Public strTextureFile As String

Instead of having nine different Boolean variables controlling which Fill method has been clicked, you use an Enum with nine "settings." It is much easier to control. To use the Enum, you need to declare a variable of type BrushType (the Enum). you also declared a variable to hold the Graphic's (that you are going to use as a Pattern) location,

frmTools design changes

Add a new ContextMenu named cmCanvasFill to frmTools. Design the menu to resemble the following pictures:

[cmCanvasFillHatch.png]     [cmCanvasFillGradient.png]

Set the following Text properties for your newly created Menu Items

Name Text
MenuItem1 Brush Type
MenuItem2 Hatch
mnuHorizontal Horizontal
mnuVertical Vertical
mnuZigZag Zig Zag
mnuWeave Weave
mnuPlaid Plaid
MenuItem8 Gradient
mnuLinear Linear
mnuPath Path
mnuTexture Texture
mnuSolid Solid

Set the butCColor's ContextMenu property to cmCanvasFill to activate your ContextMenu once the user has right-clicked.

frmTools Code Changes

Add the following event handlers for each of your new Menu Items to store the Fill tool selection:

Private Sub mnuHorizontal_Click(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles mnuHorizontal.Click
   TBrushType = modCanvas.BrushType.brHorizontal    ' Horizontal Hatch
   CHatchBrush = New HatchBrush(HatchStyle.Horizontal, cColor, _
                                Color.White)
End Sub

Private Sub mnuVertical_Click(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles mnuVertical.Click
   TBrushType = modCanvas.BrushType.brVertical 'Vertical Hacth
   CHatchBrush = New HatchBrush(HatchStyle.Vertical, cColor, _
                                Color.White)
End Sub

Private Sub mnuZigZag_Click(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles mnuZigZag.Click
   TBrushType = modCanvas.BrushType.brZigZag    'ZIG ZAG Hatch
   CHatchBrush = New HatchBrush(HatchStyle.ZigZag, cColor, _
                                Color.White)
End Sub

Private Sub mnuWeave_Click(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles mnuWeave.Click
   TBrushType = modCanvas.BrushType.brWeave    'Weave Hatch
   CHatchBrush = New HatchBrush(HatchStyle.Weave, cColor, _
                                Color.White)
End Sub

Private Sub mnuPlaid_Click(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles mnuPlaid.Click
   TBrushType = modCanvas.BrushType.brPlaid    'Plaid Hatch
   CHatchBrush = New HatchBrush(HatchStyle.Plaid, cColor, Color.White)
End Sub

Private Sub mnuLinear_Click(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles mnuLinear.Click
   TBrushType = modCanvas.BrushType.brLinear    'Linear Gradient
End Sub

Private Sub mnuPath_Click(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles mnuPath.Click
   TBrushType = modCanvas.BrushType.brPath    'Path Gradient
End Sub

Private Sub mnuTexture_Click(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles mnuTexture.Click
   TBrushType = modCanvas.BrushType.brTexture    'Texture Fill

   'Create New Open File Dialog Box
   Dim opfTexture As New OpenFileDialog

   opfTexture.Filter = "*.png Files|*.png|*.jpg Files|*.jpg| _
      *.gif Files|*.gif" 'Set The OFD Filter
   'If Something Selected
   If opfTexture.ShowDialog() = DialogResult.OK Then
   'Set Selected FileName To strTextureFile
      strTextureFile = opfTexture.FileName
   Else 'Nothing Selected
      strTextureFile = "Colors.png"    'Default To Colors.png
   End If
End Sub

Private Sub mnuSolid_Click(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles mnuSolid.Click
    TBrushType = modCanvas.BrushType.brSolid    'Solid Brush
End Sub

frmCanvas changes

All of the code changes (to incorporate your new Brushes/Fills) will take place in the picCDraw_MouseUp event. Take a closer look:

Earlier (when coding the new shapes), you only provided for Outline drawings. What you need to do now is to test whether the Full Fill draw tool has been selected, THEN test which Brush the user selected.

In picCDraw_MouseUp, add the following to the Circle's creation code, after the Outline If block:

'Was The Fill Tool Clicked
If blnFillDrawClicked Then
   Select Case TBrushType    'Determine Brush Style
      Case modCanvas.BrushType.brHorizontal    'Horizontal Hatch.
         'ForeColor = Our Selected Color, BackColor =
         'picCDraw's BackColor
         Dim hrzCircleBrush As New HatchBrush(HatchStyle.Horizontal, _
            cColor, picCDraw.BackColor)
         'Fill The Circle
         gCanvas.FillEllipse(hrzCircleBrush, sStartX, sStartY, _
            sEndX - sStartX, sEndY - sStartY)
      Case modCanvas.BrushType.brLinear    'Linear Gradient
         'Starting Point, Ending Point, Color 1 (Main Color)
         'Selected Color, Color 2 = "Fade Into Color" which is White
         Dim linCircleBrush As New LinearGradientBrush(New Point(sStartX, _
            sStartY), New Point(sEndX, sEndY), cColor, picCDraw.BackColor)
         gCanvas.FillEllipse(linCircleBrush, sStartX, sStartY, _
            sEndX - sStartX, sEndY - sStartY)
      Case modCanvas.BrushType.brPath    'Path Gradient
        'Create a New Graphics path, the Shape of the Ellipse
         Dim pCirclePath As New GraphicsPath
         pCirclePath.AddEllipse(sStartX, sStartY, sEndX - sStartX, _
                                sEndY - sStartY)

         ' Use the path to construct a brush.
         Dim pthCircleGrBrush As New PathGradientBrush(pCirclePath)

        'The Main Color To Work With
         pthCircleGrBrush.CenterColor = cColor

         'The Fading Into Color. It is Basically The Same As
         'cColor (The Selected Color), But it's Only 40% Opaque
         Dim pthColors As Color() = {Color.FromArgb(40, cColor.R, _
                                     cColor.G, cColor.B)}
         'Surround The Main Color, With The fading Into Color
         pthCircleGrBrush.SurroundColors = pthColors
         'Fill
         gCanvas.FillEllipse(pthCircleGrBrush, sStartX, sStartY, _
                             sEndX - sStartX, sEndY - sStartY)

      Case modCanvas.BrushType.brPlaid    'Plaid Hatch
        'USe Selected Color
         Dim pldCircleBrush As New HatchBrush(HatchStyle.Plaid, cColor)
         gCanvas.FillEllipse(pldCircleBrush, sStartX, sStartY, _
                             sEndX - sStartX, sEndY - sStartY)
      Case modCanvas.BrushType.brSolid    'Solid Brush
        'Use Selected Color
         Dim sbCircleBrush As New SolidBrush(cColor)
         gCanvas.FillEllipse(sbCircleBrush, sStartX, sStartY, _
                             sEndX - sStartX, sEndY - sStartY)
      Case modCanvas.BrushType.brTexture    'Texture/Picture File
         'Create A New Bitmap From Selected Picture path
         Dim txCircleImage = New Bitmap(strTextureFile)
         'Create The Texture Brush, With Selected Picture
         Dim txCircleBrush As New TextureBrush(txCircleImage)
         'Wrapmode Will Tile The Picture, But Also Flip It On The
         'Y Axis
         txCircleBrush.WrapMode = WrapMode.TileFlipY
         'Fill
         gCanvas.FillEllipse(txCircleBrush, sStartX, sStartY, _
                             sEndX - sStartX, sEndY - sStartY)

      Case modCanvas.BrushType.brVertical    'Vertical Hatch
         'Use Selected Color As Main Color, And picCDraw's
         'backColor As The Secondary Color
         Dim vrtCircleBrush As New HatchBrush(HatchStyle.Vertical, _
            cColor, picCDraw.BackColor)
         'Fill
         gCanvas.FillEllipse(vrtCircleBrush, sStartX, sStartY, _
                             sEndX - sStartX, sEndY - sStartY)
      Case modCanvas.BrushType.brWeave    'Weave Hatch
         'Use Selected Color As Main Color, And picCDraw's backColor
         'As The Secondary Color
         Dim wvCirclebrush As New _
            HatchBrush(HatchStyle.Weave, cColor, picCDraw.BackColor)
         'Fill
         gCanvas.FillEllipse(wvCirclebrush, sStartX, sStartY, _
                             sEndX - sStartX, sEndY - sStartY)
      Case modCanvas.BrushType.brZigZag 'ZigZag Hatch
         'Use Selected Color As Main Color, And picCDraw's backColor
         'As The Secondary Color
         Dim zzCirclebrush As New HatchBrush(HatchStyle.ZigZag, _
            cColor, picCDraw.BackColor)
         'Fill
         gCanvas.FillEllipse(zzCirclebrush, sStartX, sStartY, _
                             sEndX - sStartX, sEndY - sStartY)

   End Select

End If

The following pictures demonstrate all your filling options:

[CircleHatchHorz.png] [CircleHatchVert.png] [CircleZigZag.png] [CircleWeave.png] [CirclePlaid.png]
[CircleLinear.png] [CirclePath.png] [CircleTexture.png] [CircleSolid.png]

At the same place for the Square and Other Shapes, you can simply follow the same principle as you did with the Circle, but of course the Shape creation code will be different.

For convenience sake, I'm including my entire completed picCDraw_MouseUp code; it looks like the following:

Private Sub picCDraw_MouseUp(ByVal sender As System.Object, _
   ByVal e As System.Windows.Forms.MouseEventArgs) _
   Handles picCDraw.MouseUp

   blnMouse = False

   'Create And Initialise Pens To Draw The Particular Outline
   'Shapes With.  Color : Black, Width : 3
   Dim pCirclePen   As New Pen(Color.Black, 3)
   Dim pSquarePen   As New Pen(Color.Black, 3)
   Dim pTrianglePen As New Pen(Color.Black, 3)

   Dim pHeartPen     As New Pen(Color.Black, 3)
   Dim pRoundRectPen As New Pen(Color.Black, 3)
   Dim pLinePen      As New Pen(Color.Black, 3)
   Dim pSpiralPen    As New Pen(Color.Black, 3)
   'Initialise Ending Points Of Shape, Once Mouse Button Is Released
   sEndX = e.X
   sEndY = e.Y

   ptHeartEndPoint.X = e.X 'Heart End Point - X
   ptHeartEndPoint.Y = e.Y 'Heart End Point - Y

   'If The Pencil (Free Hand) Tools Is NOT Selected, I.O.W.,
   'A Shape Tool Is Selected
   If Not blnDrawing Then
      'Set The Images Drawn Thus Far In The Picture Box = To The
      'In - Memory Image Object
      Me.picCDraw.Image = bImage


      ' If we have drawn previously, draw again in that spot
      ' to remove the lines.
      If (ptEnd.X <> -1) Then
         Dim ptCurrent As Point
         ptCurrent.X = e.X
         ptCurrent.Y = e.Y
         CanvasMarquee(ptStart, ptEnd) 'Draw Marquee
      End If
      ' Set flags to know that there is no "previous" line to reverse.
      ptEnd.X = -1
      ptEnd.Y = -1
      ptStart.X = -1
      ptStart.Y = -1


      'A Color Has Not Been Selected, Revert To Default Black Color
      If Not blnColorClicked Then
         cColor = Color.Black
      Else
         'A Color Has Been Selected, Use Selected Color To Draw With
         cColor = cColor
      End If

      'Determine If The Circle Tool Has Been Clicked
      If blnCircleClicked Then

         'Yes, It Has Been Clicked, Set The Pen's Color To
         'Selected Color
         pCirclePen.Color = cColor

         If blnOutlineDrawClicked Then
            'Draw The Circle With The Current Starting, And Ending
            'Values
            gCanvas.DrawEllipse(pCirclePen, sStartX, sStartY, _
               sEndX - sStartX, sEndY - sStartY)
            Dim CirclePath As System.Drawing.Drawing2D.GraphicsPath = _
               New System.Drawing.Drawing2D.GraphicsPath
            CirclePath.AddEllipse(New Rectangle(sStartX, sStartY, _
               (sEndX - sStartX), (sEndY - sStartY)))
            'CFillRegion = New Region(CirclePath)

         End If

         'Was The Fill Tool Clicked
         If blnFillDrawClicked Then
            Select Case TBrushType    'Determine Brush Style
               'Horizontal Hatch.
               Case modCanvas.BrushType.brHorizontal
                  'ForeColor = Our Selected Color,
                  'BackColor = picCDraw's BackColor
                  Dim hrzCircleBrush As New _
                     HatchBrush(HatchStyle.Horizontal, cColor, _
                     picCDraw.BackColor)
                  'Fill The Circle
                  gCanvas.FillEllipse(hrzCircleBrush, sStartX, _
                     sStartY, sEndX - sStartX, sEndY - sStartY)
               Case modCanvas.BrushType.brLinear 'Linear Gradient
                  'Starting Point, Ending Point, Color 1 (Main Color)
                  'Selected Color, Color 2 = "Fade Into Color"
                  'which is White
                  Dim linCircleBrush As New _
                     LinearGradientBrush(New Point(sStartX, sStartY), _
                     New Point(sEndX, sEndY), cColor, _
                     picCDraw.BackColor)
                  gCanvas.FillEllipse(linCircleBrush, sStartX, _
                     sStartY, sEndX - sStartX, sEndY - sStartY)
               Case modCanvas.BrushType.brPath    'Path Gradient
                  'Create a New Graphics path, the Shape of the Ellipse
                  Dim pCirclePath As New GraphicsPath
                  pCirclePath.AddEllipse(sStartX, sStartY, _
                     sEndX - sStartX, sEndY - sStartY)

                  ' Use the path to construct a brush.
                  Dim pthCircleGrBrush As New _
                     PathGradientBrush(pCirclePath)

                  'The Main Color To Work With
                  pthCircleGrBrush.CenterColor = cColor

                  'The Fading Into Color. It is Basically The Same
                  'As cColor (The Selected Color), But it's Only
                  '40% Opaque
                  Dim pthColors As Color() = {Color.FromArgb(40, _
                     cColor.R, cColor.G, cColor.B)}
                  'Surround The Main Color, With The fading Into Color
                  pthCircleGrBrush.SurroundColors = pthColors
                  'Fill
                  gCanvas.FillEllipse(pthCircleGrBrush, sStartX, _
                     sStartY, sEndX - sStartX, sEndY - sStartY)

               Case modCanvas.BrushType.brPlaid    'Plaid Hatch
                  'Use Selected Color
                  Dim pldCircleBrush As New _
                     HatchBrush(HatchStyle.Plaid, cColor)
                  gCanvas.FillEllipse(pldCircleBrush, sStartX, _
                     sStartY, sEndX - sStartX, sEndY - sStartY)
               Case modCanvas.BrushType.brSolid    'Solid Brush
                  'Use Selected Color
                  Dim sbCircleBrush As New SolidBrush(cColor)
                  gCanvas.FillEllipse(sbCircleBrush, sStartX, _
                     sStartY, sEndX - sStartX, sEndY - sStartY)
               'Texture/Picture File
               Case modCanvas.BrushType.brTexture
                  'Create A New Bitmap From Selected Picture path
                  Dim txCircleImage = New Bitmap(strTextureFile)
                  'Create The Texture Brush, With Selected Picture
                  Dim txCircleBrush As New TextureBrush(txCircleImage)
                  'Wrapmode Will Tile The Picture, But Also Flip It
                  'On The Y Axis
                  txCircleBrush.WrapMode = WrapMode.TileFlipY
                  'Fill
                  gCanvas.FillEllipse(txCircleBrush, sStartX, _
                     sStartY, sEndX - sStartX, sEndY - sStartY)

               Case modCanvas.BrushType.brVertical    'Vertical Hatch
                  'Use Selected Color As Main Color, And picCDraw's
                  'backColor As The Secondary Color
                  Dim vrtCircleBrush As New _
                     HatchBrush(HatchStyle.Vertical, cColor, _
                                picCDraw.BackColor)
                  'Fill
                  gCanvas.FillEllipse(vrtCircleBrush, sStartX, _
                     sStartY, sEndX - sStartX, sEndY - sStartY)
               Case modCanvas.BrushType.brWeave    'Weave Hatch
                  'Use Selected Color As Main Color, And picCDraw's
                  'backColor As The Secondary Color
                  Dim wvCirclebrush As New HatchBrush(HatchStyle.Weave, _
                     cColor, picCDraw.BackColor)
                  'Fill
                  gCanvas.FillEllipse(wvCirclebrush, sStartX, _
                     sStartY, sEndX - sStartX, sEndY - sStartY)
               Case modCanvas.BrushType.brZigZag    'ZigZag Hatch
                  'Use Selected Color As Main Color, And picCDraw's
                  'backColor As The Secondary Color
                  Dim zzCirclebrush As New _
                     HatchBrush(HatchStyle.ZigZag, cColor, _
                                picCDraw.BackColor)
                  'Fill
                  gCanvas.FillEllipse(zzCirclebrush, sStartX, _
                     sStartY, sEndX - sStartX, sEndY - sStartY)

            End Select

         End If

      End If

      'Determine If The Square Tool Has Been Clicked
      If blnSquareClicked Then

         'Yes, It Has Been Clicked, Set The Pen's Color To Selected Color
         pSquarePen.Color = cColor

         Dim SquareX As Integer      = Math.Min(sStartX, sEndX)
         Dim SquareY As Integer      = Math.Min(sStartY, sEndY)
         Dim SquareWidth As Integer  = Math.Abs(sStartX - sEndX)
         Dim SquareHeight As Integer = Math.Abs(sStartY - sEndY)

         If blnOutlineDrawClicked Then
            'Draw The Square With The Current Starting And Ending
            'Values

            gCanvas.DrawRectangle(pSquarePen, SquareX, SquareY, _
               SquareWidth, SquareHeight)

         End If

         'Was The Fill Tool Clicked
         If blnFillDrawClicked Then
            'Yes, Set The Brush Color To The Selected Color

            Select Case TBrushType    'Determine Brush Style
               'Horizontal Hatch.
               Case modCanvas.BrushType.brHorizontal
                  'ForeColor = Our Selected Color,
                  'BackColor = picCDraw's BackColor
                  Dim hrzSquareBrush As New _
                     HatchBrush(HatchStyle.Horizontal, cColor, _
                                picCDraw.BackColor)
                  'Fill
                  gCanvas.FillRectangle(hrzSquareBrush, SquareX, _
                     SquareY, SquareWidth, SquareHeight)
               Case modCanvas.BrushType.brLinear    'Linear Gradient
                  'Starting Point, Ending Point, Color 1
                  '(Main Color) Selected Color, Color 2 =
                  '"Fade Into Color" which is White
                  Dim linSquareBrush As New _
                     LinearGradientBrush(New Point(sStartX, sStartY), _
                     New Point(sEndX, sEndY), cColor, _
                     picCDraw.BackColor)
                  'Fill
                  gCanvas.FillRectangle(linSquareBrush, SquareX, _
                     SquareY, SquareWidth, SquareHeight)
               Case modCanvas.BrushType.brPath    'Path Gradient
                  'Create a New Graphics path, the Shape of the Square
                  Dim pSquarePath As New GraphicsPath
                  pSquarePath.AddRectangle(New Rectangle(SquareX, _
                     SquareY, SquareWidth, SquareHeight))

                  ' Use the path to construct a brush.
                  Dim pthSquareGrBrush As New _
                     PathGradientBrush(pSquarePath)
                  'The Main Color To Work With
                  pthSquareGrBrush.CenterColor = cColor
                  'The Fading Into Color. It is Basically The Same
                  'As cColor (The Selected Color), But it's Only
                  '40% Opaque
                  Dim pthColors As Color() = {Color.FromArgb(40, _
                     cColor.R, cColor.G, cColor.B)}
                  'Surround The Main Color, With The fading Into Color
                  pthSquareGrBrush.SurroundColors = pthColors
                  'Fill
                  gCanvas.FillRectangle(pthSquareGrBrush, SquareX, _
                     SquareY, SquareWidth, SquareHeight)
               Case modCanvas.BrushType.brPlaid    'Plaid Hatch
                  'Use Selected Color
                  Dim pldSquareBrush As New _
                     HatchBrush(HatchStyle.Plaid, cColor)
                  'Fill
                  gCanvas.FillRectangle(pldSquareBrush, SquareX, _
                     SquareY, SquareWidth, SquareHeight)
               Case modCanvas.BrushType.brSolid    'Solid Brush
                  'Use Selected Color
                  Dim sbSquareBrush As New SolidBrush(cColor)
                  'Fill
                  gCanvas.FillRectangle(sbSquareBrush, SquareX, _
                     SquareY, SquareWidth, SquareHeight)
               'Texture/Picture File
               Case modCanvas.BrushType.brTexture
                  'Create A New Bitmap From Selected Picture path
                  Dim txSquareImage = New Bitmap(strTextureFile)
                  'Create The Texture Brush, With Selected Picture
                  Dim txSquareBrush As New TextureBrush(txSquareImage)
                  'Wrapmode Will Tile The Picture, But Also Flip It
                  'On The Y Axis
                  txSquareBrush.WrapMode = WrapMode.TileFlipY
                  'Fill
                  gCanvas.FillRectangle(txSquareBrush, SquareX, _
                     SquareY, SquareWidth, SquareHeight)
               Case modCanvas.BrushType.brVertical    'Vertical Hatch
                  'Use Selected Color As Main Color, And picCDraw's
                  'backColor As The Secondary Color
                  Dim vrtSquareBrush As New _
                     HatchBrush(HatchStyle.Vertical, cColor, _
                                picCDraw.BackColor)
                  'Fill
                  gCanvas.FillRectangle(vrtSquareBrush, SquareX, _
                     SquareY, SquareWidth, SquareHeight)
               Case modCanvas.BrushType.brWeave    'Weave Hatch
                  'Use Selected Color As Main Color, And picCDraw's
                  'backColor As The Secondary Color
                  Dim wvSquarebrush As New HatchBrush(HatchStyle.Weave, _
                     cColor, picCDraw.BackColor)
                  'Fill
                  gCanvas.FillRectangle(wvSquarebrush, SquareX, _
                     SquareY, SquareWidth, SquareHeight)
               Case modCanvas.BrushType.brZigZag    'ZigZag hatch
                  'Use Selected Color As Main Color, And picCDraw's
                  'backColor As The Secondary Color
                  Dim zzSquarebrush As New _
                     HatchBrush(HatchStyle.ZigZag, cColor, _
                                picCDraw.BackColor)
                  'Fill
                  gCanvas.FillRectangle(zzSquarebrush, SquareX, _
                     SquareY, SquareWidth, SquareHeight)
            End Select

         End If
      End If

      'Determine If The Triangle Tool Has Been Clicked
      If blnTraingleClicked Then

         'Yes, It Has Been Clicked, Set The Pen's Color To Selected
         'Color
         pTrianglePen.Color = cColor


         If blnOutlineDrawClicked Then
            Dim p(2) As Point
            p(0) = New Point(sStartX - 10, sStartY)
            p(1) = New Point(sEndX, sEndY)
            p(2) = New Point(sStartX + 50, sStartY - 50)
            gCanvas.DrawPolygon(pTrianglePen, p)

         End If


         'Was The Fill Tool Clicked
         If blnFillDrawClicked Then
            'Use A Point Array, In Order To Be Able To Use The
            'FillPolygon Method - To Fill The Triangle
            Dim pTPoints As Point() = { _
               New Point(sStartX - 10, sStartY), _
               New Point(sEndX, sEndY), _
               New Point(sStartX + 50, sStartY - 50)}

                  Select Case TBrushType
                     Case modCanvas.BrushType.brHorizontal
                        Dim hrzTriangleBrush As New _
                           HatchBrush(HatchStyle.Horizontal, cColor, _
                                      picCDraw.BackColor)

                        gCanvas.FillPolygon(hrzTriangleBrush, pTPoints)
                     Case modCanvas.BrushType.brLinear
                        Dim linTriangleBrush As New _
                           LinearGradientBrush(New Point(sStartX, sStartY), _
                              New Point(sEndX, sEndY), cColor, _
                              picCDraw.BackColor)
                        gCanvas.FillPolygon(linTriangleBrush, pTPoints)
                     Case modCanvas.BrushType.brPath
                        Dim pTrianglePath As New GraphicsPath

                        pTrianglePath.AddPolygon(pTPoints)

                        ' Use the path to construct a brush.
                        Dim pthTriangleGrBrush As New _
                           PathGradientBrush(pTrianglePath)

                        pthTriangleGrBrush.CenterColor = cColor


                        Dim pthColors As Color() = _
                           {Color.FromArgb(40, cColor.R, cColor.G, _
                                           cColor.B)}
                        pthTriangleGrBrush.SurroundColors = pthColors

                        gCanvas.FillPolygon(pthTriangleGrBrush, pTPoints)

                     Case modCanvas.BrushType.brPlaid
                        Dim pldTriangleBrush As New _
                           HatchBrush(HatchStyle.Plaid, cColor)
                        gCanvas.FillPolygon(pldTriangleBrush, pTPoints)
                     Case modCanvas.BrushType.brSolid
                        Dim sbTriangleBrush As New SolidBrush(cColor)
                        gCanvas.FillPolygon(sbTriangleBrush, pTPoints)
                     Case modCanvas.BrushType.brTexture
                        Dim txTriangleImage = New Bitmap(strTextureFile)
                        Dim txTriangleBrush As New _
                           TextureBrush(txTriangleImage)
                        txTriangleBrush.WrapMode = WrapMode.TileFlipY
                        gCanvas.FillPolygon(txTriangleBrush, pTPoints)

                     Case modCanvas.BrushType.brVertical
                        Dim vrtTriangleBrush As New _
                           HatchBrush(HatchStyle.Vertical, cColor, _
                                      picCDraw.BackColor)
                        gCanvas.FillPolygon(vrtTriangleBrush, pTPoints)
                     Case modCanvas.BrushType.brWeave
                        Dim wvTrianglebrush As New _
                           HatchBrush(HatchStyle.Weave, cColor, _
                                      picCDraw.BackColor)
                        gCanvas.FillPolygon(wvTrianglebrush, pTPoints)
                     Case modCanvas.BrushType.brZigZag
                        Dim zzTrianglebrush As New _
                           HatchBrush(HatchStyle.ZigZag, cColor, _
                                      picCDraw.BackColor)
                        gCanvas.FillPolygon(zzTrianglebrush, pTPoints)
                  End Select

         End If
      End If

      'Determine If The Heart Tool Has Been Clicked
      If blnHeartClicked Then

         'Yes, It Has Been Clicked, Set The Pen's Color To Selected
         'Color
         pHeartPen.Color = cColor

         If blnOutlineDrawClicked Then
            'Draw The Heart With The Current Starting And
            'Ending Values
            Dim HeartWidth As Integer = _
               Math.Max(ptHeartEndPoint.X, ptHeartStartPoint.X) - _
               Math.Min(ptHeartEndPoint.X, ptHeartStartPoint.X)
            Dim HeartHeight As Integer = _
               Math.Max(ptHeartEndPoint.Y, ptHeartStartPoint.Y) - _
               Math.Min(ptHeartEndPoint.Y, ptHeartStartPoint.Y)
            Dim HeartAvg As Integer = CInt((HeartWidth + HeartHeight) / 2)
            If HeartAvg > 0 Then
               Dim HeartTopLeft As Point = ptHeartStartPoint
               If ptHeartEndPoint.X < ptHeartStartPoint.X Then
                  HeartTopLeft.X = ptHeartEndPoint.X
               End If
               If ptHeartEndPoint.Y < ptHeartStartPoint.Y Then
                  HeartTopLeft.Y = ptHeartEndPoint.Y
               End If

               Dim HeartRadius As Integer = CInt(HeartAvg / 2)
               Dim HeartTopLeftSquare As New _
                  Rectangle(HeartTopLeft.X, HeartTopLeft.Y, _
                            HeartRadius, HeartRadius)
               Dim HeartTopRightSquare As New _
                  Rectangle(HeartTopLeft.X + HeartRadius, _
                            HeartTopLeft.Y, HeartRadius, HeartRadius)

               gCanvas.DrawArc(pHeartPen, HeartTopLeftSquare, _
                               135.0F, 225.0F)
               gCanvas.DrawArc(pHeartPen, HeartTopRightSquare, _
                               180.0F, 225.0F)
               gCanvas.DrawLine(pHeartPen, CInt(HeartTopLeft.X + _
                  HeartRadius / 2 - Math.Sin(45 / 180 * Math.PI) * _
                  HeartRadius / 2), _
                  CInt(HeartTopLeft.Y + HeartRadius / 2 + _
                  Math.Sin(45 / 180 * Math.PI) * HeartRadius / 2), _
                  HeartTopLeft.X + HeartRadius, HeartTopLeft.Y + HeartAvg)
               gCanvas.DrawLine(pHeartPen, CInt(HeartTopLeft.X + _
                  HeartAvg - (1 - Math.Sin(45 / 180 * Math.PI)) * _
                  HeartRadius / 2), _
                  CInt(HeartTopLeft.Y + HeartRadius / 2 + _
                     Math.Sin(45 / 180 * Math.PI) * HeartRadius / 2), _
                     HeartTopLeft.X + HeartRadius, HeartTopLeft.Y + _
                     HeartAvg)
            End If
         End If


         'Was The Fill Tool Clicked
         If blnFillDrawClicked Then
            'Yes, Set The Brush Color To The Selected Color
            Dim pHPath As New System.Drawing.Drawing2D.GraphicsPath
            Dim HeartWidth As Integer = _
               Math.Max(ptHeartEndPoint.X, ptHeartStartPoint.X) - _
               Math.Min(ptHeartEndPoint.X, ptHeartStartPoint.X)
            Dim HeartHeight As Integer = _
            Math.Max(ptHeartEndPoint.Y, ptHeartStartPoint.Y) - _
            Math.Min(ptHeartEndPoint.Y, ptHeartStartPoint.Y)
            Dim HeartAvg As Integer = CInt((HeartWidth + _
               HeartHeight) / 2)

            If HeartAvg > 0 Then
               Dim HeartTopLeft As Point = ptHeartStartPoint
               If ptHeartEndPoint.X < ptHeartStartPoint.X Then
                  HeartTopLeft.X = ptHeartEndPoint.X
               End If
               If ptHeartEndPoint.Y < ptHeartStartPoint.Y Then
                  HeartTopLeft.Y = ptHeartEndPoint.Y
               End If

               Dim HeartRadius As Integer = CInt(HeartAvg / 2)

               pHPath.AddArc(HeartTopLeft.X, HeartTopLeft.Y, _
                  HeartRadius, HeartRadius, 135.0F, 225.0F)
               pHPath.AddArc(HeartTopLeft.X + HeartRadius, _
                  HeartTopLeft.Y, HeartRadius, HeartRadius, _
                  180.0F, 225.0F)
               pHPath.AddLine(CInt(HeartTopLeft.X + HeartAvg - _
                  (1 - Math.Sin(45 / 180 * Math.PI)) * HeartRadius / 2), _
                  CInt(HeartTopLeft.Y + HeartRadius / 2 + _
                  Math.Sin(45 / 180 * Math.PI) * HeartRadius / 2), _
                  HeartTopLeft.X + HeartRadius, HeartTopLeft.Y + _
                  HeartAvg)

               pHPath.AddLine(CInt(HeartTopLeft.X + HeartRadius / 2 - _
                  Math.Sin(45 / 180 * Math.PI) * HeartRadius / 2), _
                  CInt(HeartTopLeft.Y + HeartRadius / 2 + _
                  Math.Sin(45 / 180 * Math.PI) * HeartRadius / 2), _
                  HeartTopLeft.X + HeartRadius, HeartTopLeft.Y + _
                  HeartAvg)

               Select Case TBrushType
                  Case modCanvas.BrushType.brHorizontal
                     Dim hrzHeartBrush As New _
                     HatchBrush(HatchStyle.Horizontal, cColor, _
                                picCDraw.BackColor)

                     gCanvas.FillPath(hrzHeartBrush, pHPath)
                  Case modCanvas.BrushType.brLinear
                     Dim linHeartBrush As New _
                        LinearGradientBrush(New Point(sStartX, sStartY), _
                        New Point(sEndX, sEndY), cColor, _
                        picCDraw.BackColor)
                     gCanvas.FillPath(linHeartBrush, pHPath)
                  Case modCanvas.BrushType.brPath

                     ' Use the path to construct a brush.
                     Dim pthHeartGrBrush As New PathGradientBrush(pHPath)

                     pthHeartGrBrush.CenterColor = cColor


                     Dim pthColors As Color() = _
                        {Color.FromArgb(40, cColor.R, cColor.G, _
                                        cColor.B)}
                     pthHeartGrBrush.SurroundColors = pthColors

                     gCanvas.FillPath(pthHeartGrBrush, pHPath)

                  Case modCanvas.BrushType.brPlaid
                     Dim pldHeartBrush As New _
                        HatchBrush(HatchStyle.Plaid, cColor)
                     gCanvas.FillPath(pldHeartBrush, pHPath)
                  Case modCanvas.BrushType.brSolid
                     Dim sbHeartBrush As New SolidBrush(cColor)
                     gCanvas.FillPath(sbHeartBrush, pHPath)
                  Case modCanvas.BrushType.brTexture
                     Dim txHeartImage = New Bitmap(strTextureFile)
                     Dim txHeartBrush As New TextureBrush(txHeartImage)
                     txHeartBrush.WrapMode = WrapMode.TileFlipY
                     gCanvas.FillPath(txHeartBrush, pHPath)

                  Case modCanvas.BrushType.brVertical
                     Dim vrtHeartBrush As New _
                        HatchBrush(HatchStyle.Vertical, cColor, _
                                   picCDraw.BackColor)
                     gCanvas.FillPath(vrtHeartBrush, pHPath)
                  Case modCanvas.BrushType.brWeave
                     Dim wvHeartbrush As New _
                        HatchBrush(HatchStyle.Weave, cColor, _
                                   picCDraw.BackColor)
                     gCanvas.FillPath(wvHeartbrush, pHPath)
                  Case modCanvas.BrushType.brZigZag
                     Dim zzHeartbrush As New _
                        HatchBrush(HatchStyle.ZigZag, cColor, _
                                   picCDraw.BackColor)
                     gCanvas.FillPath(zzHeartbrush, pHPath)
               End Select


            End If
         End If

      End If
      'Determine If The RoundRect Tool Has Been Clicked
      If blnRoundRectClicked Then

         'Yes, It Has Been Clicked, Set The Pen's Color To Selected
         'Color
         pRoundRectPen.Color = cColor

         If blnOutlineDrawClicked Then
            'Draw The RoundRect With The Current Starting And
            'Ending Values
            Dim RectX As Integer      = Math.Min(sStartX, sEndX)
            Dim RectY As Integer      = Math.Min(sStartY, sEndY)
            Dim RectWidth As Integer  = Math.Abs(sStartX - sEndX)
            Dim RectHeight As Integer = Math.Abs(sStartY - sEndY)
            Dim RectRadius As Integer = 15
            Dim pRRPath As New System.Drawing.Drawing2D.GraphicsPath

               If RectRadius > Rectwidth / 2 OrElse RectRadius > _
                  RectHeight / 2 Then RectRadius = RectHeight / 2

            pRRPath.StartFigure()

            pRRPath.AddArc(RectX, RectY, RectRadius * 2, _
                           RectRadius * 2, 180, 90)
            pRRPath.AddArc(RectX + RectWidth - RectRadius * 2, _
                           RectY, RectRadius * 2, _
                           RectRadius * 2, 270, 90)
            pRRPath.AddArc(RectX + RectWidth - RectRadius * 2, _
                           RectY + RectHeight - RectRadius * 2, _
                           RectRadius * 2, RectRadius * 2, 0, 90)
            pRRPath.AddArc(RectX, RectY + RectHeight - RectRadius * 2, _
                           RectRadius * 2, RectRadius * 2, 90, 90)
            pRRPath.CloseFigure()

            gCanvas.DrawPath(pRoundRectPen, pRRPath)
         End If


         'Was The Fill Tool Clicked
         If blnFillDrawClicked Then
            'Yes, Set The Brush Color To The Selected Color

            Dim RectX As Integer      = Math.Min(sStartX, sEndX)
            Dim RectY As Integer      = Math.Min(sStartY, sEndY)
            Dim RectWidth As Integer  = Math.Abs(sStartX - sEndX)
            Dim RectHeight As Integer = Math.Abs(sStartY - sEndY)
            Dim RectRadius As Integer = 15

            'Fill The Shape Being Drawn
            Dim pRRPath As New System.Drawing.Drawing2D.GraphicsPath

            If RectRadius > Rectwidth / 2 OrElse RectRadius > _
               RectHeight / 2 Then RectRadius = RectHeight / 2

            pRRPath.StartFigure()

            pRRPath.AddArc(RectX, RectY, RectRadius * 2, _
                           RectRadius * 2, 180, 90)
            pRRPath.AddArc(RectX + RectWidth - RectRadius * 2, _
                           RectY, RectRadius * 2, _
                           RectRadius * 2, 270, 90)
            pRRPath.AddArc(RectX + RectWidth - RectRadius * 2, _
                           RectY + RectHeight - RectRadius * 2, _
                           RectRadius * 2, RectRadius * 2, 0, 90)
            pRRPath.AddArc(RectX, RectY + RectHeight - RectRadius * 2, _
                           RectRadius * 2, RectRadius * 2, 90, 90)
            pRRPath.CloseFigure()

            Select Case TBrushType
               Case modCanvas.BrushType.brHorizontal
                  Dim hrzRoundRectBrush As New _
                     HatchBrush(HatchStyle.Horizontal, cColor, _
                                picCDraw.BackColor)

                  gCanvas.FillPath(hrzRoundRectBrush, pRRPath)
               Case modCanvas.BrushType.brLinear
                  Dim linRoundRectBrush As New _
                     LinearGradientBrush(New Point(sStartX, sStartY), _
                     New Point(sEndX, sEndY), cColor, picCDraw.BackColor)
                  gCanvas.FillPath(linRoundRectBrush, pRRPath)
                Case modCanvas.BrushType.brPath

                  ' Use the path to construct a brush.
                  Dim pthRoundRectGrBrush As New _
                     PathGradientBrush(pRRPath)

                  pthRoundRectGrBrush.CenterColor = cColor


                  Dim pthColors As Color() = _
                     {Color.FromArgb(40, cColor.R, cColor.G, cColor.B)}
                  pthRoundRectGrBrush.SurroundColors = pthColors

                  gCanvas.FillPath(pthRoundRectGrBrush, pRRPath)

               Case modCanvas.BrushType.brPlaid
                  Dim pldRoundRectBrush As New _
                     HatchBrush(HatchStyle.Plaid, cColor)
                  gCanvas.FillPath(pldRoundRectBrush, pRRPath)
               Case modCanvas.BrushType.brSolid
                  Dim sbRoundRectBrush As New SolidBrush(cColor)
                  gCanvas.FillPath(sbRoundRectBrush, pRRPath)
               Case modCanvas.BrushType.brTexture
                  Dim txRoundRectImage = New Bitmap(strTextureFile)
                  Dim txRoundRectBrush As New _
                     TextureBrush(txRoundRectImage)
                  txRoundRectBrush.WrapMode = WrapMode.TileFlipY
                  gCanvas.FillPath(txRoundRectBrush, pRRPath)

                Case modCanvas.BrushType.brVertical
                  Dim vrtRoundRectBrush As New _
                     HatchBrush(HatchStyle.Vertical, cColor, _
                                picCDraw.BackColor)
                  gCanvas.FillPath(vrtRoundRectBrush, pRRPath)
               Case modCanvas.BrushType.brWeave
                  Dim wvRoundRectbrush As New _
                     HatchBrush(HatchStyle.Weave, cColor, _
                                picCDraw.BackColor)
                  gCanvas.FillPath(wvRoundRectbrush, pRRPath)
               Case modCanvas.BrushType.brZigZag
                  Dim zzRoundRectbrush As New _
                     HatchBrush(HatchStyle.ZigZag, cColor, _
                                picCDraw.BackColor)
                  gCanvas.FillPath(zzRoundRectbrush, pRRPath)
            End Select


         End If

      End If

      'Determine If The Line Tool Has Been Clicked
      If blnLineClicked Then

         'Yes, It Has Been Clicked, Set The Pen's Color To Selected
         'Color
         pLinePen.Color = cColor


         If blnOutlineDrawClicked Then
            'Draw The Line With The Current Starting, And Ending Values
            gCanvas.DrawLine(pLinePen, sStartX, sStartY, sEndX, sEndY)
         End If

      End If


      'Determine If The Spiral Tool Has Been Clicked
      If blnSpiralClicked Then

         'Yes, It Has Been Clicked, Set The Pen's Color To Selected
         'Color
         pSpiralPen.Color = cColor

         If blnOutlineDrawClicked Then
            'Draw The Spiral With The Current Starting And Ending
            'Values
            Dim SpiralPI As Double = 3.14159265358979
            'orientation
            Dim SpiralOrientation As Double = 3.356987413
            Dim SpiralWidth As Integer
            Dim SpiralX As Integer
            Dim SpiralHeight As Integer
            Dim SpiralY As Integer

            'location of spiral
            Dim SpiralRect As New Rectangle(sStartX, sStartY, _
                                            sEndX, sEndY)

            SpiralWidth  = SpiralRect.Width
            SpiralHeight = SpiralRect.Height

            Dim SpiralAAng As Single
            Dim SpiralBAng As Single
            Dim SpiralLoop As Long
            Dim SpiralAngle As Double

            SpiralAAng = 0.15
            SpiralBAng = 0.15

            For SpiralLoop = 0 To 8000    ' size of spiral
               SpiralAngle = (SpiralPI / 720) * SpiralLoop
               SpiralX = SpiralWidth + (SpiralAAng * _
                  (System.Math.Cos(SpiralAngle)) * _
                  (SpiralOrientation ^ (SpiralBAng * SpiralAngle)))
               SpiralY = SpiralHeight - (SpiralAAng * _
                  (System.Math.Sin(SpiralAngle)) * _
                  (SpiralOrientation ^ (SpiralBAng * SpiralAngle)))
               'the higher the + number the thicker the lines
               gCanvas.DrawLine(pSpiralPen, SpiralX, SpiralY, _
                                SpiralX + 5, SpiralY + 5)
               Next SpiralLoop
         End If

      End If
   Else

   End If

   'If The User Is Not Erasing, Refresh The Picturebox's Display
   If Not blnErasing Then
         picCDraw.Refresh()
   End If

   'Dispose Of All Pens
   pCirclePen.Dispose()
   pSquarePen.Dispose()
   pTrianglePen.Dispose()

   pHeartPen.Dispose()
   pRoundRectPen.Dispose()
   pLinePen.Dispose()
   pSpiralPen.Dispose()

   'Dispose Of All Brushes
   '     sbCircleBrush.Dispose()
   'sbSquareBrush.Dispose()
   '    sbTriangleBrush.Dispose()

   '   sbHeartBrush.Dispose()
   '    sbRoundRectBrush.Dispose()
   '    sbSpiralBrush.Dispose()

   'Set The Drawing And Erasing Flags To False, Because The Mouse
   'Button Is Realease - We're Not drawing/Erasing ANymore
   blnDrawing = False
   blnErasing = False

   End Sub

The above code looks really complicated, but you have covered most of it already. What you did was just to determine which Fill tool has been selected, then, fill the shapes accordingly as you draw them. The above code will also only work when Full Fill mode has been selected. As you can see, the line and the Spiral objects doesn't need any filling, as they only work in Outline Draw mode.

If you were to Build and run Canvas, you should be able to select any Fill Style, then, if FullFillDraw mode is selected, you will be able to fill the current object being drawn accordingly.

Get Adventurous!

What you have done thus far has been fun. You now have a "almost" complete drawing application that functions like most other drawing applications—especially where colors and shapes are concerned, but, still, something is missing! As I've mentioned in the previous two parts of this article series: "Canvas is not an ordinary Drawing application." I thought for days about what more to add to Canvas, so that I can say: Part 3 is entirely complete. Eventually, I realised that the Draw Then Fill functionality is quite limited because it only allow you to fill the shapes with solid colors. Wouldn't it be nice to fill these shapes with our Hatch Brushes as well? Yes it would! This will be a lot of work!

Creating Your Own Drawing Application in Visual Basic.NET, Part 3

Filling the Drawn Shapes with the Various Hatch Styles

Start by creating a Bitmap class that will allow you to create an in-memory Bitmap (a copy) and enable you to gain write access to the image bits of the copy. This class has two main methods, named CanvasLockBmp and CanvasUnlockBmp. CanvasLockBmp allows you to gain access to the image bits of the copy. CanvasUnlockBmp frees all the memory resources.

To create this class, follow these steps:

  • From the main menu, select Project, Add Class
  • Name the class CBitmapBytes
  • Add the following 2 Imports:
  • Imports System.Drawing.Imaging
    Imports System.Runtime.InteropServices
    
  • Add the following constants and variable declarations in CBitmapBytes:
  • ' Picture's Byte Data.
    Public CImgBytes() As Byte    'Image Bytes
    Public CRowSizeBytes As Integer
    
    ' 4 bytes/pixel.
    Public Const CPixSizeBytes As Integer = 4
    ' 32 bits per pixel.
    Public Const CPixSizeBits As Integer  = CPixSizeBytes * 8
    
    ' A reference to the Bitmap.
    Private CBmp As Bitmap
    
    ' Bitmap Data.
    Private CBmpData As BitmapData
    
  • Add the constructor:
  • ' Save a Reference To The Bitmap.
    Public Sub New(ByVal bmp As Bitmap)
     CBmp = bmp
    End Sub
    
  • Add the CanvasLockBmp method:
  • Public Sub CanvasLockBmp()
       'Lock the bitmap data.using LockBits method to gain write
       'access to the image bits of the copy
       'The LockBits function first creates a Bitmap object that
       'has the pixel format of PixelFormat.Format32bppArgb because
       'that is the object that is saved to create the picture file
       'with nColors
    
       'The stride is the width of a single row of pixels
       '(a scan line), rounded up to a four-byte boundary.
       'The stride is always greater than or equal to the actual
       'pixel width.
       'If the stride is positive, the bitmap is top-down.
       'If the stride is negative, the bitmap is bottom-up.
    
       Dim CRectBounds As Rectangle = New Rectangle( _
           0, 0, CBmp.Width, CBmp.Height)
       CBmpData = CBmp.LockBits(CRectBounds, _
           Imaging.ImageLockMode.ReadWrite, _
           Imaging.PixelFormat.Format32bppArgb)
       CRowSizeBytes = CBmpData.Stride
    
       ' Allocate room for the data.
       Dim CTotSize As Integer = CBmpData.Stride * CBmpData.Height
       ReDim CImgBytes(CTotSize)
    
       ' Copy the data into the ImageBytes array.
       'transfer the contents of this Byte buffer to the memory
       'buffer that is referenced by the Scan0 IntPtr of the
       'BitmapData object
       Marshal.Copy(CBmpData.Scan0, CImgBytes, _
           0, CTotSize)
    End Sub
    
  • Add the CanvasUnlockBmp method:
  • ' Copy the data back into the Bitmap
    ' and release resources.
    Public Sub CanvasUnlockBmp(Optional ByVal CSaveRes As _
                               Boolean = True)
       If CSaveRes Then
            Dim CTotSize As Integer = CBmpData.Stride * _
               CBmpData.Height
            Marshal.Copy(CImgBytes, 0, _
                CBmpData.Scan0, CTotSize)
       End If
    
       ' Unlock the bitmap using UnLockBits to release the
       ' image bits
       CBmp.UnlockBits(CBmpData)
    
       ' Release resources.
       CImgBytes = Nothing
       CBmpData  = Nothing
    End Sub
    

The next big step is to add another class to create the actual Hatch brush fill region. You will do the actual coloring here. The problem you have is that a Hatch brush is not only one color; it is comprised of two colors, a Forecolor and a Backcolor. Another problem is the different Hatch Styles; you need to ensure that you get the right effect when filling your objects. The CanvasHatchRegion class creates a region that would be the current shape/object you are filling. The CanvasHatchRegion class uses the CanvasSetCol method to do the actual coloring. The CTotHatchReg parameter is the region that gets created (that is, what will be filled). The CSCBmpBytes parameter is an implementation of the CBitmapBytes class. The CSNewR, CSNewG, and CSNewB parameters will be the RGB (Red, Green, and Blue) color values you are going to fill the region with.

  • From the main menu, select Project, Add Class.
  • Name the class CanvasHatchRegion.
  • Add the following variables to keep track of where to start and where to end:
  • Public CXMin As Integer    'Start X
    Public CXMax As Integer    'End X
    Public CY As Integer       'Y
    
  • Add the constructor:
  • Public Sub New(ByVal CNewXMin As Integer, _
                   ByVal CNewXMax As Integer, _
                   ByVal CNewY As Integer)
       CXMin = CNewXMin
       CXMax = CNewXMax
       CY    = CNewY
    End Sub
    
  • Add the CanvasSetCol method to do the actual coloring:
  • ' Color The Pixels
    Public Sub CanvasSetCol(ByRef CTotHatchReg As Region, _
                            ByVal CSCBmpBytes As CBitmapBytes, _
                            ByVal CSNewR As Byte, _
                            ByVal CSNewG As Byte, _
                            ByVal CSNewB As Byte)
       Dim CSCPixel As Integer = CY * CSCBmpBytes.CRowSizeBytes + _
          CXMin * CSCBmpBytes.CPixSizeBytes
    
       For CSCx As Integer = CXMin To CXMax
          CSCBmpBytes.CImgBytes(CSCPixel + 2) = CSNewR 'R
          CSCBmpBytes.CImgBytes(CSCPixel + 1) = CSNewG 'G
          CSCBmpBytes.CImgBytes(CSCPixel)     = CSNewB 'B
    
          CSCPixel += CSCBmpBytes.CPixSizeBytes
       Next CSCx
    
       ' Add this rectangle to the total region.
       Dim CSCRect As New Rectangle(CXMin, CY, CXMax - CXMin + 1, 1)
    
       If CTotHatchReg Is Nothing Then
          CTotHatchReg = New Region(CSCRect)
       Else
       'Updates this Region to the union of itself and the
        'specified GraphicsPath.
          CTotHatchReg.Union(CSCRect)
       End If
    End Sub
    

What Is Still Needed at This Point?

You still need a method to determine what the current pixel color is. This is actually in a sense similar to the CanvasGetSetPixel method you used earlier (when you filled with only solids). The CanvasPointColor method will serve the same purpose. You then need to create a method to calculate the actual positions of the pixels that need coloring, and color them accordingly. The CanvasFloodHatch method does just that and it sets the pixel colors one by one, with the use of the stack where it pushes each new color to the top of the stack. Lastly, after all this information has been obtained, you need a way to return a CanvasHatchRegion object, which does the actual coloring.

To achieve this, open the code window for frmCanvas, and the next three methods directly after the CanvasGetSetPixel method and directly above the CanvasMarquee method (I chose to include these methods here, but feel free to add them anywhere in frmCanvas—just as long as you add them!)

' See if this point has the target color.
Private Function CanvasPointColor(ByVal CPCBmpBytes As CBitmapBytes, _
   ByVal CPCx As Integer, ByVal CPCy As Integer, _
   ByVal CPCr As Byte, ByVal CPCg As Byte, ByVal CPCb As Byte) _
   As Boolean
   Dim CPCPixel As Integer = CPCy * CPCBmpBytes.CRowSizeBytes + _
      CPCx * CPCBmpBytes.CPixSizeBytes

   Dim CNewR As Byte = CPCBmpBytes.CImgBytes(CPCPixel + 2) 'R
   Dim CNewG As Byte = CPCBmpBytes.CImgBytes(CPCPixel + 1) 'G
   Dim CNewB As Byte = CPCBmpBytes.CImgBytes(CPCPixel) 'B

   'return the R, G, B colors
   Return (CNewR = CPCr) AndAlso (CNewG = CPCg) AndAlso (CNewB = CPCb)

End Function

' Flood the area at this point.
Private Sub CanvasFloodHatch(ByVal CFHBmp As Bitmap, _
   ByVal CFHx As Integer, ByVal CFHy As Integer, _
   ByVal CFHBorder As Color, ByVal CFHg As Graphics, _
   ByVal CFHFloodBrush As Brush)
   ' Get the old and new colors' components.
   Dim CFHOldR As Byte = CFHBmp.GetPixel(CFHx, CFHy).R
   Dim CFHOldG As Byte = CFHBmp.GetPixel(CFHx, CFHy).G
   Dim CFHOldB As Byte = CFHBmp.GetPixel(CFHx, CFHy).B

   Dim CFHBorderR As Byte = CFHBorder.R
   Dim CFHBorderG As Byte = CFHBorder.G
   Dim CFHBorderB As Byte = CFHBorder.B

   ' Make a CBitmapBytes object.
   Dim CFHBmpBytes As New CBitmapBytes(CFHBmp)

   ' Lock the bitmap.
   CFHBmpBytes.CanvasLockBmp()

   ' Make the total region.
   Dim CFHTotReg As Region = Nothing

   ' Find and color the initial run.
   Dim InitRun As CanvasHatchRegion = CanvasRunRegion(CFHBmpBytes, _
      CFHBmp.Width, CFHBmp.Height, CFHx, CFHy, CFHBorderR, _
      CFHBorderG, CFHBorderB)
   InitRun.CanvasSetCol(CFHTotReg, CFHBmpBytes, CFHBorderR, _
      CFHBorderG, CFHBorderB)

   ' Start with the initial run in the stack.
   Dim CFHRuns As New Stack(1000)
   CFHRuns.Push(InitRun)    'Push Next Run To Top Of Stack

   ' While the stack is not empty, process a run.
   Dim CFHPixel As Integer
   Do While CFHRuns.Count > 0
      ' Get the next run.
      Dim CFHCurrentRun As CanvasHatchRegion = _
         DirectCast(CFHRuns.Pop(), _
         CanvasHatchRegion)
      CFHy = CFHCurrentRun.CY

      ' Look for runs above.
      If CFHy > 0 Then
         Dim x0 As Integer = CFHCurrentRun.CXMin
         Do
            If x0 > CFHCurrentRun.CXMax Then Exit Do
            ' See if this point has the border color.
            If Not CanvasPointColor(CFHBmpBytes, x0, CFHy - 1, _
               CFHBorderR, CFHBorderG, CFHBorderB) Then
               ' Make and color a run here.
               Dim CFHNewRun As CanvasHatchRegion = _
                  CanvasRunRegion(CFHBmpBytes, CFHBmp.Width, _
                  CFHBmp.Height, x0, CFHy - 1, CFHBorderR, _
                  CFHBorderG, CFHBorderB)
               CFHNewRun.canvasSetCol(CFHTotReg, CFHBmpBytes, _
                  CFHBorderR, CFHBorderG, CFHBorderB)
               CFHRuns.Push(CFHNewRun)

               ' Skip one pixel beyond the end of this run.
               x0 = CFHNewRun.CXmax + 2
            Else
               x0 += 1
            End If
         Loop
      End If

      ' Look for runs below.
      If CFHy < CFHBmp.Height - 1 Then
         Dim x0 As Integer = CFHCurrentRun.CXMin
         Do
            If x0 > CFHCurrentRun.CXMax Then Exit Do
            ' See if this point has the old color.
            If Not CanvasPointColor(CFHBmpBytes, x0, CFHy + 1, _
               CFHBorderR, CFHBorderG, CFHBorderB) Then
               ' Make and color a run here.
               Dim CFHNewRun As CanvasHatchRegion = _
                  CanvasRunRegion(CFHBmpBytes, CFHBmp.Width, _
                  CFHBmp.Height, x0, CFHy + 1, CFHBorderR, _
                  CFHBorderG, CFHBorderB)
               CFHNewRun.canvasSetCol(CFHTotReg, CFHBmpBytes, _
                  CFHBorderR, CFHBorderG, CFHBorderB)
               CFHRuns.Push(CFHNewRun)

               ' Skip one pixel beyond the end of this run.
               x0 = CFHNewRun.CXmax + 2
            Else
               x0 += 1
            End If
         Loop
      End If
   Loop

   ' Unlock the bitmap.
   CFHBmpBytes.CanvasUnlockBmp(False)

   ' Fill the region.
   CFHg.FillRegion(CFHFloodBrush, CFHTotReg)
   CFHTotReg.Dispose()
End Sub

   ' Find the run at this point.
   Private Function CanvasRunRegion(ByVal CRRBmpBytes As CBitmapBytes, _
      ByVal CRRWid As Integer, ByVal CRRHgt As Integer, _
      ByVal CRRx As Integer, ByVal CRRy As Integer, _
      ByVal CRRBorderR As Byte, ByVal CRRBorderG As Byte, _
      ByVal CRRBorderB As Byte) As CanvasHatchRegion
      Dim x0 As Integer = CRRx
      Do
         If x0 < 0 Then Exit Do
          If CanvasPointColor(CRRBmpBytes, x0, CRRy, CRRBorderR, _
                              CRRBorderG, CRRBorderB) Then Exit Do
         x0 -= 1
      Loop
      Dim x1 As Integer = CRRx
      Do
         If x1 >= CRRWid Then Exit Do
         If CanvasPointColor(CRRBmpBytes, x1, CRRy, CRRBorderR, _
                             CRRBorderG, CRRBorderB) Then Exit Do
         x1 += 1
      Loop
      'Fill The Region
      Return New CanvasHatchRegion(x0 + 1, x1 - 1, CRRy)
   End Function

All you need to do now is to call the CanvasFloodHatch method when you right-click the shape you want filled with a Hatch Brush.

Private Sub picCDraw_MouseDown(ByVal sender As System.Object, _
   ByVal e As System.Windows.Forms.MouseEventArgs) _
   Handles picCDraw.MouseDown
   'Determine Whether Or Not The Pencil (Free Hand) Tool Has Been
   'Clicked
   If blnDrawClicked Then
      blnDrawing = True
   End If

   'Determine Whether Or Not The Eraser Tool Has Been Clicked
   If blnEraserClicked Then
      blnErasing = True
   End If

   'Initialise Starting Points Of Shape, Once Mouse Button Is
   'Pressed Down
   sStartX = e.X
   sStartY = e.Y

   ptStart.X = e.X    'Start = Current X
   ptStart.Y = e.Y    'Start = current Y

   ptEnd.X = -1
   ptEnd.Y = -1

   blnMouse = True

   If blnDrawFillClicked Then
     'Get Current Color
      Dim old_color As Color = bImage.GetPixel(e.X, e.Y)
      Dim new_color As Color = cColor 'New Color = Selected Color

      If e.Button = MouseButtons.Left Then
         'Fill Shape With New Color If Left Clicked
         CanvasFloodFill(DirectCast(picCDraw.Image, Bitmap), _
            e.X, e.Y, new_color)
      Else 'Right Clicked
         CanvasFloodFill(DirectCast(picCDraw.Image, Bitmap), _
                                    e.X, e.Y, new_color)

         'Fill With Hatch
         CanvasFloodHatch(DirectCast(picCDraw.Image, Bitmap), _
            e.X, e.Y, Color.Black, gCanvas, CHatchBrush)
       End If

   End If

   ptHeartStartPoint.X = e.X 'Heart Start Point - X
   ptHeartStartPoint.Y = e.Y 'Heart Start Point - Y

End Sub

The following picture illustrates just how much fun you can now have with Canvas, and how powerful your application has become!

[FillHatches.png]

All you need to do now is to update the display of your cursor to indicate to you when you are filling the shapes with color (in other words, the Draw Then Fill Tool was selected). You can do this at the top of the picCDraw_MouseMove event:

If blnDrawFillClicked _
And Not blnDrawClicked _
And Not blnSquareClicked _
And Not blnCircleClicked _
And Not blnTraingleClicked _
And Not blnEraserClicked _
And Not blnHeartClicked _
And Not blnRoundRectClicked _
And Not blnLineClicked _
   And Not blnSpiralClicked Then
      Me.Cursor = Cursors.PanSouth
Else
      Me.Cursor = Cursors.Cross
End If

When you Build and run Canvas now, you can fill the shapes with a solid color when you left click, and fill the shapes with any selected Hatch Brush when you right click. Nice, isn't it? Yes!

I have also included all the code mentioned throughout this article series (including everything you covered in Part 3) with this article, so feel free to download it!

Special Thank Yous!

  • Rod Stephens from vb-helper.com for all his kindness and help with filling the shapes.
  • Rich2189 for helping with the Spiral logic.
  • Craig Gemmill, for identifying a bug in the Heart shape's logic, which fixed filling the Heart shape properly.

Conclusion

I sincerely hope that you have enjoyed reading and working on this part. It was extremely fun for me, and it literally took months to complete—unfortunately, due to time constraints. You now have a real drawing application that is extremely powerful. You can feel free to further your own version of Canvas to include more Hatch Styles; I haven't even covered half of them here. Be on the lookout for Part 4; it will allow you to Crop, Zoom, and Rotate selections.



About the Author

Hannes du Preez

Hannes du Preez is a Microsoft MVP for Visual Basic. He is a trainer at a South African-based company. He is the co-founder of hmsmp.co.za, a community for South African developers.

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • On-demand Event Event Date: September 10, 2014 Modern mobile applications connect systems-of-engagement (mobile apps) with systems-of-record (traditional IT) to deliver new and innovative business value. But the lifecycle for development of mobile apps is also new and different. Emerging trends in mobile development call for faster delivery of incremental features, coupled with feedback from the users of the app "in the wild." This loop of continuous delivery and continuous feedback is how the best mobile …

  • Packaged application development teams frequently operate with limited testing environments due to time and labor constraints. By virtualizing the entire application stack, packaged application development teams can deliver business results faster, at higher quality, and with lower risk.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds