4

Trying to get that center area filled, not sure what I'm doing wrong.

enter image description here

Imports System.Drawing.Drawing2D

Public Class Form1
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load

    End Sub

    Private Sub Form1_Paint(sender As System.Object, e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
        Dim gp As GraphicsPath = CreatePath(New Rectangle(New Point(200, 200), New Size(250, 100)))
        e.Graphics.FillPath(New SolidBrush(Color.LightBlue), gp)
        e.Graphics.DrawPath(New Pen(Color.Black), gp)

    End Sub

    Private Function CreatePath(ByVal area As Rectangle) As GraphicsPath

        Dim gp As New GraphicsPath()
        Dim rect As Rectangle = New Rectangle(area.Location, New Size(area.Width, area.Height \ 3))
        gp.AddEllipse(rect)

        Dim hadj As Integer = area.Height \ 4
        gp.AddLine(New Point(area.X, area.Y + area.Height - hadj), New Point(area.X, area.Y + (rect.Height \ 3) + 8))

        Dim gh As Integer = area.Width \ 4
        Dim pts(4) As PointF
        pts(0) = New PointF(area.X, area.Y + area.Height - hadj)
        pts(1) = New Point(area.X + gh, area.Y + area.Height - (hadj \ 2))
        pts(2) = New Point(area.X + gh * 2, area.Y + area.Height - (hadj \ 3))
        pts(3) = New Point(area.X + gh * 3, area.Y + area.Height - (hadj \ 2))
        pts(4) = New Point(area.X + area.Width, area.Y + area.Height - hadj)
        gp.AddCurve(pts)

        gp.AddLine(New PointF(area.X + area.Width, area.Y + (rect.Height \ 3) + 9), New PointF(area.X + area.Width, area.Y + area.Height - hadj))
        Return gp
    End Function
End Class
Jimi
  • 29,621
  • 8
  • 43
  • 61

2 Answers2

4

Try the following. By default the GraphicsPath uses an alternating fill mode which alternately adds/subtracts from the fill. Winding fill mode uses the direction to add (clockwise) or subtract (counter clockwise) from the fill.

Private Function CreatePath(ByVal area As Rectangle) As GraphicsPath
    '* Set fill mode to winding (see FillMode Help)
    Dim gp As New GraphicsPath(FillMode.Winding)

    Dim rect As Rectangle = New Rectangle(area.Location, New Size(area.Width, area.Height \ 3))
    gp.AddEllipse(rect)

    '* Reorder points so that path goes clockwise (see FillMode.Winding Help)

    Dim hadj As Integer = area.Height \ 4
    'Left side bottom -> top
    'gp.AddLine(New Point(area.X, area.Y + area.Height - hadj), New Point(area.X, area.Y + (rect.Height \ 3) + 8))

    '* Right side top -> bottom (clockwise)
    '* NOTE: Top Y value was a bit too high, adjusted by -3.  Adjust calculation or maybe use floating point math and used rounding, ceil, or floor
    gp.AddLine(New PointF(area.X + area.Width, area.Y + (rect.Height \ 3) + 9 - 3), New PointF(area.X + area.Width, area.Y + area.Height - hadj))

    Dim gh As Integer = area.Width \ 4
    Dim pts(4) As PointF
    'pts(0) = New PointF(area.X, area.Y + area.Height - hadj)
    'pts(1) = New Point(area.X + gh, area.Y + area.Height - (hadj \ 2))
    'pts(2) = New Point(area.X + gh * 2, area.Y + area.Height - (hadj \ 3))
    'pts(3) = New Point(area.X + gh * 3, area.Y + area.Height - (hadj \ 2))
    'pts(4) = New Point(area.X + area.Width, area.Y + area.Height - hadj)
    '* Reordered points (clockwise)
    pts(0) = New Point(area.X + area.Width, area.Y + area.Height - hadj)
    pts(1) = New Point(area.X + gh * 3, area.Y + area.Height - (hadj \ 2))
    pts(2) = New Point(area.X + gh * 2, area.Y + area.Height - (hadj \ 3))
    pts(3) = New Point(area.X + gh, area.Y + area.Height - (hadj \ 2))
    pts(4) = New PointF(area.X, area.Y + area.Height - hadj)
    gp.AddCurve(pts)

    'Right side top -> bottom
    'gp.AddLine(New PointF(area.X + area.Width, area.Y + (rect.Height \ 3) + 9), New PointF(area.X + area.Width, area.Y + area.Height - hadj))

    '* Left side bottom -> top (clockwise)
    '* NOTE: Top Y value was a bit too high, adjusted by -2.  Adjust calculation or maybe use floating point math and used rounding, ceil, or floor
    gp.AddLine(New Point(area.X, area.Y + area.Height - hadj), New Point(area.X, area.Y + (rect.Height \ 3) + 8 - 2))

    Return gp
End Function
Marker
  • 972
  • 6
  • 9
2

Another method, just to show the Reset() method of the GraphicsPath object.
Since a cylinder can be seen as the combination of two ellipsis and a rectangle (the perspective creates the illusion of a 3D object), here these shapes are first filled, then the outer parts (the shape borders) are drawn independently.

This will allow, eventually, to use different brushes for the figures, if needed.

Sample of the result:

GraphicsPath Cylinder


Private upperRect As Rectangle = New Rectangle(New Point(10, 10), New Size(180, 80))
Private lowerRect As Rectangle = New Rectangle(New Point(10, 100), New Size(180, 80))

Private Sub Form1_Paint(sender As Object, e As PaintEventArgs) Handles MyBase.Paint
    e.Graphics.SmoothingMode = SmoothingMode.AntiAlias

    Dim middleRect As Rectangle = CylinderMiddleRect(upperRect, lowerRect)

    Using gp As New GraphicsPath(FillMode.Winding)
        gp.AddEllipse(upperRect)
        gp.AddRectangle(middleRect)
        gp.AddEllipse(lowerRect)
        e.Graphics.FillPath(Brushes.LightBlue, gp)
        gp.Reset()

        gp.AddArc(lowerRect, 0, 180)
        gp.AddEllipse(upperRect)
        gp.AddLine(New Point(middleRect.X, middleRect.Y), New Point(upperRect.X, middleRect.Bottom))
        gp.CloseFigure()
        gp.AddLine(New Point(middleRect.Right, middleRect.Y), New Point(middleRect.Right, middleRect.Bottom))
        gp.CloseFigure()
        e.Graphics.DrawPath(Pens.Black, gp)
    End Using
End Sub

Private Function CylinderMiddleRect(upperRect As Rectangle, lowerRect As Rectangle) As Rectangle
    Dim distance = (lowerRect.Y + (lowerRect.Height \ 2)) - (upperRect.Y + (upperRect.Height \ 2))

    Return New Rectangle(New Point(upperRect.X, upperRect.Y + (upperRect.Height \ 2)),
                         New Size(upperRect.Width, distance))
End Function
Jimi
  • 29,621
  • 8
  • 43
  • 61
  • very nice, much better than mine –  Feb 13 '19 at 18:19
  • It's just the antialiasing: `e.Graphics.SmoothingMode = SmoothingMode.AntiAlias` :) – Jimi Feb 13 '19 at 18:23
  • yeah, I knew about that. But yours still looks nicer. Any way to accept an input rect and draw based on that? –  Feb 13 '19 at 18:26
  • Actually, I declared the `upperRect` and `lowerRect` rectangles outside the Paint event just for that. The `CylinderMiddleRect` method then calculates the distance between the two rectangles. This distance is used to build the inner rectangle and its sides provide the connecting lines. So, yes, you can use two rectangles of any size (that makes sense). Here, the difference between the `upperRect` and `lowerRect` is just the `Y` position. – Jimi Feb 13 '19 at 18:32
  • ahh, the rects are the same except the location. –  Feb 13 '19 at 18:51
  • Yes, exactly. The `Y` distance determines the shape of the cylinder, while the rectangles' heights provide the *perspective*. – Jimi Feb 13 '19 at 18:54