Partilhar via


SkiaSharp ruído e composição

Gráficos vetoriais simples tendem a parecer antinaturais. As linhas retas, as curvas suaves e as cores sólidas não se assemelham às imperfeições dos objetos do mundo real. Enquanto trabalhava nos gráficos gerados por computador para o filme Tron de 1982, o cientista da computação Ken Perlin começou a desenvolver algoritmos que usavam processos aleatórios para dar a essas imagens texturas mais realistas. Em 1997, Ken Perlin ganhou um Oscar por Realização Técnica. Seu trabalho passou a ser conhecido como Perlin noise, e é apoiado em SkiaSharp. Veja um exemplo:

Amostra de ruído Perlin

Como você pode ver, cada pixel não é um valor de cor aleatório. A continuidade de pixel para pixel resulta em formas aleatórias.

O suporte de ruído Perlin no Skia é baseado em uma especificação W3C para CSS e SVG. A seção 8.20 do Módulo de Efeitos de Filtro Nível 1 inclui os algoritmos de ruído Perlin subjacentes no código C.

Explorando o ruído de Perlin

A SKShader classe define dois métodos estáticos diferentes para gerar ruído Perlin: CreatePerlinNoiseFractalNoise e CreatePerlinNoiseTurbulence. Os parâmetros são idênticos:

public static SkiaSharp CreatePerlinNoiseFractalNoise (float baseFrequencyX, float baseFrequencyY, int numOctaves, float seed);

public static SkiaSharp.SKShader CreatePerlinNoiseTurbulence (float baseFrequencyX, float baseFrequencyY, int numOctaves, float seed);

Ambos os métodos também existem em versões sobrecarregadas com um parâmetro adicional SKPointI . A seção Tiling Perlin noise discute essas sobrecargas.

Os dois baseFrequency argumentos são valores positivos definidos na documentação do SkiaSharp como variando de 0 a 1, mas também podem ser definidos com valores mais altos. Quanto maior o valor, maior a mudança na imagem aleatória nas direções horizontal e vertical.

O numOctaves valor é um inteiro de 1 ou superior. Relaciona-se a um fator de iteração nos algoritmos. Cada oitava adicional contribui com um efeito que é metade da oitava anterior, de modo que o efeito diminui com valores de oitava mais altos.

O seed parâmetro é o ponto de partida para o gerador de números aleatórios. Embora especificado como um valor de ponto flutuante, a fração é truncada antes de ser usada e 0 é o mesmo que 1.

A página Perlin Noise no exemplo permite que você experimente vários valores dos baseFrequency argumentos e numOctaves . Aqui está o arquivo XAML:

<?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.Views.Forms;assembly=SkiaSharp.Views.Forms"
             x:Class="SkiaSharpFormsDemos.Effects.PerlinNoisePage"
             Title="Perlin Noise">

    <StackLayout>
        <skia:SKCanvasView x:Name="canvasView"
                           VerticalOptions="FillAndExpand"
                           PaintSurface="OnCanvasViewPaintSurface" />

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

        <Label x:Name="baseFrequencyXText"
               HorizontalTextAlignment="Center" />

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

        <Label x:Name="baseFrequencyYText"
               HorizontalTextAlignment="Center" />

        <StackLayout Orientation="Horizontal"
                     HorizontalOptions="Center"
                     Margin="10">

            <Label Text="{Binding Source={x:Reference octavesStepper},
                                  Path=Value,
                                  StringFormat='Number of Octaves: {0:F0}'}"
                   VerticalOptions="Center" />

            <Stepper x:Name="octavesStepper"
                     Minimum="1"
                     ValueChanged="OnStepperValueChanged" />
        </StackLayout>
    </StackLayout>
</ContentPage>

Ele usa dois Slider modos de exibição para os dois baseFrequency argumentos. Para expandir o intervalo dos valores mais baixos, os controles deslizantes são logarítmicos. O arquivo code-behind calcula os argumentos para os SKShadermétodos de potências dos Slider valores. As Label exibições exibem os valores calculados:

float baseFreqX = (float)Math.Pow(10, baseFrequencyXSlider.Value - 4);
baseFrequencyXText.Text = String.Format("Base Frequency X = {0:F4}", baseFreqX);

float baseFreqY = (float)Math.Pow(10, baseFrequencyYSlider.Value - 4);
baseFrequencyYText.Text = String.Format("Base Frequency Y = {0:F4}", baseFreqY);

Um Slider valor de 1 corresponde a 0,001, um Slider valor de 2 corresponde a 0,01, um Slider valor de 3 corresponde a 0,1 e um Slider valor de 4 corresponde a 1.

Aqui está o arquivo code-behind que inclui esse código:

public partial class PerlinNoisePage : ContentPage
{
    public PerlinNoisePage()
    {
        InitializeComponent();
    }

    void OnSliderValueChanged(object sender, ValueChangedEventArgs args)
    {
        canvasView.InvalidateSurface();
    }

    void OnStepperValueChanged(object sender, ValueChangedEventArgs args)
    {
        canvasView.InvalidateSurface();
    }

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

        canvas.Clear();

        // Get values from sliders and stepper
        float baseFreqX = (float)Math.Pow(10, baseFrequencyXSlider.Value - 4);
        baseFrequencyXText.Text = String.Format("Base Frequency X = {0:F4}", baseFreqX);

        float baseFreqY = (float)Math.Pow(10, baseFrequencyYSlider.Value - 4);
        baseFrequencyYText.Text = String.Format("Base Frequency Y = {0:F4}", baseFreqY);

        int numOctaves = (int)octavesStepper.Value;

        using (SKPaint paint = new SKPaint())
        {
            paint.Shader =
                SKShader.CreatePerlinNoiseFractalNoise(baseFreqX,
                                                       baseFreqY,
                                                       numOctaves,
                                                       0);

            SKRect rect = new SKRect(0, 0, info.Width, info.Height / 2);
            canvas.DrawRect(rect, paint);

            paint.Shader =
                SKShader.CreatePerlinNoiseTurbulence(baseFreqX,
                                                     baseFreqY,
                                                     numOctaves,
                                                     0);

            rect = new SKRect(0, info.Height / 2, info.Width, info.Height);
            canvas.DrawRect(rect, paint);
        }
    }
}

Aqui está o programa em execução em dispositivos iOS, Android e Plataforma Universal do Windows (UWP). O ruído fractal é mostrado na metade superior da tela. O ruído de turbulência está na metade inferior:

Ruído Perlin

Os mesmos argumentos sempre produzem o mesmo padrão que começa no canto superior esquerdo. Essa consistência é óbvia quando você ajusta a largura e a altura da janela UWP. À medida que o Windows 10 redesenha a tela, o padrão na metade superior da tela permanece o mesmo.

O padrão de ruído incorpora vários graus de transparência. A transparência se torna óbvia se você definir uma cor na canvas.Clear() chamada. Essa cor se torna proeminente no padrão. Você também verá esse efeito na seção Combinando vários sombreadores.

Esses padrões de ruído Perlin raramente são usados por eles mesmos. Muitas vezes eles são submetidos a modos de mistura e filtros de cores discutidos em artigos posteriores.

Ruído de azulejo Perlin

Os dois métodos estáticos SKShader para criar ruído Perlin também existem em versões de sobrecarga. As CreatePerlinNoiseFractalNoise sobrecargas e CreatePerlinNoiseTurbulence têm um parâmetro adicional SKPointI :

public static SKShader CreatePerlinNoiseFractalNoise (float baseFrequencyX, float baseFrequencyY, int numOctaves, float seed, SKPointI tileSize);

public static SKShader CreatePerlinNoiseTurbulence (float baseFrequencyX, float baseFrequencyY, int numOctaves, float seed, SKPointI tileSize);

A SKPointI estrutura é a versão inteira da estrutura familiar SKPoint . SKPointI define X e Y propriedades do tipo int em vez de float.

