本文說明如何使用
SoftwareBitmap 類別是一個多功能的 API,可從多個來源建立,包括影像檔案、可寫的位圖物件、Direct3D 曲面及程式碼。 SoftwareBitmap 讓你能輕鬆在不同像素格式和 alpha 模式間轉換,並允許對像素資料的低階存取。 此外,SoftwareBitmap 是 Windows 多項功能常見的介面,包括:
CapturedFrame 允許你取得相機捕捉到的影格,格式為 SoftwareBitmap。
VideoFrame 可以讓你獲得VideoFrame的SoftwareBitmap表示。
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();
}
}
當您建立
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;
}
你也可以使用 SoftwareBitmapSource 將 SoftwareBitmap 設定為 ImageBrush 的 ImageSource。
從可寫的位圖建立軟體位圖
你可以從現有的 WriteableBitmap 建立 SoftwareBitmap,方法是呼叫 SoftwareBitmap.CreateCopyFromBuffer,並提供 WriteableBitmap 的 PixelBuffer 屬性來設定像素資料。 第二個參數讓你可以指定新建立的 SoftwareBitmap 的像素格式。 你可以使用 WriteableBitmap 的 PixelWidth 和 PixelHeight 屬性來指定新影像的尺寸。
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();
}