Drawing a Triangle With Rounded Corners

Draw triangle with rounded corner

For more control over the rounded corners (beyond using a Stroke), you can combine 3 lines for the sides, and 3 bezier curves for the rounded corners. Use linear interpolation to get the start/end point of the lines and curves, with the corner points being the control point for the curves.

public Point interpolate(Point p1, Point p2, double t){
return new Point((int)Math.round(p1.x * (1-t) + p2.x*t),
(int)Math.round(p1.y * (1-t) + p2.y*t));
}

Point p1 = new Point(50,10);
Point p2 = new Point(10,100);
Point p3 = new Point(100,100);

Point p1p2a = interpolate(p1, p2, 0.2);
Point p1p2b = interpolate(p1, p2, 0.8);

Point p2p3a = interpolate(p2, p3, 0.2);
Point p2p3b = interpolate(p2, p3, 0.8);

Point p3p1a = interpolate(p3, p1, 0.2);
Point p3p1b = interpolate(p3, p1, 0.8);
...

g.drawLine(p1p2a.x, p1p2a.y, p1p2b.x, p1p2b.y);
g.drawLine(p2p3a.x, p2p3a.y, p2p3b.x, p2p3b.y);
g.drawLine(p3p1a.x, p3p1a.y, p3p1b.x, p3p1b.y);
QuadCurve2D c1 = new QuadCurve2D.Double(p1p2b.x, p1p2b.y, p2.x, p2.y, p2p3a.x, p2p3a.y);
QuadCurve2D c2 = new QuadCurve2D.Double(p2p3b.x, p2p3b.y, p3.x, p3.y, p3p1a.x, p3p1a.y);
QuadCurve2D c3 = new QuadCurve2D.Double(p3p1b.x, p3p1b.y, p1.x, p1.y, p1p2a.x, p1p2a.y);
g.draw(c1);
g.draw(c2);
g.draw(c3);

In the above code, you can adjust the t parameter passed to interpolate to change how rounded the corners are.

You can also append all of these into a Path2D. Path2D implements the Shape interface, which among other things allows on to pass the object to Graphics2D.fill to fill the Shape

Path2D path = new Path2D.Double();
AffineTransform at = new AffineTransform();
path.moveTo(p1p2a.x, p1p2a.y);
path.lineTo(p1p2b.x, p1p2b.y);
path.append(c1.getPathIterator(at), true);
path.lineTo(p2p3b.x, p2p3b.y);
path.append(c2.getPathIterator(at), true);
path.lineTo(p3p1b.x, p3p1b.y);
path.append(c3.getPathIterator(at), true);
path.closePath();
g.fill(path);

Drawing a triangle with rounded corners

It is not possible out of the box.

You can create rounded polygons like this:

  • Change each line by making it shorter (on both ends) by your chosen number of pixels, now line AB is line A1B1..
  • Create a GraphicsPath which consists of these lines and in between each pair of lines a curve that connects those new endpoints
  • To better control the curves it would help to add a point in the middle between the original corners and the connection of the new endpoints

For a Triangle ABC this would mean to

  • Create A1, A2, B1, B2, C1, C2
  • Then calculate the middle points A3, B3, C3
  • and finally adding to a GraphicsPath:

    • Line A1B1, Curve B1B3B2, Line B2C1, Curve C1C3C2, Line C2A2 and Curve A2A3A1.

enter image description here

Here is a piece of code that uses this method:

A GraphicsPaths to be used in the Paint event:

GraphicsPath GP = null;

A test method with a list of points, making up a triangle - (*) we overdraw by two points to make things easier; one could add the final lines and curves by ading a few lines of code instead..

private void TestButton_Click(object sender, EventArgs e)
{
Point A = new Point(5, 50);
Point B = new Point(250, 100);
Point C = new Point(50, 250);

List<Point> points = new List<Point>();
points.Add(A);
points.Add(B);
points.Add(C);
points.Add(A); // *
points.Add(B); // *

GP = roundedPolygon(points.ToArray(), 20);

panel1.Invalidate();
}

private void panel1_Paint(object sender, PaintEventArgs e)
{
if (GP == null) return;
using (Pen pen = new Pen(Brushes.BlueViolet, 3f))
e.Graphics.DrawPath(pen, GP);
}


GraphicsPath roundedPolygon(Point[] points, int rounding)
{
GraphicsPath GP = new GraphicsPath();
List<Line> lines = new List<Line>();
for (int p = 0; p < points.Length - 1; p++)
lines.Add( shortenLine(new Line(points[p], points[p+1]), rounding) );
for (int l = 0; l < lines.Count - 1; l++)
{
GP.AddLine(lines[l].P1, lines[l].P2);
GP.AddCurve(new Point[] { lines[l].P2,
supPoint(lines[l].P2,points[l+1], lines[l+1].P1),
lines[l+1].P1 });

}
return GP;
}

// a simple structure
struct Line
{
public Point P1; public Point P2;
public Line(Point p1, Point p2){P1 = p1; P2 = p2;}
}

// routine to make a line shorter on both ends
Line shortenLine(Line line, int byPixels)
{
float len = (float)Math.Sqrt(Math.Pow(line.P1.X - line.P2.X, 2)
+ Math.Pow(line.P1.Y - line.P2.Y, 2));
float prop = (len - byPixels) / len;
Point p2 = pointBetween(line.P1, line.P2, prop);
Point p1 = pointBetween(line.P1, line.P2, 1 - prop);
return new Line(p1,p2);
}

// with a proportion of 0.5 the point sits in the middle
Point pointBetween(Point p1, Point p2, float prop)
{
return new Point((int)(p1.X + (p2.X - p1.X) * prop),
(int)(p1.Y + (p2.Y - p1.Y) * prop));
}

// a supporting point, change the second prop (**) to change the rounding shape!
Point supPoint(Point p1, Point p2, Point p0)
{
Point p12 = pointBetween(p1, p2, 0.5f);
return pointBetween(p12, p0, 0.5f); // **
}

Example:

enter image description here

How do I draw a triangle with a corner radius?

Maybe you should try this:

let trianglePath = UIBezierPath()
trianglePath.lineJoinStyle = .round
trianglePath.lineWidth = 25
trianglePath.move(to: CGPoint(x: 5.0, y: 10.0))
trianglePath.addLine(to: CGPoint(x: 130, y: 280.0))
trianglePath.addLine(to: CGPoint(x: 265.0, y: 10.0))
trianglePath.close()

That gives you this in Playground:
enter image description here

How to draw a triangle with one rounded corner programatically with SVG?

There are several ways to do it, depending on what sort of curve you want. The simplest way is probably to use Q/q as you were attempting.

Calculate the endpoints, leading into and out of the curve, by calculating a position along that line segment. For instance in the second SVG I have chosen a point 80% along the first line (20,120 -> 70,20).

x = x0 + 80% * (x1 - x0)
= 20 + 80% * (70 - 20)
= 60

y = y0 + 80% * (y1 - y0)
= 120 + 80% * (20 - 120)
= 120 + -80
= 40

and the same for the line exiting the curved corner. Except, of course this time it will only be 20% aaway from the corner.

Once you have those two points, just use the original corner point as the control point (first coordinate pair) in the Q command.

So the original corner

M 20,120
L 70,20
L 120,120

becomes

M 20 120
L 60 40
Q 70 20 80 40
L 120 120

As shown in the third SVG below.

<p>Triangle</p><svg height="200" width="200" style="margin: 20px">  <path d="M 20 120           L 70 20           L 120 120               Z"         fill="LightBlue"         stroke="Blue"         stroke-width="10" /> </svg>
<svg height="200" width="200" style="margin: 20px"> <path d="M 20 120 L 60 40 L 80 40 L 120 120 Z" fill="LightBlue" stroke="Blue" stroke-width="10" /> </svg>
<svg height="200" width="200" style="margin: 20px"> <path d="M 20 120 L 60 40 Q 70 20 80 40 L 120 120 Z" fill="LightBlue" stroke="Blue" stroke-width="10" /> </svg>

UIBezierPath Triangle with rounded edges

Edit

FWIW: This answer serves its educational purpose by explaining what CGPathAddArcToPoint(...) does for you. I would highly recommend that you read through it as it will help you understand and appreciate the CGPath API. Then you should go ahead and use that, as seen in an0's answer, instead of this code when you round edges in your app. This code should only be used as a reference if you want to play around with and learn about geometry calculations like this.


Original answer

Because I find questions like this so fun, I had to answer it :)

This is a long answer. There is no short version :D


Note: For my own simplicity, my solution is making some assumptions about the points that are being used to form the triangle such as:

  • The area of the triangle is large enough to fit the rounded corner (e.g. the height of the triangle is greater than the diameter of the circles in the corners. I'm not checking for or trying to prevent any kind of strange results that may happen otherwise.
  • The corners are listed in counter clock-wise order. You could make it work for any order but it felt like a fair enough constraint to add for simplicity.

If you wanted, you could use the same technique to round any polygon as long as it's strictly convex (i.e. not a pointy star). I won't explain how to do it though but it follows the same principle.


It all starts of with a triangle, that you want to round the corners of with some radius, r:

enter image description here

The rounded triangle should be contained in the pointy triangle so the first step is to find the locations, as close to the corners as possible, where you can fit a circle with the radius, r.

A simple way of doing this is to create 3 new lines parallel to the 3 sides in the triangle and shift each of the the distance r inwards, orthogonal to the side of the original side.

To do this you calculate the slope/angle of each line and the offset to apply to the two new points:

CGFloat angle = atan2f(end.y - start.y,
end.x - start.x);

CGVector offset = CGVectorMake(-sinf(angle)*radius,
cosf(angle)*radius);

Note: for clarity I'm using the CGVector type (available in iOS 7), but you can just as well use a point or a size to work with previous OS versions.

then you add the offset to both start and end points for each line:

CGPoint offsetStart = CGPointMake(start.x + offset.dx,
start.y + offset.dy);

CGPoint offsetEnd = CGPointMake(end.x + offset.dx,
end.y + offset.dy);

When you do tho you will see that the three lines intersect each other in three places:

enter image description here

Each intersection point is exactly the distance r from two of the sides (assuming that the triangle is large enough, as stated above).

You can calculate the intersection of two lines as:

//       (x1⋅y2-y1⋅x2)(x3-x4) - (x1-x2)(x3⋅y4-y3⋅x4)
// px = –––––––––––––––––––––––––––––––––––––––––––
// (x1-x2)(y3-y4) - (y1-y2)(x3-x4)

// (x1⋅y2-y1⋅x2)(y3-y4) - (y1-y2)(x3⋅y4-y3⋅x4)
// py = –––––––––––––––––––––––––––––––––––––––––––
// (x1-x2)(y3-y4) - (y1-y2)(x3-x4)

CGFloat intersectionX = ((x1*y2-y1*x2)*(x3-x4) - (x1-x2)*(x3*y4-y3*x4)) / ((x1-x2)*(y3-y4) - (y1-y2)*(x3-x4));
CGFloat intersectionY = ((x1*y2-y1*x2)*(y3-y4) - (y1-y2)*(x3*y4-y3*x4)) / ((x1-x2)*(y3-y4) - (y1-y2)*(x3-x4));

CGPoint intersection = CGPointMake(intersectionX, intersectionY);

where (x1, y1) to (x2, y2) is the first line and (x3, y3) to (x4, y4) is the second line.

If you then put a circle, with the radius r, on each intersection point you can see that it will indeed for the rounded triangle (ignoring the different line widths of the triangle and the circles):

enter image description here

Now to create the rounded triangle you want to create a path that changes from a line to an arc to a line (etc.) on the points where the original triangle is orthogonal to the intersection points. This is also the point where the circles tangent the original triangle.

Knowing the slopes of all 3 sides in the triangle, the corner radius and the center of the circles (the intersection points), the start and stop angle for each rounded corner is the slope of that side - 90 degrees. To group these things together, I created a struct in my code, but you don't have to if you don't want to:

typedef struct {
CGPoint centerPoint;
CGFloat startAngle;
CGFloat endAngle;
} CornerPoint;

To reduce code duplication I created a method for myself that calculates the intersection and the angles for one point given a line from one point, via another, to a final point (it's not closed so it's not a triangle):

enter image description here

The code is as follows (it's really just the code that I've shown above, put together):

- (CornerPoint)roundedCornerWithLinesFrom:(CGPoint)from
via:(CGPoint)via
to:(CGPoint)to
withRadius:(CGFloat)radius
{
CGFloat fromAngle = atan2f(via.y - from.y,
via.x - from.x);
CGFloat toAngle = atan2f(to.y - via.y,
to.x - via.x);

CGVector fromOffset = CGVectorMake(-sinf(fromAngle)*radius,
cosf(fromAngle)*radius);
CGVector toOffset = CGVectorMake(-sinf(toAngle)*radius,
cosf(toAngle)*radius);


CGFloat x1 = from.x +fromOffset.dx;
CGFloat y1 = from.y +fromOffset.dy;

CGFloat x2 = via.x +fromOffset.dx;
CGFloat y2 = via.y +fromOffset.dy;

CGFloat x3 = via.x +toOffset.dx;
CGFloat y3 = via.y +toOffset.dy;

CGFloat x4 = to.x +toOffset.dx;
CGFloat y4 = to.y +toOffset.dy;

CGFloat intersectionX = ((x1*y2-y1*x2)*(x3-x4) - (x1-x2)*(x3*y4-y3*x4)) / ((x1-x2)*(y3-y4) - (y1-y2)*(x3-x4));
CGFloat intersectionY = ((x1*y2-y1*x2)*(y3-y4) - (y1-y2)*(x3*y4-y3*x4)) / ((x1-x2)*(y3-y4) - (y1-y2)*(x3-x4));

CGPoint intersection = CGPointMake(intersectionX, intersectionY);

CornerPoint corner;
corner.centerPoint = intersection;
corner.startAngle = fromAngle - M_PI_2;
corner.endAngle = toAngle - M_PI_2;

return corner;
}

I then used that code 3 times to calculate the 3 corners:

CornerPoint leftCorner  = [self roundedCornerWithLinesFrom:right
via:left
to:top
withRadius:radius];

CornerPoint topCorner = [self roundedCornerWithLinesFrom:left
via:top
to:right
withRadius:radius];

CornerPoint rightCorner = [self roundedCornerWithLinesFrom:top
via:right
to:left
withRadius:radius];

Now, having all the necessary data, starts the part where we create the actual path. I'm going to rely on the fact that CGPathAddArc will add a straight line from the current point to the start point to not have to draw those lines myself (this is documented behaviour).

The only point I manually have to calculate is the start point of the path. I choose the start of the lower right corner (no specific reason). From there you just add an arc with the center in the intersection points from the start and end angles:

CGMutablePathRef roundedTrianglePath = CGPathCreateMutable();
// manually calculated start point
CGPathMoveToPoint(roundedTrianglePath, NULL,
leftCorner.centerPoint.x + radius*cosf(leftCorner.startAngle),
leftCorner.centerPoint.y + radius*sinf(leftCorner.startAngle));
// add 3 arcs in the 3 corners
CGPathAddArc(roundedTrianglePath, NULL,
leftCorner.centerPoint.x, leftCorner.centerPoint.y,
radius,
leftCorner.startAngle, leftCorner.endAngle,
NO);
CGPathAddArc(roundedTrianglePath, NULL,
topCorner.centerPoint.x, topCorner.centerPoint.y,
radius,
topCorner.startAngle, topCorner.endAngle,
NO);
CGPathAddArc(roundedTrianglePath, NULL,
rightCorner.centerPoint.x, rightCorner.centerPoint.y,
radius,
rightCorner.startAngle, rightCorner.endAngle,
NO);
// close the path
CGPathCloseSubpath(roundedTrianglePath);

Looking something like this:

enter image description here

The final result without all the support lines, look like this:

enter image description here

Triangle with one rounded corner

I know this is a little hacky, but I don't think there is an easy way to do this with a single class.

All I've done is rotated a box 45 degrees with border-radius:10px and then contained it in another div with width set to the desired width of your arrow and overflow:hidden so that everything that spills over is invisible.

.arrow-left {  position: absolute;  width: 100px;  height: 100px;  left: 20px;  background: black;  -webkit-transform: rotate(45deg);  transform: rotate(45deg);  border-radius: 10px;}
.cover { position: absolute; height: 100px; width: 40px; overflow: hidden;}
<div class="cover">  <div class="arrow-left"></div></div>

Best way to add rounded corners to a triangle in OpenGL ES 2+ on iOS

There is no shortcut to doing this. Adding rounded corners to a triangle means it is not a triangle any more, so cannot be rendered as a single primitive.

Instead it needs to be constructed out of many triangles to give the illusion of a smooth rounded corner. So your own suggestion about 'stepping around the corner' is the correct way to do it.



Related Topics



Leave a reply



Submit