Compartir vía


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:

Ejemplo de Porter-Duff

Conceptos de Porter-Duff

Supongamos que un rectángulo marrón ocupa los dos tercios izquierdos y superiores de la superficie de visualización:

Destino de Porter-Duff

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:

Origen de Porter-Duff

Esto se llama origen o, a veces el primer plano.

Al mostrar el origen en el destino, esto es lo que espera:

Origen de Porter-Duff sobre

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:

Destino de Porter-Duff sobre

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:

Destino de Porter-Duff en

El modo de fusión de SKBlendMode.Xor (OR exclusivo) no hace que aparezca nada donde se superpongan las dos áreas:

Porter-Duff exclusivo o

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:

Porter-Duff

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

Cuadrícula de Porter-Duff

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:

Mono sentado

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:

Mate de mono sentado

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

Paso 0 de la composición de un muro de ladrillo

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:

Paso 1 de la composición de un muro de ladrillo

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:

Paso 2 de la composición de un muro de ladrillo

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:

Paso 3 de la composición de un muro de ladrillo

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:

Paso 4 de la composición de un muro de ladrillo

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, ClipPathy 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:

Transparencia de Porter-Duff

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:

Máscara de redacción

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 %.:

Transiciones de degradado