UWP:SoftwareBitmap を縮小する
執筆日時:
はてなブログにデカい写真を貼るときのフローがめんどくさい。どれぐらいめんどくさいかというと、ブログを書くペースが月1回に落ちるぐらいめんどくさい。
――というわけで、年始は画像を縮小できるアプリを開発していた。要件は以下のとおり。
- [共有]コマンドに対応(必須)
- シンプル。ブログへのアップロードに使いそうな機能しか追加しない
- 画像の縮小(できた)
- 画像の回転(すぐできそうだけどやってない)
- 顔認識して隠す(進捗半分)
- 画像のクロップ(優先度低)
UWP アプリの開発は1年以上ぶりで、右も左もわからぬ。Microsoft Docs をさまよった結果、内部での画像データは SoftwareBitmap あたりで持つのがよさげだったが、当初は縮小の方法もいまいちわからかった。
Win2D を使う
そういうときは、やっぱり StackOverFlow だよね。親切にも SoftwareBitmap の拡張メソッドにしてくれていたので、そのまま使うことにした。ちなみに、これを利用するには NuGet で Win2D パッケージを別途インストールする必要がある。
// https://stackoverflow.com/questions/41251716/how-to-resize-a-softwarebitmappublic 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/8642588public 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 するだけ)。作るうちにペン対応なんかもやりだして、なかなか完成しないけれど、開発中のアプリはなかなかよく動いており、「ブログ、また書こうかな」っていう気がわいてきた。