Udostępnij za pośrednictwem


Obrót — przekształcenie

Eksplorowanie efektów i animacji możliwych za pomocą przekształcenia obracania SkiaSharp

Dzięki przekształceniu obróceniu obiekty graficzne SkiaSharp łamią się bez ograniczeń wyrównania do osi poziomych i pionowych:

Tekst obracany wokół środka

W przypadku obracania obiektu graficznego wokół punktu (0, 0), SkiaSharp obsługuje zarówno metodę RotateDegrees , jak i metodę RotateRadians :

public void RotateDegrees (Single degrees)

public Void RotateRadians (Single radians)

Okrąg 360 stopni jest taki sam jak 2 radianyπ, więc łatwo jest przekonwertować między dwiema jednostkami. Użyj niezależnie od tego, co jest wygodne. Wszystkie funkcje trygonometryczne w klasie .NET Math używają jednostek radianów.

Obrót jest zgodnie z ruchem wskazówek zegara w celu zwiększenia kątów. (Mimo że rotacja układu współrzędnych kartezjańskich jest przeciwdługorowa zgodnie z konwencją, obrót zgodnie z ruchem wskazówek zegara jest zgodny ze współrzędnymi Y zwiększającymi się jak w SkiaSharp). Dozwolone są kąty ujemne i kąty większe niż 360 stopni.

Formuły przekształcania dla rotacji są bardziej złożone niż te do tłumaczenia i skalowania. Dla kąta α formuły przekształcania to:

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

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

Na stronie Rotacja podstawowa przedstawiono metodę RotateDegrees . Plik BasicRotate.xaml.cs wyświetla jakiś tekst z punktem odniesienia wyśrodkowany na stronie i obraca go na Slider podstawie zakresu od –360 do 360. Oto odpowiednia PaintSurface część procedury obsługi:

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

Ponieważ obrót jest wyśrodkowany wokół lewego górnego rogu kanwy, w przypadku większości kątów ustawionych w tym programie tekst jest obracany poza ekranem:

Potrójny zrzut ekranu przedstawiający stronę Rotacja podstawowa

Bardzo często należy obrócić coś wyśrodkowanego wokół określonego RotateDegrees punktu przestawnego przy użyciu tych wersji metod i RotateRadians :

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

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

Na stronie Wyśrodkowana rotacja jest podobna do obrócenia podstawowego, z tą różnicą, że rozszerzona wersja RotateDegrees elementu służy do ustawiania środka obrotu na ten sam punkt służący do umieszczania tekstu:

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

Teraz tekst obraca się wokół punktu użytego do pozycjonowania tekstu, który jest wyśrodkowany w poziomie linii bazowej tekstu:

Potrójny zrzut ekranu przedstawiający stronę Obracanie wyśrodkowane

Podobnie jak w przypadku wyśrodkowanej wersji Scale metody, wyśrodkowana wersja RotateDegrees wywołania jest skrótem. Oto metoda:

RotateDegrees (degrees, px, py);

To wywołanie jest równoważne z następującymi elementami:

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

Dowiesz się, że czasami można łączyć Translate wywołania z wywołaniami Rotate . Oto na przykład RotateDegrees wywołania i DrawText na stronie wyśrodkowana rotacja ;

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

Wywołanie RotateDegrees jest równoważne dwóm Translate wywołaniom i nieśrodkowanemu RotateDegrees:

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

Wywołanie wyświetlania DrawText tekstu w określonej lokalizacji jest równoważne Translate wywołaniu tej lokalizacji, po DrawText którym następuje w punkcie (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);

Dwa kolejne Translate wywołania anulują się nawzajem:

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

Koncepcyjnie dwa przekształcenia są stosowane w kolejności odwrotnej do sposobu ich wyświetlania w kodzie. Wywołanie DrawText wyświetla tekst w lewym górnym rogu kanwy. Wywołanie RotateDegrees obraca ten tekst względem lewego górnego rogu. Translate Następnie wywołanie przenosi tekst do środka kanwy.

Zwykle istnieje kilka sposobów łączenia rotacji i tłumaczenia. Na stronie Obrócony tekst zostanie utworzony następujący ekran:

Potrójny zrzut ekranu przedstawiający stronę Obrócony tekst

PaintSurface Oto procedura obsługi RotatedTextPage klasy:

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

Wartości xCenter i yCenter wskazują środek kanwy. Wartość yText jest nieco przesunięty z tego. Ta wartość jest współrzędną Y niezbędną do umieszczania tekstu tak, aby był naprawdę wyśrodkowany w pionie na stronie. Następnie pętla for ustawia obrót na podstawie środka kanwy. Obrót jest zwiększany o 30 stopni. Tekst jest rysowany przy użyciu yText wartości . Liczba pustych wartości przed określeniem wyrazu "ROTATE" w text wartości została określona empirycznie, aby połączenie między tymi 12 ciągami tekstowymi wydawało się być dodecagonem.

Jednym ze sposobów uproszczenia tego kodu jest zwiększanie kąta obrotu o 30 stopni za każdym razem przez pętlę po wywołaniu DrawText . Eliminuje to potrzebę wywołań do Save i Restore. Zwróć uwagę, że zmienna degrees nie jest już używana w treści for bloku:

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

Można również użyć prostej formy RotateDegrees , tworząc prefakcing pętli za pomocą wywołania , aby Translate przenieść wszystko na środek kanwy:

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

yText Zmodyfikowane obliczenie nie zawiera yCenterjuż elementu . Teraz wywołanie DrawText wyśrodkuje tekst w pionie u góry kanwy.

Ponieważ przekształcenia są koncepcyjnie stosowane w przeciwieństwie do sposobu ich wyświetlania w kodzie, często można rozpocząć od bardziej globalnych przekształceń, a następnie bardziej lokalnych przekształceń. Jest to często najprostszy sposób łączenia rotacji i tłumaczenia.

Załóżmy na przykład, że chcesz narysować obiekt graficzny, który obraca się wokół jego środka, podobnie jak planeta obraca się na osi. Ale chcesz również, aby ten obiekt obracał się wokół środka ekranu podobnie jak planeta obraca się wokół słońca.

Można to zrobić, umieszczając obiekt w lewym górnym rogu kanwy, a następnie używając animacji, aby obrócić go w tym rogu. Następnie przetłumacz obiekt poziomo jak promień orbitalny. Teraz zastosuj drugą animowaną rotację, również wokół źródła. Dzięki temu obiekt obraca się po rogu. Teraz przetłumacz na środek kanwy.

PaintSurface Oto procedura obsługi zawierająca te wywołania przekształcania w odwrotnej kolejności:

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

Pola revolveDegrees i rotateDegrees są animowane. Ten program używa innej techniki animacji na Xamarin.FormsAnimation podstawie klasy. (Ta klasa jest opisana w rozdziale 22 Bezpłatne pobieranie plików PDF z tematem Creating Mobile Apps with Xamarin.Forms) Zastąpienie OnAppearing powoduje utworzenie dwóch Animation obiektów z metodami wywołania zwrotnego, a następnie wywołanie Commit ich przez czas trwania animacji:

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

Pierwszy Animation obiekt animuje revolveDegrees od 0 stopni do 360 stopni w ciągu 10 sekund. Drugi animuje rotateDegrees od 0 stopni do 360 stopni co 1 sekundę, a także unieważnia powierzchnię, aby wygenerować kolejne wywołanie PaintSurface programu obsługi. Zastąpienie OnDisappearing anuluje te dwie animacje:

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

Ugly Analog Clock program (tak zwany, ponieważ bardziej atrakcyjny zegar analogowy zostanie opisany w późniejszym artykule) używa obrotu, aby narysować minuty i godziny zegara i obracać ręce. Program rysuje zegar przy użyciu dowolnego układu współrzędnych na podstawie okręgu, który jest wyśrodkowany w punkcie (0, 0) z promieniem 100. Używa tłumaczenia i skalowania, aby rozwinąć i wyśrodkować ten okrąg na stronie.

Wywołania Translate i Scale mają zastosowanie globalnie do zegara, więc są to pierwsze wywołania, które mają być wywoływane po zainicjowaniu SKPaint obiektów:

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

Istnieje 60 znaków o dwóch różnych rozmiarach, które muszą być rysowane w okręgu przez całą dobę. Wywołanie DrawCircle rysuje ten okrąg w punkcie (0, –90), który względem środka zegara odpowiada 12:00. Wywołanie RotateDegrees zwiększa kąt obrotu o 6 stopni po każdym znaczniku znacznika. Zmienna angle jest używana wyłącznie do określania, czy jest rysowany duży okrąg lub mały okrąg:

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

PaintSurface Na koniec program obsługi uzyskuje bieżący czas i oblicza stopnie rotacji godzin, minut i sekund. Każda ręka jest rysowana w pozycji 12:00, tak aby kąt obrotu był względem tego:

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

Zegar jest z pewnością funkcjonalny, chociaż ręce są raczej surowe:

Potrójny zrzut ekranu przedstawiający stronę Tekst zegara brzydkiego

Aby uzyskać bardziej atrakcyjny zegar, zobacz artykuł SVG Path Data in SkiaSharp (Dane ścieżki SVG w usłudze SkiaSharp).