Отображение растровых карт SkiaSharp
Тема растровых карт SkiaSharp была представлена в статье "Основные сведения о растровых картах" в SkiaSharp. В этой статье показаны три способа загрузки растровых изображений и три способа отображения растровых изображений. В этой статье рассматриваются методы загрузки растровых изображений и более глубокое использование DrawBitmap
методов SKCanvas
.
Методы DrawBitmapLattice
рассматриваются в статье Сегментированные отображения растровых карт SkiaSharp.DrawBitmapNinePatch
Примеры на этой странице приведены из примера приложения. На домашней странице этого приложения выберите Bitmaps SkiaSharp, а затем перейдите в раздел "Отображение растровых карт".
Загрузка растрового изображения
Растровое изображение, используемое приложением SkiaSharp, обычно поставляется из одного из трех разных источников:
- Из Интернета
- Из ресурса, внедренного в исполняемый файл
- Из библиотеки фотографий пользователя
Кроме того, приложение SkiaSharp может создать новую растровую карту, а затем рисовать на ней или задавать битовые биты алгоритмически. Эти методы рассматриваются в статьях о создании и рисовании растровых карт SkiaSharp и доступе к растровым пикселям SkiaSharp.
В следующих трех примерах кода загрузки растрового изображения предполагается, что класс содержит поле типа SKBitmap
:
SKBitmap bitmap;
Как говорится в статье Bitmap Basics в SkiaSharp, лучший способ загрузить растровое изображение через Интернет с классомHttpClient
. Один экземпляр класса можно определить как поле:
HttpClient httpClient = new HttpClient();
При использовании HttpClient
с приложениями iOS и Android необходимо задать свойства проекта, как описано в документах по протоколу TLS 1.2.
Код, который часто использует HttpClient
await
оператор, поэтому он должен находиться в методе async
:
try
{
using (Stream stream = await httpClient.GetStreamAsync("https:// ··· "))
using (MemoryStream memStream = new MemoryStream())
{
await stream.CopyToAsync(memStream);
memStream.Seek(0, SeekOrigin.Begin);
bitmap = SKBitmap.Decode(memStream);
···
};
}
catch
{
···
}
Обратите внимание, что Stream
полученный объект GetStreamAsync
копируется в объект MemoryStream
. Android не разрешает Stream
обработку от HttpClient
основного потока, за исключением асинхронных методов.
Это SKBitmap.Decode
Stream
делает много работы: объект, переданный в него, ссылается на блок памяти, содержащий всю растровую карту в одном из общих форматов файлов растрового изображения, как правило, JPEG, PNG или GIF. Метод Decode
должен определить формат, а затем декодировать растровый файл в собственный внутренний формат растрового изображения SkiaSharp.
После вызова SKBitmap.Decode
кода, вероятно, будет недействительным CanvasView
, чтобы обработчик смог отобразить только что PaintSurface
загруженное растровое изображение.
Вторым способом загрузки растрового изображения является включение растрового изображения в качестве внедренного ресурса в библиотеку .NET Standard, на которую ссылается отдельные проекты платформы. Идентификатор ресурса передается методу GetManifestResourceStream
. Этот идентификатор ресурса состоит из имени сборки, имени папки и имени файла ресурса, разделенного точками:
string resourceID = "assemblyName.folderName.fileName";
Assembly assembly = GetType().GetTypeInfo().Assembly;
using (Stream stream = assembly.GetManifestResourceStream(resourceID))
{
bitmap = SKBitmap.Decode(stream);
···
}
Файлы растровых карт также можно хранить в виде ресурсов в отдельном проекте платформы для iOS, Android и универсальная платформа Windows (UWP). Однако для загрузки этих растровых изображений требуется код, расположенный в проекте платформы.
Третий подход к получению растрового изображения — из библиотеки рисунков пользователя. В следующем коде используется служба зависимостей, включенная в пример приложения. Библиотека .NET Standard SkiaSharpFormsDemo включает IPhotoLibrary
интерфейс, а каждый из проектов платформы PhotoLibrary
содержит класс, реализующий этот интерфейс.
IPhotoicturePicker picturePicker = DependencyService.Get<IPhotoLibrary>();
using (Stream stream = await picturePicker.GetImageStreamAsync())
{
if (stream != null)
{
bitmap = SKBitmap.Decode(stream);
···
}
}
Как правило, такой код также недействителен CanvasView
таким образом, чтобы PaintSurface
обработчик может отображать новое растровое изображение.
Класс SKBitmap
определяет несколько полезных свойств, включая Width
и Height
, которые показывают размеры растрового изображения, а также множество методов, включая методы для создания растровых изображений, копирования их и предоставления битов пикселей.
Отображение в измерениях пикселей
Класс SkiaSharp Canvas
определяет четыре DrawBitmap
метода. Эти методы позволяют отображать растровые изображения двумя основными способами:
- При
SKPoint
указании значения (или отдельноx
иy
значений) отображается растровое изображение в его измерениях пикселей. Пиксели растрового изображения сопоставляются непосредственно с пикселями отображения видео. - Указание прямоугольника приводит к растянутой растровой диаграмме до размера и формы прямоугольника.
Вы отображаете растровое изображение в его измерениях пикселей с DrawBitmap
параметром SKPoint
или DrawBitmap
отдельными x
параметрами y
:
DrawBitmap(SKBitmap bitmap, SKPoint pt, SKPaint paint = null)
DrawBitmap(SKBitmap bitmap, float x, float y, SKPaint paint = null)
Эти два метода функционально идентичны. Указанная точка указывает расположение левого верхнего угла растрового изображения относительно холста. Так как разрешение пикселей мобильных устройств настолько высоко, небольшие растровые изображения обычно отображаются довольно крошечными на этих устройствах.
Необязательный SKPaint
параметр позволяет отображать растровое изображение с помощью прозрачности. Для этого создайте SKPaint
объект и задайте Color
для свойства любое SKColor
значение с альфа-каналом меньше 1. Например:
paint.Color = new SKColor(0, 0, 0, 0x80);
0x80, переданный в качестве последнего аргумента, указывает на прозрачность 50 %. Вы также можете задать альфа-канал на одном из предварительно определенных цветов:
paint.Color = SKColors.Red.WithAlpha(0x80);
Однако сам цвет не имеет значения. Только альфа-канал проверяется при использовании SKPaint
объекта в вызове DrawBitmap
.
Объект SKPaint
также играет роль при отображении растровых изображений с помощью режимов смешения или эффектов фильтра. Они показаны в статьях SkiaSharp compositing и смешивания режимов и фильтров изображений SkiaSharp.
Страница "Измерения пикселей" в примере программы отображает ресурс растрового изображения размером 320 пикселей, широкий на 240 пикселей:
public class PixelDimensionsPage : ContentPage
{
SKBitmap bitmap;
public PixelDimensionsPage()
{
Title = "Pixel Dimensions";
// Load the bitmap from a resource
string resourceID = "SkiaSharpFormsDemos.Media.Banana.jpg";
Assembly assembly = GetType().GetTypeInfo().Assembly;
using (Stream stream = assembly.GetManifestResourceStream(resourceID))
{
bitmap = SKBitmap.Decode(stream);
}
// Create the SKCanvasView and set the PaintSurface handler
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();
float x = (info.Width - bitmap.Width) / 2;
float y = (info.Height - bitmap.Height) / 2;
canvas.DrawBitmap(bitmap, x, y);
}
}
Обработчик PaintSurface
центризует x
растровое изображение путем вычисления и значений на основе измерений пиксельной поверхности отображения и y
измерений пикселя растрового изображения:
Если приложение хочет отобразить растровое изображение в левом верхнем углу, оно просто передает координаты (0, 0).
Метод загрузки растровых карт ресурсов
Многие из приведенных примеров потребуются для загрузки ресурсов растрового изображения. Статический BitmapExtensions
класс в примере решения содержит метод, который поможет:
static class BitmapExtensions
{
public static SKBitmap LoadBitmapResource(Type type, string resourceID)
{
Assembly assembly = type.GetTypeInfo().Assembly;
using (Stream stream = assembly.GetManifestResourceStream(resourceID))
{
return SKBitmap.Decode(stream);
}
}
···
}
Обратите внимание на Type
параметр. Это может быть объект, связанный Type
с любым типом в сборке, в которой хранится ресурс растрового изображения.
Этот LoadBitmapResource
метод будет использоваться во всех последующих примерах, для которых требуются ресурсы растрового изображения.
Растяжение для заполнения прямоугольника
Класс SKCanvas
также определяет метод, который отрисовывает DrawBitmap
растровое изображение в прямоугольник, а другой метод, который отрисовывает прямоугольное подмножество DrawBitmap
растрового изображения прямоугольником:
DrawBitmap(SKBitmap bitmap, SKRect dest, SKPaint paint = null)
DrawBitmap(SKBitmap bitmap, SKRect source, SKRect dest, SKPaint paint = null)
В обоих случаях растровое изображение растянуто для заполнения прямоугольника с именем dest
. Во втором методе source
прямоугольник позволяет выбрать подмножество растрового изображения. Прямоугольник dest
относительно выходного устройства; source
прямоугольник относительно растрового изображения.
Страница "Заливка прямоугольника " демонстрирует первую из этих двух методов, отображая ту же растровую карту, используемую в предыдущем примере в прямоугольнике того же размера, что и холст:
public class FillRectanglePage : ContentPage
{
SKBitmap bitmap =
BitmapExtensions.LoadBitmapResource(typeof(FillRectanglePage),
"SkiaSharpFormsDemos.Media.Banana.jpg");
public FillRectanglePage ()
{
Title = "Fill Rectangle";
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(bitmap, info.Rect);
}
}
Обратите внимание на использование нового BitmapExtensions.LoadBitmapResource
метода для задания SKBitmap
поля. Прямоугольник назначения получается из Rect
свойства SKImageInfo
, которое дефрибирует размер поверхности отображения:
Это обычно не то, что вы хотите. Изображение искажается по-разному в горизонтальных и вертикальных направлениях. При отображении растрового изображения в чем-то, отличном от размера пикселя, обычно требуется сохранить исходное соотношение пропорций растрового изображения.
Растяжение при сохранении пропорций
Растяжение растрового изображения при сохранении пропорций — это процесс, также известный как равномерное масштабирование. Этот термин предполагает алгоритмический подход. Одно из возможных решений отображается на странице универсального масштабирования :
public class UniformScalingPage : ContentPage
{
SKBitmap bitmap =
BitmapExtensions.LoadBitmapResource(typeof(UniformScalingPage),
"SkiaSharpFormsDemos.Media.Banana.jpg");
public UniformScalingPage()
{
Title = "Uniform Scaling";
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();
float scale = Math.Min((float)info.Width / bitmap.Width,
(float)info.Height / bitmap.Height);
float x = (info.Width - scale * bitmap.Width) / 2;
float y = (info.Height - scale * bitmap.Height) / 2;
SKRect destRect = new SKRect(x, y, x + scale * bitmap.Width,
y + scale * bitmap.Height);
canvas.DrawBitmap(bitmap, destRect);
}
}
Обработчик PaintSurface
вычисляет scale
фактор, который является минимальным соотношением ширины отображения и высоты до ширины и высоты растрового изображения. Затем x
можно вычислить значения y
для выравнивания масштабируемого растрового изображения в пределах ширины и высоты дисплея. Прямоугольник назначения имеет верхний левый угол и y
нижний правый угол x
этих значений, а также масштабируемую ширину и высоту растрового изображения:
Переверните телефон боковую сторону, чтобы увидеть растровое изображение растянуто на эту область:
Преимущество использования этого scale
фактора становится очевидным, если вы хотите реализовать немного другой алгоритм. Предположим, вы хотите сохранить пропорции растрового изображения, а также заполнить прямоугольник назначения. Единственным способом этого является обрезка части изображения, но вы можете реализовать этот алгоритм, просто изменив Math.Min
Math.Max
его в приведенном выше коде. Ниже приведен результат:
Пропорции растрового изображения сохраняются, но области слева и справа от растрового изображения обрезаются.
Универсальная функция отображения растрового изображения
Среды программирования на основе XAML (например, UWP и Xamarin.Forms) имеют возможность расширить или уменьшить размер растровых изображений при сохранении их пропорций. Хотя SkiaSharp не включает эту функцию, ее можно реализовать самостоятельно.
Класс, BitmapExtensions
включенный в пример приложения, показывает, как. Класс определяет два новых DrawBitmap
метода, выполняющих вычисление пропорций. Эти новые методы являются методами SKCanvas
расширения.
Новые DrawBitmap
методы включают параметр типа BitmapStretch
, перечисление, определенное в файле BitmapExtensions.cs :
public enum BitmapStretch
{
None,
Fill,
Uniform,
UniformToFill,
AspectFit = Uniform,
AspectFill = UniformToFill
}
Элементы None
, Fill
Uniform
и UniformToFill
члены совпадают с элементами перечисления UWPStretch
. Аналогичное Xamarin.FormsAspect
перечисление определяет элементы Fill
, AspectFit
и AspectFill
.
Страница универсального масштабирования , показанная выше, сосредоточена на растровом изображении в прямоугольнике, но могут потребоваться другие параметры, такие как размещение растрового изображения в левой или правой части прямоугольника или сверху или вниз. Это цель перечисления BitmapAlignment
:
public enum BitmapAlignment
{
Start,
Center,
End
}
Параметры выравнивания не влияют при использовании с BitmapStretch.Fill
.
Первая DrawBitmap
функция расширения содержит прямоугольник назначения, но не имеет исходного прямоугольника. Значения по умолчанию определяются таким образом, чтобы в том случае, если вы хотите, чтобы растровое изображение было центрировано, необходимо указать BitmapStretch
только элемент:
static class BitmapExtensions
{
···
public static void DrawBitmap(this SKCanvas canvas, SKBitmap bitmap, SKRect dest,
BitmapStretch stretch,
BitmapAlignment horizontal = BitmapAlignment.Center,
BitmapAlignment vertical = BitmapAlignment.Center,
SKPaint paint = null)
{
if (stretch == BitmapStretch.Fill)
{
canvas.DrawBitmap(bitmap, dest, paint);
}
else
{
float scale = 1;
switch (stretch)
{
case BitmapStretch.None:
break;
case BitmapStretch.Uniform:
scale = Math.Min(dest.Width / bitmap.Width, dest.Height / bitmap.Height);
break;
case BitmapStretch.UniformToFill:
scale = Math.Max(dest.Width / bitmap.Width, dest.Height / bitmap.Height);
break;
}
SKRect display = CalculateDisplayRect(dest, scale * bitmap.Width, scale * bitmap.Height,
horizontal, vertical);
canvas.DrawBitmap(bitmap, display, paint);
}
}
···
}
Основной целью этого метода является вычисление коэффициента масштабирования, который scale
затем применяется к ширине и высоте растрового изображения при вызове CalculateDisplayRect
метода. Это метод, который вычисляет прямоугольник для отображения растрового изображения на основе горизонтального и вертикального выравнивания:
static class BitmapExtensions
{
···
static SKRect CalculateDisplayRect(SKRect dest, float bmpWidth, float bmpHeight,
BitmapAlignment horizontal, BitmapAlignment vertical)
{
float x = 0;
float y = 0;
switch (horizontal)
{
case BitmapAlignment.Center:
x = (dest.Width - bmpWidth) / 2;
break;
case BitmapAlignment.Start:
break;
case BitmapAlignment.End:
x = dest.Width - bmpWidth;
break;
}
switch (vertical)
{
case BitmapAlignment.Center:
y = (dest.Height - bmpHeight) / 2;
break;
case BitmapAlignment.Start:
break;
case BitmapAlignment.End:
y = dest.Height - bmpHeight;
break;
}
x += dest.Left;
y += dest.Top;
return new SKRect(x, y, x + bmpWidth, y + bmpHeight);
}
}
Класс BitmapExtensions
содержит дополнительный DrawBitmap
метод с исходным прямоугольником для указания подмножества растрового изображения. Этот метод аналогичен первому, за исключением того, что коэффициент масштабирования вычисляется на source
основе прямоугольника, а затем применяется к source
прямоугольнику в вызове CalculateDisplayRect
:
static class BitmapExtensions
{
···
public static void DrawBitmap(this SKCanvas canvas, SKBitmap bitmap, SKRect source, SKRect dest,
BitmapStretch stretch,
BitmapAlignment horizontal = BitmapAlignment.Center,
BitmapAlignment vertical = BitmapAlignment.Center,
SKPaint paint = null)
{
if (stretch == BitmapStretch.Fill)
{
canvas.DrawBitmap(bitmap, source, dest, paint);
}
else
{
float scale = 1;
switch (stretch)
{
case BitmapStretch.None:
break;
case BitmapStretch.Uniform:
scale = Math.Min(dest.Width / source.Width, dest.Height / source.Height);
break;
case BitmapStretch.UniformToFill:
scale = Math.Max(dest.Width / source.Width, dest.Height / source.Height);
break;
}
SKRect display = CalculateDisplayRect(dest, scale * source.Width, scale * source.Height,
horizontal, vertical);
canvas.DrawBitmap(bitmap, source, display, paint);
}
}
···
}
Первый из этих двух новых DrawBitmap
методов показан на странице "Режимы масштабирования". XAML-файл содержит три Picker
элемента, которые позволяют выбирать элементы BitmapStretch
и BitmapAlignment
перечисления:
<?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:local="clr-namespace:SkiaSharpFormsDemos"
xmlns:skia="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
x:Class="SkiaSharpFormsDemos.Bitmaps.ScalingModesPage"
Title="Scaling Modes">
<Grid Padding="10">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<skia:SKCanvasView x:Name="canvasView"
Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"
PaintSurface="OnCanvasViewPaintSurface" />
<Label Text="Stretch:"
Grid.Row="1" Grid.Column="0"
VerticalOptions="Center" />
<Picker x:Name="stretchPicker"
Grid.Row="1" Grid.Column="1"
SelectedIndexChanged="OnPickerSelectedIndexChanged">
<Picker.ItemsSource>
<x:Array Type="{x:Type local:BitmapStretch}">
<x:Static Member="local:BitmapStretch.None" />
<x:Static Member="local:BitmapStretch.Fill" />
<x:Static Member="local:BitmapStretch.Uniform" />
<x:Static Member="local:BitmapStretch.UniformToFill" />
</x:Array>
</Picker.ItemsSource>
<Picker.SelectedIndex>
0
</Picker.SelectedIndex>
</Picker>
<Label Text="Horizontal Alignment:"
Grid.Row="2" Grid.Column="0"
VerticalOptions="Center" />
<Picker x:Name="horizontalPicker"
Grid.Row="2" Grid.Column="1"
SelectedIndexChanged="OnPickerSelectedIndexChanged">
<Picker.ItemsSource>
<x:Array Type="{x:Type local:BitmapAlignment}">
<x:Static Member="local:BitmapAlignment.Start" />
<x:Static Member="local:BitmapAlignment.Center" />
<x:Static Member="local:BitmapAlignment.End" />
</x:Array>
</Picker.ItemsSource>
<Picker.SelectedIndex>
0
</Picker.SelectedIndex>
</Picker>
<Label Text="Vertical Alignment:"
Grid.Row="3" Grid.Column="0"
VerticalOptions="Center" />
<Picker x:Name="verticalPicker"
Grid.Row="3" Grid.Column="1"
SelectedIndexChanged="OnPickerSelectedIndexChanged">
<Picker.ItemsSource>
<x:Array Type="{x:Type local:BitmapAlignment}">
<x:Static Member="local:BitmapAlignment.Start" />
<x:Static Member="local:BitmapAlignment.Center" />
<x:Static Member="local:BitmapAlignment.End" />
</x:Array>
</Picker.ItemsSource>
<Picker.SelectedIndex>
0
</Picker.SelectedIndex>
</Picker>
</Grid>
</ContentPage>
Файл программной части просто делает недействительным момент CanvasView
, когда любой Picker
элемент изменился. Обработчик PaintSurface
обращается к трем Picker
представлениям для вызова DrawBitmap
метода расширения:
public partial class ScalingModesPage : ContentPage
{
SKBitmap bitmap =
BitmapExtensions.LoadBitmapResource(typeof(ScalingModesPage),
"SkiaSharpFormsDemos.Media.Banana.jpg");
public ScalingModesPage()
{
InitializeComponent();
}
private 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();
SKRect dest = new SKRect(0, 0, info.Width, info.Height);
BitmapStretch stretch = (BitmapStretch)stretchPicker.SelectedItem;
BitmapAlignment horizontal = (BitmapAlignment)horizontalPicker.SelectedItem;
BitmapAlignment vertical = (BitmapAlignment)verticalPicker.SelectedItem;
canvas.DrawBitmap(bitmap, dest, stretch, horizontal, vertical);
}
}
Ниже приведены некоторые сочетания параметров:
Страница подмножества прямоугольника имеет практически тот же XAML-файл, что и режимы масштабирования, но файл кода определяет прямоугольное подмножество растрового изображения, заданного SOURCE
полем:
public partial class ScalingModesPage : ContentPage
{
SKBitmap bitmap =
BitmapExtensions.LoadBitmapResource(typeof(ScalingModesPage),
"SkiaSharpFormsDemos.Media.Banana.jpg");
static readonly SKRect SOURCE = new SKRect(94, 12, 212, 118);
public RectangleSubsetPage()
{
InitializeComponent();
}
private 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();
SKRect dest = new SKRect(0, 0, info.Width, info.Height);
BitmapStretch stretch = (BitmapStretch)stretchPicker.SelectedItem;
BitmapAlignment horizontal = (BitmapAlignment)horizontalPicker.SelectedItem;
BitmapAlignment vertical = (BitmapAlignment)verticalPicker.SelectedItem;
canvas.DrawBitmap(bitmap, SOURCE, dest, stretch, horizontal, vertical);
}
}
Этот прямоугольник изолирует голову обезьяны, как показано на следующих снимках экрана: