Поделиться через


Создание и рисование растровых карт SkiaSharp

Вы узнали, как приложение может загружать растровые изображения из Интернета, из ресурсов приложения и из библиотеки фотографий пользователя. Кроме того, в приложении можно создать новые растровые изображения. Самый простой подход включает один из конструкторов SKBitmap:

SKBitmap bitmap = new SKBitmap(width, height);

height И width параметры являются целыми числами и указывают размеры пикселя растрового изображения. Этот конструктор создает полноцветную растровую карту с четырьмя байтами на пиксель: по одному байту для компонентов красного, зеленого, синего и альфа-(непрозрачности).

После создания растрового изображения необходимо получить что-то на поверхности растрового изображения. Обычно это можно сделать одним из двух способов:

  • Нарисуйте растровое изображение с помощью стандартных Canvas методов рисования.
  • Доступ к битам пикселей напрямую.

В этой статье демонстрируется первый подход:

Пример рисования

Второй подход рассматривается в статье Accessing SkiaSharp Bitmap Pixel.

Рисование растрового изображения

Рисование на поверхности растрового изображения совпадает с рисунком на экране видео. Чтобы нарисовать на экране видео, вы получите SKCanvas объект из PaintSurface аргументов события. Чтобы нарисовать на растровом рисунке, создайте 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, в котором цвет фона поверхности отображения:

Привет, битовая карта!

Внешний вид фона aqua показывает, что растровое изображение прозрачно, за исключением текста.

Очистка и прозрачность

Отображение страницы "Растровое изображение Hello" показывает, что созданная программа является прозрачной, за исключением черного текста. Вот почему цвет воды поверхности дисплея показывается через.

Документация Clear по методам SKCanvas описывает их с помощью инструкции: "Заменяет все пиксели в текущем клипе холста". Использование слова "заменяет" показывает важную характеристику этих методов: все методы рисования SKCanvas добавляют что-то в существующую поверхность отображения. Методы Clearзаменяют то, что уже есть.

Clear существует в двух разных версиях:

  • Метод Clear с параметром SKColor заменяет пиксели поверхности отображения пикселями этого цвета.

  • Метод Clear без параметров заменяет пиксели SKColors.Empty цветом, который является цветом, в котором все компоненты (красный, зеленый, синий и альфа-) равны нулю. Этот цвет иногда называется "прозрачным черным".

Вызов Clear без аргументов на новой растровой карте инициализирует весь растровый рисунок, чтобы быть полностью прозрачным. Все, что впоследствии нарисовано на растровом рисунке, обычно будет непрозрачным или частично непрозрачным.

Ниже приведено следующее: на странице "Растровое изображение Hello" замените метод, примененный Clear к bitmapCanvas этому:

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 формат, который позволяет большее разрешение цветов, чем даже полноцветные форматы. Однако этот формат используется для специализированных целей, таких как медицинская визуализация, и не имеет большого смысла при использовании со стандартными полноцветными дисплеями.

Эта серия статей ограничивается SKBitmap цветными форматами, используемыми по умолчанию, если элемент не SKColorType указан. Этот формат по умолчанию основан на базовой платформе. Для платформ, поддерживаемых типом Xamarin.Formsцвета по умолчанию, является следующее:

  • Rgba8888 для iOS и Android
  • Bgra8888 для UWP

Единственное различие заключается в порядке 4 байта в памяти, и это становится проблемой только при непосредственном доступе к битам пикселей. Это не станет важным, пока вы не получите статью Accessing SkiaSharp Bitmap Pixel.

Перечисление SKAlphaType содержит четыре элемента:

  • Unknown
  • Opaque — растровое изображение не имеет прозрачности
  • Premul — компоненты цвета предварительно умножаются на альфа-компонент
  • Unpremul — компоненты цвета не умножаются на альфа-компонент

Вот 4-байтовый красный растровый пиксель с прозрачностью 50 % с байтами, отображаемыми в порядке красного, зеленого, синего, альфа::

0xFF 0x00 0x00 0x80

Если растровое изображение, содержащее полупрозрачные пиксели, отрисовывается на поверхности отображения, компоненты цвета каждого пикселя растрового изображения должны быть умножены на альфа-значение этого пикселя, а компоненты цвета соответствующего пикселя поверхности отображения должны умножаться на 255 минус альфа-значение. Затем можно объединить два пикселя. Растровое изображение может быть отрисовано быстрее, если цветовые компоненты в пикселях растрового изображения уже были предварительно переопределены альфа-значением. Этот же красный пиксель будет храниться таким же образом в предварительно умноженном формате:

0x80 0x00 0x00 0x80

Это улучшение производительности заключается в том, почему SkiaSharp растровые изображения по умолчанию создаются с форматом Premul . Но опять же, необходимо знать это только при доступе к битам пикселей и манипулирования ими.

Рисование существующих растровых изображений

Для рисования на ней не требуется создать точечный рисунок. Вы также можете рисовать на существующем растровом рисунке.

