Freigeben über


Die Skalierungstransformation

Entdecken Sie die SkiaSharp-Skalierungstransformation zum Skalieren von Objekten auf verschiedene Größen

Wie Sie im Artikel "Übersetzungstransformation " gesehen haben, kann die Übersetzungstransformation ein grafisches Objekt von einer Stelle an eine andere verschieben. Im Gegensatz dazu ändert die Skalierungstransformation die Größe des grafischen Objekts:

Ein hochformatiertes Wort

Die Skalierungstransformation bewirkt auch häufig, dass Grafikkoordinaten verschoben werden, wenn sie größer werden.

Zuvor haben Sie zwei Transformationsformeln gesehen, die die Auswirkungen von Übersetzungsfaktoren dx beschreiben und dy:

x' = x + dx

y' = y + dy

Skalierungsfaktoren von sx und sy sind multiplikativ und nicht additiv:

x' = sx · X

y' = sy · Y

Die Standardwerte der Übersetzungsfaktoren sind 0; Die Standardwerte der Skalierungsfaktoren sind 1.

Die SKCanvas Klasse definiert vier Scale Methoden. Die erste Scale Methode gilt für Fälle, in dem Sie denselben horizontalen und vertikalen Skalierungsfaktor verwenden möchten:

public void Scale (Single s)

Dies wird als isotrope Skalierung bezeichnet – Skalierung, die in beide Richtungen identisch ist. Die isotrope Skalierung behält das Seitenverhältnis des Objekts bei.

Mit der zweiten Scale Methode können Sie unterschiedliche Werte für die horizontale und vertikale Skalierung angeben:

public void Scale (Single sx, Single sy)

Dies führt zu anisotroper Skalierung. Die dritte Scale Methode kombiniert die beiden Skalierungsfaktoren in einem einzelnen SKPoint Wert:

public void Scale (SKPoint size)

Die vierte Scale Methode wird kurz beschrieben.

Auf der Seite "Einfache Skalierung " wird die Scale Methode veranschaulicht. Die Datei BasicScalePage.xaml enthält zwei Slider Elemente, mit denen Sie horizontale und vertikale Skalierungsfaktoren zwischen 0 und 10 auswählen können. Die BasicScalePage.xaml.cs CodeBehind-Datei verwendet diese Werte, um aufzurufen Scale , bevor ein abgerundetes Rechteck mit einer gestrichelten Linie angezeigt wird und die Größe so angepasst wird, dass text in die obere linke Ecke des Zeichenbereichs passt:

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

Sie fragen sich vielleicht: Wie wirken sich die Skalierungsfaktoren auf den von der MeasureText Methode SKPaintzurückgegebenen Wert aus? Die Antwort lautet: Gar nicht. Scale ist eine Methode von SKCanvas. Es wirkt sich nicht auf alles aus, was Sie mit einem SKPaint Objekt tun, bis Sie dieses Objekt verwenden, um etwas auf dem Zeichenbereich zu rendern.

Wie Sie sehen können, erhöht sich alles, was nach dem Scale Anruf gezeichnet wurde, proportional:

Dreifacher Screenshot der Seite

Der Text, die Breite der gestrichelten Linie, die Länge der Striche in dieser Zeile, die Rundung der Ecken und der 10-Pixel-Rand zwischen dem linken und oberen Rand des Zeichenbereichs und des abgerundeten Rechtecks unterliegen allen den gleichen Skalierungsfaktoren.

Wichtig

Der Universelle Windows-Plattform rendert nicht ordnungsgemäß anisotropisch skalierten Text.

Anisotropische Skalierung bewirkt, dass die Strichbreite für Linien, die an den horizontalen und vertikalen Achsen ausgerichtet sind, unterschiedlich wird. (Dies ist auch aus dem ersten Bild auf dieser Seite ersichtlich.) Wenn die Strichbreite nicht von den Skalierungsfaktoren beeinflusst werden soll, legen Sie sie auf 0 fest, und es wird unabhängig von der Scale Einstellung immer ein Pixel breit sein.

Die Skalierung ist relativ zur oberen linken Ecke des Zeichenbereichs. Dies kann genau das sein, was Sie wollen, aber es ist möglicherweise nicht. Angenommen, Sie möchten den Text und das Rechteck an einer anderen Stelle auf dem Zeichenbereich positionieren und sie relativ zur Mitte skalieren. In diesem Fall können Sie die vierte Version der Scale Methode verwenden, die zwei zusätzliche Parameter enthält, um den Mittelpunkt der Skalierung anzugeben:

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

Die px Parameter py definieren einen Punkt, der manchmal als Skalierungscenter bezeichnet wird, aber in der SkiaSharp-Dokumentation wird als Pivotpoint bezeichnet. Dies ist ein Punkt relativ zur oberen linken Ecke des Zeichenbereichs, der von der Skalierung nicht betroffen ist. Alle Skalierungen erfolgen relativ zu dieser Mitte.

