Režimy mixu porter-Duff

Download Sample Stažení ukázky

Režimy mixu Porter-Duff jsou pojmenovány po Thomasi Porterovi a Tomu Duffovi, který vyvinul algebra kompozitingu při práci pro Lucasfilm. Jejich papír Kompoziting Digital Images byl publikován v červenci 1984 vydání počítačové grafiky, stránky 253 až 259. Tyto režimy blendu jsou nezbytné pro kompozitování, což je sestavení různých obrázků do složeného scény:

Porter-Duff Sample

Koncepty porter-Duff

Předpokládejme, že černěný obdélník zabírá levou a horní dvě třetiny povrchu displeje:

Porter-Duff Destination

Tato oblast se nazývá cíl nebo někdy pozadí nebo pozadí.

Chcete nakreslit následující obdélník, což je stejná velikost cíle. Obdélník je průhledný s výjimkou zmodré oblasti, která zabírá vpravo a dole dvě třetiny:

Porter-Duff Source

Tomu se říká zdroj nebo někdy popředí.

Když zdroj zobrazíte v cíli, očekáváte toto:

Porter-Duff Source Over

Průhledné pixely zdroje umožňují, aby se pozadí zobrazilo, zatímco modré zdrojové pixely zakrývají pozadí. To je normální případ, a to je uvedeno ve SkiaSharp jako SKBlendMode.SrcOver. Tato hodnota je výchozí nastavení BlendMode vlastnosti při SKPaint prvním vytvoření instance objektu.

Pro jiný efekt je ale možné určit jiný režim mixu. Pokud zadáte SKBlendMode.DstOver, pak v oblasti, kde zdroj a cíl protínají, se místo zdroje zobrazí cíl:

Porter-Duff Destination Over

Režim SKBlendMode.DstIn blendu zobrazí pouze oblast, ve které cíl a zdroj protínají cílovou barvou:

Porter-Duff Destination In

Režim kombinace (výhradního SKBlendMode.Xor or) způsobí, že se tyto dvě oblasti překrývají, nic se nezobrazí:

Porter-Duff Exclusive Or

Barevné cílové a zdrojové obdélníky efektivně rozdělují plochu zobrazení do čtyř jedinečných oblastí, které se dají obarvit různými způsoby, které odpovídají přítomnosti cílových a zdrojových obdélníků:

Porter-Duff

Obdélníky v pravém horním a levém dolním rohu jsou vždy prázdné, protože cíl i zdroj jsou v těchto oblastech průhledné. Cílová barva zabírá levou horní oblast, takže oblast může být buď barevná s cílovou barvou, nebo vůbec. Podobně zdrojová barva zabírá oblast vpravo dole, takže oblast může být barevná se zdrojovou barvou nebo vůbec. Průnik cíle a zdroje uprostřed může být barevný s cílovou barvou, zdrojovou barvou nebo vůbec ne.

Celkový počet kombinací je 2 (vlevo nahoře) krát 2 (vpravo dole) krát 3 (pro střed) nebo 12. Jedná se o 12 základních režimů kompozitování Porter-Duff.

Na konec kompozitingu digitálních obrázků (strana 256), Porter a Duff přidejte 13. režim s názvem plus (odpovídající členu SkiaSharp SKBlendMode.Plus a světlejší režim W3C (který se nesmí zaměňovat s režimem W3C Lighten.) Tento Plus režim přidá cílové a zdrojové barvy, což je proces, který se za chvíli podrobněji popisuje.

Skia přidá 14. režim, Modulate který je velmi podobný s Plus tím rozdílem, že cíl a zdrojové barvy jsou vynásobeny. Lze s ním zacházet jako s dalším režimem mixu Porter-Duff.

Tady je 14 režimů Porter-Duff definovaných ve SkiaSharpu. Tabulka ukazuje, jak v diagramu výše obarví všechny tři neprázdné oblasti:

Režim Cíl Průsečíku Source
Clear
Src Zdroj X
Dst X Cíl
SrcOver X Zdroj X
DstOver X Cíl X
SrcIn Zdroj
DstIn Cíl
SrcOut X
DstOut X
SrcATop X Zdroj
DstATop Cíl X
Xor X X
Plus X Sum X
Modulate Produkt

Tyto režimy blendu jsou symetrické. Zdroj a cíl je možné vyměnit a všechny režimy jsou stále dostupné.

Zásady vytváření názvů režimů se řídí několika jednoduchými pravidly:

  • Src nebo Dst samotný znamená, že jsou viditelné pouze zdrojové nebo cílové pixely.
  • Přípona Over označuje, co je viditelné v průsečíku. Zdroj nebo cíl je nakreslen "přes" druhý.
  • Přípona In znamená, že barva pouze průniku. Výstup je omezen pouze na část zdroje nebo cíle, která je "in" druhá.
  • Přípona Out znamená, že průsečík není barevný. Výstup je pouze část zdroje nebo cíle, která je mimo průnik.
  • Přípona ATop je sjednocení in and Out. Zahrnuje oblast, kde je zdroj nebo cíl "atop" druhého.

Všimněte si rozdílu mezi režimy Plus a Modulate režimy. Tyto režimy provádějí jiný typ výpočtu na zdrojových a cílových pixelech. Stručně jsou popsány podrobněji.

Stránka Mřížka Porter-Duff zobrazuje všechny 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 rastrové obrázky stejné velikosti, jeden s hněděným obdélníkem v levé horní oblasti a druhý s bludným obdélníkem:

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

Přepsání OnPaintSurface nakreslí dva rastrové obrázky. První se nakreslí normálně:

canvas.DrawBitmap(dstBitmap, rect);

Druhá je nakreslena objektem SKPaint , kde BlendMode byla vlastnost nastavena na argument konstruktoru:

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

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

Třída PorterDuffGridPage vytvoří čtrnáct instancí PorterDurffCanvasView, jeden pro každý člen blendModes pole. Pořadí SKBlendModes členů v poli se trochu liší od tabulky, aby bylo možné umístit podobné režimy vedle sebe. 14 instancí jsou uspořádány PorterDuffCanvasView 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;
    }
}

Tady je výsledek:

Porter-Duff Grid

Budete se chtít přesvědčit, že transparentnost je zásadní pro správné fungování režimů mixu Porter-Duff. Třída PorterDuffCanvasView obsahuje celkem tři volání metody Canvas.Clear . Všichni používají bezparametrovou metodu, která nastaví všechny pixely na transparentní:

canvas.Clear();

Zkuste některé z těchto volání změnit tak, aby pixely byly nastaveny na neprůhlenou bílou:

canvas.Clear(SKColors.White);

Po této změně se některé režimy směsi zdají fungovat, ale jiné nebudou fungovat. Pokud nastavíte pozadí zdrojového rastrového obrázku na bílou, režim nefunguje, SrcOver protože ve zdrojovém rastrovém obrázku nejsou žádné průhledné pixely, aby se cíl zobrazil. Pokud nastavíte pozadí cílového rastrového obrázku nebo plátna na bílou, nebude fungovat, DstOver protože cíl nemá žádné průhledné pixely.

Může být lákavé nahradit rastrové obrázky na stránce Mřížka Porter-Duff jednoduššími DrawRect 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ž jen modrobarevnou oblast. Zdrojový obdélník musí obsahovat průhlednou oblast, která odpovídá barevné oblasti cíle. Tyto režimy prolnutí budou fungovat pouze tehdy.

Použití matek s Porter-Duffem

Stránka s kompozitováním z cihlové zdi ukazuje příklad klasického kompozitujícího úkolu: Obrázek musí být sestaven z několika částí, včetně rastrového obrázku s pozadím, které je potřeba odstranit. Tady je rastrový obrázek SeatedMonkey.jpg s problematickým pozadím:

Seated Monkey

Při přípravě na kompozitování byl vytvořen odpovídající matný obrázek, což je další rastrový obrázek, který je černý, kde chcete, aby se obrázek zobrazoval a průhledný jinak. Tento soubor má název SeatedMonkeyMatte.png a patří mezi prostředky ve složce Media v ukázce SkiaSharpFormsDemos:

Seated Monkey Matte

Nejedná se o odborně vytvořenou matnou matici. V optimálním případě by matný materiál měl obsahovat částečně průhledné pixely kolem okraje černých pixelů a tato matná matice ne.

Soubor XAML pro stránku s cihlovou stěnou vytvoří SKCanvasView instanci a vytvoří Button instanci uživatele procesem vytvoření 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 s kódem načte dva rastrové obrázky, které potřebuje, a zpracuje Clicked událost objektu Button. Pro každé Button kliknutí se step pole zvýší a pro vlastnost je nastavena Buttonnová Text vlastnost . 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 se nic nezobrazuje s výjimkou Button:

