Teilen über


Die Drehungstransformation

Erkunden Sie die Effekte und Animationen, die mit der SkiaSharp-Drehtransformation möglich sind

Mit der Drehtransformation brechen SkiaSharp-Grafikobjekte die Einschränkung der Ausrichtung mit den horizontalen und vertikalen Achsen frei:

Text, der um eine Mitte gedreht wurde

Zum Drehen eines grafischen Objekts um den Punkt (0, 0) unterstützt SkiaSharp sowohl eine Methode als auch eine RotateDegrees RotateRadians Methode:

public void RotateDegrees (Single degrees)

public Void RotateRadians (Single radians)

Ein Kreis von 360 Grad ist identisch mit 2π Bogenmaßen, sodass es leicht ist, zwischen den beiden Einheiten zu konvertieren. Verwenden Sie, je nachdem, was praktisch ist. Alle trigonometrischen Funktionen in der .NET-Klasse Math verwenden Einheiten von Bogenmaßen.

Drehung ist im Uhrzeigersinn zum Vergrößern von Winkeln. (Obwohl die Drehung des kartesischen Koordinatensystems im Uhrzeigersinn nach Konvention gegen den Uhrzeigersinn erfolgt, ist die Drehung im Uhrzeigersinn mit Y-Koordinaten konsistent, die sich wie in SkiaSharp erhöhen.) Negative Winkel und Winkel, die größer als 360 Grad sind, sind zulässig.

Die Transformationsformeln für Drehungen sind komplexer als die transformations- und skalierungsformeln. Bei einem Winkel von α sind die Transformationsformeln:

x' = x•cos(α) – y•sin(α)

y' = x•sin(α) + y•cos(α)

Auf der Seite "Einfaches Drehen " wird die RotateDegrees Methode veranschaulicht. Die BasicRotate.xaml.cs Datei zeigt Text mit der Basislinie auf der Seite an und dreht sie basierend auf einem Slider Bereich von -360 bis 360. Hier ist der relevante Teil des PaintSurface Handlers:

using (SKPaint textPaint = new SKPaint
{
    Style = SKPaintStyle.Fill,
    Color = SKColors.Blue,
    TextAlign = SKTextAlign.Center,
    TextSize = 100
})
{
    canvas.RotateDegrees((float)rotateSlider.Value);
    canvas.DrawText(Title, info.Width / 2, info.Height / 2, textPaint);
}

Da die Drehung um die obere linke Ecke des Zeichenbereichs zentriert ist, wird der Text für die meisten winkel, die in diesem Programm festgelegt sind, vom Bildschirm gedreht:

Dreifacher Screenshot der Seite

Sehr oft möchten Sie etwas um einen angegebenen Pivotpunkt drehen, indem Sie diese Versionen und RotateDegrees RotateRadians Methoden verwenden:

public void RotateDegrees (Single degrees, Single px, Single py)

public void RotateRadians (Single radians, Single px, Single py)

Die Seite "Zentriert drehen " ähnelt der Standarddrehung , mit der Ausnahme, dass die erweiterte Version der RotateDegrees Drehung verwendet wird, um den Drehmittelpunkt auf denselben Punkt festzulegen, der zum Positionieren des Texts verwendet wird:

using (SKPaint textPaint = new SKPaint
{
    Style = SKPaintStyle.Fill,
    Color = SKColors.Blue,
    TextAlign = SKTextAlign.Center,
    TextSize = 100
})
{
    canvas.RotateDegrees((float)rotateSlider.Value, info.Width / 2, info.Height / 2);
    canvas.DrawText(Title, info.Width / 2, info.Height / 2, textPaint);
}

Nun dreht sich der Text um den Punkt, der zum Positionieren des Texts verwendet wird. Dabei handelt es sich um die horizontale Mitte der Grundlinie des Texts:

Dreifacher Screenshot der Seite

Wie bei der zentrierten Version der Scale Methode ist die zentrierte Version des RotateDegrees Aufrufs eine Verknüpfung. Dies ist die Methode:

RotateDegrees (degrees, px, py);

Dieser Aufruf entspricht folgendem:

canvas.Translate(px, py);
canvas.RotateDegrees(degrees);
canvas.Translate(-px, -py);

Sie werden feststellen, dass Sie Anrufe manchmal mit Rotate Anrufen kombinieren Translate können. Hier sind beispielsweise die RotateDegrees und DrawText die Aufrufe auf der Seite "Zentriert drehen" .

canvas.RotateDegrees((float)rotateSlider.Value, info.Width / 2, info.Height / 2);
canvas.DrawText(Title, info.Width / 2, info.Height / 2, textPaint);

Der RotateDegrees Anruf entspricht zwei Translate Anrufen und einem nicht zentrierten RotateDegreesAnruf:

canvas.Translate(info.Width / 2, info.Height / 2);
canvas.RotateDegrees((float)rotateSlider.Value);
canvas.Translate(-info.Width / 2, -info.Height / 2);
canvas.DrawText(Title, info.Width / 2, info.Height / 2, textPaint);

Der DrawText Aufruf zum Anzeigen von Text an einem bestimmten Ort entspricht einem Translate Anruf für diesen Standort gefolgt vom DrawText Punkt (0, 0):

canvas.Translate(info.Width / 2, info.Height / 2);
canvas.RotateDegrees((float)rotateSlider.Value);
canvas.Translate(-info.Width / 2, -info.Height / 2);
canvas.Translate(info.Width / 2, info.Height / 2);
canvas.DrawText(Title, 0, 0, textPaint);

Die beiden aufeinander folgenden Translate Aufrufe brechen einander ab:

canvas.Translate(info.Width / 2, info.Height / 2);
canvas.RotateDegrees((float)rotateSlider.Value);
canvas.DrawText(Title, 0, 0, textPaint);

Konzeptionell werden die beiden Transformationen in der Reihenfolge angewendet, die sich gegenüber der Darstellung im Code befindet. Der DrawText Aufruf zeigt den Text in der oberen linken Ecke des Zeichenbereichs an. Der RotateDegrees Aufruf dreht diesen Text relativ zur oberen linken Ecke. Anschließend verschiebt der Translate Aufruf den Text in die Mitte des Zeichenbereichs.

Es gibt in der Regel mehrere Möglichkeiten zum Kombinieren von Drehung und Übersetzung. Die Seite "Gedrehter Text " erstellt die folgende Anzeige:

Dreifacher Screenshot der Seite

Dies ist der PaintSurface Handler der RotatedTextPage Klasse:

static readonly string text = "    ROTATE";
...
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
    SKImageInfo info = args.Info;
    SKSurface surface = args.Surface;
    SKCanvas canvas = surface.Canvas;

    canvas.Clear();

    using (SKPaint textPaint = new SKPaint
    {
        Color = SKColors.Black,
        TextSize = 72
    })
    {
        float xCenter = info.Width / 2;
        float yCenter = info.Height / 2;

        SKRect textBounds = new SKRect();
        textPaint.MeasureText(text, ref textBounds);
        float yText = yCenter - textBounds.Height / 2 - textBounds.Top;

        for (int degrees = 0; degrees < 360; degrees += 30)
        {
            canvas.Save();
            canvas.RotateDegrees(degrees, xCenter, yCenter);
            canvas.DrawText(text, xCenter, yText, textPaint);
            canvas.Restore();
        }
    }
}

Die xCenter Werte yCenter geben die Mitte des Zeichenbereichs an. Der yText Wert ist ein kleiner Abstand davon. Dieser Wert ist die Y-Koordinate, die erforderlich ist, um den Text so zu positionieren, dass er wirklich vertikal auf der Seite zentriert ist. Die for Schleife legt dann eine Drehung basierend auf der Mitte des Zeichenbereichs fest. Die Drehung beträgt 30 Grad. Der Text wird mit dem yText Wert gezeichnet. Die Anzahl der Leerzeichen vor dem Wort "ROTATE" im text Wert wurde empirisch bestimmt, damit die Verbindung zwischen diesen 12 Textzeichenfolgen ein Dodekagon ist.

Eine Möglichkeit, diesen Code zu vereinfachen, besteht darin, den Drehwinkel jedes Mal um 30 Grad durch die Schleife nach dem DrawText Aufruf zu erhöhen. Dies beseitigt die Notwendigkeit von Anrufen und Save Restore. Beachten Sie, dass die degrees Variable nicht mehr im Textkörper des for Blocks verwendet wird:

for (int degrees = 0; degrees < 360; degrees += 30)
{
    canvas.DrawText(text, xCenter, yText, textPaint);
    canvas.RotateDegrees(30, xCenter, yCenter);
}

Es ist auch möglich, die einfache Form RotateDegrees der Schleife mit einem Aufruf zu verwenden, um alles in die Mitte des Zeichenbereichs zu Translate verschieben:

float yText = -textBounds.Height / 2 - textBounds.Top;

canvas.Translate(xCenter, yCenter);

for (int degrees = 0; degrees < 360; degrees += 30)
{
    canvas.DrawText(text, 0, yText, textPaint);
    canvas.RotateDegrees(30);
}

Die geänderte yText yCenterBerechnung enthält nicht mehr . Jetzt zentrieren die DrawText Anrufer den Text vertikal am oberen Rand des Zeichenbereichs.

Da die Transformationen konzeptionell gegenüber der Darstellung im Code angewendet werden, ist es oft möglich, mit mehr globalen Transformationen zu beginnen, gefolgt von mehr lokalen Transformationen. Dies ist häufig die einfachste Möglichkeit, Drehung und Übersetzung zu kombinieren.

Angenommen, Sie möchten ein grafisches Objekt zeichnen, das sich ähnlich wie ein Planet auf seiner Achse um seine Mitte dreht. Sie möchten aber auch, dass sich dieses Objekt um die Mitte des Bildschirms dreht, ähnlich wie ein Planet, der sich um die Sonne dreht.

Dazu können Sie das Objekt in der oberen linken Ecke des Zeichenbereichs positionieren und dann eine Animation verwenden, um es um diese Ecke zu drehen. Als Nächstes übersetzen Sie das Objekt horizontal wie einen Orbitradius. Wenden Sie nun eine zweite animierte Drehung an, auch um den Ursprung. Dadurch dreht sich das Objekt um die Ecke. Übersetzen Sie nun in die Mitte des Zeichenbereichs.

Hier ist der Handler, der PaintSurface diese Transformationsaufrufe in umgekehrter Reihenfolge enthält:

float revolveDegrees, rotateDegrees;
...
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
    SKImageInfo info = args.Info;
    SKSurface surface = args.Surface;
    SKCanvas canvas = surface.Canvas;

    canvas.Clear();

    using (SKPaint fillPaint = new SKPaint
    {
        Style = SKPaintStyle.Fill,
        Color = SKColors.Red
    })
    {
        // Translate to center of canvas
        canvas.Translate(info.Width / 2, info.Height / 2);

        // Rotate around center of canvas
        canvas.RotateDegrees(revolveDegrees);

        // Translate horizontally
        float radius = Math.Min(info.Width, info.Height) / 3;
        canvas.Translate(radius, 0);

        // Rotate around center of object
        canvas.RotateDegrees(rotateDegrees);

        // Draw a square
        canvas.DrawRect(new SKRect(-50, -50, 50, 50), fillPaint);
    }
}

Die Felder und rotateDegrees Felder revolveDegrees werden animiert. Dieses Programm verwendet eine andere Animationstechnik basierend auf der Xamarin.FormsAnimation Klasse. (Diese Klasse wird in Kapitel 22 von Kostenloser PDF-Download der Erstellung mobiler Apps mit Xamarin.Forms) Die OnAppearing Außerkraftsetzung erstellt zwei Animation Objekte mit Rückrufmethoden und ruft Commit sie dann für eine Animationsdauer auf:

protected override void OnAppearing()
{
    base.OnAppearing();

    new Animation((value) => revolveDegrees = 360 * (float)value).
        Commit(this, "revolveAnimation", length: 10000, repeat: () => true);

    new Animation((value) =>
    {
        rotateDegrees = 360 * (float)value;
        canvasView.InvalidateSurface();
    }).Commit(this, "rotateAnimation", length: 1000, repeat: () => true);
}

Das erste Animation Objekt wird von 0 Grad auf 360 Grad über 10 Sekunden animiert revolveDegrees . Der zweite wird von 0 grad bis 360 Grad alle 1 Sekunde animiert rotateDegrees und die Oberfläche ungültig, um einen weiteren Aufruf des PaintSurface Handlers zu generieren. Die OnDisappearing Außerkraftsetzung bricht diese beiden Animationen ab:

protected override void OnDisappearing()
{
    base.OnDisappearing();
    this.AbortAnimation("revolveAnimation");
    this.AbortAnimation("rotateAnimation");
}

Das Ugly Analog Clock-Programm (so genannt, weil eine attraktivere Analoguhr in einem späteren Artikel beschrieben wird) verwendet Drehung, um die Minuten- und Stundenmarken der Uhr zu zeichnen und die Hände zu drehen. Das Programm zeichnet die Uhr mit einem beliebigen Koordinatensystem basierend auf einem Kreis, der am Punkt (0, 0) mit einem Radius von 100 zentriert ist. Es verwendet Übersetzung und Skalierung, um diesen Kreis auf der Seite zu erweitern und zu zentrieren.

Die Translate Aufrufe und Scale Aufrufe gelten global für die Uhr, sodass dies die ersten sind, die nach der Initialisierung der SKPaint Objekte aufgerufen werden sollen:

void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
    SKImageInfo info = args.Info;
    SKSurface surface = args.Surface;
    SKCanvas canvas = surface.Canvas;

    canvas.Clear();

    using (SKPaint strokePaint = new SKPaint())
    using (SKPaint fillPaint = new SKPaint())
    {
        strokePaint.Style = SKPaintStyle.Stroke;
        strokePaint.Color = SKColors.Black;
        strokePaint.StrokeCap = SKStrokeCap.Round;

        fillPaint.Style = SKPaintStyle.Fill;
        fillPaint.Color = SKColors.Gray;

        // Transform for 100-radius circle centered at origin
        canvas.Translate(info.Width / 2f, info.Height / 2f);
        canvas.Scale(Math.Min(info.Width / 200f, info.Height / 200f));
        ...
    }
}

Es gibt 60 Zeichen von zwei verschiedenen Größen, die in einem Kreis um die Uhr gezeichnet werden müssen. Der DrawCircle Aufruf zeichnet diesen Kreis am Punkt (0, –90), der relativ zur Mitte der Uhr 12:00 entspricht. Der Aufruf RotateDegrees erhöht den Drehwinkel nach jeder Markierung um 6 Grad. Die angle Variable wird ausschließlich verwendet, um zu bestimmen, ob ein großer Kreis oder ein kleiner Kreis gezeichnet wird:

void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
    ...
        // Hour and minute marks
        for (int angle = 0; angle < 360; angle += 6)
        {
            canvas.DrawCircle(0, -90, angle % 30 == 0 ? 4 : 2, fillPaint);
            canvas.RotateDegrees(6);
        }
    ...
    }
}

Schließlich ruft der PaintSurface Handler die aktuelle Uhrzeit ab und berechnet Drehungsgrade für die Stunden-, Minuten- und zweiten Hände. Jede Hand wird an der Position 12:00 gezeichnet, sodass der Drehwinkel relativ zu diesem ist:

void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
    ...
        DateTime dateTime = DateTime.Now;

        // Hour hand
        strokePaint.StrokeWidth = 20;
        canvas.Save();
        canvas.RotateDegrees(30 * dateTime.Hour + dateTime.Minute / 2f);
        canvas.DrawLine(0, 0, 0, -50, strokePaint);
        canvas.Restore();

        // Minute hand
        strokePaint.StrokeWidth = 10;
        canvas.Save();
        canvas.RotateDegrees(6 * dateTime.Minute + dateTime.Second / 10f);
        canvas.DrawLine(0, 0, 0, -70, strokePaint);
        canvas.Restore();

        // Second hand
        strokePaint.StrokeWidth = 2;
        canvas.Save();
        canvas.RotateDegrees(6 * dateTime.Second);
        canvas.DrawLine(0, 10, 0, -80, strokePaint);
        canvas.Restore();
    }
}

Die Uhr ist sicherlich funktionsfähig, obwohl die Hände ziemlich roh sind:

Dreifacher Screenshot der Seite

Eine attraktivere Uhr finden Sie im Artikel SVG Path Data in SkiaSharp.