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:
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 SKShader
mé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:
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:
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:
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.