ビットマップ画像の作成、編集、保存Create, edit, and save bitmap images

この記事では、BitmapDecoderBitmapEncoder を使って画像ファイルを読み込んだり保存したりする方法のほか、SoftwareBitmap オブジェクトを使ってビットマップ画像を表現する方法について説明します。This article explains how to load and save image files using BitmapDecoder and BitmapEncoder and how to use the SoftwareBitmap object to represent bitmap images.

SoftwareBitmap クラスは、さまざまなソースから作成できる多用途の API です。画像ファイルや WriteableBitmap オブジェクト、Direct3D サーフェスから作成できるほか、コードから作成することもできます。The SoftwareBitmap class is a versatile API that can be created from multiple sources including image files, WriteableBitmap objects, Direct3D surfaces, and code. SoftwareBitmap を使うと、異なるピクセル形式間やアルファ モード間の変換、ピクセル データへの低レベル アクセスを簡単に行うことができます。SoftwareBitmap allows you to easily convert between different pixel formats and alpha modes, and allows low-level access to pixel data. Windows のさまざまな機能のインターフェイスとしても、SoftwareBitmap は広く使われています。その例を以下に挙げます。Also, SoftwareBitmap is a common interface used by multiple features of Windows, including:

  • CapturedFrame としてカメラによってキャプチャされたフレームを取得することができます、 SoftwareBitmapします。CapturedFrame allows you to get frames captured by the camera as a SoftwareBitmap.

  • VideoFrame を取得することができます、 SoftwareBitmapの表現、 VideoFrameします。VideoFrame allows you to get a SoftwareBitmap representation of a VideoFrame.

  • FaceDetector で顔を検出することができます、 SoftwareBitmapします。FaceDetector allows you to detect faces in a SoftwareBitmap.

この記事のサンプル コードには、以下の名前空間の API が使われています。The sample code in this article uses APIs from the following namespaces.

using Windows.Storage;
using Windows.Storage.Pickers;
using Windows.Storage.Streams;
using Windows.Graphics.Imaging;
using Windows.UI.Xaml.Media.Imaging;

BitmapDecoder で画像ファイルから SoftwareBitmap を作成するCreate a SoftwareBitmap from an image file with BitmapDecoder

SoftwareBitmap をファイルから作成するには、画像データを含んだ StorageFile のインスタンスを取得します。To create a SoftwareBitmap from a file, get an instance of StorageFile containing the image data. この例では、FileOpenPicker を使って、画像ファイルをユーザーが選択できるようにしています。This example uses a FileOpenPicker to allow the user to select an image file.

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 メソッドを呼び出して、画像データを含んだランダム アクセス ストリームを取得します。Call the OpenAsync method of the StorageFile object to get a random access stream containing the image data. 静的メソッド BitmapDecoder.CreateAsync を呼び出して、指定したストリームの BitmapDecoder クラスのインスタンスを取得します。Call the static method BitmapDecoder.CreateAsync to get an instance of the BitmapDecoder class for the specified stream. GetSoftwareBitmapAsync を呼び出して、画像が格納されている SoftwareBitmap オブジェクトを取得します。Call GetSoftwareBitmapAsync to get a SoftwareBitmap object containing the image.

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 をファイルに保存するSave a SoftwareBitmap to a file with BitmapEncoder

SoftwareBitmap をファイルに保存するには、画像の保存先となる StorageFile のインスタンスを取得します。To save a SoftwareBitmap to a file, get an instance of StorageFile to which the image will be saved. この例では、FileSavePicker を使って、ユーザーが出力ファイルを選択できるピッカーを表示しています。This example uses a FileSavePicker to allow the user to select an output file.

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 メソッドを呼び出して、画像の書き込み先となるランダム アクセス ストリームを取得します。Call the OpenAsync method of the StorageFile object to get a random access stream to which the image will be written. 静的メソッド BitmapEncoder.CreateAsync を呼び出して、指定したストリームの BitmapEncoder クラスのインスタンスを取得します。Call the static method BitmapEncoder.CreateAsync to get an instance of the BitmapEncoder class for the specified stream. CreateAsync の第 1 パラメーターは、画像のエンコードに使うコーデックの GUID です。The first parameter to CreateAsync is a GUID representing the codec that should be used to encode the image. エンコーダーがサポートしている各コーデックについて、この ID を保持するプロパティが、BitmapEncoder クラスによって公開されています (JpegEncoderId など)。BitmapEncoder class exposes a property containing the ID for each codec supported by the encoder, such as JpegEncoderId.

エンコードの対象となる画像は、SetSoftwareBitmap メソッドを使って設定します。Use the SetSoftwareBitmap method to set the image that will be encoded. BitmapTransform プロパティの値を設定することで、画像のエンコード中に基本的な変換を適用することができます。You can set values of the BitmapTransform property to apply basic transforms to the image while it is being encoded. エンコーダーで縮小表示が生成されるかどうかは、IsThumbnailGenerated プロパティによって決まります。The IsThumbnailGenerated property determines whether a thumbnail is generated by the encoder. ファイル形式によっては縮小表示がサポートされない場合があるので注意してください。この機能を使う場合、縮小表示がサポートされない場合にスローされるエラー (サポート外操作エラー) をキャッチする必要があります。Note that not all file formats support thumbnails, so if you use this feature, you should catch the unsupported operation error that will be thrown if thumbnails are not supported.

FlushAsync を呼び出すと、指定されたファイルへの画像データの書き込みをエンコーダーが開始します。Call FlushAsync to cause the encoder to write the image data to the specified file.

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 オブジェクトを渡すことによって指定できます。You can specify additional encoding options when you create the BitmapEncoder by creating a new BitmapPropertySet object and populating it with one or more BitmapTypedValue objects representing the encoder settings. サポートされているエンコーダー オプションの一覧については、「BitmapEncoder オプション リファレンス」をご覧ください。For a list of supported encoder options, see BitmapEncoder options reference.

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 コントロールを使うUse SoftwareBitmap with a XAML Image control

Image コントロールを使って XAML ページ内に画像を表示するには、まず XAML ページで Image コントロールを定義します。To display an image within a XAML page using the Image control, first define an Image control in your XAML page.

<Image x:Name="imageControl"/>

現時点では、Image コントロールは、BGRA8 エンコードを使用し、プリマルチプライ処理済みまたはアルファ チャネルなしの画像のみをサポートします。Currently, the Image control only supports images that use BGRA8 encoding and pre-multiplied or no alpha channel. 画像を表示する前に、画像の形式が正しいことをテストしてください。形式が不適切な場合は、SoftwareBitmapConvert メソッドを使用して、サポートされる形式に画像を変換してください。Before attempting to display an image, test to make sure it has the correct format, and if not, use the SoftwareBitmap static Convert method to convert the image to the supported format.

新しい SoftwareBitmapSource オブジェクトを作ります。Create a new SoftwareBitmapSource object. SetBitmapAsync を呼び出し、SoftwareBitmap で渡して、ソース オブジェクトの内容を設定します。Set the contents of the source object by calling SetBitmapAsync, passing in a SoftwareBitmap. その新しく作成した SoftwareBitmapSource を、Image コントロールの Source プロパティに設定します。Then you can set the Source property of the Image control to the newly created 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;

SoftwareBitmapSourceImageBrushImageSource として使用して SoftwareBitmap を設定することもできます。You can also use SoftwareBitmapSource to set a SoftwareBitmap as the ImageSource for an ImageBrush.

WriteableBitmap から SoftwareBitmap を作成するCreate a SoftwareBitmap from a WriteableBitmap

SoftwareBitmap.CreateCopyFromBuffer を呼び出して、WriteableBitmapPixelBuffer プロパティを指定することで、既存の WriteableBitmap から SoftwareBitmap を作成し、ピクセル データを設定することができます。You can create a SoftwareBitmap from an existing WriteableBitmap by calling SoftwareBitmap.CreateCopyFromBuffer and supplying the PixelBuffer property of the WriteableBitmap to set the pixel data. 新しく作成する WriteableBitmap のピクセル形式は第 2 引数で指定できます。The second argument allows you to request a pixel format for the newly created WriteableBitmap. 新しい画像のサイズは、WriteableBitmapPixelWidth プロパティと PixelHeight プロパティを使って指定してください。You can use the PixelWidth and PixelHeight properties of the WriteableBitmap to specify the dimensions of the new image.

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

SoftwareBitmap をプログラムから作成または編集するCreate or edit a SoftwareBitmap programmatically

ここまでは、画像ファイルを使った方法を紹介してきました。So far this topic has addressed working with image files. 新しい SoftwareBitmap をプログラム コードから作成し、同じ手法を用いて SoftwareBitmap のピクセル データにアクセスし、変更を加えることもできます。You can also create a new SoftwareBitmap programatically in code and use the same technique to access and modify the SoftwareBitmap's pixel data.

SoftwareBitmap では、ピクセル データを含んだ RAW バッファーが、COM 相互運用機能を使って公開されます。SoftwareBitmap uses COM interop to expose the raw buffer containing the pixel data.

COM 相互運用機能を使うには、System.Runtime.InteropServices 名前空間の参照をプロジェクトに追加する必要があります。To use COM interop, you must include a reference to the System.Runtime.InteropServices namespace in your project.

using System.Runtime.InteropServices;

COM インターフェイス IMemoryBufferByteAccess を初期化するには、対象の名前空間に以下のコードを追加します。Initialize the IMemoryBufferByteAccess COM interface by adding the following code within your namespace.

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

