建立、編輯和儲存點陣圖影像

本文說明如何使用 BitmapDecoder 以及 BitmapEncoder 載入與儲存影像檔案,以及如何使用 SoftwareBitmap 物件來表示點陣影像。

SoftwareBitmap 類別是一個多功能的 API,可從多個來源建立,包括影像檔案、可寫的位圖物件、Direct3D 曲面及程式碼。 SoftwareBitmap 讓你能輕鬆在不同像素格式和 alpha 模式間轉換,並允許對像素資料的低階存取。 此外,SoftwareBitmap 是 Windows 多項功能常見的介面,包括:

  • CapturedFrame 允許你取得相機捕捉到的影格,格式為 SoftwareBitmap

  • VideoFrame 可以讓你獲得VideoFrameSoftwareBitmap表示。

  • FaceDetector 讓你能在 SoftwareBitmap 中偵測臉部。

本文範例程式碼使用以下命名空間的 API。

using Windows.Storage;
using Windows.Storage.Pickers;
using Windows.Storage.Streams;
using Windows.Graphics.Imaging;
using Microsoft.UI.Xaml.Media.Imaging;
using System.Runtime.InteropServices.WindowsRuntime;

使用 BitmapDecoder 從影像檔建立 SoftwareBitmap

要從檔案建立 SoftwareBitmap,請取得包含影像資料的 StorageFile實例。 此範例使用 FileOpenPicker 來讓使用者選擇影像檔案。

private async Task<StorageFile?> PickInputFileAsync()
{
    var picker = new FileOpenPicker();

    // Initialize the picker with the window handle (required for WinUI 3).
    var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(App.MainWindow);
    WinRT.Interop.InitializeWithWindow.Initialize(picker, hwnd);

    picker.ViewMode = PickerViewMode.Thumbnail;
    picker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
    picker.FileTypeFilter.Add(".jpg");
    picker.FileTypeFilter.Add(".jpeg");
    picker.FileTypeFilter.Add(".png");
    picker.FileTypeFilter.Add(".bmp");

    return await picker.PickSingleFileAsync();
}

呼叫 StorageFile 物件的 OpenAsync 方法,取得包含影像資料的隨機存取串流。 呼叫靜態方法BitmapDecoder.CreateAsync 即可取得指定串流的 BitmapDecoder 類別的實例。 呼叫 GetSoftwareBitmapAsync 以取得包含該影像的 SoftwareBitmap 物件。

private async Task<SoftwareBitmap> CreateSoftwareBitmapFromFileAsync(StorageFile file)
{
    using IRandomAccessStream stream = await file.OpenAsync(FileAccessMode.Read);

    // Create a decoder from the image file.
    BitmapDecoder decoder = await BitmapDecoder.CreateAsync(stream);

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

    return softwareBitmap;
}

使用 BitmapEncoder 將 SoftwareBitmap 儲存到檔案中

要將 SoftwareBitmap 儲存到檔案中,請取得一個 StorageFile 實例,將映像檔儲存到該檔案中。 此範例使用 FileSavePicker 來讓使用者選擇輸出檔案。

private async Task<StorageFile?> PickOutputFileAsync()
{
    var picker = new FileSavePicker();

    // Initialize the picker with the window handle (required for WinUI 3).
    var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(App.MainWindow);
    WinRT.Interop.InitializeWithWindow.Initialize(picker, hwnd);

    picker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
    picker.SuggestedFileName = "output";
    picker.FileTypeChoices.Add("JPEG Image", new List<string> { ".jpg" });
    picker.FileTypeChoices.Add("PNG Image", new List<string> { ".png" });

    return await picker.PickSaveFileAsync();
}

呼叫 StorageFile 物件的 OpenAsync 方法,取得一個隨機存取串流,映像檔將被寫入。 呼叫靜態方法 BitmapEncoder.CreateAsync 即可取得指定串流的 BitmapEncoder 類別實例。 CreateAsync 的第一個參數是一個 GUID,代表應該用來編碼影像的編解碼器。 BitmapEncoder 類別會暴露包含編碼器支援的每個編解碼器 ID 的屬性,例如 JpegEncoderId

使用 SetSoftwareBitmap 方法來設定將要編碼的影像。 你可以設定 BitmapTransform 屬性的值,在影像編碼時對影像進行基本轉換。 IsThumbnailGenerated 屬性決定縮圖是否由編碼器產生。 請注意,並非所有檔案格式都支援縮圖,因此如果你使用此功能,應該會發現若縮圖不支援時會出現的不支援操作錯誤。

呼叫 FlushAsync 讓編碼器將影像資料寫入指定的檔案。

private async Task SaveSoftwareBitmapToFileAsync(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 (optional).
    encoder.BitmapTransform.ScaledWidth = (uint)softwareBitmap.PixelWidth;
    encoder.BitmapTransform.ScaledHeight = (uint)softwareBitmap.PixelHeight;
    encoder.BitmapTransform.InterpolationMode = BitmapInterpolationMode.Fant;
    encoder.IsThumbnailGenerated = true;

    try
    {
        await encoder.FlushAsync();
    }
    catch (Exception ex)
    {
        const int WINCODEC_ERR_UNSUPPORTEDOPERATION = unchecked((int)0x88982F81);
        switch (ex.HResult)
        {
            case WINCODEC_ERR_UNSUPPORTEDOPERATION:
                // If the encoder does not support thumbnail generation,
                // disable it and try again.
                encoder.IsThumbnailGenerated = false;
                break;

            default:
                throw;
        }
    }

    if (!encoder.IsThumbnailGenerated)
    {
        await encoder.FlushAsync();
    }
}

當您建立 BitmapEncoder 時,您可以透過建立一個新的 BitmapPropertySet 物件,並填充一個或多個代表編碼器設定的 BitmapTypedValue物件來指定額外的編碼選項。 有關支援的編碼器選項清單,請參見 BitmapEncoder 選項參考。

private async Task SaveWithEncodingOptionsAsync(SoftwareBitmap softwareBitmap, StorageFile outputFile)
{
    using IRandomAccessStream stream = await outputFile.OpenAsync(FileAccessMode.ReadWrite);

    // Create encoding options with a specific image quality.
    var propertySet = new BitmapPropertySet();
    var qualityValue = new BitmapTypedValue(0.9, Windows.Foundation.PropertyType.Single);
    propertySet.Add("ImageQuality", qualityValue);

    // Create the encoder with the encoding options.
    BitmapEncoder encoder = await BitmapEncoder.CreateAsync(
        BitmapEncoder.JpegEncoderId, stream, propertySet);

    encoder.SetSoftwareBitmap(softwareBitmap);
    await encoder.FlushAsync();
}

使用 SoftwareBitmap 搭配 XAML 影像控制

若要在 XAML 頁面中使用 Image 控制項顯示影像,請先在 XAML 頁面中定義 影像 控制項。

<Image x:Name="imageControl"
       Grid.Row="2"
       Stretch="Uniform"/>

目前, 影像 控制只支援使用 BGRA8 編碼且預先乘法或無 alpha 通道的影像。 在嘗試顯示影像前,先測試是否格式正確,若未正確,則使用 SoftwareBitmap 靜態 轉換 方法將影像轉換為支援格式。

建立一個新的 SoftwareBitmapSource 物件。 藉由呼叫 SetBitmapAsync 並傳入 SoftwareBitmap,設定來源物件的內容。 接著你可以將 Image 控制項的 Source 屬性設為新建立的 SoftwareBitmapSource

private async Task DisplaySoftwareBitmapAsync(SoftwareBitmap softwareBitmap)
{
    // SoftwareBitmap must be Bgra8 with premultiplied or no alpha
    // to display in a XAML Image control.
    if (softwareBitmap.BitmapPixelFormat != BitmapPixelFormat.Bgra8 ||
        softwareBitmap.BitmapAlphaMode == BitmapAlphaMode.Straight)
    {
        softwareBitmap = SoftwareBitmap.Convert(
            softwareBitmap, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
    }

    // In WinUI 3, SoftwareBitmapSource is in Microsoft.UI.Xaml.Media.Imaging.
    var source = new SoftwareBitmapSource();
    await source.SetBitmapAsync(softwareBitmap);

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

你也可以使用 SoftwareBitmapSourceSoftwareBitmap 設定為 ImageBrushImageSource

從可寫的位圖建立軟體位圖

你可以從現有的 WriteableBitmap 建立 SoftwareBitmap,方法是呼叫 SoftwareBitmap.CreateCopyFromBuffer,並提供 WriteableBitmapPixelBuffer 屬性來設定像素資料。 第二個參數讓你可以指定新建立的 SoftwareBitmap 的像素格式。 你可以使用 WriteableBitmapPixelWidthPixelHeight 屬性來指定新影像的尺寸。

private SoftwareBitmap ConvertWriteableBitmapToSoftwareBitmap(WriteableBitmap writeableBitmap)
{
    // Create a SoftwareBitmap from the WriteableBitmap's pixel buffer.
    SoftwareBitmap softwareBitmap = SoftwareBitmap.CreateCopyFromBuffer(
        writeableBitmap.PixelBuffer,
        BitmapPixelFormat.Bgra8,
        writeableBitmap.PixelWidth,
        writeableBitmap.PixelHeight);

    return softwareBitmap;
}

以程式化方式建立或編輯軟體位圖

到目前為止,這個主題主要討論的是如何處理影像檔案。 你也可以用程式化程式碼建立新的 SoftwareBitmap ,並用同樣的技術存取及修改 SoftwareBitmap 的像素資料。

使用 CopyFromBuffer 方法從位元組陣列填充 SoftwareBitmap ,並使用 CopyToBuffer 將像素資料複製到位元組陣列以便讀取或修改。 若要使用 AsBuffer 擴充方法將位元組陣列包裝為 IBuffer,請包含 System.Runtime.InteropServices.WindowsRuntime命名空間(此名稱已透過本文頂部的 SnippetNamespaces 語句中包含)。

建立一個新的 SoftwareBitmap ,包含你想要的像素格式和大小。 分配一個足夠大的位元組陣列以存放像素資料,填入所需值,然後呼叫 CopyFromBuffer 將資料寫入點陣圖。

private SoftwareBitmap CreateGradientBitmap(int width, int height)
{
    // Create a new SoftwareBitmap programmatically.
    var softwareBitmap = new SoftwareBitmap(
        BitmapPixelFormat.Bgra8, width, height, BitmapAlphaMode.Premultiplied);

    // Allocate a byte array for the pixel data.
    int bytesPerPixel = 4; // BGRA8
    byte[] pixelData = new byte[width * height * bytesPerPixel];

    // Fill the bitmap with a gradient pattern.
    for (int row = 0; row < height; row++)
    {
        for (int col = 0; col < width; col++)
        {
            int pixelIndex = (row * width + col) * bytesPerPixel;

            // Blue channel: gradient left to right.
            pixelData[pixelIndex + 0] = (byte)((double)col / width * 255);
            // Green channel: gradient top to bottom.
            pixelData[pixelIndex + 1] = (byte)((double)row / height * 255);
            // Red channel: inverse diagonal gradient.
            pixelData[pixelIndex + 2] = (byte)(255 - (((double)(col + row)
                / (width + height)) * 255));
            // Alpha channel: fully opaque.
            pixelData[pixelIndex + 3] = 255;
        }
    }

    // Copy the pixel data into the SoftwareBitmap.
    softwareBitmap.CopyFromBuffer(pixelData.AsBuffer());

    return softwareBitmap;
}

從 Direct3D 曲面建立 SoftwareBitmap

若要從 Direct3D 曲面建立 SoftwareBitmap 物件,必須包含 Windows。Graphics.DirectX.Direct3D11 命名空間。

using Windows.Graphics.DirectX.Direct3D11;

呼叫 CreateCopyFromSurfaceAsync ,從表面建立新的 SoftwareBitmap 。 顧名思義,新的 SoftwareBitmap 擁有獨立的影像資料副本。 對 SoftwareBitmap 的修改不會影響 Direct3D 表面。

private async Task<SoftwareBitmap> CreateBitmapFromSurfaceAsync(IDirect3DSurface surface)
{
    // Create a SoftwareBitmap from a Direct3D surface.
    SoftwareBitmap softwareBitmap =
        await SoftwareBitmap.CreateCopyFromSurfaceAsync(surface);

    return softwareBitmap;
}

將軟體位圖轉換成不同的像素格式

SoftwareBitmap 類別提供靜態方法「Convert」,讓你能輕鬆建立新的 SoftwareBitmap,使用你從現有 SoftwareBitmap 指定的像素格式和 alpha 模式。 請注意,新建立的點陣圖有獨立的影像資料副本。 對新位圖的修改不會影響來源位圖。

private SoftwareBitmap ConvertBitmapPixelFormat(SoftwareBitmap softwareBitmap)
{
    // Convert the pixel format and alpha mode of the SoftwareBitmap.
    SoftwareBitmap convertedBitmap = SoftwareBitmap.Convert(
        softwareBitmap,
        BitmapPixelFormat.Bgra8,
        BitmapAlphaMode.Premultiplied);

    return convertedBitmap;
}

轉碼影像檔案

你可以將影像檔直接從 BitmapDecoder 轉碼為 BitmapEncoder。 從要轉碼的檔案建立一個IRandomAccessStream。 從輸入串流建立新的 BitmapDecoder。 建立一個新的 InMemoryRandomAccessStream,讓編碼器寫入並呼叫 BitmapEncoder.CreateForTranscodingAsync,傳遞記憶體中的串流與解碼器物件。 轉碼時不支援編碼選項;你應該使用 CreateAsync。 輸入影像檔案中未在編碼器上特別設定的屬性,會被寫入輸出檔,且不改變。 呼叫 FlushAsync 讓編碼器編碼到記憶體中的串流。 最後,將檔案串流和記憶體串流都移至開頭,然後呼叫 CopyAsync,將記憶體串流寫出至檔案串流。

private async Task TranscodeImageFileAsync(StorageFile inputFile, StorageFile outputFile)
{
    using IRandomAccessStream inputStream =
        await inputFile.OpenAsync(FileAccessMode.Read);
    using IRandomAccessStream outputStream =
        await outputFile.OpenAsync(FileAccessMode.ReadWrite);

    // Create a decoder for the input file.
    BitmapDecoder decoder = await BitmapDecoder.CreateAsync(inputStream);

    // Create an encoder for transcoding to the output file.
    BitmapEncoder encoder =
        await BitmapEncoder.CreateForTranscodingAsync(outputStream, decoder);

    // Optionally apply transforms during transcoding.
    encoder.BitmapTransform.ScaledWidth = decoder.PixelWidth / 2;
    encoder.BitmapTransform.ScaledHeight = decoder.PixelHeight / 2;
    encoder.BitmapTransform.InterpolationMode = BitmapInterpolationMode.Fant;

    await encoder.FlushAsync();
}