Teilen über


SkiaSharp-Bitmap-Tiling

Wie Sie in den beiden vorherigen Artikeln gesehen haben, kann die SKShader Klasse lineare oder kreisförmige Farbverläufe erstellen. Dieser Artikel konzentriert sich auf das SKShader Objekt, das eine Bitmap zum Kacheln eines Bereichs verwendet. Die Bitmap kann horizontal und vertikal wiederholt werden, entweder in der ursprünglichen Ausrichtung oder alternativ horizontal und vertikal gekippt. Durch das Kippen werden Unterbrechungen zwischen den Kacheln vermieden:

Bitmap-Kachelbeispiel

Die statische SKShader.CreateBitmap Methode, die diesen Shader erstellt, weist einen SKBitmap Parameter und zwei Member der SKShaderTileMode Enumeration auf:

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

Die beiden Parameter geben die Modi an, die für horizontale Kacheln und vertikale Kacheln verwendet werden. Dies ist dieselbe SKShaderTileMode Aufzählung, die auch mit den Farbverlaufsmethoden verwendet wird.

Eine CreateBitmap Überladung enthält ein SKMatrix Argument, um eine Transformation für die nebeneinander angeordneten Bitmaps auszuführen:

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

Dieser Artikel enthält mehrere Beispiele für die Verwendung dieser Matrixtransformation mit nebeneinander angeordneten Bitmaps.

Erkunden der Kachelmodi

Das erste Programm im Abschnitt "Bitmap-Tiling " der Shader und anderer Effekte-Seite des Beispiels veranschaulicht die Effekte der beiden SKShaderTileMode Argumente. Die XAML-Datei "Bitmapkachel flip modi " instanziiert eine SKCanvasView und zwei Picker Ansichten, mit denen Sie einen SKShaderTilerMode Wert für horizontale und vertikale Kacheln auswählen können. Beachten Sie, dass ein Array der SKShaderTileMode Member im Resources Abschnitt definiert ist:

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

Der Konstruktor der CodeBehind-Datei wird in der Bitmapressource geladen, die einen Affen anzeigt, der sitzt. Zunächst wird das Bild mit der ExtractSubset Methode SKBitmap abgerissen, sodass der Kopf und die Füße die Ränder der Bitmap berühren. Der Konstruktor verwendet dann die Resize Methode, um eine weitere Bitmap der Hälfte der Größe zu erstellen. Durch diese Änderungen wird die Bitmap etwas besser für die Kachelung geeignet:

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

Der PaintSurface Handler ruft die SKShaderTileMode Einstellungen aus den beiden Picker Ansichten ab und erstellt ein SKShader Objekt basierend auf der Bitmap und diesen beiden Werten. Dieser Shader wird verwendet, um den Zeichenbereich auszufüllen:

Flip-Modi für Bitmap-Kacheln

Der iOS-Bildschirm links zeigt den Effekt der Standardwerte von SKShaderTileMode.Clamp. Die Bitmap befindet sich in der oberen linken Ecke. Unterhalb der Bitmap wird die untere Zeile der Pixel ganz nach unten wiederholt. Rechts neben der Bitmap wird die äußerst rechte Spalte der Pixel ganz nach oben wiederholt. Der Re Standard der des Zeichenbereichs wird durch das dunkelbraune Pixel in der unteren rechten Ecke der Bitmap gefärbt. Es sollte offensichtlich sein, dass die Clamp Option fast nie mit Bitmap-Tiling verwendet wird!

Der Android-Bildschirm in der Mitte zeigt das Ergebnis für SKShaderTileMode.Repeat beide Argumente an. Die Kachel wird horizontal und vertikal wiederholt. Der Universelle Windows-Plattform Bildschirm wird angezeigtSKShaderTileMode.Mirror. Die Kacheln werden wiederholt, aber abwechselnd horizontal und vertikal gekippt. Der Vorteil dieser Option besteht darin, dass zwischen den Kacheln keine Diskontitäten vorhanden sind.

