Мозаичное заполнение точечного рисунка SkiaSharpSkiaSharp bitmap tiling

Загрузить образец загрузить примерDownload Sample Download the sample

Загрузить образец загрузить примерDownload Sample Download the sample

Как вы уже видели в двух предыдущих статьях, SKShader класс создает линейные и циклическая градиенты.As you've seen in the two previous articles, the SKShader class can create linear or circular gradients. Эта статья посвящена SKShader объект, который использует растровое изображение для заполнения области.This article focuses on the SKShader object that uses a bitmap to tile an area. Битовая карта может повторяться по горизонтали и вертикали, либо в исходной ориентации или в качестве альтернативы перевернуто по горизонтали и вертикали.The bitmap can be repeated horizontally and vertically, either in its original orientation or alternately flipped horizontally and vertically. Отражение позволяет избежать перебоям в работе между плитки:The flipping avoids discontinuities between the tiles:

Пример мозаичного заполнения BitmapBitmap Tiling Sample

Статический SKShader.CreateBitmap имеет метод, который создает этот шейдер SKBitmap параметр и два члена SKShaderTileMode перечисления:The static SKShader.CreateBitmap method that creates this shader has an SKBitmap parameter and two members of the SKShaderTileMode enumeration:

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

Два параметра указывают режимов, используемых для мозаичного заполнения горизонтальной и вертикальной мозаичное заполнение.The two parameters indicate the modes used for horizontal tiling and vertical tiling. Это то же самое SKShaderTileMode перечисления, который используется с методами градиента.This is the same SKShaderTileMode enumeration that is also used with the gradient methods.

Объект CreateBitmap перегруженная версия включает SKMatrix аргумент провести преобразование мозаичной растровых изображений:A CreateBitmap overload includes an SKMatrix argument to perform a transform on the tiled bitmaps:

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

Эта статья содержит несколько примеров использования этого преобразования матрицы с помощью мозаичной растровые изображения.This article contains several examples of using this matrix transform with tiled bitmaps.

Изучение мозаичные режимыExploring the tile modes

Первая программа в мозаичное заполнение растрового изображения раздел построители текстуры и другие эффекты странице SkiaSharpFormsDemos образца демонстрируется действие двух SKShaderTileMode аргументы.The first program in the Bitmap Tiling section of the Shaders and other Effects page of the SkiaSharpFormsDemos sample demonstrates the effects of the two SKShaderTileMode arguments. Режимы перевернуть растрового изображения мозаики файл XAML создает экземпляр SKCanvasView и два Picker представления, которые позволяют выбрать SKShaderTilerMode значение для горизонтальных и вертикальных мозаичное заполнение.The Bitmap Tile Flip Modes XAML file instantiates an SKCanvasView and two Picker views that allow you to select an SKShaderTilerMode value for horizontal and vertical tiling. Обратите внимание, что массив SKShaderTileMode члены, определенные в Resources разделе:Notice that an array of the SKShaderTileMode members is defined in the Resources section:

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

Конструктор файла кода загружается в ресурса точечного рисунка, показывающий monkey сидите.The constructor of the code-behind file loads in the bitmap resource that shows a monkey sitting. Сначала обрезает образ с помощью ExtractSubset метод SKBitmap таким образом, head и футов касаются краев точечного рисунка.It first crops the image using the ExtractSubset method of SKBitmap so that the head and feet are touching the edges of the bitmap. Затем конструктор использует Resize метод для создания другого точечного рисунка половины размера.The constructor then uses the Resize method to create another bitmap of half the size. Эти изменения делают точечного рисунка, немного больше подходит для мозаичного заполнения:These changes make the bitmap a little more suitable for tiling:

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 объекта на основе точечного рисунка и эти два значения.The PaintSurface handler obtains the SKShaderTileMode settings from the two Picker views and creates an SKShader object based on the bitmap and those two values. Этот шейдер используется для заполнения полотна.This shader is used to fill the canvas:

Режимы Переворачивающейся плитки для точечных рисунковBitmap Tile Flip Modes

