SkiaSharp'ta Yol Efektleri

Yolların vuruş ve doldurma için kullanılmasına izin veren çeşitli yol efektlerini keşfedin

Yol efekti, sınıfı tarafından tanımlanan sekiz statik oluşturma yönteminden biriyle oluşturulan sınıfın bir örneğidirSKPathEffect. Nesne SKPathEffect daha sonra çeşitli ilginç efektler için bir SKPaint nesnenin özelliğine ayarlanırPathEffect, örneğin, küçük bir çoğaltılmış yol ile bir çizgi vuruşu:

Bağlı Zincir örneği

Yol efektleri şunları yapmanızı sağlar:

  • Bir çizgiyi nokta ve tirelerle konturla
  • Herhangi bir doldurulmuş yola sahip bir çizgiyi konturla
  • Bir alanı tarama çizgileri ile doldurma
  • Bir alanı kutucuklu bir yolla doldurma
  • Keskin köşeleri yuvarlatılmış hale getirme
  • Çizgilere ve eğrilere rastgele "değişim" ekleme

Ayrıca, iki veya daha fazla yol efektini birleştirebilirsiniz.

Bu makalede ayrıca ve PathEffectdahil olmak üzere StrokeWidth özelliklerini uygulayarak bir yolu başka bir yola dönüştürmek için yönteminin SKPaint nasıl kullanılacağı GetFillPath gösterilmektedirSKPaint. Bu, başka bir yolun ana hattı olan bir yol elde etme gibi bazı ilginç tekniklerle sonuçlanır. GetFillPath , yol efektleriyle bağlantılı olarak da yararlıdır.

Noktalar ve Tireler

yönteminin PathEffect.CreateDash kullanımı Dots and Dashes makalesinde açıklanmıştır. yönteminin ilk bağımsız değişkeni, tire uzunlukları ile tireler arasındaki boşluk uzunlukları arasında değişen çift sayıda iki veya daha fazla değer içeren bir dizidir:

public static SKPathEffect CreateDash (Single[] intervals, Single phase)

Bu değerler vuruş genişliğine göre değildir . Örneğin, vuruş genişliği 10 ise ve kare tirelerden ve kare boşluklardan oluşan bir çizgi istiyorsanız, diziyi intervals { 10, 10 } olarak ayarlayın. Bağımsız değişken, phase çizgi deseninin içinde çizginin nerede başladığını gösterir. Bu örnekte, çizginin kare boşlukla başlamasını istiyorsanız 10 olarak ayarlayın phase .

Tirelerin uçları özelliğinden StrokeCapSKPaintetkilenir. Geniş vuruş genişlikleri için, bu özelliğin tirelerin uçlarını yuvarlatacak şekilde SKStrokeCap.Round ayarlanması çok yaygındır. Bu durumda, dizideki intervals değerler yuvarlamadan kaynaklanan fazladan uzunluğu içermez. Bu durum, döngüsel noktanın sıfır genişliği belirtmeyi gerektirdiği anlamına gelir. 10 vuruş genişliği için, aynı çapta noktalar arasında dairesel noktalar ve boşluklar içeren bir intervals çizgi oluşturmak için { 0, 20 } dizisi kullanın.

Animasyonlu Noktalı Metin sayfası, içindeki Metin ve Grafikleri Tümleştirme makalesinde açıklanan Ana Hatlı Metin sayfasına benzer ve nesnenin SKPaintSKPaintStyle.Strokeözelliğini olarak ayarlayarak Style ana hatlı metin karakterleri görüntüler. Buna ek olarak, Animasyonlu Noktalı Metin , bu ana hattı noktalı bir görünüm vermek için kullanır SKPathEffect.CreateDash ve program ayrıca phase , noktaların metin karakterleri arasında geziniyor gibi görünmesini sağlamak için yöntemin bağımsız değişkenine SKPathEffect.CreateDash animasyon ekler. Sayfa şu şekilde yatay moddadır:

Animasyonlu Noktalı Metin sayfasının üçlü ekran görüntüsü

AnimatedDottedTextPage sınıfı, bazı sabitleri tanımlayarak başlar ve ayrıca animasyon için ve OnDisappearing yöntemlerini geçersiz kılarOnAppearing:

public class AnimatedDottedTextPage : ContentPage
{
    const string text = "DOTTED";
    const float strokeWidth = 10;
    static readonly float[] dashArray = { 0, 2 * strokeWidth };

    SKCanvasView canvasView;
    bool pageIsActive;

    public AnimatedDottedTextPage()
    {
        Title = "Animated Dotted Text";

        canvasView = new SKCanvasView();
        canvasView.PaintSurface += OnCanvasViewPaintSurface;
        Content = canvasView;
    }

    protected override void OnAppearing()
    {
        base.OnAppearing();
        pageIsActive = true;

        Device.StartTimer(TimeSpan.FromSeconds(1f / 60), () =>
        {
            canvasView.InvalidateSurface();
            return pageIsActive;
        });
    }

    protected override void OnDisappearing()
    {
        base.OnDisappearing();
        pageIsActive = false;
    }
    ...
}

İşleyici, PaintSurface metni görüntülemek için bir SKPaint nesne oluşturarak başlar. TextSize özelliği, ekranın genişliğine göre ayarlanır:

public class AnimatedDottedTextPage : ContentPage
{
    ...
    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        canvas.Clear();

        // Create an SKPaint object to display the text
        using (SKPaint textPaint = new SKPaint
            {
                Style = SKPaintStyle.Stroke,
                StrokeWidth = strokeWidth,
                StrokeCap = SKStrokeCap.Round,
                Color = SKColors.Blue,
            })
        {
            // Adjust TextSize property so text is 95% of screen width
            float textWidth = textPaint.MeasureText(text);
            textPaint.TextSize *= 0.95f * info.Width / textWidth;

            // Find the text bounds
            SKRect textBounds = new SKRect();
            textPaint.MeasureText(text, ref textBounds);

            // Calculate offsets to center the text on the screen
            float xText = info.Width / 2 - textBounds.MidX;
            float yText = info.Height / 2 - textBounds.MidY;

            // Animate the phase; t is 0 to 1 every second
            TimeSpan timeSpan = new TimeSpan(DateTime.Now.Ticks);
            float t = (float)(timeSpan.TotalSeconds % 1 / 1);
            float phase = -t * 2 * strokeWidth;

            // Create dotted line effect based on dash array and phase
            using (SKPathEffect dashEffect = SKPathEffect.CreateDash(dashArray, phase))
            {
                // Set it to the paint object
                textPaint.PathEffect = dashEffect;

                // And draw the text
                canvas.DrawText(text, xText, yText, textPaint);
            }
        }
    }
}

Yöntemin sonuna doğru, SKPathEffect.CreateDash alan olarak tanımlanan ve animasyonlu phase değer kullanılarak dashArray yöntemi çağrılır. ÖrnekSKPathEffect, metni görüntülemek için nesnesinin SKPaint özelliğine ayarlanırPathEffect.

Alternatif olarak, metni SKPaint ölçmeden ve sayfada ortalamadan önce nesneyi nesneye ayarlayabilirsinizSKPathEffect. Ancak bu durumda, animasyonlu noktalar ve tireler işlenen metnin boyutunda bazı farklılıklara neden olur ve metin biraz titreşme eğilimindedir. (Deneyin!)

Ayrıca, animasyonlu noktalar metin karakterlerinin çevresinde daire içine aldıkça, her kapalı eğride noktaların ortaya çıkıp çıktığı belirli bir nokta olduğunu da fark edeceksiniz. Bu, karakter ana hattını tanımlayan yolun başladığı ve sona erdiği yerdir. Yol uzunluğu, tire deseninin uzunluğunun tam sayı katı değilse (bu örnekte 20 piksel), bu desenin yalnızca bir kısmı yolun sonuna sığabilir.

Tire deseninin uzunluğunu yolun uzunluğuna uyacak şekilde ayarlamak mümkündür, ancak bunun için Yol Bilgileri ve Numaralandırma makalesinde ele alınan bir teknik olan yolun uzunluğunun belirlenmesi gerekir.

Nokta / Tire Dönüşüm programı tire deseninin kendisine animasyon ekler, böylece tireler tekrar tire oluşturmak için bir araya gelen noktalara ayrılmış gibi görünür:

NoktaLı Çizgi Dönüşüm sayfasının üçlü ekran görüntüsü

sınıfı, DotDashMorphPage önceki programın yaptığı gibi ve OnDisappearing yöntemlerini geçersiz kılarOnAppearing, ancak sınıfı nesneyi bir alan olarak tanımlarSKPaint:

public class DotDashMorphPage : ContentPage
{
    const float strokeWidth = 30;
    static readonly float[] dashArray = new float[4];

    SKCanvasView canvasView;
    bool pageIsActive = false;

    SKPaint ellipsePaint = new SKPaint
    {
        Style = SKPaintStyle.Stroke,
        StrokeWidth = strokeWidth,
        StrokeCap = SKStrokeCap.Round,
        Color = SKColors.Blue
    };
    ...
    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        canvas.Clear();

        // Create elliptical path
        using (SKPath ellipsePath = new SKPath())
        {
            ellipsePath.AddOval(new SKRect(50, 50, info.Width - 50, info.Height - 50));

            // Create animated path effect
            TimeSpan timeSpan = new TimeSpan(DateTime.Now.Ticks);
            float t = (float)(timeSpan.TotalSeconds % 3 / 3);
            float phase = 0;

            if (t < 0.25f)  // 1, 0, 1, 2 --> 0, 2, 0, 2
            {
                float tsub = 4 * t;
                dashArray[0] = strokeWidth * (1 - tsub);
                dashArray[1] = strokeWidth * 2 * tsub;
                dashArray[2] = strokeWidth * (1 - tsub);
                dashArray[3] = strokeWidth * 2;
            }
            else if (t < 0.5f)  // 0, 2, 0, 2 --> 1, 2, 1, 0
            {
                float tsub = 4 * (t - 0.25f);
                dashArray[0] = strokeWidth * tsub;
                dashArray[1] = strokeWidth * 2;
                dashArray[2] = strokeWidth * tsub;
                dashArray[3] = strokeWidth * 2 * (1 - tsub);
                phase = strokeWidth * tsub;
            }
            else if (t < 0.75f) // 1, 2, 1, 0 --> 0, 2, 0, 2
            {
                float tsub = 4 * (t - 0.5f);
                dashArray[0] = strokeWidth * (1 - tsub);
                dashArray[1] = strokeWidth * 2;
                dashArray[2] = strokeWidth * (1 - tsub);
                dashArray[3] = strokeWidth * 2 * tsub;
                phase = strokeWidth * (1 - tsub);
            }
            else               // 0, 2, 0, 2 --> 1, 0, 1, 2
            {
                float tsub = 4 * (t - 0.75f);
                dashArray[0] = strokeWidth * tsub;
                dashArray[1] = strokeWidth * 2 * (1 - tsub);
                dashArray[2] = strokeWidth * tsub;
                dashArray[3] = strokeWidth * 2;
            }

            using (SKPathEffect pathEffect = SKPathEffect.CreateDash(dashArray, phase))
            {
                ellipsePaint.PathEffect = pathEffect;
                canvas.DrawPath(ellipsePath, ellipsePaint);
            }
        }
    }
}

İşleyici, PaintSurface sayfanın boyutuna göre eliptik bir yol oluşturur ve kodun ve phase değişkenlerini ayarlayan dashArray uzun bir bölümünü yürütür. Animasyonlu değişken t 0 ile 1 arasında olduğundan bloklar if bu süreyi dört çeyreğe ayırır ve bu üç aylık dönemlerin tsub her birinde de 0 ile 1 arasında değişir. En sonunda, program öğesini oluşturur SKPathEffect ve çizim için nesnesine SKPaint ayarlar.

Yoldan Yola

yöntemiSKPaint, GetFillPath nesnedeki SKPaint ayarlara göre bir yolu başka bir yola dönüştürür. Bunun nasıl çalıştığını görmek için önceki programdaki çağrıyı canvas.DrawPath aşağıdaki kodla değiştirin:

SKPath newPath = new SKPath();
bool fill = ellipsePaint.GetFillPath(ellipsePath, newPath);
SKPaint newPaint = new SKPaint
{
    Style = fill ? SKPaintStyle.Fill : SKPaintStyle.Stroke
};
canvas.DrawPath(newPath, newPaint);

Bu yeni kodda GetFillPath çağrı , (yalnızca oval olan) newPathöğesini içine dönüştürür ellipsePath ve ardından ile newPaintgörüntülenir. newPaint nesnesi, özelliğin öğesinden GetFillPathBoole dönüş değerine göre ayarlanması dışında Style tüm varsayılan özellik ayarlarıyla oluşturulur.

içinde ayarlanmış ancak ayarlanmayan ellipsePaintnewPaintrenk dışında görseller aynıdır. içinde ellipsePathnewPath tanımlanan basit üç nokta yerine, nokta ve tire dizisini tanımlayan çok sayıda yol konturu içerir. Bu, çeşitli özelliklerini ellipsePaint (özellikle , StrokeCapStrokeWidthve ) ellipsePath uygulaması ve PathEffectsonuç yolunu içine newPathkoymanın sonucudur. yöntemi, GetFillPath hedef yolun doldurulup doldurulmayacağını belirten bir Boole değeri döndürür; bu örnekte, dönüş değeri true yolu doldurmak içindir.

ayarını newPaintSKPaintStyle.Stroke olarak değiştirmeyi Style denediğinizde tek bir piksel genişlikli çizgiyle ana hatlarıyla tek tek yol dağılımlarını görürsünüz.

Bir Yol ile Vuruş

SKPathEffect.Create1DPath yöntemi kavramsal olarak şuna benzerSKPathEffect.CreateDash, ancak tire ve boşluk deseni yerine bir yol belirtirsiniz. Bu yol, çizgiye veya eğriye vuruş yapmak için birden çok kez çoğaltılır.

Söz dizimi aşağıdaki gibidir:

public static SKPathEffect Create1DPath (SKPath path, Single advance,
                                         Single phase, SKPath1DPathEffectStyle style)

Genel olarak, geçirdiğiniz Create1DPath yol küçük olur ve nokta etrafında ortalanır (0, 0). parametresi, advance yol satırda çoğaltılırken yolun merkezleri arasındaki uzaklığı gösterir. Bu bağımsız değişkeni genellikle yolun yaklaşık genişliğine ayarlarsınız. bağımsız phase değişkeni burada yönteminde CreateDash olduğu gibi aynı rolü oynar.

üç SKPath1DPathEffectStyle üyeye sahiptir:

  • Translate
  • Rotate
  • Morph

Üye, Translate yolun bir çizgi veya eğri boyunca çoğaltıldığı yönle aynı yönde kalmasına neden olur. için Rotateyol, eğrinin tanjantını temel alarak döndürülür. Yolun yatay çizgiler için normal yönü vardır. Morph , yolun kendisi de konturlanan çizginin eğriliğiyle eşleşecek şekilde eğri olması dışında benzerdir Rotate .

1B Yol Efekti sayfasında bu üç seçenek gösterilir. OneDimensionalPathEffectPage.xaml dosyası, numaralandırmanın üç üyesine karşılık gelen üç öğe içeren bir seçici tanımlar:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:skia="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
             x:Class="SkiaSharpFormsDemos.Curves.OneDimensionalPathEffectPage"
             Title="1D Path Effect">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <Picker x:Name="effectStylePicker"
                Title="Effect Style"
                Grid.Row="0"
                SelectedIndexChanged="OnPickerSelectedIndexChanged">
            <Picker.ItemsSource>
                <x:Array Type="{x:Type x:String}">
                    <x:String>Translate</x:String>
                    <x:String>Rotate</x:String>
                    <x:String>Morph</x:String>
                </x:Array>
            </Picker.ItemsSource>
            <Picker.SelectedIndex>
                0
            </Picker.SelectedIndex>
        </Picker>

        <skia:SKCanvasView x:Name="canvasView"
                           PaintSurface="OnCanvasViewPaintSurface"
                           Grid.Row="1" />
    </Grid>
</ContentPage>

arka planda kod OneDimensionalPathEffectPage.xaml.cs dosyası üç SKPathEffect nesneyi alan olarak tanımlar. Bunların tümü kullanılarak, kullanılarak SKPathEffect.Create1DPathSKPath.ParseSvgPathDataoluşturulan nesnelerle SKPath oluşturulur. Birincisi basit bir kutu, ikincisi baklava şeklinde, üçüncüsü ise dikdörtgendir. Bunlar üç efekt stilini göstermek için kullanılır:

public partial class OneDimensionalPathEffectPage : ContentPage
{
    SKPathEffect translatePathEffect =
        SKPathEffect.Create1DPath(SKPath.ParseSvgPathData("M -10 -10 L 10 -10, 10 10, -10 10 Z"),
                                  24, 0, SKPath1DPathEffectStyle.Translate);

    SKPathEffect rotatePathEffect =
        SKPathEffect.Create1DPath(SKPath.ParseSvgPathData("M -10 0 L 0 -10, 10 0, 0 10 Z"),
                                  20, 0, SKPath1DPathEffectStyle.Rotate);

    SKPathEffect morphPathEffect =
        SKPathEffect.Create1DPath(SKPath.ParseSvgPathData("M -25 -10 L 25 -10, 25 10, -25 10 Z"),
                                  55, 0, SKPath1DPathEffectStyle.Morph);

    SKPaint pathPaint = new SKPaint
    {
        Color = SKColors.Blue
    };

    public OneDimensionalPathEffectPage()
    {
        InitializeComponent();
    }

    void OnPickerSelectedIndexChanged(object sender, EventArgs args)
    {
        if (canvasView != null)
        {
            canvasView.InvalidateSurface();
        }
    }

    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        canvas.Clear();

        using (SKPath path = new SKPath())
        {
            path.MoveTo(new SKPoint(0, 0));
            path.CubicTo(new SKPoint(2 * info.Width, info.Height),
                         new SKPoint(-info.Width, info.Height),
                         new SKPoint(info.Width, 0));

            switch ((string)effectStylePicker.SelectedItem))
            {
                case "Translate":
                    pathPaint.PathEffect = translatePathEffect;
                    break;

                case "Rotate":
                    pathPaint.PathEffect = rotatePathEffect;
                    break;

                case "Morph":
                    pathPaint.PathEffect = morphPathEffect;
                    break;
            }

            canvas.DrawPath(path, pathPaint);
        }
    }
}

İşleyici PaintSurface , kendi etrafında döngü yapan bir Bézier eğrisi oluşturur ve bunu vuruş yapmak için hangilerinin PathEffect kullanılması gerektiğini belirlemek için seçiciye erişir. Üç seçenek ( Translate, Rotateve Morph ) soldan sağa gösterilir:

1B Yol Efekti sayfasının üçlü ekran görüntüsü

yönteminde SKPathEffect.Create1DPath belirtilen yol her zaman doldurulur. Nesnenin PathEffect özelliği 1B yol efektine ayarlanmışsa yönteminde SKPaintDrawPath belirtilen yol her zaman konturlanır. Nesnenin pathPaint ayarı olmadığına Style ve normalde varsayılan değer olduğuna ancak yol ne olursa olsun konturlandığına Filldikkat edin.

Örnekte kullanılan Translate kutu 20 piksel karedir ve advance bağımsız değişken 24 olarak ayarlanmıştır. Bu fark, çizgi kabaca yatay veya dikey olduğunda kutular arasında bir boşluk olmasına neden olur, ancak kutunun köşegeni 28,3 piksel olduğundan çizgi çapraz olduğunda kutular biraz çakışıyor.

Örnekteki Rotate baklava şekli de 20 piksel genişliğindedir. advance 20 olarak ayarlanır, böylece elmas çizginin eğriliğiyle birlikte döndürülürken noktalar dokunmaya devam ediyor.

Örnekteki Morph dikdörtgen şekli, Bézier eğrisi etrafında bükülürken dikdörtgenler arasında küçük bir boşluk oluşturmak için 55 ayarıyla 50 piksel genişliğindedir advance .

advance Bağımsız değişken yolun boyutundan küçükse çoğaltılan yollar çakışabilir. Bu, bazı ilginç etkilere neden olabilir. Bağlı Zincir sayfasında, bir katenaryin ayırt edici şeklinde asılı olan, bağlantılı bir zincire benzeyen çakışan bir dizi daire görüntülenir:

Bağlı Zincir sayfasının üçlü ekran görüntüsü

Çok yakından baktığınızda, bunların aslında daire olmadığını göreceksiniz. Zincirdeki her bağlantı iki yaydır, boyutlandırılır ve konumlandırılır, böylece birbirine bitişik bağlantılarla bağlanır gibi görünürler.

Tekdüzen ağırlık dağılımının bir zinciri veya kablosu katenary şeklinde askıda kalıyor. Ters katenary biçiminde inşa edilmiş bir kemer, bir kemerin ağırlığından eşit basınç dağılımından yararlanır. Katenaryin görünüşte basit bir matematiksel açıklaması vardır:

y = a · cosh(x / a)

Cosh, hiperbolik kosinüs işlevidir. 0'a eşit x için cosh sıfır, y ise a'ya eşittir. Burası katenaryanın merkezi. Kosinüs işlevinde olduğu gibi, cosh'un eşit olduğu söylenir; bu da cosh(–x) öğesinin cosh(x) değerine eşit olduğu ve pozitif veya negatif bağımsız değişkenlerin artırılması için değerlerin arttığı anlamına gelir. Bu değerler, katenaryin kenarlarını oluşturan eğrileri açıklar.

Katenaryanın telefon sayfasının boyutlarına sığacak şekilde uygun değerini bulmak doğrudan bir hesaplama değildir. w ve h bir dikdörtgenin genişliği ve yüksekliğiyse, en uygun değeriaşağıdaki denklemi karşılar:

cosh(w / 2 / a) = 1 + h / a

sınıfındaki LinkedChainPage aşağıdaki yöntem, ve olarak leftrighteşittir işaretinin sol ve sağındaki iki ifadeye başvurarak bu eşitliği birleştirir. küçük değerleri için , değerinden rightbüyüktür; büyük değerleri için, left değerinden rightküçüktür. left Döngü, while aşağıdakilerin en uygun değerini daraltıyor:

float FindOptimumA(float width, float height)
{
    Func<float, float> left = (float a) => (float)Math.Cosh(width / 2 / a);
    Func<float, float> right = (float a) => 1 + height / a;

    float gtA = 1;         // starting value for left > right
    float ltA = 10000;     // starting value for left < right

    while (Math.Abs(gtA - ltA) > 0.1f)
    {
        float avgA = (gtA + ltA) / 2;

        if (left(avgA) < right(avgA))
        {
            ltA = avgA;
        }
        else
        {
            gtA = avgA;
        }
    }

    return (gtA + ltA) / 2;
}

SKPath Bağlantıların nesnesi sınıfın oluşturucusunda oluşturulur ve sonuç SKPathEffect nesnesi, alan olarak depolanan nesnenin SKPaint özelliğine PathEffect ayarlanır:

public class LinkedChainPage : ContentPage
{
    const float linkRadius = 30;
    const float linkThickness = 5;

    Func<float, float, float> catenary = (float a, float x) => (float)(a * Math.Cosh(x / a));

    SKPaint linksPaint = new SKPaint
    {
        Color = SKColors.Silver
    };

    public LinkedChainPage()
    {
        Title = "Linked Chain";

        SKCanvasView canvasView = new SKCanvasView();
        canvasView.PaintSurface += OnCanvasViewPaintSurface;
        Content = canvasView;

        // Create the path for the individual links
        SKRect outer = new SKRect(-linkRadius, -linkRadius, linkRadius, linkRadius);
        SKRect inner = outer;
        inner.Inflate(-linkThickness, -linkThickness);

        using (SKPath linkPath = new SKPath())
        {
            linkPath.AddArc(outer, 55, 160);
            linkPath.ArcTo(inner, 215, -160, false);
            linkPath.Close();

            linkPath.AddArc(outer, 235, 160);
            linkPath.ArcTo(inner, 395, -160, false);
            linkPath.Close();

            // Set that path as the 1D path effect for linksPaint
            linksPaint.PathEffect =
                SKPathEffect.Create1DPath(linkPath, 1.3f * linkRadius, 0,
                                          SKPath1DPathEffectStyle.Rotate);
        }
    }
    ...
}

İşleyicinin PaintSurface ana işi, katenaryin kendisi için bir yol oluşturmaktır. En uygun a değerini belirledikten ve değişkende optA depoladıktan sonra pencerenin en üstünden bir uzaklık hesaplaması gerekir. Ardından, katenary için bir değer koleksiyonu SKPoint birikebilir, bunu bir yola dönüştürebilir ve önceden oluşturulmuş SKPaint nesneyle yolu çizebilir:

public class LinkedChainPage : ContentPage
{
    ...
    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        canvas.Clear(SKColors.Black);

        // Width and height of catenary
        int width = info.Width;
        float height = info.Height - linkRadius;

        // Find the optimum 'a' for this width and height
        float optA = FindOptimumA(width, height);

        // Calculate the vertical offset for that value of 'a'
        float yOffset = catenary(optA, -width / 2);

        // Create a path for the catenary
        SKPoint[] points = new SKPoint[width];

        for (int x = 0; x < width; x++)
        {
            points[x] = new SKPoint(x, yOffset - catenary(optA, x - width / 2));
        }

        using (SKPath path = new SKPath())
        {
            path.AddPoly(points, false);

            // And render that path with the linksPaint object
            canvas.DrawPath(path, linksPaint);
        }
    }
    ...
}

Bu program, içinde (0, 0) noktasının merkezde olması için kullanılan Create1DPath yolu tanımlar. Yolun (0, 0) noktası, süslü olduğu çizgi veya eğriyle hizalandığından bu mantıklı görünür. Ancak bazı özel efektler için ortalanmamış (0, 0) nokta kullanabilirsiniz.

Taşıyıcı Bant sayfası, pencerenin boyutlarına göre boyutlandırılmış kavisli bir üst ve alta sahip dikdörtgen taşıyıcı banda benzeyen bir yol oluşturur. Bu yol, 20 piksel genişliğinde ve gri renkli basit SKPaint bir nesneyle konturlanır ve sonra küçük bir demete benzeyen bir yola başvuran bir SKPathEffect nesneyle başka SKPaint bir nesneyle yeniden konturlanır:

Taşıyıcı Bant sayfasının üçlü ekran görüntüsü

Demet yolunun (0, 0) noktası tutamaçtır, bu nedenle bağımsız değişken animasyonlu olduğunda phase , kovalar taşıyıcı bandın etrafında dönüyor gibi görünüyor, belki de en alttaki suyu alıp en üste boşaltıyor.

sınıfı, ConveyorBeltPage ve OnDisappearing yöntemlerinin OnAppearing geçersiz kılmalarıyla animasyon uygular. Demetin yolu, sayfanın oluşturucusunda tanımlanır:

public class ConveyorBeltPage : ContentPage
{
    SKCanvasView canvasView;
    bool pageIsActive = false;

    SKPaint conveyerPaint = new SKPaint
    {
        Style = SKPaintStyle.Stroke,
        StrokeWidth = 20,
        Color = SKColors.DarkGray
    };

    SKPath bucketPath = new SKPath();

    SKPaint bucketsPaint = new SKPaint
    {
        Color = SKColors.BurlyWood,
    };

    public ConveyorBeltPage()
    {
        Title = "Conveyor Belt";

        canvasView = new SKCanvasView();
        canvasView.PaintSurface += OnCanvasViewPaintSurface;
        Content = canvasView;

        // Create the path for the bucket starting with the handle
        bucketPath.AddRect(new SKRect(-5, -3, 25, 3));

        // Sides
        bucketPath.AddRoundedRect(new SKRect(25, -19, 27, 18), 10, 10,
                                  SKPathDirection.CounterClockwise);
        bucketPath.AddRoundedRect(new SKRect(63, -19, 65, 18), 10, 10,
                                  SKPathDirection.CounterClockwise);

        // Five slats
        for (int i = 0; i < 5; i++)
        {
            bucketPath.MoveTo(25, -19 + 8 * i);
            bucketPath.LineTo(25, -13 + 8 * i);
            bucketPath.ArcTo(50, 50, 0, SKPathArcSize.Small,
                             SKPathDirection.CounterClockwise, 65, -13 + 8 * i);
            bucketPath.LineTo(65, -19 + 8 * i);
            bucketPath.ArcTo(50, 50, 0, SKPathArcSize.Small,
                             SKPathDirection.Clockwise, 25, -19 + 8 * i);
            bucketPath.Close();
        }

        // Arc to suggest the hidden side
        bucketPath.MoveTo(25, -17);
        bucketPath.ArcTo(50, 50, 0, SKPathArcSize.Small,
                         SKPathDirection.Clockwise, 65, -17);
        bucketPath.LineTo(65, -19);
        bucketPath.ArcTo(50, 50, 0, SKPathArcSize.Small,
                         SKPathDirection.CounterClockwise, 25, -19);
        bucketPath.Close();

        // Make it a little bigger and correct the orientation
        bucketPath.Transform(SKMatrix.MakeScale(-2, 2));
        bucketPath.Transform(SKMatrix.MakeRotationDegrees(90));
    }
    ...

Demet oluşturma kodu, demeti biraz daha büyük hale getiren ve yanlara çeviren iki dönüşümle tamamlar. Bu dönüşümleri uygulamak, önceki koddaki tüm koordinatları ayarlamaktan daha kolaydı.

İşleyici, PaintSurface taşıyıcı bandın kendisi için bir yol tanımlayarak başlar. Bu yalnızca 20 piksel genişliğinde koyu-gri çizgiyle çizilmiş bir çift çizgi ve bir çift yarı dairedir:

public class ConveyorBeltPage : ContentPage
{
    ...
    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        canvas.Clear();

        float width = info.Width / 3;
        float verticalMargin = width / 2 + 150;

        using (SKPath conveyerPath = new SKPath())
        {
            // Straight verticals capped by semicircles on top and bottom
            conveyerPath.MoveTo(width, verticalMargin);
            conveyerPath.ArcTo(width / 2, width / 2, 0, SKPathArcSize.Large,
                               SKPathDirection.Clockwise, 2 * width, verticalMargin);
            conveyerPath.LineTo(2 * width, info.Height - verticalMargin);
            conveyerPath.ArcTo(width / 2, width / 2, 0, SKPathArcSize.Large,
                               SKPathDirection.Clockwise, width, info.Height - verticalMargin);
            conveyerPath.Close();

            // Draw the conveyor belt itself
            canvas.DrawPath(conveyerPath, conveyerPaint);

            // Calculate spacing based on length of conveyer path
            float length = 2 * (info.Height - 2 * verticalMargin) +
                           2 * ((float)Math.PI * width / 2);

            // Value will be somewhere around 200
            float spacing = length / (float)Math.Round(length / 200);

            // Now animate the phase; t is 0 to 1 every 2 seconds
            TimeSpan timeSpan = new TimeSpan(DateTime.Now.Ticks);
            float t = (float)(timeSpan.TotalSeconds % 2 / 2);
            float phase = -t * spacing;

            // Create the buckets PathEffect
            using (SKPathEffect bucketsPathEffect =
                        SKPathEffect.Create1DPath(bucketPath, spacing, phase,
                                                  SKPath1DPathEffectStyle.Rotate))
            {
                // Set it to the Paint object and draw the path again
                bucketsPaint.PathEffect = bucketsPathEffect;
                canvas.DrawPath(conveyerPath, bucketsPaint);
            }
        }
    }
}

Taşıyıcı bandın çizilme mantığı yatay modda çalışmaz.

Kovalar taşıyıcı bantta yaklaşık 200 piksel aralanmalıdır. Bununla birlikte, taşıyıcı bant büyük olasılıkla 200 piksel uzunluğunda bir kat değildir, yani bağımsız değişkeni SKPathEffect.Create1DPath animasyonlu olduğundan phase kovalar varoluşun içine ve dışına çıkar.

Bu nedenle, program ilk olarak taşıyıcı bandın uzunluğu olan adlı length bir değeri hesaplar. Taşıyıcı bant düz çizgilerden ve yarı dairelerden oluştuğundan, bu basit bir hesaplamadır. Ardından, demet sayısı 200'e bölünerek length hesaplanır. Bu, en yakın tamsayıya yuvarlanıp bu sayıya lengthbölünür. Sonuç, demetlerin tamsayı sayısı için bir aralıktır. Bağımsız phase değişken bunun yalnızca bir bölümüdür.

Yoldan Yola Yeniden

Taşıyıcı Bant'taki işleyicinin DrawSurface en altında, çağrıyı canvas.DrawPath açıklama satırı yapın ve aşağıdaki kodla değiştirin:

SKPath newPath = new SKPath();
bool fill = bucketsPaint.GetFillPath(conveyerPath, newPath);
SKPaint newPaint = new SKPaint
{
    Style = fill ? SKPaintStyle.Fill : SKPaintStyle.Stroke
};
canvas.DrawPath(newPath, newPaint);

Önceki örneğinde GetFillPatholduğu gibi, renk dışında sonuçların aynı olduğunu göreceksiniz. yürütüldikten GetFillPathnewPath sonra nesnesi, demet yolunun birden çok kopyasını içerir ve her biri animasyonun çağrı sırasında yerleştirmiş olduğu noktaya konumlandırılır.

Bir Alanı Tarama

SKPathEffect.Create2DLines yöntemi, bir alanı genellikle tarama çizgileri olarak adlandırılan paralel çizgilerle doldurur. yöntemi aşağıdaki söz dizimine sahiptir:

public static SKPathEffect Create2DLine (Single width, SKMatrix matrix)

bağımsız değişkeni, width tarama çizgilerinin vuruş genişliğini belirtir. matrix parametresi, ölçeklendirme ve isteğe bağlı döndürmenin bir bileşimidir. Ölçeklendirme faktörü, Skia'nın tarama çizgilerini aralarken kullandığı piksel artışını gösterir. Satırlar arasındaki ayrım, ölçeklendirme faktörü eksi bağımsız değişkenidir width . Ölçeklendirme faktörü değerden küçük veya buna eşitse width , tarama çizgileri arasında boşluk kalmaz ve alan doldurulmuş gibi görünür. Yatay ve dikey ölçeklendirme için aynı değeri belirtin.

Varsayılan olarak, tarama çizgileri yataydır. matrix Parametre döndürme içeriyorsa, tarama çizgileri saat yönünde döndürülür.

Tarama Dolgusu sayfasında bu yol efekti gösterilir. HatchFillPage sınıfı, üç yol efektini alan olarak tanımlar ve ilki 3 piksel genişliğinde yatay tarama çizgileri ve bunların birbirinden 6 piksel aralıklı olduğunu belirten bir ölçeklendirme faktörü tanımlar. Bu nedenle çizgiler arasındaki ayrım üç pikseldir. İkinci yol efekti, birbirinden 24 piksel aralıklı altı piksel genişliğinde dikey tarama çizgileridir (bu nedenle ayırma 18 pikseldir) ve üçüncüsü 36 piksel aralıklı 12 piksel genişliğinde çapraz tarama çizgileri içindir.

public class HatchFillPage : ContentPage
{
    SKPaint fillPaint = new SKPaint();

    SKPathEffect horzLinesPath = SKPathEffect.Create2DLine(3, SKMatrix.MakeScale(6, 6));

    SKPathEffect vertLinesPath = SKPathEffect.Create2DLine(6,
        Multiply(SKMatrix.MakeRotationDegrees(90), SKMatrix.MakeScale(24, 24)));

    SKPathEffect diagLinesPath = SKPathEffect.Create2DLine(12,
        Multiply(SKMatrix.MakeScale(36, 36), SKMatrix.MakeRotationDegrees(45)));

    SKPaint strokePaint = new SKPaint
    {
        Style = SKPaintStyle.Stroke,
        StrokeWidth = 3,
        Color = SKColors.Black
    };
    ...
    static SKMatrix Multiply(SKMatrix first, SKMatrix second)
    {
        SKMatrix target = SKMatrix.MakeIdentity();
        SKMatrix.Concat(ref target, first, second);
        return target;
    }
}

Matris Multiply yöntemine dikkat edin. Yatay ve dikey ölçeklendirme faktörleri aynı olduğundan, ölçeklendirme ve döndürme matrislerinin çarpıldığı sıra önemli değildir.

İşleyici, PaintSurface sayfaya sığacak şekilde boyutlandırılmış yuvarlatılmış dikdörtgeni doldurmak için birlikte üç farklı renk içeren fillPaint bu üç yol efektini kullanır. Style üzerinde fillPaint ayarlanan özellik yoksayılır; SKPaint nesne öğesinden SKPathEffect.Create2DLineoluşturulan bir yol efekti içerdiğinde, alan ne olursa olsun doldurulur:

public class HatchFillPage : ContentPage
{
    ...
    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        canvas.Clear();

        using (SKPath roundRectPath = new SKPath())
        {
            // Create a path
            roundRectPath.AddRoundedRect(
                new SKRect(50, 50, info.Width - 50, info.Height - 50), 100, 100);

            // Horizontal hatch marks
            fillPaint.PathEffect = horzLinesPath;
            fillPaint.Color = SKColors.Red;
            canvas.DrawPath(roundRectPath, fillPaint);

            // Vertical hatch marks
            fillPaint.PathEffect = vertLinesPath;
            fillPaint.Color = SKColors.Blue;
            canvas.DrawPath(roundRectPath, fillPaint);

            // Diagonal hatch marks -- use clipping
            fillPaint.PathEffect = diagLinesPath;
            fillPaint.Color = SKColors.Green;

            canvas.Save();
            canvas.ClipPath(roundRectPath);
            canvas.DrawRect(new SKRect(0, 0, info.Width, info.Height), fillPaint);
            canvas.Restore();

            // Outline the path
            canvas.DrawPath(roundRectPath, strokePaint);
        }
    }
    ...
}

Sonuçlara dikkatlice bakarsanız, kırmızı ve mavi tarama çizgilerinin yuvarlatılmış dikdörtgenle tam olarak sınırlı olmadığını görürsünüz. (Bu, temel alınan Skia kodunun bir özelliğidir.) Bu yetersizse, çapraz tarama çizgileri için yeşil renkli alternatif bir yaklaşım gösterilir: Yuvarlatılmış dikdörtgen kırpma yolu olarak kullanılır ve tarama çizgileri sayfanın tamamına çizilir.

İşleyici PaintSurface , yuvarlatılmış dikdörtgeni konturlama çağrısıyla sonuçlanır, böylece kırmızı ve mavi tarama çizgileriyle tutarsızlığı görebilirsiniz:

Tarama Dolgusu sayfasının üç kez ekran görüntüsü

Android ekranı gerçekten böyle görünmüyor: Ekran görüntüsünün ölçeklenmesi, ince kırmızı çizgilerin ve ince boşlukların daha geniş kırmızı çizgilerde ve daha geniş alanlarda birleştirilmesine neden oldu.

Bir Yol ile Doldurma

, SKPathEffect.Create2DPath bir alanı yatay ve dikey olarak çoğaltılan bir yolla doldurmanıza olanak tanır ve bu da alanı kaplar:

public static SKPathEffect Create2DPath (SKMatrix matrix, SKPath path)

SKMatrix Ölçeklendirme faktörleri çoğaltılan yolun yatay ve dikey aralığını gösterir. Ancak bu matrix bağımsız değişkeni kullanarak yolu döndüremezsiniz; yolun döndürülmesini istiyorsanız, tarafından SKPathtanımlanan yöntemi kullanarak Transform yolun kendisini döndürün.

Çoğaltılan yol normalde doldurulan alan yerine ekranın sol ve üst kenarlarıyla hizalanır. Sol ve üst kenarlardan yatay ve dikey uzaklıkları belirtmek için 0 ile ölçeklendirme faktörleri arasında çeviri faktörleri sağlayarak bu davranışı geçersiz kılabilirsiniz.

Yol Kutucuğu Dolgu sayfası bu yol efektini gösterir. Alanı döşemek için kullanılan yol, sınıfında bir alan PathTileFillPage olarak tanımlanır. Yatay ve dikey koordinatlar –40 ile 40 arasında değişir ve bu da bu yolun 80 piksel kare olduğu anlamına gelir:

public class PathTileFillPage : ContentPage
{
    SKPath tilePath = SKPath.ParseSvgPathData(
        "M -20 -20 L 2 -20, 2 -40, 18 -40, 18 -20, 40 -20, " +
        "40 -12, 20 -12, 20 12, 40 12, 40 40, 22 40, 22 20, " +
        "-2 20, -2 40, -20 40, -20 8, -40 8, -40 -8, -20 -8 Z");
    ...
    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        canvas.Clear();

        using (SKPaint paint = new SKPaint())
        {
            paint.Color = SKColors.Red;

            using (SKPathEffect pathEffect =
                   SKPathEffect.Create2DPath(SKMatrix.MakeScale(64, 64), tilePath))
            {
                paint.PathEffect = pathEffect;

                canvas.DrawRoundRect(
                    new SKRect(50, 50, info.Width - 50, info.Height - 50),
                    100, 100, paint);
            }
        }
    }
}

İşleyicide PaintSurface çağrılar SKPathEffect.Create2DPath , 80 piksel kare kutucuklarının çakışmasına neden olmak için yatay ve dikey aralığı 64 olarak ayarlar. Neyse ki, yol bir bulmaca parçasına benziyor, bitişik kutucuklarla güzel bir şekilde meshleniyor:

Yol Kutucuğu Dolgu sayfasının üçlü ekran görüntüsü

Özgün ekran görüntüsünden ölçeklendirme, özellikle Android ekranında bazı bozulmalara neden olur.

Bu kutucukların her zaman bütün olarak göründüğüne ve hiçbir zaman kesilmediğini göreceksiniz. İlk iki ekran görüntüsünde, doldurulan alanın yuvarlak bir dikdörtgen olduğu bile belli değildir. Bu kutucukları belirli bir alana kırpmak istiyorsanız, bir kırpma yolu kullanın.

nesnesinin StyleStrokeözelliğini olarak ayarlamayı SKPaint deneyin; tek tek kutucukların doldurulması yerine ana hatlarıyla özetlendiğini görürsünüz.

SkiaSharp bit eşlem döşemesi makalesinde gösterildiği gibi bir alanı kutucuklu bit eşlem ile doldurmak da mümkündür.

Keskin Köşeleri Yuvarlama

Bir Yay Çizmenin Üç Yolu makalesinde sunulan Yuvarlatılmış Heptagon programı, yedi taraflı bir şeklin noktalarını eğmek için tanjant yay kullandı. Başka Bir Yuvarlatılmış Heptagon sayfası, yöntemden SKPathEffect.CreateCorner oluşturulan yol efektini kullanan çok daha kolay bir yaklaşım gösterir:

public static SKPathEffect CreateCorner (Single radius)

Tek bağımsız değişken olarak adlandırılmış radiusolsa da, bunu istenen köşe yarıçapını yarıya ayarlamanız gerekir. (Bu, temel alınan Skia kodunun bir özelliğidir.)

sınıfındaki PaintSurface işleyici aşağıdadır AnotherRoundedHeptagonPage :

void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
    SKImageInfo info = args.Info;
    SKSurface surface = args.Surface;
    SKCanvas canvas = surface.Canvas;

    canvas.Clear();

    int numVertices = 7;
    float radius = 0.45f * Math.Min(info.Width, info.Height);
    SKPoint[] vertices = new SKPoint[numVertices];
    double vertexAngle = -0.5f * Math.PI;       // straight up

    // Coordinates of the vertices of the polygon
    for (int vertex = 0; vertex < numVertices; vertex++)
    {
        vertices[vertex] = new SKPoint(radius * (float)Math.Cos(vertexAngle),
                                       radius * (float)Math.Sin(vertexAngle));
        vertexAngle += 2 * Math.PI / numVertices;
    }

    float cornerRadius = 100;

    // Create the path
    using (SKPath path = new SKPath())
    {
        path.AddPoly(vertices, true);

        // Render the path in the center of the screen
        using (SKPaint paint = new SKPaint())
        {
            paint.Style = SKPaintStyle.Stroke;
            paint.Color = SKColors.Blue;
            paint.StrokeWidth = 10;

            // Set argument to half the desired corner radius!
            paint.PathEffect = SKPathEffect.CreateCorner(cornerRadius / 2);

            canvas.Translate(info.Width / 2, info.Height / 2);
            canvas.DrawPath(path, paint);

            // Uncomment DrawCircle call to verify corner radius
            float offset = cornerRadius / (float)Math.Sin(Math.PI * (numVertices - 2) / numVertices / 2);
            paint.Color = SKColors.Green;
            // canvas.DrawCircle(vertices[0].X, vertices[0].Y + offset, cornerRadius, paint);
        }
    }
}

Bu etkiyi, nesnenin Style özelliğine SKPaint göre stroking veya dolgu ile kullanabilirsiniz. Burada çalışıyor:

Başka Bir Yuvarlatılmış Heptagon sayfasının üçlü ekran görüntüsü

Bu yuvarlatılmış heptagonun önceki programla aynı olduğunu göreceksiniz. Çağrıda SKPathEffect.CreateCorner belirtilen 50 yerine köşe yarıçapı gerçekten 100 olduğuna daha ikna edici bir şekilde ihtiyacınız varsa, programdaki son deyimin açıklamasını açabilir ve köşede 100 yarıçaplı dairenin üst üste bindiğini görebilirsiniz.

Rastgele Değişim

Bazen bilgisayar grafiklerinin kusursuz düz çizgileri tam olarak istediğiniz gibi değildir ve biraz rastgelelik istenir. Bu durumda yöntemini denemek SKPathEffect.CreateDiscrete istersiniz:

public static SKPathEffect CreateDiscrete (Single segLength, Single deviation, UInt32 seedAssist)

Bu yol efektini, vuruş veya doldurma için kullanabilirsiniz. Çizgiler, yaklaşık uzunluğu tarafından segLength belirtilen bağlı segmentlere ayrılır ve farklı yönlerde genişletilir. Özgün satırdan sapmanın kapsamı tarafından deviationbelirtilir.

Son bağımsız değişken, etki için kullanılan sahte rastgele diziyi oluşturmak için kullanılan bir tohumdur. Değişim etkisi farklı tohumlar için biraz farklı görünecektir. Bağımsız değişkenin varsayılan değeri sıfırdır ve bu da programı her çalıştırdığınızda efektin aynı olduğu anlamına gelir. Ekran her yeniden boyanırsa farklı bir değişim istiyorsanız, tohumu bir DataTime.Now değerin Millisecond özelliğine ayarlayabilirsiniz (örneğin).

Değişim Denemesi sayfası, bir dikdörtgeni okşayarak farklı değerlerle denemeler yapmanızı sağlar:

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

Program basittir. JitterExperimentPage.xaml dosyası iki Slider öğe ve bir SKCanvasViewörneği oluşturur:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:skia="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
             x:Class="SkiaSharpFormsDemos.Curves.JitterExperimentPage"
             Title="Jitter Experiment">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <Grid.Resources>
            <ResourceDictionary>
                <Style TargetType="Label">
                    <Setter Property="HorizontalTextAlignment" Value="Center" />
                </Style>

                <Style TargetType="Slider">
                    <Setter Property="Margin" Value="20, 0" />
                    <Setter Property="Minimum" Value="0" />
                    <Setter Property="Maximum" Value="100" />
                </Style>
            </ResourceDictionary>
        </Grid.Resources>

        <Slider x:Name="segLengthSlider"
                Grid.Row="0"
                ValueChanged="sliderValueChanged" />

        <Label Text="{Binding Source={x:Reference segLengthSlider},
                              Path=Value,
                              StringFormat='Segment Length = {0:F0}'}"
               Grid.Row="1" />

        <Slider x:Name="deviationSlider"
                Grid.Row="2"
                ValueChanged="sliderValueChanged" />

        <Label Text="{Binding Source={x:Reference deviationSlider},
                              Path=Value,
                              StringFormat='Deviation = {0:F0}'}"
               Grid.Row="3" />

        <skia:SKCanvasView x:Name="canvasView"
                           Grid.Row="4"
                           PaintSurface="OnCanvasViewPaintSurface" />
    </Grid>
</ContentPage>

PaintSurface JitterExperimentPage.xaml.cs arka planda kod dosyasındaki işleyici, bir Slider değer değiştiğinde çağrılır. İki Slider değeri kullanarak çağırır SKPathEffect.CreateDiscrete ve bunu kullanarak bir dikdörtgeni konturlar:

void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
    SKImageInfo info = args.Info;
    SKSurface surface = args.Surface;
    SKCanvas canvas = surface.Canvas;

    canvas.Clear();

    float segLength = (float)segLengthSlider.Value;
    float deviation = (float)deviationSlider.Value;

    using (SKPaint paint = new SKPaint())
    {
        paint.Style = SKPaintStyle.Stroke;
        paint.StrokeWidth = 5;
        paint.Color = SKColors.Blue;

        using (SKPathEffect pathEffect = SKPathEffect.CreateDiscrete(segLength, deviation))
        {
            paint.PathEffect = pathEffect;

            SKRect rect = new SKRect(100, 100, info.Width - 100, info.Height - 100);
            canvas.DrawRect(rect, paint);
        }
    }
}

Bu efekti doldurma için de kullanabilirsiniz. Bu durumda, doldurulan alanın ana hattı bu rastgele sapmalara tabidir. Metni Değiştirme sayfası, metni görüntülemek için bu yol efektinin kullanılmasını gösterir. Sınıfın işleyicisindeki PaintSurface kodun JitterTextPage çoğu metni boyutlandırmaya ve ortalamaya ayrılmıştır:

