Partager via


La transformation de rotation

Explorer les effets et les animations possibles avec la transformation de rotation SkiaSharp

Avec la transformation de rotation, les objets graphiques SkiaSharp se détachent de la contrainte d’alignement avec les axes horizontaux et verticaux :

Texte pivoté autour d’un centre

Pour faire pivoter un objet graphique autour du point (0, 0), SkiaSharp prend en charge une RotateDegrees méthode et une RotateRadians méthode :

public void RotateDegrees (Single degrees)

public Void RotateRadians (Single radians)

Un cercle de 360 degrés est identique à 2π radians, il est donc facile de convertir entre les deux unités. Utilisez la fonction la plus pratique. Toutes les fonctions trigonométriques de la classe .NET Math utilisent des unités de radians.

La rotation est dans le sens des aiguilles d’une montre pour augmenter les angles. (Bien que la rotation sur le système de coordonnées cartésien soit contre-horloge par convention, la rotation dans le sens des aiguilles d’une montre est cohérente avec les coordonnées Y augmentant comme dans SkiaSharp.) Les angles négatifs et les angles supérieurs à 360 degrés sont autorisés.

Les formules de transformation pour la rotation sont plus complexes que celles de traduction et d’échelle. Pour un angle de α, les formules de transformation sont les suivantes :

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

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

La page Rotation de base illustre la RotateDegrees méthode. Le fichier BasicRotate.xaml.cs affiche du texte avec sa ligne de base centrée sur la page et le fait pivoter en fonction d’une Slider plage de -360 à 360. Voici la partie pertinente du PaintSurface gestionnaire :

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

Étant donné que la rotation est centrée autour du coin supérieur gauche du canevas, pour la plupart des angles définis dans ce programme, le texte est pivoté hors de l’écran :

Capture d’écran triple de la page Rotation de base

Très souvent, vous souhaiterez faire pivoter quelque chose centré autour d’un point croisé dynamique spécifié à l’aide RotateDegrees de ces versions des méthodes et RotateRadians des méthodes suivantes :

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

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

La page Rotation centrée est tout comme la rotation de base, sauf que la version développée de celle-ci RotateDegrees est utilisée pour définir le centre de rotation sur le même point que celui utilisé pour positionner le texte :

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

Maintenant, le texte tourne autour du point utilisé pour positionner le texte, qui est le centre horizontal de la ligne de base du texte :

Capture d’écran triple de la page Rotation centrée

Comme avec la version centrée de la Scale méthode, la version centrée de l’appel RotateDegrees est un raccourci. Voici la méthode :

RotateDegrees (degrees, px, py);

Cet appel équivaut à ce qui suit :

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

Vous découvrirez que vous pouvez parfois combiner Translate des appels avec Rotate des appels. Par exemple, voici les appels et DrawText les RotateDegrees appels dans la page Rotation centrée ;

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

L’appel RotateDegrees équivaut à deux Translate appels et un appel non centré 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);

L’appel à afficher du DrawText texte à un emplacement particulier équivaut à un Translate appel pour cet emplacement suivi DrawText du point (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);

Les deux appels consécutifs s’annulent les uns Translate les autres :

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

Conceptuellement, les deux transformations sont appliquées dans l’ordre opposé à la façon dont elles apparaissent dans le code. L’appel DrawText affiche le texte dans le coin supérieur gauche du canevas. L’appel RotateDegrees fait pivoter ce texte par rapport au coin supérieur gauche. Ensuite, l’appel Translate déplace le texte au centre du canevas.

Il existe généralement plusieurs façons de combiner la rotation et la traduction. La page Texte pivoté crée l’affichage suivant :

Capture d’écran triple de la page Texte pivoté

Voici le PaintSurface gestionnaire de la RotatedTextPage classe :

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

Les xCenter valeurs et yCenter indiquent le centre du canevas. La yText valeur est un peu décalée de cela. Cette valeur est la coordonnée Y nécessaire pour positionner le texte afin qu’il soit véritablement centré verticalement sur la page. La for boucle définit ensuite une rotation basée sur le centre du canevas. La rotation est incrémentée de 30 degrés. Le texte est dessiné à l’aide de la yText valeur. Le nombre de vides avant le mot « ROTATE » dans la text valeur a été déterminé empiriquement pour établir la connexion entre ces 12 chaînes de texte semblent être un dodecagon.

Une façon de simplifier ce code consiste à incrémenter l’angle de rotation de 30 degrés chaque fois par le biais de la boucle après l’appel DrawText . Cela élimine le besoin d’appels à Save et Restore. Notez que la degrees variable n’est plus utilisée dans le corps du for bloc :

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

Il est également possible d’utiliser la forme simple de la RotateDegrees boucle en préparant la boucle avec un appel pour Translate déplacer tout au centre de la zone de dessin :

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

Le calcul modifié yText n’intègre yCenterplus . À présent, l’appel DrawText centre le texte verticalement en haut du canevas.

Étant donné que les transformations sont appliquées de manière conceptuelle à la façon dont elles apparaissent dans le code, il est souvent possible de commencer par des transformations plus globales, suivies de transformations plus locales. Il s’agit souvent du moyen le plus simple de combiner la rotation et la traduction.

Par exemple, supposons que vous souhaitiez dessiner un objet graphique qui tourne autour de son centre comme une planète pivotant sur son axe. Mais vous voulez également que cet objet tourne autour du centre de l’écran beaucoup comme une planète qui tourne autour du soleil.

Pour ce faire, positionnez l’objet dans le coin supérieur gauche du canevas, puis en utilisant une animation pour la faire pivoter autour de ce coin. Ensuite, traduisez l’objet horizontalement comme un rayon orbital. Appliquez maintenant une deuxième rotation animée, également autour de l’origine. Cela fait tourner l’objet autour du coin. Maintenant, traduisez au centre du canevas.

Voici le PaintSurface gestionnaire qui contient ces appels de transformation dans l’ordre inverse :

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

Les revolveDegrees champs et rotateDegrees les champs sont animés. Ce programme utilise une technique d’animation différente basée sur la Xamarin.FormsAnimation classe. (Cette classe est décrite dans le chapitre 22 de Téléchargement PDF gratuit de Création d’applications mobiles avec Xamarin.Forms) Le OnAppearing remplacement crée deux Animation objets avec des méthodes de rappel, puis les appelle Commit pour une durée d’animation :

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

Le premier Animation objet anime revolveDegrees de 0 degrés à 360 degrés sur 10 secondes. Le deuxième anime rotateDegrees de 0 degrés à 360 degrés toutes les 1 secondes et invalide également la surface pour générer un autre appel au PaintSurface gestionnaire. La OnDisappearing substitution annule ces deux animations :

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

Le programme Ugly Analog Clock (ainsi appelé parce qu’une horloge analogique plus attrayante sera décrit dans un article ultérieur) utilise la rotation pour dessiner les marques de minute et d’heure de l’horloge et pour faire pivoter les mains. Le programme dessine l’horloge à l’aide d’un système de coordonnées arbitraire basé sur un cercle centré au point (0, 0) avec un rayon de 100. Il utilise la traduction et la mise à l’échelle pour développer et centrer ce cercle sur la page.

Les Translate appels s’appliquent Scale globalement à l’horloge. Il s’agit donc des premiers à être appelés en suivant l’initialisation des SKPaint objets :

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

Il y a 60 marques de deux tailles différentes qui doivent être dessinées dans un cercle autour de l’horloge. L’appel DrawCircle dessine ce cercle au point (0, –90), qui par rapport au centre de l’horloge correspond à 12:00. L’appel RotateDegrees incrémente l’angle de rotation de 6 degrés après chaque graduation. La angle variable est utilisée uniquement pour déterminer si un grand cercle ou un petit cercle est dessiné :

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

Enfin, le PaintSurface gestionnaire obtient l’heure actuelle et calcule les degrés de rotation pour l’heure, la minute et les secondes mains. Chaque main est dessinée à la position 12:00 afin que l’angle de rotation soit relatif à ceci :

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

L’horloge est certainement fonctionnelle bien que les mains soient plutôt brutes :

Capture d’écran triple de la page Texte de l’horloge analogique laid

Pour une horloge plus attrayante, consultez l’article SVG Path Data in SkiaSharp.