Udostępnij za pośrednictwem


Tryby mieszania Porter-Duff

Tryby mieszania Porter-Duff są nazwane od Thomas Porter i Tom Duff, który opracował algebrę kompositingu podczas pracy dla Lucasfilm. Ich papier Compositing Digital Images został opublikowany w lipcu 1984 r. wydanie Computer Graphics, strony 253 do 259. Te tryby mieszania są niezbędne do komponowania, co polega na montażu różnych obrazów w scenie złożonej:

Przykład Porter-Duff

Pojęcia dotyczące porter-Duff

Załóżmy, że brązowy prostokąt zajmuje lewą i dwie trzecie powierzchni ekranu:

Miejsce docelowe porter-Duff

Ten obszar jest nazywany miejscem docelowym , a czasami tłem lub tłem.

Chcesz narysować następujący prostokąt, który jest tym samym rozmiarem miejsca docelowego. Prostokąt jest przezroczysty z wyjątkiem niebieskawego obszaru, który zajmuje prawą i dolną dwie trzecie:

Źródło porter-Duff

Jest to nazywane źródłem lub czasami pierwszym planem.

Po wyświetleniu źródła w miejscu docelowym oto, czego oczekujesz:

Źródło portera-Duff przez

Przezroczyste piksele źródła umożliwiają wyświetlanie tła, a bluish pikseli źródłowych zaciemnia tło. Jest to normalny przypadek i jest określany w SkiaSharp jako SKBlendMode.SrcOver. Ta wartość jest domyślnym ustawieniem BlendMode właściwości, gdy SKPaint obiekt jest najpierw tworzone.

Można jednak określić inny tryb mieszania dla innego efektu. Jeśli określisz SKBlendMode.DstOverwartość , w obszarze, w którym przecinają się źródło i miejsce docelowe, miejsce docelowe pojawi się zamiast źródła:

Miejsce docelowe portera-Duff przez

Tryb SKBlendMode.DstIn mieszany wyświetla tylko obszar, w którym miejsce docelowe i źródło przecinają się przy użyciu koloru docelowego:

Miejsce docelowe porter-Duff w

Tryb mieszany (wyłączny SKBlendMode.Xor LUB) powoduje, że nie ma miejsca, w którym dwa obszary nakładają się na siebie:

Porter-Duff wyłączny lub

Kolorowe prostokąty docelowe i źródłowe skutecznie dzielą powierzchnię wyświetlania na cztery unikatowe obszary, które można kolorować na różne sposoby odpowiadające obecności prostokątów docelowych i źródłowych:

Porter-Duff

Prostokąty w prawym górnym i lewym dolnym rogu są zawsze puste, ponieważ zarówno miejsce docelowe, jak i źródło są przezroczyste w tych obszarach. Kolor docelowy zajmuje lewy górny obszar, dzięki czemu obszar może być kolorowy z kolorem docelowym lub w ogóle nie. Podobnie kolor źródła zajmuje prawy dolny obszar, dzięki czemu obszar może być kolorowany kolorem źródłowym lub w ogóle nie. Przecięcie miejsca docelowego i źródła w środku może być kolorowe z kolorem docelowym, kolorem źródłowym lub w ogóle.

Łączna liczba kombinacji wynosi 2 (w lewym górnym rogu) razy 2 (dla prawego dolnego rogu) razy 3 (dla środka) lub 12. Są to 12 podstawowych trybów komponowania Porter-Duff.

