Udostępnij za pośrednictwem


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:

Duże słowo skalowane w rozmiarze

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 dxdy:

x' = x + dx

y' = y + dy

Czynniki skali i sxsy 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:

Potrójny zrzut ekranu przedstawiający stronę Skalowanie podstawowe

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:

Potrójny zrzut ekranu przedstawiający stronę Wyśrodkowana skala

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 ScaleTranslate 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 sxsy 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:

Potrójny zrzut ekranu przedstawiający stronę Skalowanie anisotropowe

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):

Zrzut ekranu przedstawiający stronę Anisotropic Test

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:

Potrójny zrzut ekranu przedstawiający stronę Skalowanie isotropiczne