SkiaSharp ビットマップのタイル表示

Download Sampleサンプルのダウンロード

Download Sampleサンプルのダウンロード

前の 2 つの記事で説明したように、SKShader クラスは線形グラデーションまたは円形グラデーションを作成できます。 この記事では、ビットマップを使用して領域をタイル表示する SKShader オブジェクトについて説明します。 ビットマップは、元の向きで水平方向と垂直方向に繰り返すこともできれば、水平方向と垂直方向に交互に反転させることもできます。 反転により、タイル間の不連続性を回避できます。

Bitmap Tiling Sample

このシェーダーを作成する静的な SKShader.CreateBitmap メソッドには、SKShaderTileMode 列挙型の SKBitmap パラメータと 2 つのメンバーが含まれます。

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

2 つのパラメータは、水平タイル表示と垂直タイル表示に使用されるモードを示します。 これは、グラデーション メソッドでも使用される SKShaderTileMode 列挙型と同じです。

CreateBitmap オーバーロードには、タイル表示されたビットマップに対して変換を実行する SKMatrix 引数が含まれます。

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

この記事では、タイル表示したビットマップでこの行列変換を使用する例をいくつか示します。

タイル モードの探索

SkiaSharpFormsDemos サンプルの [シェーダーとその他の効果] ページの [ビットマップのタイル] セクションの最初のプログラムは、2 つの SKShaderTileMode 引数の効果を示しています。 Bitmap Tile Flip Modes XAML ファイルは、水平方向と垂直方向のタイルの SKShaderTilerMode 値を選択できる SKCanvasView と 2 つの Picker ビューをインスタンス化します。 SKShaderTileMode メンバーの配列が Resources セクションで定義されていることに注意してください。

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

分離コード ファイルのコンストラクターは、座っているサルを示すビットマップ リソースに読み込まれます。 まず、SKBitmapExtractSubset メソッドを使用して画像をトリミングすることで、頭と足がビットマップの端に触れるようにします。 次に、コンストラクターは Resize メソッドを 使用して、半分のサイズの別のビットマップを作成します。 これらの変更により、ビットマップはタイル表示に多少ふさわしくなります。

public partial class BitmapTileFlipModesPage : ContentPage
{
    SKBitmap bitmap;

    public BitmapTileFlipModesPage ()
    {
        InitializeComponent ();

        SKBitmap origBitmap = BitmapExtensions.LoadBitmapResource(
            GetType(), "SkiaSharpFormsDemos.Media.SeatedMonkey.jpg");

        // Define cropping rect
        SKRectI cropRect = new SKRectI(5, 27, 296, 260);

        // Get the cropped bitmap
        SKBitmap croppedBitmap = new SKBitmap(cropRect.Width, cropRect.Height);
        origBitmap.ExtractSubset(croppedBitmap, cropRect);

        // Resize to half the width and height
        SKImageInfo info = new SKImageInfo(cropRect.Width / 2, cropRect.Height / 2);
        bitmap = croppedBitmap.Resize(info, SKBitmapResizeMethod.Box);
    }

    void OnPickerSelectedIndexChanged(object sender, EventArgs args)
    {
        canvasView.InvalidateSurface();
    }

    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        canvas.Clear();

        // Get tile modes from Pickers
        SKShaderTileMode xTileMode =
            (SKShaderTileMode)(xModePicker.SelectedIndex == -1 ?
                                        0 : xModePicker.SelectedItem);
        SKShaderTileMode yTileMode =
            (SKShaderTileMode)(yModePicker.SelectedIndex == -1 ?
                                        0 : yModePicker.SelectedItem);

        using (SKPaint paint = new SKPaint())
        {
            paint.Shader = SKShader.CreateBitmap(bitmap, xTileMode, yTileMode);
            canvas.DrawRect(info.Rect, paint);
        }
    }
}

PaintSurface ハンドラーは、2 つの Picker ビューから SKShaderTileMode 設定を取得し、ビットマップとそれらの 2 つの値に基づいて SKShader オブジェクトを作成します。 このシェーダーは、キャンバスを埋めるために使用されます。

Bitmap Tile Flip Modes

