Microsoft.Maui.Graphics icon indicating copy to clipboard operation
Microsoft.Maui.Graphics copied to clipboard

ICanvas.FillRoundedRectangle/DrawRoundedRectangle does not support separate x-radius and y-radius

Open wieslawsoltes opened this issue 2 years ago • 4 comments

In SkiaSharp it is possible to set separate x and y radius for rounded rectangle.

public void AddRoundRect (SkiaSharp.SKRect rect, float rx, float ry, SkiaSharp.SKPathDirection dir = SkiaSharp.SKPathDirection.Clockwise);

https://docs.microsoft.com/en-us/dotnet/api/skiasharp.skpath.addroundrect?view=skiasharp-2.80.2

wieslawsoltes avatar Jan 09 '22 12:01 wieslawsoltes

Hi @wieslawsoltes,

I think this is possible using the current library. Here's a .NET 6 console app I made to demonstrate:

image

using Microsoft.Maui.Graphics;
using Microsoft.Maui.Graphics.Skia;

// create a new image with a blue background
using SkiaBitmapExportContext context = new(400, 300, 1.0f);
ICanvas canvas = context.Canvas;
canvas.FillColor = Colors.Navy;
canvas.FillRectangle(0, 0, context.Width, context.Height);

// draw a test pattern
canvas.StrokeColor = Colors.Yellow;
canvas.StrokeSize = 3;
canvas.DrawRoundedRectangle(
    x: 50,
    y: 50,
    width: 300,
    height: 200,
    topLeftCornerRadius: 5,
    topRightCornerRadius: 20,
    bottomLeftCornerRadius: 40,
    bottomRightCornerRadius: 60);

// save output
string filePath = Path.GetFullPath("test.png");
using FileStream fs = new(filePath, FileMode.Create);
context.WriteToStream(fs);
Console.WriteLine(filePath);
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Maui.Graphics" Version="6.0.200-preview.12.852" />
    <PackageReference Include="Microsoft.Maui.Graphics.Skia" Version="6.0.200-preview.12.852" />
  </ItemGroup>

</Project>

It looks like those rounded corner methods are canvas extensions defined here: https://github.com/dotnet/Microsoft.Maui.Graphics/blob/20e0e9e8c78d37575c7c0e1a5c64170616085f0a/src/Microsoft.Maui.Graphics/CanvasExtensions.cs#L40-L45

Related: #38

swharden avatar Jan 09 '22 18:01 swharden

@swharden This is not the same. I want different radius along x-axis and different along y-axis.

image

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="100" width="100">
	<g>
		<rect id="rounded-rectangle" x="0" y="0" height="100" width="100" stroke="red" rx="10" ry="30" stroke-width="1" fill="transparent" />
	</g>
</svg>

wieslawsoltes avatar Jan 09 '22 19:01 wieslawsoltes

I want [individual corners to have a] different radius along x-axis and different along y-axis

Okay, I understand now. Thanks for clarifying this with a picture!

swharden avatar Jan 09 '22 20:01 swharden

Hi @wieslawsoltes, I created a PR to extend ICanvas to do this (#269). You may want to comment there if you have any suggestions about how to improve the API.

In the mean time this code can be used to get a rounded rectangle with custom horizontal and vertical radii:

using Microsoft.Maui.Graphics;
using Microsoft.Maui.Graphics.Skia;

public static class Program
{
    public static void Main()
    {
        // create a new image with a blue background
        using SkiaBitmapExportContext context = new(400, 300, 1.0f);
        ICanvas canvas = context.Canvas;
        canvas.FillColor = Colors.White;
        canvas.FillRectangle(0, 0, context.Width, context.Height);

        // draw a test pattern
        InspectRoundedPath(canvas, new RectangleF(50, 50, 100, 100), xRadius: 20, yRadius: 40);
        InspectRoundedPath(canvas, new RectangleF(160, 50, 10, 100), xRadius: 20, yRadius: 40);
        InspectRoundedPath(canvas, new RectangleF(50, 160, 100, 10), xRadius: 20, yRadius: 40);

        // save output
        string filePath = Path.GetFullPath("test.png");
        using FileStream fs = new(filePath, FileMode.Create);
        context.WriteToStream(fs);
        Console.WriteLine(filePath);
    }

    private static void InspectRoundedPath(ICanvas canvas, RectangleF rect, float xRadius, float yRadius)
    {
        canvas.StrokeColor = Colors.Green;
        canvas.StrokeSize = 1;
        canvas.DrawRectangle(rect);

        canvas.StrokeColor = Colors.Magenta;
        canvas.StrokeSize = 1;
        PathF roundedRectanglePath = GetRoundedRectangle(rect, xRadius, yRadius);
        canvas.DrawPath(roundedRectanglePath);
    }

    public static PathF GetRoundedRectangle(RectangleF rect, float xRadius, float yRadius)
    {
        xRadius = Math.Min(xRadius, rect.Width / 2);
        yRadius = Math.Min(yRadius, rect.Height / 2);

        float minX = Math.Min(rect.X, rect.X + rect.Width);
        float minY = Math.Min(rect.Y, rect.Y + rect.Height);
        float maxX = Math.Max(rect.X, rect.X + rect.Width);
        float maxY = Math.Max(rect.Y, rect.Y + rect.Height);

        PathF path = new();
        path.MoveTo(minX, minY + yRadius);
        path.CurveTo(minX, minY + yRadius, minX, minY, minX + xRadius, minY);
        path.LineTo(maxX - xRadius, minY);
        path.CurveTo(maxX - xRadius, minY, maxX, minY, maxX, minY + yRadius);
        path.LineTo(maxX, maxY - yRadius);
        path.CurveTo(maxX, maxY - yRadius, maxX, maxY, maxX - xRadius, maxY);
        path.LineTo(minX + xRadius, maxY);
        path.CurveTo(minX + xRadius, maxY, minX, maxY, minX, maxY - yRadius);
        path.LineTo(minX, minY + yRadius);

        return path;
    }
}

image

swharden avatar Jan 09 '22 21:01 swharden