Porter-Duff režimy prolnutí

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

Režimy Porter-Duff Blendu se pojmenují po Tomáši Porter a Duff, kteří vyvinuli algebraický skládání při práci na Lucasfilm. Své digitální image skládání na papír byly publikovány v červenci 1984 problém grafického počítače, stránky 253 až 259. Tyto režimy Blendu jsou nezbytné pro skládání, které sestavují různé obrázky do složené scény:

Porter-Duff Sample

Porter-Duff koncepty

Předpokládejme, že brownish obdélník zabírá levou a horní dvě třetiny zobrazované plochy:

Porter-Duff Destination

Tato oblast se označuje jako cílnebo někdypozadí nebo pozadí .

Chcete vykreslit následující obdélník, který má stejnou velikost jako cíl. Obdélník je transparentní s výjimkou Bluish oblasti, která zabírá pravé a dolní dvě třetiny:

Porter-Duff source

Označuje se jako zdroj nebo někdy v popředí.

Když zobrazujete zdroj v cíli, tady je to, co očekáváte:

Porter-Duff source přes

Transparentní pixely zdroje umožňují zobrazení pozadí, zatímco zdrojové pixely Bluish překrývají pozadí. To je normální případ a v ve skiasharpu se říká SKBlendMode.SrcOver . Tato hodnota je výchozí nastavení BlendMode vlastnosti při SKPaint prvním vytvoření instance objektu.

Je však možné zadat jiný režim Blendu pro jiný efekt. Pokud zadáte SKBlendMode.DstOver , v oblasti, kde se protínají zdrojové a cílové umístění, se místo zdroje zobrazí cíl:

Porter-Duff cíl přes

V SKBlendMode.DstIn režimu Blendu se zobrazí pouze oblast, kde se cíl a zdroj protínají pomocí cílové barvy:

Porter-Duff cíl v

Režim prolnutí SKBlendMode.Xor (exkluzivní nebo) způsobí, že se nic neobjeví, kde se dvě oblasti překrývají:

Porter-Duff Exclusive nebo

Barva cílového a zdrojového obdélníku efektivně rozdělí zobrazovací plochu do čtyř jedinečných oblastí, které mohou být vybarvené různými způsoby, které odpovídají přítomnosti cílových a zdrojových obdélníků:

Porter-Duff

Pravé horní a dolní obdélník jsou vždy prázdné, protože cíl i zdroj jsou v těchto oblastech transparentní. Cílová barva zabírá levou horní oblast, takže tato oblast může být buď barevná s cílovou barvou, nebo vůbec ne. Podobně se zdrojová barva zabírá v pravé dolní části, takže tato oblast může být barevně barevně označena zdrojovou barvou nebo vůbec ne. Průnik cíle a zdroje do středu může být barevný s cílovou barvou, zdrojovou barvou nebo vůbec ne.

Celkový počet kombinací je 2 (pro levý horní) časy 2 (pro střední hodnotu) 3 (pro střed) nebo 12. Jedná se o 12 základních Porter-Duff režim skládání.

