Affiancamento bitmap skiaSharp

Download Sample Scaricare l'esempio

Download Sample Scaricare l'esempio

Come si è visto nei due articoli precedenti, la SKShader classe può creare sfumature lineari o circolari. Questo articolo è incentrato sull'oggetto SKShader che usa una bitmap per affiancare un'area. La bitmap può essere ripetuta orizzontalmente e verticalmente, nell'orientamento originale o in alternativa capovolta orizzontalmente e verticalmente. Il capovolgimento evita interruzioni tra i riquadri:

Bitmap Tiling Sample

Il metodo statico SKShader.CreateBitmap che crea questo shader ha un SKBitmap parametro e due membri dell'enumerazione SKShaderTileMode :

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

I due parametri indicano le modalità usate per la tillatura orizzontale e la tillatura verticale. Si tratta della stessa SKShaderTileMode enumerazione usata anche con i metodi sfumatura.

Un CreateBitmap overload include un SKMatrix argomento per eseguire una trasformazione nelle bitmap affiancate:

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

Questo articolo contiene diversi esempi di utilizzo di questa trasformazione matrice con bitmap affiancate.

Esplorazione delle modalità riquadro

Il primo programma nella sezione Bitmap Tiling degli shader e di altri effetti dell'esempio SkiaSharpFormsDemos illustra gli effetti dei due SKShaderTileMode argomenti. Il file XAML Bitmap Tile Flip Mode crea un'istanza di e SKCanvasView due Picker visualizzazioni che consentono di selezionare un SKShaderTilerMode valore per la tillatura orizzontale e verticale. Si noti che nella sezione è definita Resources una matrice dei SKShaderTileMode membri:

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

Il costruttore del file code-behind viene caricato nella risorsa bitmap che mostra una scimmia seduta. Prima di tutto ritaglia l'immagine usando il ExtractSubset metodo di SKBitmap in modo che la testa e i piedi tocchino i bordi della bitmap. Il costruttore usa quindi il Resize metodo per creare un'altra bitmap di metà delle dimensioni. Queste modifiche rendono la bitmap un po'più adatta per la 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);
        }
    }
}

Il PaintSurface gestore ottiene le SKShaderTileMode impostazioni dalle due Picker visualizzazioni e crea un SKShader oggetto in base alla bitmap e a questi due valori. Questo shader viene usato per riempire l'area di disegno:

Bitmap Tile Flip Modes

La schermata iOS a sinistra mostra l'effetto dei valori predefiniti di SKShaderTileMode.Clamp. La bitmap si trova nell'angolo superiore sinistro. Sotto la bitmap, la riga inferiore di pixel viene ripetuta fino in basso. A destra della bitmap, la colonna più a destra di pixel viene ripetuta in tutto il percorso. Il resto dell'area di disegno è colorato dal pixel marrone scuro nell'angolo inferiore destro della bitmap. Dovrebbe essere ovvio che l'opzione Clamp è quasi mai usata con la legatura bitmap!

La schermata Android al centro mostra il risultato di SKShaderTileMode.Repeat per entrambi gli argomenti. Il riquadro viene ripetuto orizzontalmente e verticalmente. La schermata piattaforma UWP (Universal Windows Platform) mostra SKShaderTileMode.Mirror. I riquadri vengono ripetuti ma in alternativa capovolti orizzontalmente e verticalmente. Il vantaggio di questa opzione è che non vi sono interruzioni tra i riquadri.

Tenere presente che è possibile usare diverse opzioni per la ripetizione orizzontale e verticale. È possibile specificare SKShaderTileMode.Mirror come secondo argomento, CreateBitmap ma SKShaderTileMode.Repeat come terzo argomento. Su ogni riga, le scimmie si alternano ancora tra l'immagine normale e l'immagine speculare, ma nessuno delle scimmie è capovolto.

Sfondi con pattern

L'associazione bitmap viene comunemente usata per creare uno sfondo basato su criteri da una bitmap relativamente piccola. L'esempio classico è un muro di mattoni.

La pagina Algorithmic Brick Wall crea una piccola bitmap che assomiglia a un intero mattoni e due metà di un mattone separato da malta. Poiché questo mattoni viene usato anche nell'esempio successivo, viene creato da un costruttore statico e reso pubblico con una proprietà statica:

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

La bitmap risultante ha una larghezza di 70 pixel e un'altezza di 60 pixel:

Algorithmic Brick Wall Tile

Il resto della pagina Algorithmic Brick Wall crea un SKShader oggetto che ripete l'immagine orizzontalmente e verticalmente:

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

Il risultato è il seguente:

Algorithmic Brick Wall

Potresti preferire qualcosa di più realistico. In tal caso, è possibile scattare una fotografia di un muro di mattoni effettivo e quindi ritagliarlo. Questa bitmap ha una larghezza di 300 pixel e un'altezza di 150 pixel:

Brick Wall Tile

Questa bitmap viene usata nella pagina Muro di mattoni fotografici:

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

Si noti che gli SKShaderTileMode argomenti di CreateBitmap sono entrambi Mirror. Questa opzione è in genere necessaria quando si usano riquadri creati da immagini reali. Il mirroring dei riquadri evita interruzioni:

Photographic Brick Wall

Alcune operazioni sono necessarie per ottenere una bitmap adatta per il riquadro. Questo non funziona molto bene perché il mattoni più scuro si distingue troppo. Appare regolarmente all'interno delle immagini ripetute, rivelando il fatto che questo muro di mattoni è stato costruito da una bitmap più piccola.

La cartella Media dell'esempio SkiaSharpFormsDemos include anche questa immagine di una parete in pietra:

Stone Wall Tile

Tuttavia, la bitmap originale è un po ' troppo grande per un riquadro. Potrebbe essere ridimensionato, ma il SKShader.CreateBitmap metodo può anche ridimensionare il riquadro applicando una trasformazione. Questa opzione è illustrata nella pagina Stone Wall :

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

Viene creato un SKMatrix valore per ridimensionare l'immagine a metà delle dimensioni originali:

Stone Wall

La trasformazione opera sulla bitmap originale usata nel CreateBitmap metodo ? Oppure trasforma la matrice risultante di riquadri?

Un modo semplice per rispondere a questa domanda consiste nell'includere una rotazione come parte della trasformazione:

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

Se la trasformazione viene applicata al singolo riquadro, ogni immagine ripetuta del riquadro deve essere ruotata e il risultato conterrà molte discontinuità. Tuttavia, è ovvio da questo screenshot che la matrice composita di riquadri viene trasformata:

Stone Wall Rotated

Nella sezione Allineamento riquadro verrà visualizzato un esempio di trasformazione traduci applicata allo shader.

L'esempio autonomo cat clock (non parte di SkiaSharpFormsDemos) simula uno sfondo con granularità di legno usando la affiancatura bitmap basata su questa bitmap quadrata di 240 pixel:

Wood Grain

Questa è una fotografia di un pavimento in legno. L'opzione SKShaderTileMode.Mirror consente di apparire come un'area di legno molto più grande:

Cat Clock

Allineamento dei riquadri

Tutti gli esempi mostrati finora hanno usato lo shader creato da SKShader.CreateBitmap per coprire l'intera area di disegno. Nella maggior parte dei casi, si userà la legatura bitmap per l'archiviazione di aree più piccole o (più raramente) per riempire gli interni di linee spesse. Ecco il riquadro a parete fotografica usato per un rettangolo più piccolo:

Tile Alignment

Questo potrebbe sembrare bene per te, o forse no. Forse sei disturbato che il motivo di tillatura non inizia con un mattoni pieno nell'angolo superiore sinistro del rettangolo. Questo perché gli shader sono allineati all'area di disegno e non all'oggetto grafico che adornano.

La correzione è semplice. Creare un SKMatrix valore basato su una trasformazione di traduzione. La trasformazione sposta in modo efficace il modello affiancato nel punto in cui si desidera che l'angolo superiore sinistro del riquadro venga allineato. Questo approccio è illustrato nella pagina Allineamento riquadri, che ha creato l'immagine dei riquadri non allineati illustrati sopra:

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

La pagina Allineamento riquadri include un oggetto TapGestureRecognizer. Toccare o fare clic sulla schermata e il programma passa al SKShader.CreateBitmap metodo con un SKMatrix argomento. Questa trasformazione sposta il motivo in modo che l'angolo superiore sinistro contenga un mattone pieno:

Tile Alignment Tapped

È anche possibile usare questa tecnica per assicurarsi che il modello bitmap affiancato sia centrato all'interno dell'area che disegna. Nella pagina Riquadri centrati il PaintSurface gestore calcola innanzitutto le coordinate come se visualizzerà la singola bitmap al centro dell'area di disegno. Usa quindi queste coordinate per creare una trasformazione traduci per SKShader.CreateBitmap. Questa trasformazione sposta l'intero modello in modo che un riquadro sia centrato:

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

Il PaintSurface gestore si conclude disegnando un cerchio al centro dell'area di disegno. Abbastanza sicuro, uno dei riquadri è esattamente al centro del cerchio, e gli altri sono disposti in un modello simmetrico:

Centered Tiles

Un altro approccio centrato è in realtà un po'più semplice. Invece di costruire una trasformazione di traslazione che inserisce un riquadro al centro, è possibile centrare un angolo del modello affiancato. SKMatrix.MakeTranslation Nella chiamata usare gli argomenti per il centro dell'area di disegno:

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

Il modello è ancora centrato e simmetrico, ma nessun riquadro si trova al centro:

Centered Tiles Alternate

Semplificazione tramite rotazione

A volte l'uso di una trasformazione di rotazione nel SKShader.CreateBitmap metodo può semplificare il riquadro bitmap. Questo diventa evidente quando si tenta di definire un riquadro per un recinto di collegamento a catena. Il file ChainLinkTile.cs crea il riquadro mostrato qui (con uno sfondo rosa per motivi di chiarezza):

Hard chain-link tile

Il riquadro deve includere due collegamenti, in modo che il codice divide il riquadro in quattro quadranti. I quadranti in alto a sinistra e in basso a destra sono gli stessi, ma non sono completi. I fili hanno piccole tacche che devono essere gestite con un disegno aggiuntivo nei quadranti in alto a destra e in basso a sinistra. Il file che esegue tutto questo lavoro è lungo 174 righe.

Risulta molto più semplice creare questo riquadro:

Easier chain-link tile

Se lo shader bitmap-tile è ruotato di 90 gradi, gli oggetti visivi sono quasi uguali.

Il codice per creare il riquadro concatenamento più semplice fa parte della pagina Chain-Link Tile . Il costruttore determina una dimensione del riquadro in base al tipo di dispositivo su cui è in esecuzione il programma e quindi chiama CreateChainLinkTile, che disegna sulla bitmap usando linee, percorsi e shader sfumature:

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

Ad eccezione dei fili, il riquadro è trasparente, il che significa che è possibile visualizzarlo sopra qualcos'altro. Il programma carica in una delle risorse bitmap, lo visualizza per riempire l'area di disegno e quindi disegna lo shader in alto:

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

Si noti che lo shader viene ruotato di 45 gradi in modo che sia orientato come un vero recinto a catena:

Chain-Link Fence

Animazione dei riquadri bitmap

È possibile animare un intero modello bitmap-tile animando la trasformazione matrice. È possibile che il modello si sposti orizzontalmente o verticalmente o entrambi. A tale scopo, è possibile creare una trasformazione di conversione in base alle coordinate di spostamento.

È anche possibile disegnare su una piccola bitmap o di modificare i bit pixel della bitmap alla velocità di 60 volte al secondo. Tale bitmap può quindi essere usata per l'associazione e l'intero modello affiancato può sembrare animato.

La pagina Riquadro bitmap animata illustra questo approccio. Viene creata un'istanza di una bitmap come campo con un quadrato di 64 pixel. Il costruttore chiama DrawBitmap per dare un aspetto iniziale. Se il angle campo è zero (così com'è quando il metodo viene chiamato per la prima volta), la bitmap contiene due linee incrociate come X. Le righe sono rese sufficientemente lunghe da raggiungere sempre il bordo della bitmap indipendentemente dal angle valore:

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

L'overhead dell'animazione OnAppearing si verifica nelle sostituzioni e OnDisappearing . Il OnTimerTick metodo anima il angle valore da 0 a 360 gradi ogni 10 secondi per ruotare la figura X all'interno della 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;
    }
    ···
}

A causa della simmetria della figura X, questo equivale a ruotare il angle valore da 0 gradi a 90 gradi ogni 2,5 secondi.

Il PaintSurface gestore crea uno shader dalla bitmap e usa l'oggetto paint per colorare l'intera area di disegno:

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

Le SKShaderTileMode.Mirror opzioni assicurano che le braccia della X in ogni bitmap join con la X nelle bitmap adiacenti per creare un modello animato complessivo che sembra molto più complesso rispetto all'animazione semplice suggerisce:

Animated Bitmap Tile