ImageSharp.Drawing icon indicating copy to clipboard operation
ImageSharp.Drawing copied to clipboard

Feature Request: Optional rounded corner in RectangularPolygon

Open Spawnkid opened this issue 6 years ago • 12 comments

Though this would work on images https://github.com/SixLabors/Samples/blob/master/ImageSharp/AvatarWithRoundedCorner/Program.cs this is not possible in Shapes where you need to use Pen (all paths should be closed). See attached sample rounded dashed rectangle

It might be a good idea to add an optional parameter in RectangularPolygon constructor to have a radius value of the rounded corner. Thank You.

Spawnkid avatar Dec 21 '18 07:12 Spawnkid

Also really want something like this! Any ETA

ThaDaVos avatar Feb 05 '19 15:02 ThaDaVos

Any progress on this? Or is there a workaround for drawing rectangles with rounded corners someone can point me to?

JackSteel97 avatar Dec 17 '20 17:12 JackSteel97

@JackSteel97 you can use this code to produce a rounded rectanle path:

https://github.com/SixLabors/Samples/blob/38faba5caf866c0168ea67a5e7c37c06d3043775/ImageSharp/AvatarWithRoundedCorner/Program.cs#L72-L91

Then use one of the overloads in DrawPathExtensions to draw it: https://docs.sixlabors.com/api/ImageSharp.Drawing/SixLabors.ImageSharp.Drawing.Processing.DrawPathExtensions.html#SixLabors_ImageSharp_Drawing_Processing_DrawPathExtensions_Draw_IImageProcessingContext_SixLabors_ImageSharp_Drawing_Processing_IPen_SixLabors_ImageSharp_Drawing_IPath_

antonfirsov avatar Dec 18 '20 11:12 antonfirsov

@antonfirsov This provides no options for positioning the new rectangle and no option to fill the rectangle either??

JackSteel97 avatar Dec 18 '20 12:12 JackSteel97

Positioning the new rectangle

You can transform IPathCollection with IPathCollection.Transform. Use it in tandem with Matrix3x.CreateTranslation

Fill the rectangle

Sorry second link was wrong in my previous comment. You can use imageProcessingContext.Draw(IPen pen, IPath path) for drawing the outline, and imageProcessingContext.Fill(IBrush brush, IPath path) to fill it.

antonfirsov avatar Dec 18 '20 14:12 antonfirsov

@antonfirsov could you please explain how to make those corners transparent? I need to draw one image atop of another and it should have rounded corners.

The only result that I can achieve using example above is colored corners. If Color.Transparent is provided they are not visible at all (obviously). Is there a way to use those corners as a mask and hide parts of image that they overlap?

ggolda avatar Mar 02 '22 00:03 ggolda

Here is method that converts a RectangularPolygon to a path with rounded corners.

Personally, I feel that implementing this as a user is straightforward enough, and this functionality needn't be baked into the core library.

private static IPath ApplyRoundCorners(RectangularPolygon rectangularPolygon, float radius)
{
	var squareSize = new SizeF(radius, radius);
	var ellipseSize = new SizeF(radius * 2, radius * 2);
	var offsets = new[]
	{
		(0, 0),
		(1, 0),
		(0, 1),
		(1, 1),
	};
	var holes = offsets.Select(
		offset =>
		{
			var squarePos = new PointF(
				offset.Item1 == 0 ? rectangularPolygon.Left : rectangularPolygon.Right - radius,
				offset.Item2 == 0 ? rectangularPolygon.Top : rectangularPolygon.Bottom - radius
			);
			var circlePos = new PointF(
				offset.Item1 == 0 ? rectangularPolygon.Left + radius : rectangularPolygon.Right - radius,
				offset.Item2 == 0 ? rectangularPolygon.Top + radius : rectangularPolygon.Bottom - radius
			);
			return new RectangularPolygon(squarePos, squareSize)
				.Clip(new EllipsePolygon(circlePos, ellipseSize));
		}
	);
	return rectangularPolygon.Clip(holes);
}

AndrewShepherd avatar Jun 27 '22 05:06 AndrewShepherd

Here is method that converts a RectangularPolygon to a path with rounded corners.

Personally, I feel that implementing this as a user is straightforward enough, and this functionality needn't be baked into the core library.

private static IPath ApplyRoundCorners(RectangularPolygon rectangularPolygon, float radius)
{
	var squareSize = new SizeF(radius, radius);
	var ellipseSize = new SizeF(radius * 2, radius * 2);
	var offsets = new[]
	{
		(0, 0),
		(1, 0),
		(0, 1),
		(1, 1),
	};
	var holes = offsets.Select(
		offset =>
		{
			var squarePos = new PointF(
				offset.Item1 == 0 ? rectangularPolygon.Left : rectangularPolygon.Right - radius,
				offset.Item2 == 0 ? rectangularPolygon.Top : rectangularPolygon.Bottom - radius
			);
			var circlePos = new PointF(
				offset.Item1 == 0 ? rectangularPolygon.Left + radius : rectangularPolygon.Right - radius,
				offset.Item2 == 0 ? rectangularPolygon.Top + radius : rectangularPolygon.Bottom - radius
			);
			return new RectangularPolygon(squarePos, squareSize)
				.Clip(new EllipsePolygon(circlePos, ellipseSize));
		}
	);
	return rectangularPolygon.Clip(holes);
}

this my test code

[Fact]
    public void ApplyRoundCornersTest()
    {
        var rect = new RectangularPolygon(0, 0, 100, 100);
        var path = ApplyRoundCorners(rect, 15f);
        var img = GetSquareRgba32(200, Color.Black);
        img.Mutate(w => w.Draw(Color.White, 1, path.Transform(Matrix3x2.CreateTranslation(50, 50))));
        img.ShowWithCoordinates();
    }

this your result

image

It is obvious that errors are inevitable in user code, even among experts

This is the rounded rectangle code I wrote, and I'm not sure if there might be any exceptions


    /// <summary>
    /// rounded rectangle
    /// </summary>
    /// <param name="width"></param>
    /// <param name="height"></param>
    /// <param name="cornerRadius"></param>
    /// <returns></returns>
    public static IPath CreateRoundedRectanglePath(int width, int height, float cornerRadius)
    {
        var pathBuilder = new PathBuilder();
        width--;
        height--;

        var radius = 2 * cornerRadius;

        // Make sure the rounded corners are no larger than half the size of the rectangle
        cornerRadius = Math.Min(width * 0.5f, Math.Min(height * 0.5f, cornerRadius));

        // Start drawing path
        pathBuilder.StartFigure();

        // upperBorder
        pathBuilder.AddLine(cornerRadius, 0, width - cornerRadius, 0);

        // Upper right rounded corner
        pathBuilder.AddArc(new RectangleF(width - radius, 0, radius, radius), 0, 270, 90);

        // right line
        pathBuilder.AddLine(width, cornerRadius, width, height - cornerRadius);

        // Lower right rounded corner
        pathBuilder.AddArc(new RectangleF(width - radius, height - radius, radius, radius), 0, 0, 90);

        // lower border
        pathBuilder.AddLine(width - cornerRadius, height, cornerRadius, height);

        // Lower left rounded corner
        pathBuilder.AddArc(new RectangleF(0, height - radius, radius, radius), 0, 90, 90);

        // left line
        pathBuilder.AddLine(0, height - cornerRadius, 0, cornerRadius);

        // Upper left rounded corner
        pathBuilder.AddArc(new RectangleF(0, 0, radius, radius), 0, 180, 90);

        // Close the path to form a complete rectangle
        pathBuilder.CloseFigure();

        return pathBuilder.Build();
    }

this my result

image

In today's UI design, the code for rounded rectangles is evident everywhere, omnipresent. I believe that a good core framework indeed needs to embed such codes that, despite appearing particularly simplistic or even foolish, are widely used. Though simple, their widespread usage is undeniable.

inernoro avatar Dec 11 '23 02:12 inernoro

Why are you shouting?

JimBobSquarePants avatar Dec 11 '23 08:12 JimBobSquarePants

Why are you shouting?

I'm merely stating the facts objectively. If you feel offended by what I said, I'm sorry

inernoro avatar Dec 12 '23 05:12 inernoro

It is indeed my issue, as the need to submit various styles can easily disrupt the layout, leading to some misunderstandings. In fact, my text is translated using translation software. I do not have an aggressive demeanor. Most of the time, what I state are things I already know. There are matters that I think could be handled more smoothly and flexibly, and I often consult others for advice, rather than instructing them on what to do. At this point, any answers or opinions given to me are reasonable. I apologize to you once again

inernoro avatar Dec 12 '23 06:12 inernoro

All good! I was very confused! 🤣

if someone has the time to provide a PR with tests let’s add the feature.

JimBobSquarePants avatar Dec 12 '23 07:12 JimBobSquarePants