Membuat, mengedit, dan menyimpan gambar bitmap

Artikel ini menjelaskan cara memuat dan menyimpan file gambar menggunakan BitmapDecoder dan BitmapEncoder dan cara menggunakan objek SoftwareBitmap untuk mewakili gambar bitmap.

Kelas SoftwareBitmap adalah API serbaguna yang dapat dibuat dari beberapa sumber termasuk file gambar, objek WriteableBitmap , permukaan Direct3D, dan kode. SoftwareBitmap memungkinkan Anda untuk dengan mudah mengonversi antara format piksel dan mode alfa yang berbeda, dan memungkinkan akses tingkat rendah ke data piksel. Selain itu, SoftwareBitmap adalah antarmuka umum yang digunakan oleh beberapa fitur Windows, termasuk:

  • CapturedFrame memungkinkan Anda untuk mendapatkan bingkai yang diambil oleh kamera sebagai SoftwareBitmap.

  • VideoFrame memungkinkan Anda mendapatkan representasi SoftwareBitmap dari VideoFrame.

  • FaceDetector memungkinkan Anda mendeteksi wajah di SoftwareBitmap.

Kode sampel dalam artikel ini menggunakan API dari namespace berikut.

using Windows.Storage;
using Windows.Storage.Pickers;
using Windows.Storage.Streams;
using Windows.Graphics.Imaging;
using Windows.UI.Xaml.Media.Imaging;

Membuat SoftwareBitmap dari file gambar dengan BitmapDecoder

Untuk membuat SoftwareBitmap dari file, dapatkan instans StorageFile yang berisi data gambar. Contoh ini menggunakan FileOpenPicker untuk memungkinkan pengguna memilih file gambar.

FileOpenPicker fileOpenPicker = new FileOpenPicker();
fileOpenPicker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
fileOpenPicker.FileTypeFilter.Add(".jpg");
fileOpenPicker.ViewMode = PickerViewMode.Thumbnail;

var inputFile = await fileOpenPicker.PickSingleFileAsync();

if (inputFile == null)
{
    // The user cancelled the picking operation
    return;
}

Panggil metode OpenAsync objek StorageFile untuk mendapatkan aliran akses acak yang berisi data gambar. Panggil metode statis BitmapDecoder.CreateAsync untuk mendapatkan instans kelas BitmapDecoder untuk aliran yang ditentukan. Panggil GetSoftwareBitmapAsync untuk mendapatkan objek SoftwareBitmap yang berisi gambar.

SoftwareBitmap softwareBitmap;

using (IRandomAccessStream stream = await inputFile.OpenAsync(FileAccessMode.Read))
{
    // Create the decoder from the stream
    BitmapDecoder decoder = await BitmapDecoder.CreateAsync(stream);

    // Get the SoftwareBitmap representation of the file
    softwareBitmap = await decoder.GetSoftwareBitmapAsync();
}

Menyimpan SoftwareBitmap ke file dengan BitmapEncoder

Untuk menyimpan SoftwareBitmap ke file, dapatkan instans StorageFile tempat gambar akan disimpan. Contoh ini menggunakan FileSavePicker untuk memungkinkan pengguna memilih file output.

FileSavePicker fileSavePicker = new FileSavePicker();
fileSavePicker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
fileSavePicker.FileTypeChoices.Add("JPEG files", new List<string>() { ".jpg" });
fileSavePicker.SuggestedFileName = "image";

var outputFile = await fileSavePicker.PickSaveFileAsync();

if (outputFile == null)
{
    // The user cancelled the picking operation
    return;
}

Panggil metode OpenAsync objek StorageFile untuk mendapatkan aliran akses acak tempat gambar akan ditulis. Panggil metode statis BitmapEncoder.CreateAsync untuk mendapatkan instans kelas BitmapEncoder untuk aliran yang ditentukan. Parameter pertama untuk CreateAsync adalah GUID yang mewakili codec yang harus digunakan untuk mengodekan gambar. Kelas BitmapEncoder mengekspos properti yang berisi ID untuk setiap codec yang didukung oleh encoder, seperti JpegEncoderId.

