Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SkiaSharp set new rotation point at the centre of the canvas and Rotate around that point

I'm never used SkiaSharp or dealt with SVG and rotation points in programming yet. I'm confused about how the matrix works; it looks very complex while researching. I know I can move my pivotX/Y point to the center to allow me to say I want to rotate an image 180 degrees and it would be 1:1 with all pixels.

Note: I did not create any of these SVGs, they are made by someone within the company.

I'm using Xamarin Community Toolkit to get the custom Popup. The SVG I draw is always drawn upside down. So I would like to rotate it 180 degrees but I'm not sure how to set the pivotX/Y at the center of either StarPath or the Canvas center.

Whole point of this question so I can learn how to manipulate Matrix in SkiaSharp and be able to make a method to Rotate my Path by specifying the degrees width / 2 height / 2

Xamarin Code

<?xml version="1.0" encoding="utf-8" ?>
<xct:Popup xmlns="http://xamarin.com/schemas/2014/forms"
           xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
           x:Class="uCue_Game.View.LostLifePopUp"
           xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
           xmlns:sksh="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
           Size="400, 400"
           Color="Transparent">

    <StackLayout BackgroundColor="Black"
          Opacity="1">

        <sksh:SKCanvasView x:Name="canvasView"
                           PaintSurface="canvasView_PaintSurface"
                           HorizontalOptions="FillAndExpand"
                           VerticalOptions="FillAndExpand" 
                           HeightRequest="300"
                           Opacity="1"/>
        <Label Text="Wrong Answer" 
               HorizontalOptions="FillAndExpand"
               VerticalOptions="FillAndExpand"
               FontSize="15"
               TextColor="White"
               HorizontalTextAlignment="Center"
               VerticalTextAlignment="Center"
               Opacity="1"/>
    </StackLayout>
</xct:Popup>

C# Code

public partial class LostLifePopUp : Popup
{
    SKPath StarPath = SKPath.ParseSvgPathData("M492" +
    " 348 c16 -18 47 -67 69 -110 38 -76 40 -78 77 -78 55 -1 156 -24 186 -43 42" +
    " -28 34 -61 -27 -120 -29 -29 -70 -63 -90 -77 -24 -16 -35 -30 -31 -40 18 -48" +
    " 44 -170 44 -211 0 -44 -3 -49 -25 -55 -31 -8 -144 27 -209 64 l-47 27 -97 -47" +
    " c-102 -50 -157 -65 -181 -49 -20 13 -16 97 9 195 l20 79 -58 61 c-69 73 -102" +
    " 118 -102 141 0 22 66 50 177 75 l90 21 22 53 c12 29 35 74 51 100 38 59 76 63" +
    " 122 14z");

    SKPath StarFill = SKPath.ParseSvgPathData("M487" +
    " 363 c21 -24 58 -71 83 -105 32 -46 51 -64 74 -69 65 -14 201 -56 213 -65 28" +
    " -23 2 -34 -85 -35 -109 -1 -229 -31 -336 -85 -136 -69 -223 -82 -298 -43 -32" +
    " 17 -98 73 -98 84 0 4 10 18 23 32 12 14 32 37 44 51 12 15 35 29 50 33 15 4" +
    " 49 15 75 26 38 16 51 29 76 76 50 94 89 142 116 142 15 0 38 -16 63 -42z");

    SKPath StarShine = SKPath.ParseSvgPathData(
        "M0 1215 m0 -1215 m1260 0 m1260 0 m0 1215 m0 1215 m-1260 0 m-1260 0 m0" +
        " -1215z m1395 838 c35 -35 95 -164 95 -204 0 -55 -64 -92" +
        " -117 -69 -56 25 -68 54 -68 166 0 105 10 134 45 134 9 0 30 -12 45 -27z");

    SKPaint StrokeRed = new SKPaint
    {
        Style = SKPaintStyle.StrokeAndFill,
        Color = SKColors.Red,
        StrokeWidth = 5,
        StrokeCap = SKStrokeCap.Round,
        StrokeJoin = SKStrokeJoin.Round
    };

    SKPaint FillYellow = new SKPaint
    {
        Style = SKPaintStyle.Fill,
        Color = SKColors.Yellow,
        StrokeWidth = 5,
        StrokeCap = SKStrokeCap.Round,
        StrokeJoin = SKStrokeJoin.Round
    };

    SKPaint GoldShine = new SKPaint
    {
        Style = SKPaintStyle.Stroke,
        Color = SKColors.Green,
        StrokeWidth = 40,
        StrokeCap = SKStrokeCap.Round,
        StrokeJoin = SKStrokeJoin.Round
    };

    public LostLifePopUp()
    {
        InitializeComponent();

    }

    private void canvasView_PaintSurface(object sender, SKPaintSurfaceEventArgs e)
    {
        SKRect bounds;
        StarPath.GetBounds(out bounds);

        var surface = e.Surface;
        var surfaceWidth = e.RawInfo.Width;
        var surfaceHeight = e.RawInfo.Height;

        var canvas = surface.Canvas;

        canvas.Flush();
        canvas.Clear();

        var rot = SKMatrix.CreateRotationDegrees(37, surfaceWidth / 2 - 50, surfaceHeight / 2);
        canvas.SetMatrix(rot);

        canvas.Translate(surfaceWidth / 2, surfaceHeight / 2);
        canvas.Scale(0.9f * Math.Min(surfaceWidth / bounds.Width,
                                    surfaceHeight / bounds.Height));
        canvas.Translate(-bounds.MidX, -bounds.MidY);

        canvas.DrawPath(StarShine, GoldShine);
        canvas.DrawPath(StarPath, StrokeRed);
        canvas.DrawPath(StarFill, FillYellow);
    }
}

