共用方式為


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

本文介紹如何使用 BitmapDecoderBitmapEncoder 載入和儲存影像檔,以及如何使用 SoftwareBitmap 物件來表示點陣圖影像。

SoftwareBitmap 類別是一種多功能 API,可從多個來源建立,包括影像檔、WriteableBitmap 物件、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 Windows.UI.Xaml.Media.Imaging;

使用 BitmapDecoder 從影像檔建立 SoftwareBitmap

若要從檔案建立 SoftwareBitmap,請取得包含影像資料的 StorageFile 執行個體。 此範例會使用 FileOpenPicker 讓使用者選取影像檔。

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

呼叫 StorageFile 物件的 OpenAsync 方法以取得包含影像資料的隨機存取串流。 呼叫靜態方法 BitmapDecoder.CreateAsync 以取得指定串流的 BitmapDecoder 類別之執行個體。 呼叫 GetSoftwareBitmapAsync 以取得包含影像的 SoftwareBitmap 物件。

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

使用 BitmapEncoder 將 SoftwareBitmap 儲存至檔案

若要將 SoftwareBitmap 儲存至檔案,請取得將儲存影像的 StorageFile 執行個體。 此範例會使用 FileSavePicker 讓使用者選取輸出檔。

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

呼叫 StorageFile 物件的 OpenAsync 方法以取得將寫入影像的隨機存取串流。 呼叫靜態方法 BitmapEncoder.CreateAsync 以取得指定串流的 BitmapEncoder 類別之執行個體。 CreateAsync 的第一個參數是一個 GUID,表示應用於對影像進行編碼的轉碼器。 BitmapEncoder 類別會公開一個屬性,其中包含編碼器支援的每個轉碼器的 ID,例如 JpegEncoderId

使用 SetSoftwareBitmap 方法來設定要編碼的影像。 您可以設定 BitmapTransform 屬性的值,以在影像進行編碼時,將基本轉換套用至影像。 IsThumbnailGenerated 屬性會決定編碼器是否產生縮圖。 請注意,並非所有檔案格式都支援縮圖,因此如果您使用此功能,您應該攔截不支援的作業錯誤,如果不支援縮圖,則會擲回。

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

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


    }
}

建立 BitmapEncoder 時,您可以指定其他編碼選項,方法是建立新的 BitmapPropertySet 物件,並使用一個或多個表示編碼器設定的 BitmapTypedValue 物件填入該物件。 如需支援的編碼器選項清單,請參閱 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
);

搭配 XAML 影像控制項使用 SoftwareBitmap

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

<Image x:Name="imageControl"/>

目前,Image 控制項僅支援使用 BGRA8 編碼和預先乘法或沒有 Alpha 色板的影像。 在嘗試顯示影像之前,請進行測試以確保其格式正確,如果不正確,請使用 SoftwareBitmap 靜態 Convert 方法將影像轉換為支援的格式。

建立新的 SoftwareBitmapSource 物件。 透過呼叫 SetBitmapAsync 並傳入 SoftwareBitmap 來設定來源物件的內容。 然後,您可以將 Image 控制項的 Source 屬性設定為新建立的 SoftwareBitmapSource

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;

您也可以使用 SoftwareBitmapSourceSoftwareBitmap 設定為 ImageBrushImageSource

從 WriteableBitmap 建立 SoftwareBitmap

您可以透過呼叫 SoftwareBitmap.CreateCopyFromBuffer 並提供 WriteableBitmapPixelBuffer 屬性來設定像素資料,從現有 WriteableBitmap 建立 SoftwareBitmap。 第二個自變數可讓您要求新建立 WriteableBitmap 的像素格式。 您可以使用 WriteableBitmapPixelWidthPixelHeight 屬性來指定新影像的尺寸。

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

以程式設計方式建立或編輯 SoftwareBitmap

到目前為止,本主題已解決使用影像檔的問題。 您也可以在程序代碼中以程序設計方式建立新的 SoftwareBitmap ,並使用相同的技術來存取和修改 SoftwareBitmap的像素數據。

SoftwareBitmap 會使用 COM Interop 來公開包含像素資料的原始緩衝區。

若要使用 COM Interop,您必須在專案中包含 System.Runtime.InteropServices 命名空間的參考。

using System.Runtime.InteropServices;

在命名空間中新增下列程式碼,以初始化 IMemoryBufferByteAccess COM 介面。

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

使用您想要的像素格式和大小建立新的 SoftwareBitmap。 或者,使用您想要編輯像素資料的現有 SoftwareBitmap。 呼叫 SoftwareBitmap.LockBuffer 取得表示像素資料緩衝區的 BitmapBuffer 類別的執行個體。 將 BitmapBuffer 轉換為 IMemoryBufferByteAccess COM 介面,然後呼叫 IMemoryBufferByteAccess.GetBuffer 以使用資料填充位元組陣列。 使用 BitmapBuffer.GetPlaneDescription 方法取得 BitmapPlaneDescription 物件,該物件將協助您計算每個像素到緩衝區的位移。

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

由於此方法存取 Windows 執行階段類型底層的原始緩衝區,因此必須使用 unsafe 關鍵字對其進行宣告。 您也必須在 Microsoft Visual Studio 中設定專案以允許編譯不安全程式碼,方法是開啟專案的屬性頁面,按一下建置屬性頁面,然後選取允許不安全程式碼核取方塊。

從 Direct3D 介面建立 SoftwareBitmap

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

using Windows.Graphics.DirectX.Direct3D11;

呼叫 CreateCopyFromSurfaceAsync 從表面建立新的 SoftwareBitmap。 如名稱所示,新的 SoftwareBitmap 有個別的影像資料副本。 對 SoftwareBitmap 的修改不會對 Direct3D 表面產生任何影響。

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

將 SoftwareBitmap 轉換為其他像素格式

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

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

轉碼影像檔

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

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