Los modos de combinación no separables

Como ha visto en el artículo Modos de mezcla separable SkiaSharp, los modos de mezcla separable realizan operaciones en los canales rojo, verde y azul por separado. Los modos de combinación no separables no lo hacen. Al operar sobre los niveles de tono, saturación y luminosidad de color, los modos de mezcla no separables pueden modificar los colores de maneras interesantes:

Ejemplo no separable

El modelo Hue-Saturation-luminosidad

Para comprender los modos de mezcla no separables, es necesario tratar el destino y los píxeles de origen como colores en el modelo Hue-Saturación-luminosidad. (La luminosidad también se conoce como Ligereza.)

El modelo de color HSL se explicó en el artículo Integración con Xamarin.Forms y un programa de ejemplo en ese artículo permite experimentación con colores HSL. Puede crear un valor SKColor mediante los valores Hue, saturación y luminosidad con el método estático SKColor.FromHsl.

El matiz representa la longitud de onda dominante del color. Los valores de Hue van de 0 a 360 y hacen un ciclo a través de los primarios aditivos y sustractivos: el rojo es el valor 0, el amarillo es 60, el verde es 120, el cian es 180, el azul es 240, el magenta es 300, y el ciclo vuelve al rojo en 360.

Si no hay ningún color dominante (por ejemplo, el color es blanco o negro o un tono gris), el Hue no está definido y normalmente se establece en 0.

Los valores de saturación pueden oscilar entre 0 y 100 e indicar la pureza del color. Un valor de saturación de 100 es el color más puro, mientras que los valores inferiores a 100 hacen que el color se vuelva más gris. Un valor de saturación de 0 da como resultado un tono gris.

El valor luminosidad (o ligereza) indica lo brillante que es el color. Un valor de luminosidad de 0 es negro independientemente de la otra configuración. Del mismo modo, un valor de luminosidad de 100 es blanco.

El valor HSL (0, 100, 50) es el valor RGB (FF, 00, 00), que es rojo puro. El valor HSL (180, 100, 50) es el valor RGB (00, FF, FF), cian puro. A medida que se reduce la saturación, se reduce el componente de color dominante y se incrementan los demás componentes. En un nivel de saturación de 0, todos los componentes son iguales y el color es un tono gris. Disminuir la luminosidad para ir a negro; aumenta la luminosidad para ir a blanco.

Los modos de combinación en detalle

Al igual que los otros modos de mezcla, los cuatro modos de combinación no separables implican un destino (que suele ser una imagen de mapa de bits) y un origen, que suele ser un único color o un degradado. Los modos de combinación combinan los valores Hue, saturación y luminosidad del destino y del origen:

Modo de fusión Componentes del origen Componentes del destino
Hue Hue Saturación y luminosidad
Saturation Saturación Matiz y luminosidad
Color Matiz y saturación Luminosidad
Luminosity Luminosidad Matiz y saturación

Consulte la especificación W3C Compositing and Blending Level 1 para los algoritmos.

La página Modos de mezcla no separable contiene un Picker para seleccionar uno de estos modos de combinación y tres vistas Slider para seleccionar un color HSL:

<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.NonSeparableBlendModesPage"
             Title="Non-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.Hue" />
                    <x:Static Member="skia:SKBlendMode.Saturation" />
                    <x:Static Member="skia:SKBlendMode.Color" />
                    <x:Static Member="skia:SKBlendMode.Luminosity" />
                </x:Array>
            </Picker.ItemsSource>

            <Picker.SelectedIndex>
                0
            </Picker.SelectedIndex>
        </Picker>

        <Slider x:Name="hueSlider"
                Maximum="360"
                Margin="10, 0"
                ValueChanged="OnSliderValueChanged" />

        <Slider x:Name="satSlider"
                Maximum="100"
                Margin="10, 0"
                ValueChanged="OnSliderValueChanged" />

        <Slider x:Name="lumSlider"
                Maximum="100"
                Margin="10, 0"
                ValueChanged="OnSliderValueChanged" />

        <StackLayout Orientation="Horizontal">
            <Label x:Name="hslLabel"
                   HorizontalOptions="CenterAndExpand" />

            <Label x:Name="rgbLabel"
                   HorizontalOptions="CenterAndExpand" />

        </StackLayout>
    </StackLayout>
</ContentPage>

Para ahorrar espacio, las tres vistas Slider no se identifican en la interfaz de usuario del programa. Tendrá que recordar que el orden es Hue, saturación y luminosidad. Dos vistas Label en la parte inferior de la página muestran los valores de color HSL y RGB.

El archivo de código subyacente carga uno de los recursos de mapa de bits, muestra que lo más grande posible en el lienzo y a continuación, cubre el lienzo con un rectángulo. El color del rectángulo se basa en las tres vistas Slider y el modo de combinación es el seleccionado en Picker:

public partial class NonSeparableBlendModesPage : ContentPage
{
    SKBitmap bitmap = BitmapExtensions.LoadBitmapResource(
                        typeof(NonSeparableBlendModesPage),
                        "SkiaSharpFormsDemos.Media.Banana.jpg");
    SKColor color;

    public NonSeparableBlendModesPage()
    {
        InitializeComponent();
    }

    void OnPickerSelectedIndexChanged(object sender, EventArgs args)
    {
        canvasView.InvalidateSurface();
    }

    void OnSliderValueChanged(object sender, ValueChangedEventArgs e)
    {
        // Calculate new color based on sliders
        color = SKColor.FromHsl((float)hueSlider.Value,
                                (float)satSlider.Value,
                                (float)lumSlider.Value);

        // Use labels to display HSL and RGB color values
        color.ToHsl(out float hue, out float sat, out float lum);

        hslLabel.Text = String.Format("HSL = {0:F0} {1:F0} {2:F0}",
                                      hue, sat, lum);

        rgbLabel.Text = String.Format("RGB = {0:X2} {1:X2} {2:X2}",
                                      color.Red, color.Green, color.Blue);

        canvasView.InvalidateSurface();
    }

    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        canvas.Clear();
        canvas.DrawBitmap(bitmap, info.Rect, BitmapStretch.Uniform);

        // Get blend mode from Picker
        SKBlendMode blendMode =
            (SKBlendMode)(blendModePicker.SelectedIndex == -1 ?
                                        0 : blendModePicker.SelectedItem);

        using (SKPaint paint = new SKPaint())
        {
            paint.Color = color;
            paint.BlendMode = blendMode;
            canvas.DrawRect(info.Rect, paint);
        }
    }
}

Observe que el programa no muestra el valor de color HSL seleccionado por los tres controles deslizantes. En su lugar, crea un valor de color a partir de esos controles deslizantes y a continuación, usa el métodoToHsl para obtener los valores Hue, saturación y luminosidad. Esto se debe a que el método FromHsl convierte un color HSL en un color RGB, que se almacena internamente en la estructura SKColor. El método ToHsl convierte de RGB a HSL, pero el resultado no siempre será el valor original.

Por ejemplo, FromHsl convierte el valor HSL (180, 50, 0) en el color RGB (0, 0, 0, 0) porque Luminosity es cero. El método ToHsl convierte el color RGB (0, 0, 0, 0) al color HSL (0, 0, 0, 0) porque los valores de Hue y Saturación son irrelevantes. Al usar este programa, es mejor que vea la representación del color HSL que usa el programa en lugar del especificado con los controles deslizantes.

El modo de mezcla SKBlendModes.Hue usa el nivel Hue del origen mientras conserva los niveles de saturación y luminosidad del destino. Al probar este modo de mezcla, los controles deslizantes de saturación y luminosidad deben establecerse en algo distinto de 0 o 100 porque, en esos casos, el Hue no se define de forma única.

Modos de fusión no separables: matiz

Cuando se usa establecer el control deslizante en 0 (como con la captura de pantalla de iOS a la izquierda), todo se vuelve rojizo. Pero esto no significa que la imagen esté completamente ausente de verde y azul. Obviamente, todavía hay tonos grises presentes en el resultado. Por ejemplo, el color RGB (40, 40, C0) es equivalente al color HSL (240, 50, 50). El Hue es azul, pero el valor de saturación de 50 indica que también hay componentes rojos y verdes. Si el Hue se establece en 0 con SKBlendModes.Hue, el color HSL es (0, 50, 50), que es el color RGB (C0, 40, 40). Todavía hay componentes azules y verdes, pero ahora el componente dominante es rojo.

El modo de mezcla SKBlendModes.Saturation combina el nivel saturación de la fuente con el Hue y la luminosidad del destino. Al igual que el Hue, la saturación no está bien definida si la luminosidad es 0 o 100. En teoría, cualquier configuración de luminosidad entre esos dos extremos debe funcionar. Sin embargo, el valor de luminosidad parece afectar al resultado más de lo que debería. Establezca la luminosidad en 50 y puede ver cómo puede establecer el nivel saturación de la imagen:

Modos de fusión no separables: saturación

Puede usar este modo de mezcla para aumentar la saturación de color de una imagen opaca, o puede reducir la saturación a cero (como en la captura de pantalla de iOS a la izquierda) para una imagen resultante compuesta solo de tonos grises.

