Aracılığıyla paylaş


SkiaSharp bit eşlemlerini dosyalara kaydetme

Bir SkiaSharp uygulaması bit eşlem oluşturduktan veya değiştirdikten sonra, uygulama bit eşlemi kullanıcının fotoğraf kitaplığına kaydetmek isteyebilir:

Bit Eşlemleri Kaydetme

Bu görev iki adımı kapsar:

  • SkiaSharp bit eşlemini JPEG veya PNG gibi belirli bir dosya biçimindeki verilere dönüştürme.
  • Sonucu platforma özgü kodu kullanarak fotoğraf kitaplığına kaydetme.

Dosya biçimleri ve codec bileşenleri

Günümüzün popüler bit eşlem dosya biçimlerinin çoğu depolama alanını azaltmak için sıkıştırma kullanır. Sıkıştırma tekniklerinin iki geniş kategorisi kayıplı ve kayıpsız olarak adlandırılır. Bu terimler, sıkıştırma algoritmasının veri kaybına neden olup olmadığını gösterir.

En popüler kayıp biçimi, Ortak Fotoğraf Uzmanları Grubu tarafından geliştirilmiştir ve JPEG olarak adlandırılır. JPEG sıkıştırma algoritması, ayrık kosinüs dönüşümü adlı bir matematiksel araç kullanarak görüntüyü analiz eder ve görüntünün görsel doğruluğunu koruma açısından önemli olmayan verileri kaldırmaya çalışır. Sıkıştırma derecesi genellikle kalite olarak adlandırılan bir ayar ile kontrol edilebilir. Daha yüksek kalite ayarları daha büyük dosyalara neden olabilir.

Buna karşılık, kayıpsız sıkıştırma algoritması görüntüyü, verileri azaltacak ancak herhangi bir bilginin kaybolmasına neden olmayacak şekilde kodlanabilen piksellerin tekrarı ve desenleri için analiz eder. Özgün bit eşlem verileri tamamen sıkıştırılmış dosyadan geri yüklenebilir. Günümüzde kullanılan birincil kayıpsız sıkıştırılmış dosya biçimi Taşınabilir Ağ Grafikleri'dir (PNG).

Jpeg genellikle fotoğraflar için kullanılırken PNG ise el ile veya algoritmik olarak oluşturulmuş görüntüler için kullanılır. Bazı dosyaların boyutunu azaltan kayıpsız sıkıştırma algoritmaları mutlaka başkalarının boyutunu artırmalıdır. Neyse ki, bu boyut artışı genellikle yalnızca çok sayıda rastgele (veya rastgele görünen) bilgi içeren veriler için oluşur.

Sıkıştırma algoritmaları, sıkıştırma ve sıkıştırmayı kaldırma işlemlerini açıklayan iki terim garanti edecek kadar karmaşıktır:

  • kod çözme — bit eşlem dosyası biçimini okuma ve sıkıştırmasını açma
  • kodlama — bit eşlemi sıkıştırın ve bit eşlem dosyası biçiminde yazın

sınıfı, SKBitmap sıkıştırılmış bir kaynaktan bir SKBitmap oluşturan adlı Decode birkaç yöntem içerir. Gereken tek şey bir dosya adı, akış veya bayt dizisi sağlamaktır. Kod çözücü, dosya biçimini belirleyebilir ve uygun iç kod çözme işlevine teslim edebilir.

Buna ek olarak, SKCodec sınıfı sıkıştırılmış bir kaynaktan nesne SKCodec oluşturabilen ve bir uygulamanın kod çözme işlemine daha fazla katılmasına izin veren adlı Create iki yönteme sahiptir. (SınıfıSKCodec, animasyonlu GIF dosyasının kodunu çözmeyle bağlantılı olarak SkiaSharp Bit Eşlemlerini Animasyonlandırma makalesinde gösterilmiştir.)

Bit eşlem kodlanırken daha fazla bilgi gerekir: Kodlayıcı, uygulamanın kullanmak istediği belirli dosya biçimini (JPEG veya PNG veya başka bir şey) bilmelidir. Kayıplı bir biçim istenirse kod istenen kalite düzeyini de bilmeli.

SKBitmap sınıfı aşağıdaki söz dizimine sahip bir Encode yöntem tanımlar:

public Boolean Encode (SKWStream dst, SKEncodedImageFormat format, Int32 quality)

