Ukládání rastrových obrázků SkiaSharp do souborů
Jakmile aplikace SkiaSharp vytvoří nebo upraví rastrový obrázek, může aplikace chtít uložit rastrový obrázek do knihovny fotografií uživatele:
Tento úkol zahrnuje dva kroky:
- Převod rastrového obrázku SkiaSharp na data v určitém formátu souboru, například JPEG nebo PNG.
- Uložení výsledku do knihovny fotografií pomocí kódu specifického pro platformu
Formáty souborů a kodeky
Většina dnešních oblíbených rastrových formátů souborů používá kompresi ke snížení úložného prostoru. Dvě široké kategorie technik komprese se nazývají ztrátové a bezeztrátové. Tyto termíny označují, zda algoritmus komprese vede ke ztrátě dat.
Nejoblíbenější ztrátový formát byl vyvinut skupinou Joint Photographic Experts Group a nazývá se JPEG. Algoritmus komprese JPEG analyzuje obrázek pomocí matematického nástroje označovaného jako diskrétní kosinusová transformace a pokusí se odebrat data, která nejsou zásadní pro zachování vizuální věrnosti obrázku. Stupeň komprese lze řídit nastavením obecně označovaným jako kvalita. Nastavení vyšší kvality vede k větším souborům.
Naproti tomu algoritmus komprese bezeztrátové komprese analyzuje obrázek pro opakování a vzory pixelů, které lze kódovat způsobem, který snižuje data, ale nezpůsobí ztrátu informací. Původní rastrová data lze obnovit zcela z komprimovaného souboru. Primární bezeztrátový komprimovaný formát souboru, který se dnes používá, je Portable Network Graphics (PNG).
Obecně platí, že jpeg se používá pro fotografie, zatímco PNG se používá pro obrázky, které byly ručně nebo algoritmicky generovány. Jakýkoli bezeztrátový algoritmus komprese, který zmenší velikost některých souborů, musí nutně zvětšit velikost ostatních. Naštěstí k tomuto zvýšení velikosti obvykle dochází pouze u dat, která obsahují velké množství náhodných (nebo zdánlivě náhodných) informací.
Algoritmy komprese jsou dostatečně složité, aby bylo zaručeno dva termíny, které popisují procesy komprese a dekomprese:
- dekódování – čtení formátu rastrového souboru a dekomprimace
- kódování – komprimace rastrového obrázku a zápis do formátu rastrového souboru
Třída SKBitmap
obsahuje několik metod pojmenovaných Decode
, které vytvoří SKBitmap
z komprimovaného zdroje. Stačí zadat název souboru, datový proud nebo pole bajtů. Dekodér může určit formát souboru a předat ho správné vnitřní dekódovací funkci.
Kromě toho třída má dvě metody pojmenovanéCreate
, SKCodec
které mohou vytvořit SKCodec
objekt z komprimovaného zdroje a umožnit aplikaci, aby se více zapojila do procesu dekódování. (Třída SKCodec
je zobrazena v článku Animace SkiaSharp Bitmaps v souvislosti s dekódováním animovaného souboru GIF.)
Při kódování rastrového obrázku se vyžadují další informace: Kodér musí znát konkrétní formát souboru, který chce aplikace použít (JPEG nebo PNG nebo něco jiného). Pokud je požadovaný formát ztráty, kódování musí také znát požadovanou úroveň kvality.
Třída SKBitmap
definuje jednu Encode
metodu s následující syntaxí:
public Boolean Encode (SKWStream dst, SKEncodedImageFormat format, Int32 quality)
Tato metoda je podrobněji popsána krátce. Zakódovaný rastrový obrázek se zapíše do zapisovatelného datového proudu. ("W" SKWStream
znamená "zapisovatelné".) Druhý a třetí argument určují formát souboru a (pro ztrátové formáty) požadovanou kvalitu v rozsahu od 0 do 100.
Kromě toho a SKImage
SKPixmap
třídy také definují Encode
metody, které jsou poněkud všestrannější, a které můžete preferovat. Pomocí statické SKImage.FromBitmap
metody můžete snadno vytvořit SKImage
objekt z SKBitmap
objektu. Objekt můžete získat SKPixmap
z objektu SKBitmap
PeekPixels
pomocí metody.
Jedna z metod definovaných Encode
SKImage
pomocí žádných parametrů a automaticky se uloží do formátu PNG. Tato metoda bez parametrů je velmi snadná.
Kód specifický pro platformu pro ukládání rastrových souborů
Když objekt zakódujete SKBitmap
do konkrétního formátu souboru, obvykle zůstanete s objektem datového proudu nějakého druhu nebo polem dat. Některé metody Encode
(včetně metody bez parametrů definovaných SKImage
) vrací SKData
objekt, který lze pomocí metody převést na pole bajtů ToArray
. Tato data se pak musí uložit do souboru.
Ukládání do souboru v místním úložišti aplikace je poměrně snadné, protože pro tuto úlohu můžete použít standardní System.IO
třídy a metody. Tato technika je ukázaná v článku Animating SkiaSharp Bitmaps in connection with animating a series bitmaps of the Mandelbrot set.
Pokud chcete soubor sdílet jinými aplikacemi, musíte ho uložit do knihovny fotek uživatele. Tato úloha vyžaduje kód specifický pro platformu a použití Xamarin.FormsDependencyService
.
Projekt SkiaSharpFormsDemo v aplikaci SkiaSharpFormsDemos definuje IPhotoLibrary
rozhraní používané s DependencyService
třídou. Tím se definuje syntaxe SavePhotoAsync
metody:
public interface IPhotoLibrary
{
Task<Stream> PickPhotoAsync();
Task<bool> SavePhotoAsync(byte[] data, string folder, string filename);
}
Toto rozhraní také definuje metodu PickPhotoAsync
, která se používá k otevření nástroje pro výběr souborů specifických pro platformu pro knihovnu fotografií zařízení.
První SavePhotoAsync
argument je pole bajtů, které obsahuje rastrový obrázek již zakódovaný do určitého formátu souboru, například JPEG nebo PNG. Je možné, že aplikace může chtít izolovat všechny rastrové obrázky, které vytvoří, do konkrétní složky, která je zadaná v dalším parametru a za ním název souboru. Metoda vrátí logickou hodnotu označující úspěch nebo ne.
V následujících částech se dozvíte, jak SavePhotoAsync
se implementuje na jednotlivých platformách.
Implementace iOS
Implementace SavePhotoAsync
iOS používá metodu 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;
}
}
Bohužel neexistuje způsob, jak zadat název souboru nebo složku obrázku.
Soubor Info.plist v projektu iOS vyžaduje klíč označující, že přidává obrázky do knihovny fotografií:
<key>NSPhotoLibraryAddUsageDescription</key>
<string>SkiaSharp Forms Demos adds images to your photo library</string>
Dávejte pozor! Klíč oprávnění pro jednoduchý přístup ke knihovně fotek je velmi podobný, ale ne stejný:
<key>NSPhotoLibraryUsageDescription</key>
<string>SkiaSharp Forms Demos accesses your photo library</string>
Implementace Androidu
Implementace Androidu SavePhotoAsync
nejprve zkontroluje, jestli folder
je null
argument nebo prázdný řetězec. Pokud ano, rastrový obrázek se uloží do kořenového adresáře knihovny fotek. Jinak se složka získá a pokud neexistuje, vytvoří se:
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;
}
}
Volání MediaScannerConnection.ScanFile
není nezbytně nutné, ale pokud program testujete okamžitou kontrolou knihovny fotek, pomůže to hodně aktualizací zobrazení galerie knihoven.
Soubor AndroidManifest.xml vyžaduje následující značku oprávnění:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Implementace UPW
Implementace SavePhotoAsync
UPW je velmi podobná struktuře jako implementace Androidu:
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;
}
}
Část Schopnosti souboru Package.appxmanifest vyžaduje knihovnu obrázků.
Zkoumání formátů obrázků
Tady je Encode
metoda SKImage
znovu:
public Boolean Encode (SKWStream dst, SKEncodedImageFormat format, Int32 quality)
SKEncodedImageFormat
je výčet se členy, které odkazují na jedenáct rastrových formátů souborů, z nichž některé jsou spíše nejasné:
Astc
— Adaptivní škálovatelná komprese texturyBmp
— Windows BitmapDng
— Adobe Digital NegativeGif
— Formát výměny grafikyIco
— Obrázky ikon WindowsJpeg
— Společná fotografická expertská skupinaKtx
— Formát textury Khronosu pro OpenGLPkm
— Vlastní formát pro GrafX2Png
— Přenosná síťová grafikaWbmp
— Formát rastrového obrázku bezdrátové aplikace (1 bit na pixel)Webp
— Formát Google WebP
Jak uvidíte krátce, skiaSharp ve skutečnosti podporuje pouze tři z těchto formátů souborů (Jpeg
Png
aWebp
) .
Pokud chcete uložit objekt pojmenovaný SKBitmap
bitmap
do knihovny fotografií uživatele, potřebujete také člena výčtu SKEncodedImageFormat
s názvem imageFormat
a (pro ztrátové formáty) celočíselnou quality
proměnnou. Pomocí následujícího kódu můžete tento rastrový obrázek uložit do souboru s názvem filename
ve folder
složce:
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!
}
Třída SKManagedWStream
je odvozena od SKWStream
(což je zkratka pro "zapisovatelný datový proud"). Metoda Encode
zapíše do daného datového proudu zakódovaný rastrový soubor. Komentáře v tomto kódu odkazují na určitou kontrolu chyb, kterou možná budete muset provést.
Stránka Uložit formáty souborů v aplikaci SkiaSharpFormsDemos používá podobný kód, který vám umožní experimentovat s uložením rastrového obrázku v různých formátech.
Soubor XAML obsahuje SKCanvasView
rastrový obrázek, zatímco zbytek stránky obsahuje vše, co aplikace potřebuje k volání Encode
metody SKBitmap
. Má Picker
pro člena výčtu SKEncodedImageFormat
, Slider
pro argument kvality pro ztrátové rastrové formáty, dvě Entry
zobrazení pro název souboru a název složky a Button
pro uložení souboru.
<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>
Soubor s kódem načte rastrový zdroj a použije ho SKCanvasView
k jeho zobrazení. Tento rastrový obrázek se nikdy nezmění. Obslužná rutina SelectedIndexChanged
pro Picker
modifikuje název souboru s příponou, která je stejná jako člen výčtu:
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!";
}
}
}
}
}
Obslužná rutina Clicked
pro veškerou skutečnou Button
práci. Získá dva argumenty pro Encode
z Picker
a Slider
a pak použije kód zobrazený dříve k vytvoření SKManagedWStream
metody Encode
. Entry
Dvě zobrazení zařízená složka a názvy souborů pro metoduSavePhotoAsync
.
Většina této metody se věnuje řešení problémů nebo chyb. Pokud Encode
vytvoří prázdné pole, znamená to, že konkrétní formát souboru není podporovaný. Pokud SavePhotoAsync
se vrátí false
, soubor nebyl úspěšně uložen.
Tady je spuštěný program:
Tento snímek obrazovky ukazuje pouze tři formáty podporované na těchto platformách:
- JPEG
- PNG
- WebP
Pro všechny ostatní formáty Encode
metoda zapíše do datového proudu nic a výsledné bajtové pole je prázdné.
Rastrový obrázek, který stránka Uložit formáty souborů uloží, je čtvercový 600 pixelů. S 4 bajty na pixel je to celkem 1 440 000 bajtů v paměti. Následující tabulka ukazuje velikost souboru pro různé kombinace formátu a kvality souboru:
Formát | Kvalita | Velikost |
---|---|---|
PNG | – | 492K |
JPEG | 0 | 2,95 K |
50 | 22.1K | |
100 | 206K | |
WebP | 0 | 2.71K |
50 | 11,9K | |
100 | 101K |
Můžete experimentovat s různými nastaveními kvality a zkoumat výsledky.
Ukládání malování prstem
Jedním z běžných použití rastrového obrázku je kreslení programů, kde funguje jako něco, co se nazývá stínový rastr. Veškerý výkres se zachová na rastrovém obrázku, který pak program zobrazí. Rastrový obrázek je také užitečný pro uložení výkresu.
V článku SkiaSharp se Malování Prst ukazuje, jak pomocí dotykového sledování implementovat primitivní program pro malování prstem. Program podporoval pouze jednu barvu a pouze jednu šířku tahu, ale zachoval celý výkres v kolekci SKPath
objektů.
Prst Malování se stránkou Uložit v ukázce SkiaSharpFormsDemos zachová také celý výkres v kolekci SKPath
objektů, ale také vykreslí výkres na rastrovém obrázku, který může uložit do knihovny fotografií.
Většina tohoto programu je podobná původnímu programu Finger Malování. Jedním z vylepšení je, že soubor XAML teď vytvoří instance tlačítek označených jako Vymazat a Uložit:
<?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>
Soubor s kódem udržuje pole typu SKBitmap
s názvem saveBitmap
. Tento rastrový obrázek se vytvoří nebo znovu vytvoří v obslužné rutině PaintSurface
při každé změně velikosti plochy zobrazení. Pokud je potřeba rastrový obrázek znovu vytvořit, obsah existujícího rastrového obrázku se zkopíruje do nového rastrového obrázku, aby se zachovalo vše bez ohledu na to, jak se plocha zobrazení změní ve velikosti:
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);
}
···
}
Výkres provedený obslužnou rutinou PaintSurface
se vyskytuje na samém konci a skládá se výhradně z vykreslení rastrového obrázku.
Dotykové zpracování je podobné dřívějšímu programu. Program udržuje dvě kolekce a completedPaths
, které obsahují vše, inProgressPaths
co uživatel nakreslil od posledního vymazání zobrazení. Pro každou událost dotyku volá UpdateBitmap
obslužná rutinaOnTouchEffectAction
:
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();
}
···
}
Metoda UpdateBitmap
překresluje saveBitmap
vytvořením nové SKCanvas
, vymazání a následné vykreslení všech cest na rastrovém obrázku. Končí tím, že zneplatní, canvasView
aby rastrový obrázek mohl být nakreslen na displeji.
Tady jsou obslužné rutiny pro tato dvě tlačítka. Tlačítko Vymazat vymaže obě kolekce cest, aktualizace saveBitmap
(což vede k vymazání rastrového obrázku) a zneplatní 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");
}
}
}
}
Obslužná rutina tlačítka Uložit používá zjednodušenou Encode
metodu z SKImage
. Tato metoda kóduje pomocí formátu PNG. Objekt SKImage
je vytvořen na saveBitmap
základě a SKData
objekt obsahuje kódovaný soubor PNG.
Metoda ToArray
SKData
získá pole bajtů. Toto je to, co se předává SavePhotoAsync
metodě spolu s pevným názvem složky a jedinečným názvem souboru vytvořeným z aktuálního data a času.
Tady je program v akci:
Ve vzorci Spin Malování se používá velmi podobná technika. Jedná se také o program malování prstem s tím rozdílem, že uživatel maluje na otáčejícím se disku, který pak reprodukuje návrhy na dalších čtyřech kvadrantech. Barva prstu se změní při otáčení disku:
Tlačítko Uložit třídy SpinPaint
je podobné prstu Malování v tom, že uloží obrázek do pevného názvu složky (Španělsko Malování) a název souboru vytvořeného z data a času.