Pod koniec kompositingu obrazów cyfrowych (strona 256), Porter i Duff dodaj tryb 13 o nazwie plus (odpowiadający członkowi SkiaSharp SKBlendMode.Plus i tryb lżejszy W3C (który nie jest mylony z trybem W3C Lighten). Ten Plus tryb dodaje kolory docelowe i źródłowe— proces, który zostanie wkrótce opisany bardziej szczegółowo.

Skia dodaje tryb 14 o nazwie Modulate , który jest bardzo podobny do Plus tego, że kolory docelowe i źródłowe są mnożone. Może być traktowany jako dodatkowy tryb mieszania Porter-Duff.

Oto 14 trybów Porter-Duff zgodnie z definicją w skiaSharp. W tabeli pokazano, jak kolorują każdy z trzech niepustych obszarów na powyższym diagramie:

Tryb Element docelowy Przecięcia Źródło
Clear
Src Lokalizacja źródłowa X
Dst X Element docelowy
SrcOver X Lokalizacja źródłowa X
DstOver X Element docelowy X
SrcIn Element źródłowy
DstIn Element docelowy
SrcOut X
DstOut X
SrcATop X Element źródłowy
DstATop Element docelowy X
Xor X X
Plus X Sum X
Modulate Rezultat

Te tryby mieszania są symetryczne. Źródło i miejsce docelowe można wymienić, a wszystkie tryby są nadal dostępne.

Konwencja nazewnictwa trybów jest zgodna z kilkoma prostymi regułami:

  • Sam Src lub Dst oznacza, że widoczne są tylko piksele źródłowe lub docelowe.
  • Sufiks Over wskazuje, co jest widoczne na skrzyżowaniu. Źródło lub miejsce docelowe jest rysowane "za pośrednictwem" drugiej.
  • Sufiks In oznacza, że tylko przecięcie jest kolorowe. Dane wyjściowe są ograniczone tylko do części źródłowej lub docelowej, która znajduje się "w" drugiej.
  • Sufiks Out oznacza, że przecięcie nie jest kolorowe. Dane wyjściowe są tylko częścią źródła lub miejsca docelowego, który jest "poza" skrzyżowania.
  • Sufiks ATop jest związkiem wartości In i Out. Obejmuje on obszar, w którym źródło lub miejsce docelowe jest "na szczycie" drugiego.

Zwróć uwagę na różnicę w trybach Plus i .Modulate Te tryby wykonują różne typy obliczeń na pikselach źródłowych i docelowych. Zostały one opisane bardziej szczegółowo wkrótce.

Na stronie Porter-Duff Grid są wyświetlane wszystkie 14 tryby na jednym ekranie w postaci siatki. Każdy tryb jest oddzielnym wystąpieniem SKCanvasViewklasy . Z tego powodu klasa pochodzi z SKCanvasView nazwy PorterDuffCanvasView. Konstruktor statyczny tworzy dwie mapy bitowe o tym samym rozmiarze, jeden z brązowym prostokątem w lewym górnym obszarze, a drugi z niebieskawym prostokątem:

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 wystąpienia ma parametr typu SKBlendMode. Zapisuje ten parametr w polu.

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

Przesłonięcia OnPaintSurface rysują dwie mapy bitowe. Pierwszy jest rysowany normalnie:

canvas.DrawBitmap(dstBitmap, rect);

Drugi jest rysowany z obiektem SKPaint , w którym BlendMode właściwość została ustawiona na argument konstruktora:

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

Pozostała część OnPaintSurface przesłonięcia rysuje prostokąt wokół mapy bitowej, aby wskazać ich rozmiary.

Klasa PorterDuffGridPage tworzy czternaście wystąpień PorterDurffCanvasViewklasy , po jednym dla każdego elementu członkowskiego tablicy blendModes . Kolejność SKBlendModes elementów członkowskich w tablicy jest nieco inna niż tabela, aby umieścić podobne tryby sąsiadujące ze sobą. 14 wystąpień PorterDuffCanvasView klasy są zorganizowane wraz z etykietami w obiekcie 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;
    }
}

Oto wynik:

Siatka Porter-Duff

Chcesz przekonać się, że przejrzystość ma kluczowe znaczenie dla prawidłowego funkcjonowania trybów mieszania Porter-Duff. Klasa PorterDuffCanvasView zawiera łącznie trzy wywołania Canvas.Clear metody . Wszystkie z nich używają metody bez parametrów, która ustawia wszystkie piksele na przezroczyste:

canvas.Clear();

Spróbuj zmienić dowolne z tych wywołań, aby piksele były ustawione na nieprzezroczyste białe:

canvas.Clear(SKColors.White);

Po tej zmianie niektóre tryby mieszania wydają się działać, ale inne nie. Jeśli ustawisz tło źródłowej mapy bitowej na biały, tryb nie działa, SrcOver ponieważ w źródłowej mapie bitowej nie ma przezroczystych pikseli, aby umożliwić wyświetlanie miejsca docelowego. Jeśli ustawisz tło docelowej mapy bitowej lub kanwy na biały, nie będzie działać, DstOver ponieważ miejsce docelowe nie ma żadnych przezroczystych pikseli.

Może istnieć pokusa zastąpienia map bitowych na stronie Porter-Duff Grid prostszymi DrawRect wywołaniami. Będzie to działać dla prostokąta docelowego, ale nie dla prostokąta źródłowego. Prostokąt źródłowy musi zawierać więcej niż tylko niebieskawy obszar. Prostokąt źródłowy musi zawierać przezroczysty obszar odpowiadający kolorowemu obszarowi miejsca docelowego. Tylko wtedy te tryby mieszania będą działać.

Używanie matów z funkcją Porter-Duff

Na stronie Kompositing ściany z cegły przedstawiono przykład klasycznego zadania komponowania: Obraz musi zostać zmontowany z kilku elementów, w tym mapa bitowa z tłem, który należy wyeliminować. Oto mapa bitowa SeatedMonkey.jpg z problematycznym tłem:

Siedząca małpa

W ramach przygotowań do komponowania utworzono odpowiedni matowy , czyli inną mapę bitową, która jest czarna, w której ma pojawić się obraz i w inny sposób przezroczysty. Ten plik ma nazwę SeatedMonkeyMatte.png i znajduje się wśród zasobów w folderze Media w przykładzie:

Siedząca małpa Matte

Nie jest to specjalnie stworzony matowy. Optymalnie matowy powinien zawierać częściowo przezroczyste piksele wokół krawędzi czarnych pikseli, a ten matowy nie.

Plik XAML dla strony Kompositing ściany ceglanej tworzy wystąpienie obiektu SKCanvasView i , Button który prowadzi użytkownika przez proces komponowania obrazu końcowego:

<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>

Plik z kodem ładuje dwie potrzebne mapy bitowe i obsługuje Clicked zdarzenie Button. Dla każdego Button kliknięcia pole jest zwiększane, step a nowa Text właściwość jest ustawiona dla elementu Button. Gdy step osiągnie wartość 5, zostanie ona ustawiona z powrotem na 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();
        ···
    }
}

Po pierwszym uruchomieniu programu nic nie jest widoczne z wyjątkiem :Button

Cegiełka ścienna, krok 0

Naciśnięcie Button raz powoduje step przyrost do 1, a PaintSurface procedura obsługi wyświetla teraz 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);
        }
        ···
    }
}

Nie ma SKPaint obiektu i dlatego nie ma trybu mieszania. Mapa bitowa jest wyświetlana w dolnej części ekranu:

Cegiełka ścienna, krok 1

Button Naciśnij ponownie i step zwiększ wartość do 2. Jest to kluczowy krok wyświetlania pliku 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);
            }
        }
        ···
    }
}

Tryb mieszania to SKBlendMode.DstIn, co oznacza, że miejsce docelowe zostanie zachowane w obszarach odpowiadających niezroczystym obszarom źródła. Pozostała część prostokąta docelowego odpowiadającego oryginalnej mapie bitowej staje się przezroczysta:

Komponowanie ściany z cegły, krok 2

Tło zostało usunięte.

Następnym krokiem jest narysowanie prostokąta przypominającego chodnik, na który siedzi małpa. Wygląd tego chodnika opiera się na kompozycji dwóch cieniujących: cieniatora koloru stałego i cieniowania szumu Perlin:

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

Ponieważ ten chodnik musi iść za małpą, tryb mieszania to DstOver. Miejsce docelowe jest wyświetlane tylko wtedy, gdy tło jest przezroczyste:

Cegiełka ścienna, krok 3

Ostatnim krokiem jest dodanie muru z cegły. Program używa kafelka mapy bitowej z cegły dostępnego jako właściwość BrickWallTile statyczna w AlgorithmicBrickWallPage klasie . Przekształcenie tłumaczenia jest dodawane do wywołania SKShader.CreateBitmap w celu przesunięcia kafelków tak, aby dolny wiersz był pełnym kafelkiem:

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

Dla wygody DrawRect wywołanie wyświetla ten moduł cieniujący na całej kanwie, ale DstOver tryb ogranicza dane wyjściowe tylko do obszaru kanwy, który jest nadal przezroczysty:

Cegiełka ścienna, krok 4

Oczywiście istnieją inne sposoby tworzenia tej sceny. Można go utworzyć, zaczynając od tła i postępując na pierwszym planie. Jednak korzystanie z trybów mieszania zapewnia większą elastyczność. W szczególności użycie matu pozwala na wykluczenie tła mapy bitowej z sceny komponowanej.

Jak pokazano w artykule Tworzenie wycinków ze ścieżkami i regionami, SKCanvas klasa definiuje trzy typy wycinków, odpowiadające metodom ClipRect, ClipPathi ClipRegion . Tryby mieszania Porter-Duff dodają inny typ wycinków, co pozwala ograniczyć obraz do dowolnego rysowania, w tym map bitowych. Matowe używane w ceglanej ścianie kompositing zasadniczo definiuje obszar wycinki.

Przezroczystość i przejścia gradientu

Przykłady trybów mieszania Porter-Duff pokazanych wcześniej w tym artykule mają wszystkie zaangażowane obrazy składające się z nieprzezroczystych pikseli i przezroczystych pikseli, ale nie częściowo przezroczystych pikseli. Funkcje trybu mieszania są również definiowane dla tych pikseli. Poniższa tabela zawiera bardziej formalną definicję trybów mieszania Porter-Duff, które używają notacji znajdującej się w dokumentacji Skia SkBlendMode. (Ponieważ Odwołanie SkBlendMode to odwołanie skia, używana jest składnia języka C++).

Koncepcyjnie czerwone, zielone, niebieskie i alfa składniki każdego piksela są konwertowane z bajtów na liczby zmiennoprzecinkowe w zakresie od 0 do 1. W przypadku kanału alfa 0 jest w pełni przezroczyste, a 1 jest w pełni nieprzezroczyste

Notacja w poniższej tabeli używa następujących skrótów:

  • Da to docelowy kanał alfa
  • Dc jest docelowym kolorem RGB
  • Sa to źródłowy kanał alfa
  • Sc to źródłowy kolor RGB

Kolory RGB są wstępnie mnożone przez wartość alfa. Jeśli na przykład sc reprezentuje czysty czerwony, ale Sa jest 0x80, kolor RGB to (0x80, 0, 0). Jeśli sa ma wartość 0, wszystkie składniki RGB również są zerowe.

Wynik jest wyświetlany w nawiasach z kanałem alfa i kolorem RGB oddzielonym przecinkiem: [alfa, kolor]. W przypadku koloru obliczenie jest wykonywane oddzielnie dla składników czerwonych, zielonych i niebieskich:

Tryb Operacja
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]

Te operacje są łatwiejsze do przeanalizowania, gdy da i Sa są 0 lub 1. Na przykład dla trybu domyślnegoSrcOver, jeśli sa ma wartość 0, sc ma również wartość 0, a wynikiem jest [Da, Dc], docelowa alfa i kolor. Jeśli sa ma wartość 1, wynikiem jest [Sa, Sc], źródłowa alfa i kolor lub [1, Sc].

Tryby Plus i Modulate różnią się nieco od innych w tym, że nowe kolory mogą wynikać z kombinacji źródła i miejsca docelowego. Tryb Plus można interpretować za pomocą składników bajtów lub składników zmiennoprzecinkowych. Na wyświetlonej wcześniej stronie Porter-Duff Grid kolor docelowy to (0xC0, 0x80, 0x00), a kolor źródła to (0x00, 0x80, 0xC0). Każda para składników jest dodawana, ale suma jest zaciśnięta w 0xFF. Wynikiem jest kolor (0xC0, 0xFF, 0xC0). Jest to kolor pokazany na skrzyżowaniu.

Modulate W przypadku trybu wartości RGB muszą być konwertowane na zmiennoprzecinkowe. Kolor docelowy to (0,75, 0,5, 0), a źródło to (0, 0,5, 0,75). Składniki RGB są mnożone razem, a wynikiem jest (0, 0,25, 0). Jest to kolor pokazany na przecięciu na stronie Porter-Duff Grid dla tego trybu.

Strona Porter-Duff Transparency umożliwia sprawdzenie, jak tryby mieszania Porter-Duff działają na obiektach graficznych, które są częściowo przezroczyste. Plik XAML zawiera element Picker z trybami 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>

Plik za kodem wypełnia dwa prostokąty o tym samym rozmiarze przy użyciu gradientu liniowego. Gradient docelowy znajduje się od prawej górnej do lewej dolnej. Jest brązowy w prawym górnym rogu, ale następnie w kierunku środka zaczyna blaknąć do przezroczystego i jest przezroczysty w lewym dolnym rogu.

Prostokąt źródłowy ma gradient od lewej górnej do prawej dolnej części. Lewy górny róg jest bluish, ale ponownie zanika w przezroczysty i jest przezroczysty w prawym dolnym rogu.

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

Ten program pokazuje, że tryby mieszania Porter-Duff mogą być używane z obiektami graficznymi innymi niż mapy bitowe. Jednak źródło musi zawierać przezroczysty obszar. Dzieje się tak, ponieważ gradient wypełnia prostokąt, ale część gradientu jest przezroczysta.

Oto trzy przykłady:

Przezroczystość porter-Duff

