Поделиться через


Настойка растровой карты SkiaSharp

Как вы видели в двух предыдущих статьях, SKShader класс может создавать линейные или циклические градиенты. В этой статье рассматривается SKShader объект, использующий растровое изображение для плитки области. Растровое изображение может повторяться горизонтально и вертикально либо в исходной ориентации, либо в альтернативной горизонтальной и вертикальной ориентации. Перевернутый элемент позволяет избежать разрывов между плитками:

Пример точечных карт

SKShader.CreateBitmap Статический метод, создающий этот шейдер, имеет SKBitmap параметр и два члена SKShaderTileMode перечисления:

public static SKShader CreateBitmap (SKBitmap src, SKShaderTileMode tmx, SKShaderTileMode tmy)

Два параметра указывают режимы, используемые для горизонтальной наливки и вертикальной навески. Это то же SKShaderTileMode перечисление, которое также используется с методами градиента.

Перегрузка CreateBitmapSKMatrix включает аргумент для выполнения преобразования на фрагментированных растровых изображениях:

public static SKShader CreateBitmap (SKBitmap src, SKShaderTileMode tmx, SKShaderTileMode tmy, SKMatrix localMatrix)

В этой статье содержится несколько примеров использования этого преобразования матрицы с плитками растровых изображений.

Изучение режимов плитки

Первая программа в разделе Bitmap Tiling страницы шейдеров и других эффектов примера демонстрирует эффекты двух SKShaderTileMode аргументов. Файл XAML режимов переворачивания растровых рисунков создает SKCanvasView экземпляры и два Picker представления, которые позволяют выбрать значение горизонтальной и вертикальной SKShaderTilerMode олицетворения. Обратите внимание, что массив SKShaderTileMode элементов определен в Resources разделе:

<?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;assembly=SkiaSharp"
             xmlns:skiaforms="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
             x:Class="SkiaSharpFormsDemos.Effects.BitmapTileFlipModesPage"
             Title="Bitmap Tile Flip Modes">

    <ContentPage.Resources>
        <x:Array x:Key="tileModes"
                 Type="{x:Type skia:SKShaderTileMode}">
            <x:Static Member="skia:SKShaderTileMode.Clamp" />
            <x:Static Member="skia:SKShaderTileMode.Repeat" />
            <x:Static Member="skia:SKShaderTileMode.Mirror" />
        </x:Array>
    </ContentPage.Resources>

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

        <Picker x:Name="xModePicker"
                Title="Tile X Mode"
                Margin="10, 0"
                ItemsSource="{StaticResource tileModes}"
                SelectedIndex="0"
                SelectedIndexChanged="OnPickerSelectedIndexChanged" />

        <Picker x:Name="yModePicker"
                Title="Tile Y Mode"
                Margin="10, 10"
                ItemsSource="{StaticResource tileModes}"
                SelectedIndex="0"
                SelectedIndexChanged="OnPickerSelectedIndexChanged" />

    </StackLayout>
</ContentPage>

Конструктор файла программной части загружается в ресурс растрового изображения, где показана обезьяна сидящая. Он сначала обрезает изображение с помощью ExtractSubset метода SKBitmap , чтобы голова и ноги касались краев растрового изображения. Затем конструктор использует Resize метод для создания другого растрового изображения половины размера. Эти изменения делают растровое изображение немного более подходящим для нарядки:

public partial class BitmapTileFlipModesPage : ContentPage
{
    SKBitmap bitmap;

    public BitmapTileFlipModesPage ()
    {
        InitializeComponent ();

        SKBitmap origBitmap = BitmapExtensions.LoadBitmapResource(
            GetType(), "SkiaSharpFormsDemos.Media.SeatedMonkey.jpg");

        // Define cropping rect
        SKRectI cropRect = new SKRectI(5, 27, 296, 260);

        // Get the cropped bitmap
        SKBitmap croppedBitmap = new SKBitmap(cropRect.Width, cropRect.Height);
        origBitmap.ExtractSubset(croppedBitmap, cropRect);

        // Resize to half the width and height
        SKImageInfo info = new SKImageInfo(cropRect.Width / 2, cropRect.Height / 2);
        bitmap = croppedBitmap.Resize(info, SKBitmapResizeMethod.Box);
    }

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

        // Get tile modes from Pickers
        SKShaderTileMode xTileMode =
            (SKShaderTileMode)(xModePicker.SelectedIndex == -1 ?
                                        0 : xModePicker.SelectedItem);
        SKShaderTileMode yTileMode =
            (SKShaderTileMode)(yModePicker.SelectedIndex == -1 ?
                                        0 : yModePicker.SelectedItem);

        using (SKPaint paint = new SKPaint())
        {
            paint.Shader = SKShader.CreateBitmap(bitmap, xTileMode, yTileMode);
            canvas.DrawRect(info.Rect, paint);
        }
    }
}

Обработчик PaintSurface получает SKShaderTileMode параметры из двух Picker представлений и создает SKShader объект на основе растрового изображения и этих двух значений. Этот шейдер используется для заполнения холста:

Режимы перевернутого изображения плитки

На экране iOS слева показан эффект значений SKShaderTileMode.Clampпо умолчанию. Растровое изображение находится в левом верхнем углу. Под растровым изображением в нижней строке пикселей повторяется весь путь вниз. Справа от растрового изображения самый правый столбец пикселей повторяется по всему краю. Оставшаяся часть холста цветом темно-коричневого пикселя в правом нижнем углу растрового изображения. Это должно быть очевидно, что Clamp параметр почти никогда не используется с битовой плиткой!

Экран Android в центре показывает результат SKShaderTileMode.Repeat обоих аргументов. Плитка повторяется по горизонтали и по вертикали. На экране универсальная платформа Windows показанаSKShaderTileMode.Mirror. Плитки повторяются, но перевернуты по горизонтали и по вертикали. Преимуществом этого варианта является отсутствие разрывов между плитками.

Имейте в виду, что можно использовать различные варианты горизонтального и вертикального повторения. Можно указать SKShaderTileMode.Mirror в качестве второго аргумента CreateBitmap , но SKShaderTileMode.Repeat в качестве третьего аргумента. На каждой строке обезьяны по-прежнему чередуются между обычным изображением и зеркало изображением, но ни один из обезьян не перевернут.

Шаблонные фоны

Точечные диаграммы обычно используются для создания шаблонного фона на основе относительно небольшой растровой карты. Классический пример — кирпичная стена.

Страница "Алгоритмический кирпич " создает небольшое растровое изображение, которое напоминает целый кирпич и две половины кирпича, разделенного минометом. Так как этот кирпич используется в следующем примере, он также создается статическим конструктором и делается общедоступным со статическим свойством:

public class AlgorithmicBrickWallPage : ContentPage
{
    static AlgorithmicBrickWallPage()
    {
        const int brickWidth = 64;
        const int brickHeight = 24;
        const int morterThickness = 6;
        const int bitmapWidth = brickWidth + morterThickness;
        const int bitmapHeight = 2 * (brickHeight + morterThickness);

        SKBitmap bitmap = new SKBitmap(bitmapWidth, bitmapHeight);

        using (SKCanvas canvas = new SKCanvas(bitmap))
        using (SKPaint brickPaint = new SKPaint())
        {
            brickPaint.Color = new SKColor(0xB2, 0x22, 0x22);

            canvas.Clear(new SKColor(0xF0, 0xEA, 0xD6));
            canvas.DrawRect(new SKRect(morterThickness / 2,
                                       morterThickness / 2,
                                       morterThickness / 2 + brickWidth,
                                       morterThickness / 2 + brickHeight),
                                       brickPaint);

            int ySecondBrick = 3 * morterThickness / 2 + brickHeight;

            canvas.DrawRect(new SKRect(0,
                                       ySecondBrick,
                                       bitmapWidth / 2 - morterThickness / 2,
                                       ySecondBrick + brickHeight),
                                       brickPaint);

            canvas.DrawRect(new SKRect(bitmapWidth / 2 + morterThickness / 2,
                                       ySecondBrick,
                                       bitmapWidth,
                                       ySecondBrick + brickHeight),
                                       brickPaint);
        }

        // Save as public property for other programs
        BrickWallTile = bitmap;
    }

    public static SKBitmap BrickWallTile { private set; get; }
    ···
}

Результатом растрового изображения является 70 пикселей шириной и 60 пикселей высотой:

Плитка алгоритмического кирпича

Остальная часть страницы "Алгоритмический кирпич" создает SKShader объект, повторяющий это изображение по горизонтали и по вертикали:

public class AlgorithmicBrickWallPage : ContentPage
{
    ···
    public AlgorithmicBrickWallPage ()
    {
        Title = "Algorithmic Brick Wall";

        // Create SKCanvasView
        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();

        using (SKPaint paint = new SKPaint())
        {
            // Create bitmap tiling
            paint.Shader = SKShader.CreateBitmap(BrickWallTile,
                                                 SKShaderTileMode.Repeat,
                                                 SKShaderTileMode.Repeat);
            // Draw background
            canvas.DrawRect(info.Rect, paint);
        }
    }
}

Ниже приведен результат:

Алгоритмическая кирпичная стена

Вы можете предпочесть что-то более реалистично. В этом случае вы можете сфотографировать фактическую кирпичную стену, а затем обрезать ее. Эта растровая карта имеет ширину 300 пикселей и 150 пикселей:

Кирпичная плитка стены

Эта растровая карта используется на странице "Фотография кирпичной стены ":

public class PhotographicBrickWallPage : ContentPage
{
    SKBitmap bitmap = BitmapExtensions.LoadBitmapResource(
                        typeof(PhotographicBrickWallPage),
                        "SkiaSharpFormsDemos.Media.BrickWallTile.jpg");

    public PhotographicBrickWallPage()
    {
        Title = "Photographic Brick Wall";

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

        using (SKPaint paint = new SKPaint())
        {
            // Create bitmap tiling
            paint.Shader = SKShader.CreateBitmap(bitmap,
                                                 SKShaderTileMode.Mirror,
                                                 SKShaderTileMode.Mirror);
            // Draw background
            canvas.DrawRect(info.Rect, paint);
        }
    }
}

Обратите внимание, что SKShaderTileMode аргументы для обоих CreateBitmapMirror. Этот параметр обычно необходим при использовании плиток, созданных на основе реальных изображений. Зеркальное отображение плиток позволяет избежать прерываний.

Фотография кирпичной стены

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

Папка мультимедиа примера также содержит это изображение каменной стены:

Плитка каменных стен

Однако исходный растровый рисунок слишком велик для плитки. Его можно изменить, но SKShader.CreateBitmap метод также может изменить размер плитки, применив к нему преобразование. Этот параметр показан на странице "Каменный стены ":

public class StoneWallPage : ContentPage
{
    SKBitmap bitmap = BitmapExtensions.LoadBitmapResource(
                        typeof(StoneWallPage),
                        "SkiaSharpFormsDemos.Media.StoneWallTile.jpg");

    public StoneWallPage()
    {
        Title = "Stone Wall";

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

        using (SKPaint paint = new SKPaint())
        {
            // Create scale transform
            SKMatrix matrix = SKMatrix.MakeScale(0.5f, 0.5f);

            // Create bitmap tiling
            paint.Shader = SKShader.CreateBitmap(bitmap,
                                                 SKShaderTileMode.Mirror,
                                                 SKShaderTileMode.Mirror,
                                                 matrix);
            // Draw background
            canvas.DrawRect(info.Rect, paint);
        }
    }
}

SKMatrix Значение создается для масштабирования изображения до половины исходного размера:

Каменный стены

Работает ли преобразование с исходной растровой картой, используемой в методе CreateBitmap ? Или преобразует результирующий массив плиток?

Простой способ ответить на этот вопрос заключается в том, чтобы включить поворот в рамках преобразования:

SKMatrix matrix = SKMatrix.MakeScale(0.5f, 0.5f);
SKMatrix.PostConcat(ref matrix, SKMatrix.MakeRotationDegrees(15));

Если преобразование применяется к отдельной плитке, каждое повторяющееся изображение плитки должно быть вращено, а результат будет содержать множество разрывов. Но на этом снимках экрана очевидно, что составной массив плиток преобразуется:

Каменные стены повернуты

В разделе выравнивание плитки вы увидите пример преобразования перевода, примененного к шейдеру.

Пример имитирует фон дерева с помощью растровой плитки на основе этой 240-пиксельной квадратной растровой карты:

Дерево зерна

Это фотография дерева пола. Этот SKShaderTileMode.Mirror параметр позволяет отображаться в виде гораздо большей площади дерева:

Часы кота

Выравнивание плитки

Все примеры, показанные до сих пор, использовали шейдер, созданный SKShader.CreateBitmap для покрытия всего холста. В большинстве случаев вы будете использовать битовую плитку для подачи небольших областей или (реже) для заполнения интерьеров толстых линий. Вот фотоснимок кирпичной стены плитка, используемая для меньшего прямоугольника:

Выравнивание плиток

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

Исправление просто. SKMatrix Создайте значение на основе преобразования перевода. Преобразование эффективно перемещает шаблон плитки в точку, в которую нужно выровнять верхний левый угол плитки. Этот подход показан на странице выравнивания плиток, которая создала изображение неуправляемых плиток, показанных выше:

