Sunday, September 30, 2018

Drawing thick line around GraphicsPath in VB (Updated)

This is a new version with new code. I found some time to redo it in May 2020.

The code below is designed to work with a graphics object defined from lines. The format of the array of PathTypes of the GraphicsPath object being passed should be a single set of {0,1,1,.... all ones ...,129). This can be done by adding points using AddLines (of course), then closing the GraphicsPath figure.

I created a Classic Windows Forms Project with VB coding. It has a form, Form1, with one control: A 500x500 label, Label1.

The vbcode of the form is this:

Imports System.Drawing.Drawing2D

Public Class Form1

    Private Sub Label1_Paint(sender As Object, e As PaintEventArgs) Handles Label1.Paint
        Dim SamplePoints() As PointF = {New PointF(86, 132), New PointF(302, 106), New PointF(220, 190),
            New PointF(410, 232), New PointF(242, 290), New PointF(374, 324), New PointF(352, 376),
            New PointF(316, 330), New PointF(278, 390), New PointF(268, 340), New PointF(136, 216),
            New PointF(184, 146)}
        Dim gr0 As Graphics = e.Graphics
        Dim pthx As GraphicsPath = New GraphicsPath
        Dim pth2 As GraphicsPath
        Dim pnx As Pen = New Pen(Color.Black, 1)

        pthx.AddLines(SamplePoints)
        pthx.CloseFigure()
        pth2 = outlineGraphicsPath(pthx, 10, True)
        gr0.FillPath(Brushes.Blue, pthx)
        gr0.FillPath(Brushes.Red, pth2)

    End Sub

End Class

I added a module to the project and used this code.

Imports System.Drawing.Drawing2D

Module Module1
    Friend Function calcPositiveRadianTangent(x As Double, y As Double) As Double
        Dim sn1 As Double

        If (x = 0) And (y = 0) Then Return 0
        sn1 = Math.Atan(y / x)
        If x < 0 Then
            Select Case y
                Case 0
                    sn1 = Math.PI
                Case Else
                    sn1 = Math.PI + sn1
            End Select
        ElseIf x > 0 Then
            If y < 0 Then
                sn1 = 2 * Math.PI + sn1
            End If
        Else
            If y < 0 Then
                sn1 = 3 * Math.PI / 2
            End If
        End If
        Return sn1

    End Function

    Friend Function vertexOffsetPoints(x1 As Double, y1 As Double, x2 As Double, y2 As Double, x3 As Double, y3 As Double, lineWidth As Double) As PointF()
        Dim sn3, sn4, sn5, snbrg, sn6 As Double
        Dim pts(2) As PointF

        sn3 = calcPositiveRadianTangent(x2 - x1, y2 - y1)
        sn5 = calcPositiveRadianTangent(x2 - x3, y2 - y3)
        sn4 = Math.Abs((sn3 - sn5) / 2)
        snbrg = (sn3 + sn5) / 2
        sn6 = lineWidth / Math.Sin(sn4)
        pts(0).X = sn6 * Math.Cos(snbrg) + x2
        pts(0).Y = sn6 * Math.Sin(snbrg) + y2
        pts(2).X = 2 * Math.Cos(snbrg) + x2 'for checking which side is inside
        pts(2).Y = 2 * Math.Sin(snbrg) + y2
        snbrg += Math.PI
        pts(1).X = sn6 * Math.Cos(snbrg) + x2
        pts(1).Y = sn6 * Math.Sin(snbrg) + y2
        Return pts

    End Function

    Friend Function outlineGraphicsPath(pathIn As GraphicsPath, outlineThickness As Double, isOuter As Boolean) As GraphicsPath
        Dim pthx As GraphicsPath = pathIn.Clone
        Dim secondPoints(), secondPointSet() As PointF
        Dim lt3 As Integer

        ReDim secondPointSet(pthx.PathPoints.GetUpperBound(0))
        For lt1 = 0 To pthx.PathPoints.GetUpperBound(0)
            If lt1 = 0 Then
                lt3 = pthx.PathPoints.GetUpperBound(0)
            Else
                lt3 = lt1 - 1
            End If
            secondPoints = vertexOffsetPoints(pthx.PathPoints(lt3).X, pthx.PathPoints(lt3).Y, pthx.PathPoints(lt1).X, pthx.PathPoints(lt1).Y, pthx.PathPoints((lt1 + 1) Mod pthx.PathPoints.Length).X, pthx.PathPoints((lt1 + 1) Mod pthx.PathPoints.Length).Y, outlineThickness)
            If pthx.IsVisible(secondPoints(2).X, secondPoints(2).Y) Then
                If isOuter Then
                    secondPointSet(lt1) = secondPoints(1)
                Else
                    secondPointSet(lt1) = secondPoints(0)
                End If
            Else
                If Not isOuter Then
                    secondPointSet(lt1) = secondPoints(1)
                Else
                    secondPointSet(lt1) = secondPoints(0)
                End If
            End If
        Next
        pthx.AddLines(secondPointSet)
        pthx.CloseFigure()

        Return pthx

    End Function

End Module

When the form starts it draws the figure. 




This is the irregular polygon with the third parameter of outlineGraphicsPath set to True, creating a path that is then filled to render an outer line. 




In this example the third parameter is set to False, and the line renders inside the GraphicsPath object. Notice that sharp corners inside can create protrusions that go through the figure, because the extended points surpass the line width. 










That's about it.