Current Updated Code - I'm running out of time so I cannot play around anymore with this SkiaSharp for now. I will be working on 2nd version of this game which is a more complex version so I will come back to this as soon as I get onto that. I'm really not happy with SKMatrix.PostConcat makes the method obsolete and inaccessible from anywhere. I have 2 states for my star when someone loses a level and I had to take a very wrong approach to achieve it but it's something for later to fix. Also if the canvas size changes the whole drawing goes to hell, it's all other the place.

private void canvasView_PaintSurface(object sender, SKPaintSurfaceEventArgs e)
{
    SKRect bounds;
    StarPath.GetBounds(out bounds);

    var surface = e.Surface;
    var surfaceWidth = e.RawInfo.Width;
    var surfaceHeight = e.RawInfo.Height;

    var canvas = surface.Canvas;

    var result = SKMatrix.CreateIdentity();
    var translate = SKMatrix.CreateTranslation(-surfaceWidth / 2, -surfaceHeight / 2);
    var rotate = SKMatrix.CreateRotationDegrees(180);
    var translate2 = SKMatrix.CreateTranslation(surfaceWidth * 2, surfaceHeight);
    SKMatrix.PostConcat(ref result, translate);
    SKMatrix.PostConcat(ref result, rotate);
    SKMatrix.PostConcat(ref result, translate2);
    StarPath.Transform(result);
    StarFill.Transform(result);

    canvas.Scale(0.8f * Math.Min(surfaceWidth / bounds.Width, surfaceHeight / bounds.Height));

    canvas.DrawPath(StarPath, WhiteOutline);
    canvas.DrawPath(StarPath, Yellow);
    canvas.DrawPath(StarFill, Shine);

    if (DrawMain == true)
    {
        canvas.Scale(0.7f * Math.Min(surfaceWidth / bounds.Width, surfaceHeight / bounds.Height));
        translate2 = SKMatrix.CreateTranslation(surfaceWidth * 2.5f, surfaceHeight * 2);
        SKMatrix.PostConcat(ref result, translate2);
        StarShine.Transform(result);
        canvas.DrawPath(StarShine, White);
    }
            
    if (DrawSecond == true)
    {
        canvas.DrawPath(StarPath, WhiteOutline);
    }
}

This is what is looks like currently. I cant see the Shine nor does the fill actually fill the entire star... Right Image is what it should look like.

enter image description hereenter image description here

like image 985
ThisQRequiresASpecialist Avatar asked Dec 22 '25 21:12

ThisQRequiresASpecialist


1 Answers

============update answer======

From my point of view, you could change the StarPath to draw star at the center.

I make this on a 300 X 300 canvasview

    SKPath StarPath = SKPath.ParseSvgPathData("m 492" +
    " 805.6 c 16 -18 47 -67 69 -110 c 38 -76 40 -78 77 -78 c 55 -1 156 -24 186 -43" +
    " c 42 -28 34 -61 -27 -120 c -29 -29 -70 -63 -90 -77 c -24 -16 -35 -30 -31 -40" +
    "  c 18 -48 44 -170 44 -211 c 0 -44 -3 -49 -25 -55 c -31 -8 -144 27 -209 64" +
    "  l -47 27 l -97 -47 c -102 -50 -157 -65 -181 -49 c -20 13 -16 97 9 195 l 20 79" +
    "  l -58 61 c -69 73 -102 118 -102 141 c 0 22 66 50 177 75 l 90 21 l 22 53 c 12 29" +
    "  35 74 51 100 c 38 59 76 63 122 14 z");

It draws the star at the center. I use iphone13 simulator which has a high resolution of @3x scale factor.

============Origin answer

Thanks for @ThisQRequiresASpecialist's comment. I found that the order of transform is quite important. If you rotate the canvas, the coordinate system also rotate. That means if you rotate 90 degree, x-axis changes to y-axis, and y-axis changes to x-axis which cause troubles for the later translation. So translation first and then rotate could be better.

In your case, to rotate 180 degree and translate to the center, just try the following code:

void canvasView_PaintSurface(System.Object sender, SkiaSharp.Views.Forms.SKPaintSurfaceEventArgs e)
{   
    SKImageInfo info = e.Info;
    SKRect bounds;           
    var surface = e.Surface;           
    var surfaceWidth = info.Width;
    var surfaceHeight = info.Height;     
    StarPath.GetTightBounds(out bounds);
    
    canvas.Translate(surfaceWidth/2 - bounds.MidX,  (surfaceHeight / 2 - bounds.MidY)); // Translate to the center
    canvas.RotateDegrees(180, bounds.MidX, bounds.MidY); // rotate 180        

... }

=======Update here=======

@ThisQRequiresASpecialist's comment also show a new method using Matrix. I also made a demo and it worked.

var result = SKMatrix.CreateIdentity();
var rotate = SKMatrix.CreateRotationDegrees(180, bounds.MidX, bounds.MidY);
var translate = SKMatrix.CreateTranslation(surfaceWidth / 2 - bounds.MidX, -(surfaceHeight / 2 - bounds.MidY));
SKMatrix.PostConcat(ref result, translate);
SKMatrix.PostConcat(ref result, rotate);           
StarPath.Transform(result);
canvas.DrawPath(StarPath, StrokeRed);

For more information, you could refer to The Rotate Transform

Hope my answer could help you.

like image 158
Felix Shen Avatar answered Dec 24 '25 09:12

Felix Shen



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!