Modos de fusión separables
Como has visto en el artículo Modos de fusión SkiaSharp Porter-Duff los modos de fusión Porter-Duff suelen realizar operaciones de recorte. Los modos de fusión separables son diferentes. Los modos separables modifican los componentes individuales de color rojo, verde y azul de una imagen. Los modos de fusión separables pueden mezclar color para demostrar que la combinación de rojo, verde y azul es realmente blanca:
Aclaramiento y oscurecimiento de dos maneras
Es habitual tener un mapa de bits que sea demasiado oscuro o demasiado claro. Puedes usar modos de fusión separables para aclarar o oscurecer la imagen. De hecho, dos de los modos de fusión separables de la enumeración SKBlendMode
se denominan Lighten
y Darken
.
Estos dos modos se muestran en la página Claro y Oscuro. El archivo XAML crea una instancia de dos objetos SKCanvasView
y dos vistas Slider
:
<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.LightenAndDarkenPage"
Title="Lighten and Darken">
<StackLayout>
<skia:SKCanvasView x:Name="lightenCanvasView"
VerticalOptions="FillAndExpand"
PaintSurface="OnCanvasViewPaintSurface" />
<Slider x:Name="lightenSlider"
Margin="10"
ValueChanged="OnSliderValueChanged" />
<skia:SKCanvasView x:Name="darkenCanvasView"
VerticalOptions="FillAndExpand"
PaintSurface="OnCanvasViewPaintSurface" />
<Slider x:Name="darkenSlider"
Margin="10"
ValueChanged="OnSliderValueChanged" />
</StackLayout>
</ContentPage>
El primer par SKCanvasView
y Slider
muestra SKBlendMode.Lighten
y el segundo muestra SKBlendMode.Darken
. Las dos vistas Slider
comparten el mismo controlador ValueChanged
y los dos objetos SKCanvasView
comparten el mismo controlador PaintSurface
. Ambos controladores de eventos comprueban qué objeto desencadena el evento:
public partial class LightenAndDarkenPage : ContentPage
{
SKBitmap bitmap = BitmapExtensions.LoadBitmapResource(
typeof(SeparableBlendModesPage),
"SkiaSharpFormsDemos.Media.Banana.jpg");
public LightenAndDarkenPage ()
{
InitializeComponent ();
}
void OnSliderValueChanged(object sender, ValueChangedEventArgs args)
{
if ((Slider)sender == lightenSlider)
{
lightenCanvasView.InvalidateSurface();
}
else
{
darkenCanvasView.InvalidateSurface();
}
}
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
// Find largest size rectangle in canvas
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
canvas.DrawBitmap(bitmap, rect);
// Display gray rectangle with blend mode
using (SKPaint paint = new SKPaint())
{
if ((SKCanvasView)sender == lightenCanvasView)
{
byte value = (byte)(255 * lightenSlider.Value);
paint.Color = new SKColor(value, value, value);
paint.BlendMode = SKBlendMode.Lighten;
}
else
{
byte value = (byte)(255 * (1 - darkenSlider.Value));
paint.Color = new SKColor(value, value, value);
paint.BlendMode = SKBlendMode.Darken;
}
canvas.DrawRect(rect, paint);
}
}
}
El controlador PaintSurface
calcula un rectángulo adecuado para el mapa de bits. El controlador muestra ese mapa de bits y, después, un rectángulo sobre el mapa de bits mediante un objeto SKPaint
con su propiedad BlendMode
establecida SKBlendMode.Lighten
en o SKBlendMode.Darken
. La propiedad Color
es un tono gris basado en Slider
. Para el modo Lighten
, el color oscila entre negro y blanco, pero para el modo Darken
abarca de blanco a negro.
Las capturas de pantalla de izquierda a derecha muestran valores Slider
cada vez más grandes, ya que la imagen superior se vuelve más clara y la imagen inferior se vuelve más oscura:
Este programa muestra la forma normal en que se usan los modos de fusión separables: el destino es una imagen de algún tipo, muy a menudo un mapa de bits. El origen es un rectángulo que se muestra mediante un objeto SKPaint
con su propiedad BlendMode
establecida en un modo de fusión separable. El rectángulo puede ser un color sólido (como sucede aquí) o un degradado. La transparencia no se usa generalmente con los modos de fusión separables.
A medida que experimentes con este programa, descubrirás que estos dos modos de fusión no iluminan ni oscurecen la imagen uniformemente. En su lugar, Slider
parece establecer un umbral de algún tipo. Por ejemplo, a medida que aumenta Slider
para el modo Lighten
, las áreas más oscuras de la imagen obtienen luz en primer lugar, mientras que las áreas más claras siguen siendo las mismas.
Para el modo Lighten
, si el píxel de destino es el valor de color RGB (Dr, Dg, Db) y el píxel de origen es el color (Sr, Sg, Sb), la salida es (Or, Og, Ob) calculada de la siguiente manera:
Or = max(Dr, Sr)
Og = max(Dg, Sg)
Ob = max(Db, Sb)
Para el rojo, verde y azul por separado, el resultado es el valor mayor entre el destino y el origen. Esto produce el efecto de aclarar primero las áreas oscuras del destino.
El modo Darken
es similar, salvo que el resultado es el menor entre el destino y el origen:
Or = min(Dr, Sr)
Og = min(Dg, Sg)
Ob = min(Db, Sb)
Los componentes rojo, verde y azul se controlan por separado, por lo que estos modos de fusión se conocen como modos de fusión separables. Por este motivo, las abreviaturas Dc y Sc se pueden usar para los colores de origen y destino, y se entiende que los cálculos se aplican a cada uno de los componentes rojo, verde y azul por separado.
En la tabla siguiente se muestran todos los modos de fusión separables con breves explicaciones de lo que hacen. En la segunda columna se muestra el color de origen que no produce ningún cambio:
Modo de fusión | Sin cambios | Operación |
---|---|---|
Plus |
Negro | Aclara agregando colores: Sc + Dc |
Modulate |
Blanco | Oscurece multiplicando colores: Sc·Dc |
Screen |
Negro | Complementa el producto de complementos: Sc + Dc – Sc·Dc |
Overlay |
Gris | Inverso de HardLight |
Darken |
Blanco | Mínimo de colores: min(Sc, Dc) |
Lighten |
Negro | Máximo de colores: max(Sc, Dc) |
ColorDodge |
Negro | Ilumina el destino basado en el origen |
ColorBurn |
Blanco | Oscurece el destino basado en el origen |
HardLight |
Gris | Similar al efecto del contenido destacado áspero |
SoftLight |
Gris | Similar al efecto del contenido destacado suave |
Difference |
Negro | Resta el más oscuro del más claro: Abs(Dc – Sc) |
Exclusion |
Negro | Similar a Difference pero con menor contraste |
Multiply |
Blanco | Oscurece multiplicando colores: Sc·Dc |
Los algoritmos más detallados se pueden encontrar en la especificación Composición y combinación de nivel 1 de W3C y Referencia SkBlendMode de Skia, aunque la notación en estos dos orígenes no es la misma. Ten en cuenta que Plus
normalmente se considera un modo de fusión Porter-Duff y Modulate
no forma parte de la especificación de W3C.
Si el origen es transparente, para todos los modos de combinación separables, excepto para Modulate
, el modo de fusión no tiene ningún efecto. Como has visto anteriormente, el modo de fusión Modulate
incorpora el canal alfa en la multiplicación. De lo contrario, Modulate
tiene el mismo efecto que Multiply
.
Observa los dos modos denominados ColorDodge
y ColorBurn
. Las palabras subexposición y sobreexposición se originaron en prácticas fotográficas en un cuarto oscuro. Un ampliador hace una impresión fotográfica dirigiendo la luz a través de un negativo. Sin luz, la impresión es blanca. La impresión se oscurece conforme se refleja más luz en la impresión durante un periodo de tiempo más largo. Los profesionales de impresiones suelen usar una mano o un objeto pequeño para impedir que parte de la luz se refleje sobre una parte determinada de la impresión, lo que hace que esa zona sea más clara. Esto se conoce como subexposición. Por el contrario, el material opaco con un agujero en él (o utilizar manos para bloquear la mayor parte de la luz) podría usarse para dirigir más luz en un punto determinado para oscurecerlo, llamado sobreexposición.
El programa Subexposición y sobreexposición es muy similar a Aclaramiento y oscurecimiento. El archivo XAML está estructurado igual, pero con nombres de elemento diferentes, y el archivo de código subyacente también es bastante similar, pero el efecto de estos dos modos de fusión es bastante diferente:
En el caso de los valores pequeños Slider
, el modo Lighten
aclara primero las áreas oscuras, mientras que ColorDodge
aclara más uniformemente.
Los programas de aplicaciones de procesamiento de imágenes suelen permitir que la subexposición y la sobreexposición se restrinjan a áreas específicas, al igual que en un cuarto oscuro. Esto se puede lograr mediante degradados o mediante un mapa de bits con diferentes tonos de gris.
Exploración de los modos de fusión separables
La página Modos de fusión separables permite examinar todos los modos de fusión separables. Muestra un destino de mapa de bits y un origen de rectángulo coloreado mediante uno de los modos de fusión.
El archivo XAML define un elemento Picker
(para seleccionar el modo de fusión) y cuatro controles deslizantes. Los tres primeros controles deslizantes permiten establecer los componentes rojo, verde y azul del origen. El cuarto control deslizante está pensado para invalidar esos valores estableciendo un tono gris. Los controles deslizantes individuales no se identifican, pero los colores indican su función:
<?xml version="1.0" encoding="utf-8" ?>
<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.SeparableBlendModesPage"
Title="Separable Blend Modes">
<StackLayout>
<skiaviews:SKCanvasView x:Name="canvasView"
VerticalOptions="FillAndExpand"
PaintSurface="OnCanvasViewPaintSurface" />
<Picker x:Name="blendModePicker"
Title="Blend Mode"
Margin="10, 0"
SelectedIndexChanged="OnPickerSelectedIndexChanged">
<Picker.ItemsSource>
<x:Array Type="{x:Type skia:SKBlendMode}">
<x:Static Member="skia:SKBlendMode.Plus" />
<x:Static Member="skia:SKBlendMode.Modulate" />
<x:Static Member="skia:SKBlendMode.Screen" />
<x:Static Member="skia:SKBlendMode.Overlay" />
<x:Static Member="skia:SKBlendMode.Darken" />
<x:Static Member="skia:SKBlendMode.Lighten" />
<x:Static Member="skia:SKBlendMode.ColorDodge" />
<x:Static Member="skia:SKBlendMode.ColorBurn" />
<x:Static Member="skia:SKBlendMode.HardLight" />
<x:Static Member="skia:SKBlendMode.SoftLight" />
<x:Static Member="skia:SKBlendMode.Difference" />
<x:Static Member="skia:SKBlendMode.Exclusion" />
<x:Static Member="skia:SKBlendMode.Multiply" />
</x:Array>
</Picker.ItemsSource>
<Picker.SelectedIndex>
0
</Picker.SelectedIndex>
</Picker>
<Slider x:Name="redSlider"
MinimumTrackColor="Red"
MaximumTrackColor="Red"
Margin="10, 0"
ValueChanged="OnSliderValueChanged" />
<Slider x:Name="greenSlider"
MinimumTrackColor="Green"
MaximumTrackColor="Green"
Margin="10, 0"
ValueChanged="OnSliderValueChanged" />
<Slider x:Name="blueSlider"
MinimumTrackColor="Blue"
MaximumTrackColor="Blue"
Margin="10, 0"
ValueChanged="OnSliderValueChanged" />
<Slider x:Name="graySlider"
MinimumTrackColor="Gray"
MaximumTrackColor="Gray"
Margin="10, 0"
ValueChanged="OnSliderValueChanged" />
<Label x:Name="colorLabel"
HorizontalTextAlignment="Center" />
</StackLayout>
</ContentPage>
El archivo de código subyacente carga uno de los recursos de mapa de bits y lo dibuja dos veces, una vez en la mitad superior del lienzo y otra vez en la mitad inferior:
public partial class SeparableBlendModesPage : ContentPage
{
SKBitmap bitmap = BitmapExtensions.LoadBitmapResource(
typeof(SeparableBlendModesPage),
"SkiaSharpFormsDemos.Media.Banana.jpg");
public SeparableBlendModesPage()
{
InitializeComponent();
}
void OnPickerSelectedIndexChanged(object sender, EventArgs args)
{
canvasView.InvalidateSurface();
}
void OnSliderValueChanged(object sender, ValueChangedEventArgs e)
{
if (sender == graySlider)
{
redSlider.Value = greenSlider.Value = blueSlider.Value = graySlider.Value;
}
colorLabel.Text = String.Format("Color = {0:X2} {1:X2} {2:X2}",
(byte)(255 * redSlider.Value),
(byte)(255 * greenSlider.Value),
(byte)(255 * blueSlider.Value));
canvasView.InvalidateSurface();
}
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
// Draw bitmap in top half
SKRect rect = new SKRect(0, 0, info.Width, info.Height / 2);
canvas.DrawBitmap(bitmap, rect, BitmapStretch.Uniform);
// Draw bitmap in bottom halr
rect = new SKRect(0, info.Height / 2, info.Width, info.Height);
canvas.DrawBitmap(bitmap, rect, BitmapStretch.Uniform);
// Get values from XAML controls
SKBlendMode blendMode =
(SKBlendMode)(blendModePicker.SelectedIndex == -1 ?
0 : blendModePicker.SelectedItem);
SKColor color = new SKColor((byte)(255 * redSlider.Value),
(byte)(255 * greenSlider.Value),
(byte)(255 * blueSlider.Value));
// Draw rectangle with blend mode in bottom half
using (SKPaint paint = new SKPaint())
{
paint.Color = color;
paint.BlendMode = blendMode;
canvas.DrawRect(rect, paint);
}
}
}
Hacia la parte inferior del controlador PaintSurface
, se dibuja un rectángulo sobre el segundo mapa de bits con el modo de fusión y el color seleccionados. Puedes comparar el mapa de bits modificado en la parte inferior con el mapa de bits original en la parte superior:
Colores primarios aditivos y sustractivos
La página Colores primarios dibuja tres círculos superpuestos en rojo, verde y azul:
Estos son los colores primarios aditivos. Una combinación de dos de ellos produce cian, magenta y amarillo, y una combinación de los tres genera blanco.
Estos tres círculos están dibujados con el modo SKBlendMode.Plus
, pero también puedes usar Screen
, Lighten
o Difference
para el mismo efecto. Este es el programa:
public class PrimaryColorsPage : ContentPage
{
bool isSubtractive;
public PrimaryColorsPage ()
{
Title = "Primary Colors";
SKCanvasView canvasView = new SKCanvasView();
canvasView.PaintSurface += OnCanvasViewPaintSurface;
// Switch between additive and subtractive primaries at tap
TapGestureRecognizer tap = new TapGestureRecognizer();
tap.Tapped += (sender, args) =>
{
isSubtractive ^= true;
canvasView.InvalidateSurface();
};
canvasView.GestureRecognizers.Add(tap);
Content = canvasView;
}
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
SKPoint center = new SKPoint(info.Rect.MidX, info.Rect.MidY);
float radius = Math.Min(info.Width, info.Height) / 4;
float distance = 0.8f * radius; // from canvas center to circle center
SKPoint center1 = center +
new SKPoint(distance * (float)Math.Cos(9 * Math.PI / 6),
distance * (float)Math.Sin(9 * Math.PI / 6));
SKPoint center2 = center +
new SKPoint(distance * (float)Math.Cos(1 * Math.PI / 6),
distance * (float)Math.Sin(1 * Math.PI / 6));
SKPoint center3 = center +
new SKPoint(distance * (float)Math.Cos(5 * Math.PI / 6),
distance * (float)Math.Sin(5 * Math.PI / 6));
using (SKPaint paint = new SKPaint())
{
if (!isSubtractive)
{
paint.BlendMode = SKBlendMode.Plus;
System.Diagnostics.Debug.WriteLine(paint.BlendMode);
paint.Color = SKColors.Red;
canvas.DrawCircle(center1, radius, paint);
paint.Color = SKColors.Lime; // == (00, FF, 00)
canvas.DrawCircle(center2, radius, paint);
paint.Color = SKColors.Blue;
canvas.DrawCircle(center3, radius, paint);
}
else
{
paint.BlendMode = SKBlendMode.Multiply
System.Diagnostics.Debug.WriteLine(paint.BlendMode);
paint.Color = SKColors.Cyan;
canvas.DrawCircle(center1, radius, paint);
paint.Color = SKColors.Magenta;
canvas.DrawCircle(center2, radius, paint);
paint.Color = SKColors.Yellow;
canvas.DrawCircle(center3, radius, paint);
}
}
}
}
El programa incluye un elemento TabGestureRecognizer
. Al pulsar o hacer clic en la pantalla, el programa usa SKBlendMode.Multiply
para mostrar los tres primarios sustractivos:
El modo Darken
también funciona para este mismo efecto.