Esses métodos criam um padrão de repetição do tamanho especificado. Em cada bloco, a borda direita é igual à borda esquerda e a borda superior é igual à borda inferior. Essa característica é demonstrada na página Tiled Perlin Noise . O arquivo XAML é semelhante ao exemplo anterior, mas só tem uma Stepper exibição para alterar o seed argumento:

<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.TiledPerlinNoisePage"
             Title="Tiled Perlin Noise">

    <StackLayout>
        <skia:SKCanvasView x:Name="canvasView"
                           VerticalOptions="FillAndExpand"
                           PaintSurface="OnCanvasViewPaintSurface" />

        <StackLayout Orientation="Horizontal"
                     HorizontalOptions="Center"
                     Margin="10">

            <Label Text="{Binding Source={x:Reference seedStepper},
                                  Path=Value,
                                  StringFormat='Seed: {0:F0}'}"
                   VerticalOptions="Center" />

            <Stepper x:Name="seedStepper"
                     Minimum="1"
                     ValueChanged="OnStepperValueChanged" />

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

O arquivo code-behind define uma constante para o tamanho do bloco. O PaintSurface manipulador cria um bitmap desse tamanho e um SKCanvas para desenhar nesse bitmap. O SKShader.CreatePerlinNoiseTurbulence método cria um sombreador com esse tamanho de bloco. Esse sombreador é desenhado no bitmap:

public partial class TiledPerlinNoisePage : ContentPage
{
    const int TILE_SIZE = 200;

    public TiledPerlinNoisePage()
    {
        InitializeComponent();
    }

    void OnStepperValueChanged(object sender, ValueChangedEventArgs args)
    {
        canvasView.InvalidateSurface();
    }

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

        canvas.Clear();

        // Get seed value from stepper
        float seed = (float)seedStepper.Value;

        SKRect tileRect = new SKRect(0, 0, TILE_SIZE, TILE_SIZE);

        using (SKBitmap bitmap = new SKBitmap(TILE_SIZE, TILE_SIZE))
        {
            using (SKCanvas bitmapCanvas = new SKCanvas(bitmap))
            {
                bitmapCanvas.Clear();

                // Draw tiled turbulence noise on bitmap
                using (SKPaint paint = new SKPaint())
                {
                    paint.Shader = SKShader.CreatePerlinNoiseTurbulence(
                                        0.02f, 0.02f, 1, seed,
                                        new SKPointI(TILE_SIZE, TILE_SIZE));

                    bitmapCanvas.DrawRect(tileRect, paint);
                }
            }

            // Draw tiled bitmap shader on canvas
            using (SKPaint paint = new SKPaint())
            {
                paint.Shader = SKShader.CreateBitmap(bitmap,
                                                     SKShaderTileMode.Repeat,
                                                     SKShaderTileMode.Repeat);
                canvas.DrawRect(info.Rect, paint);
            }

            // Draw rectangle showing tile
            using (SKPaint paint = new SKPaint())
            {
                paint.Style = SKPaintStyle.Stroke;
                paint.Color = SKColors.Black;
                paint.StrokeWidth = 2;

                canvas.DrawRect(tileRect, paint);
            }
        }
    }
}

Depois que o bitmap tiver sido criado, outro SKPaint objeto será usado para criar um padrão de bitmap lado a lado chamando SKShader.CreateBitmap. Observe os dois argumentos de SKShaderTileMode.Repeat:

paint.Shader = SKShader.CreateBitmap(bitmap,
                                     SKShaderTileMode.Repeat,
                                     SKShaderTileMode.Repeat);

Este sombreador é usado para cobrir a tela. Finalmente, outro SKPaint objeto é usado para traçar um retângulo mostrando o tamanho do bitmap original.

Somente o seed parâmetro é selecionável na interface do usuário. Se o mesmo seed padrão for usado em cada plataforma, eles mostrariam o mesmo padrão. Valores diferentes seed resultam em padrões diferentes:

Ruído Perlin Lazilado

O padrão quadrado de 200 pixels no canto superior esquerdo flui perfeitamente para os outros blocos.

Combinando vários sombreadores

A SKShader classe inclui um CreateColor método que cria um sombreador com uma cor sólida especificada. Esse sombreador não é muito útil por si só porque você pode simplesmente definir essa cor para a Color SKPaint propriedade do objeto e definir a Shader propriedade como null.

Este CreateColor método torna-se útil em outro método que SKShader define. Este método é CreateCompose, que combina dois sombreadores. Aqui está a sintaxe:

public static SKShader CreateCompose (SKShader dstShader, SKShader srcShader);

O srcShader (sombreador de origem) é efetivamente desenhado sobre o dstShader (sombreador de destino). Se o sombreador de origem for uma cor sólida ou um gradiente sem transparência, o sombreador de destino será completamente obscurecido.

Um sombreador de ruído Perlin contém transparência. Se esse sombreador for a origem, o sombreador de destino será exibido através das áreas transparentes.

A página Ruído Perlin Composto tem um arquivo XAML praticamente idêntico à primeira página Ruído Perlin. O arquivo code-behind também é semelhante. Mas a página Perlin Noise original define a Shader propriedade de SKPaint para o sombreador retornado da estática CreatePerlinNoiseFractalNoise e CreatePerlinNoiseTurbulence métodos. Esta página Ruído Perlin Composto pede CreateCompose um sombreador combinado. O destino é um sombreador azul sólido criado usando CreateColor. A fonte é um sombreador de ruído Perlin:

public partial class ComposedPerlinNoisePage : ContentPage
{
    public ComposedPerlinNoisePage()
    {
        InitializeComponent();
    }

    void OnSliderValueChanged(object sender, ValueChangedEventArgs args)
    {
        canvasView.InvalidateSurface();
    }

    void OnStepperValueChanged(object sender, ValueChangedEventArgs args)
    {
        canvasView.InvalidateSurface();
    }

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

        canvas.Clear();

        // Get values from sliders and stepper
        float baseFreqX = (float)Math.Pow(10, baseFrequencyXSlider.Value - 4);
        baseFrequencyXText.Text = String.Format("Base Frequency X = {0:F4}", baseFreqX);

        float baseFreqY = (float)Math.Pow(10, baseFrequencyYSlider.Value - 4);
        baseFrequencyYText.Text = String.Format("Base Frequency Y = {0:F4}", baseFreqY);

        int numOctaves = (int)octavesStepper.Value;

        using (SKPaint paint = new SKPaint())
        {
            paint.Shader = SKShader.CreateCompose(
                SKShader.CreateColor(SKColors.Blue),
                SKShader.CreatePerlinNoiseFractalNoise(baseFreqX,
                                                       baseFreqY,
                                                       numOctaves,
                                                       0));

            SKRect rect = new SKRect(0, 0, info.Width, info.Height / 2);
            canvas.DrawRect(rect, paint);

            paint.Shader = SKShader.CreateCompose(
                SKShader.CreateColor(SKColors.Blue),
                SKShader.CreatePerlinNoiseTurbulence(baseFreqX,
                                                     baseFreqY,
                                                     numOctaves,
                                                     0));

            rect = new SKRect(0, info.Height / 2, info.Width, info.Height);
            canvas.DrawRect(rect, paint);
        }
    }
}

O sombreador de ruído fractal está na parte superior; O sombreador de turbulência está na parte inferior:

Ruído Perlin Composto

Observe o quanto esses sombreadores são mais azuis do que os exibidos pela página Perlin Noise . A diferença ilustra a quantidade de transparência nos sombreadores de ruído.

Há também uma sobrecarga do CreateCompose método:

public static SKShader CreateCompose (SKShader dstShader, SKShader srcShader, SKBlendMode blendMode);

O parâmetro final é um membro da SKBlendMode enumeração, uma enumeração com 29 membros que é discutida na próxima série de artigos sobre os modos de composição e mistura do SkiaSharp.