Bu yöntem kısa süre içinde daha ayrıntılı olarak açıklanmıştır. Kodlanmış bit eşlem yazılabilir bir akışa yazılır. (içindeki SKWStream 'W', "yazılabilir" anlamına gelir.) İkinci ve üçüncü bağımsız değişkenler dosya biçimini ve (kayıplı biçimler için) 0 ile 100 arasında değişen istenen kaliteyi belirtir.

Ayrıca SKImage ve SKPixmap sınıfları, biraz daha çok yönlü olan ve tercih edebileceğiniz yöntemleri de tanımlar Encode . Statik SKImage.FromBitmap yöntemi kullanarak bir nesneden kolayca nesne SKBitmap oluşturabilirsinizSKImage. yöntemini kullanarak PeekPixels bir SKBitmap nesneden nesne alabilirsinizSKPixmap.

Encode tarafından SKImage tanımlanan yöntemlerden birinin parametresi yoktur ve otomatik olarak PNG biçimine kaydedilir. Bu parametresiz yöntemin kullanımı çok kolaydır.

Bit eşlem dosyalarını kaydetmek için platforma özgü kod

Bir nesneyi belirli bir SKBitmap dosya biçiminde kodladığınızda, genellikle bir tür akış nesnesi veya bir veri dizisiyle kalırsınız. Yöntemlerden Encode bazıları (parametresi tanımlı SKImageolmayan yöntem dahil) yöntemi kullanılarak ToArray bayt dizisine dönüştürülebilen bir SKData nesne döndürür. Bu verilerin bir dosyaya kaydedilmesi gerekir.

Bu görev için standart System.IO sınıfları ve yöntemleri kullanabileceğiniz için uygulama yerel depolama alanında bir dosyaya kaydetmek oldukça kolaydır. Bu teknik, Mandelbrot kümesinin bit eşlemlerinden oluşan bir dizi animasyonla bağlantılı olarak SkiaSharp Bit Eşlemlerini Animasyonlama makalesinde gösterilmiştir.

Dosyanın diğer uygulamalar tarafından paylaşılmasını istiyorsanız, dosyanın kullanıcının fotoğraf kitaplığına kaydedilmesi gerekir. Bu görev için platforma özgü kod ve kullanımı Xamarin.FormsDependencyServicegerekir.

Örnek uygulamadaki SkiaSharpFormsDemo projesi, sınıfıyla DependencyService kullanılan bir IPhotoLibrary arabirimi tanımlar. Bu, bir SavePhotoAsync yöntemin söz dizimini tanımlar:

public interface IPhotoLibrary
{
    Task<Stream> PickPhotoAsync();

    Task<bool> SavePhotoAsync(byte[] data, string folder, string filename);
}

Bu arabirim, cihazın fotoğraf kitaplığı için platforma özgü dosya seçiciyi açmak için kullanılan yöntemini de tanımlar PickPhotoAsync .

için SavePhotoAsyncilk bağımsız değişken, JPEG veya PNG gibi belirli bir dosya biçiminde zaten kodlanmış bit eşlemi içeren bir bayt dizisidir. Bir uygulama, oluşturduğu tüm bit eşlemleri bir sonraki parametrede belirtilen ve ardından dosya adını içeren belirli bir klasörde yalıtmak isteyebilir. yöntemi başarılı olup olmadığını gösteren bir Boole döndürür.

Aşağıdaki bölümlerde her platformda nasıl SavePhotoAsync uygulandığı açıklanmıştır.

iOS uygulaması

uygulamasının SavePhotoAsync iOS uygulaması yöntemini SaveToPhotosAlbum UIImagekullanır:

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

Ne yazık ki, görüntü için bir dosya adı veya klasör belirtmenin bir yolu yoktur.

iOS projesindeki Info.plist dosyası, fotoğraf kitaplığına resim eklediğini gösteren bir anahtar gerektirir:

<key>NSPhotoLibraryAddUsageDescription</key>
<string>SkiaSharp Forms Demos adds images to your photo library</string>

Dikkat edin! Yalnızca fotoğraf kitaplığına erişmek için izin anahtarı çok benzerdir ancak aynı değildir:

<key>NSPhotoLibraryUsageDescription</key>
<string>SkiaSharp Forms Demos accesses your photo library</string>

Android uygulaması

İlk android SavePhotoAsync uygulaması, bağımsız değişkenin folder null boş bir dize olup olmadığını denetler. Öyleyse bit eşlem fotoğraf kitaplığının kök dizinine kaydedilir. Aksi takdirde, klasör elde edilir ve yoksa oluşturulur:

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