Na konec skládání digitálních imagí (Page 256), Porter a Duff přidejte režim nějak změnily 13 s názvem plus (odpovídající členu ve SKIASHARPU a nasvětlejší režim W3C (který se Nepleťe s režimem zesvětlení W3C). Tento Plus režim přidává cílové a zdrojové barvy, což je proces, který se krátce podrobněji popisuje.

Skia přidá režim 14 s názvem Modulate , který je velmi podobný s Plus tím rozdílem, že se vynásobí cílové a zdrojové barvy. Může být zpracován jako další režim Porter-Duff Blend.

Zde jsou uvedené 14 Porter-Duff režimy, jak jsou definovány v ve skiasharpu. Tabulka ukazuje, jak barvy v jednotlivých třech neprázdných oblastech v diagramu výše:

Režim Cíl Průnik Zdroj
Clear
Src Zdroj ×
Dst × Cíl
SrcOver × Zdroj ×
DstOver × Cíl ×
SrcIn Zdroj
DstIn Cíl
SrcOut ×
DstOut ×
SrcATop × Zdroj
DstATop Cíl ×
Xor × ×
Plus × Sum ×
Modulate Produkt

Tyto režimy Blendu mají symetrický. Zdroj a cíl lze vyměňovat a všechny režimy jsou stále k dispozici.

Konvence pojmenování režimů následuje několik jednoduchých pravidel:

  • Src nebo DST samotný znamená, že jsou viditelné pouze zdrojové nebo cílové pixely.
  • Přípona over označuje, co je vidět v průniku. Zdroj nebo cíl se vykreslí "přes".
  • Přípona v příponě znamená, že pouze průnik je barevný. Výstup je omezen jenom na část zdroje nebo cíle, která je v druhé.
  • Přípona out znamená, že průnik není barevný. Výstupem je jenom část zdroje nebo cíle, která je v průniku "out".
  • Přípona základem je sjednocení v rámci a ven. Zahrnuje oblast, kde je zdroj nebo cíl "základem".

Všimněte si rozdílu s PlusModulate režimy a. Tyto režimy provádějí jiný typ výpočtu na zdrojovém a cílovém obrazovém pixelu. Podrobněji jsou popsány podrobněji.

Stránka Mřížka Porter-Duff zobrazuje všechny 14 režimy na jedné obrazovce ve formě mřížky. Každý režim je samostatná instance SKCanvasView . Z tohoto důvodu je třída odvozena z SKCanvasView pojmenovaného PorterDuffCanvasView . Statický konstruktor vytvoří dva rastry se stejnou velikostí, jednu s brownish obdélníkem v levé horní oblasti a druhou s obdélníkem Bluish:

class PorterDuffCanvasView : SKCanvasView
{
    static SKBitmap srcBitmap, dstBitmap;

    static PorterDuffCanvasView()
    {
        dstBitmap = new SKBitmap(300, 300);
        srcBitmap = new SKBitmap(300, 300);

        using (SKPaint paint = new SKPaint())
        {
            using (SKCanvas canvas = new SKCanvas(dstBitmap))
            {
                canvas.Clear();
                paint.Color = new SKColor(0xC0, 0x80, 0x00);
                canvas.DrawRect(new SKRect(0, 0, 200, 200), paint);
            }
            using (SKCanvas canvas = new SKCanvas(srcBitmap))
            {
                canvas.Clear();
                paint.Color = new SKColor(0x00, 0x80, 0xC0);
                canvas.DrawRect(new SKRect(100, 100, 300, 300), paint);
            }
        }
    }
    ···
}

Konstruktor instance má parametr typu SKBlendMode . Uloží tento parametr do pole.

class PorterDuffCanvasView : SKCanvasView
{
    ···
    SKBlendMode blendMode;

    public PorterDuffCanvasView(SKBlendMode blendMode)
    {
        this.blendMode = blendMode;
    }

    protected override void OnPaintSurface(SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        canvas.Clear();

        // Find largest square that fits
        float rectSize = Math.Min(info.Width, info.Height);
        float x = (info.Width - rectSize) / 2;
        float y = (info.Height - rectSize) / 2;
        SKRect rect = new SKRect(x, y, x + rectSize, y + rectSize);

        // Draw destination bitmap
        canvas.DrawBitmap(dstBitmap, rect);

        // Draw source bitmap
        using (SKPaint paint = new SKPaint())
        {
            paint.BlendMode = blendMode;
            canvas.DrawBitmap(srcBitmap, rect, paint);
        }

        // Draw outline
        using (SKPaint paint = new SKPaint())
        {
            paint.Style = SKPaintStyle.Stroke;
            paint.Color = SKColors.Black;
            paint.StrokeWidth = 2;
            rect.Inflate(-1, -1);
            canvas.DrawRect(rect, paint);
        }
    }
}

OnPaintSurfacePřepsání vykreslí dvě bitmapy. První je vykreslená normálně:

canvas.DrawBitmap(dstBitmap, rect);

Druhý je vykreslen s SKPaint objektem, kde byla BlendMode vlastnost nastavena na argument konstruktoru:

using (SKPaint paint = new SKPaint())
{
    paint.BlendMode = blendMode;
    canvas.DrawBitmap(srcBitmap, rect, paint);
}

Zbytek OnPaintSurface přepsání nakreslí obdélník kolem rastrového obrázku, aby označoval jejich velikosti.

PorterDuffGridPageTřída vytvoří čtrnáct instancí PorterDurffCanvasView , jednu pro každého člena blendModes pole. Pořadí SKBlendModes členů v poli je trochu jiné než tabulka, aby bylo možné umístit podobné režimy vedle sebe. 14 instancí PorterDuffCanvasView jsou uspořádány spolu s popisky v Grid :

public class PorterDuffGridPage : ContentPage
{
    public PorterDuffGridPage()
    {
        Title = "Porter-Duff Grid";

        SKBlendMode[] blendModes =
        {
            SKBlendMode.Src, SKBlendMode.Dst, SKBlendMode.SrcOver, SKBlendMode.DstOver,
            SKBlendMode.SrcIn, SKBlendMode.DstIn, SKBlendMode.SrcOut, SKBlendMode.DstOut,
            SKBlendMode.SrcATop, SKBlendMode.DstATop, SKBlendMode.Xor, SKBlendMode.Plus,
            SKBlendMode.Modulate, SKBlendMode.Clear
        };

        Grid grid = new Grid
        {
            Margin = new Thickness(5)
        };

        for (int row = 0; row < 4; row++)
        {
            grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
            grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Star });
        }

        for (int col = 0; col < 3; col++)
        {
            grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Star });
        }

        for (int i = 0; i < blendModes.Length; i++)
        {
            SKBlendMode blendMode = blendModes[i];
            int row = 2 * (i / 4);
            int col = i % 4;

            Label label = new Label
            {
                Text = blendMode.ToString(),
                HorizontalTextAlignment = TextAlignment.Center
            };
            Grid.SetRow(label, row);
            Grid.SetColumn(label, col);
            grid.Children.Add(label);

            PorterDuffCanvasView canvasView = new PorterDuffCanvasView(blendMode);

            Grid.SetRow(canvasView, row + 1);
            Grid.SetColumn(canvasView, col);
            grid.Children.Add(canvasView);
        }

        Content = grid;
    }
}

