Ořezy cestami a oblastmi

Ukázka stažení Stažení ukázky

Použití cest k vytvoření klipartů pro určité oblasti a vytváření oblastí

Někdy je nutné omezit vykreslování grafiky na určitou oblast. Toto je známé jako oříznutí. Můžete použít oříznutí pro speciální efekty, jako je například tento obrázek opice, který se zobrazuje prostřednictvím otvoru:

Opice přes otvor

Oblast oříznutí je oblast obrazovky, ve které jsou vykresleny grafiky. Cokoli, co se zobrazí mimo oblast oříznutí, není vykresleno. Oblast oříznutí je obvykle definována obdélníkem nebo SKPath objektem, ale můžete také definovat oblast oříznutí pomocí SKRegion objektu. Tyto dva typy objektů, které se zpočátku nacházejí v prvním vztahu, se týkají, protože můžete vytvořit oblast z cesty. Nemůžete ale vytvořit cestu z oblasti a je velmi rozdílná interně: cesta zahrnuje řadu čar a křivek, zatímco oblast je definována řadou horizontálního skenování.

Obrázek výše byl vytvořen pomocí stránky opice prostřednictvím stránky s díra. MonkeyThroughKeyholePageTřída definuje cestu pomocí dat SVG a používá konstruktor k načtení rastrového obrázku z prostředků programu:

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

I když keyholePath objekt popisuje obrys otvoru, souřadnice jsou zcela libovolné a odrážejí, co bylo vhodné při návrhu dat cesty. Z tohoto důvodu PaintSurface obslužná rutina získá meze této cesty a volání Translate a Scale přesune cestu do středu obrazovky a má skoro na výšku jako obrazovku:

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

Ale cesta se nevykresluje. Místo toho se po transformaci používá cesta k nastavení oblasti oříznutí tímto příkazem:

canvas.ClipPath(keyholePath);

PaintSurfaceObslužná rutina pak obnoví transformace pomocí volání ResetMatrix a nakreslí rastrový obrázek, aby se rozšířil na celou výšku obrazovky. Tento kód předpokládá, že rastr je čtvercový, což je tato konkrétní bitmapa. Bitmapa se vykreslí pouze v oblasti určené pomocí ořezové cesty:

Trojitý snímek opice na stránce s otvorem

Na ořezovou cestu se vztahují transformace v okamžiku ClipPath , kdy je metoda volána a nikoli transformace, pokud je zobrazen grafický objekt (například rastrový obrázek). Ořezová cesta je součástí stavu plátna, který je uložen s Save metodou a byl obnoven Restore metodou.

Kombinování ořezových cest

Výhradně řečeno, oblast oříznutí není nastavena ClipPath metodou. Místo toho je kombinována s existující ořezovou cestou, která začíná jako obdélník, který se rovná velikosti plátna. Obdélníkové hranice oblasti oříznutí lze získat pomocí ClipBounds vlastnosti nebo ClipDeviceBounds Vlastnosti. ClipBoundsVlastnost vrací SKRect hodnotu, která odráží všechny transformace, které mohou být v platnosti. ClipDeviceBoundsVlastnost vrací RectI hodnotu. Toto je obdélník s celočíselnými rozměry a popisuje oblast oříznutí v skutečných rozměrech v pixelech.

Jakékoli volání ClipPath snižující oblast oříznutí kombinací oblasti oříznutí k nové oblasti. Úplná syntaxe ClipPath metody je:

public void ClipPath(SKPath path, SKClipOperation operation = SKClipOperation.Intersect, Boolean antialias = false);

Existuje také ClipRect metoda, která kombinuje oblast ořezu s obdélníkem:

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

Ve výchozím nastavení je výsledná oblast oříznutí průsečík existující oblasti oříznutí a SKPath nebo SKRect , která je zadána v ClipPathClipRect metodě nebo. To je znázorněno na stránce klipu protínající čtyři kroužky . PaintSurfaceObslužná rutina ve FourCircleInteresectClipPage třídě znovu používá stejný SKPath objekt pro vytvoření čtyř překrývajících se kruhů, z nichž každý snižuje oblast oříznutí prostřednictvím po sobě jdoucích volání na ClipPath :

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

To je průnik těchto čtyř kruhů, které jsou ponechány:

Trojitý snímek obrazovky se čtyřmi kruhy na stránce klipu

SKClipOperationVýčet má pouze dva členy:

  • Difference Odebere zadanou cestu nebo obdélník z existující oblasti oříznutí.

  • Intersect protíná zadanou cestu nebo obdélník s existující oblastí oříznutí.

Pokud nahradíte čtyři SKClipOperation.Intersect argumenty ve FourCircleIntersectClipPage třídě pomocí, uvidíte SKClipOperation.Difference následující:

Trojitý snímek obrazovky se čtyřmi kruhy protínající stránku klipu s rozdílovou operací

Z oblasti oříznutí byly odebrány čtyři překrývající se kroužky.

Stránka oříznout operace znázorňuje rozdíl mezi těmito dvěma operacemi s pouze dvojicí kroužků. První kruh vlevo se přidá do oblasti oříznutí s výchozí operací Clip Intersect , zatímco druhý kroužek na pravé straně se přidá do oblasti oříznutí s operací Clip určenou textovým popiskem:

Trojitý snímek stránky operací s klipy

ClipOperationsPageTřída definuje dva SKPaint objekty jako pole a pak rozdělí obrazovku na dvě obdélníkové oblasti. Tyto oblasti se liší v závislosti na tom, jestli je telefon v režimu na výšku nebo na šířku. DisplayClipOpTřída potom zobrazí text a volání ClipPath pomocí dvou kruhových cest k ilustraci každé operace Clip:

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