左側の iOS 画面は SKShaderTileMode.Clamp の既定値を示しています。 ビットマップは左上隅に配置されます。 ビットマップの下では、ピクセルの下の行が下方向に繰り返されます。 ビットマップの右側では、ピクセルの右端の列が横方向に繰り返されます。 キャンバスの残りの部分は、ビットマップの右下隅にある濃い茶色のピクセルによって色付けされます。 Clamp オプションがビットマップのタイル表示でほとんど使用されないのは明らかです。

中央の Android 画面には、両方の引数に対する SKShaderTileMode.Repeat の結果が表示されます。 タイルは水平方向と垂直方向に繰り返されます。 ユニバーサル Windows プラットフォームは SKShaderTileMode.Mirror を表示します。 タイルは繰り返されますが、水平方向と垂直方向に交互に反転します。 このオプションの利点は、タイル間に不連続性が存在しないということです。

水平方向と垂直方向の繰り返しには、さまざまなオプションを使用できることに注意してください。 CreateBitmap に対する 2 番目の引数として SKShaderTileMode.Mirror を指定できますが、3 番目の引数として SKShaderTileMode.Repeat を指定できます。 各行では、サルの通常の画像と反転画像が交互に表示されますが、サルが逆さまで表示されることはありません。

パターン化された背景

ビットマップのタイルは、比較的小さなビットマップからパターン化された背景を作成するために一般的に使用されます。 古典的な例はレンガの壁です。

[アルゴリズムのレンガの壁] ページは、しっくいで区切られた 1 つのレンガと 2 つの半分のレンガに似た小さなビットマップを作成します。 このレンガは次のサンプルでも使用されるため、静的コンストラクターによって作成され、静的プロパティで公開されます。

public class AlgorithmicBrickWallPage : ContentPage
{
    static AlgorithmicBrickWallPage()
    {
        const int brickWidth = 64;
        const int brickHeight = 24;
        const int morterThickness = 6;
        const int bitmapWidth = brickWidth + morterThickness;
        const int bitmapHeight = 2 * (brickHeight + morterThickness);

        SKBitmap bitmap = new SKBitmap(bitmapWidth, bitmapHeight);

        using (SKCanvas canvas = new SKCanvas(bitmap))
        using (SKPaint brickPaint = new SKPaint())
        {
            brickPaint.Color = new SKColor(0xB2, 0x22, 0x22);

            canvas.Clear(new SKColor(0xF0, 0xEA, 0xD6));
            canvas.DrawRect(new SKRect(morterThickness / 2,
                                       morterThickness / 2,
                                       morterThickness / 2 + brickWidth,
                                       morterThickness / 2 + brickHeight),
                                       brickPaint);

            int ySecondBrick = 3 * morterThickness / 2 + brickHeight;

            canvas.DrawRect(new SKRect(0,
                                       ySecondBrick,
                                       bitmapWidth / 2 - morterThickness / 2,
                                       ySecondBrick + brickHeight),
                                       brickPaint);

            canvas.DrawRect(new SKRect(bitmapWidth / 2 + morterThickness / 2,
                                       ySecondBrick,
                                       bitmapWidth,
                                       ySecondBrick + brickHeight),
                                       brickPaint);
        }

        // Save as public property for other programs
        BrickWallTile = bitmap;
    }

    public static SKBitmap BrickWallTile { private set; get; }
    ···
}

結果のビットマップの幅は 70 ピクセル、高さは 60 ピクセルです。

Algorithmic Brick Wall Tile

[アルゴリズムのレンガの壁]ページの残りの部分は、この画像を水平方向と垂直方向に繰り返す SKShader オブジェクトを作成します。

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

結果は次のとおりです。

Algorithmic Brick Wall

もう少し現実的なものがお好みかもしれません。 その場合は、実際のレンガの壁の写真を撮ってトリミングできます。 このビットマップの幅は 300 ピクセル、高さは 150 ピクセルです。

Brick Wall Tile

このビットマップは [写真のレンガの壁]ページで使用されています。

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

CreateBitmap に対する SKShaderTileMode 引数が両方とも Mirror であることに注意してください。 通常、このオプションは、実際の画像から作成したタイルを使用する場合に必要です。 タイルを反転すると、不連続性を回避できます。

Photographic Brick Wall

タイルに適したビットマップを取得するには、何らかの作業が必要です。 暗いレンガが目立ちすぎているので、これはあまりうまく機能しません。 繰り返しの画像内に規則正しく表示されるため、このレンガの壁がより小さなビットマップから構築されたという事実が明らかになります。

SkiaSharpFormsDemos サンプルの [Media] フォルダーには、次の石壁の画像も含まれています。

Stone Wall Tile

ただし、元のビットマップはタイルとしては少し大きすぎます。 サイズを変更することもできますが、SKShader.CreateBitmap メソッドで変換を適用してタイルのサイズを変更することもできます。 このオプションは [石壁] ページで示されています。

public class StoneWallPage : ContentPage
{
    SKBitmap bitmap = BitmapExtensions.LoadBitmapResource(
                        typeof(StoneWallPage),
                        "SkiaSharpFormsDemos.Media.StoneWallTile.jpg");

    public StoneWallPage()
    {
        Title = "Stone Wall";

        SKCanvasView canvasView = new SKCanvasView();
        canvasView.PaintSurface += OnCanvasViewPaintSurface;
        Content = canvasView;
    }

    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        canvas.Clear();

        using (SKPaint paint = new SKPaint())
        {
            // Create scale transform
            SKMatrix matrix = SKMatrix.MakeScale(0.5f, 0.5f);

            // Create bitmap tiling
            paint.Shader = SKShader.CreateBitmap(bitmap,
                                                 SKShaderTileMode.Mirror,
                                                 SKShaderTileMode.Mirror,
                                                 matrix);
            // Draw background
            canvas.DrawRect(info.Rect, paint);
        }
    }
}

SKMatrix 値を作成すると、画像を元のサイズの半分にスケーリングできます。

Stone Wall

変換機能は、CreateBitmap メソッドで使用される元のビットマップで機能するでしょうか。 もしくは、タイルの結果の配列を変換するのでしょうか。

この質問に答える簡単な方法は、変換の一部として回転を含めるということです。

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

変換が個々のタイルに適用されると、タイルの各繰り返し画像を回転する必要があり、結果には多くの不連続性が含まれます。 しかし、このスクリーンショットから、タイルの複合配列が変換されていることは明らかです。

Stone Wall Rotated

[タイルの配置] セクションには、シェーダーに適用された平行移動変換の例が表示されます。

スタンドアロンのネコ時計サンプル (SkiaSharpFormsDemos の一部ではありません) は、この 240 ピクセルの正方形ビットマップに基づくビットマップのタイルを使用して木目の背景をシミュレートします。

Wood Grain

それは木の床の写真です。 SKShaderTileMode.Mirror オプションを使用すると、木材のはるかに広い領域のように見えます。

Cat Clock

タイルの配置

これまでに示したすべての例では、キャンバス全体をカバーするために SKShader.CreateBitmap が作成したシェーダーを使用してきました。 ほとんどの場合、小さい領域を埋めるためにビットマップのタイルを使用するか、太線の内部を埋めるために (まれに) 使用します。 小さな四角形に使用される写真のレンガ壁タイルを次に示します。

Tile Alignment

これでいいと思うこともあれば、そうでないこともあります。 四角形の左上隅でタイル パターンが完全なレンガで始まらないことが気に入らないかもしれません。 これは、シェーダーが装飾するグラフィカル オブジェクトではなくキャンバスに配置されるためです。

修正は簡単です。 平行移動変換に基づき SKMatrix 値を作成します。 変換により、タイルの左上隅を配置する位置にタイル パターンを効果的にシフトできます。 この方法は、上に示した配置されていないタイルの画像を作成した [タイルの配置] ページで示されています。

public class TileAlignmentPage : ContentPage
{
    bool isAligned;

    public TileAlignmentPage()
    {
        Title = "Tile Alignment";

        SKCanvasView canvasView = new SKCanvasView();
        canvasView.PaintSurface += OnCanvasViewPaintSurface;

        // Add tap handler
        TapGestureRecognizer tap = new TapGestureRecognizer();
        tap.Tapped += (sender, args) =>
        {
            isAligned ^= true;
            canvasView.InvalidateSurface();
        };
        canvasView.GestureRecognizers.Add(tap);

        Content = canvasView;
    }

    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        canvas.Clear();

        using (SKPaint paint = new SKPaint())
        {
            SKRect rect = new SKRect(info.Width / 7,
                                     info.Height / 7,
                                     6 * info.Width / 7,
                                     6 * info.Height / 7);

            // Get bitmap from other program
            SKBitmap bitmap = AlgorithmicBrickWallPage.BrickWallTile;

            // Create bitmap tiling
            if (!isAligned)
            {
                paint.Shader = SKShader.CreateBitmap(bitmap,
                                                     SKShaderTileMode.Repeat,
                                                     SKShaderTileMode.Repeat);
            }
            else
            {
                SKMatrix matrix = SKMatrix.MakeTranslation(rect.Left, rect.Top);

                paint.Shader = SKShader.CreateBitmap(bitmap,
                                                     SKShaderTileMode.Repeat,
                                                     SKShaderTileMode.Repeat,
                                                     matrix);
            }

            // Draw rectangle
            canvas.DrawRect(rect, paint);
        }
    }
}

[タイルの配置] ページには TapGestureRecognizer が含まれています。 画面をタップまたはクリックすると、プログラムは SKMatrix 引数を持つ SKShader.CreateBitmap メソッドに切り替わります。 この変換によりパターンがシフトし、左上隅に完全なレンガが含まれます。

Tile Alignment Tapped

また、この手法を使用して、タイル表示されたビットマップ パターンが描画される領域内の中央に配置されるようにすることもできます。 [中央揃えのタイル] ページで、PaintSurface ハンドラーは最初にキャンバスの中央に 1 つのビットマップを表示するかのように座標を計算します。 次に、これらの座標を使用して SKShader.CreateBitmap の平行移動変換を作成します。 この変換により、タイルが中央に配置されるようにパターン全体がシフトします。

public class CenteredTilesPage : ContentPage
{
    SKBitmap bitmap = BitmapExtensions.LoadBitmapResource(
                        typeof(CenteredTilesPage),
                        "SkiaSharpFormsDemos.Media.monkey.png");

    public CenteredTilesPage ()
    {
        Title = "Centered Tiles";

        SKCanvasView canvasView = new SKCanvasView();
        canvasView.PaintSurface += OnCanvasViewPaintSurface;
        Content = canvasView;
    }

    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        canvas.Clear();

        // Find coordinates to center bitmap in canvas...
        float x = (info.Width - bitmap.Width) / 2f;
        float y = (info.Height - bitmap.Height) / 2f;

        using (SKPaint paint = new SKPaint())
        {
            // ... but use them to create a translate transform
            SKMatrix matrix = SKMatrix.MakeTranslation(x, y);
            paint.Shader = SKShader.CreateBitmap(bitmap, 
                                                 SKShaderTileMode.Repeat, 
                                                 SKShaderTileMode.Repeat, 
                                                 matrix);

            // Use that tiled bitmap pattern to fill a circle
            canvas.DrawCircle(info.Rect.MidX, info.Rect.MidY,
                              Math.Min(info.Width, info.Height) / 2,
                              paint);
        }
    }
}

PaintSurface ハンドラーが、最後にキャンバスの中央に円を描画します。 確かに、タイルの 1 つは円の中心に正確に配置され、他のタイルは対称パターンで配置されます。

Centered Tiles

実は、もう 1 つの中央揃えアプローチの方が多少簡単です。 タイルを中央に配置する平行移動変換を構築するのではなく、タイル表示したパターンの隅を中央に配置できます。 SKMatrix.MakeTranslation の呼び出しで、キャンバスの中心に引数を使用します。

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

パターンは引き続き中央揃えで対称ですが、どのタイルも中央にありません。

Centered Tiles Alternate

回転による簡略化

SKShader.CreateBitmap メソッドで回転変換を使用すると、ビットマップ タイルを簡略化できることがあります。 これは、金網の塀のタイルを定義しようとすると明らかになります。 ChainLinkTile.cs ファイルは、ここに表示されるタイルを作成します (わかりやすくするために、ピンク色の背景を使用)。

Hard chain-link tile

このタイルは 2 つのつなぎ目を含む必要があるため、コードによってタイルが 4 つのクアドラントに分割されます。 左上と右下のクアドラントは同じですが、完全ではありません。 針金には、右上と左下のクアドラントに追加の描画を使用して処理する必要がある切れ目がほとんどありません。 この作業をすべて行うファイルの長さは 174 行です。

このタイルを作成する方がはるかに簡単です。

Easier chain-link tile

ビットマップ タイルのシェーダーを 90 度回転させると、見た目はほぼ同じです。

より簡単な金網タイルを作成するコードは、[金網タイル] ページに含まれています。 コンストラクターは、プログラムが実行されているデバイスの種類に基づいてタイル サイズを決定し、直線、パス、およびグラデーション シェーダーを使用してビットマップに描画する CreateChainLinkTile を呼び出します。

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

針金を除きタイルは透明です。つまり、他の何かの上にタイルを表示できます。 プログラムはビットマップ リソースの 1 つを読み込み、キャンバスを埋めるために表示し、その上にシェーダーを描画します。

public class ChainLinkFencePage : ContentPage
{
    SKBitmap monkeyBitmap = BitmapExtensions.LoadBitmapResource(
        typeof(ChainLinkFencePage), "SkiaSharpFormsDemos.Media.SeatedMonkey.jpg");
    ···

    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        canvas.Clear();

        canvas.DrawBitmap(monkeyBitmap, info.Rect, BitmapStretch.UniformToFill, 
                            BitmapAlignment.Center, BitmapAlignment.Start);

        using (SKPaint paint = new SKPaint())
        {
            paint.Shader = SKShader.CreateBitmap(tileBitmap, 
                                                 SKShaderTileMode.Repeat,
                                                 SKShaderTileMode.Repeat,
                                                 SKMatrix.MakeRotationDegrees(45));
            canvas.DrawRect(info.Rect, paint);
        }
    }
}

シェーダーは 45 度回転するため、本物の金網の塀のように表示されます。

Chain-Link Fence

ビットマップ タイルのアニメーション化

行列変換をアニメーション化することで、ビットマップ タイル パターン全体をアニメーション化できます。 パターンを水平方向または垂直方向または両方に移動したい場合があります。 これを行うには、シフト座標に基づいて平行移動変換を作成します。

小さなビットマップに描画し、ビットマップのピクセル ビットを 1 秒ごとに 60 回の速度で操作することもできます。 そのビットマップをタイル表示に使用でき、タイル表示されたパターン全体がアニメーション化されているように見えることがあります。

[アニメーション化されたビットマップ タイル] ページではこの方法を示します。 ビットマップは、64 ピクセルの正方形のフィールドとしてインスタンス化されます。 コンストラクターが DrawBitmap を呼び出して、最初の外観を提供します。 angle フィールドが 0 の場合 (メソッドが最初に呼び出されたときと同様)、ビットマップには X として交差する 2 つの直線が含まれます。angle 値に関係なく、直線は常にビットマップの端に到達するのに十分な長さになります。

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

アニメーションのオーバーヘッドは、OnAppearingOnDisappearing のオーバーライドで発生します。 ビットマップ内で X 図形を回転させるために、OnTimerTick メソッドは 10 秒ごとに 0 度から 360 度の angle 値をアニメーション化します。

public class AnimatedBitmapTilePage : ContentPage
{
    ···
    // For animation
    bool isAnimating;
    Stopwatch stopwatch = new Stopwatch();
    ···

    protected override void OnAppearing()
    {
        base.OnAppearing();

        isAnimating = true;
        stopwatch.Start();
        Device.StartTimer(TimeSpan.FromMilliseconds(16), OnTimerTick);
    }

    protected override void OnDisappearing()
    {
        base.OnDisappearing();

        stopwatch.Stop();
        isAnimating = false;
    }

    bool OnTimerTick()
    {
        const int duration = 10;     // seconds
        angle = (float)(360f * (stopwatch.Elapsed.TotalSeconds % duration) / duration);
        DrawBitmap();
        canvasView.InvalidateSurface();

        return isAnimating;
    }
    ···
}

X 図形の対称性のため、これは 2.5 秒ごとに 0 度から 90 度の angle 値を回転させるのと同じです。

PaintSurface ハンドラーはビットマップからシェーダーを作成し、ペイント オブジェクトを使用してキャンバス全体に色を付けます。

public class AnimatedBitmapTilePage : ContentPage
{
    ···
    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        canvas.Clear();

        using (SKPaint paint = new SKPaint())
        {
            paint.Shader = SKShader.CreateBitmap(bitmap,
                                                 SKShaderTileMode.Mirror,
                                                 SKShaderTileMode.Mirror);
            canvas.DrawRect(info.Rect, paint);
        }
    }
}

SKShaderTileMode.Mirror オプションを使用すると、各ビットマップ内の X のアームが隣接するビットマップ内の X と結合するため、単純なアニメーションよりもはるかに複雑に見える全体的なアニメーション パターンを作成できます。

Animated Bitmap Tile