Gunakan metode SetSoftwareBitmap untuk mengatur gambar yang akan dikodekan. Anda dapat mengatur nilai properti BitmapTransform untuk menerapkan transformasi dasar ke gambar saat sedang dikodekan. Properti IsThumbnailGenerated menentukan apakah gambar mini dihasilkan oleh encoder. Perhatikan bahwa tidak semua format file mendukung gambar mini, jadi jika Anda menggunakan fitur ini, Anda harus menangkap kesalahan operasi yang tidak didukung yang akan dilemparkan jika gambar mini tidak didukung.

Panggil FlushAsync untuk menyebabkan encoder menulis data gambar ke file yang ditentukan.

private async void SaveSoftwareBitmapToFile(SoftwareBitmap softwareBitmap, StorageFile outputFile)
{
    using (IRandomAccessStream stream = await outputFile.OpenAsync(FileAccessMode.ReadWrite))
    {
        // Create an encoder with the desired format
        BitmapEncoder encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId, stream);

        // Set the software bitmap
        encoder.SetSoftwareBitmap(softwareBitmap);

        // Set additional encoding parameters, if needed
        encoder.BitmapTransform.ScaledWidth = 320;
        encoder.BitmapTransform.ScaledHeight = 240;
        encoder.BitmapTransform.Rotation = Windows.Graphics.Imaging.BitmapRotation.Clockwise90Degrees;
        encoder.BitmapTransform.InterpolationMode = BitmapInterpolationMode.Fant;
        encoder.IsThumbnailGenerated = true;

        try
        {
            await encoder.FlushAsync();
        }
        catch (Exception err)
        {
            const int WINCODEC_ERR_UNSUPPORTEDOPERATION = unchecked((int)0x88982F81);
            switch (err.HResult)
            {
                case WINCODEC_ERR_UNSUPPORTEDOPERATION: 
                    // If the encoder does not support writing a thumbnail, then try again
                    // but disable thumbnail generation.
                    encoder.IsThumbnailGenerated = false;
                    break;
                default:
                    throw;
            }
        }

        if (encoder.IsThumbnailGenerated == false)
        {
            await encoder.FlushAsync();
        }


    }
}

Anda dapat menentukan opsi pengodean tambahan saat membuat BitmapEncoder dengan membuat objek BitmapPropertySet baru dan mengisinya dengan satu atau beberapa objek BitmapTypedValue yang mewakili pengaturan encoder. Untuk daftar opsi encoder yang didukung, lihat Referensi opsi BitmapEncoder.

var propertySet = new Windows.Graphics.Imaging.BitmapPropertySet();
var qualityValue = new Windows.Graphics.Imaging.BitmapTypedValue(
    1.0, // Maximum quality
    Windows.Foundation.PropertyType.Single
    );

propertySet.Add("ImageQuality", qualityValue);

await Windows.Graphics.Imaging.BitmapEncoder.CreateAsync(
    Windows.Graphics.Imaging.BitmapEncoder.JpegEncoderId,
    stream,
    propertySet
);

Menggunakan SoftwareBitmap dengan kontrol Gambar XAML

Untuk menampilkan gambar dalam halaman XAML menggunakan kontrol Gambar, pertama-tama tentukan kontrol Gambar di halaman XAML Anda.

<Image x:Name="imageControl"/>

Saat ini, kontrol Gambar hanya mendukung gambar yang menggunakan pengodean BGRA8 dan dikalikan sebelumnya atau tanpa saluran alfa. Sebelum mencoba menampilkan gambar, uji untuk memastikan gambar memiliki format yang benar, dan jika tidak, gunakan metode Konversi statis SoftwareBitmap untuk mengonversi gambar ke format yang didukung.

Buat objek SoftwareBitmapSource baru. Atur konten objek sumber dengan memanggil SetBitmapAsync, meneruskan SoftwareBitmap. Kemudian Anda dapat mengatur properti Sumber kontrol Gambar ke SoftwareBitmapSource yang baru dibuat.