На экране iOS слева показывает эффект от значения по умолчанию SKShaderTileMode.Clamp.The iOS screen at the left shows the effect of the default values of SKShaderTileMode.Clamp. Растровое изображение размещается в левом верхнем углу.The bitmap sits in the upper-left corner. Ниже точечного рисунка нижняя строка пикселей повторяется вниз.Below the bitmap, the bottom row of pixels is repeated all the way down. В правой части точечного рисунка этого не происходило пикселей, которое повторяется вплоть для.To the right of the bitmap, the rightmost column of pixels is repeated all the way across. В оставшейся части холста окрашивается темно-коричневой пиксель в правом нижнем углу точечного рисунка.The remainder of the canvas is colored by the dark brown pixel in the bitmap's lower-right corner. Должно быть очевидно, Clamp практически никогда не используется с параметром мозаичное заполнение растрового изображения!It should be obvious that the Clamp option is almost never used with bitmap tiling!

Экран Android в центре показывает результат SKShaderTileMode.Repeat обоих аргументов.The Android screen in the center shows the result of SKShaderTileMode.Repeat for both arguments. Плитка будет повторяться по горизонтали и вертикали.The tile is repeated horizontally and vertically. Универсальная платформа Windows экрана показывает SKShaderTileMode.Mirror.The Universal Windows Platform screen shows SKShaderTileMode.Mirror. Плитки повторить, но можно также перевернуто по горизонтали и вертикали.The tiles are repeated but alternately flipped horizontally and vertically. Этот параметр удобен тем что существуют не перебоям в работе между плитки.The advantage of this option is that there are no discontinuities between the tiles.

Имейте в виду, что можно использовать различные параметры для повторения горизонтальный и вертикальный.Keep in mind that you can use different options for the horizontal and vertical repetition. Можно указать SKShaderTileMode.Mirror как второй аргумент CreateBitmap , но SKShaderTileMode.Repeat качестве третьего аргумента.You can specify SKShaderTileMode.Mirror as the second argument to CreateBitmap but SKShaderTileMode.Repeat as the third argument. В каждой строке кодеры, по-прежнему переключайтесь между обычный образ и Зеркальное изображение, но ни один из кодеры ногами.On each row, the monkeys still alternate between the normal image and the mirror image, but none of the monkeys are upside-down.

Узора фонаPatterned backgrounds

Мозаичное заполнение растрового изображения обычно используется для создания узора фона из относительно небольшой растрового изображения.Bitmap tiling is commonly used to create a patterned background from a relatively small bitmap. Классическим примером является упираетесь в стену.The classic example is a brick wall.

Алгоритмического Упираетесь в стену страница создает небольшое растровое изображение, похожий на всей модуля и две части модуля, разделенных Базовые составляющие.The Algorithmic Brick Wall page creates a small bitmap that resembles a whole brick and two halves of a brick separated by mortar. Так как в следующем примере также используется этот модуль, его создания, статический конструктор, сделан общим с помощью статического свойства:Because this brick is used in the next sample as well, it's created by a static constructor and made public with a static property:

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 пикселей:The resultant bitmap is 70 pixels wide and 60 pixels high:

Алгоритмического модуля Wall плиткиAlgorithmic Brick Wall Tile

Остальная часть алгоритмического Упираетесь в стену страница создает SKShader объект, повторяющийся это изображение по горизонтали и вертикали:The rest of the Algorithmic Brick Wall page creates an SKShader object that repeats this image horizontally and vertically:

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

Ниже приведен результат.Here's the result:

Алгоритмического Упираетесь в стенуAlgorithmic Brick Wall

Вы можете что-нибудь немного более реалистичной.You might prefer something a little more realistic. В этом случае можно воспользоваться фотографии и фактическое упираетесь в стену и затем обрезать.In that case, you can take a photograph of an actual brick wall and then crop it. Этот рисунок — 300 пикселей в ширину и 150 пикселей:This bitmap is 300 pixels wide and 150 pixels high:

Граница Wall плиткиBrick Wall Tile

Это растровое изображение используется в фотографический Упираетесь в стену страницы:This bitmap is used in the Photographic Brick Wall page:

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 аргументы CreateBitmap оба Mirror.Notice that the SKShaderTileMode arguments to CreateBitmap are both Mirror. Этот параметр обычно необходим при использовании плиток, созданных из образов в реальных условиях.This option is usually necessary when you use tiles created from real-world images. Зеркальное отображение плитки позволяет избежать перебоям в работе:Mirroring the tiles avoids discontinuities:

Фотографический Упираетесь в стенуPhotographic Brick Wall

Некоторые действия, необходимые для получения подходящих растрового изображения для мозаичного элемента.Some work is required to get a suitable bitmap for the tile. Это не работает очень хорошо, так как более темные модуля, выделяется среди слишком много.This one doesn't work very well because the darker brick stands out too much. Он регулярно отображается в повторных образов, отображая тот факт, что упираетесь в стену был создан из небольших растрового изображения.It appears regularly within the repeated images, revealing the fact that this brick wall was constructed from a smaller bitmap.

Мультимедиа папке SkiaSharpFormsDemos образец также входит этот образ камень стене:The Media folder of the SkiaSharpFormsDemos sample also includes this image of a stone wall:

Плитка Wall камнеStone Wall Tile

Тем не менее исходного растрового изображения немного слишком велик для плитки.However, the original bitmap is a little too large for a tile. Их можно изменять, но SKShader.CreateBitmap метод можно также изменить размер плитки, применяя преобразование к нему.It could be resized, but the SKShader.CreateBitmap method can also resize the tile by applying a transform to it. Этот вариант рассматривается в камне стены страницы:This option is demonstrated in the Stone Wall page:

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 Создается значение для масштабирования изображения половины исходный размер:An SKMatrix value is created to scale the image to half its original size:

Выстрелом стенуStone Wall

Выполняет преобразование операций исходное растровое изображение, используемое в CreateBitmap метод?Does the transform operate on the original bitmap used in the CreateBitmap method? Или преобразовать результирующий массив плитки?Or does it transform the resultant array of tiles?

Простой способ ответить на этот вопрос, можно включить поворот в процессе преобразования.An easy way to answer this question is to include a rotation as part of the transform:

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

Если преобразование применяется к отдельным плитки, затем осуществляется изображению повторяющиеся плитки и результат будет содержать много перебоям в работе.If the transform is applied to the individual tile, then each repeated image of the tile should be rotated, and the result would contain many discontinuities. Но это очевидно из на этом снимке экрана, что преобразуется составной массив плиток:But it's obvious from this screenshot that the composite array of tiles is transformed:

Выстрелом по часам (повернутый)Stone Wall Rotated

В разделе плитку выравнивание, вы увидите пример преобразования переноса к шейдера.In the section Tile alignment, you'll see an example a translate transform applied to the shader.

Автономной часы Cat пример (не является частью SkiaSharpFormsDemos) имитирует фона древесина детализации помощи дробления растрового изображения, в соответствии с этим растровым изображением square 240 пикселей:The standalone Cat Clock sample (not part of SkiaSharpFormsDemos) simulates a wood-grain background using bitmap tiling based on this 240-pixel square bitmap:

Деревянные детализацииWood Grain

Это фотографию обшивка floor.That is a photograph of a wood floor. SKShaderTileMode.Mirror Параметр позволяет отобразить его как гораздо большего размера области дерева:The SKShaderTileMode.Mirror option allows it to appear as a much larger area of wood:

CAT часыCat Clock

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

Все примеры, приведенные в данный момент использовали шейдера, созданные SKShader.CreateBitmap чтобы охватить весь холст.All the examples shown so far have used the shader created by SKShader.CreateBitmap to cover the entire canvas. В большинстве случаев вы будете использовать мозаичное заполнение точечного рисунка для хранения небольших областей или (более редко) для заполнения толстой линии.In most cases, you'll be using bitmap tiling for filing smaller areas or (more rarely) for filling the interiors of thick lines. Ниже приведен фотографический модуля wall плитки, используемый для меньшего размера прямоугольника.Here's the photographic brick-wall tile used for a smaller rectangle:

Плитку выравниваниеTile Alignment

Это может выглядеть нормально, или нет.This might look fine to you, or maybe not. Возможно, вы беспокоить, что шаблон мозаики не начинается с полного модуля в левом верхнем углу прямоугольника.Perhaps you're disturbed that the tiling pattern doesn't begin with a full brick in the upper-left corner of the rectangle. Том, что шейдеры выравниваются с canvas и не они Декорирование графического объекта.That's because shaders are aligned with the canvas and not the graphical object that they adorn.

Исправление довольно простое.The fix is simple. Создание SKMatrix значение, основанное на преобразованию.Create an SKMatrix value based on a translation transform. Преобразование эффективно сдвигает шаблон заполнения кнопку мыши, в котором левом верхнем углу плитки, чтобы быть выровнены.The transform effectively shifts the tiled pattern to the point where you want the upper-left corner of the tile to be aligned. Этот подход демонстрируется в плитки выравнивание странице, создавшего образ, невыровненных элементов выше:This approach is demonstrated in the Tile Alignment page, which created the image of the unaligned tiles shown above:

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.The Tile Alignment page includes a TapGestureRecognizer. Коснитесь или щелкните на экране и командной строки для SKShader.CreateBitmap метод с SKMatrix аргумент.Tap or click the screen, and the program switches to the SKShader.CreateBitmap method with an SKMatrix argument. Это преобразование сдвигает шаблон таким образом, что верхнего левого угла полный модуля:This transform shifts the pattern so that the upper-left corner contains a full brick:

Плитку выравнивание касаниеTile Alignment Tapped

Этот метод позволяет убедиться, что шаблон мозаики растровое изображение по центру в пределах области, которая рисует его.You can also use this technique to ensure that the tiled bitmap pattern is centered within the area that it paints. В по центру плитки странице PaintSurface обработчик сначала вычисляет координаты, как если бы он будет отображен один точечный рисунок в центре части холста.In the Centered Tiles page, the PaintSurface handler first calculates coordinates as if it's going to display the single bitmap in the center of the canvas. Затем он использует эти координаты для преобразования переноса для создания SKShader.CreateBitmap.It then uses those coordinates to create a translate transform for SKShader.CreateBitmap. Это преобразование сдвигает весь шаблон, чтобы плитки выравнивается по центру:This transform shifts the entire pattern so that a tile is centered:

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 Завершении работы обработчика, нарисовав круг в центре части холста.The PaintSurface handler concludes by drawing a circle in the center of the canvas. Очевидно, что одна плиток точно в центре круга, а остальные упорядочены в шаблоне симметричный:Sure enough, one of the tiles is exactly in the center of the circle, and the others are arranged in a symmetric pattern:

По центру плиткиCentered Tiles

Фактически другой центровки подход немного проще.Another centering approach is actually a bit easier. Вместо создания преобразования переноса, которое позволяет поместить плитку в центре, можно центрировать углу шаблон заполнения.Rather than construct a translate transform that puts a tile in the center, you can center a corner of the tiled pattern. В SKMatrix.MakeTranslation вызвать, используйте аргументы для центра холста:In the SKMatrix.MakeTranslation call, use arguments for the center of the canvas:

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

Шаблон по-прежнему выровненные по центру и симметричное, но плитки не находится в центре:The pattern is still centered and symmetrical, but no tile is in the center:

По центру альтернативного плиткиCentered Tiles Alternate

Упрощение через поворотаSimplification through rotation

Иногда с помощью преобразования вращения в SKShader.CreateBitmap метод можно упростить плитку растрового изображения.Sometimes using a rotate transform in the SKShader.CreateBitmap method can simplify the bitmap tile. Это становится очевидным, при попытке определить плитки для компоновки цепочки барьер.This becomes evident when attempting to define a tile for a chain-link fence. ChainLinkTile.cs файл создает плитки, показано ниже (с Розовый фон для большей ясности):The ChainLinkTile.cs file creates the tile shown here (with a pink background for purposes of clarity):

Плитка жесткой связи цепочкиHard chain-link tile

Элемент должен включать две ссылки, таким образом, чтобы код делит плитки на четырех квадрантов.The tile needs to include two links, so that the code divides the tile into four quadrants. Квадрантах верхнего левого и правого нижнего одинаковы, но не выполнены.The upper-left and lower-right quadrants are the same, but they are not complete. Проводов имеют небольшой деления, которые должны быть обработаны с некоторых дополнительных рисования в верхний правый и левый нижний квадрантах.The wires have little notches that must be handled with some additional drawing in the upper-right and lower-left quadrants. Файл, который выполняет всю эту работу — 174 строк.The file that does all this work is 174 lines long.

