ビットマップ画像の作成、編集、保存

この記事では、BitmapDecoderBitmapEncoder を使って画像ファイルを読み込んだり保存したりする方法のほか、SoftwareBitmap オブジェクトを使ってビットマップ画像を表現する方法について説明します。

SoftwareBitmap クラスは、さまざまなソースから作成できる多用途の API です。画像ファイルや WriteableBitmap オブジェクト、Direct3D サーフェスから作成できるほか、コードから作成することもできます。 SoftwareBitmap を使うと、異なるピクセル形式間やアルファ モード間の変換、ピクセル データへの低レベル アクセスを簡単に行うことができます。 Windows のさまざまな機能のインターフェイスとしても、SoftwareBitmap は広く使われています。その例を以下に挙げます。

  • 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 の第 1 パラメーターは、画像のエンコードに使うコーデックの GUID です。 エンコーダーがサポートしている各コーデックについて、この ID を保持するプロパティが、BitmapEncoder クラスによって公開されています (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
);

SoftwareBitmap と XAML Image コントロールを使う

Image コントロールを使って XAML ページ内に画像を表示するには、まず XAML ページで Image コントロールを定義します。

<Image x:Name="imageControl"/>

現時点では、Image コントロールは、BGRA8 エンコードを使用し、プリマルチプライ処理済みまたはアルファ チャネルなしの画像のみをサポートします。 画像を表示する前に、画像の形式が正しいことをテストしてください。形式が不適切な場合は、SoftwareBitmapConvert メソッドを使用して、サポートされる形式に画像を変換してください。

新しい SoftwareBitmapSource オブジェクトを作ります。 SetBitmapAsync を呼び出し、SoftwareBitmap で渡して、ソース オブジェクトの内容を設定します。 その新しく作成した SoftwareBitmapSource を、Image コントロールの Source プロパティに設定します。

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;

SoftwareBitmapSourceImageBrushImageSource として使用して SoftwareBitmap を設定することもできます。

WriteableBitmap から SoftwareBitmap を作成する

SoftwareBitmap.CreateCopyFromBuffer を呼び出して、WriteableBitmapPixelBuffer プロパティを指定することで、既存の WriteableBitmap から SoftwareBitmap を作成し、ピクセル データを設定することができます。 新しく作成する WriteableBitmap のピクセル形式は第 2 引数で指定できます。 新しい画像のサイズは、WriteableBitmapPixelWidth プロパティと PixelHeight プロパティを使って指定してください。

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

SoftwareBitmap をプログラムから作成または編集する

ここまでは、画像ファイルを使った方法を紹介してきました。 コード内でプログラムによって新しい SoftwareBitmap を作成し、同じ手法を使用して、 SoftwareBitmap のピクセル データにアクセスして変更することもできます。

SoftwareBitmap では、ピクセル データを含んだ RAW バッファーが、COM 相互運用機能を使って公開されます。

COM 相互運用機能を使うには、System.Runtime.InteropServices 名前空間の参照をプロジェクトに追加する必要があります。

using System.Runtime.InteropServices;

COM インターフェイス IMemoryBufferByteAccess を初期化するには、対象の名前空間に以下のコードを追加します。

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

必要なピクセル形式とサイズを指定して新しい SoftwareBitmap を作成します。 既にある SoftwareBitmap のピクセル データを編集する必要がある場合は、その SoftwareBitmap を使ってもかまいません。 SoftwareBitmap.LockBuffer を呼び出して、ピクセル データ バッファーを表す BitmapBuffer クラスのインスタンスを取得します。 BitmapBuffer を COM インターフェイス IMemoryBufferByteAccess にキャストしたうえで 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 ランタイム型よりも低いレベルの RAW バッファーにアクセスするため、unsafe キーワードを使って宣言する必要があります。 また、Microsoft Visual Studio でアンセーフ コードのコンパイルを許可するようにプロジェクトを構成する必要があります。プロジェクトの [プロパティ] ページを開き、[ビルド] プロパティ ページをクリックして、[アンセーフ コードの許可] チェック ボックスをオンにしてください。

Direct3D サーフェスから SoftwareBitmap を作成する

Direct3D サーフェスから SoftwareBitmap オブジェクトを作成するには、プロジェクトで Windows.Graphics.DirectX.Direct3D11 名前空間を追加する必要があります。

using Windows.Graphics.DirectX.Direct3D11;

サーフェスから新しい SoftwareBitmap を作成するには、CreateCopyFromSurfaceAsync を呼び出します。 この名前を見るとわかるように、新しい SoftwareBitmap には、画像データのコピーが別に存在します。 SoftwareBitmap に変更を加えても、Direct3D サーフェスには一切影響しません。

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

SoftwareBitmap を異なるピクセル形式に変換する

SoftwareBitmap クラスの静的メソッド Convert を使うと、既にある SoftwareBitmap から、指定したピクセル形式とアルファ モードを使った新しい SoftwareBitmap を簡単に作成することができます。 新しく作成されたビットマップには、画像データのコピーが別に存在します。 新しいビットマップに変更を加えても、元のビットマップには一切影響しません。

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