Denken Sie daran, dass Sie verschiedene Optionen für die horizontale und vertikale Wiederholung verwenden können. Sie können als zweites Argument angeben SKShaderTileMode.Mirror , CreateBitmap aber SKShaderTileMode.Repeat als drittes Argument. In jeder Zeile wechseln die Affen immer noch zwischen dem normalen Bild und dem Spiegel Bild, aber keiner der Affen ist auf dem Kopf.

Gemusterte Hintergründe

Bitmap-Kacheln werden häufig verwendet, um einen gemusterten Hintergrund aus einer relativ kleinen Bitmap zu erstellen. Das klassische Beispiel ist eine Ziegelwand.

Das Algorithmic Brick Wall-Zeichenblatt erstellt eine kleine Bitmap, die einem ganzen Ziegel und zwei Hälften eines durch Mörtel getrennten Ziegels ähnelt. Da dieser Ziegel auch im nächsten Beispiel verwendet wird, wird er von einem statischen Konstruktor erstellt und mit einer statischen Eigenschaft öffentlich gemacht:

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

Die resultierende Bitmap ist 70 Pixel breit und 60 Pixel hoch:

Algorithmische Ziegelwandkachel

Der Rest der Algorithmic Brick Wall-Seite erstellt ein SKShader Objekt, das dieses Bild horizontal und vertikal wiederholt:

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

Das Ergebnis lautet wie folgt:

Algorithmische Ziegelwand

Vielleicht bevorzugen Sie etwas realistischer. In diesem Fall können Sie ein Foto einer tatsächlichen Ziegelwand aufnehmen und dann zuschneiden. Diese Bitmap ist 300 Pixel breit und 150 Pixel hoch:

Ziegelwandkachel

Diese Bitmap wird auf der Seite "Photographic Brick Wall " verwendet:

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

Beachten Sie, dass die SKShaderTileMode argumente CreateBitmap beide Mirrorsind. Diese Option ist in der Regel erforderlich, wenn Sie Kacheln verwenden, die aus realen Bildern erstellt wurden. Durch das Spiegeln der Kacheln werden Nichtigkeiten vermieden:

Fotografische Ziegelwand

Einige Arbeiten sind erforderlich, um eine geeignete Bitmap für die Kachel zu erhalten. Das funktioniert nicht sehr gut, weil der dunkleere Ziegel zu viel hervorsticht. Es erscheint regelmäßig in den wiederholten Bildern und zeigt die Tatsache, dass diese Ziegelwand aus einer kleineren Bitmap konstruiert wurde.

Der Medienordner des Beispiels enthält auch dieses Bild einer Steinwand:

Steinwandkachel

Die ursprüngliche Bitmap ist jedoch etwas zu groß für eine Kachel. Die Größe könnte geändert werden, aber die SKShader.CreateBitmap Methode kann auch die Größe der Kachel ändern, indem sie eine Transformation darauf anwendet. Diese Option wird auf der Seite Steinwand gezeigt:

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

Ein SKMatrix Wert wird erstellt, um das Bild auf die Hälfte seiner Originalgröße zu skalieren:

Steinmauer

Funktioniert die Transformation mit der ursprünglichen Bitmap, die in der CreateBitmap Methode verwendet wird? Oder transformiert es das resultierende Array von Kacheln?

Eine einfache Möglichkeit, diese Frage zu beantworten, besteht darin, eine Drehung als Teil der Transformation einzuschließen:

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

Wenn die Transformation auf die einzelne Kachel angewendet wird, sollte jedes wiederholte Bild der Kachel gedreht werden, und das Ergebnis würde viele Unterbrechungen enthalten. Aus diesem Screenshot ist jedoch offensichtlich, dass das zusammengesetzte Array von Kacheln transformiert wird:

Steinwand gedreht

Im Abschnitt "Kachelausrichtung" sehen Sie ein Beispiel für eine Übersetzungstransformation, die auf den Shader angewendet wird.

Das Beispiel simuliert einen Holzkornhintergrund mit Bitmap-Tiling basierend auf dieser 240-Pixel-quadratischen Bitmap:

Holzmaserung

Das ist ein Foto eines Holzbodens. Die SKShaderTileMode.Mirror Option ermöglicht es, als viel größere Fläche von Holz zu erscheinen:

Katzenuhr

Kachelausrichtung

Alle bisher gezeigten Beispiele haben den shader verwendet, der erstellt SKShader.CreateBitmap wurde, um den gesamten Zeichenbereich abzudecken. In den meisten Fällen verwenden Sie Bitmap-Tiling für die Ablage kleinerer Bereiche oder (seltener), um die Innenbereiche von dicken Linien zu füllen. Hier ist die fotografische Ziegelwandkachel, die für ein kleineres Rechteck verwendet wird:

Kachelausrichtung

Dies sieht für Sie möglicherweise in Ordnung aus oder nicht. Vielleicht sind Sie gestört, dass das Kachelmuster nicht mit einem vollständigen Ziegel in der oberen linken Ecke des Rechtecks beginnt. Das liegt daran, dass Shader an der Canvas und nicht an dem grafischen Objekt ausgerichtet sind, das sie schmücken.

Der Fix ist einfach. Erstellen Sie einen SKMatrix Wert basierend auf einer Übersetzungstransformation. Die Transformation verschiebt das nebeneinander angeordnete Muster effektiv an den Punkt, an dem die obere linke Ecke der Kachel ausgerichtet werden soll. Dieser Ansatz wird auf der Seite "Kachelausrichtung " veranschaulicht, auf der das Bild der oben gezeigten nicht ausgerichteten Kacheln erstellt wurde:

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

Die Seite "Kachelausrichtung " enthält eine TapGestureRecognizer. Tippen oder klicken Sie auf den Bildschirm, und das Programm wechselt mit einem SKMatrix Argument zur SKShader.CreateBitmap Methode. Diese Transformation verschiebt das Muster so, dass die obere linke Ecke einen vollständigen Ziegel enthält:

Angetippte Kachelausrichtung

Sie können diese Technik auch verwenden, um sicherzustellen, dass das nebeneinander angeordnete Bitmapmuster innerhalb des Bereichs zentriert ist, in dem es zeichnet. Auf der Seite "Zentrierte Kacheln " berechnet der PaintSurface Handler zunächst Koordinaten, als ob die einzelne Bitmap in der Mitte des Zeichenbereichs angezeigt wird. Anschließend werden diese Koordinaten verwendet, um eine Übersetzungstransformation für SKShader.CreateBitmap. Diese Transformation verschiebt das gesamte Muster so, dass eine Kachel zentriert ist:

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

Der PaintSurface Handler endet mit dem Zeichnen eines Kreises in der Mitte des Zeichenbereichs. Sicher genug, eine der Kacheln befindet sich genau in der Mitte des Kreises, und die anderen werden in einem symmetrischen Muster angeordnet:

Zentrierte Kacheln

Ein weiterer Zentrierungsansatz ist eigentlich etwas einfacher. Anstatt eine Übersetzungstransformation zu erstellen, die eine Kachel in der Mitte platziert, können Sie eine Ecke des nebeneinander angeordneten Musters zentrieren. Verwenden Sie im SKMatrix.MakeTranslation Aufruf Argumente für die Mitte des Zeichenbereichs:

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

Das Muster ist immer noch zentriert und symmetrisch, aber keine Kachel befindet sich in der Mitte:

Alternative Zentrierte Kacheln

Vereinfachung durch Drehung

Manchmal kann die Verwendung einer Drehtransformation in der SKShader.CreateBitmap Methode die Bitmapkachel vereinfachen. Dies wird deutlich, wenn versucht wird, eine Kachel für einen Kettenverknüpfungszaun zu definieren. Die ChainLinkTile.cs Datei erstellt die hier gezeigte Kachel (mit einem rosa Hintergrund für Klarheit):

