UWP:SoftwareBitmap を縮小する

執筆日時:

はてなブログにデカい写真を貼るときのフローがめんどくさい。どれぐらいめんどくさいかというと、ブログを書くペースが月1回に落ちるぐらいめんどくさい。

blog.daruyanagi.jp

――というわけで、年始は画像を縮小できるアプリを開発していた。要件は以下のとおり。

UWP アプリの開発は1年以上ぶりで、右も左もわからぬ。Microsoft Docs をさまよった結果、内部での画像データは SoftwareBitmap あたりで持つのがよさげだったが、当初は縮小の方法もいまいちわからかった。

Win2D を使う

そういうときは、やっぱり StackOverFlow だよね。親切にも SoftwareBitmap の拡張メソッドにしてくれていたので、そのまま使うことにした。ちなみに、これを利用するには NuGet で Win2D パッケージを別途インストールする必要がある。

// https://stackoverflow.com/questions/41251716/how-to-resize-a-softwarebitmap

public static SoftwareBitmap Resize(this SoftwareBitmap softwareBitmap, float newWidth, float newHeight) { using (var resourceCreator = CanvasDevice.GetSharedDevice()) using (var canvasBitmap = CanvasBitmap.CreateFromSoftwareBitmap(resourceCreator, softwareBitmap)) using (var canvasRenderTarget = new CanvasRenderTarget(resourceCreator, newWidth, newHeight, canvasBitmap.Dpi)) using (var drawingSession = canvasRenderTarget.CreateDrawingSession()) using (var scaleEffect = new ScaleEffect()) { scaleEffect.Source = canvasBitmap; scaleEffect.Scale = new System.Numerics.Vector2(newWidth / softwareBitmap.PixelWidth, newHeight / softwareBitmap.PixelHeight); drawingSession.DrawImage(scaleEffect); drawingSession.Flush(); return SoftwareBitmap.CreateCopyFromBuffer(canvasRenderTarget.GetPixelBytes().AsBuffer(), BitmapPixelFormat.Bgra8, (int)newWidth, (int)newHeight, BitmapAlphaMode.Premultiplied); } }

おおむね快適に動作するが、特定のサイズ(縦×横)の組み合わせで画像が乱れる問題が見つかったのが問題(割ったり、int でキャストしているところでなんかおかしいのかなぁ)。頑張って直してみようとしたが、自分には無理だった。

BitmapEncoder を用いる(WrietableBitmap 経由)

というわけで、基本に戻ることにした。BitmapEncoder を生成し、SoftwareBitmap を割り当てて、Transform してもらう。http://c5d5e5.asablo.jp/blog/2017/08/08/8642588 で提示されていたサンプルコードをベースに、SoftwareBitmap の拡張メソッドにしてみた。

// http://c5d5e5.asablo.jp/blog/2017/08/08/8642588

public static async Task<SoftwareBitmap> ResizeAsync(this SoftwareBitmap source, float newWidth, float newHeight) { if (source == null) return null;

using (var memory = new InMemoryRandomAccessStream()) { // BitmapEncoder を用いメモリ上で source をリサイズ var id = BitmapEncoder.PngEncoderId; BitmapEncoder encoder = await BitmapEncoder.CreateAsync(id, memory); encoder.BitmapTransform.ScaledHeight = (uint)newHeight; encoder.BitmapTransform.ScaledWidth = (uint)newWidth; encoder.BitmapTransform.InterpolationMode = BitmapInterpolationMode.Fant; encoder.SetSoftwareBitmap(source); await encoder.FlushAsync();

// リサイズしたメモリを WriteableBitmap に複写 var writeableBitmap = new WriteableBitmap((int)newWidth, (int)newHeight); await writeableBitmap.SetSourceAsync(memory);

// dest(XAML の Image コントロール互換)を作成し、WriteableBitmap から複写 var dest = new SoftwareBitmap(BitmapPixelFormat.Bgra8, (int)newWidth, (int)newHeight, BitmapAlphaMode.Premultiplied); dest.CopyFromBuffer(writeableBitmap.PixelBuffer);

return dest; } }

Encoder から SoftwareBitmap を得るのに WrietableBitmap を経由しているのがあまりしっくりこないけど、こっちは問題なく動作した。

BitmapEncoder + BitmapDecorder

他人の力ばかり借りるのも何なので、自分でも考えてみたのはこちら。基本的にはさっきのやり方と変わらないけれど、WrietableBitmap ではなく、BitmapDecorder を利用している。

public static async Task<SoftwareBitmap> ResizeAsync2(this SoftwareBitmap source, float newWidth, float newHeight)
{
if (source == null) return null;

using (var memory = new InMemoryRandomAccessStream()) { var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, memory); encoder.SetSoftwareBitmap(source); await encoder.FlushAsync();

var decoder = await BitmapDecoder.CreateAsync(memory);

var transform = new BitmapTransform() { ScaledHeight = (uint)newHeight, ScaledWidth = (uint)newWidth, InterpolationMode = BitmapInterpolationMode.Fant, };

var dest = await decoder.GetSoftwareBitmapAsync( BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied, transform, ExifOrientationMode.IgnoreExifOrientation, ColorManagementMode.DoNotColorManage );

return dest; } }

これも問題なく動いたが、どうも WrietableBitmap バージョンと比べると動作が遅い。Encoder/Decorder の生成にかなりコストがかかるようだ。となると、そもそも Encoder/Decorder は毎回生成せず、使いまわしたほうが良いのかもしれない(拡張メソッドにするのもあまりよくない、もしくは拡張メソッドに Encoder を渡すようにする)。

とりあえず、これで基本的な使い方はわかったような気がするので、画像の回転などは簡単に作れそう(Transform するだけ)。作るうちにペン対応なんかもやりだして、なかなか完成しないけれど、開発中のアプリはなかなかよく動いており、「ブログ、また書こうかな」っていう気がわいてきた。