Freigeben über


SkiaSharp noise and composing

Einfache Vektorgrafiken neigen dazu, unnatürlich zu aussehen. Die geraden Linien, glatten Kurven und Volltonfarben ähneln nicht den Unvollkommenheiten von realen Objekten. Während der Arbeit an den computergenerierten Grafiken für den Film Tron 1982 begann der Informatiker Ken Perlin mit der Entwicklung von Algorithmen, die zufällige Prozesse verwendeten, um diesen Bildern realistischere Texturen zu verleihen. 1997 gewann Ken Perlin einen Academy Award für technische Leistungen. Seine Arbeit ist bekannt als Perlin Noise, und es wird in SkiaSharp unterstützt. Ein Beispiel:

Perlin-Rauschbeispiel

Wie Sie sehen können, ist jedes Pixel kein zufälliger Farbwert. Die Kontinuität von Pixel zu Pixel führt zu zufälligen Formen.

Die Unterstützung von Perlin-Rauschen in Skia basiert auf einer W3C-Spezifikation für CSS und SVG. Abschnitt 8.20 des Filtereffektemoduls Ebene 1 enthält die zugrunde liegenden Perlin-Rauschalgorithmen im C-Code.

Erkunden von Perlin-Rauschen

Die SKShader Klasse definiert zwei verschiedene statische Methoden zum Generieren von Perlin-Rauschen: CreatePerlinNoiseFractalNoise und CreatePerlinNoiseTurbulence. Die Parameter sind identisch:

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

Beide Methoden sind auch in überladenen Versionen mit einem zusätzlichen SKPointI Parameter vorhanden. Im Abschnitt "Tiling Perlin-Rauschen " werden diese Überladungen erläutert.

Die beiden baseFrequency Argumente sind positive Werte, die in der SkiaSharp-Dokumentation von 0 bis 1 definiert sind, aber sie können auch auf höhere Werte festgelegt werden. Je höher der Wert ist, desto größer ist die Änderung des zufälligen Bilds in horizontaler und vertikaler Richtung.

Der numOctaves Wert ist eine ganze Zahl von 1 oder höher. Sie bezieht sich auf einen Iterationsfaktor in den Algorithmen. Jede zusätzliche Oktave trägt einen Effekt bei, der die Hälfte der vorherigen Oktave ist, sodass der Effekt mit höheren Oktavwerten abnimmt.

Der seed Parameter ist der Ausgangspunkt für den Zufallszahlengenerator. Obwohl als Gleitkommawert angegeben, wird der Bruch vor der Verwendung abgeschnitten, und 0 ist identisch mit 1.

Auf der Seite "Perlin-Rauschen " im Beispiel können Sie mit verschiedenen Werten der baseFrequency Argumente numOctaves experimentieren. Dies ist die XAML-Datei:

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

Es verwendet zwei Slider Ansichten für die beiden baseFrequency Argumente. Um den Bereich der niedrigeren Werte zu erweitern, sind die Schieberegler logarithmisch. Die CodeBehind-Datei berechnet die Argumente für die Methoden aus den SKShaderMächten der Slider Werte. In Label den Ansichten werden die berechneten Werte angezeigt:

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

Ein Slider Wert von 1 entspricht 0,001, ein Slider Wert os 2 entspricht 0,01, ein Slider Wert von 3 entspricht 0,1 und ein Slider Wert von 4 entspricht 1.

Dies ist die CodeBehind-Datei, die diesen Code enthält:

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

Hier sehen Sie das Programm, das auf iOS-, Android- und Universelle Windows-Plattform-Geräten (UWP) ausgeführt wird. Das fraktale Rauschen wird in der oberen Hälfte des Zeichenbereichs angezeigt. Der Turbulenzenrausch liegt in der unteren Hälfte:

Perlin-Rauschen

Dieselben Argumente erzeugen immer das gleiche Muster, das in der oberen linken Ecke beginnt. Diese Konsistenz ist offensichtlich, wenn Sie die Breite und Höhe des UWP-Fensters anpassen. Während Windows 10 den Bildschirm neu gezeichnet, wird das Muster in der oberen Hälfte des Zeichenbereichs erneut Standard identisch.

Das Rauschmuster enthält verschiedene Transparenzgrade. Die Transparenz wird offensichtlich, wenn Sie eine Farbe im canvas.Clear() Anruf festlegen. Diese Farbe wird im Muster hervorgehoben. Dieser Effekt wird auch im Abschnitt "Kombinieren mehrerer Shader" angezeigt.

Diese Perlin-Rauschmuster werden selten von sich selbst verwendet. Häufig unterliegen sie Mischmodi und Farbfiltern, die in späteren Artikeln erläutert werden.

Tiling Perlin-Rauschen

