Partilhar via


Mosaico de bitmap SkiaSharp

Como você viu nos dois artigos anteriores, a SKShader classe pode criar gradientes lineares ou circulares. Este artigo se concentra no SKShader objeto que usa um bitmap para lado a lado de uma área. O bitmap pode ser repetido horizontal e verticalmente, em sua orientação original ou alternadamente invertido horizontal e verticalmente. O inversão evita descontinuidades entre as telhas:

Exemplo de mosaico de bitmap

O método estático SKShader.CreateBitmap que cria esse sombreador tem um SKBitmap parâmetro e dois membros da SKShaderTileMode enumeração:

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

Os dois parâmetros indicam os modos usados para mosaico horizontal e mosaico vertical. Essa é a mesma SKShaderTileMode enumeração que também é usada com os métodos de gradiente.

Uma CreateBitmap sobrecarga inclui um SKMatrix argumento para executar uma transformação nos bitmaps lado a lado:

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

Este artigo contém vários exemplos de como usar essa transformação de matriz com bitmaps lado a lado.

Explorando os modos de bloco

O primeiro programa na seção Bitmap Tiling da página Shaders and other Effects do exemplo demonstra os efeitos dos dois SKShaderTileMode argumentos. O arquivo XAML Bitmap Tile Flip Modes instancia um SKCanvasView e dois Picker modos de exibição que permitem selecionar um SKShaderTilerMode valor para mosaico horizontal e vertical. Observe que uma matriz dos SKShaderTileMode membros é definida na Resources seção:

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

O construtor do arquivo code-behind é carregado no recurso de bitmap que mostra um macaco sentado. Primeiro, ele corta a imagem usando o ExtractSubset método de SKBitmap modo que a cabeça e os pés estejam tocando as bordas do bitmap. O construtor, em seguida, usa o Resize método para criar outro bitmap de metade do tamanho. Essas alterações tornam o bitmap um pouco mais adequado para o lado a lado:

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

O PaintSurface manipulador obtém as SKShaderTileMode configurações dos dois Picker modos de exibição e cria um SKShader objeto com base no bitmap e nesses dois valores. Este sombreador é usado para preencher a tela:

Modos de inversão de bloco de bitmap

A tela do iOS à esquerda mostra o efeito dos valores padrão de SKShaderTileMode.Clamp. O bitmap fica no canto superior esquerdo. Abaixo do bitmap, a linha inferior de pixels é repetida até o fim. À direita do bitmap, a coluna mais à direita de pixels é repetida por todo o caminho. O restante da tela é colorido pelo pixel marrom escuro no canto inferior direito do bitmap. Deve ser óbvio que a Clamp opção quase nunca é usada com bitmap tiling!

A tela do Android no centro mostra o resultado de SKShaderTileMode.Repeat para ambos os argumentos. A telha é repetida horizontal e verticalmente. A tela da Plataforma Universal do Windows mostra SKShaderTileMode.Mirror. Os blocos são repetidos, mas alternadamente invertidos horizontal e verticalmente. A vantagem dessa opção é que não há descontinuidades entre as telhas.

Tenha em mente que você pode usar diferentes opções para a repetição horizontal e vertical. Você pode especificar SKShaderTileMode.Mirror como o segundo argumento para CreateBitmap , mas SKShaderTileMode.Repeat como o terceiro argumento. Em cada fila, os macacos ainda alternam entre a imagem normal e a imagem espelhada, mas nenhum dos macacos está de cabeça para baixo.

Planos de fundo padronizados

O mosaico de bitmap é comumente usado para criar um plano de fundo padronizado a partir de um bitmap relativamente pequeno. O exemplo clássico é uma parede de tijolos.

A página Algorithmic Brick Wall cria um pequeno bitmap que se assemelha a um tijolo inteiro e duas metades de um tijolo separadas por argamassa. Como esse tijolo também é usado no próximo exemplo, ele é criado por um construtor estático e tornado público com uma propriedade estática:

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

O bitmap resultante tem 70 pixels de largura e 60 pixels de altura:

Telha de parede de tijolo algorítmico

O restante da página Algorithmic Brick Wall cria um SKShader objeto que repete essa imagem horizontal 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);
        }
    }
}

Eis o resultado:

Parede de tijolo algorítmico

Você pode preferir algo um pouco mais realista. Nesse caso, você pode tirar uma fotografia de uma parede de tijolos real e, em seguida, cortá-la. Este bitmap tem 300 pixels de largura e 150 pixels de altura:

Revestimento de parede de tijolo

Este bitmap é usado na página Parede de tijolos fotográficos:

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

Observe que os SKShaderTileMode argumentos para CreateBitmap são ambos Mirror. Essa opção geralmente é necessária quando você usa blocos criados a partir de imagens do mundo real. O espelhamento dos blocos evita descontinuidades:

Parede de tijolo fotográfico

Algum trabalho é necessário para obter um bitmap adequado para o bloco. Este não funciona muito bem porque o tijolo mais escuro se destaca demais. Ele aparece regularmente nas imagens repetidas, revelando o fato de que esta parede de tijolos foi construída a partir de um bitmap menor.

A pasta Mídia do exemplo também inclui esta imagem de uma parede de pedra:

Revestimento de parede de pedra

No entanto, o bitmap original é um pouco grande demais para um bloco. Ele pode ser redimensionado, mas o SKShader.CreateBitmap método também pode redimensionar o bloco aplicando uma transformação a ele. Esta opção é demonstrada na página Parede de Pedra:

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

Um SKMatrix valor é criado para dimensionar a imagem para metade de seu tamanho original:

Parede de Pedra

A transformação opera no bitmap original usado no CreateBitmap método? Ou transforma a matriz resultante de blocos?

Uma maneira fácil de responder a essa pergunta é incluir uma rotação como parte da transformação:

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

Se a transformação for aplicada ao bloco individual, cada imagem repetida do bloco deverá ser girada e o resultado conterá muitas descontinuidades. Mas é óbvio a partir desta captura de tela que a matriz composta de blocos é transformada:

Parede de pedra girada

Na seção Alinhamento de blocos, você verá um exemplo de transformação de conversão aplicada ao sombreador.

O exemplo simula um plano de fundo de grão de madeira usando mosaico de bitmap com base nesse bitmap quadrado de 240 pixels:

Madeira de Grão

Trata-se de uma fotografia de um piso de madeira. A SKShaderTileMode.Mirror opção permite que ele apareça como uma área muito maior de madeira:

Relógio do gato

Alinhamento de blocos

Todos os exemplos mostrados até agora usaram o sombreador criado por SKShader.CreateBitmap para cobrir toda a tela. Na maioria dos casos, você usará mosaico de bitmap para arquivar áreas menores ou (mais raramente) para preencher o interior de linhas grossas. Aqui está o revestimento fotográfico de parede de tijolos usado para um retângulo menor:

Alinhamento de blocos

Isso pode parecer bom para você, ou talvez não. Talvez você esteja incomodado com o fato de o padrão de azulejos não começar com um tijolo cheio no canto superior esquerdo do retângulo. Isso porque os sombreadores estão alinhados com a tela e não com o objeto gráfico que eles adornam.

A correção é simples. Crie um SKMatrix valor com base em uma transformação de tradução. A transformação efetivamente desloca o padrão lado a lado para o ponto em que você deseja que o canto superior esquerdo do bloco seja alinhado. Essa abordagem é demonstrada na página Alinhamento de Blocos, que criou a imagem dos blocos não alinhados mostrada acima:

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

A página Alinhamento de Blocos inclui um TapGestureRecognizerarquivo . Toque ou clique na tela e o programa alterna para o SKShader.CreateBitmap método com um SKMatrix argumento. Essa transformação desloca o padrão para que o canto superior esquerdo contenha um bloco completo:

Alinhamento de blocos com toque

Você também pode usar essa técnica para garantir que o padrão de bitmap lado a lado esteja centralizado na área que ele pinta. Na página Blocos Centralizados, o PaintSurface manipulador primeiro calcula as coordenadas como se fosse exibir o único bitmap no centro da tela. Em seguida, ele usa essas coordenadas para criar uma transformação de tradução para SKShader.CreateBitmap. Essa transformação desloca todo o padrão para que um bloco seja centralizado:

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

O PaintSurface manipulador conclui desenhando um círculo no centro da tela. Com certeza, um dos azulejos está exatamente no centro do círculo, e os outros estão dispostos em um padrão simétrico:

Telhas Centralizadas

Outra abordagem de centralização é realmente um pouco mais fácil. Em vez de construir uma transformação de tradução que coloque um bloco no centro, você pode centralizar um canto do padrão lado a lado. SKMatrix.MakeTranslation Na chamada, use argumentos para o centro da tela:

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

O padrão ainda é centralizado e simétrico, mas nenhum bloco está no centro:

Blocos centralizados alternativos

Simplificação através da rotação

Às vezes, o SKShader.CreateBitmap uso de uma transformação de rotação no método pode simplificar o bloco de bitmap. Isso fica evidente quando se tenta definir uma telha para uma cerca de corrente. O arquivo ChainLinkTile.cs cria o bloco mostrado aqui (com um plano de fundo rosa para fins de clareza):

Telha rígida de elo de corrente

O bloco precisa incluir dois links, para que o código divida o bloco em quatro quadrantes. Os quadrantes superior esquerdo e inferior direito são os mesmos, mas não estão completos. Os fios têm pequenos entalhes que devem ser manuseados com algum desenho adicional nos quadrantes superior direito e inferior esquerdo. O arquivo que faz todo esse trabalho tem 174 linhas.

Acaba por ser muito mais fácil criar este bloco:

Bloco de elo de corrente mais fácil

Se o sombreador de bloco de bitmap for girado 90 graus, os visuais serão quase os mesmos.

O código para criar o bloco de link de cadeia mais fácil faz parte da página Bloco de link de cadeia. O construtor determina um tamanho de bloco com base no tipo de dispositivo em que o programa está sendo executado e, em seguida, chama CreateChainLinkTile, que se baseia no bitmap usando linhas, caminhos e sombreadores de gradiente:

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

Com exceção dos fios, a telha é transparente, o que significa que você pode exibi-la em cima de outra coisa. O programa carrega em um dos recursos de bitmap, exibe para preencher a tela e, em seguida, desenha o sombreador na parte superior:

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

Observe que o sombreador é girado 45 graus para que ele seja orientado como uma verdadeira cerca de elo de corrente:

Cerca de elo de corrente

Animando blocos de bitmap

Você pode animar um padrão de bloco de bitmap inteiro animando a transformação de matriz. Talvez você queira que o padrão se mova horizontalmente ou verticalmente ou ambos. Você pode fazer isso criando uma transformação de tradução com base nas coordenadas variáveis.

Também é possível desenhar em um pequeno bitmap ou manipular os bits de pixel do bitmap na taxa de 60 vezes por segundo. Esse bitmap pode ser usado para mosaico, e todo o padrão lado a lado pode parecer animado.

A página Bloco de bitmap animado demonstra essa abordagem. Um bitmap é instanciado como um campo para ser quadrado de 64 pixels. O construtor chama DrawBitmap para dar-lhe uma aparência inicial. Se o angle campo for zero (como é quando o método é chamado pela primeira vez), o bitmap conterá duas linhas cruzadas como um X. As linhas são longas o suficiente para sempre alcançar a borda do bitmap, independentemente do angle valor:

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

A sobrecarga de OnAppearing animação ocorre nas substituições e OnDisappearing . O OnTimerTick método anima o angle valor de 0 graus a 360 graus a cada 10 segundos para girar a figura X dentro do 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;
    }
    ···
}

Devido à simetria da figura X, isso é o mesmo que girar o angle valor de 0 graus a 90 graus a cada 2,5 segundos.

O PaintSurface manipulador cria um sombreador a partir do bitmap e usa o objeto paint para colorir toda a tela:

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

As SKShaderTileMode.Mirror opções garantem que os braços do X em cada bitmap se unam ao X nos bitmaps adjacentes para criar um padrão animado geral que parece muito mais complexo do que a animação simples sugeriria:

Bloco de bitmap animado