Partager via


La transformation d’échelle

Découvrez la transformation de mise à l’échelle skiaSharp pour mettre à l’échelle des objets à différentes tailles

Comme vous l’avez vu dans l’article Translate Transform , la transformation de traduction peut déplacer un objet graphique d’un emplacement à un autre. En revanche, la transformation d’échelle modifie la taille de l’objet graphique :

Un grand mot mis à l’échelle en taille

La transformation d’échelle entraîne également souvent le déplacement des coordonnées graphiques au fur et à mesure qu’elles sont plus grandes.

Plus tôt, vous avez vu deux formules de transformation qui décrivent les effets des facteurs de traduction et dxdy:

x' = x + dx

y' = y + dy

Les facteurs d’échelle et sxsy sont multipliés plutôt que additifs :

x' = sx · X

y' = sy · y

Les valeurs par défaut des facteurs de traduction sont 0 ; les valeurs par défaut des facteurs d’échelle sont 1.

La SKCanvas classe définit quatre Scale méthodes. La première Scale méthode est pour les cas où vous souhaitez le même facteur de mise à l’échelle horizontale et verticale :

public void Scale (Single s)

Il s’agit de la mise à l’échelle isotropique , mise à l’échelle identique dans les deux sens. La mise à l’échelle isotropique conserve le rapport d’aspect de l’objet.

La deuxième Scale méthode vous permet de spécifier différentes valeurs pour la mise à l’échelle horizontale et verticale :

public void Scale (Single sx, Single sy)

Cela entraîne une mise à l’échelle anisotropique . La troisième Scale méthode combine les deux facteurs de mise à l’échelle dans une valeur unique SKPoint :

public void Scale (SKPoint size)

La quatrième Scale méthode sera décrite sous peu.

La page Mise à l’échelle de base illustre la Scale méthode. Le fichier BasicScalePage.xaml contient deux Slider éléments qui vous permettent de sélectionner des facteurs de mise à l’échelle horizontal et vertical compris entre 0 et 10. Le fichier code-behind BasicScalePage.xaml.cs utilise ces valeurs pour appeler Scale avant d’afficher un rectangle arrondi avec une ligne en pointillés et dimensionné pour ajuster un texte dans le coin supérieur gauche du canevas :

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

    canvas.Clear(SKColors.SkyBlue);

    using (SKPaint strokePaint = new SKPaint
    {
        Style = SKPaintStyle.Stroke,
        Color = SKColors.Red,
        StrokeWidth = 3,
        PathEffect = SKPathEffect.CreateDash(new float[] {  7, 7 }, 0)
    })
    using (SKPaint textPaint = new SKPaint
    {
        Style = SKPaintStyle.Fill,
        Color = SKColors.Blue,
        TextSize = 50
    })
    {
        canvas.Scale((float)xScaleSlider.Value,
                     (float)yScaleSlider.Value);

        SKRect textBounds = new SKRect();
        textPaint.MeasureText(Title, ref textBounds);

        float margin = 10;
        SKRect borderRect = SKRect.Create(new SKPoint(margin, margin), textBounds.Size);
        canvas.DrawRoundRect(borderRect, 20, 20, strokePaint);
        canvas.DrawText(Title, margin, -textBounds.Top + margin, textPaint);
    }
}

Vous pouvez vous demander : Comment les facteurs de mise à l’échelle affectent-ils la valeur retournée par la MeasureText méthode de SKPaint? La réponse est : Pas du tout. Scale est une méthode de SKCanvas. Cela n’affecte rien que vous faites avec un SKPaint objet tant que vous n’utilisez pas cet objet pour afficher quelque chose sur le canevas.

Comme vous pouvez le voir, tout dessiné après l’appel Scale augmente proportionnellement :

Capture d’écran triple de la page Mise à l’échelle de base

Le texte, la largeur de la ligne en pointillés, la longueur des tirets de cette ligne, l’arrondi des angles et la marge de 10 pixels entre les bords gauche et supérieur du canevas et le rectangle arrondi sont tous soumis aux mêmes facteurs de mise à l’échelle.

Important

La plateforme Windows universelle ne restitue pas correctement le texte à l’échelle anisotropique.

La mise à l’échelle anisotropique entraîne la différence de largeur de trait pour les lignes alignées avec les axes horizontaux et verticaux. (Cela est également évident à partir de la première image de cette page.) Si vous ne souhaitez pas que la largeur du trait soit affectée par les facteurs de mise à l’échelle, définissez-la sur 0 et elle sera toujours d’un pixel, quel que soit le Scale paramètre.

La mise à l’échelle est relative au coin supérieur gauche du canevas. C’est peut-être exactement ce que vous voulez, mais ce n’est peut-être pas le cas. Supposons que vous souhaitez positionner le texte et le rectangle ailleurs sur le canevas et que vous souhaitez le mettre à l’échelle par rapport à son centre. Dans ce cas, vous pouvez utiliser la quatrième version de la Scale méthode, qui inclut deux paramètres supplémentaires pour spécifier le centre de mise à l’échelle :

public void Scale (Single sx, Single sy, Single px, Single py)

Les px paramètres et py les paramètres définissent un point parfois appelé centre de mise à l’échelle, mais dans la documentation SkiaSharp, on parle de point croisé dynamique. Il s’agit d’un point relatif au coin supérieur gauche du canevas qui n’est pas affecté par la mise à l’échelle. Toutes les mises à l’échelle se produisent par rapport à ce centre.

La page Mise à l’échelle centrée montre comment cela fonctionne. Le PaintSurface gestionnaire est similaire au programme Échelle de base, sauf que la margin valeur est calculée pour centrer le texte horizontalement, ce qui implique que le programme fonctionne le mieux en mode portrait :

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

    canvas.Clear(SKColors.SkyBlue);

    using (SKPaint strokePaint = new SKPaint
    {
        Style = SKPaintStyle.Stroke,
        Color = SKColors.Red,
        StrokeWidth = 3,
        PathEffect = SKPathEffect.CreateDash(new float[] { 7, 7 }, 0)
    })
    using (SKPaint textPaint = new SKPaint
    {
        Style = SKPaintStyle.Fill,
        Color = SKColors.Blue,
        TextSize = 50
    })
    {
        SKRect textBounds = new SKRect();
        textPaint.MeasureText(Title, ref textBounds);
        float margin = (info.Width - textBounds.Width) / 2;

        float sx = (float)xScaleSlider.Value;
        float sy = (float)yScaleSlider.Value;
        float px = margin + textBounds.Width / 2;
        float py = margin + textBounds.Height / 2;

        canvas.Scale(sx, sy, px, py);

        SKRect borderRect = SKRect.Create(new SKPoint(margin, margin), textBounds.Size);
        canvas.DrawRoundRect(borderRect, 20, 20, strokePaint);
        canvas.DrawText(Title, margin, -textBounds.Top + margin, textPaint);
    }
}

Le coin supérieur gauche du rectangle arrondi est positionné margin en pixels à partir de la gauche du canevas et margin des pixels du haut. Les deux derniers arguments de la Scale méthode sont définis sur ces valeurs, ainsi que la largeur et la hauteur du texte, qui est également la largeur et la hauteur du rectangle arrondi. Cela signifie que toute la mise à l’échelle est relative au centre de ce rectangle :

Capture d’écran triple de la page Mise à l’échelle centrée

Les Slider éléments de ce programme ont une plage de -10 à 10. Comme vous pouvez le voir, les valeurs négatives de la mise à l’échelle verticale (par exemple, sur l’écran Android au centre) entraînent un retournement d’objets autour de l’axe horizontal qui passe par le centre de la mise à l’échelle. Les valeurs négatives de la mise à l’échelle horizontale (par exemple, dans l’écran UWP à droite) entraînent le retournement d’objets autour de l’axe vertical qui traverse le centre de la mise à l’échelle.

La version de la Scale méthode avec des points croisés dynamiques est un raccourci pour une série de trois Translate appels.Scale Vous pouvez voir comment cela fonctionne en remplaçant la Scale méthode dans la page Mise à l’échelle centrée par les éléments suivants :

canvas.Translate(-px, -py);

Il s’agit des points négatifs des coordonnées de point croisé dynamique.

Réexécutez le programme. Vous verrez que le rectangle et le texte sont décalés afin que le centre se trouve dans le coin supérieur gauche du canevas. Vous pouvez à peine le voir. Les curseurs ne fonctionnent pas bien sûr, car le programme n’est pas mis à l’échelle du tout.

Ajoutez maintenant l’appel de base Scale (sans centre de mise à l’échelle) avant cet Translate appel :

canvas.Scale(sx, sy);
canvas.Translate(–px, –py);

Si vous êtes familiarisé avec cet exercice dans d’autres systèmes de programmation graphique, vous pouvez penser que c’est incorrect, mais ce n’est pas le cas. Skia gère les appels de transformation successifs un peu différemment de ce que vous connaissez peut-être.

Avec les appels successifs Scale , Translate le centre du rectangle arrondi se trouve toujours dans le coin supérieur gauche, mais vous pouvez maintenant l’adapter par rapport au coin supérieur gauche du canevas, qui est également le centre du rectangle arrondi.

Maintenant, avant cet Scale appel, ajoutez un autre Translate appel avec les valeurs de centrement :

canvas.Translate(px, py);
canvas.Scale(sx, sy);
canvas.Translate(–px, –py);

Cela déplace le résultat mis à l’échelle vers la position d’origine. Ces trois appels sont équivalents à :

canvas.Scale(sx, sy, px, py);

Les transformations individuelles sont composées afin que la formule de transformation totale soit :

x' = sx · (x – px) + px

y' = sy · (y – py) + py

N’oubliez pas que les valeurs par défaut et sxsy sont 1. Il est facile de vous convaincre que le point pivot (px, py) n’est pas transformé par ces formules. Il reste dans le même emplacement par rapport au canevas.

Lorsque vous combinez et Scale appelezTranslate, l’ordre est important. Si l’élément Translate vient après le Scale, les facteurs de traduction sont mis à l’échelle efficacement par les facteurs de mise à l’échelle. Si la Translate valeur est antérieure Scale, les facteurs de traduction ne sont pas mis à l’échelle. Ce processus devient un peu plus clair (quoique plus mathématique) lorsque le sujet des matrices de transformation est introduit.

La SKPath classe définit une propriété en lecture seule Bounds qui retourne une SKRect définition de l’étendue des coordonnées dans le chemin d’accès. Par exemple, lorsque la Bounds propriété est obtenue à partir du chemin d’accès hendecagram créé précédemment, les propriétés et Top les Left propriétés du rectangle sont d’environ -100, et Bottom les Right propriétés sont d’environ 100, et les WidthHeight propriétés sont d’environ 200. (La plupart des valeurs réelles sont un peu moins parce que les points des étoiles sont définis par un cercle avec un rayon de 100, mais seul le point supérieur est parallèle aux axes horizontaux ou verticaux.)

La disponibilité de ces informations implique qu’il doit être possible de dériver l’échelle et de traduire des facteurs adaptés à la mise à l’échelle d’un chemin vers la taille du canevas. La page Mise à l’échelle anisotropique montre cela avec l’étoile à pointe de 11. Une échelle anisotropique signifie qu’elle est inégale dans les directions horizontales et verticales, ce qui signifie que l’étoile ne conservera pas son rapport d’aspect d’origine. Voici le code approprié dans le PaintSurface gestionnaire :

SKPath path = HendecagramPage.HendecagramPath;
SKRect pathBounds = path.Bounds;

using (SKPaint fillPaint = new SKPaint
{
    Style = SKPaintStyle.Fill,
    Color = SKColors.Pink
})
using (SKPaint strokePaint = new SKPaint
{
    Style = SKPaintStyle.Stroke,
    Color = SKColors.Blue,
    StrokeWidth = 3,
    StrokeJoin = SKStrokeJoin.Round
})
{
    canvas.Scale(info.Width / pathBounds.Width,
                 info.Height / pathBounds.Height);
    canvas.Translate(-pathBounds.Left, -pathBounds.Top);

    canvas.DrawPath(path, fillPaint);
    canvas.DrawPath(path, strokePaint);
}

Le pathBounds rectangle est obtenu près du haut de ce code, puis utilisé ultérieurement avec la largeur et la hauteur du canevas dans l’appel Scale . Cet appel lui-même met à l’échelle les coordonnées du chemin lorsqu’il est rendu par l’appel DrawPath , mais l’étoile est centrée dans le coin supérieur droit du canevas. Il doit être décalé vers le bas et vers la gauche. Il s’agit du travail de l’appel Translate . Ces deux propriétés sont d’environ -100, de sorte que les facteurs de pathBounds traduction sont d’environ 100. Étant donné que l’appel Translate est après l’appel Scale , ces valeurs sont mises à l’échelle efficacement par les facteurs de mise à l’échelle, de sorte qu’elles déplacent le centre de l’étoile au centre du canevas :

Capture d’écran triple de la page Mise à l’échelle anisotropique

Une autre façon de réfléchir Scale aux appels consiste Translate à déterminer l’effet dans la séquence inverse : l’appel Translate déplace le chemin afin qu’il devienne entièrement visible mais orienté dans le coin supérieur gauche du canevas. La Scale méthode rend ensuite cette étoile plus grande par rapport au coin supérieur gauche.

En fait, il semble que l’étoile est un peu plus grande que la toile. Le problème est la largeur du trait. La Bounds propriété de SKPath indique les dimensions des coordonnées encodées dans le chemin d’accès, et c’est ce que le programme utilise pour l’adapter. Lorsque le chemin est rendu avec une largeur de trait particulière, le chemin rendu est plus grand que le canevas.

Pour résoudre ce problème, vous devez compenser cela. Une approche simple dans ce programme consiste à ajouter l’instruction suivante juste avant l’appel Scale :

pathBounds.Inflate(strokePaint.StrokeWidth / 2,
                   strokePaint.StrokeWidth / 2);

Cela augmente le pathBounds rectangle de 1,5 unités sur les quatre côtés. Il s’agit d’une solution raisonnable uniquement lorsque la jointure de trait est arrondie. Une jointure de mitreur peut être plus longue et est difficile à calculer.

Vous pouvez également utiliser une technique similaire avec du texte, comme le montre la page Texte anisotropique. Voici la partie pertinente du PaintSurface gestionnaire de la AnisotropicTextPage classe :

using (SKPaint textPaint = new SKPaint
{
    Style = SKPaintStyle.Stroke,
    Color = SKColors.Blue,
    StrokeWidth = 0.1f,
    StrokeJoin = SKStrokeJoin.Round
})
{
    SKRect textBounds = new SKRect();
    textPaint.MeasureText("HELLO", ref textBounds);

    // Inflate bounds by the stroke width
    textBounds.Inflate(textPaint.StrokeWidth / 2,
                       textPaint.StrokeWidth / 2);

    canvas.Scale(info.Width / textBounds.Width,
                 info.Height / textBounds.Height);
    canvas.Translate(-textBounds.Left, -textBounds.Top);

    canvas.DrawText("HELLO", 0, 0, textPaint);
}

Il s’agit d’une logique similaire, et le texte s’étend à la taille de la page en fonction du rectangle lié au texte retourné MeasureText (qui est un peu plus grand que le texte réel) :

Capture d’écran triple de la page Test anisotropique

Si vous devez conserver les proportions des objets graphiques, vous devez utiliser la mise à l’échelle isotropique. La page Mise à l’échelle isotropique montre ceci pour l’étoile à pointe de 11. Conceptuellement, les étapes d’affichage d’un objet graphique au centre de la page avec mise à l’échelle isotropique sont les suivantes :

  • Traduisez le centre de l’objet graphique dans le coin supérieur gauche.
  • Mettez à l’échelle l’objet en fonction du minimum des dimensions de page horizontale et verticale divisées par les dimensions de l’objet graphique.
  • Traduisez le centre de l’objet mis à l’échelle au centre de la page.

Les IsotropicScalingPage étapes suivantes sont effectuées dans l’ordre inverse avant d’afficher l’étoile :

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

    canvas.Clear();

    SKPath path = HendecagramArrayPage.HendecagramPath;
    SKRect pathBounds = path.Bounds;

    using (SKPaint fillPaint = new SKPaint())
    {
        fillPaint.Style = SKPaintStyle.Fill;

        float scale = Math.Min(info.Width / pathBounds.Width,
                               info.Height / pathBounds.Height);

        for (int i = 0; i <= 10; i++)
        {
            fillPaint.Color = new SKColor((byte)(255 * (10 - i) / 10),
                                          0,
                                          (byte)(255 * i / 10));
            canvas.Save();
            canvas.Translate(info.Width / 2, info.Height / 2);
            canvas.Scale(scale);
            canvas.Translate(-pathBounds.MidX, -pathBounds.MidY);
            canvas.DrawPath(path, fillPaint);
            canvas.Restore();

            scale *= 0.9f;
        }
    }
}

Le code affiche également l’étoile 10 fois plus, chaque fois que le facteur de mise à l’échelle diminue de 10 % et change progressivement la couleur du rouge au bleu :

Capture d’écran triple de la page Mise à l’échelle isotropique