Brick-Wall Compositing Step 0

Button Stisknutím jednou se step zvýší na 1 a obslužná rutina PaintSurface se teď zobrazí SeatedMonkey.jpg:

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 blendu. Rastrový obrázek se zobrazí v dolní části obrazovky:

Brick-Wall Compositing Step 1

Button Stiskněte tlačítko znovu a step navýší na 2. Toto je zásadní krok zobrazení souboru SeatedMonkeyMatte.png :

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 mixu znamená SKBlendMode.DstIn, že cíl se zachová v oblastech odpovídajících neprůhledým oblastem zdroje. Zbytek cílového obdélníku odpovídající původnímu rastru se stane průhledný:

Brick-Wall Compositing Step 2

Pozadí bylo odebráno.

Dalším krokem je nakreslit obdélník, který se podobá chodníku, na který opice sedí. Vzhled tohoto chodníku je založen na složení dvou shaderů: solid color shader a perlin šum shader:

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

Protože tento chodník musí jít za opicí, režim mixu je DstOver. Cíl se zobrazí jenom tam, kde je pozadí průhledné:

Brick-Wall Compositing Step 3

Posledním krokem je přidání cihlové zdi. Program používá brick-wall bitmap dlaždice k dispozici jako static vlastnost BrickWallTile ve AlgorithmicBrickWallPage třídě. Do volání pro přesunutí dlaždic se přidá SKShader.CreateBitmap transformace překladu tak, aby dolní řádek byl celou dlaždicí:

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í DrawRect zobrazí tento shader na celé plátno, ale DstOver režim omezuje výstup pouze na oblast plátna, která je stále průhledná:

Brick-Wall Compositing Step 4

Je zřejmé, že existují další způsoby, jak vytvořit tuto scénu. Mohlo by se vytvořit od pozadí a přejít na popředí. Použití režimů mixu vám ale dává větší flexibilitu. Zejména použití matného objektu umožňuje vyloučit pozadí rastrového obrázku z složené scény.

Jak jste se dozvěděli v článku Výřez s cestami a oblastmi, SKCanvas třída definuje tři typy výřezů, které odpovídají ClipRect, ClipPatha ClipRegion metodám. Režimy blendu Porter-Duff přidávají další typ výřezu, který umožňuje omezit obrázek na cokoli, co můžete kreslit, včetně rastrových obrázků. Matná matice použitá ve zděných kompozitingech v podstatě definuje oblast výřezu.

Průhlednost přechodu a přechody

Příklady režimů mixu Porter-Duff zobrazené dříve v tomto článku obsahují všechny zahrnuté obrázky, které se skládají z neprůhledných pixelů a průhledných pixelů, ale ne částečně průhledných pixelů. Pro tyto pixely jsou definovány také funkce režimu blendu. Následující tabulka představuje formální definici režimů mixu Porter-Duff, která používá notaci nalezenou v odkazu Skia SkBlendMode. (Protože Odkaz SkBlendMode je odkaz skia, používá se syntaxe jazyka C++.)

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

Zápis v následující tabulce používá následující zkratky:

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

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

Výsledek se zobrazí v závorkách s alfa kanálem a barvou RGB oddělenou čárkou: [alfa, barva]. Pro barvu 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 buď 0, nebo 1. Například pro výchozí SrcOver režim, pokud Sa je 0, pak Sc je také 0 a výsledek je [Da, Dc], cíl alfa a barva. Pokud je Sa 1, výsledek je [Sa, Sc], zdroj alfa a barva nebo [1, Sc].

Režimy Plus jsou Modulate trochu jiné než ostatní v tom, že nové barvy můžou mít za následek kombinaci zdroje a cíle. Režim Plus lze interpretovat buď s komponentami bajtů, nebo komponentami s plovoucí desetinou čárkou. Na stránce mřížky Porter-Duff zobrazené výše je cílová barva (0xC0, 0x80, 0x00) a zdrojová barva je (0x00, 0x80, 0xC0). Každá dvojice součástí je přidána, ale součet je uchycený na 0xFF. Výsledkem je barva (0xC0, 0xFF, 0xC0).) To je barva zobrazená v průsečíku.

Modulate V režimu musí být hodnoty RGB převedeny na plovoucí desetina. Cílová barva 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ůsečíku na stránce mřížky Porter-Duff pro tento režim.

