Partilhar via


Modos de mistura Porter-Duff

Os modos de mistura Porter-Duff são nomeados em homenagem a Thomas Porter e Tom Duff, que desenvolveram uma álgebra de composição enquanto trabalhavam para a Lucasfilm. Seu artigo Compositing Digital Images foi publicado na edição de julho de 1984 da Computer Graphics, páginas 253 a 259. Esses modos de mistura são essenciais para a composição, que é montar várias imagens em uma cena composta:

Amostra de Porter-Duff

Conceitos de Porter-Duff

Suponha que um retângulo acastanhado ocupe os dois terços esquerdo e superior da superfície da tela:

Destino Porter-Duff

Essa área é chamada de destino ou, às vezes, de plano de fundo ou pano de fundo.

Você deseja desenhar o seguinte retângulo, que é do mesmo tamanho do destino. O retângulo é transparente, exceto por uma área azulada que ocupa os dois terços direito e inferior:

Fonte Porter-Duff

Isso é chamado de fonte ou, às vezes, de primeiro plano.

Quando você exibe a origem no destino, veja o que você espera:

Fonte de Porter-Duff Over

Os pixels transparentes da fonte permitem que o plano de fundo apareça, enquanto os pixels azulados da fonte obscurecem o plano de fundo. Esse é o caso normal, e é referido no SkiaSharp como SKBlendMode.SrcOver. Esse valor é a configuração padrão da propriedade quando um SKPaint objeto é instanciado BlendMode pela primeira vez.

No entanto, é possível especificar um modo de mesclagem diferente para um efeito diferente. Se você especificar SKBlendMode.DstOver, na área em que a origem e o destino se cruzam, o destino aparecerá em vez da origem:

Destino Porter-Duff Over

O SKBlendMode.DstIn modo de mesclagem exibe apenas a área onde o destino e a origem se cruzam usando a cor de destino:

Destino Porter-Duff em

O modo de mistura de SKBlendMode.Xor (OR exclusivo) faz com que nada apareça onde as duas áreas se sobrepõem:

Porter-Duff Exclusivo Ou

Os retângulos coloridos de destino e origem dividem efetivamente a superfície de exibição em quatro áreas exclusivas que podem ser coloridas de várias maneiras correspondentes à presença dos retângulos de destino e origem:

Porter-Duff

Os retângulos superior direito e inferior esquerdo estão sempre em branco porque o destino e a origem são transparentes nessas áreas. A cor de destino ocupa a área superior esquerda, de modo que essa área pode ser colorida com a cor de destino ou não. Da mesma forma, a cor de origem ocupa a área inferior direita, de modo que essa área pode ser colorida com a cor de origem ou não. A interseção do destino e da origem no meio pode ser colorida com a cor de destino, a cor de origem ou não ser colorida.

O número total de combinações é 2 (para o canto superior esquerdo) vezes 2 (para o canto inferior direito) vezes 3 (para o centro) ou 12. Estes são os 12 modos básicos de composição Porter-Duff.