El modo de mezcla SKBlendModes.Color conserva la luminosidad del destino, pero usa el Hue y la saturación de la fuente. De nuevo, eso implica que cualquier ajuste del control deslizante luminosidad en algún lugar entre los extremos debe funcionar.

Modos de fusión no separables: color

Verá una aplicación de este modo de mezcla en breve.

Por último, el modo de mezcla SKBlendModes.Luminosity es el contrario de SKBlendModes.Color. Conserva el Hue y la saturación del destino, pero usa la luminosidad de la fuente. El modo de mezclaLuminosity es el más misterioso del lote: los controles deslizantes Hue y saturación afectan a la imagen, pero incluso en la luminosidad media, la imagen no es distinta:

Modos de fusión no separables: luminosidad

En teoría, aumentar o disminuir la luminosidad de una imagen debe hacer que sea más claro o oscuro. Este Ejemplo de propiedad luminosidad o esta Enumeración SKBlendMode Enum puede ser de interés.

Por lo general, no es el caso de que quiera usar uno de los modos de combinación no separables con un origen que consta de un único color aplicado a toda la imagen de destino. El efecto es demasiado grande. Querrá restringir el efecto a una parte de la imagen. En ese caso, es probable que la fuente incorpore transparencia, o puede que se limite a un gráfico más pequeño.

Un mate para un modo separable

Este es uno de los mapas de bits incluidos como un recurso en el ejemplo. El nombre de archivo es Banana.jpg:

Mono con plátano

Es posible crear un mate que abarque solo el plátano. Esto también es un recurso del ejemplo. El nombre de archivo es BananaMatte.png:

Mate de plátano

Aparte de la forma de plátano negro, el resto del mapa de bits es transparente.

La página Blue Banana utiliza ese mate para modificar el Hue y la saturación del plátano que el mono está sosteniendo, pero para cambiar nada más en la imagen.

En la siguiente clase BlueBananaPage, el mapa de bits Banana.jpg se carga como un campo. El constructor carga el mapa de bitsBananaMatte.png como objeto matteBitmap, pero no conserva ese objeto más allá del constructor. En su lugar, se crea un tercer mapa de bits denominado blueBananaBitmap. El matteBitmap se dibuja seguido de blueBananaBitmap un objeto SKPaint con su conjunto en azul Color y su conjunto BlendMode en SKBlendMode.SrcIn. El blueBananaBitmap permanece principalmente transparente pero con una sólida imagen azul pura del plátano:

public class BlueBananaPage : ContentPage
{
    SKBitmap bitmap = BitmapExtensions.LoadBitmapResource(
        typeof(BlueBananaPage),
        "SkiaSharpFormsDemos.Media.Banana.jpg");

    SKBitmap blueBananaBitmap;

    public BlueBananaPage()
    {
        Title = "Blue Banana";

        // Load banana matte bitmap (black on transparent)
        SKBitmap matteBitmap = BitmapExtensions.LoadBitmapResource(
            typeof(BlueBananaPage),
            "SkiaSharpFormsDemos.Media.BananaMatte.png");

        // Create a bitmap with a solid blue banana and transparent otherwise
        blueBananaBitmap = new SKBitmap(matteBitmap.Width, matteBitmap.Height);

        using (SKCanvas canvas = new SKCanvas(blueBananaBitmap))
        {
            canvas.Clear();
            canvas.DrawBitmap(matteBitmap, new SKPoint(0, 0));

            using (SKPaint paint = new SKPaint())
            {
                paint.Color = SKColors.Blue;
                paint.BlendMode = SKBlendMode.SrcIn;
                canvas.DrawPaint(paint);
            }
        }

        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();

        canvas.DrawBitmap(bitmap, info.Rect, BitmapStretch.Uniform);

        using (SKPaint paint = new SKPaint())
        {
            paint.BlendMode = SKBlendMode.Color;
            canvas.DrawBitmap(blueBananaBitmap,
                              info.Rect,
                              BitmapStretch.Uniform,
                              paint: paint);
        }
    }
}

El controlador PaintSurface dibuja el mapa de bits con el mono que mantiene el plátano. Este código va seguido de la presentación de blueBananaBitmap con SKBlendMode.Color. Sobre la superficie del plátano, el tono y la saturación de cada píxel se reemplazan por el azul sólido, que corresponde a un valor de matiz de 240 y un valor de saturación de 100. Sin embargo, la luminosidad sigue siendo la misma, lo que significa que el plátano sigue teniendo una textura realista a pesar de su nuevo color:

Plátano azul

Intente cambiar el modo de mezcla a SKBlendMode.Saturation. El plátano permanece amarillo, pero es un amarillo más intenso. En una aplicación real, esto podría ser un efecto más deseable que convertir el azul de plátano.