Stránka Průhlednost porter-Duff umožňuje prozkoumat, jak režimy mixu Porter-Duff fungují s grafickými objekty, které jsou částečně průhledné. Soubor XAML obsahuje Picker režimy Porter-Duff:

<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 zaplní dva obdélníky stejné velikosti pomocí lineárního přechodu. Cílový přechod je z pravého horního rohu do levého dolního rohu. Je v pravém horním rohu zhnědlý, ale pak směrem ke středu začíná ulétnout na průhledný a je průhledný v levém dolním rohu.

Zdrojový obdélník má přechod z levého horního rohu do pravého dolního rohu. Levý horní roh je bludný, ale zase zmizí na průhledný a je průhledný v pravém dolním rohu.

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 režimy mixu Porter-Duff lze použít s grafickými objekty jinými než rastry. Zdroj však musí obsahovat průhlednou oblast. Toto je tento případ, protože 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 papíru Porter-Duff Compositing Digital Images , ale tato stránka ukazuje, že režimy směsi se dobře chovají pro oblasti částečné průhlednosti.

Pro některé různé efekty můžete použít průhledné přechody. Jednou z možností je maskování, což je podobné technice znázorněné v kruhových přechodech na stránce s kruhovými přechody SkiaSharp. Většina stránky Maska pro kompoziting je podobná dřívějšímu programu. Načte rastrový zdroj a určuje obdélník, ve kterém se má zobrazit. Paprskový přechod se vytvoří na základě předem určeného středu a poloměru:

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á černou ve středu a končí průhledností. Zobrazí se na rastrovém obrázku s režimem DstInprolnutí , který zobrazuje cíl pouze v oblastech zdroje, které nejsou transparentní.

DrawRect Po volání je celý povrch plátna průhledný s výjimkou kruhu definovaného paprskovým přechodem. Provede se konečný hovor:

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

Všechny průhledné oblasti plátna jsou barevně růžové:

Compositing Mask

Můžete také použít režimy Porter-Duff a částečně průhledné přechody pro přechody z jednoho obrázku do druhého. Stránka Přechody přechodu obsahuje Slider popis úrovně průběhu přechodu od 0 do 1 a Picker k výběru požadovaného typu přechodu:

<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 s kódem načte dva rastrové prostředky, které předvádějí přechod. Jedná se o stejné dva obrázky, které se používají na stránce Rozpustit rastrové obrázky dříve v tomto článku. Kód také definuje výčet se třemi členy odpovídajícími třem typům přechodů – lineární, radiální a uklidit. Tyto hodnoty jsou načteny 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 za kódem vytvoří tři SKPaint objekty. Objekt paint0 nepoužívá režim blendu. Tento objekt malování slouží k vykreslení obdélníku s přechodem, který jde z černé na průhledný, jak je uvedeno v colors poli. Pole positions je založeno na pozici Slider, ale poněkud upraveno. Pokud je hodnota Slider minimální nebo maximální, progress hodnoty jsou 0 nebo 1 a jedna z těchto dvou rastrových obrázků by měla být plně viditelná. Pole positions musí být pro tyto hodnoty nastaveno odpovídajícím způsobem.

progress Pokud je hodnota 0, positions matice obsahuje hodnoty -0,1 a 0. SkiaSharp upraví první hodnotu tak, aby byla rovna 0, což znamená, že přechod je černý pouze při 0 a průhledné jinak. Pokud progress je 0,5, matice obsahuje hodnoty 0,45 a 0,55. Přechod je černý od 0 do 0,45, pak přejde na průhledný a je zcela průhledný od 0,55 do 1. Když progress je 1, positions matice je 1 a 1,1, což znamená, že přechod je černý od 0 do 1.

position Obě pole colors se používají ve třech metodáchSKShader, které vytvářejí přechod. Na základě Picker výběru se vytvoří pouze jeden z těchto 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 průhledný. Množství černé hodnoty se zvyšuje s vyššími Slider hodnotami.

V posledních čtyřech příkazech obslužné rutiny PaintSurface se zobrazí dvě rastrové obrázky. Režim SrcOut blendu znamená, že první rastrový obrázek se zobrazí pouze v průhledných oblastech pozadí. Režim DstOver druhého rastrového obrázku znamená, že se druhý rastrový obrázek zobrazí pouze v oblastech, kde se první rastrový obrázek nezobrazuje.

Následující snímky obrazovky znázorňují tři různé typy přechodů, z nichž každý má 50 % značku:

Gradient Transitions