Die beiden statischen SKShader Methoden zum Erstellen von Perlin-Rauschen sind auch in Überladungsversionen vorhanden. Die CreatePerlinNoiseFractalNoise Überladungen CreatePerlinNoiseTurbulence weisen einen zusätzlichen SKPointI Parameter auf:

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

Die SKPointI Struktur ist die ganzzahlige Version der vertrauten SKPoint Struktur. SKPointIdefiniert und Y Eigenschaften des Typs intX und nicht .float

Diese Methoden erstellen ein wiederholtes Muster der angegebenen Größe. In jeder Kachel entspricht der rechte Rand dem linken Rand, und der obere Rand entspricht dem unteren Rand. Diese Eigenschaft wird auf der Seite "Nebeneinander angeordnetes Perlin-Rauschen " veranschaulicht. Die XAML-Datei ähnelt dem vorherigen Beispiel, hat aber nur eine Stepper Ansicht zum Ändern des seed Arguments:

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

Die CodeBehind-Datei definiert eine Konstante für die Kachelgröße. Der PaintSurface Handler erstellt eine Bitmap dieser Größe und eine SKCanvas zum Zeichnen in diese Bitmap. Die SKShader.CreatePerlinNoiseTurbulence Methode erstellt einen Shader mit dieser Kachelgröße. Dieser Shader wird auf der Bitmap gezeichnet:

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

Nachdem die Bitmap erstellt wurde, wird ein weiteres SKPaint Objekt verwendet, um ein nebeneinander angeordnetes Bitmapmuster durch Aufrufen SKShader.CreateBitmapzu erstellen. Beachten Sie die beiden Argumente von SKShaderTileMode.Repeat:

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

Dieser Shader wird verwendet, um den Zeichenbereich abzudecken. Schließlich wird ein weiteres SKPaint Objekt verwendet, um ein Rechteck mit der Größe der ursprünglichen Bitmap zu strichen.

Nur der seed Parameter kann über die Benutzeroberfläche ausgewählt werden. Wenn dasselbe seed Muster auf jeder Plattform verwendet wird, würden sie dasselbe Muster anzeigen. Unterschiedliche seed Werte führen zu unterschiedlichen Mustern:

Nebeneinander angeordnetes Rauschen

Das quadratische Muster von 200 Pixeln in der oberen linken Ecke fließt nahtlos in die anderen Kacheln.

Kombinieren mehrerer Shader

Die SKShader Klasse enthält eine CreateColor Methode, mit der ein Shader mit einer angegebenen Volltonfarbe erstellt wird. Dieser Shader ist nicht sehr nützlich, da Sie diese Farbe einfach auf die Color Eigenschaft des SKPaint Objekts festlegen und die Shader Eigenschaft auf NULL festlegen können.

Diese CreateColor Methode wird in einer anderen Methode nützlich, die SKShader definiert wird. Diese Methode ist CreateCompose, die zwei Shader kombiniert. Dies ist die Syntax:

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

Der srcShader (Quell-Shader) wird effektiv über dem dstShader (Ziel-Shader) gezeichnet. Wenn der Quell-Shader eine Volltonfarbe oder ein Farbverlauf ohne Transparenz ist, wird der Ziel-Shader vollständig verdeckt.

Ein Perlin-Rausch-Shader enthält Transparenz. Wenn dieser Shader die Quelle ist, wird der Ziel-Shader durch die transparenten Bereiche angezeigt.

Die Seite "Zusammengesetztes Perlin-Rauschen " verfügt über eine XAML-Datei, die nahezu identisch mit der ersten Perlin Noise-Seite ist. Die CodeBehind-Datei ist ebenfalls ähnlich. Die ursprüngliche Perlin Noise-Seite legt jedoch die Shader Eigenschaft des SKPaint Shaders fest, der von der statischen CreatePerlinNoiseFractalNoise Und CreatePerlinNoiseTurbulence Methoden zurückgegeben wird. Diese zusammengesetzte Perlin-Rauschseite ruft einen Kombinations-Shader auf CreateCompose . Das Ziel ist ein einfarbiger blauer Shader, der mit CreateColor. Die Quelle ist ein Perlin-Rausch-Shader:

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

Der Fraktal-Rausch-Shader befindet sich oben; Der Turbulenzenshader befindet sich unten:

Zusammengesetztes Perlin-Rauschen

Beachten Sie, wie viel Blauer diese Shader sind als die von der Perlin Noise-Seite angezeigten. Der Unterschied veranschaulicht die Transparenz in den Rausch-Shadern.

Es gibt auch eine Überladung der CreateCompose Methode:

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

Der letzte Parameter ist ein Element der SKBlendMode Enumeration, eine Aufzählung mit 29 Membern, die in der nächsten Reihe von Artikeln über SkiaSharp Compositing und Blend-Modi behandelt wird.