Při volání DrawPaint obvykle dojde k vyplnění celého plátna tímto SKPaint objektem, ale v tomto případě metoda pouze maluje v oblasti oříznutí.

Zkoumání oblastí

Můžete také definovat oblast oříznutí z podmínek SKRegion objektu.

Nově vytvořený SKRegion objekt popisuje prázdnou oblast. Obvykle první volání objektu je SetRect tak, že oblast popisuje obdélníkovou oblast. Parametr na SetRectSKRectI hodnotu je hodnota – obdélník s celočíselnými souřadnicemi, protože určuje obdélník z hlediska pixelů. Pak můžete volat SetPath s SKPath objektem. Tím se vytvoří oblast, která je stejná jako vnitřní část cesty, ale ořízne se na počáteční obdélníkovou oblast.

Oblast lze také upravit voláním jednoho z Op přetížení metod, jako je například tento:

public Boolean Op(SKRegion region, SKRegionOperation op)

SKRegionOperationVýčet je podobný, SKClipOperation ale má více členů:

  • Difference

  • Intersect

  • Union

  • XOR

  • ReverseDifference

  • Replace

Oblast, na které provádíte Op volání, je kombinována s oblastí zadanou jako parametr na základě SKRegionOperation člena. Když nakonec získáte oblast vhodnou pro oříznutí, můžete ji nastavit jako oblast oříznutí plátna pomocí ClipRegion metody SKCanvas :

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

Následující snímek obrazovky ukazuje oblasti oříznutí na základě šesti operací oblasti. Levý kroužek je oblast, Op na kterou je metoda volána, a pravý kroužek je oblast předaná Op metodě:

Trojitý snímek stránky operací oblasti

Jsou to všechny možnosti kombinace těchto dvou kroužků? Vezměte výsledný obraz jako kombinaci tří komponent, které samy o sebe jsou vidět v DifferenceIntersectReverseDifference operacích, a. Celkový počet kombinací je třetí mocninou nebo osmi. K dispozici jsou původní oblast (která je výsledkem nevolání Op na všechny) a zcela prázdnou oblast.

Je těžké použít oblasti pro oříznutí, protože je třeba nejprve vytvořit cestu a pak oblast z této cesty a pak zkombinovat více oblastí. Celková struktura stránky operací oblasti je velmi podobná operaci Clip , ale Třída rozdělí obrazovku na šest oblastí a zobrazuje další práci nutnou k použití oblastí pro tuto úlohu:

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

Zde je velký rozdíl mezi ClipPath metodou a ClipRegion metodou:

Důležité

Na rozdíl od ClipPath metody tato ClipRegion metoda není ovlivněna transformacemi.

Pro pochopení přehlednosti tohoto rozdílu je užitečné pochopit, co je oblast. Pokud jste si mysleli, jak se operace klipartu nebo oblasti můžou implementovat interně, je pravděpodobné, že se jeví velmi komplikovanější. Je možné kombinovat několik potenciálně velmi složitých cest a obrys výsledné cesty je pravděpodobně algoritmus Nightmare.

Tato úloha je podstatně zjednodušená, pokud je každá cesta zmenšená na řadu vodorovných prověřovaných čar, jako jsou třeba ty, které jsou na Old vypsaných trubicích s vakuy. Každá čára skenování je pouze vodorovná čára s počátečním bodem a koncovým bodem. Například kružnice s poloměrem 10 pixelů může být roztvořena 20 vodorovnými řádky skenování, z nichž každá začíná v levé části kružnice a končí v pravé části. Kombinování dvou kruhů s libovolnou operací oblasti je velmi jednoduché, protože je jednoduše prověřena začátkem a koncovými souřadnicemi obou párů odpovídajících řádků skenování.

To je to, co je to oblast: řada svislých čar skenování, které definují oblast.

Pokud je však oblast zmenšena na řadu řádků skenování, jsou tyto řádky skenování založeny na konkrétní dimenzi v pixelech. Pouze v případě, že oblast není objektem vektorové grafiky. Je blíž k komprimované monochromatické bitmapě než cesta. V důsledku toho nelze oblasti škálovat ani otočit bez ztráty přesnosti a z tohoto důvodu nejsou transformované při použití pro oblasti oříznutí.

Pro účely Malování ale můžete použít transformaci na oblasti. oblast Malování program v podstatě znázorňuje vnitřní povahu oblastí. RegionPaintPageTřída vytvoří SKRegion objekt založený na SKPath kruhu poloměru 10 jednotek. Transformace pak rozbalí tento kruh, aby vyplnil stránku:

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

DrawRegionVolání vyplní oblast oranžovou barvou, zatímco volání přepíná DrawPath původní cestu modře za účelem porovnání:

obrázek stránky Malování oblasti se třemi snímky

Oblast je jasně řadou diskrétních souřadnic.

Pokud nepotřebujete transformovat v souvislosti s vašimi oblastmi oříznutí, můžete použít oblasti pro oříznutí, jak ukazuje stránka Jetelové na čtyřech listech . FourLeafCloverPageTřída vytvoří složenou oblast ze čtyř cyklických oblastí, nastaví tuto složenou oblast jako oblast oříznutí a poté nakreslí řadu 360 přímých řádků, které se nastavují od středu stránky:

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

Ve skutečnosti nevypadá jako Jetelové se čtyřmi listy, ale jedná se o obrázek, který může být jinak obtížné vykreslit bez oříznutí:

Trojitý snímek obrazovky Four-Leaf stránky Jetelové