Modos de fusión Porter-Duff
Los modo de fusión Porter-Duff se denominan Thomas Porter y Tom Duff, que desarrollaron un álgebra de composición mientras trabajaban para Lucasfilm. Su documento Compositing Digital Images fue publicado en la edición de julio de 1984 de Computer Graphics, páginas de 253 a 259. Estos modo de fusión son esenciales para la composición, que ensambla varias imágenes en una escena compuesta:
Conceptos de Porter-Duff
Supongamos que un rectángulo marrón ocupa los dos tercios izquierdos y superiores de la superficie de visualización:
Esta área se denomina destino. o a veces el fondo otelón de fondo.
Desea dibujar el siguiente rectángulo, que es el mismo tamaño del destino. El rectángulo es transparente, excepto para un área azulada que ocupa la derecha y la parte inferior de dos tercios:
Esto se llama origen o, a veces el primer plano.
Al mostrar el origen en el destino, esto es lo que espera:
Los píxeles transparentes del origen permiten que el fondo se muestre a través, mientras que los píxeles de origen azulado ocultan el fondo. Ese es el caso normal, y se conoce en SkiaSharp como SKBlendMode.SrcOver
. Ese valor es predeterminado de la propiedad BlendMode
cuando se crea una instancia de un objeto SKPaint
.
Sin embargo, es posible especificar un modo de fusión diferente para un efecto diferente. Si especifica SKBlendMode.DstOver
, en el área donde se interseca el origen y el destino, el destino aparece en lugar del origen:
El modo de fusión de SKBlendMode.DstIn
muestra solo el área donde el destino y el origen se intersecan mediante el color de destino:
El modo de fusión de SKBlendMode.Xor
(OR exclusivo) no hace que aparezca nada donde se superpongan las dos áreas:
El destino coloreado y los rectángulos de origen dividen eficazmente la superficie de visualización en cuatro áreas únicas que se pueden colorear de varias maneras correspondientes a la presencia de los rectángulos de origen y destino:
Los rectángulos superior derecho e inferior izquierdo siempre estarán en blanco, porque tanto el destino como el origen son transparentes en esas áreas. El color de destino ocupa el área superior izquierda, de modo que ese área se pueda colorear con el color de destino o no se coloree del todo. Del mismo modo, el color de origen ocupará el área inferior derecha, para que el área se pueda colorear con el color de origen o no se coloree del todo. La intersección del destino y el origen en el medio se pueden colorear con el color de destino, el color de origen o no en absoluto.
El número total de combinaciones es 2 (para la esquina superior izquierda) veces 2 (para la parte inferior derecha) 3 (para el centro) o 12. Estos son los 12 modos básicos de composición Porter-Duff.
Hacia el final de Compositing Digital Images (página 256), Porter y Duff agregan un modo 13th llamado plus (correspondiente al miembro SkiaSharp SKBlendMode.Plus
y al modo W3C Lighten (que no se debe confundir con el modo W3C Lighten.) Este Plus
modo agrega los colores de origen y destino, un proceso que se describirá con más detalle en breve.
Skia agrega un modo 14º denominado Modulate
que es muy similar a Plus
excepto que se multiplican los colores de origen y destino. Se puede tratar como un modo de fusión Porter-Duff adicional.
Estos son los 14 modos Porter-Duff definidos en SkiaSharp. En la tabla se muestra cómo colorea cada una de las tres áreas que no están en blanco en el diagrama anterior:
Modo | Destino | Intersección | Source |
---|---|---|---|
Clear |
|||
Src |
Source | X | |
Dst |
X | Destino | |
SrcOver |
X | Source | X |
DstOver |
X | Destino | X |
SrcIn |
Origen | ||
DstIn |
Destination | ||
SrcOut |
X | ||
DstOut |
X | ||
SrcATop |
X | Origen | |
DstATop |
Destination | X | |
Xor |
X | X | |
Plus |
X | Sum | X |
Modulate |
Producto |
Estos modos de fusión son simétricos. El origen y el destino se pueden intercambiar y todos los modos siguen estando disponibles.
La convención de nomenclatura de los modos sigue algunas reglas simples:
- Src o Dst por sí mismo significa que solo están visibles los píxeles de origen o destino.
- El sufijo Over indica lo que está visible en la intersección. El origen o el destino se dibujan "sobre" el otro.
- El sufijo In significa que solo se colorea la intersección. La salida está restringida solo a la parte del origen o destino que está "en" el otro.
- El sufijo Out significa que la intersección no está colorada. La salida es solo la parte del origen o destino que está "fuera" de la intersección.
- El sufijo ATop es la unión de En y Out. Incluye el área donde el origen o el destino es "atop" del otro.
Observe la diferencia con los Plus
modos y Modulate
. Estos modos realizan un tipo de cálculo diferente en los píxeles de origen y destino. Se describen con más detalle en breve.
La página Porter-Duff Grid muestra todos los 14 modos en una pantalla en forma de cuadrícula. Cada modo es una instancia independiente de SKCanvasView
. Por ese motivo, una clase se deriva de SKCanvasView
denominada PorterDuffCanvasView
. El constructor estático crea dos mapas de bits del mismo tamaño, uno con un rectángulo marrón en su área superior izquierda y otro con un rectángulo azulado:
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);
}
}
}
···
}
El constructor de instancia tiene un parámetro de tipo SKBlendMode
. Guarde este parámetro en 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);
}
}
}
El OnPaintSurface
invalida dibuja los dos mapas de bits. La primera se dibuja normalmente:
canvas.DrawBitmap(dstBitmap, rect);
El segundo se dibuja con un SKPaint
objeto donde la BlendMode
propiedad se ha establecido en el argumento constructor:
using (SKPaint paint = new SKPaint())
{
paint.BlendMode = blendMode;
canvas.DrawBitmap(srcBitmap, rect, paint);
}
El resto del OnPaintSurface
invalidación dibuja un rectángulo alrededor del mapa de bits para indicar sus tamaños.
La clase PorterDuffGridPage
crea catorce instancias de PorterDurffCanvasView
, una para cada miembro de la matriz de blendModes
. El orden de los miembros de SKBlendModes
la matriz es un poco diferente de la tabla para colocar modos similares adyacentes entre sí. Las 14 instancias de PorterDuffCanvasView
se organizan junto con las etiquetas de un 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;
}
}
Este es el resultado:
Querrá convencerse de que la transparencia es fundamental para el funcionamiento adecuado de los modos de mezcla Porter-Duff. La clase PorterDuffCanvasView
contiene un total de tres llamadas al método Canvas.Clear
. Todos usan el método sin parámetros, que establece todos los píxeles en transparente:
canvas.Clear();
Intente cambiar cualquiera de esas llamadas para que los píxeles se establezcan en blanco opaco:
canvas.Clear(SKColors.White);
Después de ese cambio, algunos de los modos de mezcla parecen funcionar, pero otros no. Si establece el fondo del mapa de bits de origen en blanco, el SrcOver
modo no funciona porque no hay píxeles transparentes en el mapa de bits de origen para permitir que el destino se muestre. Si establece el fondo del mapa de bits de destino o el lienzo en blanco, DstOver
no funciona porque el destino no tiene ningún píxel transparente.
Puede haber una tentación de reemplazar los mapas de bits en la página de Porter-Duff Grid por llamadas DrawRect
más sencillas. Esto funcionará para el rectángulo de destino, pero no para el rectángulo de origen. El rectángulo de origen debe abarcar más que solo el área de color azulado. El rectángulo de origen debe incluir un área transparente que corresponda al área colorada del destino. Solo entonces funcionarán estos modos de fusión.
Uso de mates con Porter-Duff
En la página de Brick-Wall Compositing se muestra un ejemplo de una tarea de redacción clásica: es necesario ensamblar una imagen a partir de varias piezas, incluido un mapa de bits con un fondo que debe eliminarse. Este es el mapa de bits de SeatedMonkey.jpg con el fondo problemático:
En preparación para la composición, se creó un mate correspondiente, que es otro mapa de bits que es negro donde desea que la imagen aparezca y transparente de lo contrario. Este archivo se denomina SeatedMonkeyMatte.png y se encuentra entre los recursos de la carpeta Media en el ejemplo:
Esto no es un mate creado por expertos. Óptimamente, el mate debe incluir píxeles parcialmente transparentes alrededor del borde de los píxeles negros, y este mate no.
El archivo XAML de la página Brick-Wall Compositing crea una instancia de un SKCanvasView
y un Button
que guía al usuario a través del proceso de redacción de la imagen final:
<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>
El archivo de código subyacente carga los dos mapas de bits que necesita y controla el Clicked
evento de Button
. Para cada Button
clic, el step
campo se incrementa y se establece una nueva Text
propiedad para. Button
Cuando step
alcanza 5, se vuelve a establecer en 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();
···
}
}
Cuando se ejecuta el programa por primera vez, no hay nada visible excepto: Button
Al presionar el Button
una vez, step
aumentar a 1 y el controlador de PaintSurface
ahora muestra 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);
}
···
}
}
No hay ningún objeto SKPaint
y, por tanto, no hay ningún modo de mezcla. El mapa de bits aparece en la parte inferior de la pantalla:
Presione de nuevo el Button
y step
incrementos en 2. Este es el paso fundamental para mostrar el archivo 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);
}
}
···
}
}
El modo de fusión es SKBlendMode.DstIn
, lo que significa que el destino se conservará en áreas correspondientes a áreas no transparentes del origen. El resto del rectángulo de destino correspondiente al mapa de bits original se convierte en transparente:
Se ha quitado el fondo.
El siguiente paso es dibujar un rectángulo similar a una acera en la que el mono está sentado. La apariencia de esta acera se basa en una composición de dos sombreadores: un sombreador de color sólido y un sombreador de ruido 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);
}
}
···
}
}
Dado que esta acera debe ir detrás del mono, el modo de mezcla es DstOver
. El destino solo aparece donde el fondo es transparente:
El último paso es agregar una pared de ladrillo. El programa usa el icono de mapa de bits de muro de ladrillo disponible como la propiedad estática BrickWallTile
en la clase AlgorithmicBrickWallPage
. Se agrega una transformación de traducción al SKShader.CreateBitmap
llamada para desplazar los iconos para que la fila inferior sea un icono 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);
}
}
}
}
Por comodidad, la llamada DrawRect
muestra este sombreador en todo el lienzo, pero el modo DstOver
limita la salida solo al área del lienzo que sigue siendo transparente:
Obviamente hay otras maneras de componer esta escena. Podría crearse comenzando en segundo plano y progresando en primer plano. Pero el uso de los modos de mezcla proporciona más flexibilidad. En concreto, el uso del mate permite excluir el fondo de un mapa de bits de la escena compuesta.
Como ha aprendido en el artículo Recorte con rutas de acceso y regiones, la clase SKCanvas
define tres tipos de recorte, que corresponden a los métodos ClipRect
, ClipPath
y ClipRegion
. Los modos de mezcla Porter-Duff agregan otro tipo de recorte, lo que permite restringir una imagen a cualquier elemento que pueda dibujar, incluidos los mapas de bits. El mate utilizado en composición de ladrillo-pared básicamente define un área de recorte.
Transparencia y transiciones de degradado
Los ejemplos de los modos de fusión Porter-Duff mostrados anteriormente en este artículo tienen todas las imágenes implicadas que constan de píxeles opacos y píxeles transparentes, pero no píxeles parcialmente transparentes. Las funciones en modo de fusión también se definen para esos píxeles. La tabla siguiente es una definición más formal de los modos de mezcla Porter-Duff que usa la notación encontrada en la Referencia de Skia SkBlendMode. (Dado que referencia de SkBlendMode es una referencia de Skia, se usa la sintaxis de C++).
Conceptualmente, los componentes rojo, verde, azul y alfa de cada píxel se convierten de bytes a números de punto flotante en el intervalo de 0 a 1. Para el canal alfa, 0 es totalmente transparente y 1 es totalmente opaco
La notación de la tabla siguiente usa las abreviaturas siguientes:
- Da es el canal alfa de destino.
- Dc es el color RGB de destino
- Sa es el canal alfa de origen
- Sc es el color RGB de origen
Los colores RGB se multiplican previamente por el valor alfa. Por ejemplo, si Sc representa rojo puro, pero sa es 0x80, el color RGB es (0x80, 0, 0). Si Sa es 0, todos los componentes RGB también son cero.
El resultado se muestra entre corchetes con el canal alfa y el color RGB separados por una coma: [alfa, color]. Para el color, el cálculo se realiza por separado para los componentes rojo, verde y azul:
Modo | Operación |
---|---|
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] |
Estas operaciones son más fáciles de analizar cuando Da y Sa son 0 o 1. Por ejemplo, para el modo predeterminado SrcOver
, si Sa es 0, Sc también es 0 y el resultado es [Da, Dc], el alfa de destino y el color. Si Sa es 1, el resultado es [Sa, Sc], el alfa y el color de origen, o [1, Sc].
Los modos Plus
y Modulate
son un poco diferentes de los demás en que los nuevos colores pueden resultar de la combinación del origen y el destino. El modo Plus
se puede interpretar con componentes de bytes o componentes de punto flotante. En la página Cuadrícula Porter-Duff mostrada anteriormente, el color de destino se (0xC0, 0x80, 0x00 ) y el color de origen es (0x00, 0x80, 0xC0). Cada par de componentes se agrega, pero la suma se fija en 0xFF. El resultado es el color (0xC0, 0xFF, 0xC0). Ese es el color que se muestra en la intersección.
Para el modo Modulate
, los valores RGB deben convertirse en punto flotante. El color de destino es (0,75, 0,5, 0) y el origen es (0, 0,5, 0,75). Los componentes RGB se multiplican juntos y el resultado es (0, 0,25, 0). Ese es el color que se muestra en la intersección de la página Cuadrícula de Porter-Duff para este modo.
La página Transparencia de Porter-Duff le permite examinar cómo funcionan los modos de mezcla Porter-Duff en objetos gráficos que son parcialmente transparentes. El archivo XAML incluye un Picker
objeto con los modos 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>
El archivo de código subyacente rellena dos rectángulos del mismo tamaño mediante un degradado lineal. El degradado de destino está de la esquina superior derecha a la parte inferior izquierda. Es marrón en la esquina superior derecha, pero luego hacia el centro comienza a desvanecerse a transparente y es transparente en la esquina inferior izquierda.
El rectángulo de origen tiene un degradado de la parte superior izquierda a la esquina inferior derecha. La esquina superior izquierda es azulada, pero de nuevo se atenua a transparente y es transparente en la esquina inferior derecha.
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);
}
}
}
Este programa muestra que los modos de mezcla Porter-Duff se pueden usar con objetos gráficos distintos de mapas de bits. Sin embargo, el origen debe incluir un área transparente. Este es el caso aquí porque el degradado rellena el rectángulo, pero parte del degradado es transparente.
Estos son tres ejemplos:
La configuración del destino y el origen es muy similar a los diagramas que se muestran en la página 255 del documento Original Porter-Duff Compositing Digital Images, pero en esta página se muestra que los modos de combinación se comportan bien para áreas de transparencia parcial.
Puede usar degradados transparentes para algunos efectos diferentes. Una posibilidad es el enmascaramiento, que es similar a la técnica que se muestra en la sección Degradados radiales para enmascaramiento de lapágina degradados circulares SkiaSharp. Gran parte de la página Compositing Mask es similar a la anterior. Carga un recurso de mapa de bits y determina un rectángulo en el que mostrarlo. Se crea un degradado radial basado en un centro y radio predefinidos:
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 diferencia con este programa es que el degradado comienza con negro en el centro y termina con transparencia. Se muestra en el mapa de bits con un modo de fusión de DstIn
, que muestra el destino solo en las áreas del origen que no son transparentes.
Después de la llamada DrawRect
, toda la superficie del lienzo es transparente, excepto el círculo definido por el degradado radial. Se realiza una llamada final:
canvas.DrawColor(SKColors.Pink, SKBlendMode.DstOver);
Todas las áreas transparentes del lienzo son de color rosa:
También puede usar los modos Porter-Duff y degradados parcialmente transparentes para las transiciones de una imagen a otra. La página Transiciones de degradadoincluye un Slider
para indicar un nivel de progreso en la transición de 0 a 1 y un Picker
para elegir el tipo de transición que desee:
<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>
El archivo de código subyacente carga dos recursos de mapa de bits para mostrar la transición. Estas son las mismas dos imágenes que se usan en la página Disolución de mapa de bits en este artículo. El código también define una enumeración con tres miembros correspondientes a tres tipos de degradados: lineal, radial y barrido. Estos valores se cargan en: 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();
}
···
}
El archivo de código subyacente crea tres SKPaint
objetos. El objeto paint0
no usa un modo de fusión. Este objeto de pintura se usa para dibujar un rectángulo con un degradado que va de negro a transparente como se indica en la colors
matriz. La matriz positions
se basa en la posición del Slider
, pero se ajusta un poco. Si el Slider
es como mínimo o máximo, los valores de progress
son 0 o 1 y uno de los dos mapas de bits debe estar totalmente visible. La matriz positions
debe establecerse en consecuencia para esos valores.
Si el valor de progress
es 0, la matriz de positions
contiene los valores -0.1 y 0. SkiaSharp ajustará ese primer valor para que sea igual a 0, lo que significa que el degradado es negro solo en 0 y transparente de lo contrario. Cuando progress
es 0,5, la matriz contiene los valores 0,45 y 0,55. El degradado es negro de 0 a 0,45, después pasa a transparente y es totalmente transparente de 0,55 a 1. Cuando progress
es 1, la matriz de positions
es 1 y 1.1, lo que significa que el degradado es negro de 0 a 1.
Las matrices colors
y position
se usan en los tres métodos de SKShader
que crean un degradado. Solo se crea uno de estos sombreadores en función de la selección de Picker
:
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);
}
}
}
Ese degradado se muestra en el rectángulo sin un modo de combinación. Después de esa DrawRect
llamada, el lienzo simplemente contiene un degradado de negro a transparente. La cantidad de negro aumenta con valores de Slider
más altos.
En las cuatro últimas instrucciones del controlador de PaintSurface
, se muestran los dos mapas de bits. El modo de fusión SrcOut
significa que el primer mapa de bits solo se muestra en las áreas transparentes del fondo. El modo de DstOver
para el segundo mapa de bits significa que el segundo mapa de bits solo se muestra en aquellas áreas en las que no se muestra el primer mapa de bits.
Las capturas de pantalla siguientes muestran los tres tipos de transiciones diferentes, cada uno en la marca del 50 %.: