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 :
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 :
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 :
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 :
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 yCenter
plus . À 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 :
Pour une horloge plus attrayante, consultez l’article SVG Path Data in SkiaSharp.