I am interested to learn why, for the following square hollow section (SHS), colour bleeds beyond the inside and outside shape boundaries at the location of its rounded corners. When the the number of coordinates calculated to create a corner curve is 90 (i.e. for each degree of arc), bleeding is barely visible, but for a value of 120 or more, bleeding is obvious.
Please can someone explain why this occurs?
To recreate the issue, create a Form not less than 1000 x 1000px in size with a button located on it, then vary the value for the 'AngularIncrements' variable in the Button1_Click Subroutine, which defines how smooth (faceted) a corner curve is, but also affects the magnitude of bleeding. I also find that an increased value for 't', which controls shape wall thickness, correspondingly increases bleeding.
Note that the shape's yellow infill is a DrawPolygon line of user-defined thickness, not a FillPolygon infill. I appreciate that one way around my issue is to:
- Paint a solid shape using FillPolygon infill, then;
- Paint on top of it a 'hole' in the Form's BackColor to create the illusion of a hollow shape, then;
- Paint the inside boundary line, then;
- Paint the outside boundary line.
However, this leads to a visible flicker, I think because VB takes a perceptible split-second for the solid area of point 2 to be painted over that for point 1. Paining a line of specified thickness for wall thickness eliminates this, but leads to bleeding as noted above.
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
' Define square hollow section (SHS) dimensions.
Dim D As Decimal = 500 ' Depth.
Dim B As Decimal = D ' Breadth.
Dim t As Decimal = 50 ' Wall thickness.
Dim ro As Decimal = 2 * t ' Outside radius.
Dim ri As Decimal = t ' Inside radius.
' Define number of increments around quarter-curves.
Dim AngularIncrements As Integer = 120
' Calculate SHS coordinates.
Dim sectionCoords(-1)() As Decimal
sectionCoords = Calculate_SHS_Coordinates(AngularIncrements, D, B, ro, ri)
' Paint SHS.
Paint_SHS(sectionCoords, t)
End Sub
Private Function Calculate_SHS_Coordinates(ByVal AngularIncrements As Integer, ByVal D As Decimal, ByVal B As Decimal, ByVal ro As Decimal, ByVal ri As Decimal)
Dim ϴ As Decimal = Math.PI / 2
Dim IncrementAngle As Decimal = ϴ / AngularIncrements
' Calculate radii coordinates.
Dim rx As Decimal = -B / 2 + ro
Dim ry As Decimal = -D / 2 + ro
' Calculate quarter-section coordinates.
Dim inputCoordsOuter(2 * AngularIncrements + 1) As Decimal
Dim inputCoordsInner(2 * AngularIncrements + 1) As Decimal
For i As Integer = 0 To 2 * AngularIncrements Step 2
inputCoordsOuter(i) = rx - ro * Math.Sin(i / 2 * IncrementAngle)
inputCoordsOuter(i + 1) = ry - ro * Math.Cos(i / 2 * IncrementAngle)
inputCoordsInner(i) = rx - ri * Math.Sin(i / 2 * IncrementAngle)
inputCoordsInner(i + 1) = ry - ri * Math.Cos(i / 2 * IncrementAngle)
Next
' Mirror quarter-section to calculate half-section coordinates.
ReDim Preserve inputCoordsOuter(2 * inputCoordsOuter.Length - 1)
ReDim Preserve inputCoordsInner(2 * inputCoordsInner.Length - 1)
For i As Integer = inputCoordsOuter.Length - 2 To inputCoordsOuter.Length / 2 Step -2
inputCoordsOuter(i) = inputCoordsOuter(inputCoordsOuter.Length - (i + 2))
inputCoordsOuter(i + 1) = -inputCoordsOuter(inputCoordsOuter.Length - (i + 1))
inputCoordsInner(i) = inputCoordsInner(inputCoordsInner.Length - (i + 2))
inputCoordsInner(i + 1) = -inputCoordsInner(inputCoordsInner.Length - (i + 1))
Next
' Mirror half-section to calculate full-section coordinates.
ReDim Preserve inputCoordsOuter(2 * inputCoordsOuter.Length - 1)
ReDim Preserve inputCoordsInner(2 * inputCoordsInner.Length - 1)
For i As Integer = inputCoordsOuter.Length - 2 To inputCoordsOuter.Length / 2 Step -2
inputCoordsOuter(i) = -inputCoordsOuter(inputCoordsOuter.Length - (i + 2))
inputCoordsOuter(i + 1) = inputCoordsOuter(inputCoordsOuter.Length - (i + 1))
inputCoordsInner(i) = -inputCoordsInner(inputCoordsInner.Length - (i + 2))
inputCoordsInner(i + 1) = inputCoordsInner(inputCoordsInner.Length - (i + 1))
Next
' Return to first coordinate pair.
ReDim Preserve inputCoordsOuter(inputCoordsOuter.Length + 1)
inputCoordsOuter(inputCoordsOuter.Length - 2) = inputCoordsOuter(0)
inputCoordsOuter(inputCoordsOuter.Length - 1) = inputCoordsOuter(1)
ReDim Preserve inputCoordsInner(inputCoordsInner.Length + 1)
inputCoordsInner(inputCoordsInner.Length - 2) = inputCoordsInner(0)
inputCoordsInner(inputCoordsInner.Length - 1) = inputCoordsInner(1)
' Place outer and inner arrays in jagged array.
Dim inputCoords()() = {inputCoordsOuter, inputCoordsInner}
Return inputCoords
End Function
Private Sub Paint_SHS(ByVal sectionCoords()() As Decimal, ByVal t As Decimal)
' Define the SHS centre location.
Dim originX As Integer = Me.Width / 2
Dim originY As Integer = Me.Height / 2
' Define paint colours.
Dim infillColour As Color = Color.Yellow
Dim inlineOutlineColour As Color = Color.Black
Dim lineWidth As Integer = 1
' Define form as graphics object.
Dim Canvas As Graphics = Me.CreateGraphics
' Calculate coordinates for centre of wall thickness, and paint (thick) line.
Dim paintCoords(sectionCoords(0).Length / 2 - 1) As Point
For i As Integer = 0 To sectionCoords(0).Length - 1 Step 2
paintCoords(i / 2) = New Point(originX + (sectionCoords(0)(i) + sectionCoords(1)(i)) / 2, originY - (sectionCoords(0)(i + 1) + sectionCoords(1)(i + 1)) / 2)
Next
Paint_Line(Canvas, paintCoords, infillColour, t)
' Calculate outline coordinates and paint outline.
For i As Integer = 0 To sectionCoords(0).Length - 1 Step 2
paintCoords(i / 2) = New Point(originX + sectionCoords(0)(i), originY - sectionCoords(0)(i + 1))
Next
Paint_Line(Canvas, paintCoords, inlineOutlineColour, lineWidth)
' Calculate inline coordinates and paint inline.
For i As Integer = 0 To sectionCoords(1).Length - 1 Step 2
paintCoords(i / 2) = New Point(originX + sectionCoords(1)(i), originY - sectionCoords(1)(i + 1))
Next
Paint_Line(Canvas, paintCoords, inlineOutlineColour, lineWidth)
End Sub
Private Sub Paint_Line(ByVal Canvas As Graphics, ByVal paintCoords As Point(), ByVal paintColour As Color, ByVal lineWidth As Decimal)
Dim outlinePen = New Pen(paintColour, CSng(lineWidth))
Canvas.DrawPolygon(outlinePen, paintCoords)
outlinePen.Dispose()
End Sub
End Class
EDIT: I've rewritten the example to simplify it, address issues noted below in the comments and to increase legibility. However, I still am unable to paint the shape to the screen correctly, as follows:
- For a small AngularIncrements value, which defines the number of facets around a 90 degree curve, the shape appears to be painted correctly, but some increment values (e.g. 150) result in a clearly visible error.
- As the value for AngularIncrements increases, the width of the shape's wall, which should be constant everywhere, varies over the 90 degree curve length, being thickest at its mid-length. I could understand this reducing as the number of facets increases, but not the converse. Try an AngularIncrements value of 1500 to see what I mean. Now, one could argue that this number of angular increments is overkill, and I'd agree, but I'm interested to learn what is causing this issue.
Here's the revised code:
Imports System.Drawing.Drawing2D
Public Class Form1
Private yesPaint As Boolean = False
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
' Paint shape.
yesPaint = True
Me.Invalidate()
End Sub
Private Sub Form1_Paint(sender As Object, e As PaintEventArgs) Handles Me.Paint
If yesPaint = True Then
' Define shape.
Dim D As Decimal = 450
Dim B As Decimal = D
Dim t As Decimal = 100
Dim r As Decimal = 100
Dim AngularIncrements As Integer = 150
' Calculate coordinates for painting by converting to integer values the set of coordinates used for engineering property calculations (not included in this example).
' Coordinates represent the centre of shape wall thickness.
Dim paintCoords As Point() = Calculate_SHS_Coordinates(AngularIncrements, D, B, r)
' Calculate screen origin coordinates.
Dim originX As Integer = CInt(Me.Width / 2)
Dim originY As Integer = CInt(Me.Height / 2)
' Recalculate paint coordinates relative to screen origin.
For i As Integer = 0 To paintCoords.Length - 1
paintCoords(i).X = paintCoords(i).X + originX
paintCoords(i).Y = paintCoords(i).Y + originY
Next
' Create graphics path object and add polygon.
Dim SHSpath = New GraphicsPath()
SHSpath.AddPolygon(paintCoords)
' Create pen.
Dim blackPen = New Pen(Color.Black, t)
' Paint graphics path.
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias
e.Graphics.DrawPath(blackPen, SHSpath)
blackPen.Dispose()
yesPaint = False
End If
End Sub
Private Function Calculate_SHS_Coordinates(ByVal AngularIncrements As Integer, ByVal D As Decimal, ByVal B As Decimal, ByVal r As Decimal) As Point()
Dim ϴ As Double = Math.PI / 2
Dim IncrementAngle As Decimal = CDec(ϴ) / AngularIncrements
' Calculate quarter-section radius coordinates.
Dim rx As Decimal = -B / 2 + r
Dim ry As Decimal = -D / 2 + r
' Calculate quarter-section coordinates.
Dim inputCoords(AngularIncrements) As Point
For i As Integer = 0 To AngularIncrements
inputCoords(i).X = CInt(rx - r * Math.Sin(CDec(i) * IncrementAngle))
inputCoords(i).Y = CInt(ry - r * Math.Cos(CDec(i) * IncrementAngle))
Next
' Mirror quarter-section to calculate half-section coordinates.
ReDim Preserve inputCoords(2 * inputCoords.Length - 1)
For i As Integer = inputCoords.Length - 1 To CInt(inputCoords.Length / 2) Step -1
inputCoords(i).X = inputCoords(inputCoords.Length - (i + 1)).X
inputCoords(i).Y = -inputCoords(inputCoords.Length - (i + 1)).Y
Next
' Mirror half-section to calculate full-section coordinates.
ReDim Preserve inputCoords(2 * inputCoords.Length - 1)
For i As Integer = inputCoords.Length - 1 To CInt(inputCoords.Length / 2) Step -1
inputCoords(i).X = -inputCoords(inputCoords.Length - (i + 1)).X
inputCoords(i).Y = inputCoords(inputCoords.Length - (i + 1)).Y
Next
' Return to first coordinate pair.
ReDim Preserve inputCoords(inputCoords.Length)
inputCoords(inputCoords.Length - 1).X = inputCoords(0).X
inputCoords(inputCoords.Length - 1).Y = inputCoords(0).Y
Return inputCoords
End Function
End Class