void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
    SKImageInfo info = args.Info;
    SKSurface surface = args.Surface;
    SKCanvas canvas = surface.Canvas;

    canvas.Clear();

    string text = "FUZZY";

    using (SKPaint textPaint = new SKPaint())
    {
        textPaint.Color = SKColors.Purple;
        textPaint.PathEffect = SKPathEffect.CreateDiscrete(3f, 10f);

        // Adjust TextSize property so text is 95% of screen width
        float textWidth = textPaint.MeasureText(text);
        textPaint.TextSize *= 0.95f * info.Width / textWidth;

        // Find the text bounds
        SKRect textBounds = new SKRect();
        textPaint.MeasureText(text, ref textBounds);

        // Calculate offsets to center the text on the screen
        float xText = info.Width / 2 - textBounds.MidX;
        float yText = info.Height / 2 - textBounds.MidY;

        canvas.DrawText(text, xText, yText, textPaint);
    }
}

Burada yatay modda çalışıyor:

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

Yol Ana Hat Oluşturma

yönteminin GetFillPathSKPaintiki küçük örneğini zaten gördünüz ve iki sürümü vardır:

public Boolean GetFillPath (SKPath src, SKPath dst, Single resScale = 1)

public Boolean GetFillPath (SKPath src, SKPath dst, SKRect cullRect, Single resScale = 1)

Yalnızca ilk iki bağımsız değişken gereklidir. yöntemi bağımsız değişken tarafından src başvuruda bulunan yola erişir, nesnedeki SKPaint vuruş özelliklerine (özelliği dahil) PathEffect göre yol verilerini değiştirir ve ardından sonuçları dst yola yazar. resScale parametresi, daha küçük bir hedef yol oluşturmak için duyarlığı azaltmaya olanak tanır ve cullRect bağımsız değişken dikdörtgen dışındaki dağılımları ortadan kaldırabilir.

Bu yöntemin temel kullanımlarından biri yol efektleri içermez: Nesnenin SKPaintStyle özelliği olarak ayarlanmışsa SKPaintStyle.Strokeve kümesi yoksaPathEffect, GetFillPath boya özellikleri tarafından konturlanmış gibi kaynak yolun ana hattını temsil eden bir yol oluşturur.

Örneğin, yol 500 yarıçaplı basit bir daireyse ve SKPaint nesne 100 vuruş genişliği belirtiyorsa srcdst, yol biri 450 yarıçaplı, diğeri 550 yarıçaplı iki eşmerkezli daireye dönüşür. Bu dst yolu doldurmak yolu vuruş ile aynı olduğundan yöntemi çağrılır.GetFillPathsrc Ancak yol ana hatlarını görmek için yolu da konturlayabilirsiniz dst .

Yol Ana Hatlarına Dokun seçeneği bunu gösterir. ve örneği TapToOutlineThePathPage.xaml dosyasında oluşturulur.TapGestureRecognizerSKCanvasView Arka planda kod TapToOutlineThePathPage.xaml.cs dosyası üç SKPaint nesneyi alan olarak tanımlar; ikisi 100 ve 20 vuruş genişliklerine ve üçüncüsü doldurmaya yöneliktir:

public partial class TapToOutlineThePathPage : ContentPage
{
    bool outlineThePath = false;

    SKPaint redThickStroke = new SKPaint
    {
        Style = SKPaintStyle.Stroke,
        Color = SKColors.Red,
        StrokeWidth = 100
    };

    SKPaint redThinStroke = new SKPaint
    {
        Style = SKPaintStyle.Stroke,
        Color = SKColors.Red,
        StrokeWidth = 20
    };

    SKPaint blueFill = new SKPaint
    {
        Style = SKPaintStyle.Fill,
        Color = SKColors.Blue
    };

    public TapToOutlineThePathPage()
    {
        InitializeComponent();
    }

    void OnCanvasViewTapped(object sender, EventArgs args)
    {
        outlineThePath ^= true;
        (sender as SKCanvasView).InvalidateSurface();
    }
    ...
}

Ekrana dokunmadıysa, PaintSurface işleyici döngüsel bir yol işlemek için ve redThickStroke boya nesnelerini kullanırblueFill:

public partial class TapToOutlineThePathPage : ContentPage
{
    ...
    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        canvas.Clear();

        using (SKPath circlePath = new SKPath())
        {
            circlePath.AddCircle(info.Width / 2, info.Height / 2,
                                 Math.Min(info.Width / 2, info.Height / 2) -
                                 redThickStroke.StrokeWidth);

            if (!outlineThePath)
            {
                canvas.DrawPath(circlePath, blueFill);
                canvas.DrawPath(circlePath, redThickStroke);
            }
            else
            {
                using (SKPath outlinePath = new SKPath())
                {
                    redThickStroke.GetFillPath(circlePath, outlinePath);

                    canvas.DrawPath(outlinePath, blueFill);
                    canvas.DrawPath(outlinePath, redThinStroke);
                }
            }
        }
    }
}

Daire beklediğiniz gibi doldurulur ve konturlanır:

Yol Sayfasını Ana Hat olarak ayarlamak için normal dokunun üç kez ekran görüntüsü

Ekrana dokunduğunuzda, outlineThePath olarak ayarlanır trueve PaintSurface işleyici yeni SKPath bir nesne oluşturur ve bunu paint nesnesi üzerindeki redThickStroke bir çağrıda GetFillPath hedef yol olarak kullanır. Bu hedef yol daha sonra ile redThinStrokedoldurulur ve konturlanır ve sonuç olarak aşağıdakiler elde edilir:

Ana hatla çizilen Yol sayfasını ana hatla görüntülemek için dokunun üç ekran görüntüsü

İki kırmızı daire, orijinal dairesel yolun iki dairesel kontura dönüştürüldüğünü açıkça gösterir.

Bu yöntem, yöntem için SKPathEffect.Create1DPath kullanılacak yollar geliştirmede çok yararlı olabilir. Bu yöntemlerde belirttiğiniz yollar, yollar çoğaltıldığında her zaman doldurulur. Yolun tamamının doldurulmasını istemiyorsanız, ana hatları dikkatlice tanımlamanız gerekir.

Örneğin, Bağlı Zincir örneğinde bağlantılar, her biri doldurulacak yolun alanını ana hatlarıyla özetleyen iki yarıçapı temel alan dört yaylı bir diziyle tanımlanmıştır. Bunu biraz farklı bir şekilde yapmak için sınıftaki LinkedChainPage kodu değiştirmek mümkündür.

İlk olarak, sabiti linkRadius yeniden tanımlamak istersiniz:

const float linkRadius = 27.5f;
const float linkThickness = 5;

linkPath artık istenen başlangıç açıları ve süpürme açıları ile bu tek yarıçapı temel alan iki yaydır:

using (SKPath linkPath = new SKPath())
{
    SKRect rect = new SKRect(-linkRadius, -linkRadius, linkRadius, linkRadius);
    linkPath.AddArc(rect, 55, 160);
    linkPath.AddArc(rect, 235, 160);

    using (SKPaint strokePaint = new SKPaint())
    {
        strokePaint.Style = SKPaintStyle.Stroke;
        strokePaint.StrokeWidth = linkThickness;

        using (SKPath outlinePath = new SKPath())
        {
            strokePaint.GetFillPath(linkPath, outlinePath);

            // Set that path as the 1D path effect for linksPaint
            linksPaint.PathEffect =
                SKPathEffect.Create1DPath(outlinePath, 1.3f * linkRadius, 0,
                                          SKPath1DPathEffectStyle.Rotate);

        }

    }
}

Nesne outlinePath daha sonra içinde belirtilen strokePaintözelliklerle konturlandığında ana hattının linkPath alıcısı olur.

Bu tekniği kullanan başka bir örnek, bir yöntemde kullanılan yol için bir sonraki adımdır.

Yol Efektlerini Birleştirme

öğesinin son iki statik oluşturma yöntemi SKPathEffect şunlardırSKPathEffect.CreateSum:SKPathEffect.CreateCompose

public static SKPathEffect CreateSum (SKPathEffect first, SKPathEffect second)

public static SKPathEffect CreateCompose (SKPathEffect outer, SKPathEffect inner)

Bu yöntemlerin her ikisi de bileşik yol efekti oluşturmak için iki yol efektini birleştirir. yöntemi, CreateSum ayrı olarak uygulanan iki yol efektine benzer bir yol efekti oluştururken CreateCompose , bir yol efekti () inneruygular ve ardından bunu uygular outer .

yönteminin GetFillPathSKPaint özelliklere (dahilPathEffect) göre SKPaint bir yolu başka bir yola nasıl dönüştürebildiğini zaten gördünüz, bu nedenle bir SKPaint nesnenin veya CreateCompose yöntemlerinde CreateSum belirtilen iki yol efektiyle bu işlemi iki kez gerçekleştirebilmesi çok gizemli olmamalıdır.

