.netvb.netgraphicssystem.drawingdrawing2d

Modifying drawn image as a rounded rectangle


Following the code here I am trying to curve the edges of the rectangle so that its not all square.

An example of what it looks like now:

enter image description here

And what I am looking for it to do:

enter image description here

Using this code:

Using br As New SolidBrush(solidBGColor)
   Dim r As New RectangleF(0, 0, myPictureBox.Width, myPictureBox.Height)
   Dim gp As New System.Drawing.Drawing2D.GraphicsPath()
   Dim d As Integer = 5

   gp.AddArc(r.X, r.Y, imgSizeWH(0), imgSizeWH(1), 180, 90)
   gp.AddArc(r.X + r.Width - d, r.Y, imgSizeWH(0), imgSizeWH(1), 270, 90)
   gp.AddArc(r.X + r.Width - d, r.Y + r.Height - d, imgSizeWH(0), imgSizeWH(1), 0, 90)
   gp.AddArc(r.X, r.Y + r.Height - d, imgSizeWH(0), imgSizeWH(1), 90, 90)

   g.FillPath(br, gp)
End Using

I have an image that just doesn't seem correct:

enter image description here

Full code:

Private Function CreateLabeledAvatar(av As Image, text As String) As Image
    Dim imgSizeWH() As Integer = {800, 800}
    Dim bmp As New Bitmap(imgSizeWH(0), imgSizeWH(1))
    Dim solidBGColor As Color = DirectCast(New ColorConverter().ConvertFromString("#" + _BackgroundColours(New Random().[Next](0, _BackgroundColours.Count - 1))), Color)

    Using g As Graphics = Graphics.FromImage(bmp)
        Using br As New SolidBrush(solidBGColor)
            Dim r As New RectangleF(0, 0, myPictureBox.Width, myPictureBox.Height)
            Dim gp As New System.Drawing.Drawing2D.GraphicsPath()
            Dim d As Integer = 5

            gp.AddArc(r.X, r.Y, imgSizeWH(0), imgSizeWH(1), 180, 90)
            gp.AddArc(r.X + r.Width - d, r.Y, imgSizeWH(0), imgSizeWH(1), 270, 90)
            gp.AddArc(r.X + r.Width - d, r.Y + r.Height - d, imgSizeWH(0), imgSizeWH(1), 0, 90)
            gp.AddArc(r.X, r.Y + r.Height - d, imgSizeWH(0), imgSizeWH(1), 90, 90)

            g.FillPath(br, gp)
            'g.FillRectangle(br, 0, 0, bmp.Width, bmp.Height)
        End Using

        g.InterpolationMode = Drawing2D.InterpolationMode.HighQualityBicubic
        g.CompositingQuality = CompositingQuality.HighQuality
        g.TextRenderingHint = TextRenderingHint.AntiAlias
        g.SmoothingMode = SmoothingMode.HighQuality
        g.DrawImage(av, 0, 0, bmp.Width, bmp.Height)

        Using fnt As New Font("Arial", 132, FontStyle.Bold, GraphicsUnit.Pixel)
            TextRenderer.DrawText(g, text, fnt, New Rectangle(0, 0, imgSizeWH(0), imgSizeWH(1)),
                  Color.WhiteSmoke, TextFormatFlags.HorizontalCenter Or TextFormatFlags.VerticalCenter)
        End Using
    End Using

    Return bmp
End Function

Solution

  • Your GraphicsPath is just some arcs, no lines to make it an actual rounded rectangle. If the size is 800x800, you might want a larger d (diameter); and since you are working on an image, you should use the image size not the size of a PictureBox (this may be why the circle is larger than the image). Use this for the GP block:

    ' use the actual size, not those array values
    Dim rect As New Rectangle(0, 0, bmp.Width, bmp.Height)
    
    Using gp As New GraphicsPath
        ' arc radius is specified, but we use it as diameter
        Dim d As Int32 = radius * 2
    
        gp.StartFigure()
        ' top left
        ' LRTB creates a temp rect rather than calculating Size
        '   complete with typos and miscalcs
        gp.AddArc(Rectangle.FromLTRB(rect.Left, rect.Top,
                                 rect.Left + d, rect.Top + d), 180, 90)
        gp.AddLine(rect.Left + d, rect.Top, rect.Right - d, rect.Top)
    
        ' top right
        gp.AddArc(Rectangle.FromLTRB(rect.Right - d, rect.Top,
                                rect.Right, rect.Top + d), -90, 90)
        gp.AddLine(rect.Right, rect.Top + d, rect.Right, rect.Bottom - d)
    
        ' bottom right
        gp.AddArc(Rectangle.FromLTRB(rect.Right - d, rect.Bottom - d,
                                rect.Right, rect.Bottom), 0, 90)
        gp.AddLine(rect.Right - d, rect.Bottom, rect.Left + d, rect.Bottom)
    
        ' bottom left
        gp.AddArc(Rectangle.FromLTRB(rect.Left, rect.Bottom - d,
                                rect.Left + d, rect.Bottom), 90, 90)
        gp.AddLine(rect.Left, rect.Bottom - d, rect.Left, rect.Top + d)
    
        gp.CloseFigure()
        Using p As New Pen(BackColor), br = New SolidBrush(BackColor)
            g.DrawPath(p, gp)
            g.FillPath(br, gp)
        End Using
    End Using
    

    Note that when trying this on smaller images, it can help to create a much larger image, say 2, 4 or 8 times the final size, draw the rounded rect (using a radius * {2, 4 or 8}) then shrink down the result. This makes for nicer corners.1

    Result using a 250x250 image and radius of 42:

    enter image description here

    The Picturebox has a fixed border and is on a panel (yellowish), to proof the transparency and edges. Use a smaller diameter for smaller cuts. Also, for max flexibility, mine is in a class which allows all those hardcoded Font, Size and Color params to be specified - the image shows MistyRose used for the text color.

    1 The supersizing helps on small images or sharp curves. My 250 image with a very small curve (Radius == 8 ) looks kind of bad.