建立、編輯和儲存點陣圖影像
本文介紹如何使用 BitmapDecoder 和 BitmapEncoder 載入和儲存影像檔,以及如何使用 SoftwareBitmap 物件來表示點陣圖影像。
SoftwareBitmap 類別是一種多功能 API,可從多個來源建立,包括影像檔、WriteableBitmap 物件、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 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;
您也可以使用 SoftwareBitmapSource 將 SoftwareBitmap 設定為 ImageBrush 的 ImageSource。
從 WriteableBitmap 建立 SoftwareBitmap
您可以透過呼叫 SoftwareBitmap.CreateCopyFromBuffer 並提供 WriteableBitmap 的 PixelBuffer 屬性來設定像素資料,從現有 WriteableBitmap 建立 SoftwareBitmap。 第二個自變數可讓您要求新建立 WriteableBitmap 的像素格式。 您可以使用 WriteableBitmap 的 PixelWidth 和 PixelHeight 屬性來指定新影像的尺寸。
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();
}
}
相關主題