Сохранение растровых карт SkiaSharp в файлы
После создания или изменения растрового изображения приложение SkiaSharp может потребоваться сохранить растровое изображение в библиотеке фотографий пользователя:
Эта задача включает два шага.
- Преобразование растрового изображения SkiaSharp в данные в определенном формате файла, например JPEG или PNG.
- Сохранение результата в библиотеке фотографий с помощью кода для конкретной платформы.
Форматы файлов и кодеки
Большинство популярных форматов файлов растрового изображения используют сжатие для уменьшения места хранения. Две широкие категории методов сжатия называются потерей и потерей. Эти термины указывают, приводит ли алгоритм сжатия к потере данных.
Самый популярный формат потери был разработан Группой совместных фотографических экспертов и называется JPEG. Алгоритм сжатия JPEG анализирует изображение с помощью математического средства, называемого дискретным преобразованием косинуса, и пытается удалить данные, которые не имеют решающего значения для сохранения визуальной точности изображения. Степень сжатия можно контролировать с помощью параметра, который обычно называется качеством. Более высокие параметры качества приводят к более крупным файлам.
В отличие от этого, алгоритм сжатия без потери анализирует изображение для повторения и шаблонов пикселей, которые могут быть закодированы таким образом, чтобы уменьшить данные, но не приводит к потере любой информации. Исходные растровые данные можно восстановить полностью из сжатого файла. Основной формат сжатых сжатых файлов без потери, используемый сегодня, — переносимая сетевая графика (PNG).
Как правило, JPEG используется для фотографий, а PNG используется для изображений, которые были созданы вручную или алгоритмически. Любой алгоритм сжатия без потери, уменьшающий размер некоторых файлов, должен обязательно увеличить размер других файлов. К счастью, это увеличение размера обычно происходит только для данных, содержащих много случайных (или, казалось бы, случайных) сведений.
Алгоритмы сжатия достаточно сложны, чтобы гарантировать два термина, описывающие процессы сжатия и декомпрессии:
- декодирование — чтение формата растрового файла и распаковка его
- кодирование — сжатие растрового изображения и запись в формат файла растрового изображения
Класс SKBitmap
содержит несколько методов, которые Decode
создаются SKBitmap
из сжатого источника. Все, что необходимо, — указать имя файла, поток или массив байтов. Декодатор может определить формат файла и передать его в соответствующую внутреннюю функцию декодирования.
Кроме того, SKCodec
класс имеет два метода с именем Create
, которые могут создать SKCodec
объект из сжатого источника и разрешить приложению более активно участвовать в процессе декодирования. (Класс SKCodec
показан в статье Animating SkiaSharp Bitmaps в связи с декодированием анимированного GIF-файла.)
При кодировании растрового изображения требуется дополнительная информация: кодировщик должен знать конкретный формат файла, который приложение хочет использовать (JPEG или PNG или что-то другое). Если требуется формат потери, код должен также знать требуемый уровень качества.
Класс SKBitmap
определяет один Encode
метод со следующим синтаксисом:
public Boolean Encode (SKWStream dst, SKEncodedImageFormat format, Int32 quality)
Этот метод подробно описан в ближайшее время. Закодированное растровое изображение записывается в записываемый поток. (W в SKWStream
означает "записываемый".) Второй и третий аргументы указывают формат файла и (для форматов потери) требуемое качество от 0 до 100.
Кроме того, классы SKImage
SKPixmap
также определяют Encode
методы, которые являются несколько более универсальными и которые можно предпочесть. Вы можете легко создать SKImage
объект из SKBitmap
объекта с помощью статического SKImage.FromBitmap
метода. Объект можно получить SKPixmap
из SKBitmap
объекта с помощью PeekPixels
метода.
Один из методов, определенных Encode
без SKImage
параметров, и автоматически сохраняется в формате PNG. Этот метод без параметров очень прост в использовании.
Код для конкретной платформы для сохранения растровых файлов
При кодировании SKBitmap
объекта в определенном формате файла обычно вы останетесь с объектом потока определенного типа или массивом данных. Encode
Некоторые методы (включая один без параметров, определенных параметромSKImage
) возвращают SKData
объект, который можно преобразовать в массив байтов с помощью ToArray
метода. Затем эти данные должны быть сохранены в файле.
Сохранение файла в локальном хранилище приложения довольно легко, так как для этой задачи можно использовать стандартные System.IO
классы и методы. Этот метод демонстрируется в статье Animating SkiaSharp Bitmaps в связи с анимацией ряда растровых изображений набора Mandelbrot.
Если вы хотите, чтобы файл был предоставлен другим приложениям, его необходимо сохранить в библиотеке фотографий пользователя. Для этой задачи требуется код для конкретной Xamarin.FormsDependencyService
платформы и использование .
Проект SkiaSharpFormsDemo в примере приложения определяет интерфейс, используемый IPhotoLibrary
с классом DependencyService
. Это определяет синтаксис SavePhotoAsync
метода:
public interface IPhotoLibrary
{
Task<Stream> PickPhotoAsync();
Task<bool> SavePhotoAsync(byte[] data, string folder, string filename);
}
Этот интерфейс также определяет PickPhotoAsync
метод, который используется для открытия средства выбора файлов для конкретной платформы для фототеки устройства.
Для SavePhotoAsync
этого первым аргументом является массив байтов, содержащий растровое изображение, которое уже закодировано в определенный формат файла, например JPEG или PNG. Возможно, что приложению может потребоваться изолировать все точечные изображения, создаваемые в определенную папку, которая указана в следующем параметре, за которым следует имя файла. Метод возвращает логическое значение, указывающее на успех или нет.
В следующих разделах описывается SavePhotoAsync
реализация на каждой платформе.
Реализация iOS
Реализация iOS SavePhotoAsync
использует SaveToPhotosAlbum
метод UIImage
:
public class PhotoLibrary : IPhotoLibrary
{
···
public Task<bool> SavePhotoAsync(byte[] data, string folder, string filename)
{
NSData nsData = NSData.FromArray(data);
UIImage image = new UIImage(nsData);
TaskCompletionSource<bool> taskCompletionSource = new TaskCompletionSource<bool>();
image.SaveToPhotosAlbum((UIImage img, NSError error) =>
{
taskCompletionSource.SetResult(error == null);
});
return taskCompletionSource.Task;
}
}
К сожалению, невозможно указать имя файла или папку для образа.
Файл Info.plist в проекте iOS требует ключа, указывающего, что он добавляет изображения в библиотеку фотографий:
<key>NSPhotoLibraryAddUsageDescription</key>
<string>SkiaSharp Forms Demos adds images to your photo library</string>
Осторожно! Ключ разрешения для простого доступа к библиотеке фотографий очень похож, но не тот же:
<key>NSPhotoLibraryUsageDescription</key>
<string>SkiaSharp Forms Demos accesses your photo library</string>
Реализация Android
Реализация Android SavePhotoAsync
сначала проверяет, является null
ли folder
аргумент или пустая строка. Если да, то растровое изображение сохраняется в корневом каталоге библиотеки фотографий. В противном случае папка получается и если она не существует, она создается:
public class PhotoLibrary : IPhotoLibrary
{
···
public async Task<bool> SavePhotoAsync(byte[] data, string folder, string filename)
{
try
{
File picturesDirectory = Environment.GetExternalStoragePublicDirectory(Environment.DirectoryPictures);
File folderDirectory = picturesDirectory;
if (!string.IsNullOrEmpty(folder))
{
folderDirectory = new File(picturesDirectory, folder);
folderDirectory.Mkdirs();
}
using (File bitmapFile = new File(folderDirectory, filename))
{
bitmapFile.CreateNewFile();
using (FileOutputStream outputStream = new FileOutputStream(bitmapFile))
{
await outputStream.WriteAsync(data);
}
// Make sure it shows up in the Photos gallery promptly.
MediaScannerConnection.ScanFile(MainActivity.Instance,
new string[] { bitmapFile.Path },
new string[] { "image/png", "image/jpeg" }, null);
}
}
catch
{
return false;
}
return true;
}
}
Вызов MediaScannerConnection.ScanFile
не требуется строго, но если вы тестируете программу, немедленно проверяя библиотеку фотографий, это помогает многое, обновив представление коллекции библиотек.
Файл AndroidManifest.xml требует следующего тега разрешений:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Реализация UWP
Реализация UWP SavePhotoAsync
очень похожа на реализацию Android:
public class PhotoLibrary : IPhotoLibrary
{
···
public async Task<bool> SavePhotoAsync(byte[] data, string folder, string filename)
{
StorageFolder picturesDirectory = KnownFolders.PicturesLibrary;
StorageFolder folderDirectory = picturesDirectory;
// Get the folder or create it if necessary
if (!string.IsNullOrEmpty(folder))
{
try
{
folderDirectory = await picturesDirectory.GetFolderAsync(folder);
}
catch
{ }
if (folderDirectory == null)
{
try
{
folderDirectory = await picturesDirectory.CreateFolderAsync(folder);
}
catch
{
return false;
}
}
}
try
{
// Create the file.
StorageFile storageFile = await folderDirectory.CreateFileAsync(filename,
CreationCollisionOption.GenerateUniqueName);
// Convert byte[] to Windows buffer and write it out.
IBuffer buffer = WindowsRuntimeBuffer.Create(data, 0, data.Length, data.Length);
await FileIO.WriteBufferAsync(storageFile, buffer);
}
catch
{
return false;
}
return true;
}
}
В разделе "Возможности" файла Package.appxmanifest требуется библиотека изображений.
Изучение форматов изображений
Вот метод повтора Encode
SKImage
:
public Boolean Encode (SKWStream dst, SKEncodedImageFormat format, Int32 quality)
SKEncodedImageFormat
— это перечисление с элементами, ссылающимися на одиннадцать форматов растровых файлов, некоторые из которых являются довольно неясными:
Astc
— адаптивное сжатие текстуры с возможностью масштабированияBmp
— Растровое изображение WindowsDng
— Adobe Digital NegativeGif
— формат обмена графикойIco
— изображения значков WindowsJpeg
— Совместная группа фотографических экспертовKtx
— формат текстуры Khronos для OpenGLPkm
— настраиваемый формат для GrafX2Png
— переносимая сетевая графикаWbmp
— формат растрового изображения протокола беспроводного приложения (1 бит на пиксель)Webp
— формат Google WebP
Как вы увидите вскоре, только три из этих форматов файлов (Jpeg
, Png
и Webp
) на самом деле поддерживаются SkiaSharp.
Чтобы сохранить SKBitmap
объект с именем bitmap
в библиотеке фотографий пользователя, также требуется элемент SKEncodedImageFormat
перечисления с именем imageFormat
и (для форматов потери) целочисленной quality
переменной. Для сохранения растрового рисунка в файле с именем filename
в папке folder
можно использовать следующий код:
using (MemoryStream memStream = new MemoryStream())
using (SKManagedWStream wstream = new SKManagedWStream(memStream))
{
bitmap.Encode(wstream, imageFormat, quality);
byte[] data = memStream.ToArray();
// Check the data array for content!
bool success = await DependencyService.Get<IPhotoLibrary>().SavePhotoAsync(data, folder, filename);
// Check return value for success!
}
Класс SKManagedWStream
является производным от SKWStream
(который обозначает "записываемый поток"). Метод Encode
записывает кодированный растровый файл в этот поток. Примечания в этом коде ссылаются на некоторые проверки ошибок, которые может потребоваться выполнить.
Страница "Сохранить форматы файлов" в примере приложения использует аналогичный код, чтобы можно было экспериментировать с сохранением растрового изображения в различных форматах.
XAML-файл содержит SKCanvasView
растровое изображение, а остальная часть страницы содержит все, что приложение должно вызывать Encode
метод SKBitmap
. Он имеет Picker
элемент SKEncodedImageFormat
перечисления, аргумент качества для форматов растровых изображений потери, Slider
два Entry
представления для имени файла и имени папки, а также Button
для сохранения файла.
<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.Bitmaps.SaveFileFormatsPage"
Title="Save Bitmap Formats">
<StackLayout Margin="10">
<skiaforms:SKCanvasView PaintSurface="OnCanvasViewPaintSurface"
VerticalOptions="FillAndExpand" />
<Picker x:Name="formatPicker"
Title="image format"
SelectedIndexChanged="OnFormatPickerChanged">
<Picker.ItemsSource>
<x:Array Type="{x:Type skia:SKEncodedImageFormat}">
<x:Static Member="skia:SKEncodedImageFormat.Astc" />
<x:Static Member="skia:SKEncodedImageFormat.Bmp" />
<x:Static Member="skia:SKEncodedImageFormat.Dng" />
<x:Static Member="skia:SKEncodedImageFormat.Gif" />
<x:Static Member="skia:SKEncodedImageFormat.Ico" />
<x:Static Member="skia:SKEncodedImageFormat.Jpeg" />
<x:Static Member="skia:SKEncodedImageFormat.Ktx" />
<x:Static Member="skia:SKEncodedImageFormat.Pkm" />
<x:Static Member="skia:SKEncodedImageFormat.Png" />
<x:Static Member="skia:SKEncodedImageFormat.Wbmp" />
<x:Static Member="skia:SKEncodedImageFormat.Webp" />
</x:Array>
</Picker.ItemsSource>
</Picker>
<Slider x:Name="qualitySlider"
Maximum="100"
Value="50" />
<Label Text="{Binding Source={x:Reference qualitySlider},
Path=Value,
StringFormat='Quality = {0:F0}'}"
HorizontalTextAlignment="Center" />
<StackLayout Orientation="Horizontal">
<Label Text="Folder Name: "
VerticalOptions="Center" />
<Entry x:Name="folderNameEntry"
Text="SaveFileFormats"
HorizontalOptions="FillAndExpand" />
</StackLayout>
<StackLayout Orientation="Horizontal">
<Label Text="File Name: "
VerticalOptions="Center" />
<Entry x:Name="fileNameEntry"
Text="Sample.xxx"
HorizontalOptions="FillAndExpand" />
</StackLayout>
<Button Text="Save"
Clicked="OnButtonClicked">
<Button.Triggers>
<DataTrigger TargetType="Button"
Binding="{Binding Source={x:Reference formatPicker},
Path=SelectedIndex}"
Value="-1">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
<DataTrigger TargetType="Button"
Binding="{Binding Source={x:Reference fileNameEntry},
Path=Text.Length}"
Value="0">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Button.Triggers>
</Button>
<Label x:Name="statusLabel"
Text="OK"
Margin="10, 0" />
</StackLayout>
</ContentPage>
Файл программной части загружает ресурс растрового изображения и использует SKCanvasView
его для отображения. Это растровое изображение никогда не изменяется. Обработчик SelectedIndexChanged
для Picker
имени файла изменяет расширение, которое совпадает с элементом перечисления:
public partial class SaveFileFormatsPage : ContentPage
{
SKBitmap bitmap = BitmapExtensions.LoadBitmapResource(typeof(SaveFileFormatsPage),
"SkiaSharpFormsDemos.Media.MonkeyFace.png");
public SaveFileFormatsPage ()
{
InitializeComponent ();
}
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
args.Surface.Canvas.DrawBitmap(bitmap, args.Info.Rect, BitmapStretch.Uniform);
}
void OnFormatPickerChanged(object sender, EventArgs args)
{
if (formatPicker.SelectedIndex != -1)
{
SKEncodedImageFormat imageFormat = (SKEncodedImageFormat)formatPicker.SelectedItem;
fileNameEntry.Text = Path.ChangeExtension(fileNameEntry.Text, imageFormat.ToString());
statusLabel.Text = "OK";
}
}
async void OnButtonClicked(object sender, EventArgs args)
{
SKEncodedImageFormat imageFormat = (SKEncodedImageFormat)formatPicker.SelectedItem;
int quality = (int)qualitySlider.Value;
using (MemoryStream memStream = new MemoryStream())
using (SKManagedWStream wstream = new SKManagedWStream(memStream))
{
bitmap.Encode(wstream, imageFormat, quality);
byte[] data = memStream.ToArray();
if (data == null)
{
statusLabel.Text = "Encode returned null";
}
else if (data.Length == 0)
{
statusLabel.Text = "Encode returned empty array";
}
else
{
bool success = await DependencyService.Get<IPhotoLibrary>().
SavePhotoAsync(data, folderNameEntry.Text, fileNameEntry.Text);
if (!success)
{
statusLabel.Text = "SavePhotoAsync return false";
}
else
{
statusLabel.Text = "Success!";
}
}
}
}
}
Обработчик Clicked
для Button
выполнения всех реальных работ. Он получает два аргумента Encode
из Picker
и Slider
затем использует код, показанный ранее, Encode
для создания SKManagedWStream
метода. Два Entry
представления предоставляет имена папок и файлов для SavePhotoAsync
метода.
Большая часть этого метода посвящена обработке проблем или ошибок. Если Encode
создается пустой массив, это означает, что конкретный формат файла не поддерживается. Если SavePhotoAsync
возвращается false
, файл не был успешно сохранен.
Ниже приведена программа:
Снимок экрана: только три формата, поддерживаемые на этих платформах:
- JPEG
- PNG
- WebP
Для всех других форматов Encode
метод ничего не записывает в поток, а результирующий массив байтов пуст.
Растровое изображение, которое сохраняет страница "Форматы файлов", составляет 600 пикселей. С 4 байтами на пиксель, это в общей сложности 1440 000 байт в памяти. В следующей таблице показан размер файла для различных сочетаний формата файла и качества:
Формат | Качество | Размер |
---|---|---|
PNG | Н/П | 492K |
JPEG | 0 | 2.95K |
50 | 22.1K | |
100 | 206K | |
WebP | 0 | 2.71K |
50 | 11.9K | |
100 | 101K |
Вы можете поэкспериментировать с различными параметрами качества и проверить результаты.
Сохранение искусства пальцем
Одним из распространенных вариантов использования растрового изображения является рисование программ, в которых она функционирует как то, что называется теневым растровым изображением. Все рисунки сохраняются на растровом рисунке, который затем отображается программой. Растровое изображение также удобно для сохранения рисунка.
Картина пальцев в статье SkiaSharp демонстрирует, как использовать отслеживание сенсорного ввода для реализации примитивной программы рисования пальцев. Программа поддерживает только один цвет и только одну ширину штриха SKPath
, но она сохранила весь рисунок в коллекции объектов.
На странице " Сохранить пальцем" в примере также сохраняется весь рисунок в коллекции SKPath
объектов, но он также отображает рисунок на растровом рисунке, который он может сохранить в вашей библиотеке фотографий.
Большая часть этой программы похожа на исходную программу Пальцем Краска . Одно из улучшений заключается в том, что XAML-файл теперь создает экземпляр кнопок с меткой Clear and Save:
<?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.Views.Forms;assembly=SkiaSharp.Views.Forms"
xmlns:tt="clr-namespace:TouchTracking"
x:Class="SkiaSharpFormsDemos.Bitmaps.FingerPaintSavePage"
Title="Finger Paint Save">
<StackLayout>
<Grid BackgroundColor="White"
VerticalOptions="FillAndExpand">
<skia:SKCanvasView x:Name="canvasView"
PaintSurface="OnCanvasViewPaintSurface" />
<Grid.Effects>
<tt:TouchEffect Capture="True"
TouchAction="OnTouchEffectAction" />
</Grid.Effects>
</Grid>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
</Grid>
<Button Text="Clear"
Grid.Row="0"
Margin="50, 5"
Clicked="OnClearButtonClicked" />
<Button Text="Save"
Grid.Row="1"
Margin="50, 5"
Clicked="OnSaveButtonClicked" />
</StackLayout>
</ContentPage>
Файл программной части поддерживает поле типа SKBitmap
с именем saveBitmap
. Эта растровая карта создается или воссоздается в PaintSurface
обработчике при изменении размера поверхности отображения. Если необходимо повторно создать растровое изображение, содержимое существующей растровой карты копируется в новую растровую карту, чтобы все сохранялось независимо от того, как область отображения изменяется в размере:
public partial class FingerPaintSavePage : ContentPage
{
···
SKBitmap saveBitmap;
public FingerPaintSavePage ()
{
InitializeComponent ();
}
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
// Create bitmap the size of the display surface
if (saveBitmap == null)
{
saveBitmap = new SKBitmap(info.Width, info.Height);
}
// Or create new bitmap for a new size of display surface
else if (saveBitmap.Width < info.Width || saveBitmap.Height < info.Height)
{
SKBitmap newBitmap = new SKBitmap(Math.Max(saveBitmap.Width, info.Width),
Math.Max(saveBitmap.Height, info.Height));
using (SKCanvas newCanvas = new SKCanvas(newBitmap))
{
newCanvas.Clear();
newCanvas.DrawBitmap(saveBitmap, 0, 0);
}
saveBitmap = newBitmap;
}
// Render the bitmap
canvas.Clear();
canvas.DrawBitmap(saveBitmap, 0, 0);
}
···
}
Рисунок, выполненный PaintSurface
обработчиком, происходит в самом конце и состоит исключительно из отрисовки растрового изображения.
Обработка касания аналогична предыдущей программе. Программа поддерживает две коллекции и inProgressPaths
completedPaths
содержит все, что пользователь нарисовал с момента последнего очистки дисплея. Для каждого события OnTouchEffectAction
касания обработчик вызывает UpdateBitmap
:
public partial class FingerPaintSavePage : ContentPage
{
Dictionary<long, SKPath> inProgressPaths = new Dictionary<long, SKPath>();
List<SKPath> completedPaths = new List<SKPath>();
SKPaint paint = new SKPaint
{
Style = SKPaintStyle.Stroke,
Color = SKColors.Blue,
StrokeWidth = 10,
StrokeCap = SKStrokeCap.Round,
StrokeJoin = SKStrokeJoin.Round
};
···
void OnTouchEffectAction(object sender, TouchActionEventArgs args)
{
switch (args.Type)
{
case TouchActionType.Pressed:
if (!inProgressPaths.ContainsKey(args.Id))
{
SKPath path = new SKPath();
path.MoveTo(ConvertToPixel(args.Location));
inProgressPaths.Add(args.Id, path);
UpdateBitmap();
}
break;
case TouchActionType.Moved:
if (inProgressPaths.ContainsKey(args.Id))
{
SKPath path = inProgressPaths[args.Id];
path.LineTo(ConvertToPixel(args.Location));
UpdateBitmap();
}
break;
case TouchActionType.Released:
if (inProgressPaths.ContainsKey(args.Id))
{
completedPaths.Add(inProgressPaths[args.Id]);
inProgressPaths.Remove(args.Id);
UpdateBitmap();
}
break;
case TouchActionType.Cancelled:
if (inProgressPaths.ContainsKey(args.Id))
{
inProgressPaths.Remove(args.Id);
UpdateBitmap();
}
break;
}
}
SKPoint ConvertToPixel(Point pt)
{
return new SKPoint((float)(canvasView.CanvasSize.Width * pt.X / canvasView.Width),
(float)(canvasView.CanvasSize.Height * pt.Y / canvasView.Height));
}
void UpdateBitmap()
{
using (SKCanvas saveBitmapCanvas = new SKCanvas(saveBitmap))
{
saveBitmapCanvas.Clear();
foreach (SKPath path in completedPaths)
{
saveBitmapCanvas.DrawPath(path, paint);
}
foreach (SKPath path in inProgressPaths.Values)
{
saveBitmapCanvas.DrawPath(path, paint);
}
}
canvasView.InvalidateSurface();
}
···
}
Метод UpdateBitmap
перерисовывается saveBitmap
путем создания нового SKCanvas
, очистки и отрисовки всех путей на растровом рисунке. Он завершается недействительным canvasView
, чтобы растровое изображение можно было нарисовать на экране.
Ниже приведены обработчики для двух кнопок. Кнопка "Очистить" очищает обе коллекции путей, обновляет saveBitmap
(что приводит к очистке растрового изображения) и делает недопустимым :SKCanvasView
public partial class FingerPaintSavePage : ContentPage
{
···
void OnClearButtonClicked(object sender, EventArgs args)
{
completedPaths.Clear();
inProgressPaths.Clear();
UpdateBitmap();
canvasView.InvalidateSurface();
}
async void OnSaveButtonClicked(object sender, EventArgs args)
{
using (SKImage image = SKImage.FromBitmap(saveBitmap))
{
SKData data = image.Encode();
DateTime dt = DateTime.Now;
string filename = String.Format("FingerPaint-{0:D4}{1:D2}{2:D2}-{3:D2}{4:D2}{5:D2}{6:D3}.png",
dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second, dt.Millisecond);
IPhotoLibrary photoLibrary = DependencyService.Get<IPhotoLibrary>();
bool result = await photoLibrary.SavePhotoAsync(data.ToArray(), "FingerPaint", filename);
if (!result)
{
await DisplayAlert("FingerPaint", "Artwork could not be saved. Sorry!", "OK");
}
}
}
}
Обработчик кнопки "Сохранить " использует упрощенный Encode
метод из SKImage
. Этот метод кодирует формат PNG. Объект SKImage
создается на основе saveBitmap
, и SKData
объект содержит закодированный PNG-файл.
Метод ToArray
SKData
получения массива байтов. Это то, что передается SavePhotoAsync
методу, а также фиксированное имя папки и уникальное имя файла, созданное из текущей даты и времени.
Вот программа в действии:
В примере используется очень похожий метод. Это также программа рисования пальцев, за исключением того, что пользователь рисует на спиннинг-диске, который затем воспроизводит конструкции на других четырех квадрантах. Цвет пальца изменяется по мере вращения диска:
Кнопка SpinPaint
"Сохранить" класса аналогична кнопке "Палец" в том, что изображение сохраняется в фиксированном имени папки (SpainPaint) и имени файла, созданного с даты и времени.