Výsledek:

Porter-Duff Grid

Budete chtít, abyste si sami přesvědčili, že transparentnost je zásadní pro správné fungování Porter-Duff režimů prolnutí. Třída PorterDuffCanvasView obsahuje celkem tři volání Canvas.Clear metody . Všechny používají metodu bez parametrů, která nastaví všechny pixely na průhledné:

canvas.Clear();

Zkuste kterékoli z těchto volání změnit tak, aby byly pixely nastavené na neprůhledné bílé:

canvas.Clear(SKColors.White);

Po této změně budou některé režimy prolnutí zdánlivě fungovat, ale jiné ne. Pokud nastavíte pozadí zdrojové bitmapy na bílou, režim nebude fungovat, protože ve zdrojové bitmapě nejsou žádné průhledné pixely, které by cíl SrcOver ukázal. Pokud nastavíte pozadí cílové bitmapy nebo plátna na bílou, nebude fungovat, protože cíl nemá žádné DstOver průhledné pixely.

Na stránce Mřížka Porter-Duff může být nahrazování rastrových obrázků jednoduššími voláními. To bude fungovat pro cílový obdélník, ale ne pro zdrojový obdélník. Zdrojový obdélník musí zahrnovat více než jenom namodralou oblast. Zdrojový obdélník musí obsahovat transparentní oblast, která odpovídá barevné oblasti cíle. Teprve pak budou tyto režimy prolnutí fungovat.

Použití s Porter-Duff

Stránka Brick-Wall Compositing (Kompozice stěny) ukazuje příklad klasické úlohy skládání: Obrázek je potřeba sestavit z několika částí, včetně rastrového obrázku s pozadím, které je potřeba eliminovat. Tady je obrázek SeatedMonkey.jpg s problematickým pozadím:

Seated

Při přípravě na skládání se vytvořil odpovídající matný objekt, což je další rastrová mapa, která je černá, kde chcete, aby se obrázek objevil a jinak byl transparentní. Tento soubor má názevSeatedMonkeyMatte.png a patří mezi prostředky ve složce Media v ukázce SkiaSharpFormsDemos:

Seated Namyšlovaná

Toto není zkušleně vytvořený matný matte. Matice by měla optimálně obsahovat částečně průhledné pixely kolem hrany černých pixelů, ale tento matný nikoli.

Soubor XAML pro stránku sestavení Brick-Wall Compositing vytvoří instanci a , která uživatele provede procesem sestavení Button konečného obrázku:

<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.Effects.BrickWallCompositingPage"
             Title="Brick-Wall Compositing">

    <StackLayout>
        <skia:SKCanvasView x:Name="canvasView"
                           VerticalOptions="FillAndExpand"
                           PaintSurface="OnCanvasViewPaintSurface" />

        <Button Text="Show sitting monkey"
                HorizontalOptions="Center"
                Margin="0, 10"
                Clicked="OnButtonClicked" />

    </StackLayout>
</ContentPage>

Soubor kódu načte dvě rastrové obrázky, které potřebuje, a zpracuje Clicked událost Button objektu . Při každém kliknutí se pole zvýší a pro se nastaví nová Buttonstep vlastnost TextButton . Když step dosáhne hodnoty 5, nastaví se zpět na hodnotu 0:

public partial class BrickWallCompositingPage : ContentPage
{
    SKBitmap monkeyBitmap = BitmapExtensions.LoadBitmapResource(
        typeof(BrickWallCompositingPage), 
        "SkiaSharpFormsDemos.Media.SeatedMonkey.jpg");

    SKBitmap matteBitmap = BitmapExtensions.LoadBitmapResource(
        typeof(BrickWallCompositingPage), 
        "SkiaSharpFormsDemos.Media.SeatedMonkeyMatte.png");

    int step = 0;

    public BrickWallCompositingPage ()
    {
        InitializeComponent ();
    }

    void OnButtonClicked(object sender, EventArgs args)
    {
        Button btn = (Button)sender;
        step = (step + 1) % 5;

        switch (step)
        {
            case 0: btn.Text = "Show sitting monkey"; break;
            case 1: btn.Text = "Draw matte with DstIn"; break;
            case 2: btn.Text = "Draw sidewalk with DstOver"; break;
            case 3: btn.Text = "Draw brick wall with DstOver"; break;
            case 4: btn.Text = "Reset"; break;
        }

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

        canvas.Clear();
        ···
    }
}

Při prvním spuštění programu není vidět nic kromě Button :

Brick-Wall Compositing Step 0

Jednou Button se zvýší na 1 a obslužná rutina teď zobrazí stepPaintSurfaceButton:

public partial class BrickWallCompositingPage : ContentPage
{
    ···
    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        ···
        float x = (info.Width - monkeyBitmap.Width) / 2;
        float y = info.Height - monkeyBitmap.Height;

        // Draw monkey bitmap
        if (step >= 1)
        {
            canvas.DrawBitmap(monkeyBitmap, x, y);
        }
        ···
    }
}

Neexistuje žádný SKPaint objekt, a proto žádný režim prolnutí. Bitmapa se zobrazí v dolní části obrazovky:

Brick-Wall Compositing Step 1

Znovu stiskněte Button a zvýší se na step 2. Toto je zásadní krok zobrazení SeatedMonkeyMatte.png souboru:

public partial class BrickWallCompositingPage : ContentPage
{
    ···
    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        ···
        // Draw matte to exclude monkey's surroundings
        if (step >= 2)
        {
            using (SKPaint paint = new SKPaint())
            {
                paint.BlendMode = SKBlendMode.DstIn;
                canvas.DrawBitmap(matteBitmap, x, y, paint);
            }
        }
        ···
    }
}

Režim prolnutí je , což znamená, že cíl se zachová v oblastech odpovídajících neprůhledných SKBlendMode.DstIn oblastem zdroje. Zbývající část cílového obdélníku odpovídající původnímu rastru se stane transparentním:

Brick-Wall Compositing Step 2

Pozadí bylo odebráno.

Dalším krokem je nakreslit obdélník, který se podobá nákresu, na které se opéka nachází. Vzhled tohoto předchůdku je založený na složení dvou shaderů: plnou barvu shaderu a shaderu šumu Perlinu:

public partial class BrickWallCompositingPage : ContentPage
{
    ···
    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        ···
        const float sidewalkHeight = 80;
        SKRect rect = new SKRect(info.Rect.Left, info.Rect.Bottom - sidewalkHeight,
                                 info.Rect.Right, info.Rect.Bottom);

        // Draw gravel sidewalk for monkey to sit on
        if (step >= 3)
        {
            using (SKPaint paint = new SKPaint())
            {
                paint.Shader = SKShader.CreateCompose(
                                    SKShader.CreateColor(SKColors.SandyBrown),
                                    SKShader.CreatePerlinNoiseTurbulence(0.1f, 0.3f, 1, 9));

                paint.BlendMode = SKBlendMode.DstOver;
                canvas.DrawRect(rect, paint);
            }
        }
        ···
    }
}

Vzhledem k tomu, že tento boční můstek musí jít za opici, režim prolnutí je DstOver . Cíl se zobrazí pouze tam, kde je pozadí transparentní:

Brick-Wall Compositing Step 3

Posledním krokem je přidání kamenných zdí. Program používá dlaždici rastrového obrázku zdí, která je k dispozici jako statická vlastnost BrickWallTile ve AlgorithmicBrickWallPage třídě . Do volání se přidá transformace překladu, která posune dlaždice tak, aby dolní SKShader.CreateBitmap řádek byl úplná dlaždice:

public partial class BrickWallCompositingPage : ContentPage
{
    ···
    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        ···
        // Draw bitmap tiled brick wall behind monkey
        if (step >= 4)
        {
            using (SKPaint paint = new SKPaint())
            {
                SKBitmap bitmap = AlgorithmicBrickWallPage.BrickWallTile;
                float yAdjust = (info.Height - sidewalkHeight) % bitmap.Height;

                paint.Shader = SKShader.CreateBitmap(bitmap,
                                                     SKShaderTileMode.Repeat,
                                                     SKShaderTileMode.Repeat,
                                                     SKMatrix.MakeTranslation(0, yAdjust));
                paint.BlendMode = SKBlendMode.DstOver;
                canvas.DrawRect(info.Rect, paint);
            }
        }
    }
}

Pro usnadnění volání zobrazí tento shader na celém plátně, ale režim omezuje výstup pouze na oblast plátna, která DrawRectDstOver je stále transparentní:

Brick-Wall Compositing Step 4

Je zřejmé, že existují i jiné způsoby, jak tuto scénu vytvořit. Může být vytvořena na pozadí a posouváním do popředí. Použití režimů prolnutí vám ale poskytuje větší flexibilitu. Konkrétně použití matné bitové mapy umožňuje vyloučit pozadí rastrového obrázku ze skládající se scény.

Jak jste se dozvěděli v článku Oříznutí cestami a oblastmi , třída definuje tři typy oříznutí, které odpovídají metodám , a ClipRectClipPathClipRegion . Režim Porter-Duff režimy prolnutí přidávají další typ oříznutí, který umožňuje omezit obrázek na cokoli, co můžete nakreslit, včetně rastrových obrázků. Matná matice použitá v kompozicích brick-wall v podstatě definuje oříznutí oblasti.

Přechodová průhlednost a přechody

Příklady režimů prolnutí Porter-Duff v předchozí části tohoto článku obsahují všechny obrázky, které se skládají z neprůhledných pixelů a transparentních pixelů, ale ne částečně transparentních pixelů. Funkce režimu blend-mode jsou definovány také pro tyto pixely. Následující tabulka je formálnější definicí režimu prolnutí Porter-Duff který používá notaci, kterou najdete v referenčních informacích ke Skia SkBlendMode. (Vzhledem k tomu, že odkaz SkBlendMode je odkaz Skia, používá se syntaxe jazyka C++.)

Koncepčně jsou červené, zelené, modré a alfa komponenty každého pixelu převedeny z bajtů na čísla s plovoucí desetinnou čárkou v rozsahu od 0 do 1. Pro alfa kanál je hodnota 0 plně transparentní a hodnota 1 je zcela neprůhledná.

Notace v následující tabulce používá následující zkratky:

  • Da je cílový alfa kanál.
  • Dc je barva cílového RGB
  • Sa je zdrojový alfa kanál.
  • Sc je zdrojová barva RGB

Barvy RGB se předem vynásobí hodnotou alfa. Pokud například Sc představuje čistě červenou, ale Sa 0x80, pak je barva RGB (0x80, 0, 0). Pokud je Sa 0, pak jsou všechny komponenty RGB také nulové.

Výsledek se zobrazí v hranatých závorkách s alfa kanálem a barvou RGB oddělenou čárkou: [alfa, color]. U barvy se výpočet provádí samostatně pro červené, zelené a modré komponenty:

Režim Operace
Clear [0, 0]
Src [Sa, Sc]
Dst [Da, Dc]
SrcOver [Sa + Da· (1 – Sa), Sc + Dc· (1 – Sa)
DstOver [Da + Sa· (1 – Da), Dc + Sc· (1 – Da)
SrcIn [Sa· Da, Sc· Da]
DstIn [Da· Sa, Dc·Sa]
SrcOut [Sa· (1 – Da), Sc· (1 – Da)]
DstOut [Da· (1 – Sa), Dc· (1 – Sa)]
SrcATop [da, Sc· Da + Dc· (1 – Sa)]
DstATop [Sa, Dc·Sa + Sc· (1 – Da)]
Xor [Sa + Da – 2· Sa· Da, Sc· (1 – Da) + Dc· (1 – Sa)]
Plus [Sa + Da, Sc + Dc]
Modulate [Sa· Da, Sc· Dc]

Tyto operace se snadněji analyzují, když jsou Da a Sa 0 nebo 1. Například pro výchozí režim platí, že pokud SrcOverSrcOver 0, pak Sc je také 0 a výsledek je [Da, Dc], cílový alfa a barva. Pokud je Sa 1, pak výsledek je [Sa, Sc], zdrojová alfa a barva nebo [1, Sc].

Režimy a se trochu liší od ostatních v tom, že nové barvy mohou být výsledkem kombinace zdroje PlusModulate a cíle. Režim lze interpretovat buď s komponentami s plovoucí desetinnou čárkou, nebo s komponentami s plovoucí Plus desetinnou čárkou. Na stránce Porter-Duff Grid zobrazená výše je cílová barva (0xC0, 0x80, 0x00) a zdrojová barva je (0x00, 0x80, 0xC0). Každá dvojice komponent se přidá, ale součet se zachytá 0xFF. Výsledkem je barva (0xC0, 0xFF, 0xC0). To je barva zobrazená v průniku.

Pro režim musí být hodnoty RGB převedeny na číslo s plovoucí Modulate desetinnou čárkou. Barva cíle je (0,75, 0,5, 0) a zdroj je (0, 0,5, 0,75). Komponenty RGB se vynásobí společně a výsledek je (0, 0,25, 0). To je barva zobrazená v průniku na stránce Mřížka Porter-Duff pro tento režim.

Stránka Porter-Duff Transparency (Průhlednost porteru duff) umožňuje prozkoumat, Porter-Duff režimy prolnutí pracují s grafickými objekty, které jsou částečně transparentní. Soubor XAML obsahuje s Picker Porter-Duff režimy:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:skia="clr-namespace:SkiaSharp;assembly=SkiaSharp"
             xmlns:skiaviews="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
             x:Class="SkiaSharpFormsDemos.Effects.PorterDuffTransparencyPage"
             Title="Porter-Duff Transparency">

    <StackLayout>
        <skiaviews:SKCanvasView x:Name="canvasView"
                                VerticalOptions="FillAndExpand"
                                PaintSurface="OnCanvasViewPaintSurface" />

        <Picker x:Name="blendModePicker"
                Title="Blend Mode"
                Margin="10"
                SelectedIndexChanged="OnPickerSelectedIndexChanged">
            <Picker.ItemsSource>
                <x:Array Type="{x:Type skia:SKBlendMode}">
                    <x:Static Member="skia:SKBlendMode.Clear" />
                    <x:Static Member="skia:SKBlendMode.Src" />
                    <x:Static Member="skia:SKBlendMode.Dst" />
                    <x:Static Member="skia:SKBlendMode.SrcOver" />
                    <x:Static Member="skia:SKBlendMode.DstOver" />
                    <x:Static Member="skia:SKBlendMode.SrcIn" />
                    <x:Static Member="skia:SKBlendMode.DstIn" />
                    <x:Static Member="skia:SKBlendMode.SrcOut" />
                    <x:Static Member="skia:SKBlendMode.DstOut" />
                    <x:Static Member="skia:SKBlendMode.SrcATop" />
                    <x:Static Member="skia:SKBlendMode.DstATop" />
                    <x:Static Member="skia:SKBlendMode.Xor" />
                    <x:Static Member="skia:SKBlendMode.Plus" />
                    <x:Static Member="skia:SKBlendMode.Modulate" />
                </x:Array>
            </Picker.ItemsSource>

            <Picker.SelectedIndex>
                3
            </Picker.SelectedIndex>
        </Picker>
    </StackLayout>
</ContentPage>

Soubor kódu vyplní dva obdélníky stejné velikosti pomocí lineárního přechodu. Cílový přechod je z pravého horního do levého dolního rohu. V pravém horním rohu je hnědý, ale pak směrem ke středu začne dosáhnou průhledného a v levém dolním rohu je průhledné.

Zdrojový obdélník má přechod z levého horního do pravého dolního rohu. Levý horní roh je rozmazaný, ale opět zeslábne na průhledné a v pravém dolním rohu je transparentní.

public partial class PorterDuffTransparencyPage : ContentPage
{
    public PorterDuffTransparencyPage()
    {
        InitializeComponent();
    }

    void OnPickerSelectedIndexChanged(object sender, EventArgs args)
    {
        canvasView.InvalidateSurface();
    }

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

        canvas.Clear();

        // Make square display rectangle smaller than canvas
        float size = 0.9f * Math.Min(info.Width, info.Height);
        float x = (info.Width - size) / 2;
        float y = (info.Height - size) / 2;
        SKRect rect = new SKRect(x, y, x + size, y + size);

        using (SKPaint paint = new SKPaint())
        {
            // Draw destination
            paint.Shader = SKShader.CreateLinearGradient(
                                new SKPoint(rect.Right, rect.Top),
                                new SKPoint(rect.Left, rect.Bottom),
                                new SKColor[] { new SKColor(0xC0, 0x80, 0x00),
                                                new SKColor(0xC0, 0x80, 0x00, 0) },
                                new float[] { 0.4f, 0.6f },
                                SKShaderTileMode.Clamp);

            canvas.DrawRect(rect, paint);

            // Draw source
            paint.Shader = SKShader.CreateLinearGradient(
                                new SKPoint(rect.Left, rect.Top),
                                new SKPoint(rect.Right, rect.Bottom),
                                new SKColor[] { new SKColor(0x00, 0x80, 0xC0), 
                                                new SKColor(0x00, 0x80, 0xC0, 0) },
                                new float[] { 0.4f, 0.6f },
                                SKShaderTileMode.Clamp);

            // Get the blend mode from the picker
            paint.BlendMode = blendModePicker.SelectedIndex == -1 ? 0 :
                                    (SKBlendMode)blendModePicker.SelectedItem;

            canvas.DrawRect(rect, paint);

            // Stroke surrounding rectangle
            paint.Shader = null;
            paint.BlendMode = SKBlendMode.SrcOver;
            paint.Style = SKPaintStyle.Stroke;
            paint.Color = SKColors.Black;
            paint.StrokeWidth = 3;
            canvas.DrawRect(rect, paint);
        }
    }
}

Tento program ukazuje, že Porter-Duff režimy prolnutí lze použít s grafickými objekty jinými než rastrové obrázky. Zdroj však musí obsahovat transparentní oblast. To je případ, kdy přechod vyplní obdélník, ale část přechodu je průhledná.

Tady jsou tři příklady:

Porter-Duff Transparency

Konfigurace cíle a zdroje je velmi podobná diagramům zobrazeným na stránce 255 původního dokumentu o kompozicích digitálních obrázků Porter-Duff, ale tato stránka ukazuje, že režimy prolnutí se dobře chovají z oblastí částečné průhlednosti.

Pro různé efekty můžete použít průhledné přechody. Jednou z možností je maskování, které se podobá technice znázorněné v části Radiální přechody pro maskování stránky kruhových přechodů SkiaSharp. Velká část stránky Maska skládání se podobá předchozímu programu. Načte prostředek bitmapy a určí obdélník, ve kterém se má zobrazit. Radiální přechod se vytvoří na základě předem stanovených středových a poloměrů:

public class CompositingMaskPage : ContentPage
{
    SKBitmap bitmap = BitmapExtensions.LoadBitmapResource(
        typeof(CompositingMaskPage),
        "SkiaSharpFormsDemos.Media.MountainClimbers.jpg");

    static readonly SKPoint CENTER = new SKPoint(180, 300);
    static readonly float RADIUS = 120;

    public CompositingMaskPage ()
    {
        Title = "Compositing Mask";

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

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

        canvas.Clear();

        // Find rectangle to display bitmap
        float scale = Math.Min((float)info.Width / bitmap.Width,
                               (float)info.Height / bitmap.Height);

        SKRect rect = SKRect.Create(scale * bitmap.Width, scale * bitmap.Height);

        float x = (info.Width - rect.Width) / 2;
        float y = (info.Height - rect.Height) / 2;
        rect.Offset(x, y);

        // Display bitmap in rectangle
        canvas.DrawBitmap(bitmap, rect);

        // Adjust center and radius for scaled and offset bitmap
        SKPoint center = new SKPoint(scale * CENTER.X + x,
                                        scale * CENTER.Y + y);
        float radius = scale * RADIUS;

        using (SKPaint paint = new SKPaint())
        {
            paint.Shader = SKShader.CreateRadialGradient(
                                center,
                                radius,
                                new SKColor[] { SKColors.Black,
                                                SKColors.Transparent },
                                new float[] { 0.6f, 1 },
                                SKShaderTileMode.Clamp);

            paint.BlendMode = SKBlendMode.DstIn;

            // Display rectangle using that gradient and blend mode
            canvas.DrawRect(rect, paint);
        }

        canvas.DrawColor(SKColors.Pink, SKBlendMode.DstOver);
    }
}

Rozdíl v tomto programu spočívá v tom, že přechod začíná uprostřed černou a končí průhledností. Zobrazí se na rastrovém obrázku s režimem prolnutí , který zobrazuje cíl pouze v oblastech zdroje, které DstIn nejsou transparentní.

Po volání je celý povrch plátna transparentní s výjimkou kruhu definovaného DrawRect radiálním přechodem. Konečné volání:

canvas.DrawColor(SKColors.Pink, SKBlendMode.DstOver);

Všechny průhledné oblasti plátna jsou barevné tmavě:

Skládání masky

Pro přechody z jednoho Porter-Duff na jiný můžete použít také režimy přechodu a částečně průhledné přechody. Stránka Přechody přechodů obsahuje , která označuje úroveň průběhu při přechodu z 0 na 1, a pro výběr typu Picker přechodu, který chcete:

<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.Effects.GradientTransitionsPage"
             Title="Gradient Transitions">
    
    <StackLayout>
        <skia:SKCanvasView x:Name="canvasView"
                           VerticalOptions="FillAndExpand"
                           PaintSurface="OnCanvasViewPaintSurface" />

        <Slider x:Name="progressSlider"
                Margin="10, 0"
                ValueChanged="OnSliderValueChanged" />

        <Label Text="{Binding Source={x:Reference progressSlider},
                              Path=Value,
                              StringFormat='Progress = {0:F2}'}"
               HorizontalTextAlignment="Center" />

        <Picker x:Name="transitionPicker" 
                Title="Transition" 
                Margin="10"
                SelectedIndexChanged="OnPickerSelectedIndexChanged" />
        
    </StackLayout>
</ContentPage>

Soubor kódu načte dva prostředky bitmapy pro předvedení přechodu. Jedná se o stejné dva obrázky, které jste dříve v tomto článku použili na stránce bitmapy o bitmapě. Kód také definuje výčet se třemi členy, které odpovídají třem typům přechodů – lineární, radiální a úklid. Tyto hodnoty se načítou do Picker :

public partial class GradientTransitionsPage : ContentPage
{
    SKBitmap bitmap1 = BitmapExtensions.LoadBitmapResource(
        typeof(GradientTransitionsPage),
        "SkiaSharpFormsDemos.Media.SeatedMonkey.jpg");

    SKBitmap bitmap2 = BitmapExtensions.LoadBitmapResource(
        typeof(GradientTransitionsPage),
        "SkiaSharpFormsDemos.Media.FacePalm.jpg");

    enum TransitionMode
    {
        Linear,
        Radial,
        Sweep
    };

    public GradientTransitionsPage ()
    {
        InitializeComponent ();

        foreach (TransitionMode mode in Enum.GetValues(typeof(TransitionMode)))
        {
            transitionPicker.Items.Add(mode.ToString());
        }

        transitionPicker.SelectedIndex = 0;
    }

    void OnSliderValueChanged(object sender, ValueChangedEventArgs args)
    {
        canvasView.InvalidateSurface();
    }

