Skalowanie — przekształcenie
Odkrywanie przekształcenia skali SkiaSharp na potrzeby skalowania obiektów do różnych rozmiarów
Jak pokazano w artykule Tłumaczenie przekształcenia , przekształcenie tłumaczenia może przenieść obiekt graficzny z jednej lokalizacji do innej. Natomiast przekształcenie skalowania zmienia rozmiar obiektu graficznego:
Transformacja skali często powoduje również przenoszenie współrzędnych graficznych w miarę ich zwiększania.
Wcześniej przedstawiono dwie formuły przekształcania, które opisują efekty czynników tłumaczeń i dx
dy
:
x' = x + dx
y' = y + dy
Czynniki skali i sx
sy
są mnożenia, a nie addytywne:
x' = sx · X
y ' = sy · Y
Wartości domyślne współczynników tłumaczenia to 0; wartości domyślne czynników skalowania to 1.
Klasa SKCanvas
definiuje cztery Scale
metody. Pierwsza Scale
metoda dotyczy przypadków, gdy chcesz mieć ten sam współczynnik skalowania w poziomie i w pionie:
public void Scale (Single s)
Jest to nazywane skalowaniem izotropicznym — skalowanie, które jest takie samo w obu kierunkach. Izotropowe skalowanie zachowuje współczynnik proporcji obiektu.
Druga Scale
metoda umożliwia określenie różnych wartości skalowania w poziomie i w pionie:
public void Scale (Single sx, Single sy)
Powoduje to anisotropowe skalowanie.
Trzecia Scale
metoda łączy dwa czynniki skalowania w jednej SKPoint
wartości:
public void Scale (SKPoint size)
Czwarta Scale
metoda zostanie wkrótce opisana.
Na stronie Skala podstawowa przedstawiono metodę Scale
. Plik BasicScalePage.xaml zawiera dwa Slider
elementy, które umożliwiają wybranie czynników skalowania poziomego i pionowego z zakresu od 0 do 10. Plik BasicScalePage.xaml.cs za pomocą kodu używa tych wartości do wywołania Scale
przed wyświetleniem zaokrąglonego prostokąta z kreskowaną linią i rozmiarem, aby zmieścić tekst w lewym górnym rogu kanwy:
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);
}
}
Możesz się zastanawiać: Jak czynniki skalowania wpływają na wartość zwróconą MeasureText
z metody SKPaint
? Odpowiedź brzmi: W ogóle nie. Scale
jest metodą SKCanvas
. Nie ma to wpływu na nic, co robisz z obiektem SKPaint
, dopóki nie użyjesz tego obiektu do renderowania czegoś na kanwie.
Jak widać, wszystko narysowane po Scale
wywołaniu zwiększa się proporcjonalnie:
Tekst, szerokość linii kreskowanej, długość kreski w tym wierszu, zaokrąglenie narożników i margines 10 pikseli między lewą i górną krawędzią kanwy i zaokrąglonym prostokątem podlegają tym samym czynnikom skalowania.
Ważne
Platforma uniwersalna systemu Windows nie renderuje poprawnie tekstu skalowalnego anisotropowo.
Skalowanie anisotropowe powoduje, że szerokość pociągnięcia staje się inna dla linii wyrównanych z osiami poziomymi i pionowymi. (Jest to również widoczne na pierwszej ilustracji na tej stronie). Jeśli nie chcesz, aby szerokość pociągnięcia miała wpływ na czynniki skalowania, ustaw ją na 0 i zawsze będzie miała jeden piksel szerokości niezależnie od Scale
ustawienia.
Skalowanie jest względne względem lewego górnego rogu kanwy. Może to być dokładnie to, co chcesz, ale może nie być. Załóżmy, że chcesz umieścić tekst i prostokąt w innym miejscu na kanwie i chcesz go skalować względem środka. W takim przypadku można użyć czwartej Scale
wersji metody, która zawiera dwa dodatkowe parametry, aby określić środek skalowania:
public void Scale (Single sx, Single sy, Single px, Single py)
Parametry px
i py
definiują punkt, który jest czasami nazywany centrum skalowania, ale w dokumentacji SkiaSharp jest określany jako punkt przestawny. Jest to punkt względem lewego górnego rogu kanwy, który nie ma wpływu na skalowanie. Wszystkie skalowanie odbywa się w stosunku do tego środka.
Na stronie Wyśrodkowana skala pokazano, jak to działa. Procedura PaintSurface
obsługi jest podobna do programu skalowania podstawowego, z tą różnicą, że margin
wartość jest obliczana w celu wyśrodkowania tekstu w poziomie, co oznacza, że program działa najlepiej w trybie pionowym:
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);
}
}
Lewy górny róg zaokrąglonego prostokąta znajduje się margin
w pikselach z lewej strony kanwy i margin
pikseli u góry. Ostatnie dwa argumenty Scale
metody są ustawione na te wartości oraz szerokość i wysokość tekstu, który jest również szerokością i wysokością zaokrąglonego prostokąta. Oznacza to, że wszystkie skalowanie jest względem środka tego prostokąta:
Slider
Elementy w tym programie mają zakres od –10 do 10. Jak widać, ujemne wartości skalowania w pionie (na przykład na ekranie systemu Android w środku) powodują przerzucanie obiektów wokół osi poziomej przechodzącej przez środek skalowania. Ujemne wartości skalowania w poziomie (na przykład na ekranie platformy UWP po prawej stronie) powodują przerzucanie obiektów wokół osi pionowej przechodzącej przez środek skalowania.
Wersja Scale
metody z punktami przestawnymi to skrót do serii trzech Translate
wywołań.Scale
Możesz zobaczyć, jak to działa, zastępując metodę Scale
na stronie Wyśrodkowana skala następującymi elementami:
canvas.Translate(-px, -py);
Są to wartości ujemne współrzędnych punktu przestawnego.
Teraz ponownie uruchom program. Zobaczysz, że prostokąt i tekst są przesunięte tak, aby środek był w lewym górnym rogu kanwy. Ledwo widać to. Suwaki nie działają oczywiście, ponieważ teraz program w ogóle nie skaluje.
Teraz dodaj wywołanie podstawowe Scale
(bez centrum skalowania) przed wywołaniem Translate
:
canvas.Scale(sx, sy);
canvas.Translate(–px, –py);
Jeśli znasz to ćwiczenie w innych systemach programowania graficznego, możesz pomyśleć, że to nie tak, ale tak nie jest. Skia obsługuje kolejne przekształcenia nieco inaczej niż to, co możesz zaznajomić.
Po kolejnych Scale
Translate
wywołaniach środek zaokrąglonego prostokąta jest nadal w lewym górnym rogu, ale teraz można go skalować w stosunku do lewego górnego rogu kanwy, który jest również środek zaokrąglonego prostokąta.
Teraz przed wywołaniem Scale
dodaj kolejne Translate
wywołanie z wartościami wyśrodkowania:
canvas.Translate(px, py);
canvas.Scale(sx, sy);
canvas.Translate(–px, –py);
Spowoduje to przeniesienie wyniku skalowalnego z powrotem do oryginalnej pozycji. Te trzy wywołania są równoważne:
canvas.Scale(sx, sy, px, py);
Poszczególne przekształcenia są złożone, tak aby formuła transformacji całkowitej to:
x' = sx · (x – px) + px
y ' = sy · (y – py) + py
Pamiętaj, że wartości domyślne i sx
sy
mają wartość 1. Łatwo przekonać się, że punkt przestawny (px, py) nie jest przekształcany przez te formuły. Pozostaje w tej samej lokalizacji względem kanwy.
Po połączeniu Translate
i Scale
wywołaniu kolejność ma znaczenie. Translate
Jeśli wartość wystąpi po metodzie Scale
, czynniki tłumaczenia są skutecznie skalowane przez czynniki skalowania. Jeśli element Translate
występuje przed parametrem Scale
, czynniki tłumaczenia nie są skalowane. Ten proces staje się nieco jaśniejszy (choć bardziej matematyczny), gdy zostanie wprowadzony temat macierzy transformacji.
Klasa SKPath
definiuje właściwość tylko Bounds
do odczytu, która zwraca SKRect
definiujący zakres współrzędnych w ścieżce. Na przykład, gdy Bounds
właściwość jest uzyskiwana ze ścieżki hendecagram utworzonej wcześniej, Left
właściwości i Top
prostokąta są około –100, Right
właściwości i Bottom
są około 100, a Width
właściwości i Height
są około 200. (Większość rzeczywistych wartości jest nieco mniejsza, ponieważ punkty gwiazd są definiowane przez okrąg z promieniem 100, ale tylko górny punkt jest równoległy z osiami poziomymi lub pionowymi).
Dostępność tych informacji oznacza, że powinno być możliwe uzyskanie skali i tłumaczenie czynników odpowiednich do skalowania ścieżki do rozmiaru kanwy. Strona Anisotropic Scaling pokazuje to z 11-punktową gwiazdą. Skala anisotropowa oznacza, że jest nierówna w kierunkach poziomych i pionowych, co oznacza, że gwiazda nie zachowa pierwotnego współczynnika proporcji. Oto odpowiedni kod w procedurze PaintSurface
obsługi:
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);
}
Prostokąt pathBounds
jest uzyskiwany w górnej części tego kodu, a następnie używany później z szerokością i wysokością kanwy w wywołaniu Scale
. To wywołanie samo w sobie skaluje współrzędne ścieżki, gdy jest renderowane przez DrawPath
wywołanie, ale gwiazda zostanie wyśrodkowana w prawym górnym rogu kanwy. Należy go przesunąć w dół i z lewej strony. Jest to zadanie wywołania Translate
. Te dwie właściwości pathBounds
to około –100, więc czynniki tłumaczenia to około 100. Translate
Ponieważ wywołanie jest po Scale
wywołaniu, te wartości są skutecznie skalowane przez czynniki skalowania, więc przenoszą środek gwiazdy do środka kanwy:
Innym sposobem, w jaki można myśleć o wywołaniach Scale
i Translate
jest określenie efektu w odwrotnej sekwencji: Translate
wywołanie przesuwa ścieżkę tak, aby stało się w pełni widoczne, ale zorientowane w lewym górnym rogu kanwy. Następnie metoda sprawia, że gwiazda Scale
jest większa względem lewego górnego rogu.
W rzeczywistości wydaje się, że gwiazda jest nieco większa niż kanwa. Problem polega na szerokości pociągnięcia. Właściwość Bounds
parametru SKPath
wskazuje wymiary współrzędnych zakodowanych w ścieżce i to, czego program używa do skalowania. Gdy ścieżka jest renderowana z określoną szerokością pociągnięcia, renderowana ścieżka jest większa niż kanwa.
Aby rozwiązać ten problem, musisz zrekompensować to. Jednym z prostych metod w tym programie jest dodanie następującej instrukcji bezpośrednio przed wywołaniem Scale
:
pathBounds.Inflate(strokePaint.StrokeWidth / 2,
strokePaint.StrokeWidth / 2);
pathBounds
Zwiększa to prostokąt o 1,5 jednostki na wszystkich czterech stronach. Jest to rozsądne rozwiązanie tylko wtedy, gdy sprzężenie pociągnięcia jest zaokrąglane. Sprzężenie miter może być dłuższe i jest trudne do obliczenia.
Możesz również użyć podobnej techniki z tekstem, jak pokazuje strona Anisotropic Text . Oto odpowiednia PaintSurface
część procedury obsługi z AnisotropicTextPage
klasy :
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);
}
Jest to podobna logika, a tekst rozszerza się na rozmiar strony na podstawie prostokąta granic tekstu zwróconego z MeasureText
(co jest nieco większe niż rzeczywisty tekst):
Jeśli chcesz zachować współczynnik proporcji obiektów graficznych, należy użyć skalowania izotropowego. Na stronie Isotropic Scaling (Skalowanie isotropowe) pokazano to dla 11-punktowej gwiazdki. Koncepcyjnie kroki wyświetlania obiektu graficznego na środku strony z izotropicznym skalowaniem to:
- Przetłumacz środek obiektu graficznego na lewy górny róg.
- Skaluj obiekt na podstawie minimalnych wymiarów stron poziomych i pionowych podzielonych przez wymiary obiektu graficznego.
- Przetłumacz środek skalowanego obiektu na środek strony.
Te IsotropicScalingPage
kroki są wykonywane w odwrotnej kolejności przed wyświetleniem gwiazdki:
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;
}
}
}
Kod wyświetla również gwiazdkę 10 razy, za każdym razem zmniejszając współczynnik skalowania o 10% i stopniowo zmieniając kolor z czerwonego na niebieski: