酢ろぐ!

カレーが嫌いなスマートフォンアプリプログラマのブログ。

WindowsストアアプリでIRandomAccessStream型のストリームからWriteableBitmapオブジェクトへ変換する

通常、CameraCaptureUIやFileOpenPickerを使って写真の撮影、画像の取得をおこなった場合にIRandomAccessStream型、またはIRandomAccessStreamWithContentType型のストリームから、BitmapImage型やWriteableBitmap型でストリームの読み込みをおこないます。

Metro スタイル アプリにおける画像処理ではBitmapImageオブジェクトは使いにくいため、積極的に紹介はしておりませんが、BitmapImageオブジェクトへの変換をおこなう場合には「WindowsストアアプリでIRandomAccessStream型のストリームからBitmapImageオブジェクトへ変換する - 酢ろぐ!」をご参照ください。

byte配列型からWriteableBitmapオブジェクトへ変換する

幅(Width)と高さ(Height)が分かっていれば、byte配列型からWriteableBitmapオブジェクトを作成することができます。WriteableBitmapオブジェクトを返すFromArrayメソッドを紹介します。

ピクセルデータであるWriteableBitmapオブジェクトのPixelBufferプロパティにbyte配列型のデータを書き込んでいきます。IBuffer型であるPixelBufferプロパティに対して書き込みをおこなうには、一度System.IO.Streamオブジェクトへ変換します。

このストリームはWriteableBitampのピクセルデータそのものですので、ストリーム内の位置を先頭に戻し、パラメータで渡されたbyte配列で上書きします。

public static WriteableBitmap FromArray(int width, int height, byte[] array)
{
    var bitmap = new WriteableBitmap(width, height);
    using (var pixelStream = bitmap.PixelBuffer.AsStream())
    {
        pixelStream.Seek(0, SeekOrigin.Begin);
        pixelStream.Write(array, 0, array.Length);
    }
    return bitmap;
}

IRandomAccessStream型のストリームからWriteableBitmapオブジェクトへ変換する

IRandomAccessStream型のストリームから直接WriteableBitmapオブジェクトを生成することはできません。WriteableBitmapオブジェクトを生成するには幅と高さがあらかじめ知っておく必要があるからです。

BitmapDecoderクラスを使用して、データソースであるJPEGやPNGファイルをBitmapへデコードします。一度デコードしてしまえば、高さと幅、そしてピクセルデータであるbyte配列を得ることができますので、先ほど紹介したbyte配列型からWriteableBitmapオブジェクトへ変換するFromArrayメソッドを使ってWriteableBitmapオブジェクトを生成することができます。

毎回長いコードを書くのは冗長ですので、拡張メソッドを作ってみましょう。

using System;
using System.IO;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
using Softbuild.Media.Effects;
using Windows.Graphics.Imaging;
using Windows.Storage;
using Windows.Storage.Streams;
using Windows.UI.Xaml.Media.Imaging;

namespace Softbuild.Media
{
    static public class WriteableBitmapExtensions
    {
        /// <summary>
        /// IRandomAccessStreamからWriteableBitmapを生成する
        /// </summary>
        /// <param name="stream">ランダムアクセスストリーム</param>
        /// <returns>WriteableBitmapオブジェクト</returns>
        public static async Task<WriteableBitmap> FromStreamAsync(IRandomAccessStream stream)
        {
            // ストリームからピクセルデータを読み込む
            var decoder = await BitmapDecoder.CreateAsync(stream);
            var transform = new BitmapTransform();
            var pixelData = await decoder.GetPixelDataAsync(decoder.BitmapPixelFormat, decoder.BitmapAlphaMode,
                transform, ExifOrientationMode.RespectExifOrientation, ColorManagementMode.ColorManageToSRgb);
            var pixels = pixelData.DetachPixelData();

            // ピクセルデータからWriteableBitmapオブジェクトを生成する
            return WriteableBitmapExtensions.FromArray((int)decoder.OrientedPixelWidth, (int)decoder.OrientedPixelHeight, pixels);
        }

        /// <summary>
        /// バイト配列からWriteableBitmapを生成する
        /// </summary>
        /// <param name="width"></param>
        /// <param name="height">高さ</param>
        /// <param name="array">ピクセルデータ</param>
        /// <returns>WriteableBitmapオブジェクト</returns>
        public static WriteableBitmap FromArray(int width, int height, byte[] array)
        {
            // 出力用のWriteableBitmapオブジェクトを生成する
            var bitmap = new WriteableBitmap(width, height);
            // WriteableBitmapへバイト配列のピクセルデータをコピーする
            using (var pixelStream = bitmap.PixelBuffer.AsStream())
            {
                pixelStream.Seek(0, SeekOrigin.Begin);
                pixelStream.Write(array, 0, array.Length);
            }
            return bitmap;
        }
    }
}

今後の検討課題

Windows Phoneアプリケーション開発では使えたBitmapImageがコンストラクタ引数に取ってWriteableBitmapオブジェクトをできないのが面倒すぎます。

byte配列かIRandomAccessStream型のストリームであれば、それらからWriteableBitmapを生成する方法は前述した通りなのですが、BitmapImageオブジェクトをbyte配列やストリームに変換するルートが今のところ存在しません。というかBitmapImageの場合ピクセルデータに触ることができません。結果的にBitmapImageからWriteableBitmapへの変換ができない状態となっています。

追記(2013/11/7)

Softbuild.Mediaで、WriteableBitmapと合わせて使うことのできる拡張メソッド集を作っています。こちらをお使いください。

関連記事

WindowsランタイムAPI(Windows Runtime API, WinRT API)を使ってアプリ開発する際に逆引きとしてお使いください。