🌟
BitmapからBitmapSourceへ変換するベストプラクティス
はじめに
WPFで画像処理関連の開発をしているとBitmapとBitmapSourceの相互変換が頻繁に発生します。
ここではBitmapからBitmapSourceへの変換のベストプラクティスを整理しました。
ベストプラクティス
BitmapからBitmapDataを作って、直接メモリー内容を流し込むのがベストです。
var bitmap = (Bitmap)Image.FromFile("foo.jpg");
var bitmapData = bitmap.LockBits(
new Rectangle(0, 0, bitmap.Width, bitmap.Height),
ImageLockMode.ReadOnly,
bitmap.PixelFormat);
try
{
var bitmapSource = BitmapSource.Create(
bitmapData.Width,
bitmapData.Height,
bitmap.HorizontalResolution,
bitmap.VerticalResolution,
PixelFormats.Bgr24,
null,
bitmapData.Scan0,
bitmapData.Stride * bitmapData.Height,
bitmapData.Stride);
return bitmapSource;
}
finally
{
bitmap.UnlockBits(bitmapData);
}
UnlockBitsを忘れずに。
検証
実現にはいくつかの手段があるのですが、今回はつぎの4つで評価しました。
- BitmapDataを利用する(ベストプラクティス)
- StreamにJPEGを保存してBitmapSourceをロードする
- StreamにBMPを保存してBitmapSourceをロードする
- CreateBitmapSourceFromHBitmapを利用する
これらのベンチマーク結果が以下の通りです。
処理速度もメモリーの使用量も圧倒的にBitmapDataです。
2と3は、JPEGを利用するとメモリーの使用量を抑えられますが、JPEGは不可逆圧縮なので厳密な再現性はなく、BMPだと厳密だけどメモリーの使用量が増えるという対比になっています。
これまでCreateBitmapSourceFromHBitmapを利用することが多かったのですが、今後はBitmapData経由を利用しようと思います。CreateBitmapSourceFromHBitmapだと、解像度(DPI)が保存されない点でも注意が必要です。
おまけ
前述の検証だとPixelFormats.Bgr24しか対応していないので、二値のTIFFを読み取ったBitmapからの変換も追加対応してみた。
using var bitmap = (Bitmap)Image.FromStream(new MemoryStream(Data));
var bitmapData = bitmap.LockBits(
new Rectangle(0, 0, bitmap.Width, bitmap.Height),
ImageLockMode.ReadOnly, bitmap.PixelFormat);
try
{
var bitmapSource = BitmapSource.Create(
bitmapData.Width,
bitmapData.Height,
bitmap.HorizontalResolution,
bitmap.VerticalResolution,
bitmap.PixelFormat == PixelFormat.Format1bppIndexed
? PixelFormats.Indexed1
: PixelFormats.Bgr24,
bitmap.PixelFormat == PixelFormat.Format1bppIndexed
? new BitmapPalette(new List<System.Windows.Media.Color> { Colors.Black, Colors.White })
: null,
bitmapData.Scan0, bitmapData.Stride * bitmapData.Height, bitmapData.Stride);
return bitmapSource;
}
finally
{
bitmap.UnlockBits(bitmapData);
}
PixcelFormatすべてに対応するのは大変だなぁ。きれいにマップしきれるかも分からないし。
サンプルコード
SaveJpegStream
using var bitmapStream = new MemoryStream();
_bitmap.Save(bitmapStream, ImageFormat.Jpeg);
bitmapStream.Position = 0;
var bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.StreamSource = bitmapStream;
bitmapImage.EndInit();
bitmapImage.Freeze();
return bitmapImage;
SaveBmpStream
using var bitmapStream = new MemoryStream();
_bitmap.Save(bitmapStream, ImageFormat.Bmp);
bitmapStream.Position = 0;
var bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.StreamSource = bitmapStream;
bitmapImage.EndInit();
bitmapImage.Freeze();
return bitmapImage;
CreateBitmapSourceFromHBitmap
[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern bool DeleteObject(System.IntPtr hObject);
public BitmapSource CreateBitmapSourceFromHBitmap()
{
var handle = _bitmap.GetHbitmap();
try
{
var bitmapSource = Imaging.CreateBitmapSourceFromHBitmap(handle, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
bitmapSource.Freeze();
return bitmapSource;
}
finally { DeleteObject(handle); }
}
Discussion