Дело в том станет намного проще для создания этой плитки:It turns out to be much easier to create this tile:

Проще плитки компоновки цепочкиEasier chain-link tile

Если шейдер растрового изображения плитки Повернуть на 90 градусов, визуальные элементы являются почти одинакова.If the bitmap-tile shader is rotated 90 degrees, the visuals are nearly the same.

Код для создания плитки проще компоновки цепочки является частью плитку компоновки цепочки страницы.The code to create the easier chain-link tile is part of the Chain-Link Tile page. Конструктор определяет размер мозаики, в зависимости от типа устройства под управлением программы, а затем вызывает CreateChainLinkTile, который рисует изображения на точечный рисунок с помощью строки, пути и шейдеры градиента:The constructor determines a tile size based on the type of device that the program is running on, and then calls CreateChainLinkTile, which draws on the bitmap using lines, paths, and gradient shaders:

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

За исключением провода Плитка является прозрачным, означающее, которую можно отобразить поверх что-то еще.Except for the wires, the tile is transparent, which means that you can display it on top of something else. Программа загружает в один из ресурсов точечного рисунка, отображает его на холст, а затем выводит шейдера в верхней части:The program loads in one of the bitmap resources, displays it to fill the canvas, and then draws the shader on top:

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 градусов, поэтому его компонент ориентирован как барьер реальных цепочка:Notice that the shader is rotated 45 degrees so it is oriented like a real chain-link fence:

Граница компоновки цепочкиChain-Link Fence

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

Это шаблон всей плитке точечного рисунка можно анимировать, анимация матрицы преобразования.You can animate an entire bitmap-tile pattern by animating the matrix transform. Возможно, вам необходимо шаблоне для вертикального или горизонтального перемещения или оба.Perhaps you want the pattern to move horizontally or vertically or both. Это выполняется путем создания на основе координат сдвига преобразования.You can do that by creating a translation transform based on the shifting coordinates.

Можно также для рисования с небольшими изображениями, или для работы биты пикселов точечного рисунка в размере 60 раз в секунду.It's also possible to draw on a small bitmap, or to manipulate the bitmap's pixel bits at the rate of 60 times a second. Затем этот точечный рисунок можно использовать для заполнения, а весь шаблон заполнения может показаться анимировать.That bitmap can then be used for tiling, and the entire tiled pattern can seem to be animated.

Анимировано плитки точечного рисунка страница демонстрирует этот подход.The Animated Bitmap Tile page demonstrates this approach. Битовая карта создается в виде поля должно быть квадрат 64 пикселя.A bitmap is instantiated as a field to be 64-pixels square. Конструктор вызывает DrawBitmap присвоить ей исходный вид.The constructor calls DrawBitmap to give it an initial appearance. Если angle поле является ноль (так как это при первом вызове метода), то Битовая карта содержит две строки, пересекающиеся как X. Строки выполняются достаточно длинным, всегда достигал к границе растрового изображения вне зависимости от angle значение:If the angle field is zero (as it is when the method is first called), then the bitmap contains two lines crossed as an X. The lines are made long enough to always reach to the edge of the bitmap regardless of the angle value:

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 переопределяет.The animation overhead occurs in the OnAppearing and OnDisappearing overrides. OnTimerTick Анимирует метод angle значение от 0 до 360 градусов каждые 10 секунд для поворота на рисунке X внутри точечного рисунка:The OnTimerTick method animates the angle value from 0 degrees to 360 degrees every 10 seconds to rotate the X figure within the bitmap:

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 секунды.Because of the symmetry of the X figure, this is the same as rotating the angle value from 0 degrees to 90 degrees every 2.5 seconds.

PaintSurface Обработчик создает шейдера из растрового изображения и использует объект paint для цвета весь холст:The PaintSurface handler creates a shader from the bitmap and uses the paint object to color the entire canvas:

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» в каждый рисунок присоединена с Х в смежных точечные рисунки, чтобы создать общую анимированный шаблон, который кажется практически сложнее, чем простая анимация предлагаем:The SKShaderTileMode.Mirror options ensure that the arms of the X in each bitmap join with the X in the adjacent bitmaps to create an overall animated pattern that seems much more complex than the simple animation would suggest:

Анимированные плитки растрового изображенияAnimated Bitmap Tile