none
Continuing Rainbow Rotating Ring RRS feed

  • Question

  • This is a continuation from the original question - Why does it look so jagged ? I tried adding 360 points along the circle, didn't work out too well, code is commented out, tried 180 points also. SmoothingMode.AntiAlias, and High Quality didn't help either.

    Code (all controls are added at runtime):

    Imports System.Drawing.Drawing2D
    
    Public Class Form1
        WithEvents T As New Timer With {.Interval = 10, .Enabled = False}
        WithEvents SButton As New Button With {.Text = "Start", .Size = New Size(75, 25), .Location = New Point(350, 380), .Anchor = AnchorStyles.Bottom Or AnchorStyles.Right}
        Dim Lab1 As New Label With {.Text = "Ring Size", .AutoSize = False, .BorderStyle = BorderStyle.FixedSingle, .Location = New Point(10, 340), .Size = New Size(80, 20), .TextAlign = ContentAlignment.MiddleRight}
        Dim Lab2 As New Label With {.Text = "Thickness", .AutoSize = False, .BorderStyle = BorderStyle.FixedSingle, .Location = New Point(10, 370), .Size = New Size(80, 20), .TextAlign = ContentAlignment.MiddleRight}
        Dim Lab3 As New Label With {.Text = "Rot Speed", .AutoSize = False, .BorderStyle = BorderStyle.FixedSingle, .Location = New Point(10, 400), .Size = New Size(80, 20), .TextAlign = ContentAlignment.MiddleRight}
        WithEvents NUDSz As New NumericUpDown With {.Size = New Size(70, 20), .Location = New Point(100, 340), .Minimum = 20, .Maximum = 200, .Value = 100}
        WithEvents NUDTh As New NumericUpDown With {.Size = New Size(70, 20), .Location = New Point(100, 370), .Minimum = 5, .Maximum = 100, .Value = 20}
        WithEvents NUDSp As New NumericUpDown With {.Size = New Size(70, 20), .Location = New Point(100, 400), .Minimum = 300, .Maximum = 500, .Value = 400, .Increment = 10}
        Dim transformangle As Single = 0.0
        Dim Thickness As Integer = 20
        Dim Upperleft As Point = Nothing
        Dim SquareSize As Integer = 100
        Dim CenterX As Integer = 0
        Dim CenterY As Integer = 0
        Dim Outside As Rectangle = Nothing
        Dim Inside As Rectangle = Nothing
        Dim SpinSpeed As Integer = 0
        Dim wheel_path As GraphicsPath = Nothing
        Dim br As PathGradientBrush = Nothing
    
        Private Function FindPointOnCircle(originPoint As Point, radius As Double, angleDegrees As Double) As Point
            Dim x As Double = radius * Math.Cos(Math.PI * angleDegrees / 180.0) + originPoint.X
            Dim y As Double = radius * Math.Sin(Math.PI * angleDegrees / 180.0) + originPoint.Y
            Return New Point(x, y)
        End Function
        Private Sub SetParams()
            SpinSpeed = NUDSp.Value
            T.Interval = 510 - SpinSpeed
            Upperleft = New Point(10, 10)
            Outside = New Rectangle(Upperleft, New Size(SquareSize, SquareSize))
            Inside = New Rectangle(Upperleft.X + Thickness, Upperleft.Y + Thickness, SquareSize - (2 * Thickness), SquareSize - (2 * Thickness))
            CenterX = Upperleft.X + (SquareSize \ 2)
            CenterY = Upperleft.Y + (SquareSize \ 2)
            If Not br Is Nothing Then
                br.Dispose()
            End If
            If Not wheel_path Is Nothing Then
                wheel_path.Dispose()
            End If
            wheel_path = New GraphicsPath
            'For angle As Double = 1 To 360 ' UGLY
            '    wheel_path.AddLine(FindPointOnCircle(New Point(CenterX, CenterY), SquareSize \ 2, angle - 1), FindPointOnCircle(New Point(CenterX, CenterY), SquareSize \ 2, angle))
            'Next
            wheel_path.AddEllipse(Outside)
            wheel_path.Flatten()
            Dim r, g, b, dr, dg, db As Single
            Dim num_pts As Integer = (wheel_path.PointCount - 1) \ 3
            Me.Text = wheel_path.PointCount.ToString
            Dim surround_colors(wheel_path.PointCount - 1) As Color
            r = 255 : g = 0 : b = 0
            dr = -255 / num_pts : db = 255 / num_pts
            For i As Integer = 0 To num_pts - 1
                surround_colors(i) = Color.FromArgb(255, r, g, b)
                r += dr : b += db
            Next i
    
            r = 0 : g = 0 : b = 255
            dg = 255 / num_pts : db = -255 / num_pts
            For i As Integer = num_pts To 2 * num_pts - 1
                surround_colors(i) = Color.FromArgb(255, r, g, b)
                g += dg : b += db
            Next i
    
            r = 0 : g = 255 : b = 0
            dr = 255 / (wheel_path.PointCount - 2 * num_pts)
            dg = -255 / (wheel_path.PointCount - 2 * num_pts)
            For i As Integer = 2 * num_pts To wheel_path.PointCount - 1
                surround_colors(i) = Color.FromArgb(255, r, g, b)
                r += dr : g += dg
            Next i
    
            br = New PathGradientBrush(wheel_path)
            br.CenterColor = Color.White
            br.SurroundColors = surround_colors
        End Sub
    
        Private Sub Form1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
            e.Graphics.SmoothingMode = SmoothingMode.AntiAlias
            e.Graphics.ResetTransform()
            e.Graphics.TranslateTransform(CenterX, CenterY)
            e.Graphics.RotateTransform(transformangle)
            e.Graphics.TranslateTransform(-CenterX, -CenterY)
            e.Graphics.FillPath(br, wheel_path)
            e.Graphics.FillEllipse(Brushes.White, Inside)
        End Sub
        Private Sub T_Tick(sender As Object, e As Object) Handles T.Tick
            transformangle += 5
            If transformangle >= 360 Then transformangle = 0
            Me.Invalidate()
        End Sub
        Private Sub SButton_Click(sender As System.Object, e As System.EventArgs) Handles SButton.Click
            If SButton.Text = "Start" Then
                T.Start()
                SButton.Text = "Stop"
            Else
                T.Stop()
                SButton.Text = "Start"
            End If
        End Sub
    
        Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
            Me.Size = New Size(470, 470)
            Me.Font = New Font("Consolas", 8)
            Me.DoubleBuffered = True
            Me.Controls.Add(SButton)
            Me.Controls.Add(Lab1)
            Me.Controls.Add(Lab2)
            Me.Controls.Add(Lab3)
            Me.Controls.Add(NUDSz)
            Me.Controls.Add(NUDTh)
            Me.Controls.Add(NUDSp)
            SetParams()
        End Sub
    
        Private Sub NUDSize_ValueChanged(sender As System.Object, e As System.EventArgs) Handles NUDSz.ValueChanged, NUDTh.ValueChanged, NUDSp.ValueChanged
            T.Stop()
            SquareSize = NUDSz.Value
            Thickness = NUDTh.Value
            SpinSpeed = NUDSp.Value
            SetParams()
            T.Start()
        End Sub
    End Class
    

    Screenshot:

    Sunday, May 14, 2017 5:32 PM

Answers

  • So if we draw over the gradient fill using the path with background color we get a new anti-aliased edge on the gradient fill.

       Private Sub Form1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
            Using FormBrush As Brush = New SolidBrush(Me.BackColor)
                Using FormPen As Pen = New Pen(FormBrush, 2)
                    With e.Graphics
                        .ResetTransform()
                        .SmoothingMode = SmoothingMode.AntiAlias
                        .TranslateTransform(CenterX, CenterY)
                        .RotateTransform(transformangle)
                        .TranslateTransform(-CenterX, -CenterY)
    
                        .FillPath(br, wheel_path)
                        .DrawPath(New Pen(Me.BackColor, 4), wheel_path)
    
                    End With
                End Using
            End Using
        End Sub

    PS Note the pen width is 4 so the path ellipse is drawn wide enough to overlap the gradient fill on the inside.



    PS My understanding is a region can be thought of as not the same drawing surface as our e.graphics paint is giving us.

    The region we make with the path brush fill is really like second bitmap in memory. So that is why there is no anti-aliasing between the two. The second path region is separate and does not know the other drawing surface we use with e.graphics exists. So the two cant be anti-aliased as there is only one part of it in the region alone, the gradient fill.

    When we draw the filled path we copy the clipped brush region bits to the e.graphics drawing surface bitmap and that is not anti-aliased it is an exact copy of the region defined by the inside of the path.

    Once the region bits (the gradient brush fill) are copied onto our first drawing surface we can draw over them with another color and the edge gets anti-aliased with the rest of the bits all on the one surface now e.graphics. ie the first bitmap in memory that we are drawing on with e.graphics.

    So finally our paint bitmap gets copied to the video card display memory by the system at the appropriate time and combined with other surfaces like the controls and the backgroundimages other apps etc. Those are all other regions. Or something like that there.

    You can also think of them as layers. We draw on one layer with e.graphics. The system does the rest.

    Monday, May 15, 2017 1:15 PM

