Udostępnij za pośrednictwem


Tiling mapy bitowej SkiaSharp

Jak pokazano w dwóch poprzednich artykułach, SKShader klasa może tworzyć gradienty liniowe lub cykliczne. Ten artykuł koncentruje się na SKShader obiekcie, który używa mapy bitowej do kafelka obszaru. Mapa bitowa może być powtarzana w poziomie i w pionie, w oryginalnej orientacji lub alternatywnie przerzucana w poziomie i w pionie. Przerzucanie pozwala uniknąć przerw między kafelkami:

Przykład tilingu mapy bitowej

Metoda statyczna SKShader.CreateBitmap tworząca ten moduł cieniowania ma SKBitmap parametr i dwa elementy członkowskie SKShaderTileMode wyliczenia:

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

Dwa parametry wskazują tryby używane do układania kafelków poziomych i kafelków pionowych. Jest to to samo SKShaderTileMode wyliczenie, które jest również używane z metodami gradientu.

Przeciążenie CreateBitmap zawiera SKMatrix argument umożliwiający wykonanie przekształcenia na kafelkach map bitowych:

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

Ten artykuł zawiera kilka przykładów użycia tej transformacji macierzy z kafelkami map bitowych.

Eksplorowanie trybów kafelków

Pierwszy program w sekcji Tiling mapy bitowej na stronie Cieniowanie i inne efekty w przykładzie pokazuje efekty dwóch SKShaderTileMode argumentów. Kafelek mapy bitowej przerzuca tryby XAML plik tworzy wystąpienie i SKCanvasView dwa Picker widoki, które umożliwiają wybranie SKShaderTilerMode wartości dla kafelka poziomego i pionowego. Zwróć uwagę, że tablica elementów SKShaderTileMode członkowskich jest zdefiniowana Resources w sekcji:

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

Konstruktor pliku za pomocą kodu ładuje się w zasobie mapy bitowej, który pokazuje małpę siedzącą. Najpierw przycina obraz przy użyciu ExtractSubset metody SKBitmap , aby głowa i stopy dotykały krawędzi mapy bitowej. Konstruktor następnie używa Resize metody , aby utworzyć kolejną mapę bitową o połowę rozmiaru. Te zmiany sprawiają, że mapa bitowa jest nieco bardziej odpowiednia do układania:

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

Procedura PaintSurface obsługi uzyskuje SKShaderTileMode ustawienia z dwóch Picker widoków i tworzy SKShader obiekt na podstawie mapy bitowej i tych dwóch wartości. Ten cieniator służy do wypełniania kanwy:

Tryby przerzucania kafelka mapy bitowej

Ekran systemu iOS po lewej stronie pokazuje efekt wartości domyślnych .SKShaderTileMode.Clamp Mapa bitowa znajduje się w lewym górnym rogu. Poniżej mapy bitowej dolny wiersz pikseli jest powtarzany aż w dół. Po prawej stronie mapy bitowej po prawej stronie wielokrotnie powtarzana jest prawa kolumna pikseli. Pozostała część kanwy jest kolorowana ciemnym brązowym pikselem w prawym dolnym rogu mapy bitowej. Powinno być oczywiste, że Clamp opcja prawie nigdy nie jest używana z tilingiem mapy bitowej!

Ekran systemu Android w środku pokazuje wynik SKShaderTileMode.Repeat dla obu argumentów. Kafelek jest powtarzany w poziomie i w pionie. Na ekranie platforma uniwersalna systemu Windows jest wyświetlana wartość SKShaderTileMode.Mirror. Kafelki są powtarzane, ale alternatywnie przerzucane w poziomie i w pionie. Zaletą tej opcji jest brak przerw między kafelkami.

Należy pamiętać, że można użyć różnych opcji dla powtórzeń poziomych i pionowych. Możesz określić SKShaderTileMode.Mirror jako drugi argument, CreateBitmap ale SKShaderTileMode.Repeat jako trzeci argument. W każdym wierszu małpy nadal zmieniają się między normalnym obrazem a obrazem lustrzanym, ale żadna z małp nie jest do góry nogami.

Wzorzyste tła

Tiling mapy bitowej jest często używany do tworzenia wzorzystego tła na podstawie stosunkowo małej mapy bitowej. Klasycznym przykładem jest ściana z cegły.

Strona Algorithmic Brick Wall tworzy małą mapę bitową przypominającą całą cegłę i dwie połówki cegły oddzielonej moździerzem. Ponieważ ta cegła jest również używana w następnym przykładzie, jest tworzona przez konstruktor statyczny i upubliczniona z właściwością statyczną:

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

Wynikowa mapa bitowa ma szerokość 70 pikseli i 60 pikseli wysokości:

Algorytmiczny kafelek ściany ceglanej

Pozostała część strony Algorytmiczna ściana ceglana tworzy SKShader obiekt, który powtarza ten obraz w poziomie i w pionie:

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

