SkiaSharp 비트맵 만들기 및 그리기

애플리케이션이 웹, 애플리케이션 리소스 및 사용자의 사진 라이브러리에서 비트맵을 로드하는 방법을 살펴보았습니다. 애플리케이션 내에서 새 비트맵을 만들 수도 있습니다. 가장 간단한 방법은 다음의 생성자 SKBitmap중 하나를 포함합니다.

SKBitmap bitmap = new SKBitmap(width, height);

height 매개 변수는 width 정수이며 비트맵의 픽셀 크기를 지정합니다. 이 생성자는 빨강, 녹색, 파랑 및 알파(불투명도) 구성 요소에 대해 각각 1바이트인 픽셀당 4바이트로 전체 색 비트맵을 만듭니다.

새 비트맵을 만든 후에는 비트맵의 표면에 무언가를 가져와야 합니다. 일반적으로 다음 두 가지 방법 중 하나로 이 작업을 수행합니다.

  • 표준 Canvas 그리기 메서드를 사용하여 비트맵에 그립니다.
  • 픽셀 비트에 직접 액세스합니다.

이 문서에서는 첫 번째 방법을 보여 줍니다.

그리기 샘플

두 번째 방법은 SkiaSharp 비트맵 픽셀 액세스 문서에서 설명합니다.

비트맵에 그리기

비트맵의 표면에 그리는 것은 비디오 디스플레이에 그리는 것과 같습니다. 비디오 디스플레이를 그리려면 이벤트 인수에서 개체를 PaintSurface 가져옵니다SKCanvas. 비트맵을 그리려면 생성자를 SKCanvas 사용하여 개체를 SKCanvas 만듭니다.

SKCanvas canvas = new SKCanvas(bitmap);

비트맵에서 그리기를 마쳤으면 개체를 삭제할 SKCanvas 수 있습니다. 이러한 이유로 SKCanvas 생성자는 일반적으로 문에서 using 호출됩니다.

using (SKCanvas canvas = new SKCanvas(bitmap))
{
    ··· // call drawing function
}

그런 다음 비트맵을 표시할 수 있습니다. 나중에 프로그램은 동일한 비트맵을 기반으로 새 SKCanvas 개체를 만들고 좀 더 그릴 수 있습니다.

샘플 애플리케이션의 Hello 비트맵 페이지는 비트맵에 "Hello, Bitmap!" 텍스트를 쓴 다음 해당 비트맵을 여러 번 표시합니다.

HelloBitmapPage 텍스트 표시를 위한 개체를 SKPaint 만들어 시작합니다. 텍스트 문자열의 차원을 결정하고 해당 차원을 사용하여 비트맵을 만듭니다. 그런 다음, 해당 비트맵을 기반으로 개체를 만들고 SKCanvas 호출 Clear한 다음 DrawText호출합니다. 새로 만든 비트맵에 임의 데이터가 포함될 수 있으므로 항상 새 비트맵으로 호출 Clear 하는 것이 좋습니다.

생성자는 비트맵을 표시하는 개체를 SKCanvasView 만들어 종료합니다.

public partial class HelloBitmapPage : ContentPage
{
    const string TEXT = "Hello, Bitmap!";
    SKBitmap helloBitmap;

    public HelloBitmapPage()
    {
        Title = TEXT;

        // Create bitmap and draw on it
        using (SKPaint textPaint = new SKPaint { TextSize = 48 })
        {
            SKRect bounds = new SKRect();
            textPaint.MeasureText(TEXT, ref bounds);

            helloBitmap = new SKBitmap((int)bounds.Right,
                                       (int)bounds.Height);

            using (SKCanvas bitmapCanvas = new SKCanvas(helloBitmap))
            {
                bitmapCanvas.Clear();
                bitmapCanvas.DrawText(TEXT, 0, -bounds.Top, textPaint);
            }
        }

        // Create SKCanvasView to view result
        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(SKColors.Aqua);

        for (float y = 0; y < info.Height; y += helloBitmap.Height)
            for (float x = 0; x < info.Width; x += helloBitmap.Width)
            {
                canvas.DrawBitmap(helloBitmap, x, y);
            }
    }
}

PaintSurface 처리기는 비트맵을 디스플레이의 행과 열에서 여러 번 렌더링합니다. Clear 처리기의 메서드 PaintSurface 에는 디스플레이 화면의 SKColors.Aqua배경에 색을 지정하는 인수가 있습니다.

안녕하세요, 비트맵!

아쿠아 배경의 모양은 비트맵이 텍스트를 제외하고 투명하다는 것을 보여줍니다.

지우기 및 투명도

Hello 비트맵 페이지의 표시는 프로그램이 만든 비트맵이 검은색 텍스트를 제외하고 투명함을 보여 줍니다. 디스플레이 화면의 아쿠아 색이 표시되는 이유입니다.

메서드 설명 Clear 서에서는 "캔버스의 SKCanvas 현재 클립에 있는 모든 픽셀을 바꿉니다."라는 문으로 설명합니다. "replaces"라는 단어를 사용하면 기존 디스플레이 화면에 무언가를 추가하는 모든 그리기 메서드가 이러한 메서드의 SKCanvas 중요한 특징을 알 수 있습니다. 메서드Clear 이미 있는 항목을 대체합니다.

Clear 은 두 가지 버전으로 존재합니다.

  • 매개 변수가 있는 SKColor 메서드는 Clear 디스플레이 화면의 픽셀을 해당 색의 픽셀로 바꿉니다.

  • 매개 변수가 없는 메서드는 Clear 픽셀을 SKColors.Empty 모든 구성 요소(빨강, 녹색, 파랑 및 알파)가 0으로 설정된 색으로 바꿉니다. 이 색을 "투명 검정"이라고도 합니다.

새 비트맵에서 인수 없이 호출 Clear 하면 전체 비트맵이 완전히 투명하게 초기화됩니다. 이후에 비트맵에 그려진 모든 항목은 일반적으로 불투명하거나 부분적으로 불투명합니다.

다음과 같이 시도해 보세요. Hello 비트맵 페이지에서 적용 bitmapCanvas 된 메서드를 Clear 다음 메서드로 바꿉다.

bitmapCanvas.Clear(new SKColor(255, 0, 0, 128));

생성자 매개 변수의 SKColor 순서는 빨강, 녹색, 파랑 및 알파이며 각 값의 범위는 0에서 255까지입니다. 알파 값 0은 투명하고 알파 값은 255는 불투명합니다.

값(255, 0, 0, 128)은 불투명도가 50%인 비트맵 픽셀을 빨간색 픽셀로 지웁니다. 즉, 비트맵 배경이 반투명합니다. 비트맵의 반투명 빨간색 배경은 디스플레이 화면의 아쿠아 배경과 결합하여 회색 배경을 만듭니다.

이니셜라이저에 다음 할당 SKPaint 을 배치하여 텍스트 색을 투명 검정으로 설정해 보세요.

Color = new SKColor(0, 0, 0, 0)

이 투명 텍스트는 디스플레이 화면의 아쿠아 배경을 볼 수 있는 비트맵의 완전히 투명한 영역을 만들 것이라고 생각할 수 있습니다. 그러나 그것은 그렇지 않습니다. 텍스트는 비트맵에 이미 있는 내용 위에 그려집니다. 투명 텍스트는 전혀 표시되지 않습니다.

비트맵을 더 투명하게 만드는 메서드는 없습니다 Draw . 오직 Clear 그렇게 할 수 있습니다.

비트맵 색 형식

가장 SKBitmap 간단한 생성자를 사용하면 비트맵의 정수 픽셀 너비와 높이를 지정할 수 있습니다. 다른 SKBitmap 생성자는 더 복잡합니다. 이러한 생성자에는 두 개의 열거형 형식 SKColorType 인 및 SKAlphaType.의 인수가 필요합니다. 다른 생성자는 이 정보를 통합하는 구조를 사용합니다 SKImageInfo .

