Aracılığıyla paylaş


Ölçekleme Dönüşümü

Nesneleri çeşitli boyutlara ölçeklendirmek için SkiaSharp ölçek dönüşümünü keşfedin

Dönüştürmeyi Çevir makalesinde gördüğünüz gibi, çeviri dönüşümü grafik bir nesneyi bir konumdan diğerine taşıyabilir. Buna karşılık, ölçek dönüşümü grafik nesnenin boyutunu değiştirir:

Boyut olarak ölçeklendirilmiş uzun bir sözcük

Ölçek dönüştürmesi genellikle grafik koordinatlarının daha büyük hale getirildiğinde hareket etmelerine de neden olur.

Daha önce ve dyçeviri faktörlerinin dx etkilerini açıklayan iki dönüşüm formülü gördünüz:

x' = x + dx

y' = y + dy

ve sy ölçek faktörlerisx, katkı maddesi yerine çarpımsaldır:

x' = sx · X

y' = sy · Y

Çeviri faktörlerinin varsayılan değerleri 0'dır; ölçek faktörlerinin varsayılan değerleri 1'tir.

SKCanvas sınıfı dört Scale yöntem tanımlar. İlk Scale yöntem, aynı yatay ve dikey ölçeklendirme faktörünü istediğiniz durumlar içindir:

public void Scale (Single s)

Bu, her iki yönde de aynı olan ölçeklendirme olan izotropik ölçeklendirme olarak bilinir. İzotropik ölçeklendirme nesnenin en boy oranını korur.

İkinci Scale yöntem, yatay ve dikey ölçeklendirme için farklı değerler belirtmenize olanak tanır:

public void Scale (Single sx, Single sy)

Bu da anisotropik ölçeklendirmeyle sonuçlanmalı. Üçüncü Scale yöntem, iki ölçeklendirme faktörünü tek SKPoint bir değerde birleştirir:

public void Scale (SKPoint size)

Dördüncü Scale yöntem kısa süre içinde açıklanacaktır.

Temel Ölçek sayfasında yöntemi gösterilirScale. BasicScalePage.xaml dosyası, 0 ile 10 arasında yatay ve dikey ölçeklendirme faktörleri seçmenize olanak sağlayan iki Slider öğe içerir. Arka planda kod BasicScalePage.xaml.cs dosyası, tuvalin sol üst köşesine metin sığacak şekilde boyutlandırılmış ve kesik çizgili yuvarlak bir dikdörtgen görüntülemeden önce çağrı Scale yapmak için bu değerleri kullanır:

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

Merak ediyor olabilirsiniz: Ölçeklendirme faktörleri yönteminden MeasureTextSKPaintdöndürülen değeri nasıl etkiler? Cevap: Hiç değil. Scale bir yöntemidir SKCanvas. Tuvalde bir SKPaint şeyi işlemek için bu nesneyi kullanana kadar nesneyle yaptığınız hiçbir şeyi etkilemez.

Gördüğünüz gibi, çağrıdan Scale sonra çekilen her şey orantılı olarak artar:

Temel Ölçek sayfasının üçlü ekran görüntüsü

Metin, kesik çizginin genişliği, bu çizgideki tirelerin uzunluğu, köşelerin yuvarlanması ve tuvalin sol ve üst kenarları ile yuvarlatılmış dikdörtgen arasındaki 10 piksellik kenar boşluğu aynı ölçeklendirme faktörlerine tabidir.

Önemli

Evrensel Windows Platformu, anizotropik olarak ölçeklendirilmiş metinleri düzgün şekilde işlemez.

Anisotropik ölçeklendirme, yatay ve dikey eksenlerle hizalanan çizgiler için vuruş genişliğinin farklı olmasına neden olur. (Bu, bu sayfadaki ilk görüntüden de açıktır.) Vuruş genişliğinin ölçeklendirme faktörlerinden etkilenmesini istemiyorsanız, bunu 0 olarak ayarlayın; ayardan bağımsız olarak Scale her zaman bir piksel genişliğinde olur.

Ölçeklendirme, tuvalin sol üst köşesine göredir. Bu tam olarak istediğiniz şey olabilir, ancak olmayabilir. Metni ve dikdörtgeni tuvalin başka bir yerine konumlandırmak ve ortasına göre ölçeklendirmek istediğinizi varsayalım. Bu durumda, ölçeklendirme merkezini belirtmek için iki ek parametre içeren yöntemin dördüncü sürümünü Scale kullanabilirsiniz:

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

px ve py parametreleri bazen ölçeklendirme merkezi olarak adlandırılan bir noktayı tanımlar, ancak SkiaSharp belgelerinde pivot noktası olarak adlandırılır. Bu, tuvalin sol üst köşesine göre ölçeklendirmeden etkilenmeyen bir noktadır. Tüm ölçeklendirme bu merkeze göre gerçekleşir.

Ortalanmış Ölçek sayfası bunun nasıl çalıştığını gösterir. İşleyici PaintSurface Temel Ölçek programına benzer, ancak değerin margin metni yatay olarak ortalamak için hesaplanmış olması, programın dikey modda en iyi şekilde çalıştığını gösterir:

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

Yuvarlatılmış dikdörtgenin sol üst köşesi, tuvalin solundan pikseller ve margin üstten pikseller konumlandırılırmargin. yöntemine yönelik Scale son iki bağımsız değişken, bu değerlere ek olarak metnin genişliğine ve yüksekliğine ayarlanır; bu da yuvarlatılmış dikdörtgenin genişliği ve yüksekliğidir. Bu, tüm ölçeklendirmenin bu dikdörtgenin merkezine göre olduğu anlamına gelir:

Ortalanmış Ölçek sayfasının üç kez ekran görüntüsü

Slider Bu programdaki öğelerin aralığı –10 ile 10 arasındadır. Gördüğünüz gibi, dikey ölçeklendirmenin negatif değerleri (örneğin ortadaki Android ekranında), nesnelerin ölçeklendirme merkezinden geçen yatay eksen etrafında ters çevirmesine neden olur. Yatay ölçeklendirmenin negatif değerleri (sağdaki UWP ekranında olduğu gibi) nesnelerin ölçeklendirme merkezinden geçen dikey eksen etrafında ters çevirmesine neden olur.

Yöntemin Scale özet noktaları olan sürümü, üç Translate ve Scale çağrılardan oluşan bir serinin kısayoludur. Ortalanmış Ölçek sayfasındaki yöntemini aşağıdakilerle değiştirerek Scale bunun nasıl çalıştığını görmek isteyebilirsiniz:

canvas.Translate(-px, -py);

Bunlar, özet noktası koordinatlarının negatifleridir.

Şimdi programı yeniden çalıştırın. Dikdörtgenin ve metnin, ortanın tuvalin sol üst köşesinde olacak şekilde kaydırıldığını göreceksiniz. Zar zor görebiliyorsunuz. Kaydırıcılar elbette çalışmıyor çünkü artık program hiç ölçeklendirilmiyor.

Şimdi bu çağrıdan önceTranslate temel Scale çağrıyı (ölçeklendirme merkezi olmadan) ekleyin:

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

Bu alıştırmayı diğer grafik programlama sistemlerinde biliyorsanız, bunun yanlış olduğunu düşünebilirsiniz, ancak değildir. Skia, ardışık dönüşüm çağrılarını bildiğinizden biraz farklı işler.

Ardışık Scale ve Translate çağrılarla, yuvarlatılmış dikdörtgenin merkezi hala sol üst köşededir, ancak artık tuvalin sol üst köşesine göre ölçeklendirin; bu da yuvarlatılmış dikdörtgenin merkezidir.

Şimdi bu Scale çağrıdan önce, ortalama değerleriyle başka bir Translate çağrı ekleyin:

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

Bu, ölçeklendirilmiş sonucu özgün konuma geri taşır. Bu üç çağrı şunlara eşdeğerdir:

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

Tek tek dönüşümler, toplam dönüşüm formülü şöyle olacak şekilde birleştirilmiştir:

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

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

varsayılan ve sy değerlerinin sx 1 olduğunu unutmayın. Pivot noktasının (px, py) bu formüller tarafından dönüştürülmediğine kendinizi ikna etmek kolaydır. Tuvale göre aynı konumda kalır.

ve Scale çağrılarını birleştirdiğinizdeTranslate, sipariş önemlidir. ' den Translate sonra Scalegeliyorsa, çeviri faktörleri ölçeklendirme faktörleri tarafından etkili bir şekilde ölçeklendirilir. ' den Translate önce Scalegeliyorsa, çeviri faktörleri ölçeklendirilmez. Bu işlem, dönüşüm matrislerinin konusu ortaya çıktığında biraz daha net hale gelir (daha matematiksel olsa da).

sınıfı, SKPath yoldaki koordinatların kapsamını tanımlayan bir SKRect döndüren salt okunur Bounds bir özellik tanımlar. Örneğin, özellik daha önce oluşturulan hendekagram yolundan elde edildiğinde dikdörtgenin Left ve Top özellikleri yaklaşık –100, Right ve Bottom özellikleri yaklaşık 100 ve Width ve Height özellikleri yaklaşık 200'dürBounds. (Yıldızların noktaları yarıçapı 100 olan bir daire tarafından tanımlandığından ancak yalnızca üst nokta yatay veya dikey eksenlerle paralel olduğundan, gerçek değerlerin çoğu biraz daha azdır.)

Bu bilgilerin kullanılabilirliği, tuvalin boyutuna yönelik bir yolu ölçeklendirmeye uygun ölçek ve çeviri faktörlerinin türetilmesi gerektiğini gösterir. Anisotropik Ölçeklendirme sayfası bunu 11 noktalı yıldızla gösterir. Anizotropik ölçek, yatay ve dikey yönlerde eşit olmadığını gösterir ve bu da yıldızın özgün en boy oranını koruymayacağı anlamına gelir. İşleyicideki ilgili kod aşağıdadır 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);
}

Dikdörtgen pathBounds bu kodun en üstüne yakın bir şekilde elde edilir ve daha sonra çağrıdaki Scale tuvalin genişliği ve yüksekliğiyle kullanılır. Bu çağrı, çağrı tarafından DrawPath işlendiğinde yolun koordinatlarını ölçeklendirir, ancak yıldız tuvalin sağ üst köşesinde ortalanır. Aşağı ve sola kaydırılmalıdır. Bu, aramanın Translate işidir. bu iki özelliği pathBounds yaklaşık –100'lerdir, bu nedenle çeviri faktörleri yaklaşık 100'lerdir. Translate Çağrı çağrıdan Scale sonra olduğundan, bu değerler ölçeklendirme faktörleri tarafından etkili bir şekilde ölçeklendirilir, bu nedenle yıldızın merkezini tuvalin ortasına taşır:

Anisotropik Ölçeklendirme sayfasının üçlü ekran görüntüsü

ve Translate çağrılarını Scale düşünmenin bir diğer yolu da efekti ters sırada belirlemektir: Translate Çağrı, tamamen görünür hale gelmesi için yolu kaydırarak tuvalin sol üst köşesinde yönlendirilir. Yöntemi Scale daha sonra bu yıldızı sol üst köşeye göre daha büyük hale getirir.

Aslında görünen o ki yıldız tuvalden biraz daha büyük. Sorun vuruş genişliğidir. Bounds özelliğiSKPath, yolda kodlanmış koordinatların boyutlarını gösterir ve program bunu ölçeklendirmek için bunu kullanır. Yol belirli bir vuruş genişliğiyle işlendiğinde, işlenen yol tuvalden daha büyüktür.

Bu sorunu çözmek için bunu telafi etmeniz gerekir. Bu programdaki kolay yaklaşımlardan biri, çağrıdan hemen önce Scale aşağıdaki deyimi eklemektir:

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

Bu, dikdörtgeni pathBounds dört tarafta da 1,5 birim artırır. Bu, yalnızca vuruş birleştirme yuvarlandığında makul bir çözümdür. Bir caydırıcı birleşimi daha uzun olabilir ve hesaplanması zordur.

Ayrıca, Anisotropik Metin sayfasında gösterildiği gibi metinle benzer bir teknik de kullanabilirsiniz. sınıfındaki işleyicinin PaintSurface ilgili bölümü aşağıdadır AnisotropicTextPage :

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

Benzer bir mantıktır ve metin, döndürülen metin sınırları dikdörtgenine (gerçek metinden MeasureText biraz daha büyük) göre sayfanın boyutuna genişletilir:

Anisotropik Test sayfasının üçlü ekran görüntüsü

Grafik nesnelerin en boy oranını korumanız gerekiyorsa, izotropik ölçeklendirmeyi kullanmak istersiniz. isotropik ölçeklendirme sayfası bunu 11 noktalı yıldız için gösterir. Kavramsal olarak, sayfanın ortasında izotropik ölçeklendirme ile grafik nesne görüntüleme adımları şunlardır:

  • Grafik nesnenin merkezini sol üst köşeye çevirin.
  • Nesneyi, grafik nesne boyutlarına bölünen yatay ve dikey sayfa boyutlarının en küçük boyutuna göre ölçeklendirin.
  • Ölçeklendirilen nesnenin merkezini sayfanın ortasına çevirin.

, IsotropicScalingPage yıldızı görüntülemeden önce bu adımları ters sırada gerçekleştirir:

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 ayrıca, her seferinde ölçeklendirme faktörünü %10 oranında azaltarak ve rengi aşamalı olarak kırmızıdan maviye değiştirerek yıldızı 10 kez daha görüntüler:

isotropik ölçeklendirme sayfasının üçlü ekran görüntüsü