views:

462

answers:

3

Given an array of points, it is easy to draw a line based on these, e.g. using the GraphicsPath class.

For instance, the following array of points...

[0]: (0,0)
[1]: (100,0)
[2]: (0,100)
[3]: (100,100)

...describes a line that resembles a Z.

But here comes the challenge; I need to draw rounded corners with a radius of e.g. 10 pixels. By corners I mean the points in the line that aren't start or end points. In this case there are two corners at (0,100) and (100,0).

I've played around with beziers, curves and arcs, some of which might hold the solution - I just haven't been able to find it myself yet, since I have to be able to handle lines drawn in all angles, not just horizontal or vertical lines.

Setting the LineJoin of the Pen object to Round isn't sufficient, since this only shows with wider pens.


Edit: To clarify, I'm well aware of the bezier, curve and arc capabilities of the GraphicsPath class. I am looking for some more specific advice in regard to building the algorithm that can take any number of points, and string them together with rounded corners.


Solution

I put together the following function which returns a path representing the line with rounded corners. The function makes use of a LengthenLine function, which can be found here.

protected GraphicsPath GetRoundedLine(PointF[] points, float cornerRadius)
{
  GraphicsPath path = new GraphicsPath();
  PointF previousEndPoint = PointF.Empty;
  for (int i = 1; i < points.Length; i++)
  {
    PointF startPoint = points[i - 1];
    PointF endPoint = points[i];

    if (i > 1)
    {
      // shorten start point and add bezier curve for all but the first line segment:
      PointF cornerPoint = startPoint;
      LengthenLine(endPoint, ref startPoint, -cornerRadius);
      PointF controlPoint1 = cornerPoint;
      PointF controlPoint2 = cornerPoint;
      LengthenLine(previousEndPoint, ref controlPoint1, -cornerRadius / 2);
      LengthenLine(startPoint, ref controlPoint2, -cornerRadius / 2);
      path.AddBezier(previousEndPoint, controlPoint1, controlPoint2, startPoint);
    }
    if (i + 1 < points.Length) // shorten end point of all but the last line segment.
      LengthenLine(startPoint, ref endPoint, -cornerRadius);

    path.AddLine(startPoint, endPoint);
    previousEndPoint = endPoint;
  }
  return path;
}
A: 

This url has a description of how to draw rounded rectangles that might help you start out.

But I would think that if nothing else you would be able to add more points to your path, to give the illusion of rounded corners. So add in several points between 0,0 and 100,0. An example might be:

(0,0) (90,0) (95,5) (95,10) (0,100)

I have not tested that path in any way, just pulled some numbers that might work out of the air :).

Glenn Condron
+5  A: 

This is the function I use to draw a rectangle with rounded corners... from this you may calculate the angle of each line.

Public Sub DrawRoundRect(ByVal g As Graphics, ByVal p As Pen, ByVal x As Single, ByVal y As Single, ByVal width As Single, ByVal height As Single, ByVal radius As Single)
    Dim gp As GraphicsPath = New GraphicsPath
    gp.AddLine(x + radius, y, x + width - (radius * 2), y)
    gp.AddArc(x + width - (radius * 2), y, radius * 2, radius * 2, 270, 90)
    gp.AddLine(x + width, y + radius, x + width, y + height - (radius * 2))
    gp.AddArc(x + width - (radius * 2), y + height - (radius * 2), radius * 2, radius * 2, 0, 90)
    gp.AddLine(x + width - (radius * 2), y + height, x + radius, y + height)
    gp.AddArc(x, y + height - (radius * 2), radius * 2, radius * 2, 90, 90)
    gp.AddLine(x, y + height - (radius * 2), x, y + radius)
    gp.AddArc(x, y, radius * 2, radius * 2, 180, 90)
    gp.CloseFigure()
    g.DrawPath(p, gp)
    gp.Dispose()
End Sub

Hope this help you in the harder part of trigonometry ;)

Romias
of course you could make this more legible by substituting radius * 2 by a variable like r2. +1 anyway :)
rein
Yeah... I did it time ago, it definitly needs some refactor :D BTW, instead of the AddArc() function **Bernhof** may want to use the Splines (AddCurve I think) using just 2 points and play with the tension to get a fine radius. Then, the only thing to really calculate is the coordinates of the 2 points (x amount minus from the vertix in the two directions).
Romias
+1  A: 

Bezier curves are pretty straightforward to implement:

http://www.codeproject.com/KB/recipes/BezirCurves.aspx

Luckily you also have them as part of the GraphicsPath class if you wanna omit the gory details:

http://msdn.microsoft.com/en-us/library/system.drawing.drawing2d.graphicspath.addbezier.aspx

And you can also look into splines:

http://msdn.microsoft.com/en-us/library/system.drawing.drawing2d.graphicspath.addcurve.aspx

Pedery
As mentioned in my post, I've already played around with these. I was looking for some more specific advice, but thanks anyway :)
Bernhof
Do you know the difference between splines and bezier curves? You can easily get them to join smoothly by setting the control points properly.
Pedery
Yes, I know the difference. In my case, the lines HAVE to be straight at all times, except in the corners. Beziers and curves have the habit of distorting/curving the rest of the line somewhat, probably because I don't find it that easy setting the control points properly.
Bernhof
Ah, but then your problem should be easy to solve! Use normal lines for the straight segments and bezier curves for the corners. Remember, the control points will match the line of the derivative of the curve. You probably want to put them both where the corner of the full rectangle would have been. See here: http://en.wikipedia.org/wiki/B%C3%A9zier_curve and here http://msdn.microsoft.com/en-us/library/xt9t4wah.aspx and here http://msdn.microsoft.com/en-us/library/8kc0eez9.aspx for more info.
Pedery
That might have given me the clues needed to continue. Thanks for the links, I'll post a new question if I run into more trouble :)
Bernhof
Glad to help :)
Pedery