All replies

  • Hi

    The image AntiAlias may account for the 'blurred' edges?


    Regards Les, Livingston, Scotland

    Sunday, May 14, 2017 8:57 PM
  • Try this. :)

    The order is important.

    e.Graphics.ResetTransform()

         e.Graphics.SmoothingMode = SmoothingMode.AntiAlias


    Sunday, May 14, 2017 9:39 PM
  • Try this. :)

    The order is important.

    e.Graphics.ResetTransform()

         e.Graphics.SmoothingMode = SmoothingMode.AntiAlias


    That didn't seem to help so I changed the Paint Event and cheated a bit:

        Private Sub Form1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
            Using FormBrush As Brush = New SolidBrush(Me.BackColor)
                Using FormPen As Pen = New Pen(FormBrush, 2)
                    With e.Graphics
                        .ResetTransform()
                        .SmoothingMode = SmoothingMode.AntiAlias
                        .TranslateTransform(CenterX, CenterY)
                        .RotateTransform(transformangle)
                        .TranslateTransform(-CenterX, -CenterY)
                        .FillPath(br, wheel_path)
                        .FillEllipse(FormBrush, Inside)
                        .DrawEllipse(FormPen, Outside)
                    End With
                End Using
            End Using
        End Sub
    

    Old on left, new on right

    Sunday, May 14, 2017 11:09 PM
  • Try this. :)

    The order is important.

    e.Graphics.ResetTransform()

         e.Graphics.SmoothingMode = SmoothingMode.AntiAlias


    That didn't seem to help so I changed the Paint Event and cheated a bit:

        Private Sub Form1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
            Using FormBrush As Brush = New SolidBrush(Me.BackColor)
                Using FormPen As Pen = New Pen(FormBrush, 2)
                    With e.Graphics
                        .ResetTransform()
                        .SmoothingMode = SmoothingMode.AntiAlias
                        .TranslateTransform(CenterX, CenterY)
                        .RotateTransform(transformangle)
                        .TranslateTransform(-CenterX, -CenterY)
                        .FillPath(br, wheel_path)
                        .FillEllipse(FormBrush, Inside)
                        .DrawEllipse(FormPen, Outside)
                    End With
                End Using
            End Using
        End Sub

    Old on left, new on right

    LOL. Ok.

    When you reset the transform it also resets smoothing to none. So you need to reset before you set to antialias. That is the problem. :)

    Monday, May 15, 2017 12:18 AM
  • That is what is in the Paint Sub

              .ResetTransform()
              .SmoothingMode = SmoothingMode.AntiAlias

    If I comment out the outside circle

    .DrawEllipse(FormPen, Outside)

    It looks jagged just like the Original Post - does that not occur when you try it ?

    Monday, May 15, 2017 6:29 AM
  • That is what is in the Paint Sub

              .ResetTransform()
              .SmoothingMode = SmoothingMode.AntiAlias

    If I comment out the outside circle

    .DrawEllipse(FormPen, Outside)

    It looks jagged just like the Original Post - does that not occur when you try it ?

    I see. I did not know exactly what you meant.

    I am not sure but I think when you use a pathgradientbrush it is actually creating a region and that does not get antialiased.

    If you only change to a normal solidbrush fill color and then it is smooth. Using br the pathgradientbrush is jagged. ie

       .FillPath(Brushes.Red, wheel_path)

    is smooth.

    The pathgradientbrush is a clipping region I think which does not get anti-aliased. One of its drawbacks.

    So in this code change I added a draw path in red before the gradient fill. You can see the outside draw path red outline in the image below is smooth and the purple gradient fill with the jagged edge just inside.

        Private Sub Form1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
            Using FormBrush As Brush = New SolidBrush(Me.BackColor)
                Using FormPen As Pen = New Pen(FormBrush, 2)
                    With e.Graphics
                        .ResetTransform()
                        .SmoothingMode = SmoothingMode.AntiAlias
                        .TranslateTransform(CenterX, CenterY)
                        .RotateTransform(transformangle)
                        .TranslateTransform(-CenterX, -CenterY)
    
                        .DrawPath(New Pen(Color.Red, 4), wheel_path)
                        .FillPath(br, wheel_path)
    
    
                    End With
                End Using
            End Using
        End Sub
    

    Monday, May 15, 2017 12:24 PM
  • PS Here I removed the anti-aliasing and now the jagged edge is visible inside and outside the path edge. So the inside edge is where the pathgradient clipping is occurring. My theory.


    PS Images enlarged x 3 times actual size.
    Monday, May 15, 2017 12:41 PM
  • So if we draw over the gradient fill using the path with background color we get a new anti-aliased edge on the gradient fill.

       Private Sub Form1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
            Using FormBrush As Brush = New SolidBrush(Me.BackColor)
                Using FormPen As Pen = New Pen(FormBrush, 2)
                    With e.Graphics
                        .ResetTransform()
                        .SmoothingMode = SmoothingMode.AntiAlias
                        .TranslateTransform(CenterX, CenterY)
                        .RotateTransform(transformangle)
                        .TranslateTransform(-CenterX, -CenterY)
    
                        .FillPath(br, wheel_path)
                        .DrawPath(New Pen(Me.BackColor, 4), wheel_path)
    
                    End With
                End Using
            End Using
        End Sub

    PS Note the pen width is 4 so the path ellipse is drawn wide enough to overlap the gradient fill on the inside.



    PS My understanding is a region can be thought of as not the same drawing surface as our e.graphics paint is giving us.

    The region we make with the path brush fill is really like second bitmap in memory. So that is why there is no anti-aliasing between the two. The second path region is separate and does not know the other drawing surface we use with e.graphics exists. So the two cant be anti-aliased as there is only one part of it in the region alone, the gradient fill.

    When we draw the filled path we copy the clipped brush region bits to the e.graphics drawing surface bitmap and that is not anti-aliased it is an exact copy of the region defined by the inside of the path.

    Once the region bits (the gradient brush fill) are copied onto our first drawing surface we can draw over them with another color and the edge gets anti-aliased with the rest of the bits all on the one surface now e.graphics. ie the first bitmap in memory that we are drawing on with e.graphics.

    So finally our paint bitmap gets copied to the video card display memory by the system at the appropriate time and combined with other surfaces like the controls and the backgroundimages other apps etc. Those are all other regions. Or something like that there.

    You can also think of them as layers. We draw on one layer with e.graphics. The system does the rest.

    Monday, May 15, 2017 1:15 PM
  • As always, a clear and concise explanation, which I appreciate so much more than a "just put this code in there" response. Thanks again.

    Monday, May 15, 2017 7:48 PM