if (softwareBitmap.BitmapPixelFormat != BitmapPixelFormat.Bgra8 ||
    softwareBitmap.BitmapAlphaMode == BitmapAlphaMode.Straight)
{
    softwareBitmap = SoftwareBitmap.Convert(softwareBitmap, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
}

var source = new SoftwareBitmapSource();
await source.SetBitmapAsync(softwareBitmap);

// Set the source of the Image control
imageControl.Source = source;

Anda juga dapat menggunakan SoftwareBitmapSource untuk mengatur SoftwareBitmap sebagai ImageSource untuk ImageBrush.

Membuat SoftwareBitmap dari WriteableBitmap

Anda dapat membuat SoftwareBitmap dari WriteableBitmap yang ada dengan memanggil SoftwareBitmap.CreateCopyFromBuffer dan menyediakan properti PixelBuffer dari WriteableBitmap untuk mengatur data piksel. Argumen kedua memungkinkan Anda meminta format piksel untuk WriteableBitmap yang baru dibuat. Anda dapat menggunakan properti PixelWidth dan PixelHeight dari WriteableBitmap untuk menentukan dimensi gambar baru.

SoftwareBitmap outputBitmap = SoftwareBitmap.CreateCopyFromBuffer(
    writeableBitmap.PixelBuffer,
    BitmapPixelFormat.Bgra8,
    writeableBitmap.PixelWidth,
    writeableBitmap.PixelHeight
);

Membuat atau mengedit SoftwareBitmap secara terprogram

Sejauh ini topik ini telah membahas bekerja dengan file gambar. Anda juga dapat membuat SoftwareBitmap baru secara terprogram dalam kode dan menggunakan teknik yang sama untuk mengakses dan memodifikasi data piksel SoftwareBitmap.

SoftwareBitmap menggunakan interop COM untuk mengekspos buffer mentah yang berisi data piksel.

Untuk menggunakan interop COM, Anda harus menyertakan referensi ke namespace Layanan System.Runtime.InteropServices dalam proyek Anda.

using System.Runtime.InteropServices;

Inisialisasi antarmuka COM IMemoryBufferByteAccess dengan menambahkan kode berikut dalam namespace Anda.

[ComImport]
[Guid("5B0D3235-4DBA-4D44-865E-8F1D0E4FD04D")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
unsafe interface IMemoryBufferByteAccess
{
    void GetBuffer(out byte* buffer, out uint capacity);
}

Buat SoftwareBitmap baru dengan format dan ukuran piksel yang Anda inginkan. Atau, gunakan SoftwareBitmap yang sudah ada yang ingin Anda edit data pikselnya. Panggil SoftwareBitmap.LockBuffer untuk mendapatkan instans kelas BitmapBuffer yang mewakili buffer data piksel. Transmisikan BitmapBuffer ke antarmuka IMemoryBufferByteAccess COM lalu panggil IMemoryBufferByteAccess.GetBuffer untuk mengisi array byte dengan data. Gunakan metode BitmapBuffer.GetPlaneDescription untuk mendapatkan objek BitmapPlaneDescription yang akan membantu Anda menghitung offset ke dalam buffer untuk setiap piksel.

softwareBitmap = new SoftwareBitmap(BitmapPixelFormat.Bgra8, 100, 100, BitmapAlphaMode.Premultiplied);

using (BitmapBuffer buffer = softwareBitmap.LockBuffer(BitmapBufferAccessMode.Write))
{
    using (var reference = buffer.CreateReference())
    {
        byte* dataInBytes;
        uint capacity;
        ((IMemoryBufferByteAccess)reference).GetBuffer(out dataInBytes, out capacity);

        // Fill-in the BGRA plane
        BitmapPlaneDescription bufferLayout = buffer.GetPlaneDescription(0);
        for (int i = 0; i < bufferLayout.Height; i++)
        {
            for (int j = 0; j < bufferLayout.Width; j++)
            {

                byte value = (byte)((float)j / bufferLayout.Width * 255);
                dataInBytes[bufferLayout.StartIndex + bufferLayout.Stride * i + 4 * j + 0] = value;
                dataInBytes[bufferLayout.StartIndex + bufferLayout.Stride * i + 4 * j + 1] = value;
                dataInBytes[bufferLayout.StartIndex + bufferLayout.Stride * i + 4 * j + 2] = value;
                dataInBytes[bufferLayout.StartIndex + bufferLayout.Stride * i + 4 * j + 3] = (byte)255;
            }
        }
    }
}

Karena metode ini mengakses buffer mentah yang mendasari jenis Windows Runtime, metode ini harus dinyatakan menggunakan kata kunci yang tidak aman . Anda juga harus mengonfigurasi proyek di Microsoft Visual Studio untuk mengizinkan kompilasi kode tidak aman dengan membuka halaman Properti proyek, mengklik halaman properti Build, dan memilih kotak centang Izinkan Kode Tidak Aman.

Membuat SoftwareBitmap dari permukaan Direct3D

Untuk membuat objek SoftwareBitmap dari permukaan Direct3D, Anda harus menyertakan namespace Windows.Graphics.DirectX.Direct3D11 dalam proyek Anda.

using Windows.Graphics.DirectX.Direct3D11;

Panggil CreateCopyFromSurfaceAsync untuk membuat SoftwareBitmap baru dari permukaan. Seperti namanya, SoftwareBitmap baru memiliki salinan terpisah dari data gambar. Modifikasi pada SoftwareBitmap tidak akan berpengaruh pada permukaan Direct3D.

private async void CreateSoftwareBitmapFromSurface(IDirect3DSurface surface)
{
    softwareBitmap = await SoftwareBitmap.CreateCopyFromSurfaceAsync(surface);
}

Mengonversi SoftwareBitmap ke format piksel yang berbeda

Kelas SoftwareBitmap menyediakan metode statis, Convert, yang memungkinkan Anda untuk dengan mudah membuat SoftwareBitmap baru yang menggunakan format piksel dan mode alfa yang Anda tentukan dari SoftwareBitmap yang ada. Perhatikan bahwa bitmap yang baru dibuat memiliki salinan terpisah dari data gambar. Modifikasi pada bitmap baru tidak akan memengaruhi bitmap sumber.

SoftwareBitmap bitmapBGRA8 = SoftwareBitmap.Convert(softwareBitmap, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);

Transkode file gambar

Anda dapat mengodekan file gambar langsung dari BitmapDecoder ke BitmapEncoder. Buat IRandomAccessStream dari file yang akan ditranskodekan. Buat BitmapDecoder baru dari aliran input. Buat InMemoryRandomAccessStream baru untuk encoder untuk menulis dan memanggil BitmapEncoder.CreateForTranscodingAsync, melewati aliran dalam memori dan objek dekoder. Opsi enkode tidak didukung saat transcoding; sebagai gantinya, Anda harus menggunakan CreateAsync. Properti apa pun dalam file gambar input yang tidak Anda atur secara khusus pada encoder, akan ditulis ke file output yang tidak berubah. Panggil FlushAsync untuk menyebabkan encoder dikodekan ke aliran dalam memori. Terakhir, cari aliran file dan aliran dalam memori ke awal dan panggil CopyAsync untuk menulis aliran dalam memori ke aliran file.

private async void TranscodeImageFile(StorageFile imageFile)
{


    using (IRandomAccessStream fileStream = await imageFile.OpenAsync(FileAccessMode.ReadWrite))
    {
        BitmapDecoder decoder = await BitmapDecoder.CreateAsync(fileStream);

        var memStream = new Windows.Storage.Streams.InMemoryRandomAccessStream();
        BitmapEncoder encoder = await BitmapEncoder.CreateForTranscodingAsync(memStream, decoder);

        encoder.BitmapTransform.ScaledWidth = 320;
        encoder.BitmapTransform.ScaledHeight = 240;

        await encoder.FlushAsync();

        memStream.Seek(0);
        fileStream.Seek(0);
        fileStream.Size = 0;
        await RandomAccessStream.CopyAsync(memStream, fileStream);

        memStream.Dispose();
    }
}