열거형에는 SKColorType 9명의 멤버가 있습니다. 이러한 각 멤버는 비트맵 픽셀을 저장하는 특정 방법을 설명합니다.

  • Unknown
  • Alpha8 - 각 픽셀은 8비트이며 알파 값을 완전히 투명에서 완전히 불투명한 값으로 나타냅니다.
  • Rgb565 - 각 픽셀은 16비트, 빨강과 파랑의 경우 5비트, 녹색은 6비트입니다.
  • Argb4444 - 각 픽셀은 알파, 빨강, 녹색 및 파랑의 경우 각각 16비트, 4비트입니다.
  • Rgba8888 - 각 픽셀은 32비트이며 빨간색, 녹색, 파랑 및 알파의 경우 각각 8개입니다.
  • Bgra8888 - 각 픽셀은 파란색, 녹색, 빨간색 및 알파의 경우 각각 32비트, 8비트입니다.
  • Index8 - 각 픽셀은 8비트이며 인덱스를 나타냅니다. SKColorTable
  • Gray8 - 각 픽셀은 검은색에서 흰색으로 회색 음영을 나타내는 8비트입니다.
  • RgbaF16 - 각 픽셀은 64비트이며 빨강, 녹색, 파랑 및 알파는 16비트 부동 소수점 형식입니다.

각 픽셀이 32픽셀(4바이트)인 두 가지 형식은 종종 전체 색 형식이라고 합니다. 다른 형식의 대부분은 비디오 디스플레이 자체가 전체 색을 할 수 없는 시점부터 날짜가 지정됩니다. 제한된 색의 비트맵은 이러한 디스플레이에 적합했으며 비트맵이 메모리 공간을 적게 차지하도록 허용했습니다.

요즘 프로그래머는 거의 항상 전체 색 비트맵을 사용하고 다른 형식을 귀찮게하지 않습니다. 예외는 RgbaF16 전체 색 형식보다 더 큰 색 해상도를 허용하는 형식입니다. 그러나 이 형식은 의료용 이미징과 같은 특수 용도로 사용되며 표준 풀 컬러 디스플레이와 함께 사용할 때는 별로 의미가 없습니다.

이 문서 시리즈는 멤버가 지정되지 않은 SKColorType 경우 기본적으로 사용되는 색 형식으로 제한 SKBitmap 됩니다. 이 기본 형식은 기본 플랫폼을 기반으로 합니다. 지원되는 Xamarin.Forms플랫폼의 경우 기본 색 유형은 다음과 같습니다.

  • Rgba8888 iOS 및 Android용
  • Bgra8888 UWP용

유일한 차이점은 메모리의 4바이트 순서이며 픽셀 비트에 직접 액세스할 때만 문제가 됩니다. SkiaSharp 비트맵 픽셀에 액세스하는 문서에 액세스할 때까지 이 작업은 중요하지 않습니다.

열거형에는 SKAlphaType 4개의 멤버가 있습니다.

  • Unknown
  • Opaque - 비트맵에 투명도가 없음
  • Premul — 색 구성 요소에 알파 구성 요소를 미리 곱합니다.
  • Unpremul — 색 구성 요소는 알파 구성 요소를 미리 곱하지 않습니다.

다음은 50% 투명도의 4바이트 빨간색 비트맵 픽셀로, 바이트는 빨간색, 녹색, 파랑, 알파 순서로 표시됩니다.

0xFF 0x00 0x00 0x80

반투명 픽셀이 포함된 비트맵을 디스플레이 화면에 렌더링하는 경우 각 비트맵 픽셀의 색 구성 요소에 해당 픽셀의 알파 값을 곱해야 하며, 디스플레이 화면의 해당 픽셀의 색 구성 요소에 알파 값을 뺀 255를 곱해야 합니다. 그런 다음 두 픽셀을 결합할 수 있습니다. 비트맵 픽셀의 색 구성 요소가 이미 알파 값으로 미리 곱한 경우 비트맵을 더 빠르게 렌더링할 수 있습니다. 동일한 빨간색 픽셀은 다음과 같이 미리 곱한 형식으로 저장됩니다.

0x80 0x00 0x00 0x80

이러한 성능 향상은 기본적으로 비트맵이 형식으로 만들어지는 이유 SkiaSharp 입니다 Premul . 그러나 다시 말하지만, 픽셀 비트에 액세스하고 조작할 때만 이를 알아야 합니다.

기존 비트맵에 그리기

새 비트맵을 만들어 그릴 필요는 없습니다. 기존 비트맵에 그릴 수도 있습니다.

원숭이 콧수염 페이지는 생성자를 사용하여 MonkeyFace.png 이미지를 로드합니다. 그런 다음, 해당 비트맵을 기반으로 개체를 만들고 SKCanvas 개체를 SKPath 사용하여 SKPaint 콧수염을 그립니다.

public partial class MonkeyMoustachePage : ContentPage
{
    SKBitmap monkeyBitmap;

    public MonkeyMoustachePage()
    {
        Title = "Monkey Moustache";

        monkeyBitmap = BitmapExtensions.LoadBitmapResource(GetType(),
            "SkiaSharpFormsDemos.Media.MonkeyFace.png");

        // Create canvas based on bitmap
        using (SKCanvas canvas = new SKCanvas(monkeyBitmap))
        {
            using (SKPaint paint = new SKPaint())
            {
                paint.Style = SKPaintStyle.Stroke;
                paint.Color = SKColors.Black;
                paint.StrokeWidth = 24;
                paint.StrokeCap = SKStrokeCap.Round;

                using (SKPath path = new SKPath())
                {
                    path.MoveTo(380, 390);
                    path.CubicTo(560, 390, 560, 280, 500, 280);

                    path.MoveTo(320, 390);
                    path.CubicTo(140, 390, 140, 280, 200, 280);

                    canvas.DrawPath(path, paint);
                }
            }
        }

        // Create SKCanvasView to view result
        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();
        canvas.DrawBitmap(monkeyBitmap, info.Rect, BitmapStretch.Uniform);
    }
}

생성자는 처리기가 결과를 표시하기만 하면 SKCanvasViewPaintSurface 종료됩니다.

원숭이 콧수염

비트맵 복사 및 수정

비트맵에 SKCanvas 그리는 데 사용할 수 있는 메서드는 다음과 같습니다 DrawBitmap. 즉, 한 비트맵을 다른 비트맵에 그릴 수 있으며 일반적으로 어떤 방식으로든 수정할 수 있습니다.

비트맵을 수정하는 가장 다양한 방법은 SkiaSharp 비트맵 픽셀 액세스 문서에서 다루는 주제인 실제 픽셀 비트에 액세스하는 것입니다. 그러나 픽셀 비트에 액세스할 필요가 없는 비트맵을 수정하는 다른 많은 기술이 있습니다.

샘플 애플리케이션에 포함된 다음 비트맵은 너비가 360픽셀이고 높이가 480픽셀입니다.

등산객

왼쪽에 있는 원숭이로부터 이 사진을 게시할 수 있는 허가를 받지 못했다고 가정해 보겠습니다. 한 가지 해결책은 픽셀화라는 기술을 사용하여 원숭이의 얼굴을 가리는 것입니다. 얼굴의 픽셀은 색 블록으로 대체되므로 기능을 만들 수 없습니다. 색 블록은 일반적으로 이러한 블록에 해당하는 픽셀의 색을 평균하여 원래 이미지에서 파생됩니다. 하지만 당신은 자신을 평균이 수행 할 필요가 없습니다. 비트맵을 더 작은 픽셀 차원으로 복사하면 자동으로 발생합니다.

왼쪽 원숭이의 얼굴은 약 72픽셀 정사각형 영역을 차지하고 있으며, 왼쪽 위 모서리는 포인트(112, 238)입니다. 72픽셀 정사각형 영역을 각각 8x8픽셀 정사각형인 9x9 색 블록 배열로 바꾸겠습니다.

Pixelize 이미지 페이지는 해당 비트맵에 로드되고 먼저 호출faceBitmap된 작은 9픽셀 정사각형 비트맵을 만듭니다. 이것은 원숭이의 얼굴만 복사하는 대상입니다. 대상 사각형은 9픽셀 정사각형에 불과하지만 원본 사각형은 72픽셀 정사각형입니다. 원본 픽셀의 모든 8 x 8 블록은 색을 평균하여 1 픽셀로 통합됩니다.

다음 단계는 원래 비트맵을 동일한 크기의 새 비트맵으로 복사하는 것입니다 pixelizedBitmap. 그런 다음 72픽셀 정사각형 대상 사각형을 사용하여 그 위에 작은 faceBitmap 픽셀이 복사되므로 각 픽셀이 크기의 faceBitmap 8배로 확장됩니다.

public class PixelizedImagePage : ContentPage
{
    SKBitmap pixelizedBitmap;