Страница Обезьяны Мустаче использует его конструктор для загрузки MonkeyFace.png изображения. Затем он создает SKCanvas объект на основе этого растрового изображения и использует SKPaint и SKPath объекты для рисования мустаче на нем:

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

Конструктор завершает создание обработчика SKCanvasView , который PaintSurface просто отображает результат:

Обезьяна Мустаче

Копирование и изменение растровых изображений

Методы SKCanvas , которые можно использовать для рисования на растровом рисунке, включают DrawBitmap. Это означает, что вы можете нарисовать одно растровое изображение на другом, обычно изменяя его каким-то образом.

Наиболее универсальным способом изменения растрового изображения является доступ к фактическим битам пикселей, тема, описанная в статье Accessing SkiaSharp bitmap пикселей. Но существует множество других методов изменения растровых изображений, которые не требуют доступа к битам пикселей.

Следующая растровая карта, включенная в пример приложения, составляет 360 пикселей в ширину и 480 пикселей в высоту:

Альпинисты

Предположим, что вы не получили разрешения от обезьяны слева, чтобы опубликовать эту фотографию. Одно из решений заключается в том, чтобы скрыть лицо обезьяны с помощью техники, называемой пикселизации. Пиксели лица заменяются блоками цвета, чтобы вы не могли выделять функции. Блоки цвета обычно являются производными от исходного изображения путем усреднения цветов пикселей, соответствующих этим блокам. Но вам не нужно выполнять это в среднем самостоятельно. Это происходит автоматически при копировании растрового изображения в меньшее измерение пикселя.

Лицо левой обезьяны занимает примерно 72-пиксельную квадратную площадь с верхним левым углом в точке (112, 238). Давайте заменим эту площадь размером 72 пикселя с массивом цветных блоков размером от 9 до 9 пикселей, каждый из которых составляет 8–8 пикселей.

Страница "Пиксельное изображение" загружается в этом растровом изображении и сначала создает крошечную 9-пиксельную квадратную растровую карту faceBitmap. Это место для копирования только лица обезьяны. Прямоугольник назначения составляет всего 9 пикселей, но исходный прямоугольник составляет 72 пикселя. Каждый блок от 8 до 8 исходных пикселей консолидируется до одного пикселя, усреднив цвета.

Следующим шагом является копирование исходного растрового изображения в новое растровое изображение того же размера, которое называется pixelizedBitmap. Затем крошечный faceBitmap копируется поверх этого с прямоугольником назначения размером 72 пикселей, чтобы каждый пиксель 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 градусов при копировании первого в второй. Во всех примерах этого раздела — это SKBitmap объект, bitmap который необходимо повернуть:

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 пикселей. Задайте для преобразования и поворота, чтобы растровое изображение повернуло его верхний левый угол, а затем переместилось в представление. (Имейте в виду, что TranslateRotateDegrees методы вызываются в противоположном порядке того, как они применяются.) Ниже приведен код для поворота 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());
}

Эти два метода используются на страницах фото-головоломки, описанных в статье Обрезка СкиаШарп битовых карт.

Программа, которая позволяет пользователю повернуть растровое изображение в 90-градусных добавочных значениях, необходимо реализовать только одну функцию для поворота на 90 градусов. Затем пользователь может повернуть любое увеличение 90 градусов путем повторного выполнения этой одной функции.

Программа также может повернуть растровое изображение по любому объему. Одним из простых подходов является изменение функции, которая поворачивается на 180 градусов, заменив 180 обобщенной angle переменной:

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-файл создает экземпляр и SKCanvasView может Slider варьироваться от 0 до 360 градусов с Label отображением текущего значения:

<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. Растровое изображение, отображаемое в обработчике PaintSurfacerotatedBitmap, имеет значение :originalBitmap

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

Обработчик ValueChangedSlider выполняет операции, которые создаются rotatedBitmap на основе угла поворота. Новая ширина и высота основаны на абсолютных значениях синусов и косинусов исходной ширины и высоты. Преобразования, используемые для рисования исходной растровой карты на повернутом растровом рисунке, перемещают исходный центр растрового изображения в источник, а затем поворачивают его по указанному числу градусов, а затем переводят его в центр повернутого растрового изображения. (Методы TranslateRotateDegrees вызываются в противоположном порядке, чем при применении.)

Обратите внимание на использование Clear метода, чтобы сделать фон светло-розового rotatedBitmap . Это исключительно для иллюстрации rotatedBitmap размера на дисплее:

Точечный поворот карты

Повернутый растровый рисунок достаточно большой, чтобы включить всю исходную растровую карту, но не больше.

Перевернутые растровые изображения

Другая операция, обычно выполняемая на растровых изображениях, называется перевернутой. Концептуально растровое изображение поворачивается в трех измерениях вокруг вертикальной оси или горизонтальной оси через центр растрового изображения. Вертикальное переворачивание создает изображение зеркало.

Страница "Перевернуть растровое изображение" в примере приложения демонстрирует эти процессы. 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.