Auf der Seite "Zentrierte Skalierung " wird gezeigt, wie dies funktioniert. Der PaintSurface Handler ähnelt dem Basic Scale-Programm , mit der Ausnahme, dass der margin Wert so berechnet wird, dass der Text horizontal zentriert wird, was bedeutet, dass das Programm im Hochformat am besten funktioniert:

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

Die obere linke Ecke des abgerundeten Rechtecks ist margin Pixel von links neben dem Zeichenbereich und margin pixeln vom oberen Rand positioniert. Die letzten beiden Argumente für die Scale Methode werden auf diese Werte sowie die Breite und Höhe des Texts festgelegt, was auch die Breite und Höhe des abgerundeten Rechtecks ist. Dies bedeutet, dass die gesamte Skalierung relativ zur Mitte dieses Rechtecks ist:

Dreifacher Screenshot der Seite

Die Slider Elemente in diesem Programm haben einen Bereich von -10 bis 10. Wie Sie sehen können, führen negative Werte der vertikalen Skalierung (z. B. auf dem Android-Bildschirm in der Mitte) dazu, dass Objekte um die horizontale Achse gedreht werden, die durch die Mitte der Skalierung verläuft. Negative Werte der horizontalen Skalierung (z. B. im UWP-Bildschirm auf der rechten Seite) bewirken, dass Objekte um die vertikale Achse gedreht werden, die durch die Mitte der Skalierung verläuft.

Die Version der Scale Methode mit Pivotpunkten ist eine Verknüpfung für eine Reihe von drei Translate und Scale Aufrufen. Möglicherweise möchten Sie sehen, wie dies funktioniert, indem Sie die Scale Methode auf der Seite "Zentrierte Skalierung " durch Folgendes ersetzen:

canvas.Translate(-px, -py);

Dies sind die Negativen der Pivotpunktkoordinaten.

Führen Sie nun das Programm erneut aus. Sie sehen, dass das Rechteck und der Text verschoben werden, sodass sich die Mitte in der oberen linken Ecke des Zeichenbereichs befindet. Sie können es kaum sehen. Die Schieberegler funktionieren natürlich nicht, da das Programm jetzt überhaupt nicht skaliert wird.

Fügen Sie nun den einfachen Scale Anruf (ohne Skalierungscenter) vor diesem Translate Anruf hinzu:

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

Wenn Sie mit dieser Übung in anderen Grafikprogrammiersystemen vertraut sind, denken Sie vielleicht, dass das falsch ist, aber nicht. Skia behandelt aufeinander folgende Transformationen etwas anders als das, was Sie vielleicht kennen.

Bei aufeinander folgenden Scale und Translate aufrufen befindet sich die Mitte des abgerundeten Rechtecks noch in der oberen linken Ecke, Sie können es jedoch relativ zur oberen linken Ecke des Zeichenbereichs skalieren, was auch die Mitte des abgerundeten Rechtecks ist.

Fügen Sie nun vor diesem Scale Anruf einen weiteren Translate Anruf mit den zentrierenden Werten hinzu:

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

Dadurch wird das skalierte Ergebnis wieder an die ursprüngliche Position verschoben. Diese drei Anrufe entsprechen folgendem:

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

Die einzelnen Transformationen werden zusammengesetzt, sodass die Gesamttransformationsformel wie folgt lautet:

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

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

Denken Sie daran, dass die Standardwerte von sx und sy sind 1. Es ist einfach, sich selbst zu überzeugen, dass der Pivotpunkt (px, py) nicht von diesen Formeln transformiert wird. Sie wird an derselben Position relativ zum Zeichenbereich neu Standard.

Wenn Sie sie kombinieren Translate und Scale anrufen, ist die Reihenfolge wichtig. Wenn das Translate kommt, Scalewerden die Übersetzungsfaktoren effektiv durch die Skalierungsfaktoren skaliert. Wenn dies Translate geschieht Scale, werden die Übersetzungsfaktoren nicht skaliert. Dieser Prozess wird etwas klarer (wenn auch mathematischer), wenn der Betreff der Transformationsmatrizen eingeführt wird.

Die SKPath Klasse definiert eine schreibgeschützte Bounds Eigenschaft, die den SKRect Umfang der Koordinaten im Pfad definiert. Wenn z. B. die Bounds Eigenschaft aus dem zuvor erstellten Hendekagrampfad abgerufen wird, sind die Left Eigenschaften Top und Eigenschaften des Rechtecks ungefähr –100, die und Bottom die Right Eigenschaften sind ungefähr 100, und die Width Eigenschaften und Height Eigenschaften sind ca. 200. (Die meisten tatsächlichen Werte sind etwas weniger, da die Punkte der Sterne durch einen Kreis mit einem Radius von 100 definiert werden, aber nur der obere Punkt ist parallel mit den horizontalen oder vertikalen Achsen.)