Oto wynik:

Algorytmiczna ściana ceglana

Możesz wolisz coś nieco bardziej realistycznego. W takim przypadku możesz zrobić zdjęcie rzeczywistej ściany ceglanej, a następnie przyciągnąć ją. Ta mapa bitowa ma szerokość 300 pikseli i 150 pikseli:

Ceglana ściana - kafelek

Ta mapa bitowa jest używana na stronie Ściana fotograficzna ceglana :

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

Zwróć uwagę, że SKShaderTileMode argumenty , które CreateBitmap mają być oba Mirror. Ta opcja jest zwykle niezbędna w przypadku używania kafelków utworzonych na podstawie obrazów rzeczywistych. Dublowanie kafelków pozwala uniknąć przerw:

Fotograficzna ściana ceglana

Niektóre prace są wymagane, aby uzyskać odpowiednią mapę bitową dla kafelka. Ten nie działa bardzo dobrze, ponieważ ciemniejszy cegła wyróżnia się zbyt wiele. Pojawia się regularnie w powtarzających się obrazach, ujawniając fakt, że ta ściana ceglana została zbudowana z mniejszej mapy bitowej.

Folder Media przykładu zawiera również ten obraz kamiennej ściany:

Kamienna ściana kafelka

Jednak oryginalna mapa bitowa jest nieco za duża dla kafelka. Można go zmienić, ale SKShader.CreateBitmap metoda może również zmienić rozmiar kafelka, stosując do niego przekształcenie. Ta opcja jest pokazana na stronie Kamienna ściana :

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

Zostanie utworzona SKMatrix wartość w celu skalowania obrazu do połowy oryginalnego rozmiaru:

Kamienna ściana

Czy transformacja działa na oryginalnej mapie bitowej używanej w metodzie CreateBitmap ? Czy też przekształca wynikową tablicę kafelków?

Łatwym sposobem odpowiedzi na to pytanie jest dołączenie rotacji w ramach transformacji:

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

Jeśli przekształcenie zostanie zastosowane do pojedynczego kafelka, każdy powtórzony obraz kafelka powinien zostać obrócony, a wynik będzie zawierać wiele przerw. Jednak na tym zrzucie ekranu widać, że złożona tablica kafelków została przekształcona:

Ściana kamienna obrócona

W sekcji Wyrównanie kafelka zobaczysz przykład przekształcenia tłumaczenia zastosowanego do cieniowania.

Przykład symuluje tło ziarna drewna przy użyciu tilingu mapy bitowej na podstawie tej 240-pikselowej kwadratowej mapy bitowej:

Ziarno drewna

To jest zdjęcie drewnianej podłogi. Opcja SKShaderTileMode.Mirror pozwala na pojawienie się znacznie większego obszaru drewna:

Zegar kota

Wyrównanie kafelka

Wszystkie pokazane do tej pory przykłady używały cieniowania utworzonego przez SKShader.CreateBitmap program w celu pokrycia całej kanwy. W większości przypadków będziesz używać tilingu mapy bitowej do wypełniania mniejszych obszarów lub (rzadziej) do wypełniania wnętrz grubych linii. Oto fotograficzna ścianka z cegły używana do mniejszego prostokąta:

Wyrównanie kafelka

Może to wyglądać dobrze, a może nie. Być może jesteś zaniepokojony, że wzór tilinga nie zaczyna się od pełnej cegły w lewym górnym rogu prostokąta. Dzieje się tak dlatego, że cieniowania są wyrównane do kanwy, a nie z obiektem graficznym, który zdobią.

Poprawka jest prosta. SKMatrix Utwórz wartość na podstawie przekształcenia tłumaczenia. Przekształcenie skutecznie przenosi wzorzec kafelków do punktu, w którym ma zostać wyrównany lewy górny róg kafelka. To podejście przedstawiono na stronie Wyrównanie kafelka, która utworzyła obraz nieprzygotowanych kafelków pokazanych powyżej:

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

Strona Wyrównanie kafelka zawiera element TapGestureRecognizer. Naciśnij lub kliknij ekran, a program przełącza się do SKShader.CreateBitmap metody z argumentem SKMatrix . Ta transformacja przesuwa wzorzec, tak aby lewy górny róg zawierał pełną cegłę:

Naciśnięcie wyrównania kafelka

Można również użyć tej techniki, aby upewnić się, że wyśrodkowany wzorzec mapy bitowej kafelków znajduje się w obszarze, który maluje. Na stronie Wyśrodkowane kafelki program obsługi najpierw oblicza współrzędne tak, PaintSurface jakby wyświetlał pojedynczą mapę bitową w środku kanwy. Następnie używa tych współrzędnych do utworzenia przekształcenia translate dla SKShader.CreateBitmapelementu . Ta transformacja przesuwa cały wzorzec, tak aby kafelek był wyśrodkowany:

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