Hardchainverknüpfungskachel

Die Kachel muss zwei Verknüpfungen enthalten, sodass der Code die Kachel in vier Quadranten aufteilt. Die Quadranten oben links und unten rechts sind identisch, aber sie sind nicht vollständig. Die Kabel haben kleine Noten, die mit einer zusätzlichen Zeichnung in den oberen rechten und unteren linken Quadranten behandelt werden müssen. Die Datei, die all diese Arbeit ausführt, ist 174 Zeilen lang.

Es stellt sich heraus, dass es viel einfacher ist, diese Kachel zu erstellen:

Einfachere Verkettenverknüpfungskachel

Wenn der Bitmapkachel-Shader um 90 Grad gedreht wird, sind die visuellen Elemente fast gleich.

Der Code zum Erstellen der einfacheren Kettenverknüpfungskachel ist Teil der Chain-Link-Kachelseite . Der Konstruktor bestimmt eine Kachelgröße basierend auf dem Gerätetyp, auf dem das Programm ausgeführt wird, und ruft dann auf CreateChainLinkTile, was auf der Bitmap mit Linien, Pfaden und Farbverlaufs-Shadern zeichnet:

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

Mit Ausnahme der Kabel ist die Kachel transparent, was bedeutet, dass Sie sie über etwas anderes anzeigen können. Das Programm wird in einer der Bitmapressourcen geladen, zeigt sie an, um den Zeichenbereich auszufüllen, und zeichnet dann den Shader oben:

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

Beachten Sie, dass der Shader um 45 Grad gedreht wird, sodass er wie ein echter Kettenverknüpfungszaun ausgerichtet ist:

Kettenverknüpfungszaun

Animieren von Bitmapkacheln

Sie können ein gesamtes Bitmap-Kachelmuster animieren, indem Sie die Matrixtransformation animieren. Vielleicht möchten Sie, dass das Muster horizontal oder vertikal oder beides verschoben werden soll. Dazu können Sie eine Übersetzungstransformation basierend auf den Verschiebungskoordinaten erstellen.

Es ist auch möglich, auf eine kleine Bitmap zu zeichnen oder die Pixelbits der Bitmap mit der Geschwindigkeit von 60 Mal pro Sekunde zu bearbeiten. Diese Bitmap kann dann für die Tilung verwendet werden, und das gesamte nebeneinander angeordnete Muster kann animiert werden.

Auf der Seite "Animierte Bitmapkachel " wird dieser Ansatz veranschaulicht. Eine Bitmap wird als Feld instanziiert, das 64 Pixel groß ist. Der Konstruktor ruft DrawBitmap auf, um ihm eine anfängliche Darstellung zu verleihen. Wenn das angle Feld null ist (wie beim ersten Aufruf der Methode), enthält die Bitmap zwei Linien, die als X gekreuzt sind. Die Linien sind lang genug, um unabhängig vom angle Wert immer zum Rand der Bitmap zu gelangen:

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

Der Animationsaufwand tritt in den OnAppearing Und OnDisappearing Außerkraftsetzungen auf. Die OnTimerTick Methode animiert den angle Wert von 0 Grad bis 360 Grad alle 10 Sekunden, um die X-Abbildung innerhalb der Bitmap zu drehen:

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

Aufgrund der Symmetrie der X-Abbildung entspricht dies dem Drehen des angle Werts von 0 Grad auf 90 Grad alle 2,5 Sekunden.

Der PaintSurface Handler erstellt einen Shader aus der Bitmap und verwendet das Paint-Objekt, um den gesamten Zeichenbereich zu färben:

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

Die SKShaderTileMode.Mirror Optionen stellen sicher, dass die Arme des X in jeder Bitmap-Verknüpfung mit dem X in den angrenzenden Bitmaps ein allgemeines animiertes Muster erstellen, das viel komplexer erscheint, als die einfache Animation vorschlagen würde:

Animierte Bitmap-Kachel