Konfiguracja miejsca docelowego i źródła jest bardzo podobna do diagramów przedstawionych na stronie 255 oryginalnego dokumentu Porter-Duff Compositing Digital Images , ale ta strona pokazuje, że tryby mieszania są dobrze zachowywane dla obszarów częściowej przejrzystości.

Możesz użyć przezroczystych gradientów dla różnych efektów. Jedną z możliwości jest maskowanie, które jest podobne do techniki pokazanej w gradientach promieniowych na potrzeby maskowania na stronie gradientów okrągłych SkiaSharp. Większość strony Maska kompositing jest podobna do tego wcześniejszego programu. Ładuje zasób mapy bitowej i określa prostokąt, w którym ma być wyświetlany. Gradient promieniowy jest tworzony na podstawie wstępnie określonego środka i promienia:

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

Różnica w tym programie polega na tym, że gradient zaczyna się od czarnego w środku i kończy się przezroczystością. Jest on wyświetlany na mapie bitowej z trybem DstInmieszanym , który pokazuje miejsce docelowe tylko w obszarach źródła, które nie są przezroczyste.

Po wywołaniu DrawRect cała powierzchnia kanwy jest przezroczysta z wyjątkiem okręgu zdefiniowanego przez gradient promieniowy. Wykonano ostateczne połączenie:

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

Wszystkie przezroczyste obszary kanwy są kolorowe różowo:

Maska kompositingu

Można również użyć trybów Porter-Duff i częściowo przezroczystych gradientów przejścia z jednego obrazu do drugiego. Strona Przejścia gradientu zawiera element , Slider aby wskazać poziom postępu przejścia z zakresu od 0 do 1, a element do Picker wybrania żądanego typu przejścia:

<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>

Plik związany z kodem ładuje dwa zasoby mapy bitowej w celu zademonstrowania przejścia. Są to te same dwa obrazy używane na stronie Rozpuszczone mapy bitowej we wcześniejszej sekcji tego artykułu. Kod definiuje również wyliczenie z trzema elementami odpowiadającymi trzem typom gradientów — liniowym, promieniowym i zamiatanym. Te wartości są ładowane do elementu 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();
    }
    ···
}

Plik tworzący kod tworzy trzy SKPaint obiekty. Obiekt paint0 nie używa trybu mieszania. Ten obiekt farby służy do rysowania prostokąta z gradientem, który przechodzi od czarnego do przezroczystego, jak wskazano w tablicy colors . Tablica positions jest oparta na pozycji Sliderobiektu , ale nieco skorygowana. Jeśli wartość Slider jest minimalna lub maksymalna, progress wartości to 0 lub 1, a jedna z dwóch map bitowych powinna być w pełni widoczna. Tablica positions musi być odpowiednio ustawiona dla tych wartości.

Jeśli wartość to 0, tablica progress positions zawiera wartości -0.1 i 0. SkiaSharp dostosuje, że pierwsza wartość będzie równa 0, co oznacza, że gradient jest czarny tylko przy wartości 0 i przezroczysty w przeciwnym razie. Gdy progress wartość to 0,5, tablica zawiera wartości 0,45 i 0,55. Gradient jest czarny z zakresu od 0 do 0,45, a następnie przechodzi do przezroczystego i jest w pełni przezroczysty z zakresu od 0,55 do 1. Gdy progress wartość to 1, tablica positions ma wartość 1 i 1,1, co oznacza, że gradient jest czarny z zakresu od 0 do 1.

Tablice colors i position są używane w trzech metodach SKShader , które tworzą gradient. Na podstawie zaznaczenia Picker jest tworzony tylko jeden z tych cieniowania:

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

Ten gradient jest wyświetlany w prostokątie bez trybu mieszania. Po tym DrawRect wywołaniu kanwa po prostu zawiera gradient od czarnego do przezroczystego. Ilość czarnych zwiększa się o wyższe Slider wartości.

W ostatnich czterech instrukcjach PaintSurface programu obsługi są wyświetlane dwie mapy bitowe. Tryb SrcOut mieszania oznacza, że pierwsza mapa bitowa jest wyświetlana tylko w przezroczystych obszarach tła. Tryb DstOver drugiej mapy bitowej oznacza, że druga mapa bitowa jest wyświetlana tylko w tych obszarach, w których pierwsza mapa bitowa nie jest wyświetlana.

Na poniższych zrzutach ekranu przedstawiono trzy różne typy przejść, z których każdy ma znacznik 50%:

Przejścia gradientowe