SkiaSharp 비트맵 타일링

Download Sample 샘플 다운로드

Download Sample 샘플 다운로드

이전 두 문서에서 본 것처럼 클래스는 SKShader 선형 또는 원형 그라데이션을 만들 수 있습니다. 이 문서에서는 비트맵을 SKShader 사용하여 영역을 타일로 지정하는 개체에 중점을 둡니다. 비트맵은 원래 방향에서 가로 및 세로로 반복되거나 가로 및 세로로 대칭 이동될 수 있습니다. 대칭 이동은 타일 간의 불연속성을 방지합니다.

Bitmap Tiling Sample

이 셰이더를 만드는 정적 SKShader.CreateBitmap 메서드에는 SKBitmap 매개 변수와 열거형의 두 멤버가 SKShaderTileMode 있습니다.

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

두 매개 변수는 가로 바둑판식 바둑판식 배열과 세로 바둑판식 배열에 사용되는 모드를 나타냅니다. 그라데이션 메서드와 함께 사용되는 것과 동일한 SKShaderTileMode 열거형입니다.

CreateBitmap 오버로드에는 타일식 비트맵에서 변환을 수행하는 인수가 포함됩니다SKMatrix.

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

이 문서에는 타일식 비트맵과 함께 이 행렬 변환을 사용하는 몇 가지 예제가 포함되어 있습니다.

타일 모드 탐색

SkiaSharpFormsDemos 샘플의 셰이더 및 기타 효과 페이지의 비트맵 타일링 섹션에 있는 첫 번째 프로그램은 SKShaderTileMode 인수의 효과를 보여 줍니다. 비트맵 타일 대칭 이동 모드 XAML 파일은 가로 및 세로 바둑판식 배열 값을 선택할 SKShaderTilerMode 수 있는 두 개의 Picker 보기를 인스턴스화 SKCanvasView 합니다. 멤버의 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>

코드 숨김 파일의 생성자는 앉은 원숭이를 보여 주는 비트맵 리소스에 로드됩니다. 먼저 헤드와 발이 비트맵의 SKBitmap 가장자리에 닿도록 하는 방법을 사용하여 ExtractSubset 이미지를 자작합니다. 그런 다음 생성자는 메서드를 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 처리기는 두 Picker 뷰에서 설정을 가져오 SKShaderTileMode 고 비트맵과 해당 두 값을 기반으로 개체를 만듭니다SKShader. 이 셰이더는 캔버스를 채우는 데 사용됩니다.

Bitmap Tile Flip Modes

왼쪽의 iOS 화면에는 기본값 SKShaderTileMode.Clamp의 효과가 표시됩니다. 비트맵은 왼쪽 위 모서리에 있습니다. 비트맵 아래에서 픽셀의 아래쪽 행은 아래로 반복됩니다. 비트맵의 오른쪽에 있는 픽셀의 맨 오른쪽 열이 계속 반복됩니다. 캔버스의 re기본der는 비트맵의 오른쪽 아래 모서리에 있는 어두운 갈색 픽셀로 색이 지정됩니다. 이 옵션은 비트맵 타일링과 함께 거의 사용되지 않는다는 것이 Clamp 분명합니다!

가운데의 Android 화면에는 두 인수의 SKShaderTileMode.Repeat 결과가 표시됩니다. 타일은 가로 및 세로로 반복됩니다. 유니버설 Windows 플랫폼 화면에 표시됩니다SKShaderTileMode.Mirror. 타일은 반복되지만 가로 및 세로로 대칭 이동됩니다. 이 옵션의 장점은 타일 사이에 불연속성이 없다는 것입니다.

가로 및 세로 반복에 다른 옵션을 사용할 수 있습니다. 두 번째 인수 CreateBitmap 로 지정할 수 있지만 SKShaderTileMode.Repeat 세 번째 인수로 지정할 SKShaderTileMode.Mirror 수 있습니다. 각 행에서 원숭이는 여전히 일반 이미지와 미러 이미지를 번갈아 가며 있지만 원숭이는 거꾸로 되어 있지 않습니다.

패턴이 지정된 배경

비트맵 타일링은 일반적으로 상대적으로 작은 비트맵에서 패턴화된 배경을 만드는 데 사용됩니다. 고전적인 예는 벽돌 벽입니다.

알고리즘 벽돌 벽 페이지는 전체 벽돌과 박격포로 분리 된 벽돌의 두 반쪽을 닮은 작은 비트맵을 만듭니다. 이 벽돌은 다음 샘플에서도 사용되므로 정적 생성자에 의해 생성되고 정적 속성으로 공개됩니다.

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

인수 CreateBitmapSKShaderTileMode 둘 다Mirror입니다. 이 옵션은 일반적으로 실제 이미지에서 만든 타일을 사용하는 경우에 필요합니다. 타일을 미러링하면 불연속성이 방지됩니다.

Photographic Brick Wall

타일에 적합한 비트맵을 얻으려면 일부 작업이 필요합니다. 어두운 벽돌이 너무 눈에 띄는 때문에이 작업은 잘 작동하지 않습니다. 그것은 반복 된 이미지 내에서 정기적으로 나타납니다., 이 벽돌 벽은 작은 비트맵에서 구성 된 사실을 공개.

SkiaSharpFormsDemos 샘플의 미디어 폴더에는 돌벽 이미지도 포함됩니다.

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

섹션 타일 맞춤에는 셰이더에 적용된 변환 예제가 표시됩니다.

독립 실행형 Cat Clock 샘플(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 화면을 탭하거나 클릭하면 프로그램이 인수를 사용하여 SKShader.CreateBitmap 메서드로 SKMatrix 전환됩니다. 이 변환은 왼쪽 위 모서리에 전체 벽돌이 포함되도록 패턴을 이동합니다.

Tile Alignment Tapped

또한 이 기술을 사용하여 타일식 비트맵 패턴이 그리는 영역 내에서 가운데에 배치되도록 할 수 있습니다. 가운데 맞춤 타일 페이지에서 PaintSurface 처리기는 먼저 캔버스 중앙에 단일 비트맵을 표시하는 것처럼 좌표를 계산합니다. 그런 다음 이러한 좌표를 사용하여 변환 변환을 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 캔버스 중앙에 원을 그려 마무리합니다. 물론 타일 중 하나는 정확히 원 가운데에 있으며 다른 타일은 대칭 패턴으로 정렬됩니다.

Centered Tiles

또 다른 중심 접근 방식은 실제로 조금 더 쉽습니다. 타일을 가운데에 배치하는 변환 변환을 생성하는 대신 바둑판식 패턴의 모퉁이를 가운데에 배치할 수 있습니다. 호출에서 SKMatrix.MakeTranslation 캔버스의 가운데에 대한 인수를 사용합니다.

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

패턴은 여전히 가운데에 있고 대칭적이지만 가운데에 타일이 없습니다.

Centered Tiles Alternate

회전을 통한 단순화

경우에 따라 메서드에서 회전 변환을 SKShader.CreateBitmap 사용하면 비트맵 타일을 간소화할 수 있습니다. 체인 링크 울타리에 대한 타일을 정의하려고 할 때 이 작업이 분명해집니다. ChainLinkTile.cs 파일은 여기에 표시된 타일을 만듭니다(명확성을 위해 분홍색 배경 사용).

Hard chain-link tile

코드가 타일을 네 개의 사분면으로 나누도록 타일에 두 개의 링크가 포함되어야 합니다. 왼쪽 위와 오른쪽 아래 사분면은 동일하지만 완료되지는 않습니다. 전선에는 오른쪽 위와 왼쪽 아래 사분면에 추가 그리기를 사용하여 처리해야 하는 노치가 거의 없습니다. 이 모든 작업을 수행하는 파일의 길이는 174줄입니다.

이 타일을 만드는 것이 훨씬 더 쉬운 것으로 밝혀졌습니다.

Easier chain-link tile

비트맵 타일 셰이더가 90도 회전하는 경우 시각적 개체는 거의 동일합니다.

더 쉬운 체인 링크 타일을 만드는 코드는 Chain-Link 타일 페이지의 일부입니다. 생성자는 프로그램이 실행 중인 디바이스의 유형에 따라 타일 크기를 확인한 다음, 호출 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;
        }
    }
    ···
}

와이어를 제외하고 타일은 투명하므로 다른 항목 위에 표시할 수 있습니다. 프로그램은 비트맵 리소스 중 하나에 로드되고 캔버스를 채우도록 표시한 다음 위에 셰이더를 그립니다.

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

비트맵 타일 애니메이션

행렬 변환에 애니메이션을 적용하여 전체 비트맵 타일 패턴에 애니메이션 효과를 적용할 수 있습니다. 패턴이 가로 또는 세로 또는 둘 다 이동하려고 할 수 있습니다. 이렇게 하려면 이동 좌표를 기반으로 변환 변환을 만들 수 있습니다.

작은 비트맵에 그리거나 비트맵의 픽셀 비트를 초당 60배 속도로 조작할 수도 있습니다. 그런 다음 타일링에 비트맵을 사용할 수 있으며 전체 타일 패턴에 애니메이션 효과를 주는 것처럼 보일 수 있습니다.

애니메이션 비트맵 타일 페이지에서는 이 방법을 보여 줍니다. 비트맵은 64픽셀 정사각형 필드로 인스턴스화됩니다. 생성자는 초기 모양을 제공하기 위해 호출 DrawBitmap 합니다. 필드가 angle 0이면(메서드가 처음 호출될 때와 같이) 비트맵에 X로 교차된 두 줄이 포함됩니다. 선은 값에 관계없이 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 발생합니다. 이 메서드 angleOnTimerTick 10초마다 값을 0도에서 360도로 애니메이션하여 비트맵 내에서 X 그림을 회전합니다.

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