Aracılığıyla paylaş


Yollar ve Bölgeler ile Kırpma

Grafikleri belirli alanlara kırpmak ve bölgeler oluşturmak için yolları kullanma

Bazen grafiklerin işlenmesini belirli bir alanla kısıtlamak gerekir. Bu, kırpma olarak bilinir. Kırpmayı, bir anahtar deliğinden görülen bir maymun resmi gibi özel efektler için kullanabilirsiniz:

Anahtar deliğinden geçen maymun

Kırpma alanı, ekranın grafiklerin işlendiği alandır. Kırpma alanının dışında görüntülenen hiçbir şey işlenmez. Kırpma alanı genellikle bir dikdörtgen veya SKPath nesne tarafından tanımlanır, ancak alternatif olarak bir nesne kullanarak SKRegion kırpma alanı tanımlayabilirsiniz. Bir yoldan bölge oluşturabildiğiniz için bu iki nesne türü ilk başta birbiriyle ilişkili görünür. Ancak, bir bölgeden yol oluşturamazsınız ve bunlar dahili olarak çok farklıdır: Yol bir dizi çizgi ve eğriden oluşurken, bölge bir dizi yatay tarama çizgisiyle tanımlanır.

Yukarıdaki görüntü Keyhole aracılığıyla Maymun tarafından oluşturulmuştur. sınıfı, MonkeyThroughKeyholePage SVG verilerini kullanarak bir yol tanımlar ve program kaynaklarından bit eşlem yüklemek için oluşturucuyu kullanır:

public class MonkeyThroughKeyholePage : ContentPage
{
    SKBitmap bitmap;
    SKPath keyholePath = SKPath.ParseSvgPathData(
        "M 300 130 L 250 350 L 450 350 L 400 130 A 70 70 0 1 0 300 130 Z");

    public MonkeyThroughKeyholePage()
    {
        Title = "Monkey through Keyhole";

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

        string resourceID = "SkiaSharpFormsDemos.Media.SeatedMonkey.jpg";
        Assembly assembly = GetType().GetTypeInfo().Assembly;

        using (Stream stream = assembly.GetManifestResourceStream(resourceID))
        {
            bitmap = SKBitmap.Decode(stream);
        }
    }
    ...
}

Nesne bir anahtar deliğinin ana hattını açıklasa keyholePath da, koordinatlar tamamen rastgeledir ve yol verileri oluşturulurken neyin uygun olduğunu yansıtır. Bu nedenle, PaintSurface işleyici bu yolun sınırlarını elde eder ve yolu ekranın ortasına taşımak ve neredeyse ekran kadar uzun yapmak için çağırır TranslateScale :

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

        canvas.Clear();

        // Set transform to center and enlarge clip path to window height
        SKRect bounds;
        keyholePath.GetTightBounds(out bounds);

        canvas.Translate(info.Width / 2, info.Height / 2);
        canvas.Scale(0.98f * info.Height / bounds.Height);
        canvas.Translate(-bounds.MidX, -bounds.MidY);

        // Set the clip path
        canvas.ClipPath(keyholePath);

        // Reset transforms
        canvas.ResetMatrix();

        // Display monkey to fill height of window but maintain aspect ratio
        canvas.DrawBitmap(bitmap,
            new SKRect((info.Width - info.Height) / 2, 0,
                       (info.Width + info.Height) / 2, info.Height));
    }
}

Ancak yol işlenmez. Bunun yerine, dönüşümlerin ardından yol şu deyimle bir kırpma alanı ayarlamak için kullanılır:

canvas.ClipPath(keyholePath);

İşleyici PaintSurface daha sonra bir çağrısıyla ResetMatrix dönüşümleri sıfırlar ve bit eşlemi ekranın tam yüksekliğine genişletecek şekilde çizer. Bu kod, bit eşlem değerinin kare olduğunu ve bu bit eşlemin kare olduğunu varsayar. Bit eşlem yalnızca kırpma yolu tarafından tanımlanan alan içinde işlenir:

Anahtar deliği aracılığıyla Maymun sayfasının üçlü ekran görüntüsü

Kırpma yolu, yöntem çağrıldığında ClipPath geçerli olan dönüşümlere tabidir ve grafik nesne (bit eşlem gibi) görüntülendiğinde geçerli olan dönüşümlere tabi değildir. Kırpma yolu, yöntemiyle kaydedilen ve yöntemiyle Save geri yüklenen tuval durumunun Restore bir parçasıdır.

Kırpma Yollarını Birleştirme