bir yolu bir yol efektiyle dolduran ve yolu başka bir SKPaint yol efektiyle konturlayan bir nesne tanımlamak, bunun belirgin bir kullanımıdırCreateSum. Bu, taraklı kenarlara sahip bir çerçeve içinde bir dizi kedi görüntüleyen Çerçevedeki Kediler örneğinde gösterilmiştir:

Çerçevedeki Kediler sayfasının üçlü ekran görüntüsü

sınıfı birkaç CatsInFramePage alan tanımlayarak başlar. SVG Yol Verileri makalesinden sınıfından ilk alanı PathDataCatPage tanıyabilirsiniz. İkinci yol, çerçevenin tarak deseni için bir çizgi ve yay temel alır:

public class CatsInFramePage : ContentPage
{
    // From PathDataCatPage.cs
    SKPath catPath = SKPath.ParseSvgPathData(
        "M 160 140 L 150 50 220 103" +              // Left ear
        "M 320 140 L 330 50 260 103" +              // Right ear
        "M 215 230 L 40 200" +                      // Left whiskers
        "M 215 240 L 40 240" +
        "M 215 250 L 40 280" +
        "M 265 230 L 440 200" +                     // Right whiskers
        "M 265 240 L 440 240" +
        "M 265 250 L 440 280" +
        "M 240 100" +                               // Head
        "A 100 100 0 0 1 240 300" +
        "A 100 100 0 0 1 240 100 Z" +
        "M 180 170" +                               // Left eye
        "A 40 40 0 0 1 220 170" +
        "A 40 40 0 0 1 180 170 Z" +
        "M 300 170" +                               // Right eye
        "A 40 40 0 0 1 260 170" +
        "A 40 40 0 0 1 300 170 Z");

    SKPaint catStroke = new SKPaint
    {
        Style = SKPaintStyle.Stroke,
        StrokeWidth = 5
    };

    SKPath scallopPath =
        SKPath.ParseSvgPathData("M 0 0 L 50 0 A 60 60 0 0 1 -50 0 Z");

    SKPaint framePaint = new SKPaint
    {
        Color = SKColors.Black
    };
    ...
}

catPath nesnesi Style özelliği olarak ayarlandıysa Strokeyönteminde SKPathEffect.Create2DPathSKPaint kullanılabilir. Ancak, doğrudan bu programda kullanılırsa catPath , kedinin tüm başı doldurulur ve bıyıklar bile görünmez. (Deneyin!) Bu yolun ana hattını almak ve yönteminde SKPathEffect.Create2DPath bu ana hattı kullanmak gerekir.

Oluşturucu bu işi yapar. İlk olarak (0, 0) noktasını ortaya taşımak ve boyutunu küçültmek için iki dönüşüm catPath uygular. GetFillPath içindeki konturların tüm ana hatlarını outlinedCatPathalır ve bu nesne çağrıda SKPathEffect.Create2DPath kullanılır. Değerdeki SKMatrix ölçeklendirme faktörleri, kutucuklar arasında küçük bir arabellek sağlamak için kedinin yatay ve dikey boyutundan biraz daha büyüktür, çeviri faktörleri ise çerçevenin sol üst köşesinde tam bir kedi görünecek şekilde kısmen ampirik olarak türetilmiştir:

public class CatsInFramePage : ContentPage
{
    ...
    public CatsInFramePage()
    {
        Title = "Cats in Frame";

        SKCanvasView canvasView = new SKCanvasView();
        canvasView.PaintSurface += OnCanvasViewPaintSurface;
        Content = canvasView;

        // Move (0, 0) point to center of cat path
        catPath.Transform(SKMatrix.MakeTranslation(-240, -175));

        // Now catPath is 400 by 250
        // Scale it down to 160 by 100
        catPath.Transform(SKMatrix.MakeScale(0.40f, 0.40f));

        // Get the outlines of the contours of the cat path
        SKPath outlinedCatPath = new SKPath();
        catStroke.GetFillPath(catPath, outlinedCatPath);

        // Create a 2D path effect from those outlines
        SKPathEffect fillEffect = SKPathEffect.Create2DPath(
            new SKMatrix { ScaleX = 170, ScaleY = 110,
                           TransX = 75, TransY = 80,
                           Persp2 = 1 },
            outlinedCatPath);

        // Create a 1D path effect from the scallop path
        SKPathEffect strokeEffect =
            SKPathEffect.Create1DPath(scallopPath, 75, 0, SKPath1DPathEffectStyle.Rotate);

        // Set the sum the effects to frame paint
        framePaint.PathEffect = SKPathEffect.CreateSum(fillEffect, strokeEffect);
    }
    ...
}

Oluşturucu daha sonra taraklı çerçeveyi çağırır SKPathEffect.Create1DPath . Yolun genişliğinin 100 piksel olduğuna, ancak çoğaltılan yolun çerçevenin etrafında çakışması için ilerlemenin 75 piksel olduğuna dikkat edin. Oluşturucunun son deyimi, iki yol efektini birleştirmek ve sonucu nesnesine ayarlamak için SKPaint çağırırSKPathEffect.CreateSum.

Tüm bu çalışmalar işleyicinin PaintSurface oldukça basit olmasını sağlar. Yalnızca kullanarak bir dikdörtgen tanımlaması ve çizmesi framePaintgerekir:

public class CatsInFramePage : ContentPage
{
    ...
    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        canvas.Clear();

        SKRect rect = new SKRect(50, 50, info.Width - 50, info.Height - 50);
        canvas.ClipRect(rect);
        canvas.DrawRect(rect, framePaint);
    }
}

Yol efektlerinin arkasındaki algoritmalar her zaman, vuruş veya doldurma için kullanılan tüm yolun görüntülenmesine neden olur ve bu da bazı görsellerin dikdörtgenin dışında görünmesine neden olabilir. Aramadan ClipRect önceki DrawRect arama, görsellerin oldukça temiz olmasını sağlar. (Kırpmadan deneyin!)

Başka bir yol efektine biraz değişim eklemek için yaygın olarak kullanılır SKPathEffect.CreateCompose . Kesinlikle kendi başınıza denemeler yapabilirsiniz, ancak aşağıda biraz farklı bir örnek verilmiştir:

Kesikli Tarama Çizgileri, üç noktayı kesikli tarama çizgileri ile doldurur. Sınıfındaki çalışmanın DashedHatchLinesPage çoğu doğrudan alan tanımlarında gerçekleştirilir. Bu alanlar bir tire efekti ve bir tarama efekti tanımlar. Bunlar, tanımdaki bir SKPathEffect.CreateCompose çağrıda SKPaint başvurulacağından olarak static tanımlanır:

public class DashedHatchLinesPage : ContentPage
{
    static SKPathEffect dashEffect =
        SKPathEffect.CreateDash(new float[] { 30, 30 }, 0);

    static SKPathEffect hatchEffect = SKPathEffect.Create2DLine(20,
        Multiply(SKMatrix.MakeScale(60, 60),
                 SKMatrix.MakeRotationDegrees(45)));

    SKPaint paint = new SKPaint()
    {
        PathEffect = SKPathEffect.CreateCompose(dashEffect, hatchEffect),
        StrokeCap = SKStrokeCap.Round,
        Color = SKColors.Blue
    };
    ...
    static SKMatrix Multiply(SKMatrix first, SKMatrix second)
    {
        SKMatrix target = SKMatrix.MakeIdentity();
        SKMatrix.Concat(ref target, first, second);
        return target;
    }
}

İşleyicinin PaintSurface yalnızca standart ek yükü ve bir çağrısı içermesi DrawOvalgerekir:

public class DashedHatchLinesPage : ContentPage
{
    ...
    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        canvas.Clear();

        canvas.DrawOval(info.Width / 2, info.Height / 2,
                        0.45f * info.Width, 0.45f * info.Height,
                        paint);
    }
    ...
}

Daha önce keşfettiğiniz gibi, tarama çizgileri tam olarak alanın iç kısmıyla sınırlı değildir ve bu örnekte, her zaman soldan bir tireyle başlarlar:

Kesikli Tarama Çizgileri sayfasının üçlü ekran görüntüsü

Basit nokta ve tirelerden garip kombinasyonlara kadar uzanan yol efektleri gördüğünüze göre, hayal gücünüzü kullanın ve neler oluşturabileceğinizi görün.