çağrısı MediaScannerConnection.ScanFile kesinlikle gerekli değildir, ancak fotoğraf kitaplığını hemen denetleyerek programınızı test ediyorsanız, kitaplık galerisi görünümünü güncelleştirerek çok yardımcı olur.

AndroidManifest.xml dosyası aşağıdaki izin etiketini gerektirir:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

UWP uygulaması

UWP uygulaması, Android uygulamasına SavePhotoAsync çok benzer:

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 dosyasının Capabilities bölümü Için Resim Kitaplığı gerekir.

Görüntü biçimlerini keşfetme

Tekrar yöntemini aşağıda bulabilirsiniz Encode SKImage :

public Boolean Encode (SKWStream dst, SKEncodedImageFormat format, Int32 quality)

SKEncodedImageFormat , bazıları oldukça belirsiz olan on bir bit eşlem dosya biçimine başvuran üyeleri olan bir numaralandırmadır:

  • Astc — Uyarlamalı Ölçeklenebilir Doku Sıkıştırma
  • Bmp — Windows Bit Eşlem
  • Dng — Adobe Digital Negative
  • Gif — Grafik Değişim Biçimi
  • Ico — Windows simgesi görüntüleri
  • Jpeg — Ortak Fotoğraf Uzmanları Grubu
  • Ktx — OpenGL için Khronos doku biçimi
  • Pkm — GrafX2 için özel biçim
  • Png — Taşınabilir Ağ Grafikleri
  • Wbmp — Kablosuz Uygulama Protokolü Bit Eşlem Biçimi (piksel başına 1 bit)
  • Webp — Google WebP biçimi

Kısa süre sonra göreceğiniz gibi bu dosya biçimlerinden yalnızca üçü (Jpeg, Pngve Webp) SkiaSharp tarafından desteklenir.

Kullanıcının fotoğraf kitaplığına adlı bitmap bir SKBitmap nesneyi kaydetmek için, ve (kayıplı biçimler için) bir tamsayı quality değişkeni adlı imageFormat sabit listesi üyesine SKEncodedImageFormat de ihtiyacınız vardır. Bu bit eşlemi klasörde adı filename folder olan bir dosyaya kaydetmek için aşağıdaki kodu kullanabilirsiniz:

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 sınıfı öğesinden SKWStream türetilir ("yazılabilir akış" anlamına gelir). yöntemi kodlanmış Encode bit eşlem dosyasını bu akışa yazar. Bu koddaki açıklamalar, gerçekleştirmeniz gerekebilecek bazı hata denetimlerine başvurur.

Örnek uygulamadaki Dosya Biçimlerini Kaydet sayfası, bit eşlemleri çeşitli biçimlerde kaydetmeyi denemenize olanak sağlamak için benzer kod kullanır.

XAML dosyası bit eşlem görüntüleyen bir SKCanvasView dosya içerirken, sayfanın geri kalanı uygulamanın yöntemini SKBitmapçağırmak Encode için ihtiyaç duyduğu her şeyi içerir. Numaralandırmanın SKEncodedImageFormat bir üyesine, Slider kayıplı bit eşlem biçimleri için kalite bağımsız değişkenine, dosya adı ve klasör adı için iki Entry görünüme ve dosyayı kaydetmeye yönelik bir Button görünümü vardırPicker.

<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>

Arka planda kod dosyası bir bit eşlem kaynağı yükler ve dosyasını kullanarak SKCanvasView görüntüler. Bu bit eşlem hiçbir zaman değişmez. işleyicisi SelectedIndexChanged Picker dosya adını numaralandırma üyesiyle aynı uzantıyla değiştirir:

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!";
                }
            }
        }
    }
}

işleyicisi Clicked Button tüm gerçek işi yapar. ve Slider'den Picker için Encode iki bağımsız değişken alır ve daha önce gösterilen kodu kullanarak yöntemi için Encode bir SKManagedWStream oluşturur. İki Entry görünüm, yöntemi için klasör ve dosya adlarını sağlar SavePhotoAsync .

Bu yöntemin çoğu sorunları veya hataları işlemeye ayrılmıştır. Boş bir dizi oluşturursa Encode , bu, belirli bir dosya biçiminin desteklenmediğini gösterir. döndürürse SavePhotoAsync false, dosya başarıyla kaydedilmedi.

Çalışan program şu şekildedir:

Dosya Biçimlerini Kaydet

Bu ekran görüntüsü, bu platformlarda desteklenen yalnızca üç biçimi gösterir:

  • JPEG
  • PNG
  • WebP

Diğer tüm biçimler için yöntemi akışa Encode hiçbir şey yazmaz ve sonuçta elde edilen bayt dizisi boş olur.

Dosya Biçimlerini Kaydet sayfasının kaydettiği bit eşlem 600 piksel karedir. Piksel başına 4 bayt ile bellekte toplam 1.440.000 bayt olur. Aşağıdaki tabloda, dosya biçiminin ve kalitesinin çeşitli bileşimleri için dosya boyutu gösterilmektedir:

Biçimlendir Kalite Size
PNG Yok 492K
JPEG 0 2,95K
50 22.1K
100 206K
WebP 0 2,71K
50 11,9K
100 101K

Çeşitli kalite ayarlarıyla denemeler yapabilir ve sonuçları inceleyebilirsiniz.

Parmak boyası resimlerini kaydetme

Bit eşlemlerin yaygın kullanımlarından biri, gölge bit eşlem olarak adlandırılan çizim programlarında kullanılır. Tüm çizim bit eşlem üzerinde tutulur ve daha sonra program tarafından görüntülenir. Bit eşlem, çizimi kaydetmek için de kullanışlıdır.

SkiaSharp'ta Parmakla Boyama makalesinde ilkel bir parmak boyama programı uygulamak için dokunma izlemenin nasıl kullanılacağı gösterilmiştir. Program yalnızca bir rengi ve yalnızca bir vuruş genişliğini desteklese de çizimin tamamını bir nesne koleksiyonunda SKPath korudu.

Örnekteki Kaydet ile Parmak Boya sayfası da çizimin tamamını bir nesne koleksiyonunda SKPath tutar, ancak aynı zamanda çizimi fotoğraf kitaplığınıza kaydedebileceği bir bit eşlem üzerinde de işler.

Bu programın çoğu orijinal Finger Paint programına benzer. Bir geliştirme, XAML dosyasının artık Temizle ve Kaydet etiketli düğmelerin örneğini oluşturmasıdır:

<?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>

Arka planda kod dosyası adlı saveBitmapbir alan SKBitmap tutar. Bu bit eşlem, görüntü yüzeyinin PaintSurface boyutu her değiştiğinde işleyicide oluşturulur veya yeniden oluşturulur. Bit eşlem yeniden oluşturulması gerekiyorsa, mevcut bit eşlem içeriği yeni bit eşleme kopyalanır, böylece ekran yüzeyi boyut olarak nasıl değişirse değişsin her şey korunur:

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

İşleyici tarafından PaintSurface yapılan çizim en sonda gerçekleşir ve yalnızca bit eşlem işlemeden oluşur.

Dokunma işlemi önceki programa benzer. Program, inProgressPaths completedPathsgörüntülemenin son temizlenişinden sonra kullanıcının çizdiği her şeyi içeren ve iki koleksiyonu tutar. İşleyici her dokunma olayı için OnTouchEffectAction öğesini çağırır 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 yöntemi, yeni SKCanvasbir oluşturup temizleyerek ve ardından bit eşlem üzerindeki tüm yolları işleyerek yeniden çizersaveBitmap. Bit eşlemin ekranda çizilmesi için geçersiz kılınarak canvasView sonuçlanıyor.

İki düğmenin işleyicileri aşağıdadır. Temizle düğmesi her iki yol koleksiyonunu da temizler, güncelleştirmeleri saveBitmap (bit eşlem temizlenir) ve geçersiz kılmasını SKCanvasViewsağlar:

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

Kaydet düğmesi işleyicisi, 'den SKImagebasitleştirilmiş Encode yöntemini kullanır. Bu yöntem PNG biçimini kullanarak kodlar. SKImage nesnesi, tabanlı saveBitmapolarak oluşturulur ve SKData nesne kodlanmış PNG dosyasını içerir.

ToArray yöntemi SKData bir bayt dizisi alır. Sabit klasör adı ve geçerli tarih ve saatle birlikte benzersiz bir dosya adı ile birlikte yöntemine geçirilen SavePhotoAsync budur.

İşte program şu şekildedir:

Parmak Boyası Kaydet

Örnekte çok benzer bir teknik kullanılmıştır. Bu aynı zamanda parmakla boyama programıdır, ancak kullanıcı dönen bir diske boyar ve ardından tasarımları diğer dört çeyrekte yeniden üretir. Disk dönerken parmak boyasının rengi değişir:

Paint'i Döndürme

Sınıfın SpinPaint Kaydet düğmesi, görüntüyü sabit bir klasör adına (SpainPaint) ve tarih ve saatten oluşturulmuş bir dosya adına kaydettiği için Finger Paint'e benzer.