Kesin olarak belirtmek gerekirse, kırpma alanı yöntemi tarafından ClipPath "ayarlı" değildir. Bunun yerine, tuvale eşit boyutta bir dikdörtgen olarak başlayan mevcut kırpma yolu ile birleştirilir. Özelliğini veya DeviceClipBounds özelliğini kullanarak LocalClipBounds kırpma alanının dikdörtgen sınırlarını elde edebilirsiniz. özelliği, LocalClipBounds etkin olabilecek dönüştürmeleri yansıtan bir SKRect değer döndürür. DeviceClipBounds özelliği bir RectI değer döndürür. Bu, tamsayı boyutlarına sahip bir dikdörtgendir ve gerçek piksel boyutlarında kırpma alanını açıklar.

Herhangi bir çağrı ClipPath , kırpma alanını yeni bir alanla birleştirerek kırpma alanını azaltır. Kırpma alanını dikdörtgenle birleştiren yöntemin tam söz dizimi ClipPath :

public Void ClipRect(SKRect rect, SKClipOperation operation = SKClipOperation.Intersect, Boolean antialias = false);

Varsayılan olarak, sonuç kırpma alanı var olan kırpma alanının ve SKPath veya yönteminde belirtilen veya SKRect değerinin ClipPathClipRect kesişimidir. Bu, Dört Daire Kesiştir Klibi sayfasında gösterilmiştir. PaintSurface sınıfındaki FourCircleInteresectClipPage işleyici aynı nesneyi yeniden kullanarak SKPath çakışan dört daire oluşturur ve her biri için ardışık çağrılar ClipPatharacılığıyla kırpma alanını azaltır:

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

    canvas.Clear();

    float size = Math.Min(info.Width, info.Height);
    float radius = 0.4f * size;
    float offset = size / 2 - radius;

    // Translate to center
    canvas.Translate(info.Width / 2, info.Height / 2);

    using (SKPath path = new SKPath())
    {
        path.AddCircle(-offset, -offset, radius);
        canvas.ClipPath(path, SKClipOperation.Intersect);

        path.Reset();
        path.AddCircle(-offset, offset, radius);
        canvas.ClipPath(path, SKClipOperation.Intersect);

        path.Reset();
        path.AddCircle(offset, -offset, radius);
        canvas.ClipPath(path, SKClipOperation.Intersect);

        path.Reset();
        path.AddCircle(offset, offset, radius);
        canvas.ClipPath(path, SKClipOperation.Intersect);

        using (SKPaint paint = new SKPaint())
        {
            paint.Style = SKPaintStyle.Fill;
            paint.Color = SKColors.Blue;
            canvas.DrawPaint(paint);
        }
    }
}

Geriye kalan, bu dört dairenin kesişimidir:

Dört Daire Kesiştir Klibi sayfasının üçlü ekran görüntüsü

Numaralandırmada SKClipOperation yalnızca iki üye vardır:

  • Difference belirtilen yolu veya dikdörtgeni varolan kırpma alanından kaldırır

  • Intersect belirtilen yolu veya dikdörtgeni var olan kırpma alanıyla kesiştir

sınıfındaki dört SKClipOperation.Intersect bağımsız değişkeni FourCircleIntersectClipPage ile SKClipOperation.Differencedeğiştirirseniz aşağıdakileri görürsünüz:

Fark işlemiyle Dört Daire Kesiştir Klibi sayfasının üçlü ekran görüntüsü

Kırpma alanından çakışan dört daire kaldırıldı.

Kırpma İşlemleri sayfası, yalnızca bir daire çifti olan bu iki işlem arasındaki farkı gösterir. Soldaki ilk daire kırpma alanına varsayılan kırpma işlemiyle Intersecteklenirken, sağdaki ikinci daire kırpma alanına eklenir ve metin etiketiyle gösterilen kırpma işlemi gösterilir:

Kırpma İşlemleri sayfasının üçlü ekran görüntüsü

ClipOperationsPage sınıfı iki SKPaint nesneyi alan olarak tanımlar ve ardından ekranı iki dikdörtgen alana böler. Bu alanlar, telefonun dikey veya yatay modda olmasına bağlı olarak farklıdır. Sınıf DisplayClipOp daha sonra metni görüntüler ve her küçük resim işlemini göstermek için iki daire yolu ile çağrılar ClipPath :

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

    canvas.Clear();

    float x = 0;
    float y = 0;

    foreach (SKClipOperation clipOp in Enum.GetValues(typeof(SKClipOperation)))
    {
        // Portrait mode
        if (info.Height > info.Width)
        {
            DisplayClipOp(canvas, new SKRect(x, y, x + info.Width, y + info.Height / 2), clipOp);
            y += info.Height / 2;
        }
        // Landscape mode
        else
        {
            DisplayClipOp(canvas, new SKRect(x, y, x + info.Width / 2, y + info.Height), clipOp);
            x += info.Width / 2;
        }
    }
}