Procedura PaintSurface obsługi kończy się rysowaniem okręgu w środku kanwy. Na pewno jeden z kafelków znajduje się dokładnie w środku okręgu, a pozostałe są rozmieszczone w wzorcu symetrycznym:

Wyśrodkowane kafelki

Inne podejście skoncentrowane jest w rzeczywistości nieco łatwiejsze. Zamiast konstruować transformację tłumaczenia, która umieszcza kafelek w środku, można wyśrodkować róg wzorca kafelka. W wywołaniu SKMatrix.MakeTranslation użyj argumentów dla środka kanwy:

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

Wzorzec jest nadal wyśrodkowany i symetryczny, ale żaden kafelek nie znajduje się w środku:

Wyśrodkowane kafelki alternatywne

Uproszczenie poprzez rotację

Czasami użycie przekształcenia obracania w metodzie SKShader.CreateBitmap może uprościć kafelek mapy bitowej. Staje się to oczywiste podczas próby zdefiniowania kafelka dla ogrodzenia łączącego łańcuch. Plik ChainLinkTile.cs tworzy tutaj kafelek (z różowym tłem na potrzeby jasności):

Kafelek z twardym łączem łańcuchowym

Kafelek musi zawierać dwa linki, aby kod dzielił kafelek na cztery ćwiartki. Ćwiartki w lewym górnym i prawym dolnym rogu są takie same, ale nie są kompletne. Przewody mają małe wycięcia, które muszą być obsługiwane z dodatkowym rysunkiem w prawym górnym i lewym dolnym rogu ćwiartki. Plik, który wykonuje całą tę pracę, to 174 wiersze długie.

Okazuje się, że tworzenie tego kafelka jest znacznie łatwiejsze:

Łatwiejszy kafelek linku łańcuchowego

Jeśli cieniator kafelków bitowych jest obracany o 90 stopni, wizualizacje są prawie takie same.

Kod umożliwiający utworzenie łatwiejszego kafelka linku łańcuchowego jest częścią strony Kafelek linku łańcuchowego. Konstruktor określa rozmiar kafelka na podstawie typu urządzenia, na którym działa program, a następnie wywołuje CreateChainLinkTilemetodę , która korzysta z mapy bitowej przy użyciu linii, ścieżek i cieniowań gradientu:

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

Z wyjątkiem przewodów, kafelek jest przezroczysty, co oznacza, że można go wyświetlić na wierzchu czegoś innego. Program ładuje się w jednym z zasobów mapy bitowej, wyświetla go, aby wypełnić kanwę, a następnie rysuje cieniowanie na górze:

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

Zwróć uwagę, że cieniowanie jest obracane o 45 stopni, więc jest zorientowane jak prawdziwe ogrodzenie linku łańcucha:

Płot linku do łańcucha

Animowanie kafelków mapy bitowej

Możesz animować cały wzorzec kafelka mapy bitowej, animując transformację macierzy. Być może chcesz, aby wzorzec poruszał się w poziomie lub w pionie lub w obu przypadkach. Można to zrobić, tworząc przekształcenie tłumaczenia na podstawie współrzędnych przesuwających.

Istnieje również możliwość narysowania na małej mapie bitowej lub manipulowania bitami pikseli mapy bitowej w tempie 60 razy na sekundę. Ta mapa bitowa może być następnie używana do układania płytek, a cały wzór kafelków może być animowany.

Na stronie Animowany kafelek mapy bitowej przedstawiono to podejście. Mapa bitowa jest tworzone jako pole o powierzchni 64 pikseli. Konstruktor wywołuje metodę DrawBitmap , aby nadać jej początkowy wygląd. angle Jeśli pole ma wartość zero (jak to jest, gdy metoda jest wywoływana po raz pierwszy), mapa bitowa zawiera dwa linie skrzyżowane jako X. Linie są wystarczająco długie, aby zawsze docierać do krawędzi mapy bitowej niezależnie od angle wartości:

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

Obciążenie animacji występuje w przesłonięciach OnAppearing i OnDisappearing . Metoda OnTimerTick animuje angle wartość z zakresu od 0 stopni do 360 stopni co 10 sekund, aby obrócić figurę X w mapie bitowej:

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

Ze względu na symetrię rysunku X jest to samo co obracanie angle wartości z 0 stopni do 90 stopni co 2,5 sekundy.

Procedura PaintSurface obsługi tworzy cieniowanie na podstawie mapy bitowej i używa obiektu farby do kolorowania całej kanwy:

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

Opcje SKShaderTileMode.Mirror zapewniają, że ramiona X w każdej mapie bitowej łączą się z X w sąsiednich mapach bitowych, aby utworzyć ogólny animowany wzorzec, który wydaje się znacznie bardziej złożony, niż sugeruje prosta animacja:

Animowany kafelek mapy bitowej