必要なピクセル形式とサイズを指定して新しい SoftwareBitmap を作成します。Create a new SoftwareBitmap with pixel format and size you want. 既にある SoftwareBitmap のピクセル データを編集する必要がある場合は、その SoftwareBitmap を使ってもかまいません。Or, use an existing SoftwareBitmap for which you want to edit the pixel data. SoftwareBitmap.LockBuffer を呼び出して、ピクセル データ バッファーを表す BitmapBuffer クラスのインスタンスを取得します。Call SoftwareBitmap.LockBuffer to obtain an instance of the BitmapBuffer class representing the pixel data buffer. BitmapBuffer を COM インターフェイス IMemoryBufferByteAccess にキャストしたうえで IMemoryBufferByteAccess.GetBuffer を呼び出し、バイト配列にデータを設定します。Cast the BitmapBuffer to the IMemoryBufferByteAccess COM interface and then call IMemoryBufferByteAccess.GetBuffer to populate a byte array with data. ピクセルごとにバッファーのオフセットを計算しやすいよう、BitmapBuffer.GetPlaneDescription メソッドを使って BitmapPlaneDescription オブジェクトを取得します。Use the BitmapBuffer.GetPlaneDescription method to get a BitmapPlaneDescription object that will help you calculate the offset into the buffer for each pixel.

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 キーワードを使って宣言する必要があります。Because this method accesses the raw buffer underlying the Windows Runtime types, it must be declared using the unsafe keyword. また、Microsoft Visual Studio でアンセーフ コードのコンパイルを許可するようにプロジェクトを構成する必要があります。プロジェクトの [プロパティ] ページを開き、 [ビルド] プロパティ ページをクリックして、 [アンセーフ コードの許可] チェック ボックスをオンにしてください。You must also configure your project in Microsoft Visual Studio to allow the compilation of unsafe code by opening the project's Properties page, clicking the Build property page, and selecting the Allow Unsafe Code checkbox.

Direct3D サーフェスから SoftwareBitmap を作成するCreate a SoftwareBitmap from a Direct3D surface

Direct3D サーフェスから SoftwareBitmap オブジェクトを作成するには、プロジェクトで Windows.Graphics.DirectX.Direct3D11 名前空間を追加する必要があります。To create a SoftwareBitmap object from a Direct3D surface, you must include the Windows.Graphics.DirectX.Direct3D11 namespace in your project.

using Windows.Graphics.DirectX.Direct3D11;

サーフェスから新しい SoftwareBitmap を作成するには、CreateCopyFromSurfaceAsync を呼び出します。Call CreateCopyFromSurfaceAsync to create a new SoftwareBitmap from the surface. この名前を見るとわかるように、新しい SoftwareBitmap には、画像データのコピーが別に存在します。As the name indicates, the new SoftwareBitmap has a separate copy of the image data. SoftwareBitmap に変更を加えても、Direct3D サーフェスには一切影響しません。Modifications to the SoftwareBitmap will not have any effect on the Direct3D surface.

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

SoftwareBitmap を異なるピクセル形式に変換するConvert a SoftwareBitmap to a different pixel format

SoftwareBitmap クラスの静的メソッド Convert を使うと、既にある SoftwareBitmap から、指定したピクセル形式とアルファ モードを使った新しい SoftwareBitmap を簡単に作成することができます。The SoftwareBitmap class provides the static method, Convert, that allows you to easily create a new SoftwareBitmap that uses the pixel format and alpha mode you specify from an existing SoftwareBitmap. 新しく作成されたビットマップには、画像データのコピーが別に存在します。Note that the newly created bitmap has a separate copy of the image data. 新しいビットマップに変更を加えても、元のビットマップには一切影響しません。Modifications to the new bitmap will not affect the source bitmap.

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

画像ファイルのトランスコードTranscode an image file

画像ファイルを BitmapDecoder から BitmapEncoder に直接トランスコードすることができます。You can transcode an image file directly from a BitmapDecoder to a BitmapEncoder. トランスコードの対象となるファイルから IRandomAccessStream を作成します。Create a IRandomAccessStream from the file to be transcoded. 入力ストリームから新しい BitmapDecoder を作成します。Create a new BitmapDecoder from the input stream. エンコーダーの書き込み先となる新しい InMemoryRandomAccessStream を作成し、BitmapEncoder.CreateForTranscodingAsync を呼び出します。このとき、引数にインメモリ ストリームとデコーダー オブジェクトを渡します。Create a new InMemoryRandomAccessStream for the encoder to write to and call BitmapEncoder.CreateForTranscodingAsync, passing in the in-memory stream and the decoder object. トランスコードではエンコード オプションはサポートされません。オプションを指定する場合は、代わりに CreateAsync を使う必要があります。Encode options are not supported when transcoding; instead you should use CreateAsync. 入力画像ファイルのプロパティのうち、エンコーダーに対して明示的に指定しなかったプロパティはすべて、元のまま出力ファイルに書き込まれます。Any properties in the input image file that you do not specifically set on the encoder, will be written to the output file unchanged. FlushAsync を呼び出すと、インメモリ ストリームへのエンコードをエンコーダーが開始します。Call FlushAsync to cause the encoder to encode to the in-memory stream. 最後に、ファイル ストリームとインメモリ ストリームを先頭までシークし、CopyAsync を呼び出してインメモリ ストリームをファイル ストリームに書き込みます。Finally, seek the file stream and the in-memory stream to the beginning and call CopyAsync to write the in-memory stream out to the file stream.

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