public class TileAlignmentPage : ContentPage
{
    bool isAligned;

    public TileAlignmentPage()
    {
        Title = "Tile Alignment";

        SKCanvasView canvasView = new SKCanvasView();
        canvasView.PaintSurface += OnCanvasViewPaintSurface;

        // Add tap handler
        TapGestureRecognizer tap = new TapGestureRecognizer();
        tap.Tapped += (sender, args) =>
        {
            isAligned ^= true;
            canvasView.InvalidateSurface();
        };
        canvasView.GestureRecognizers.Add(tap);

        Content = canvasView;
    }

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

        canvas.Clear();

        using (SKPaint paint = new SKPaint())
        {
            SKRect rect = new SKRect(info.Width / 7,
                                     info.Height / 7,
                                     6 * info.Width / 7,
                                     6 * info.Height / 7);

            // Get bitmap from other program
            SKBitmap bitmap = AlgorithmicBrickWallPage.BrickWallTile;

            // Create bitmap tiling
            if (!isAligned)
            {
                paint.Shader = SKShader.CreateBitmap(bitmap,
                                                     SKShaderTileMode.Repeat,
                                                     SKShaderTileMode.Repeat);
            }
            else
            {
                SKMatrix matrix = SKMatrix.MakeTranslation(rect.Left, rect.Top);

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

            // Draw rectangle
            canvas.DrawRect(rect, paint);
        }
    }
}

Страница выравнивания плиток включает в TapGestureRecognizerсебя . Коснитесь или щелкните экран, а программа переключается на SKShader.CreateBitmap метод с аргументом SKMatrix . Это преобразование сдвигает шаблон, чтобы верхний левый угол содержал полный кирпич:

Выравнивание плитки

Этот метод также можно использовать, чтобы убедиться, что шаблон растрового рисунка по плитке находится в пределах области, которую он красит. На странице PaintSurface "Центрированные плитки" обработчик сначала вычисляет координаты, как если бы он отображал однобитовое изображение в центре холста. Затем эти координаты используются для создания преобразования перевода.SKShader.CreateBitmap Это преобразование сдвигает весь шаблон, чтобы плитка была центрирована:

public class CenteredTilesPage : ContentPage
{
    SKBitmap bitmap = BitmapExtensions.LoadBitmapResource(
                        typeof(CenteredTilesPage),
                        "SkiaSharpFormsDemos.Media.monkey.png");

    public CenteredTilesPage ()
    {
        Title = "Centered Tiles";

        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 coordinates to center bitmap in canvas...
        float x = (info.Width - bitmap.Width) / 2f;
        float y = (info.Height - bitmap.Height) / 2f;

        using (SKPaint paint = new SKPaint())
        {
            // ... but use them to create a translate transform
            SKMatrix matrix = SKMatrix.MakeTranslation(x, y);
            paint.Shader = SKShader.CreateBitmap(bitmap,
                                                 SKShaderTileMode.Repeat,
                                                 SKShaderTileMode.Repeat,
                                                 matrix);

            // Use that tiled bitmap pattern to fill a circle
            canvas.DrawCircle(info.Rect.MidX, info.Rect.MidY,
                              Math.Min(info.Width, info.Height) / 2,
                              paint);
        }
    }
}

Обработчик PaintSurface завершается рисованием круга в центре холста. Конечно, одна из плиток находится в центре круга, и остальные упорядочены в симметричном шаблоне:

Центрированные плитки

Другой центривный подход на самом деле немного проще. Вместо того чтобы создать преобразование перевода, которое помещает плитку в центр, можно по центру указать угол шаблона плитки. В вызове SKMatrix.MakeTranslation используйте аргументы для центра холста:

SKMatrix matrix = SKMatrix.MakeTranslation(info.Rect.MidX, info.Rect.MidY);

Шаблон по-прежнему находится в центре и симметрично, но плитка не находится в центре:

Альтернативные центрированные плитки

Упрощение с помощью поворота

Иногда использование преобразования поворота в SKShader.CreateBitmap методе может упростить плитку растрового изображения. Это становится очевидным при попытке определить плитку для забора канала цепочки. Файл ChainLinkTile.cs создает плитку, показанную здесь (с розовым фоном для ясности):

Плитка жесткой цепочки

Плитка должна содержать две ссылки, чтобы код разделял плитку на четыре квадранта. Квадранты в левом верхнем и правом нижнем углу совпадают, но они не завершены. Провода имеют небольшие ноты, которые должны обрабатываться с некоторыми дополнительными рисунками в правом верхнем и нижнем левом квадрантах. Файл, который выполняет всю эту работу, составляет 174 строк.

Оказывается, гораздо проще создать эту плитку:

Упрощенная плитка канала цепочки

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

Код для создания плитки с более простым каналом цепочки является частью страницы плитки Chain-Link. Конструктор определяет размер плитки на основе типа устройства, на котором выполняется программа, а затем вызывается CreateChainLinkTileна растровом рисунке с помощью линий, путей и градиентных шейдеров:

public class ChainLinkFencePage : ContentPage
{
    ···
    SKBitmap tileBitmap;

    public ChainLinkFencePage ()
    {
        Title = "Chain-Link Fence";

        // Create bitmap for chain-link tiling
        int tileSize = Device.Idiom == TargetIdiom.Desktop ? 64 : 128;
        tileBitmap = CreateChainLinkTile(tileSize);

        SKCanvasView canvasView = new SKCanvasView();
        canvasView.PaintSurface += OnCanvasViewPaintSurface;
        Content = canvasView;
    }

    SKBitmap CreateChainLinkTile(int tileSize)
    {
        tileBitmap = new SKBitmap(tileSize, tileSize);
        float wireThickness = tileSize / 12f;

        using (SKCanvas canvas = new SKCanvas(tileBitmap))
        using (SKPaint paint = new SKPaint())
        {
            canvas.Clear();
            paint.Style = SKPaintStyle.Stroke;
            paint.StrokeWidth = wireThickness;
            paint.IsAntialias = true;

            // Draw straight wires first
            paint.Shader = SKShader.CreateLinearGradient(new SKPoint(0, 0),
                                                         new SKPoint(0, tileSize),
                                                         new SKColor[] { SKColors.Silver, SKColors.Black },
                                                         new float[] { 0.4f, 0.6f },
                                                         SKShaderTileMode.Clamp);

            canvas.DrawLine(0, tileSize / 2,
                            tileSize / 2, tileSize / 2 - wireThickness / 2, paint);

            canvas.DrawLine(tileSize, tileSize / 2,
                            tileSize / 2, tileSize / 2 + wireThickness / 2, paint);

            // Draw curved wires
            using (SKPath path = new SKPath())
            {
                path.MoveTo(tileSize / 2, 0);
                path.LineTo(tileSize / 2 - wireThickness / 2, tileSize / 2);
                path.ArcTo(wireThickness / 2, wireThickness / 2,
                           0,
                           SKPathArcSize.Small,
                           SKPathDirection.CounterClockwise,
                           tileSize / 2, tileSize / 2 + wireThickness / 2);

                paint.Shader = SKShader.CreateLinearGradient(new SKPoint(0, 0),
                                                             new SKPoint(0, tileSize),
                                                             new SKColor[] { SKColors.Silver, SKColors.Black },
                                                             null,
                                                             SKShaderTileMode.Clamp);
                canvas.DrawPath(path, paint);

                path.Reset();
                path.MoveTo(tileSize / 2, tileSize);
                path.LineTo(tileSize / 2 + wireThickness / 2, tileSize / 2);
                path.ArcTo(wireThickness / 2, wireThickness / 2,
                           0,
                           SKPathArcSize.Small,
                           SKPathDirection.CounterClockwise,
                           tileSize / 2, tileSize / 2 - wireThickness / 2);

                paint.Shader = SKShader.CreateLinearGradient(new SKPoint(0, 0),
                                                             new SKPoint(0, tileSize),
                                                             new SKColor[] { SKColors.White, SKColors.Silver },
                                                             null,
                                                             SKShaderTileMode.Clamp);
                canvas.DrawPath(path, paint);
            }
            return tileBitmap;
        }
    }
    ···
}

За исключением проводов, плитка прозрачна, что означает, что ее можно отобразить поверх чего-то другого. Программа загружается в одном из ресурсов растрового изображения, отображает его для заполнения холста, а затем рисует шейдер сверху:

public class ChainLinkFencePage : ContentPage
{
    SKBitmap monkeyBitmap = BitmapExtensions.LoadBitmapResource(
        typeof(ChainLinkFencePage), "SkiaSharpFormsDemos.Media.SeatedMonkey.jpg");
    ···

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

        canvas.Clear();

        canvas.DrawBitmap(monkeyBitmap, info.Rect, BitmapStretch.UniformToFill,
                            BitmapAlignment.Center, BitmapAlignment.Start);