No final de Compositing Digital Images (página 256), Porter e Duff adicionam um 13º modo chamado plus (correspondente ao membro SkiaSharp SKBlendMode.Plus e ao modo W3C Lighter (que não deve ser confundido com o modo W3C Lighten ). Esse Plus modo adiciona as cores de destino e origem, um processo que será descrito com mais detalhes em breve.

Skia adiciona um modo 14 chamado Modulate que é muito semelhante a Plus exceto que as cores de destino e origem são multiplicadas. Pode ser tratado como um modo de mistura Porter-Duff adicional.

Aqui estão os 14 modos Porter-Duff, conforme definido no SkiaSharp. A tabela mostra como eles colorem cada uma das três áreas não em branco no diagrama acima:

Mode Destino Cruzamento Origem
Clear
Src Origem X
Dst X Destino
SrcOver X Origem X
DstOver X Destino X
SrcIn Origem
DstIn Destino
SrcOut X
DstOut X
SrcATop X Origem
DstATop Destino X
Xor X X
Plus X Somar X
Modulate Product

Esses modos de mesclagem são simétricos. A origem e o destino podem ser trocados e todos os modos ainda estão disponíveis.

A convenção de nomenclatura dos modos segue algumas regras simples:

  • Src ou Dst por si só significa que apenas os pixels de origem ou destino estão visíveis.
  • O sufixo Over indica o que é visível na interseção. Ou a origem ou o destino é desenhado "sobre" o outro.
  • O sufixo In significa que apenas a interseção é colorida. A saída é restrita apenas à parte da origem ou destino que está "dentro" da outra.
  • O sufixo Out significa que a interseção não é colorida. A saída é apenas a parte da origem ou destino que está "fora" da interseção.
  • O sufixo ATop é a união de In e Out. Inclui a área onde a origem ou destino está "em cima" do outro.

Observe a diferença com os Plus modos e Modulate . Esses modos estão executando um tipo diferente de cálculo nos pixels de origem e destino. Eles são descritos com mais detalhes em breve.

A página Grade de Porter-Duff mostra todos os 14 modos em uma tela na forma de uma grade. Cada modo é uma instância separada do SKCanvasView. Por essa razão, uma classe é derivada de SKCanvasView named PorterDuffCanvasView. O construtor estático cria dois bitmaps do mesmo tamanho, um com um retângulo acastanhado em sua área superior esquerda e outro com um retâ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);
            }
        }
    }
    ···
}

O construtor de instância tem um parâmetro do tipo SKBlendMode. Ele salva esse parâmetro em um 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);
        }
    }
}

A OnPaintSurface substituição desenha os dois bitmaps. O primeiro é desenhado normalmente:

canvas.DrawBitmap(dstBitmap, rect);

O segundo é desenhado com um SKPaint objeto onde a BlendMode propriedade foi definida para o argumento do construtor:

using (SKPaint paint = new SKPaint())
{
    paint.BlendMode = blendMode;
    canvas.DrawBitmap(srcBitmap, rect, paint);
}

O restante da OnPaintSurface substituição desenha um retângulo ao redor do bitmap para indicar seus tamanhos.

A PorterDuffGridPage classe cria quatorze instâncias de PorterDurffCanvasView, uma para cada membro da blendModes matriz. A ordem dos SKBlendModes membros na matriz é um pouco diferente da tabela para posicionar modos semelhantes adjacentes uns aos outros. As 14 instâncias de PorterDuffCanvasView são organizadas junto com rótulos em um 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;
    }
}

Eis o resultado:

Grade Porter-Duff

Você vai querer se convencer de que a transparência é crucial para o bom funcionamento dos modos de mistura Porter-Duff. A PorterDuffCanvasView classe contém um total de três chamadas para o Canvas.Clear método. Todos eles usam o método sem parâmetros, que define todos os pixels como transparentes:

canvas.Clear();

Tente alterar qualquer uma dessas chamadas para que os pixels sejam definidos como branco opaco:

canvas.Clear(SKColors.White);

Após essa mudança, alguns dos modos de mistura parecerão funcionar, mas outros não. Se você definir o plano de fundo do bitmap de origem como branco, o SrcOver modo não funcionará porque não há pixels transparentes no bitmap de origem para permitir que o destino seja exibido. Se você definir o plano de fundo do bitmap de destino ou da tela como branco, não funcionará DstOver porque o destino não tem pixels transparentes.

Pode haver a tentação de substituir os bitmaps na página Grade de Porter-Duff por chamadas mais simples DrawRect . Isso funcionará para o retângulo de destino, mas não para o retângulo de origem. O retângulo de origem deve abranger mais do que apenas a área de cor azulada. O retângulo de origem deve incluir uma área transparente que corresponda à área colorida do destino. Só então esses modos de mistura funcionarão.

Usando mattes com Porter-Duff

A página Brick-Wall Compositing mostra um exemplo de uma tarefa clássica de composição: uma imagem precisa ser montada a partir de várias peças, incluindo um bitmap com um plano de fundo que precisa ser eliminado. Aqui está o bitmap SeatedMonkey.jpg com o plano de fundo problemático:

Macaco sentado

Em preparação para a composição, um fosco correspondente foi criado, que é outro bitmap que é preto onde você deseja que a imagem apareça e transparente caso contrário. Esse arquivo é chamado SeatedMonkeyMatte.png e está entre os recursos na pasta Mídia no exemplo:

Macaco Fosco Sentado

Este não é um fosco criado habilmente. Idealmente, o fosco deve incluir pixels parcialmente transparentes ao redor da borda dos pixels pretos, e esse fosco não.

O arquivo XAML para a página Brick-Wall Compositing instancia um SKCanvasView e um Button que orienta o usuário pelo processo de composição da imagem 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>

O arquivo code-behind carrega os dois bitmaps necessários e manipula o Clicked evento do Button. Para cada Button clique, o campo é incrementado step e uma nova Text propriedade é definida para o Button. Quando step atinge 5, ele é definido de volta para 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();
        ···
    }
}

Quando o programa é executado pela primeira vez, nada é visível, exceto o Button:

Composição Tijolo-Parede Passo 0

Pressionar o Button once faz com que step aumente para 1 e o manipulador PaintSurface agora exibe 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);
        }
        ···
    }
}

Não há SKPaint nenhum objeto e, portanto, nenhum modo de mistura. O bitmap aparece na parte inferior da tela:

Composição tijolo-parede Passo 1

Pressione o botão Button novamente e step incrementa para 2. Esta é a etapa crucial da exibição do arquivo 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);
            }
        }
        ···
    }
}

O modo de mistura é SKBlendMode.DstIn, o que significa que o destino será preservado em áreas correspondentes a áreas não transparentes da fonte. O restante do retângulo de destino correspondente ao bitmap original torna-se transparente:

Composição de tijolo-parede Passo 2

O plano de fundo foi removido.

O próximo passo é desenhar um retângulo que se assemelhe a uma calçada em que o macaco está sentado. A aparência desta calçada é baseada em uma composição de dois shaders: um sombreador de cor sólida e um sombreador de ruído 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);
            }
        }
        ···
    }
}

Como essa calçada deve ir atrás do macaco, o modo blend é DstOver. O destino aparece somente onde o plano de fundo é transparente:

Composição tijolo-parede Passo 3

O passo final é adicionar uma parede de tijolos. O programa usa o bloco de bitmap de parede de tijolo disponível como a propriedade BrickWallTile estática na AlgorithmicBrickWallPage classe. Uma transformação de conversão é adicionada SKShader.CreateBitmap à chamada para deslocar os blocos de modo que a linha inferior seja um bloco 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 conveniência, a DrawRect chamada exibe esse sombreador em toda a tela, mas o modo limita DstOver a saída apenas à área da tela que ainda é transparente:

Composição de tijolo-parede Passo 4

Obviamente existem outras formas de compor essa cena. Poderia ser construído começando em segundo plano e progredindo para o primeiro plano. Mas usar os modos de mistura oferece mais flexibilidade. Em particular, o uso do fosco permite que o plano de fundo de um bitmap seja excluído da cena composta.

Como você aprendeu no artigo Recorte com caminhos e regiões, a SKCanvas classe define três tipos de recorte, correspondentes aos ClipRectmétodos , ClipPathe ClipRegion . Os modos de mesclagem Porter-Duff adicionam outro tipo de recorte, que permite restringir uma imagem a qualquer coisa que você possa desenhar, incluindo bitmaps. O fosco usado na composição de tijolo-parede define essencialmente uma área de recorte.