Die Verfügbarkeit dieser Informationen bedeutet, dass es möglich sein sollte, Skalierung abzuleiten und Faktoren zu übersetzen, die für die Skalierung eines Pfads zur Größe des Zeichenbereichs geeignet sind. Die Seite "Anisotropische Skalierung " zeigt dies mit dem 11-Spitzenstern. Eine anisotrope Skala bedeutet, dass sie in horizontaler und vertikaler Richtung ungleich ist, was bedeutet, dass der Stern sein ursprüngliches Seitenverhältnis nicht behält. Hier sehen Sie den relevanten Code im PaintSurface Handler:

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

Das pathBounds Rechteck wird am oberen Rand dieses Codes abgerufen und später mit der Breite und Höhe des Zeichenbereichs im Scale Aufruf verwendet. Dieser Aufruf selbst skaliert die Koordinaten des Pfads, wenn er vom DrawPath Aufruf gerendert wird, aber der Stern wird in der oberen rechten Ecke des Zeichenbereichs zentriert. Es muss nach unten und nach links verschoben werden. Dies ist der Auftrag des Translate Anrufs. Diese beiden Eigenschaften sind pathBounds ungefähr –100, daher sind die Übersetzungsfaktoren etwa 100. Da der Translate Aufruf nach dem Scale Aufruf erfolgt, werden diese Werte effektiv durch die Skalierungsfaktoren skaliert, sodass sie die Mitte des Sterns in die Mitte des Zeichenbereichs verschieben:

Dreifacher Screenshot der Seite

Eine weitere Möglichkeit, die und Translate die Aufrufe zu bestimmenScale, besteht darin, den Effekt in umgekehrter Reihenfolge zu bestimmen: Der Translate Aufruf verschiebt den Pfad, sodass er vollständig sichtbar, aber in der oberen linken Ecke des Zeichenbereichs ausgerichtet wird. Die Scale Methode macht diesen Stern dann relativ zur oberen linken Ecke größer.

Tatsächlich scheint es, dass der Stern etwas größer als die Canvas ist. Das Problem ist die Strichbreite. Die Bounds Eigenschaft von SKPath gibt die Dimensionen der Koordinaten an, die im Pfad codiert sind, und das ist das, was das Programm zum Skalieren verwendet. Wenn der Pfad mit einer bestimmten Strichbreite gerendert wird, ist der gerenderte Pfad größer als der Zeichenbereich.

Um dieses Problem zu beheben, müssen Sie dies ausgleichen. Ein einfacher Ansatz in diesem Programm besteht darin, direkt vor dem Scale Aufruf die folgende Anweisung hinzuzufügen:

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

Dadurch wird das pathBounds Rechteck um 1,5 Einheiten auf allen vier Seiten erhöht. Dies ist eine vernünftige Lösung nur, wenn der Strichbeitritt gerundet wird. Eine Miterverknung kann länger sein und ist schwierig zu berechnen.

Sie können auch eine ähnliche Technik mit Text verwenden, wie die Anisotrope Textseite veranschaulicht. Dies ist der relevante Teil des PaintSurface Handlers aus der AnisotropicTextPage Klasse:

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

Es ist ähnliche Logik, und der Text wird auf die Größe der Seite basierend auf dem zurückgegebenen Textgrenzenrechteck erweitert MeasureText (was etwas größer als der tatsächliche Text ist):

Dreifacher Screenshot der Seite

Wenn Sie das Seitenverhältnis der grafischen Objekte beibehalten müssen, sollten Sie isotrope Skalierung verwenden. Die Isotropische Skalierungsseite zeigt dies für den 11-spitzen Stern. Konzeptionell sind die Schritte zum Anzeigen eines grafischen Objekts in der Mitte der Seite mit isotroper Skalierung:

  • Übersetzen Sie die Mitte des grafischen Objekts in die obere linke Ecke.
  • Skalieren Sie das Objekt basierend auf dem Minimum der horizontalen und vertikalen Seitenabmessungen dividiert durch die grafischen Objektabmessungen.
  • Übersetzen Sie die Mitte des skalierten Objekts in die Mitte der Seite.

Die IsotropicScalingPage folgenden Schritte werden in umgekehrter Reihenfolge ausgeführt, bevor der Stern angezeigt wird:

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

Der Code zeigt auch den Stern 10 mal mehr an, jedes Mal, wenn der Skalierungsfaktor um 10 % verringert wird und die Farbe von Rot in Blau schrittweise geändert wird:

Dreifacher Screenshot der Seite