void DisplayClipOp(SKCanvas canvas, SKRect rect, SKClipOperation clipOp)
{
    float textSize = textPaint.TextSize;
    canvas.DrawText(clipOp.ToString(), rect.MidX, rect.Top + textSize, textPaint);
    rect.Top += textSize;

    float radius = 0.9f * Math.Min(rect.Width / 3, rect.Height / 2);
    float xCenter = rect.MidX;
    float yCenter = rect.MidY;

    canvas.Save();

    using (SKPath path1 = new SKPath())
    {
        path1.AddCircle(xCenter - radius / 2, yCenter, radius);
        canvas.ClipPath(path1);

        using (SKPath path2 = new SKPath())
        {
            path2.AddCircle(xCenter + radius / 2, yCenter, radius);
            canvas.ClipPath(path2, clipOp);

            canvas.DrawPaint(fillPaint);
        }
    }

    canvas.Restore();
}

Çağırma DrawPaint normalde tuvalin tamamının bu SKPaint nesneyle doldurulmasına neden olur, ancak bu durumda yöntem yalnızca kırpma alanı içinde boyanabilir.

Bölgeleri Keşfetme

Bir kırpma alanını nesne açısından SKRegion da tanımlayabilirsiniz.

Yeni oluşturulan SKRegion nesne boş bir alanı açıklar. Genellikle nesnedeki ilk çağrı, bölgenin dikdörtgen bir alanı tanımlaması için yapılır SetRect . parametresi SetRect bir SKRectI değerdir; dikdörtgeni piksel cinsinden belirttiğinden tamsayı koordinatlarına sahip bir dikdörtgendir. Ardından bir SKPath nesneyle çağırabilirsinizSetPath. Bu, yolun iç kısmıyla aynı olan ancak ilk dikdörtgen bölgeye kırpılmış bir bölge oluşturur.

Bölge, aşağıdaki gibi yöntem aşırı yüklemelerinden Op biri çağrılarak da değiştirilebilir:

public Boolean Op(SKRegion region, SKRegionOperation op)

Numaralandırma SKRegionOperation şuna benzer SKClipOperation ancak daha fazla üyesi vardır:

  • Difference

  • Intersect

  • Union

  • XOR

  • ReverseDifference

  • Replace

Çağrıyı yaptığınız Op bölge, üyeye göre SKRegionOperation parametre olarak belirtilen bölgeyle birleştirilir. Kırpmaya uygun bir bölge elde ettiğinizde, yöntemini SKCanvaskullanarak ClipRegion bunu tuvalin kırpma alanı olarak ayarlayabilirsiniz:

public void ClipRegion(SKRegion region, SKClipOperation operation = SKClipOperation.Intersect)

Aşağıdaki ekran görüntüsünde altı bölge işlemine dayalı kırpma alanları gösterilmektedir. Sol daire yöntemin Op çağrıldığı bölgedir ve sağ daire yöntemine Op geçirilen bölgedir:

Bölge İşlemleri sayfasının üçlü ekran görüntüsü

Bu iki daireyi birleştirmenin tüm olasılıkları bunlar mı? Sonuçta elde edilen görüntüyü, kendi başlarına , Intersectve ReverseDifference işlemlerinde görülen üç bileşenin Differencebir bileşimi olarak düşünün. Toplam kombinasyon sayısı, üçüncü güç için iki veya sekizdir. Eksik olan ikisi özgün bölgedir (hiç çağrı Op yapılmamasından elde edilir) ve tamamen boş bir bölgedir.

Kırpma için bölgeleri kullanmak daha zordur çünkü önce bir yol oluşturmanız, ardından bu yoldan bir bölge oluşturmanız ve ardından birden çok bölgeyi birleştirmeniz gerekir. Bölge İşlemleri sayfasının genel yapısı Kırpma İşlemleri'ne çok benzer, ancak RegionOperationsPage sınıfı ekranı altı alana böler ve bu iş için bölgeleri kullanmak için gereken ek çalışmayı gösterir:

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

    canvas.Clear();

    float x = 0;
    float y = 0;
    float width = info.Height > info.Width ? info.Width / 2 : info.Width / 3;
    float height = info.Height > info.Width ? info.Height / 3 : info.Height / 2;

    foreach (SKRegionOperation regionOp in Enum.GetValues(typeof(SKRegionOperation)))
    {
        DisplayClipOp(canvas, new SKRect(x, y, x + width, y + height), regionOp);

        if ((x += width) >= info.Width)
        {
            x = 0;
            y += height;
        }
    }
}