Transparência e transições de gradiente

Os exemplos dos modos de mesclagem de Porter-Duff mostrados anteriormente neste artigo envolveram imagens que consistiam em pixels opacos e pixels transparentes, mas não pixels parcialmente transparentes. As funções de modo de mistura também são definidas para esses pixels. A tabela a seguir é uma definição mais formal dos modos de mesclagem Porter-Duff que usa a notação encontrada na Referência Skia SkBlendMode. (Porque SkBlendMode Reference é uma referência Skia, sintaxe C++ é usada.)

Conceitualmente, os componentes vermelho, verde, azul e alfa de cada pixel são convertidos de bytes em números de ponto flutuante no intervalo de 0 a 1. Para o canal alfa, 0 é totalmente transparente e 1 é totalmente opaco

A notação na tabela abaixo usa as seguintes abreviações:

  • Da é o canal alfa de destino
  • Dc é a cor RGB de destino
  • Sa é o canal alfa de origem
  • Sc é a cor RGB de origem

As cores RGB são pré-multiplicadas pelo valor alfa. Por exemplo, se Sc representa vermelho puro, mas Sa é 0x80, então a cor RGB é (0x80, 0, 0). Se Sa for 0, todos os componentes RGB também serão zero.

O resultado é mostrado entre colchetes com o canal alfa e a cor RGB separados por vírgula: [alfa, cor]. Para a cor, o cálculo é realizado separadamente para os componentes vermelho, verde e azul:

Mode Operação
Clear [0, 0]
Src [Sa, Sc]
Dst [Da, Dc]
SrcOver [Sa + Da· (1 – Sá), Sc + Dc· (1 – Sá)
DstOver [Da + Sá· (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 – Sá), 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]

Essas operações são mais fáceis de analisar quando Da e Sa são 0 ou 1. Por exemplo, para o modo padrão SrcOver , se Sa é 0, então Sc também é 0 e o resultado é [Da, Dc], o alfa de destino e a cor. Se Sa é 1, então o resultado é [Sa, Sc], a fonte alfa e cor, ou [1, Sc].

Os Plus modos e Modulate são um pouco diferentes dos outros em que novas cores podem resultar da combinação da origem e do destino. O Plus modo pode ser interpretado com componentes de byte ou componentes de ponto flutuante. Na página Grade de Porter-Duff mostrada anteriormente, a cor de destino é (0xC0, 0x80, 0x00) e a cor de origem é (0x00, 0x80 0xC0). Cada par de componentes é adicionado, mas a soma é fixada em 0xFF. O resultado é a cor (0xC0, 0xFF, 0xC0). Essa é a cor mostrada na interseção.

Para o Modulate modo, os valores RGB devem ser convertidos em ponto flutuante. A cor de destino é (0,75, 0,5, 0) e a origem é (0, 0,5, 0,75). Os componentes RGB são multiplicados juntos, e o resultado é (0, 0,25, 0). Essa é a cor mostrada na interseção na página Grade de Porter-Duff para esse modo.

A página Transparência Porter-Duff permite examinar como os modos de mesclagem Porter-Duff operam em objetos gráficos parcialmente transparentes. O arquivo XAML inclui um Picker com os 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>

O arquivo code-behind preenche dois retângulos do mesmo tamanho usando um gradiente linear. O gradiente de destino é do canto superior direito para o inferior esquerdo. É acastanhado no canto superior direito, mas depois em direção ao centro começa a desaparecer para transparente, e é transparente no canto inferior esquerdo.

O retângulo de origem tem um gradiente do canto superior esquerdo para o inferior direito. O canto superior esquerdo é azulado, mas novamente desaparece para transparente, e é transparente no canto inferior direito.

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 demonstra que os modos de mesclagem Porter-Duff podem ser usados com objetos gráficos diferentes de bitmaps. No entanto, a fonte deve incluir uma área transparente. Esse é o caso aqui porque o gradiente preenche o retângulo, mas parte do gradiente é transparente.

Aqui estão três exemplos:

Transparência Porter-Duff

A configuração do destino e da origem é muito semelhante aos diagramas mostrados na página 255 do artigo original Porter-Duff Compositing Digital Images , mas esta página demonstra que os modos de mistura são bem comportados para áreas de transparência parcial.

Você pode usar gradientes transparentes para alguns efeitos diferentes. Uma possibilidade é o mascaramento, que é semelhante à técnica mostrada na seção Gradientes radiais para mascaramento da página de gradientes circulares SkiaSharp. Grande parte da página Máscara de composição é semelhante ao programa anterior. Ele carrega um recurso de bitmap e determina um retângulo no qual exibi-lo. Um gradiente radial é criado com base em um centro e raio pré-determinados:

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

A diferença desse programa é que o degradê começa com preto no centro e termina com transparência. Ele é exibido no bitmap com um modo de mesclagem de DstIn, que mostra o destino somente nas áreas da origem que não são transparentes.

Após a DrawRect chamada, toda a superfície da tela é transparente, exceto o círculo definido pelo gradiente radial. Uma última chamada é feita:

canvas.DrawColor(SKColors.Pink, SKBlendMode.DstOver);

Todas as áreas transparentes da tela são cor-de-rosa:

Máscara de composição

Você também pode usar modos Porter-Duff e gradientes parcialmente transparentes para transições de uma imagem para outra. A página Transições de gradiente inclui um Slider para indicar um nível de progresso na transição de 0 para 1 e um Picker para escolher o tipo de transição desejado:

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

O arquivo code-behind carrega dois recursos de bitmap para demonstrar a transição. Essas são as mesmas duas imagens usadas na página Bitmap Dissolve anteriormente neste artigo. O código também define uma enumeração com três membros correspondentes a três tipos de gradientes — linear, radial e de varredura. Esses valores são carregados no 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();
    }
    ···
}

O arquivo code-behind cria três SKPaint objetos. O paint0 objeto não usa um modo de mesclagem. Esse objeto de pintura é usado para desenhar um retângulo com um gradiente que vai de preto a transparente, conforme indicado na colors matriz. A positions matriz é baseada na posição do , mas ajustada Sliderum pouco. Se o Slider estiver em seu mínimo ou máximo, os progress valores serão 0 ou 1 e um dos dois bitmaps deverá estar totalmente visível. A positions matriz deve ser definida de acordo com esses valores.

Se o progress valor for 0, a positions matriz conterá os valores -0.1 e 0. O SkiaSharp ajustará esse primeiro valor para ser igual a 0, o que significa que o gradiente é preto apenas em 0 e transparente caso contrário. Quando progress é 0,5, a matriz contém os valores 0,45 e 0,55. O gradiente é preto de 0 a 0,45, depois faz a transição para transparente e é totalmente transparente de 0,55 a 1. Quando progress é 1, a matriz é 1 e 1.1, o positions que significa que o gradiente é preto de 0 a 1.

As colors matrizes e são usadas nos três métodos que SKShader criam um gradienteposition. Apenas um desses sombreadores é criado com base na Picker seleção:

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

Esse gradiente é exibido no retângulo sem um modo de mesclagem. Após essa DrawRect chamada, a tela simplesmente contém um gradiente de preto para transparente. A quantidade de preto aumenta com valores mais altos Slider .

Nas quatro instruções finais do PaintSurface manipulador, os dois bitmaps são exibidos. O SrcOut modo de mesclagem significa que o primeiro bitmap é exibido somente nas áreas transparentes do plano de fundo. O DstOver modo para o segundo bitmap significa que o segundo bitmap é exibido somente nas áreas onde o primeiro bitmap não é exibido.

As capturas de tela a seguir mostram os três tipos de transições diferentes, cada um na marca de 50%:

Transições de gradiente