Sdílet prostřednictvím


Transformace rotace

Prozkoumejte možné efekty a animace pomocí transformace otáčení SkiaSharp

Při transformaci otočení přeruší grafické objekty SkiaSharp bez omezení zarovnání vodorovnými a svislými osami:

Text otočený kolem středu

Pro otáčení grafického objektu kolem bodu (0, 0), SkiaSharp podporuje metodu RotateDegrees i metodu RotateRadians :

public void RotateDegrees (Single degrees)

public Void RotateRadians (Single radians)

Kruh 360 stupňů je stejný jako 2π radiány, takže je snadné převést mezi těmito dvěma jednotkami. Použijte, podle toho, co je vhodné. Všechny trigonometrické funkce ve třídě .NET Math používají jednotky radiánů.

Otočení je ve směru hodinových ručiček pro zvýšení úhlů. (I když rotace kartézského souřadnicového systému je podle konvence proti směru hodinových ručiček, je konzistentní se souřadnicemi Y rostoucím směrem dolů jako ve SkiaSharp.) Jsou povoleny záporné úhly a úhly větší než 360 stupňů.

Vzorce transformace pro otáčení jsou složitější než vzorce transformace pro překlad a škálování. Pro úhel α jsou transformační vzorce:

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

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

Stránka Basic Rotate ukazuje metodu RotateDegrees . V souboru BasicRotate.xaml.cs se na stránce zobrazí nějaký text se středem jeho směrného plánu a otočí ho na základě rozsahu Slider –360 až 360. Tady je relevantní část obslužné rutiny PaintSurface :

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

Vzhledem k tomu, že otočení je uprostřed levého horního rohu plátna, u většiny úhlů nastavených v tomto programu se text otočí mimo obrazovku:

Trojitý snímek obrazovky se stránkou Základní otočení

Velmi často budete chtít otočit něco na střed kolem zadaného kontingenčního bodu pomocí těchto verzí RotateDegrees a RotateRadians metod:

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

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

Stránka Otočení na střed je stejná jako základní otočení s tím rozdílem, že rozšířená verze RotateDegrees objektu slouží k nastavení středu otočení na stejný bod, který slouží k umístění textu:

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

Teď se text otočí kolem bodu použitého k umístění textu, což je vodorovné středy účaří textu:

Trojitý snímek obrazovky se stránkou Otočení na střed

Stejně jako u verze metody na střed Scale je středová verze RotateDegrees volání zkratka. Tady je metoda:

RotateDegrees (degrees, px, py);

Toto volání odpovídá následujícímu:

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

Zjistíte, že někdy můžete kombinovat Translate hovory s voláními Rotate . Tady jsou RotateDegrees například volání a DrawText volání na stránce Na střed otočit ;

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

Volání RotateDegrees je ekvivalentní dvěma Translate voláním a necentrovaným RotateDegreesvoláním:

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

Volání DrawText pro zobrazení textu v určitém umístění odpovídá Translate volání pro dané místo následované DrawText v bodě (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);

Dvě po sobě jdoucí Translate volání se zruší:

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

Koncepčně se tyto dvě transformace použijí v opačném pořadí, než jak se zobrazují v kódu. Volání DrawText zobrazí text v levém horním rohu plátna. Volání RotateDegrees otočí text vzhledem k levému hornímu rohu. Translate Volání pak přesune text do středu plátna.

Existuje obvykle několik způsobů, jak zkombinovat otočení a překlad. Stránka Otočený text vytvoří následující zobrazení:

Trojitý snímek obrazovky se stránkou Otočený text

Tady je PaintSurface obslužná rutina RotatedTextPage třídy:

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

Hodnoty xCenter označují yCenter střed plátna. Hodnota yText je od toho trochu posunutá. Tato hodnota je souřadnice Y potřebná k umístění textu tak, aby byla skutečně svisle na střed na stránce. Smyčka for pak nastaví otočení na základě středu plátna. Otočení je v přírůstcích o 30 stupňů. Text se nakreslí pomocí yText hodnoty. Počet prázdných hodnot před slovem "OTOČIT" v text hodnotě byl určen empiricky, aby spojení mezi těmito 12 textovými řetězci vypadalo jako dodecagon.

Jedním ze způsobů, jak tento kód zjednodušit, je zvýšit úhel otáčení o 30 stupňů pokaždé, když prochází smyčkou DrawText po volání. To eliminuje potřebu volání Save a Restore. Všimněte si, že degrees proměnná se už nepoužívá v těle for bloku:

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

Jednoduchou formu RotateDegrees smyčky je také možné použít tak, že předvoláme smyčku voláním Translate pro přesunutí všeho do středu plátna:

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

Upravený yText výpočet již neobsahuje yCenter. DrawText Teď volání zacentruje text svisle v horní části plátna.

Vzhledem k tomu, že se transformace koncepčně aplikují na opak toho, jak se zobrazují v kódu, je často možné začít s více globálními transformacemi a následně s dalšími místními transformacemi. To je často nejjednodušší způsob, jak kombinovat otočení a překlad.

Předpokládejme například, že chcete nakreslit grafický objekt, který se otáčí kolem středu podobně jako planeta rotující na ose. Ale také chcete, aby se tento objekt otáčel kolem středu obrazovky podobně jako planeta otáčející se kolem slunce.

Můžete to udělat tak, že umístíte objekt do levého horního rohu plátna a pak ho pomocí animace otočíte kolem tohoto rohu. Dále přeložte objekt vodorovně jako orbitální poloměr. Teď použijte druhé animované otočení, také kolem původu. Objekt se tak otočí kolem rohu. Teď přeložte na střed plátna.

Tady je obslužná rutina PaintSurface , která obsahuje tato volání transformace v obráceném pořadí:

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

Pole revolveDegrees a rotateDegrees pole jsou animované. Tento program používá jinou animační techniku založenou na Xamarin.FormsAnimation třídě. (Tato třída je popsaná v kapitole 22 Zdarma PDF ke stažení Vytváření mobilních aplikací s Xamarin.Forms) Přepsání OnAppearing vytvoří dva Animation objekty s metodami zpětného volání a pak na ně zavolá Commit po dobu trvání animace:

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

První Animation objekt se animuje revolveDegrees z 0 stupňů do 360 stupňů za 10 sekund. Druhý se animuje rotateDegrees od 0 stupňů do 360 stupňů každých 1 sekundu a také zneplatní povrch, aby vygeneroval další volání obslužné rutiny PaintSurface . Přepsání OnDisappearing zruší tyto dvě animace:

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

Ugly Analog Clock program (tzv. proto, že atraktivní analogové hodiny budou popsány v pozdějším článku) používá otáčení k kreslení minut a hodinových známek hodin a otáčení rukou. Program nakreslí hodiny pomocí libovolného souřadnicového systému založeného na kruhu, který je na střed v bodě (0, 0) s poloměrem 100. Používá překlad a škálování k rozbalení a zarovnání kruhu na stránce.

Volání Translate a Scale volání se použijí globálně na hodiny, takže ty jsou první, které se mají volat po inicializaci SKPaint objektů:

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

Existují 60 značek dvou různých velikostí, které musí být nakresleny v kruhu po hodinách. Volání DrawCircle nakreslí kruh v bodě (0, –90), který vzhledem ke středu hodin odpovídá 12:00. Volání RotateDegrees zvýší úhel otáčení o 6 stupňů po každé značce zaškrtnutí. Proměnná angle se používá výhradně k určení, zda je nakreslen velký kruh nebo malý kruh:

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

Nakonec obslužná PaintSurface rutina získá aktuální čas a vypočítá stupně otočení pro hodinu, minutu a druhé ruce. Každá ruka je nakreslena na pozici 12:00 tak, aby úhel otáčení byl relativní k 12:00:

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

Hodiny jsou jistě funkční, i když ruce jsou spíše hrubé:

Trojitý snímek obrazovky se stránkou Text s ugly Analog Clock Text

Atraktivnější hodiny najdete v článku SVG Path Data ve SkiaSharpu.