Compartilhar via


Os modos de mistura separáveis

Como você viu no artigo SkiaSharp Porter-Duff blend modes, os modos de mesclagem Porter-Duff geralmente executam operações de recorte. Os modos de mistura separáveis são diferentes. Os modos separáveis alteram os componentes individuais de cor vermelho, verde e azul de uma imagem. Os modos de mesclagem separáveis podem misturar cores para demonstrar que a combinação de vermelho, verde e azul é realmente branca:

Cores primárias

Clarear e escurecer de duas maneiras

É comum ter um bitmap um pouco escuro demais ou muito claro. Você pode usar modos de mesclagem separáveis para clarear ou escurecer a imagem. De fato, dois dos modos de mesclagem separáveis na SKBlendMode enumeração são nomeados Lighten e Darken.

Esses dois modos são demonstrados na página Lighten e Darken . O arquivo XAML instancia dois SKCanvasView objetos e dois Slider modos de exibição:

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

O primeiro SKCanvasView e Slider demonstra SKBlendMode.Lighten e o segundo par demonstra SKBlendMode.Darken. Os dois Slider modos de exibição compartilham o mesmo ValueChanged manipulador, e os dois SKCanvasView compartilham o mesmo PaintSurface manipulador. Ambos os manipuladores de eventos verificam qual objeto está disparando o 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);
        }
    }
}

O PaintSurface manipulador calcula um retângulo adequado para o bitmap. O manipulador exibe esse bitmap e, em seguida, exibe um retângulo sobre o bitmap usando um SKPaint objeto com sua BlendMode propriedade definida como SKBlendMode.Lighten ou SKBlendMode.Darken. A Color propriedade é um tom de cinza baseado no Slider. Para o Lighten modo, a cor varia de preto a branco, mas para o Darken modo varia de branco a preto.

As capturas de tela da esquerda para a direita mostram valores cada vez maiores Slider à medida que a imagem superior fica mais clara e a imagem inferior fica mais escura:

Clarear e escurecer

Este programa demonstra a maneira normal em que os modos de mistura separáveis são usados: O destino é uma imagem de algum tipo, muitas vezes um bitmap. A origem é um retângulo exibido usando um SKPaint objeto com sua BlendMode propriedade definida para um modo de mesclagem separável. O retângulo pode ser uma cor sólida (como é aqui) ou um gradiente. A transparência geralmente não é usada com os modos de mistura separáveis.

Ao experimentar este programa, você descobrirá que esses dois modos de mesclagem não clareiam e escurecem a imagem uniformemente. Em vez disso, o Slider parece definir um limite de algum tipo. Por exemplo, à medida que você aumenta o Slider para o Lighten modo, as áreas mais escuras da imagem ficam claras primeiro, enquanto as áreas mais claras permanecem as mesmas.

Para o Lighten modo, se o pixel de destino for o valor de cor RGB (Dr, Dg, Db) e o pixel de origem for a cor (Sr, Sg, Sb), a saída será (Ou, Og, Ob) calculada da seguinte maneira:

Or = max(Dr, Sr) Og = max(Dg, Sg) Ob = max(Db, Sb)

Para vermelho, verde e azul separadamente, o resultado é o maior do destino e da origem. Isso produz o efeito de clarear as áreas escuras do destino primeiro.

O Darken modo é semelhante, exceto que o resultado é o menor do destino e da origem:

Or = min(Dr, Sr) Og = min(Dg, Sg) Ob = min(Db, Sb)

Os componentes vermelho, verde e azul são tratados separadamente, e é por isso que esses modos de mistura são chamados de modos de mistura separáveis . Por esse motivo, as abreviaturas Dc e Sc podem ser usadas para as cores de destino e origem, e entende-se que os cálculos se aplicam a cada um dos componentes vermelho, verde e azul separadamente.

A tabela a seguir mostra todos os modos de mesclagem separáveis com breves explicações do que eles fazem. A segunda coluna mostra a cor de origem que não produz nenhuma alteração:

Modo de mistura Nenhuma alteração Operação
Plus Preto Ilumina adicionando cores: Sc + Dc
Modulate Branca Escurece multiplicando cores: Sc· Dc
Screen Preto Produto de complementos: Sc + Dc – Sc· Dc
Overlay Cinza Inverso de HardLight
Darken Branca Mínimo de cores: min (Sc, Dc)
Lighten Preto Máximo de cores: max (Sc, Dc)
ColorDodge Preto Ilumina o destino com base na origem
ColorBurn Branca Destino de Darkens com base na origem
HardLight Cinza Semelhante ao efeito de holofote severo
SoftLight Cinza Semelhante ao efeito do holofote macio
Difference Preto Subtrai o mais escuro do mais claro: Abs(Dc – Sc)
Exclusion Preto Contraste semelhante, Difference mas mais baixo
Multiply Branca Escurece multiplicando cores: Sc· Dc

Algoritmos mais detalhados podem ser encontrados na especificação W3C Compositing and Blending Level 1 e na Skia SkBlendMode Reference, embora a notação nessas duas fontes não seja a mesma. Tenha em mente que Plus é comumente considerado como um modo de mistura Porter-Duff e Modulate não faz parte da especificação W3C.

Se a origem for transparente, então para todos os modos de mesclagem separáveis, exceto Modulate, o modo de mesclagem não terá efeito. Como você viu anteriormente, o Modulate modo de mistura incorpora o canal alfa na multiplicação. Caso contrário, Modulate tem o mesmo efeito que Multiply.

Observe os dois modos nomeados ColorDodge e ColorBurn. As palavras esquivar e queimar tiveram origem em práticas fotográficas de câmara escura. Um ampliador faz uma impressão fotográfica brilhando luz através de um negativo. Sem luz, a estampa é branca. A impressão fica mais escura à medida que mais luz incide sobre a impressão por um longo período de tempo. Os fabricantes de impressão geralmente usavam uma mão ou um pequeno objeto para impedir que parte da luz caísse sobre uma determinada parte da impressão, tornando essa área mais leve. Isso é conhecido como esquiva. Por outro lado, material opaco com um buraco (ou mãos bloqueando a maior parte da luz) poderia ser usado para direcionar mais luz em um determinado local para escurecê-lo, chamado de queima.

O programa Dodge and Burn é muito semelhante ao Lighten e Darken. O arquivo XAML é estruturado da mesma forma, mas com nomes de elementos diferentes, e o arquivo code-behind também é bastante semelhante, mas o efeito desses dois modos de mesclagem é bem diferente:

Esquivar-se e queimar

Para valores pequenos Slider , o Lighten modo clareia primeiro as áreas escuras, enquanto ColorDodge clareia de forma mais uniforme.

Programas de aplicativos de processamento de imagens geralmente permitem que a esquiva e a gravação sejam restritas a áreas específicas, assim como em uma câmara escura. Isso pode ser feito por gradientes ou por um bitmap com tons variados de cinza.

Explorando os modos de mistura separáveis

A página Modos de mesclagem separáveis permite examinar todos os modos de mesclagem separáveis. Ele exibe um destino de bitmap e uma fonte de retângulo colorida usando um dos modos de mesclagem.

O arquivo XAML define um Picker (para selecionar o modo de mesclagem) e quatro controles deslizantes. Os três primeiros controles deslizantes permitem definir os componentes vermelho, verde e azul da origem. O quarto controle deslizante destina-se a substituir esses valores definindo um tom cinza. Os controles deslizantes individuais não são identificados, mas as cores indicam sua função:

<?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>

O arquivo code-behind carrega um dos recursos de bitmap e o desenha duas vezes, uma na metade superior da tela e novamente na metade inferior da tela:

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

Na parte inferior do PaintSurface manipulador, um retângulo é desenhado sobre o segundo bitmap com o modo de mesclagem selecionado e a cor selecionada. Você pode comparar o bitmap modificado na parte inferior com o bitmap original na parte superior:

Modos do Blend separáveis

Cores primárias aditivas e subtrativas

A página Cores Primárias desenha três círculos sobrepostos de vermelho, verde e azul:

Cores primárias aditivas

Estas são as cores primárias aditivas. Combinações de quaisquer dois produzem ciano, magenta e amarelo, e uma combinação de todos os três é branca.

Esses três círculos são desenhados com o SKBlendMode.Plus modo, mas você também pode usar Screen, Lightenou Difference para o mesmo efeito. Aqui está o 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);
            }
        }
    }
}

O programa inclui um TabGestureRecognizerarquivo . Quando você toca ou clica na tela, o programa usa SKBlendMode.Multiply para exibir as três primárias subtrativas:

Cores primárias subtrativas

O Darken modo também funciona para esse mesmo efeito.