Salvataggio di bitmap SkiaSharp in file
Dopo che un'applicazione SkiaSharp ha creato o modificato una bitmap, l'applicazione potrebbe voler salvare la bitmap nella raccolta foto dell'utente:
Questa attività include due passaggi:
- Conversione della bitmap SkiaSharp in dati in un formato di file specifico, ad esempio JPEG o PNG.
- Salvare il risultato nella raccolta foto usando codice specifico della piattaforma.
Formati e codec di file
La maggior parte dei formati di file bitmap più diffusi di oggi usa la compressione per ridurre lo spazio di archiviazione. Le due ampie categorie di tecniche di compressione sono denominate lossy e lossless. Questi termini indicano se l'algoritmo di compressione comporta o meno la perdita di dati.
Il formato più diffuso è stato sviluppato dal Joint Photographic Experts Group ed è chiamato JPEG. L'algoritmo di compressione JPEG analizza l'immagine usando uno strumento matematico denominato trasformazione del coseno discreto e tenta di rimuovere i dati che non sono cruciali per preservare la fedeltà visiva dell'immagine. Il grado di compressione può essere controllato con un'impostazione generalmente definita qualità. Le impostazioni di qualità più elevate comportano file di dimensioni maggiori.
Al contrario, un algoritmo di compressione senza perdita di dati analizza l'immagine per la ripetizione e i modelli di pixel che possono essere codificati in modo da ridurre i dati, ma non comportano la perdita di informazioni. I dati bitmap originali possono essere ripristinati interamente dal file compresso. Il formato primario di file compresso senza perdita di dati attualmente in uso è Portable Network Graphics (PNG).
In genere, JPEG viene usato per le fotografie, mentre PNG viene usato per le immagini che sono state generate manualmente o in modo algoritmico. Qualsiasi algoritmo di compressione senza perdita che riduce le dimensioni di alcuni file deve necessariamente aumentare le dimensioni di altri. Fortunatamente, questo aumento delle dimensioni si verifica in genere solo per i dati che contengono molte informazioni casuali (o apparentemente casuali).
Gli algoritmi di compressione sono sufficientemente complessi da giustificare due termini che descrivono i processi di compressione e decompressione:
- decodifica : leggere un formato di file bitmap e decomprimerlo
- encode : comprimere la bitmap e scrivere in un formato di file bitmap
La SKBitmap
classe contiene diversi metodi denominati Decode
che creano un oggetto da un'origine SKBitmap
compressa. Tutto ciò che è necessario è fornire un nome file, un flusso o una matrice di byte. Il decodificatore può determinare il formato del file e consegnarlo alla funzione di decodifica interna appropriata.
Inoltre, la SKCodec
classe dispone di due metodi denominati Create
che possono creare un SKCodec
oggetto da un'origine compressa e consentire a un'applicazione di essere più coinvolti nel processo di decodifica. La SKCodec
classe è illustrata nell'articolo Animazione di bitmap SkiaSharp in relazione alla decodifica di un file GIF animato.
Quando si codifica una bitmap, sono necessarie altre informazioni: il codificatore deve conoscere il formato di file specifico che l'applicazione vuole usare (JPEG o PNG o qualcos'altro). Se si desidera un formato di perdita, la codifica deve conoscere anche il livello di qualità desiderato.
La SKBitmap
classe definisce un Encode
metodo con la sintassi seguente:
public Boolean Encode (SKWStream dst, SKEncodedImageFormat format, Int32 quality)
Questo metodo è descritto in modo più dettagliato a breve. La bitmap codificata viene scritta in un flusso scrivibile. ('W' in SKWStream
sta per "scrivibile".) Il secondo e il terzo argomento specificano il formato di file e (per i formati di perdita) la qualità desiderata compresa tra 0 e 100.
Inoltre, le SKImage
classi e SKPixmap
definiscono Encode
anche metodi che sono un po 'più versatili e che si potrebbe preferire. È possibile creare facilmente un SKImage
oggetto da un SKBitmap
oggetto usando il metodo statico SKImage.FromBitmap
. È possibile ottenere un SKPixmap
oggetto da un SKBitmap
oggetto usando il PeekPixels
metodo .
Uno dei Encode
metodi definiti da SKImage
non ha parametri e salva automaticamente in un formato PNG. Questo metodo senza parametri è molto facile da usare.
Codice specifico della piattaforma per il salvataggio di file bitmap
Quando si codifica un SKBitmap
oggetto in un formato di file specifico, in genere si verrà lasciati con un oggetto flusso di qualche ordinamento o una matrice di dati. Alcuni dei Encode
metodi (incluso quello senza parametri definiti da SKImage
) restituiscono un SKData
oggetto, che può essere convertito in una matrice di byte usando il ToArray
metodo . Questi dati devono quindi essere salvati in un file.
Il salvataggio in un file nell'archiviazione locale dell'applicazione è piuttosto semplice perché è possibile usare classi e metodi standard System.IO
per questa attività. Questa tecnica è illustrata nell'articolo Animare le bitmap SkiaSharp in relazione all'animazione di una serie di bitmap del set Di Mandelbrot.
Se si desidera che il file venga condiviso da altre applicazioni, deve essere salvato nella raccolta foto dell'utente. Questa attività richiede codice specifico della Xamarin.FormsDependencyService
piattaforma e l'uso di .
Il progetto SkiaSharpFormsDemo nell'applicazione di esempio definisce un'interfaccia IPhotoLibrary
usata con la DependencyService
classe . In questo modo viene definita la sintassi di un SavePhotoAsync
metodo:
public interface IPhotoLibrary
{
Task<Stream> PickPhotoAsync();
Task<bool> SavePhotoAsync(byte[] data, string folder, string filename);
}
Questa interfaccia definisce anche il PickPhotoAsync
metodo , usato per aprire la selezione file specifica della piattaforma per la libreria di foto del dispositivo.
Per SavePhotoAsync
, il primo argomento è una matrice di byte che contiene la bitmap già codificata in un formato di file specifico, ad esempio JPEG o PNG. È possibile che un'applicazione voglia isolare tutte le bitmap create in una determinata cartella, specificata nel parametro successivo, seguita dal nome del file. Il metodo restituisce un valore Boolean che indica o meno l'esito positivo.
Le sezioni seguenti illustrano come SavePhotoAsync
viene implementato in ogni piattaforma.
Implementazione di iOS
L'implementazione di iOS di SavePhotoAsync
usa il SaveToPhotosAlbum
metodo di 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;
}
}
Sfortunatamente, non è possibile specificare un nome file o una cartella per l'immagine.
Il file Info.plist nel progetto iOS richiede una chiave che indica che aggiunge immagini alla raccolta foto:
<key>NSPhotoLibraryAddUsageDescription</key>
<string>SkiaSharp Forms Demos adds images to your photo library</string>
Attento! La chiave di autorizzazione per accedere semplicemente alla raccolta foto è molto simile ma non la stessa:
<key>NSPhotoLibraryUsageDescription</key>
<string>SkiaSharp Forms Demos accesses your photo library</string>
Implementazione di Android
L'implementazione android di SavePhotoAsync
controlla innanzitutto se l'argomento folder
è null
o una stringa vuota. In tal caso, la bitmap viene salvata nella directory radice della raccolta foto. In caso contrario, la cartella viene ottenuta e, se non esiste, viene creata:
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;
}
}
La chiamata a MediaScannerConnection.ScanFile
non è strettamente necessaria, ma se si sta testando il programma controllando immediatamente la raccolta foto, è molto utile aggiornando la visualizzazione della raccolta librerie.
Il file AndroidManifest.xml richiede il tag di autorizzazione seguente:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Implementazione della piattaforma UWP
L'implementazione UWP di SavePhotoAsync
è molto simile alla struttura dell'implementazione di 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;
}
}
La sezione Capabilities del file Package.appxmanifest richiede la raccolta immagini.
Esplorazione dei formati di immagine
Ecco il Encode
metodo di SKImage
nuovo:
public Boolean Encode (SKWStream dst, SKEncodedImageFormat format, Int32 quality)
SKEncodedImageFormat
è un'enumerazione con membri che fanno riferimento a undici formati di file bitmap, alcuni dei quali sono piuttosto oscuri:
Astc
— Compressione a trama scalabile adattivaBmp
— Bitmap di WindowsDng
— Adobe Digital NegativeGif
— Formato interscambio graficoIco
— Immagini delle icone di WindowsJpeg
— Gruppo congiunto di esperti fotograficiKtx
— Formato di trama Khronos per OpenGLPkm
— Formato personalizzato per GrafX2Png
— Grafica di rete portabileWbmp
— Formato bitmap del protocollo applicazione wireless (1 bit per pixel)Webp
— Formato Google WebP
Come si vedrà a breve, solo tre di questi formati di file (Jpeg
, Png
e Webp
) sono effettivamente supportati da SkiaSharp.
Per salvare un SKBitmap
oggetto denominato bitmap
nella raccolta foto dell'utente, è necessario anche un membro dell'enumerazione SKEncodedImageFormat
denominata imageFormat
e (per i formati di perdita) di una variabile integer quality
. È possibile usare il codice seguente per salvare tale bitmap in un file con il nome filename
nella folder
cartella :
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!
}
La SKManagedWStream
classe deriva da SKWStream
(che sta per "flusso scrivibile"). Il Encode
metodo scrive il file bitmap codificato in tale flusso. I commenti in tale codice fanno riferimento a un controllo degli errori che potrebbe essere necessario eseguire.
La pagina Salva formati di file nell'applicazione di esempio usa codice simile per consentire di sperimentare il salvataggio di una bitmap nei vari formati.
Il file XAML contiene un oggetto SKCanvasView
che visualizza una bitmap, mentre il resto della pagina contiene tutto ciò che l'applicazione deve chiamare il Encode
metodo di SKBitmap
. Ha un Picker
oggetto per un membro dell'enumerazione SKEncodedImageFormat
, un Slider
per l'argomento di qualità per i formati bitmap in perdita, due Entry
visualizzazioni per un nome file e un nome di cartella e un Button
per il salvataggio del file.
<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>
Il file code-behind carica una risorsa bitmap e usa per SKCanvasView
visualizzarlo. La bitmap non cambia mai. Il SelectedIndexChanged
gestore per l'oggetto Picker
modifica il nome file con un'estensione uguale al membro di enumerazione:
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!";
}
}
}
}
}
Il Clicked
gestore per l'oggetto Button
esegue tutto il lavoro reale. Ottiene due argomenti per Encode
da Picker
e Slider
e quindi usa il codice illustrato in precedenza per creare un SKManagedWStream
oggetto per il Encode
metodo . Le due Entry
visualizzazioni forniscono nomi di file e cartelle per il SavePhotoAsync
metodo .
La maggior parte di questo metodo è dedicata alla gestione di problemi o errori. Se Encode
crea una matrice vuota, significa che il formato di file specifico non è supportato. Se SavePhotoAsync
restituisce false
, il file non è stato salvato correttamente.
Ecco il programma in esecuzione:
Screenshot che mostra gli unici tre formati supportati in queste piattaforme:
- JPEG
- PNG
- WebP
Per tutti gli altri formati, il Encode
metodo scrive nulla nel flusso e la matrice di byte risultante è vuota.
La bitmap salvata nella pagina Salva formati file è un quadrato di 600 pixel. Con 4 byte per pixel, questo è un totale di 1.440.000 byte in memoria. La tabella seguente illustra le dimensioni del file per varie combinazioni di formato e qualità del file:
Formato | Qualità | Dimensione |
---|---|---|
PNG | N/D | 492.000 |
JPEG | 0 | 2,95 K |
50 | 22.1K | |
100 | 206.000 | |
WebP | 0 | 2.71.000 |
50 | 11,9 K | |
100 | 101.000 |
È possibile sperimentare diverse impostazioni di qualità ed esaminare i risultati.
Salvataggio dell'arte con dita
Un uso comune di una bitmap è nei programmi di disegno, in cui funziona come qualcosa chiamato bitmap di ombreggiatura. Tutto il disegno viene conservato nella bitmap, che viene quindi visualizzato dal programma. La bitmap è utile anche per salvare il disegno.
L'articolo Finger Painting in SkiaSharp ha illustrato come usare il rilevamento del tocco per implementare un programma primitivo di pittura con dita. Il programma supportava un solo colore e una sola larghezza del tratto, ma conservava l'intero disegno in una raccolta di SKPath
oggetti.
La pagina Finger Paint con Salva nell'esempio mantiene anche l'intero disegno in una raccolta di SKPath
oggetti, ma esegue anche il rendering del disegno su una bitmap, che può essere salvata nella raccolta foto.
Gran parte di questo programma è simile al programma originale Finger Paint . Un miglioramento è che il file XAML crea ora un'istanza dei pulsanti con etichetta Cancella e Salva:
<?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>
Il file code-behind gestisce un campo di tipo SKBitmap
denominato saveBitmap
. Questa bitmap viene creata o ricreata nel PaintSurface
gestore ogni volta che cambiano le dimensioni della superficie di visualizzazione. Se la bitmap deve essere ricreata, il contenuto della bitmap esistente viene copiato nella nuova bitmap in modo che tutto venga mantenuto indipendentemente dal modo in cui la superficie di visualizzazione cambia di dimensione:
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);
}
···
}
Il disegno eseguito dal PaintSurface
gestore si verifica alla fine e consiste esclusivamente nel rendering della bitmap.
L'elaborazione del tocco è simile al programma precedente. Il programma gestisce due raccolte, inProgressPaths
e completedPaths
, che contengono tutto ciò che l'utente ha disegnato dall'ultima volta che la visualizzazione è stata cancellata. Per ogni evento di tocco, il OnTouchEffectAction
gestore chiama 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();
}
···
}
Il UpdateBitmap
metodo viene ridisegnato saveBitmap
creando un nuovo SKCanvas
oggetto , cancellandolo e quindi eseguendo il rendering di tutti i percorsi nella bitmap. Termina invalidando canvasView
in modo che la bitmap possa essere disegnata sullo schermo.
Ecco i gestori per i due pulsanti. Il pulsante Cancella cancella entrambe le raccolte di percorsi, gli aggiornamenti saveBitmap
(che comportano la cancellazione della bitmap) e invalida :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");
}
}
}
}
Il gestore del pulsante Salva usa il metodo semplificato Encode
da SKImage
. Questo metodo codifica usando il formato PNG. L'oggetto SKImage
viene creato in base a saveBitmap
e l'oggetto SKData
contiene il file PNG codificato.
Il ToArray
metodo di SKData
ottiene una matrice di byte. Questo è ciò che viene passato al metodo, insieme a SavePhotoAsync
un nome di cartella fisso, e un nome file univoco costruito dalla data e dall'ora correnti.
Ecco il programma in azione:
Nell'esempio viene usata una tecnica molto simile. Si tratta anche di un programma di disegno con dita, ad eccezione del fatto che l'utente disegna su un disco rotante che riproduce quindi i disegni sugli altri quattro quadranti. Il colore della vernice del dito cambia man mano che il disco sta ruotando:
Il pulsante Salva della SpinPaint
classe è simile a Finger Paint in quanto salva l'immagine in un nome di cartella fisso (SpagnaPaint) e un nome file costruito dalla data e dall'ora.