SkiaSharp在画布的中心设置新的旋转点,并围绕该点进行旋转。

3

我以前从未使用过SkiaSharp或在编程中处理SVG和旋转点。我对矩阵的工作原理感到困惑,因为在研究时看起来非常复杂。我知道我可以将我的pivotX/Y点移动到中心,以便让我说我要旋转图像180度,并且它将与所有像素保持1:1。

注意:我没有创建这些SVG之一,它们是由公司内部的某个人创建的。

我正在使用Xamarin社区工具包获取自定义弹出窗口。我绘制的SVG总是倒置的。所以我想将其旋转180度,但我不确定如何将pivotX/Y设置在StarPath画布中心的中心。

这个问题的整个重点是我学习如何在SkiaSharp中操作矩阵,并能够通过指定degrees width/2 height/2的方法旋转我的路径。

Xamarin 代码:

<?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# 代码

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);
    }
}

当前更新的代码 - 我时间不多了,所以不能再把这个SkiaSharp再搞砸了。我将会开发第二版本的游戏,这个版本更为复杂,所以我会回头再来处理这个问题。我对于SKMatrix.PostConcat方法感到不满意,因为它使得这个方法变得过时并且无法从任何地方访问。当有人失败一关时,我的星星有两种状态,而我必须采取非常错误的方法来实现它,但这是之后要修复的事情。此外,如果画布大小改变,整个绘画就会乱掉,所有东西都会乱成一锅粥。
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);
    }
}

目前的情况是这样的。我看不到闪光,填充也没有填满整个星形……正确的图像应该是右侧的那个。

enter image description hereenter image description here


1
我还没有详细查看你的矩阵变换,但通常问题在于找到正确的执行变换的顺序。这需要一些试错。如果你逐个尝试变换,最容易得到正确的结果。首先只使用旋转和图像,不要在画布上添加其他元素。你可能需要先进行小幅度旋转,以确定它的作用方式。它是围绕一个角落旋转,还是围绕一条边的中心旋转?接下来,尝试平移和旋转。尝试两种方式:先平移再旋转?还是先旋转再平移? - ToolmakerSteve
1
...比例尺是最后添加的。因为它与旋转和平移的交互通常不明显。希望您可以在旋转+平移达到您想要的效果之后,将其添加到开头或结尾的其中一个位置,并且其中一个位置将起作用。 - ToolmakerSteve
1个回答

1

============更新答案======

在我看来,你可以将StarPath改为在中心绘制星形。

我在一个300 x 300的画布视图上制作了这个。

    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");

它在中心绘制星形。我使用的是iPhone13模拟器,其比例因子高达@3x,具有很高的分辨率。
============
感谢@ThisQRequiresASpecialist的评论。我发现变换的顺序非常重要。如果你旋转画布,坐标系统也会旋转。这意味着如果你旋转90度,x轴会变成y轴,y轴会变成x轴,这会给后面的翻译带来麻烦。因此,先进行平移,再进行旋转可能更好。
在您的情况下,要将其旋转180度并平移到中心,请尝试以下代码:
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        

=======更新内容如下=======

@ThisQRequiresASpecialist的评论中还展示了一种使用矩阵的新方法。我也制作了一个演示并且它成功了。

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);

更多信息,请参考 旋转变换

希望我的回答对你有帮助。


我在一个新的Xamarin Forms应用中尝试了你的代码,以便我可以拥有最小可重现代码。对于180度来说完美运行,但是如果我想将其旋转90度,它就会超出屏幕。我想要的完美示例是这篇文章,但我很难理解其中的数学内容。你会看到正方形正在自身中心旋转。那就是我想要实现的。 - ThisQRequiresASpecialist
canvas.RotateDegrees(180, bounds.MidX, bounds.MidY); 只需让它在中心旋转即可。我也更新了我的答案,请看一下。 - Liqun Shen-MSFT
我真的很感激你花时间帮助我。这肯定不是解决方案,从开发角度来看,更改SVG路径非常低效。我的画布大小始终在变化。我必须确保此应用程序适用于所有手机尺寸和屏幕,如果我必须为每个尺寸差异更改路径,我还要在这里再待几个星期。一定是我们都忽略了某些东西。一定有一种方法可以使SVG始终位于画布中心,无论尺寸如何。我为你的努力点了赞,但我不能接受它作为答案。 - ThisQRequiresASpecialist
在我看来,使用矩阵基本上就像进行一些平移或旋转。我的意思是仅使用矩阵可能不是将SVG放置在中心的好方法。 - Liqun Shen-MSFT
有没有其他方法可以做我需要做的事情?SVG很好,因为它们可以与画布完美缩放。但只有在代码正确时才能将它们置于中心。 - ThisQRequiresASpecialist
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接