void DisplayClipOp(SKCanvas canvas, SKRect rect, SKRegionOperation regionOp)
{
    float textSize = textPaint.TextSize;
    canvas.DrawText(regionOp.ToString(), rect.MidX, rect.Top + textSize, textPaint);
    rect.Top += textSize;

    float radius = 0.9f * Math.Min(rect.Width / 3, rect.Height / 2);
    float xCenter = rect.MidX;
    float yCenter = rect.MidY;

    SKRectI recti = new SKRectI((int)rect.Left, (int)rect.Top,
                                (int)rect.Right, (int)rect.Bottom);

    using (SKRegion wholeRectRegion = new SKRegion())
    {
        wholeRectRegion.SetRect(recti);

        using (SKRegion region1 = new SKRegion(wholeRectRegion))
        using (SKRegion region2 = new SKRegion(wholeRectRegion))
        {
            using (SKPath path1 = new SKPath())
            {
                path1.AddCircle(xCenter - radius / 2, yCenter, radius);
                region1.SetPath(path1);
            }

            using (SKPath path2 = new SKPath())
            {
                path2.AddCircle(xCenter + radius / 2, yCenter, radius);
                region2.SetPath(path2);
            }

            region1.Op(region2, regionOp);

            canvas.Save();
            canvas.ClipRegion(region1);
            canvas.DrawPaint(fillPaint);
            canvas.Restore();
        }
    }
}

Yöntemi ile ClipRegion yöntemi arasında ClipPath büyük bir fark vardır:

Önemli

yönteminden ClipPath farklı olarak, ClipRegion yöntem dönüşümlerden etkilenmez.

Bu farkın rasyonalitesini anlamak için bir bölgenin ne olduğunu anlamak yararlı olur. Küçük resim işlemlerinin veya bölge işlemlerinin dahili olarak nasıl uygulanabileceğini düşündüyseniz, büyük olasılıkla çok karmaşık görünmektedir. Çok karmaşık olabilecek birçok yol birleştiriliyor ve sonuçta elde edilen yolun ana hattı büyük olasılıkla algoritmik bir kabus.

Her yol, eski moda vakum tüplü TELEVIZYON'lar gibi bir dizi yatay tarama çizgisine indirgenirse bu iş önemli ölçüde basitleştirilmiştir. Her tarama çizgisi, başlangıç noktası ve bitiş noktası içeren yatay bir çizgidir. Örneğin, yarıçapı 10 piksel olan bir daire, her biri dairenin sol kısmından başlayıp sağ kısımda biten 20 yatay tarama çizgisine bölünebilir. İki daireyi herhangi bir bölge işlemiyle birleştirmek çok basit hale gelir çünkü tek yapmanız gereken her bir tarama çizgisi çiftinin başlangıç ve bitiş koordinatlarını incelemektir.

Bölge budur: Bir alanı tanımlayan bir dizi yatay tarama çizgisi.

Ancak, bir alan bir dizi tarama çizgisine indirgendiğinde, bu tarama çizgileri belirli bir piksel boyutuna dayanır. Kesin olarak belirtmek gerekirse, bölge bir vektör grafik nesnesi değildir. Sıkıştırılmış tek renkli bit eşlem, bir yola kıyasla doğası gereği daha yakındır. Sonuç olarak, bölgeler uygunluk kaybı olmadan ölçeklendirilemez veya döndürülemez ve bu nedenle kırpma alanları için kullanıldığında dönüştürülemezler.

