Шум и создание skiaSharp

Download Sample Скачайте пример

Простая векторная графика, как правило, выглядит неестественно. Прямые линии, гладкие кривые и сплошные цвета не похожи на несовершенства реальных объектов. Работая на созданной компьютером графике для фильма Tron 1982 года, компьютерный ученый Кен Перлин начал разрабатывать алгоритмы, которые использовали случайные процессы, чтобы дать этим изображениям более реалистичные текстуры. В 1997 году Кен Перлин выиграл премию Академии за техническое достижение. Его работа стала известной как перлин шум, и она поддерживается в SkiaSharp. Приведем пример:

Perlin Noise sample

Как видно, каждый пиксель не является случайным значением цвета. Непрерывность от пикселя до пикселя приводит к случайным фигурам.

Поддержка шума Perlin в Skia основана на спецификации W3C для CSS и SVG. Раздел 8.20 модуля "Эффекты фильтра" уровня 1 включает базовые алгоритмы шума Perlin в коде C.

Изучение шума Perlin

Класс SKShader определяет два разных статических метода для создания шума Perlin: CreatePerlinNoiseFractalNoise и CreatePerlinNoiseTurbulence. Параметры идентичны:

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

Оба метода также существуют в перегруженных версиях с дополнительным SKPointI параметром. В разделе "Тилинг Перлин шум " обсуждаются эти перегрузки.

Два baseFrequency аргумента являются положительными значениями, определенными в документации SkiaSharp в диапазоне от 0 до 1, но их также можно задать для более высоких значений. Чем выше значение, тем больше изменение случайного изображения в горизонтальных и вертикальных направлениях.

Значение numOctaves — целое число 1 или выше. Он относится к коэффициенту итерации в алгоритмах. Каждый дополнительный октава способствует эффекту, который составляет половину предыдущего октава, поэтому эффект уменьшается с более высокими октавами значений.

Параметр seed является отправной точкой для генератора случайных чисел. Хотя оно указано как значение с плавающей запятой, дробь усечена до его использования, а 0 совпадает с значением 1.

Страница "Перлин шум" в примере SkiaSharpFormsDemos позволяет экспериментировать с различными значениями baseFrequency и numOctaves аргументами. Ниже приведен 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>

В нем используются два представления для двух SliderbaseFrequency аргументов. Чтобы расширить диапазон нижних значений, ползунки являются логарифмическими. Файл программной части вычисляет аргументы SKShaderдля методов из полномочий значений Slider . Представления Label отображают вычисляемые значения:

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

Slider Значение 1 соответствует 0,001, Slider значение os 2 соответствует 0,01, Slider значения 3 соответствуют 0,1, а Slider значение 4 соответствует 1.

Ниже приведен файл программной части, включающий этот код:

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

Ниже приведена программа, запущенная на устройствах iOS, Android и универсальная платформа Windows (UWP). Фрактальный шум показан в верхней половине холста. Шум турбулентности находится в нижней половине:

Perlin Noise

Одни и те же аргументы всегда создают тот же шаблон, который начинается в левом верхнем углу. Эта согласованность очевидна при настройке ширины и высоты окна UWP. Так как Windows 10 перерисовывает экран, шаблон в верхней половине холста остается неизменным.

Шаблон шума включает различные степени прозрачности. Прозрачность становится очевидной, если задать цвет в вызове canvas.Clear() . Этот цвет становится заметным в шаблоне. Вы также увидите этот эффект в разделе "Объединение нескольких шейдеров".

Эти шаблоны шума Perlin редко используются сами по себе. Часто они подвергаются смешиваниям режимов и цветовых фильтров, рассмотренных в последующих статьях.

Шум Перлин

Два статических SKShader метода для создания шума Perlin также существуют в версиях перегрузки. У CreatePerlinNoiseFractalNoise и CreatePerlinNoiseTurbulence перегрузки есть дополнительный 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);

Структура SKPointI представляет собой целочисленную версию знакомой SKPoint структуры. SKPointIX определяет и Y свойства типаint, а не float.

Эти методы создают повторяющийся шаблон указанного размера. На каждой плитке правый край совпадает с левым краем, а верхний край совпадает с нижним краем. Эта характеристика показана на странице "Плитка перлин шум ". XAML-файл похож на предыдущий пример, но он имеет Stepper только представление для изменения аргумента seed :

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

Файл программной части определяет константу для размера плитки. Обработчик PaintSurface создает растровое изображение этого размера и для рисования в этом растровом SKCanvas изображении. Метод SKShader.CreatePerlinNoiseTurbulence создает шейдер с таким размером плитки. Этот шейдер рисуется на растровом рисунке:

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

После создания растрового изображения другой SKPaint объект используется для создания шаблона растрового изображения на плитках путем вызова SKShader.CreateBitmap. Обратите внимание на два аргумента SKShaderTileMode.Repeat:

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

Этот шейдер используется для покрытия холста. Наконец, другой SKPaint объект используется для росчерка прямоугольника, отображающего размер исходного растрового изображения.

seed Из пользовательского интерфейса можно выбрать только параметр. Если на каждой платформе используется один и тот же seed шаблон, он будет отображать один и тот же шаблон. Различные seed значения приводят к разным шаблонам:

Tiled Perlin Noise

Шаблон квадрата 200 пикселей в левом верхнем углу легко перемещается на другие плитки.

Объединение нескольких шейдеров

Класс SKShader включает CreateColor метод, который создает шейдер с указанным сплошным цветом. Этот шейдер не очень полезен сам по себе, так как вы можете просто задать этот цвет свойству Color объекта и задать Shader для свойства SKPaint значение NULL.

Этот CreateColor метод становится полезным в другом методе, который SKShader определяет. Этот метод состоит CreateComposeиз двух шейдеров. Ниже приведен синтаксис:

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

( srcShader исходный dstShader шейдер) фактически рисуется на вершине (шейдер назначения). Если исходный шейдер является сплошным цветом или градиентом без прозрачности, шейдер назначения будет полностью скрыт.

Шейдер шума Perlin содержит прозрачность. Если этот шейдер является источником, шейдер назначения будет отображаться через прозрачные области.

На странице "Составный перлин шум " есть XAML-файл, который практически идентичен первой странице шума Perlin. Файл программной части также аналогичен. Но исходная страница "Шум Perlin" задает Shader свойство SKPaint шейдера, возвращаемое статическим CreatePerlinNoiseFractalNoise и CreatePerlinNoiseTurbulence методами. Эта страница "Составной перлин шум " вызывает CreateCompose сочетание шейдера. Назначение — это твердый синий шейдер, созданный с помощью CreateColor. Источник — это шейдер шума 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);
        }
    }
}

Фрактальный шумовой шейдер находится на вершине; Шейдер турбулентности находится внизу:

Composed Perlin Noise

Обратите внимание, сколько синих этих шейдеров больше, чем те, которые отображаются страницей "Шум Перлин". Разница иллюстрирует объем прозрачности в шумовых шейдерах.

Существует также перегрузка CreateCompose метода:

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

Окончательный параметр является членом SKBlendMode перечисления, перечислением с 29 элементами, которые обсуждаются в следующей серии статей о режимах создания и смешивания SkiaSharp.