    public PixelizedImagePage ()
    {
        Title = "Pixelize Image";

        SKBitmap originalBitmap = BitmapExtensions.LoadBitmapResource(GetType(),
            "SkiaSharpFormsDemos.Media.MountainClimbers.jpg");

        // Create tiny bitmap for pixelized face
        SKBitmap faceBitmap = new SKBitmap(9, 9);

        // Copy subset of original bitmap to that
        using (SKCanvas canvas = new SKCanvas(faceBitmap))
        {
            canvas.Clear();
            canvas.DrawBitmap(originalBitmap,
                              new SKRect(112, 238, 184, 310),   // source
                              new SKRect(0, 0, 9, 9));          // destination

        }

        // Create full-sized bitmap for copy
        pixelizedBitmap = new SKBitmap(originalBitmap.Width, originalBitmap.Height);

        using (SKCanvas canvas = new SKCanvas(pixelizedBitmap))
        {
            canvas.Clear();

            // Draw original in full size
            canvas.DrawBitmap(originalBitmap, new SKPoint());

            // Draw tiny bitmap to cover face
            canvas.DrawBitmap(faceBitmap,
                              new SKRect(112, 238, 184, 310));  // destination
        }

        // Create SKCanvasView to view result
        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();
        canvas.DrawBitmap(pixelizedBitmap, info.Rect, BitmapStretch.Uniform);
    }
}

생성자는 결과를 표시하는 방법을 만들어 SKCanvasView 결론을 내립니다.

이미지 픽셀화

비트맵 회전

또 다른 일반적인 작업은 비트맵을 회전하는 것입니다. i전화 또는 iPad 사진 라이브러리에서 비트맵을 검색할 때 특히 유용합니다. 사진을 찍을 때 장치가 특정 방향으로 유지되지 않는 한 사진은 거꾸로 또는 옆으로 있을 수 있습니다.

비트맵을 거꾸로 설정하려면 첫 번째 비트맵과 동일한 크기의 다른 비트맵을 만든 다음 첫 번째 비트맵을 두 번째 비트맵에 복사하는 동안 180도 회전하도록 변환을 설정해야 합니다. 이 섹션 bitmap 의 모든 예제에서 회전해야 하는 개체는 SKBitmap 다음과 같습니다.

SKBitmap rotatedBitmap = new SKBitmap(bitmap.Width, bitmap.Height);

using (SKCanvas canvas = new SKCanvas(rotatedBitmap))
{
    canvas.Clear();
    canvas.RotateDegrees(180, bitmap.Width / 2, bitmap.Height / 2);
    canvas.DrawBitmap(bitmap, new SKPoint());
}

90도 회전할 때 높이와 너비를 교환하여 원래 크기와 다른 비트맵을 만들어야 합니다. 예를 들어 원래 비트맵의 너비가 1200픽셀이고 높이가 800픽셀인 경우 회전된 비트맵은 너비가 800픽셀이고 너비가 1200픽셀입니다. 비트맵이 왼쪽 위 모서리를 중심으로 회전한 다음 보기로 이동되도록 변환 및 회전을 설정합니다. (메서드와 RotateDegrees 메서드는 적용되는 방식의 반대 순서로 호출된다는 사실에 유 Translate 의하세요.) 시계 방향으로 90도 회전하는 코드는 다음과 같습니다.

SKBitmap rotatedBitmap = new SKBitmap(bitmap.Height, bitmap.Width);

using (SKCanvas canvas = new SKCanvas(rotatedBitmap))
{
    canvas.Clear();
    canvas.Translate(bitmap.Height, 0);
    canvas.RotateDegrees(90);
    canvas.DrawBitmap(bitmap, new SKPoint());
}

시계 반대 방향으로 90도 회전하는 유사한 함수는 다음과 같습니다.

SKBitmap rotatedBitmap = new SKBitmap(bitmap.Height, bitmap.Width);

using (SKCanvas canvas = new SKCanvas(rotatedBitmap))
{
    canvas.Clear();
    canvas.Translate(0, bitmap.Width);
    canvas.RotateDegrees(-90);
    canvas.DrawBitmap(bitmap, new SKPoint());
}

이 두 가지 방법은 SkiaSharp 비트맵 자르기 문서에 설명된 사진 퍼즐 페이지에 사용됩니다.

사용자가 비트맵을 90도 단위로 회전할 수 있도록 하는 프로그램은 90도 회전을 위한 하나의 함수만 구현하면 됩니다. 그러면 사용자는 이 함수를 반복 실행하여 90도 단위로 회전할 수 있습니다.

프로그램은 비트맵을 임의의 양만큼 회전할 수도 있습니다. 한 가지 간단한 방법은 180을 일반화된 angle 변수로 바꿔서 180도 회전하는 함수를 수정하는 것입니다.

SKBitmap rotatedBitmap = new SKBitmap(bitmap.Width, bitmap.Height);

using (SKCanvas canvas = new SKCanvas(rotatedBitmap))
{
    canvas.Clear();
    canvas.RotateDegrees(angle, bitmap.Width / 2, bitmap.Height / 2);
    canvas.DrawBitmap(bitmap, new SKPoint());
}

그러나 일반적으로 이 논리는 회전된 비트맵의 모서리를 잘라 내게 됩니다. 더 나은 방법은 이러한 모서리를 포함하기 위해 삼각을 사용하여 회전된 비트맵의 크기를 계산하는 것입니다.

이 삼각법은 비트맵 회전자 페이지에 표시됩니다. XAML 파일은 현재 값을 표시하여 Slider 0도에서 360도까지의 범위가 될 수 있는 Label 및 해당 값을 인스턴스화 SKCanvasView 합니다.

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:skia="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
             x:Class="SkiaSharpFormsDemos.Bitmaps.BitmapRotatorPage"
             Title="Bitmap Rotator">
    <StackLayout>
        <skia:SKCanvasView x:Name="canvasView"
                           VerticalOptions="FillAndExpand"
                           PaintSurface="OnCanvasViewPaintSurface" />

        <Slider x:Name="slider"
                Maximum="360"
                Margin="10, 0"
                ValueChanged="OnSliderValueChanged" />

        <Label Text="{Binding Source={x:Reference slider},
                              Path=Value,
                              StringFormat='Rotate by {0:F0}&#x00B0;'}"
               HorizontalTextAlignment="Center" />

    </StackLayout>
</ContentPage>

코드 숨김 파일은 비트맵 리소스를 로드하고 명명 originalBitmap된 정적 읽기 전용 필드로 저장합니다. 처리기에 표시되는 PaintSurface 비트맵은 처음에 다음으로 originalBitmap설정된 비트맵입니다rotatedBitmap.

public partial class BitmapRotatorPage : ContentPage
{
    static readonly SKBitmap originalBitmap =
        BitmapExtensions.LoadBitmapResource(typeof(BitmapRotatorPage),
            "SkiaSharpFormsDemos.Media.Banana.jpg");

    SKBitmap rotatedBitmap = originalBitmap;

    public BitmapRotatorPage ()
    {
        InitializeComponent ();
    }

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

        canvas.Clear();
        canvas.DrawBitmap(rotatedBitmap, info.Rect, BitmapStretch.Uniform);
    }

    void OnSliderValueChanged(object sender, ValueChangedEventArgs args)
    {
        double angle = args.NewValue;
        double radians = Math.PI * angle / 180;
        float sine = (float)Math.Abs(Math.Sin(radians));
        float cosine = (float)Math.Abs(Math.Cos(radians));
        int originalWidth = originalBitmap.Width;
        int originalHeight = originalBitmap.Height;
        int rotatedWidth = (int)(cosine * originalWidth + sine * originalHeight);
        int rotatedHeight = (int)(cosine * originalHeight + sine * originalWidth);

        rotatedBitmap = new SKBitmap(rotatedWidth, rotatedHeight);

        using (SKCanvas canvas = new SKCanvas(rotatedBitmap))
        {
            canvas.Clear(SKColors.LightPink);
            canvas.Translate(rotatedWidth / 2, rotatedHeight / 2);
            canvas.RotateDegrees((float)angle);
            canvas.Translate(-originalWidth / 2, -originalHeight / 2);
            canvas.DrawBitmap(originalBitmap, new SKPoint());
        }

        canvasView.InvalidateSurface();
    }
}

ValueChanged 처리 Slider 기는 회전 각도에 따라 새로 rotatedBitmap 만드는 작업을 수행합니다. 새 너비와 높이는 원래 너비와 높이의 사인과 코사인의 절대값을 기반으로 합니다. 회전된 비트맵에서 원래 비트맵을 그리는 데 사용되는 변환은 원래 비트맵 중심을 원점으로 이동한 다음 지정된 수만큼 회전한 다음 해당 중심을 회전된 비트맵의 중심으로 변환합니다. Translate(및 메서드는 적용되는 방식과 RotateDegrees 반대 순서로 호출됩니다.)

연한 분홍색의 배경을 만들기 위해 메서드를 rotatedBitmap 사용합니다Clear. 이는 디스플레이의 rotatedBitmap 크기를 설명하기 위한 것입니다.

비트맵 회전기

회전된 비트맵은 원래 비트맵 전체를 포함할 만큼 충분히 크지만 더 크지는 않습니다.

비트맵 대칭 이동

비트맵에서 일반적으로 수행되는 다른 작업을 대칭 이동이라고 합니다. 개념적으로 비트맵은 비트맵의 중심을 통해 세로 축 또는 가로 축을 중심으로 3차원으로 회전됩니다. 세로 대칭 이동은 미러 이미지를 만듭니다.

샘플 애플리케이션의 비트맵 플리퍼 페이지는 이러한 프로세스를 보여 줍니다. XAML 파일에는 세로 SKCanvasView 및 가로로 대칭 이동하기 위한 단추가 두 개 포함되어 있습니다.

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:skia="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
             x:Class="SkiaSharpFormsDemos.Bitmaps.BitmapFlipperPage"
             Title="Bitmap Flipper">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>

        <skia:SKCanvasView x:Name="canvasView"
                           Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"
                           PaintSurface="OnCanvasViewPaintSurface" />

        <Button Text="Flip Vertical"
                Grid.Row="1" Grid.Column="0"
                Margin="0, 10"
                Clicked="OnFlipVerticalClicked" />

        <Button Text="Flip Horizontal"
                Grid.Row="1" Grid.Column="1"
                Margin="0, 10"
                Clicked="OnFlipHorizontalClicked" />
    </Grid>
</ContentPage>

코드 숨김 파일은 단추에 대한 처리기에서 Clicked 다음 두 작업을 구현합니다.

public partial class BitmapFlipperPage : ContentPage
{
    SKBitmap bitmap =
        BitmapExtensions.LoadBitmapResource(typeof(BitmapRotatorPage),
            "SkiaSharpFormsDemos.Media.SeatedMonkey.jpg");

    public BitmapFlipperPage()
    {
        InitializeComponent();
    }

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

        canvas.Clear();
        canvas.DrawBitmap(bitmap, info.Rect, BitmapStretch.Uniform);
    }

    void OnFlipVerticalClicked(object sender, ValueChangedEventArgs args)
    {
        SKBitmap flippedBitmap = new SKBitmap(bitmap.Width, bitmap.Height);

        using (SKCanvas canvas = new SKCanvas(flippedBitmap))
        {
            canvas.Clear();
            canvas.Scale(-1, 1, bitmap.Width / 2, 0);
            canvas.DrawBitmap(bitmap, new SKPoint());
        }

        bitmap = flippedBitmap;
        canvasView.InvalidateSurface();
    }

    void OnFlipHorizontalClicked(object sender, ValueChangedEventArgs args)
    {
        SKBitmap flippedBitmap = new SKBitmap(bitmap.Width, bitmap.Height);

        using (SKCanvas canvas = new SKCanvas(flippedBitmap))
        {
            canvas.Clear();
            canvas.Scale(1, -1, 0, bitmap.Height / 2);
            canvas.DrawBitmap(bitmap, new SKPoint());
        }

        bitmap = flippedBitmap;
        canvasView.InvalidateSurface();
    }
}

수직 대칭 이동은 수평 배율 인수가 –1인 배율 변환을 통해 수행됩니다. 크기 조정 중심은 비트맵의 세로 중심입니다. 가로 대칭 이동은 세로 배율 인수가 -1인 배율 변환입니다.

원숭이 셔츠의 반전된 글자에서 볼 수 있듯이 뒤집기는 회전과 다릅니다. 그러나 오른쪽의 UWP 스크린샷에서 볼 수 있듯이 가로 및 세로로 대칭 이동은 180도 회전과 동일합니다.

비트맵 플리퍼

비슷한 기술을 사용하여 처리할 수 있는 또 다른 일반적인 작업은 비트맵을 사각형 하위 집합으로 자르는 것입니다. 이 내용은 SkiaSharp 비트맵 자르기 문서에서 설명합니다.