Ancak, boyama amacıyla bölgelere dönüşümler uygulayabilirsiniz. Region Paint programı, bölgelerin iç doğasını canlı bir şekilde gösterir. sınıfı, RegionPaintPage 10 birimlik yarıçap dairesini temel alan SKPath bir nesne oluştururSKRegion. Ardından dönüşüm, sayfayı doldurmak için bu daireyi genişletir:

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

    canvas.Clear();

    int radius = 10;

    // Create circular path
    using (SKPath circlePath = new SKPath())
    {
        circlePath.AddCircle(0, 0, radius);

        // Create circular region
        using (SKRegion circleRegion = new SKRegion())
        {
            circleRegion.SetRect(new SKRectI(-radius, -radius, radius, radius));
            circleRegion.SetPath(circlePath);

            // Set transform to move it to center and scale up
            canvas.Translate(info.Width / 2, info.Height / 2);
            canvas.Scale(Math.Min(info.Width / 2, info.Height / 2) / radius);

            // Fill region
            using (SKPaint fillPaint = new SKPaint())
            {
                fillPaint.Style = SKPaintStyle.Fill;
                fillPaint.Color = SKColors.Orange;

                canvas.DrawRegion(circleRegion, fillPaint);
            }

            // Stroke path for comparison
            using (SKPaint strokePaint = new SKPaint())
            {
                strokePaint.Style = SKPaintStyle.Stroke;
                strokePaint.Color = SKColors.Blue;
                strokePaint.StrokeWidth = 0.1f;

                canvas.DrawPath(circlePath, strokePaint);
            }
        }
    }
}

Çağrı DrawRegion bölgeyi turuncuyla doldururken DrawPath , çağrı karşılaştırma için özgün yolu mavi renkte konturlar:

Bölge Boyası sayfasının üçlü ekran görüntüsü

Bölge açıkça bir dizi ayrık koordinattır.

Kırpma alanlarınızla bağlantılı dönüşümleri kullanmanız gerekmiyorsa, Dört Yapraklı Yonca sayfasında gösterildiği gibi kırpma için bölgeleri kullanabilirsiniz. FourLeafCloverPage sınıfı dört dairesel bölgeden bileşik bölge oluşturur, bu bileşik bölgeyi kırpma alanı olarak ayarlar ve ardından sayfanın ortasından 360 düz çizgiden oluşan bir seri çizer:

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

    canvas.Clear();

    float xCenter = info.Width / 2;
    float yCenter = info.Height / 2;
    float radius = 0.24f * Math.Min(info.Width, info.Height);

    using (SKRegion wholeScreenRegion = new SKRegion())
    {
        wholeScreenRegion.SetRect(new SKRectI(0, 0, info.Width, info.Height));

        using (SKRegion leftRegion = new SKRegion(wholeScreenRegion))
        using (SKRegion rightRegion = new SKRegion(wholeScreenRegion))
        using (SKRegion topRegion = new SKRegion(wholeScreenRegion))
        using (SKRegion bottomRegion = new SKRegion(wholeScreenRegion))
        {
            using (SKPath circlePath = new SKPath())
            {
                // Make basic circle path
                circlePath.AddCircle(xCenter, yCenter, radius);

                // Left leaf
                circlePath.Transform(SKMatrix.MakeTranslation(-radius, 0));
                leftRegion.SetPath(circlePath);

                // Right leaf
                circlePath.Transform(SKMatrix.MakeTranslation(2 * radius, 0));
                rightRegion.SetPath(circlePath);

                // Make union of right with left
                leftRegion.Op(rightRegion, SKRegionOperation.Union);

                // Top leaf
                circlePath.Transform(SKMatrix.MakeTranslation(-radius, -radius));
                topRegion.SetPath(circlePath);

                // Combine with bottom leaf
                circlePath.Transform(SKMatrix.MakeTranslation(0, 2 * radius));
                bottomRegion.SetPath(circlePath);

                // Make union of top with bottom
                bottomRegion.Op(topRegion, SKRegionOperation.Union);

                // Exclusive-OR left and right with top and bottom
                leftRegion.Op(bottomRegion, SKRegionOperation.XOR);

                // Set that as clip region
                canvas.ClipRegion(leftRegion);

                // Set transform for drawing lines from center
                canvas.Translate(xCenter, yCenter);

                // Draw 360 lines
                for (double angle = 0; angle < 360; angle++)
                {
                    float x = 2 * radius * (float)Math.Cos(Math.PI * angle / 180);
                    float y = 2 * radius * (float)Math.Sin(Math.PI * angle / 180);

                    using (SKPaint strokePaint = new SKPaint())
                    {
                        strokePaint.Color = SKColors.Green;
                        strokePaint.StrokeWidth = 2;

                        canvas.DrawLine(0, 0, x, y, strokePaint);
                    }
                }
            }
        }
    }
}

Aslında dört yapraklı bir yoncaya benzemez, ancak kırpma olmadan işlenmesi zor olabilecek bir görüntüdür:

Dört Yapraklı Yonca sayfasının üç ekran görüntüsü