Modalità di fusione Porter-Duff
Le modalità di fusione Porter-Duff sono denominate dopo Thomas Porter e Tom Duff, che hanno sviluppato un algebra di composizione mentre lavora per Lucasfilm. Il loro documento Compositing Digital Images è stato pubblicato nel numero di luglio 1984 di Computer Graphics, pagine da 253 a 259. Queste modalità di fusione sono essenziali per la composizione, che assembla varie immagini in una scena composita:
Concetti relativi a Porter-Duff
Si supponga che un rettangolo marrone occupi i due terzi sinistro e superiore della superficie di visualizzazione:
Questa area è detta destinazione o a volte sfondo o sfondo.
Si desidera disegnare il rettangolo seguente, che corrisponde alla stessa dimensione della destinazione. Il rettangolo è trasparente ad eccezione di un'area bluish che occupa i due terzi destro e inferiore:
Questa operazione viene chiamata origine o talvolta in primo piano.
Quando si visualizza l'origine nella destinazione, ecco quanto previsto:
I pixel trasparenti dell'origine consentono allo sfondo di essere visualizzati, mentre i pixel di origine bluish oscurano lo sfondo. Questo è il caso normale e viene indicato in SkiaSharp come SKBlendMode.SrcOver
. Tale valore è l'impostazione predefinita della BlendMode
proprietà quando viene creata una prima istanza di un SKPaint
oggetto.
Tuttavia, è possibile specificare una modalità di fusione diversa per un effetto diverso. Se si specifica SKBlendMode.DstOver
, nell'area in cui si intersecano l'origine e la destinazione, la destinazione viene visualizzata anziché l'origine:
La SKBlendMode.DstIn
modalità di fusione visualizza solo l'area in cui la destinazione e l'origine si intersecano usando il colore di destinazione:
La modalità blend di (OR esclusivo) non comporta la visualizzazione di SKBlendMode.Xor
alcun elemento in cui le due aree si sovrappongono:
I rettangoli di destinazione e di origine colorati dividono efficacemente la superficie di visualizzazione in quattro aree univoche che possono essere colorate in vari modi corrispondenti alla presenza dei rettangoli di destinazione e di origine:
I rettangoli in alto a destra e in basso a sinistra sono sempre vuoti perché sia la destinazione che l'origine sono trasparenti in tali aree. Il colore di destinazione occupa l'area superiore sinistra, in modo che l'area possa essere colorata con il colore di destinazione o non affatto. Analogamente, il colore di origine occupa l'area inferiore destra, in modo che tale area possa essere colorata con il colore di origine o non affatto. L'intersezione della destinazione e dell'origine al centro può essere colorata con il colore di destinazione, il colore di origine o non affatto.
Il numero totale di combinazioni è 2 (per l'angolo superiore sinistro) volte 2 (per la parte inferiore destra) volte 3 (per il centro) o 12. Queste sono le 12 modalità di composizione Porter-Duff di base.
Verso la fine di Compositing Digital Images (pagina 256), Porter e Duff aggiungono una 13a modalità denominata plus (corrispondente al membro SkiaSharp SKBlendMode.Plus
e alla modalità W3C Light (che non deve essere confusa con la modalità Luce W3C). Questa Plus
modalità aggiunge i colori di destinazione e di origine, un processo che verrà descritto più dettagliatamente a breve.
Skia aggiunge una modalità 14a chiamata Modulate
molto simile a, Plus
ad eccezione del fatto che i colori di destinazione e di origine vengono moltiplicati. Può essere considerato come una modalità aggiuntiva di fusione Porter-Duff.
Ecco le 14 modalità Porter-Duff definite in SkiaSharp. La tabella mostra il colore di ognuna delle tre aree non vuote nel diagramma precedente:
Modalità | Destinazione | Intersezione | Origine |
---|---|---|---|
Clear |
|||
Src |
Origine | X | |
Dst |
X | Destinazione | |
SrcOver |
X | Origine | X |
DstOver |
X | Destinazione | X |
SrcIn |
Source (Sorgente) | ||
DstIn |
Destination | ||
SrcOut |
X | ||
DstOut |
X | ||
SrcATop |
X | Source (Sorgente) | |
DstATop |
Destination | X | |
Xor |
X | X | |
Plus |
X | Sum | X |
Modulate |
Prodotto |
Queste modalità di fusione sono simmetriche. L'origine e la destinazione possono essere scambiate e tutte le modalità sono ancora disponibili.
La convenzione di denominazione delle modalità segue alcune semplici regole:
- Src o Dst significa che solo i pixel di origine o di destinazione sono visibili.
- Il suffisso Over indica ciò che è visibile nell'intersezione. L'origine o la destinazione viene disegnata "sopra" l'altra.
- Il suffisso In indica che solo l'intersezione è colorata. L'output è limitato solo alla parte dell'origine o della destinazione che è "in" l'altra.
- Il suffisso Out indica che l'intersezione non è colorata. L'output è solo la parte dell'origine o della destinazione "out" dell'intersezione.
- Il suffisso ATop è l'unione di In e Out. Include l'area in cui l'origine o la destinazione è "in cima" dell'altra.
Si noti la differenza con le Plus
modalità e Modulate
. Queste modalità eseguono un tipo diverso di calcolo sui pixel di origine e di destinazione. Sono descritti in modo più dettagliato a breve.
La pagina Porter-Duff Grid mostra tutte le 14 modalità su uno schermo sotto forma di griglia. Ogni modalità è un'istanza separata di SKCanvasView
. Per questo motivo, una classe è derivata da SKCanvasView
denominata PorterDuffCanvasView
. Il costruttore statico crea due bitmap delle stesse dimensioni, una con un rettangolo marrone nell'area superiore sinistra e un'altra con un rettangolo blu:
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);
}
}
}
···
}
Il costruttore dell'istanza ha un parametro di tipo SKBlendMode
. Salva questo parametro in un campo.
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);
}
}
}
L'override OnPaintSurface
disegna le due bitmap. Il primo viene disegnato normalmente:
canvas.DrawBitmap(dstBitmap, rect);
Il secondo viene disegnato con un SKPaint
oggetto in cui la BlendMode
proprietà è stata impostata sull'argomento del costruttore:
using (SKPaint paint = new SKPaint())
{
paint.BlendMode = blendMode;
canvas.DrawBitmap(srcBitmap, rect, paint);
}
Il resto dell'override OnPaintSurface
disegna un rettangolo intorno alla bitmap per indicare le dimensioni.
La PorterDuffGridPage
classe crea quattordici istanze di PorterDurffCanvasView
, una per ogni membro della blendModes
matrice. L'ordine SKBlendModes
dei membri nella matrice è leggermente diverso rispetto alla tabella per posizionare modalità simili adiacenti tra loro. Le 14 istanze di PorterDuffCanvasView
sono organizzate insieme alle etichette in un Grid
oggetto :
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;
}
}
Il risultato è il seguente:
Si vuole convincere se stessi che la trasparenza è fondamentale per il corretto funzionamento delle modalità di fusione Porter-Duff. La PorterDuffCanvasView
classe contiene un totale di tre chiamate al Canvas.Clear
metodo . Tutti usano il metodo senza parametri, che imposta tutti i pixel su trasparente:
canvas.Clear();
Provare a modificare una di queste chiamate in modo che i pixel siano impostati su bianco opaco:
canvas.Clear(SKColors.White);
In seguito a questo cambiamento, alcune delle modalità di fusione sembreranno funzionare, ma altre non funzioneranno. Se imposti lo sfondo della bitmap di origine su bianco, la SrcOver
modalità non funziona perché nella bitmap di origine non sono presenti pixel trasparenti per consentire la visualizzazione della destinazione. Se imposti lo sfondo della bitmap di destinazione o dell'area di disegno su bianco, DstOver
non funziona perché la destinazione non ha pixel trasparenti.
Potrebbe esserci una tentazione di sostituire le bitmap nella pagina Porter-Duff Grid con chiamate più semplici DrawRect
. Funzionerà per il rettangolo di destinazione, ma non per il rettangolo di origine. Il rettangolo di origine deve includere più che solo l'area bluish.The source rectangle must encompass more than just the bluish-color area. Il rettangolo di origine deve includere un'area trasparente che corrisponde all'area colorata della destinazione. Solo allora queste modalità di fusione funzionano.
Uso di mattes con Porter-Duff
La pagina Brick-Wall Compositing mostra un esempio di un'attività classica di composizione : un'immagine deve essere assemblata da diversi pezzi, inclusa una bitmap con uno sfondo che deve essere eliminato. Ecco la bitmap SeatedMonkey.jpg con lo sfondo problematico:
In preparazione alla composizione, è stato creato un matto corrispondente, ovvero un'altra bitmap nera in cui si vuole che l'immagine venga visualizzata e trasparente in caso contrario. Questo file è denominato SeatedMonkeyMatte.png ed è tra le risorse nella cartella Media nell'esempio:
Questo non è un matto creato con esperienza. In modo ottimale, l'opaco deve includere pixel parzialmente trasparenti intorno al bordo dei pixel neri e questo matto non lo fa.
Il file XAML per la pagina Brick-Wall Compositing crea un'istanza SKCanvasView
di e un oggetto Button
che guida l'utente nel processo di composizione dell'immagine finale:
<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>
Il file code-behind carica le due bitmap necessarie e gestisce l'evento Clicked
di Button
. Per ogni Button
clic, il step
campo viene incrementato e viene impostata una nuova Text
proprietà per .Button
Quando step
raggiunge 5, viene impostato su 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();
···
}
}
Quando il programma viene eseguito per la prima volta, non è visibile ad eccezione di Button
:
Premendo una volta si verifica un Button
incremento a 1 e il PaintSurface
gestore visualizza ora SeatedMonkey.jpg:step
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);
}
···
}
}
Non c'è alcun SKPaint
oggetto e quindi nessuna modalità di fusione. La bitmap viene visualizzata nella parte inferiore della schermata:
Button
Premere di nuovo e step
incrementa su 2. Questo è il passaggio fondamentale della visualizzazione del file 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);
}
}
···
}
}
La modalità blend è SKBlendMode.DstIn
, il che significa che la destinazione verrà mantenuta in aree corrispondenti ad aree non trasparenti dell'origine. Il resto del rettangolo di destinazione corrispondente alla bitmap originale diventa trasparente:
Lo sfondo è stato rimosso.
Il passaggio successivo consiste nel disegnare un rettangolo simile a un marciapiede su cui si trova la scimmia. L'aspetto di questo marciapiede si basa su una composizione di due shader: uno shader a tinta unita e uno shader di rumore 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);
}
}
···
}
}
Poiché questo marciapiede deve andare dietro la scimmia, la modalità di fusione è DstOver
. La destinazione viene visualizzata solo in cui lo sfondo è trasparente:
Il passaggio finale consiste nell'aggiungere un muro di mattoni. Il programma usa il riquadro bitmap a parete di mattoni disponibile come proprietà BrickWallTile
statica nella AlgorithmicBrickWallPage
classe . Una trasformazione di conversione viene aggiunta alla SKShader.CreateBitmap
chiamata per spostare i riquadri in modo che la riga inferiore sia un riquadro completo:
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);
}
}
}
}
Per praticità, la DrawRect
chiamata visualizza questo shader sull'intero canvas, ma la DstOver
modalità limita l'output solo all'area dell'area di disegno ancora trasparente:
Ovviamente ci sono altri modi per comporre questa scena. Potrebbe essere costruito a partire dallo sfondo e passando in primo piano. Tuttavia, l'uso delle modalità blend offre maggiore flessibilità. In particolare, l'uso dell'opaco consente di escludere lo sfondo di una bitmap dalla scena composta.
Come si è appreso nell'articolo Ritaglio con percorsi e aree, la SKCanvas
classe definisce tre tipi di ritaglio, corrispondenti ai ClipRect
metodi , ClipPath
e ClipRegion
. Le modalità di fusione Porter-Duff aggiungono un altro tipo di ritaglio, che consente di limitare un'immagine a qualsiasi elemento che è possibile disegnare, incluse le bitmap. L'opaco utilizzato in Brick-Wall Compositing definisce essenzialmente un'area di ritaglio.
Trasparenza e transizioni sfumature
Gli esempi delle modalità di fusione Porter-Duff illustrate in precedenza in questo articolo includono tutte le immagini costituite da pixel opachi e pixel trasparenti, ma non parzialmente trasparenti. Le funzioni in modalità blend sono definite anche per tali pixel. La tabella seguente è una definizione più formale delle modalità di fusione Porter-Duff che usa la notazione trovata nel riferimento Skia SkBlendMode. (Perché SkBlendMode Reference è un riferimento Skia, viene usata la sintassi C++.
Concettualmente, i componenti rosso, verde, blu e alfa di ogni pixel vengono convertiti da byte a numeri a virgola mobile nell'intervallo da 0 a 1. Per il canale alfa, 0 è completamente trasparente e 1 è completamente opaco
La notazione nella tabella seguente usa le abbreviazioni seguenti:
- Da è il canale alfa di destinazione
- Dc è il colore RGB di destinazione
- Sa è il canale alfa di origine
- Sc è il colore RGB di origine
I colori RGB vengono pre-moltiplicati per il valore alfa. Ad esempio, se Sc rappresenta il rosso puro ma Sa è 0x80, il colore RGB è (0x80, 0, 0). Se Sa è 0, anche tutti i componenti RGB sono zero.
Il risultato viene visualizzato tra parentesi quadre con il canale alfa e il colore RGB separati da una virgola: [alfa, colore]. Per il colore, il calcolo viene eseguito separatamente per i componenti rosso, verde e blu:
Modalità | Operazione |
---|---|
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] |
Queste operazioni sono più facili da analizzare quando Da e Sa sono 0 o 1. Ad esempio, per la modalità predefinita SrcOver
, se Sa è 0, sc è anche 0 e il risultato è [Da, Dc], l'alfa e il colore di destinazione. Se Sa è 1, il risultato è [Sa, Sc], l'alfa e il colore di origine o [1, Sc].
Le Plus
modalità e Modulate
sono leggermente diverse dalle altre, in quanto i nuovi colori possono derivare dalla combinazione dell'origine e della destinazione. La Plus
modalità può essere interpretata con componenti di byte o componenti a virgola mobile. Nella pagina Porter-Duff Grid mostrata in precedenza, il colore di destinazione è (0xC0, 0x80, 0x00) e il colore di origine è (0x00, 0x80, 0xC0). Ogni coppia di componenti viene aggiunta, ma la somma viene bloccata a 0xFF. Il risultato è il colore (0xC0, 0xFF, 0xC0). Questo è il colore mostrato nell'intersezione.
Per la Modulate
modalità, i valori RGB devono essere convertiti in virgola mobile. Il colore di destinazione è (0,75, 0,5, 0) e l'origine è (0, 0,5, 0,75). I componenti RGB vengono moltiplicati insieme e il risultato è (0, 0,25, 0). Questo è il colore visualizzato nell'intersezione nella pagina Porter-Duff Grid per questa modalità.
La pagina Porter-Duff Transparency consente di esaminare il funzionamento delle modalità di fusione Porter-Duff su oggetti grafici parzialmente trasparenti. Il file XAML include un oggetto Picker
con le modalità 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>
Il file code-behind riempie due rettangoli della stessa dimensione usando una sfumatura lineare. La sfumatura di destinazione si trova dall'angolo superiore destro a sinistra inferiore. È marrone nell'angolo superiore destro, ma poi verso il centro inizia a dissolvenza verso trasparente, ed è trasparente nell'angolo inferiore sinistro.
Il rettangolo di origine ha una sfumatura dall'angolo superiore sinistro a quello inferiore destro. L'angolo superiore sinistro è bluish, ma si dissolve di nuovo in trasparente ed è trasparente nell'angolo in basso a destra.
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);
}
}
}
Questo programma dimostra che le modalità di fusione Porter-Duff possono essere usate con oggetti grafici diversi dalle bitmap. Tuttavia, l'origine deve includere un'area trasparente. Questo è il caso in questo caso perché la sfumatura riempie il rettangolo, ma parte della sfumatura è trasparente.
Ecco tre esempi:
La configurazione della destinazione e dell'origine è molto simile ai diagrammi mostrati nella pagina 255 del documento originale Porter-Duff Compositing Digital Images , ma questa pagina dimostra che le modalità di fusione sono ben si comportano per aree di trasparenza parziale.
È possibile usare sfumature trasparenti per alcuni effetti diversi. Una possibilità è la mascheratura, simile alla tecnica illustrata nella sezione Sfumature radiali per la maschera della pagina Sfumature circolari SkiaSharp. Gran parte della pagina Compositing Mask è simile a quella precedente. Carica una risorsa bitmap e determina un rettangolo in cui visualizzarlo. Viene creata una sfumatura radiale basata su un centro e un raggio pre-determinati:
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);
}
}
La differenza con questo programma è che la sfumatura inizia con nero al centro e termina con trasparenza. Viene visualizzato nella bitmap con una modalità di fusione di DstIn
, che mostra la destinazione solo nelle aree dell'origine che non sono trasparenti.
Dopo la DrawRect
chiamata, l'intera superficie dell'area di disegno è trasparente, ad eccezione del cerchio definito dalla sfumatura radiale. Viene effettuata una chiamata finale:
canvas.DrawColor(SKColors.Pink, SKBlendMode.DstOver);
Tutte le aree trasparenti della tela sono di colore rosa:
È anche possibile usare le modalità Porter-Duff e le sfumature parzialmente trasparenti per le transizioni da un'immagine a un'altra. La pagina Transizioni sfumature include un oggetto Slider
per indicare un livello di stato nella transizione da 0 a 1 e un oggetto Picker
per scegliere il tipo di transizione desiderato:
<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>
Il file code-behind carica due risorse bitmap per illustrare la transizione. Queste sono le stesse due immagini usate nella pagina Dissolvenza bitmap più indietro in questo articolo. Il codice definisce anche un'enumerazione con tre membri corrispondenti a tre tipi di sfumature, lineari, radiali e sweep. Questi valori vengono caricati in 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();
}
···
}
Il file code-behind crea tre SKPaint
oggetti. L'oggetto paint0
non usa una modalità di fusione. Questo oggetto paint viene utilizzato per disegnare un rettangolo con una sfumatura che passa da nero a trasparente, come indicato nella colors
matrice. La positions
matrice si basa sulla posizione dell'oggetto Slider
, ma leggermente modificata. Se è Slider
al valore minimo o massimo, i progress
valori sono 0 o 1 e una delle due bitmap deve essere completamente visibile. La positions
matrice deve essere impostata di conseguenza per tali valori.
Se il progress
valore è 0, la positions
matrice contiene i valori -0.1 e 0. SkiaSharp regola il primo valore in modo che sia uguale a 0, il che significa che la sfumatura è nera solo a 0 e trasparente in caso contrario. Quando progress
è 0,5, la matrice contiene i valori 0,45 e 0,55. La sfumatura è nera da 0 a 0,45, quindi passa a trasparente ed è completamente trasparente da 0,55 a 1. Quando progress
è 1, la positions
matrice è 1 e 1.1, il che significa che la sfumatura è nera da 0 a 1.
Le colors
matrici e position
vengono usate entrambi nei tre metodi di SKShader
che creano una sfumatura. Solo uno di questi shader viene creato in base alla Picker
selezione:
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);
}
}
}
Tale sfumatura viene visualizzata nel rettangolo senza una modalità di fusione. Dopo tale DrawRect
chiamata, l'area di disegno contiene semplicemente una sfumatura da nero a trasparente. La quantità di nero aumenta con valori più elevati Slider
.
Nelle quattro istruzioni finali del PaintSurface
gestore vengono visualizzate le due bitmap. La SrcOut
modalità blend indica che la prima bitmap viene visualizzata solo nelle aree trasparenti dello sfondo. La DstOver
modalità per la seconda bitmap indica che la seconda bitmap viene visualizzata solo in quelle aree in cui la prima bitmap non viene visualizzata.
Gli screenshot seguenti mostrano i tre diversi tipi di transizioni, ognuno con il contrassegno del 50%: