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:
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 SKPaint
zurü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:
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:
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, Scale
werden 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:
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):
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: