Sdílet prostřednictvím


Transformace měřítka

Objevte transformaci škálování SkiaSharp pro škálování objektů na různé velikosti.

Jak jste viděli v článku Přeložit transformaci , může transformace přeložit grafický objekt z jednoho umístění do jiného. Naproti tomu transformace měřítka změní velikost grafického objektu:

Vysoké slovo s měřítkem velikosti

Transformace měřítka také často způsobuje, že se grafické souřadnice přesunou, protože se zvětší.

Dříve jste viděli dva transformační vzorce, které popisují účinky faktorů překladu a dxdy:

x' = x + dx

y' = y + dy

Faktory měřítka sx a sy jsou násobené místo sčítání:

x' = sx · X

y' = sy · Y

Výchozí hodnoty přeložených faktorů jsou 0; výchozí hodnoty faktorů škálování jsou 1.

Třída SKCanvas definuje čtyři Scale metody. První Scale metoda je pro případy, kdy chcete stejný horizontální a svislý faktor škálování:

public void Scale (Single s)

To se označuje jako isotropní škálování – škálování, které je stejné v obou směrech. Isotropní měřítko zachovává poměr stran objektu.

Druhá Scale metoda umožňuje zadat různé hodnoty pro horizontální a vertikální škálování:

public void Scale (Single sx, Single sy)

Výsledkem je anisotropní škálování. Třetí Scale metoda kombinuje dva faktory škálování v jedné SKPoint hodnotě:

public void Scale (SKPoint size)

Čtvrtá Scale metoda bude stručně popsána.

Stránka Základní měřítko ukazuje metodu Scale . Soubor BasicScalePage.xaml obsahuje dva Slider prvky, které umožňují vybrat vodorovné a svislé faktory škálování mezi 0 a 10. Soubor BasicScalePage.xaml.cs kódu používá tyto hodnoty k volání Scale před zobrazením zaobleného obdélníku s přerušovanou čárou a velikostí tak, aby se do levého horního rohu plátna vešel nějaký text:

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žná vás zajímá: Jak faktory škálování ovlivňují hodnotu vrácenou metodou MeasureTextSKPaint? Odpověď je: Vůbec ne. Scale je metoda SKCanvas. Nemá vliv na nic, co s objektem SKPaint děláte, dokud ho nepoužijete k vykreslení na plátně.

Jak vidíte, vše, co se vykresluje po Scale volání, se proporcionálně zvýší:

Trojitý snímek obrazovky se stránkou Základní měřítko

Text, šířka přerušované čáry, délka pomlček v této čáře, zaokrouhlování rohů a 10 pixelů okraje mezi levým a horním okrajem plátna a zaoblený obdélník se řídí stejnými faktory měřítka.

Důležité

Univerzální platforma Windows nevykresluje správně anisotropně škálovaný text.

Anisotropní měřítko způsobí, že šířka tahu se pro čáry zarovnané s vodorovnou a svislou osou liší. (To je také zřejmé z prvního obrázku na této stránce.) Pokud nechcete, aby šířka tahu byla ovlivněna faktory měřítka, nastavte ji na 0 a vždy bude mít šířku o jeden pixel bez Scale ohledu na nastavení.

Měřítko je relativní vzhledem k levému hornímu rohu plátna. To může být přesně to, co chcete, ale nemusí to být. Předpokládejme, že chcete umístit text a obdélník někam jinam na plátno a chcete ho škálovat vzhledem ke středu. V takovém případě můžete použít čtvrtou verzi Scale metody, která obsahuje dva další parametry pro určení středu škálování:

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

Parametry px definují bod, který se někdy označuje jako centrum škálování, ale v dokumentaci SkiaSharp se označuje jako kontingenční bod.py Jedná se o bod relativní vzhledem k levému hornímu rohu plátna, které není ovlivněno škálováním. K veškerému škálování dochází vzhledem k tomuto středu.

Na stránce na středovém měřítku se dozvíte, jak to funguje. Obslužná PaintSurface rutina se podobá programu Základní měřítko s tím rozdílem, že margin hodnota je vypočítána tak, aby text vodorovně zarovná do středu, což znamená, že program funguje nejlépe v režimu na výšku:

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

Levý horní roh zaobleného obdélníku je umístěn margin pixely zleva od plátna a margin pixelů od horní části. Poslední dva argumenty metody Scale jsou nastaveny na tyto hodnoty a šířku a výšku textu, což je také šířka a výška zaobleného obdélníku. To znamená, že veškeré škálování je relativní vzhledem ke středu tohoto obdélníku:

Triple screenshot of the Centered Scale page

Prvky Slider v tomto programu mají rozsah –10 až 10. Jak vidíte, záporné hodnoty vertikálního měřítka (například na obrazovce Androidu uprostřed) způsobují, že objekty se překlopí kolem vodorovné osy, která prochází středem měřítka. Záporné hodnoty horizontálního škálování (například na obrazovce UPW vpravo) způsobují, že objekty se překlopí kolem svislé osy, která prochází středem měřítka.

Verze Scale metody s kontingenčními body je zkratka pro řadu tří Translate volání a Scale volání. Možná budete chtít zjistit, jak to funguje, nahrazením Scale metody na stránce Centered Scale následujícím kódem:

canvas.Translate(-px, -py);

Jedná se o záporné hodnoty souřadnic bodů kontingenčního bodu.

Teď program spusťte znovu. Uvidíte, že obdélník a text jsou posunuty tak, aby střed byl v levém horním rohu plátna. Sotva ji uvidíš. Posuvníky samozřejmě nefungují, protože teď se program vůbec nes škáluje.

Teď před tímto Translate voláním přidejte základní Scale volání (bez centra škálování):

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

Pokud jste obeznámeni s tímto cvičením v jiných grafických programovacích systémech, můžete si myslet, že je to špatně, ale není to. Skia zpracovává následné transformace volání trochu jinak než to, co byste mohli znát.

Při následných Scale voláních Translate je střed zaobleného obdélníku stále v levém horním rohu, ale teď ho můžete škálovat vzhledem k levému hornímu rohu plátna, což je také střed zaobleného obdélníku.

Teď před tímto Scale voláním přidejte další Translate volání se středními hodnotami:

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

Tím se škálovaný výsledek přesune zpět na původní pozici. Tato tři volání jsou ekvivalentní těmto třem voláním:

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

Jednotlivé transformace jsou složené tak, aby vzorec celkové transformace byl:

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

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

Mějte na paměti, že výchozí hodnoty sx a sy jsou 1. Je snadné přesvědčit se, že kontingenční bod (px, py) není transformován těmito vzorci. Zůstane ve stejném umístění vzhledem k plátnu.

Při kombinování Translate a Scale volání záleží na objednávce. Translate Pokud nastane následujícíScale, faktory překladu se efektivně škálují faktory škálování. Translate Pokud je před ním Scale, faktory překladu nejsou škálovány. Tento proces se stává poněkud jasnější (i když více matematický) při zavedení předmětu transformovaných matic.

Třída SKPath definuje vlastnost jen Bounds pro čtení, která vrací SKRect definici rozsahu souřadnic v cestě. Pokud se například Bounds vlastnost získá z dříve vytvořené cesty hendecagramu, Left jsou vlastnosti Top obdélníku přibližně –100, Right a Bottom vlastnosti jsou přibližně 100 a WidthHeight vlastnosti jsou přibližně 200. (Většina skutečných hodnot je o něco menší, protože body hvězd jsou definovány kruhem s poloměrem 100, ale pouze horní bod je rovnoběžný s vodorovnou nebo svislou osou.)

Dostupnost těchto informací znamená, že by mělo být možné odvodit měřítko a přeložit faktory vhodné pro škálování cesty k velikosti plátna. Stránka Anisotropní měřítko ukazuje, že s 11-špičatou hvězdou. Anisotropní měřítko znamená, že je nerovný ve vodorovných a svislých směrech, což znamená, že hvězda si nezachová svůj původní poměr stran. Tady je příslušný kód v obslužné rutině PaintSurface :

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

Obdélník pathBounds se získá v horní části tohoto kódu a později se použije s šířkou a výškou plátna ve Scale volání. Toto volání samo o sobě zvětšuje souřadnice cesty, když se vykreslí voláním DrawPath , ale hvězda se zacentruje v pravém horním rohu plátna. Musí být posunut dolů a doleva. Toto je úloha Translate hovoru. Tyto dvě vlastnosti pathBounds jsou přibližně –100, takže faktory překladu jsou přibližně 100. Translate Vzhledem k tomu, že volání je po Scale volání, tyto hodnoty se efektivně škálují faktory škálování, takže přesouvají střed hvězdy na střed plátna:

Trojitý snímek obrazovky se stránkou Anisotropní měřítko

Dalším způsobem, jak můžete uvažovat o Scale volání a Translate volání, je určit účinek v obráceném pořadí: Translate Volání posune cestu, takže se stane plně viditelným, ale orientovaným v levém horním rohu plátna. Metoda Scale pak zvětší tuto hvězdičku vzhledem k levému hornímu rohu.

Ve skutečnosti se zdá, že hvězda je o něco větší než plátno. Problém je šířka tahu. Vlastnost BoundsSKPath označuje rozměry souřadnic kódovaných v cestě a to je to, co program používá ke škálování. Při vykreslení cesty s konkrétní šířkou tahu je vykreslená cesta větší než plátno.

Pokud chcete tento problém vyřešit, musíte tento problém kompenzovat. Jedním ze snadných přístupů v tomto programu je přidat následující příkaz přímo před Scale volání:

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

Tím se pathBounds na všech čtyřech stranách zvětší obdélník o 1,5 jednotek. Toto je rozumné řešení pouze v případě, že je spojení tahu zaokrouhleno. Spojení miter může být delší a je obtížné vypočítat.

Můžete také použít podobnou techniku s textem, jak ukazuje anisotropní text stránky. Tady je relevantní část PaintSurface obslužné rutiny z AnisotropicTextPage třídy:

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

Je to podobná logika a text se rozbalí na velikost stránky na základě obdélníku vázaného na text vrácený ( MeasureText což je o něco větší než skutečný text):

Trojitý snímek obrazovky se stránkou Anisotropní test

Pokud potřebujete zachovat poměr stran grafických objektů, budete chtít použít isotropní měřítko. Isotropní měřítko stránky ukazuje to pro 11-scípé hvězdy. Koncepčně jsou kroky pro zobrazení grafického objektu ve středu stránky s isotropní měřítko:

  • Přeloží střed grafického objektu do levého horního rohu.
  • Škálujte objekt na základě minimálního vodorovného a svislého rozměru stránky děleného rozměry grafického objektu.
  • Přeložte střed škálovaného objektu na střed stránky.

Před IsotropicScalingPage zobrazením hvězdičky provede tyto kroky v opačném pořadí:

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

Kód také zobrazuje hvězdu o 10krát vícekrát, při každém snížení měřítka o 10 % a postupné změně barvy z červené na modrou:

Trojitý snímek obrazovky se stránkou Isotropní měřítko