        using (SKPaint paint = new SKPaint())
        {
            paint.Shader = SKShader.CreateBitmap(tileBitmap,
                                                 SKShaderTileMode.Repeat,
                                                 SKShaderTileMode.Repeat,
                                                 SKMatrix.MakeRotationDegrees(45));
            canvas.DrawRect(info.Rect, paint);
        }
    }
}

Обратите внимание, что шейдер поворачивается на 45 градусов, поэтому он ориентирован на реальный забор канала цепочки:

Забор канала цепочки

Анимация плиток растрового изображения

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

Кроме того, можно нарисовать на маленьком растровом рисунке или управлять битами пикселей растрового изображения со скоростью 60 раз в секунду. Затем этот растровый рисунок можно использовать для олицетворения, и весь шаблон плитки может быть анимирован.

На странице плитки анимированного растрового рисунка демонстрируется этот подход. Растровое изображение создается в виде поля, равного 64 пикселям квадрата. Конструктор вызывает DrawBitmap его начальное внешний вид. angle Если поле равно нулю (как и при первом вызове метода), то растровое изображение содержит две линии, пересекаемые как X. Линии выполняются достаточно долго, чтобы всегда добраться до края растрового изображения независимо от angle значения:

public class AnimatedBitmapTilePage : ContentPage
{
    const int SIZE = 64;

    SKCanvasView canvasView;
    SKBitmap bitmap = new SKBitmap(SIZE, SIZE);
    float angle;
    ···

    public AnimatedBitmapTilePage ()
    {
        Title = "Animated Bitmap Tile";

        // Initialize bitmap prior to animation
        DrawBitmap();

        // Create SKCanvasView
        canvasView = new SKCanvasView();
        canvasView.PaintSurface += OnCanvasViewPaintSurface;
        Content = canvasView;
    }
    ···
    void DrawBitmap()
    {
        using (SKCanvas canvas = new SKCanvas(bitmap))
        using (SKPaint paint = new SKPaint())
        {
            paint.Style = SKPaintStyle.Stroke;
            paint.Color = SKColors.Blue;
            paint.StrokeWidth = SIZE / 8;

            canvas.Clear();
            canvas.Translate(SIZE / 2, SIZE / 2);
            canvas.RotateDegrees(angle);
            canvas.DrawLine(-SIZE, -SIZE, SIZE, SIZE, paint);
            canvas.DrawLine(-SIZE, SIZE, SIZE, -SIZE, paint);
        }
    }
    ···
}

Затраты на анимацию возникают в OnAppearing и OnDisappearing переопределяются. Метод OnTimerTick анимирует angle значение от 0 до 360 градусов каждые 10 секунд, чтобы повернуть фигуру X в растровом рисунке:

public class AnimatedBitmapTilePage : ContentPage
{
    ···
    // For animation
    bool isAnimating;
    Stopwatch stopwatch = new Stopwatch();
    ···

    protected override void OnAppearing()
    {
        base.OnAppearing();

        isAnimating = true;
        stopwatch.Start();
        Device.StartTimer(TimeSpan.FromMilliseconds(16), OnTimerTick);
    }

    protected override void OnDisappearing()
    {
        base.OnDisappearing();

        stopwatch.Stop();
        isAnimating = false;
    }

    bool OnTimerTick()
    {
        const int duration = 10;     // seconds
        angle = (float)(360f * (stopwatch.Elapsed.TotalSeconds % duration) / duration);
        DrawBitmap();
        canvasView.InvalidateSurface();

        return isAnimating;
    }
    ···
}

Из-за симметрии фигуры X это то же самое, что и поворот angle значения от 0 до 90 градусов каждые 2,5 секунды.

Обработчик PaintSurface создает шейдер из растрового изображения и использует объект краски для цвета всего холста:

public class AnimatedBitmapTilePage : ContentPage
{
    ···
    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        canvas.Clear();

        using (SKPaint paint = new SKPaint())
        {
            paint.Shader = SKShader.CreateBitmap(bitmap,
                                                 SKShaderTileMode.Mirror,
                                                 SKShaderTileMode.Mirror);
            canvas.DrawRect(info.Rect, paint);
        }
    }
}

Параметры SKShaderTileMode.Mirror гарантируют, что руки X в каждом соединении растрового изображения с X в смежных растровых изображениях создают общий анимированный шаблон, который кажется гораздо более сложным, чем простая анимация, будет предлагать:

Плитка анимированного растрового рисунка