    void OnPickerSelectedIndexChanged(object sender, EventArgs args)
    {
        canvasView.InvalidateSurface();
    }
    ···
}

Soubor kódu vytvoří tři SKPaint objekty. Objekt paint0 nevyu používá režim prolnutí. Tento objekt malování se používá k nakreslení obdélníku s přechodem, který přechádne z černé na průhledné, jak je uvedeno v colors poli. Pole positions je založeno na pozici objektu , ale mírně Slider upraveno. Pokud je na minimu nebo maximu, hodnoty jsou 0 nebo 1 a jedna ze dvou rastrových obrázků Slider by měla být plně progress viditelná. Pole positions musí být pro tyto hodnoty nastaveno odpovídajícím způsobem.

Pokud je progress hodnota 0, pak positions pole obsahuje hodnoty -0,1 a 0. SkiaSharp upraví první hodnotu tak, aby se rovnala 0, což znamená, že přechod je černý jenom při 0 a v opačném případě transparentní. Pokud progress je hodnota 0,5, pak pole obsahuje hodnoty 0,45 a 0,55. Přechod je černý od 0 do 0,45, pak se přechází na transparentní a je plně transparentní z 0,55 na 1. Když progress je 1, pole je 1 a 1,1, což znamená, že přechod je černý od positions 0 do 1.

Pole a se používají ve třech metodách , colorsposition které vytvářejí SKShader přechod. Na základě výběru se vytvoří pouze jeden z těchto Picker shaderů:

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

        canvas.Clear();

        // Assume both bitmaps are square for display rectangle
        float size = Math.Min(info.Width, info.Height);
        SKRect rect = SKRect.Create(size, size);
        float x = (info.Width - size) / 2;
        float y = (info.Height - size) / 2;
        rect.Offset(x, y);

        using (SKPaint paint0 = new SKPaint())
        using (SKPaint paint1 = new SKPaint())
        using (SKPaint paint2 = new SKPaint())
        {
            SKColor[] colors = new SKColor[] { SKColors.Black,
                                               SKColors.Transparent };

            float progress = (float)progressSlider.Value;

            float[] positions = new float[]{ 1.1f * progress - 0.1f,
                                             1.1f * progress };

            switch ((TransitionMode)transitionPicker.SelectedIndex)
            {
                case TransitionMode.Linear:
                    paint0.Shader = SKShader.CreateLinearGradient(
                                        new SKPoint(rect.Left, 0),
                                        new SKPoint(rect.Right, 0),
                                        colors,
                                        positions,
                                        SKShaderTileMode.Clamp);
                    break;

                case TransitionMode.Radial:
                    paint0.Shader = SKShader.CreateRadialGradient(
                                        new SKPoint(rect.MidX, rect.MidY),
                                        (float)Math.Sqrt(Math.Pow(rect.Width / 2, 2) +
                                                         Math.Pow(rect.Height / 2, 2)),
                                        colors,
                                        positions,
                                        SKShaderTileMode.Clamp);
                    break;

                case TransitionMode.Sweep:
                    paint0.Shader = SKShader.CreateSweepGradient(
                                        new SKPoint(rect.MidX, rect.MidY),
                                        colors,
                                        positions);
                    break;
            }

            canvas.DrawRect(rect, paint0);

            paint1.BlendMode = SKBlendMode.SrcOut;
            canvas.DrawBitmap(bitmap1, rect, paint1);

            paint2.BlendMode = SKBlendMode.DstOver;
            canvas.DrawBitmap(bitmap2, rect, paint2);
        }
    }
}

Tento přechod se zobrazí v obdélníku bez režimu prolnutí. Po tomto DrawRect volání plátno jednoduše obsahuje přechod z černé na transparentní. Množství černé se zvyšuje s vyššími Slider hodnotami.

V posledních čtyřech příkazy obslužné PaintSurface rutiny jsou zobrazeny dvě rastrové obrázky. Režim SrcOut prolnutí znamená, že první bitmapa se zobrazí pouze v transparentních oblastech pozadí. Režim pro druhou bitmapu znamená, že druhá bitmapa je zobrazena pouze v těch oblastech, kde DstOver není zobrazen první rastrový obrázek.

Následující snímky obrazovky ukazují tři různé typy přechodů, každý s 50% značkou:

Přechody přechodů