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:
Pojęcia dotyczące porter-Duff
Załóżmy, że brązowy prostokąt zajmuje lewą i dwie trzecie powierzchni ekranu:
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:
Jest to nazywane źródłem lub czasami pierwszym planem.
Po wyświetleniu źródła w miejscu docelowym oto, czego oczekujesz:
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.DstOver
wartość , w obszarze, w którym przecinają się źródło i miejsce docelowe, miejsce docelowe pojawi się zamiast źródła:
Tryb SKBlendMode.DstIn
mieszany wyświetla tylko obszar, w którym miejsce docelowe i źródło przecinają się przy użyciu koloru docelowego:
Tryb mieszany (wyłączny SKBlendMode.Xor
LUB) powoduje, że nie ma miejsca, w którym dwa obszary nakładają się na siebie:
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:
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 SKCanvasView
klasy . 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ń PorterDurffCanvasView
klasy , 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:
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:
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:
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
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:
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:
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:
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:
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
, ClipPath
i 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:
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 DstIn
mieszanym , 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:
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 Slider
obiektu , 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%: