概要
「ビットマップ形式」……いわゆるラスターグラフィックスは、最も典型的な画像の表現方法でしょう。
それをプログラム上で表現するため、C#では System.Drawing.Bitmap
など様々な型が用意されています。
……そう、 様々な型 です。暗黙の型変換でよしなにしてくれない場合、メソッドを用いた 変換が必要 なのです!
というわけで、変換方法についてまとめてみました。元ネタは次のQiita記事です。
WPFの画像相互コンバーター。System.Drawing.BitmapからSystem.Windows.Controls.Imageへの変換。
WPFの画像相互コンバーター。BitmapImageからBitmapSourceへの変換。
※以下の記事では、メソッドの戻り値と同じ型を使用する場合のみ var
キーワードを使用しています。
なぜならそうしないと、型の変化が分かりにくいからです。
継承関係について整理
まず、様々な型について、その継承関係を振り返ってみましょう。
ただし以下の表では、継承元の名前を「 S.D.Image
」などと略しています。
型名 | 継承元 | 説明 |
---|---|---|
System.Drawing.Image | WinForms用の画像データで、System.Drawingの参照が必要 | |
System.Drawing.Imaging.Metafile | S.D.Image | *.emfなどのベクターグラフィックス |
System.Drawing.Bitmap | S.D.Image | *.bmpなどのラスターグラフィックス |
System.Windows.Media.ImageSource | WPF用の画像データで、PresentationCoreの参照が必要 | |
System.Windows.Media.DrawingImage | ImageSource | ベクターグラフィックス |
System.Windows.Media.Imaging.BitmapSource | ImageSource | ラスターグラフィックス |
System.Windows.Media.Imaging.BitmapImage | BitmapSource | ファイルやリソースからの画像の読み込みを想定 |
System.Windows.Media.Imaging.WriteableBitmap | BitmapSource | プログラム中で動的に画素の読み書きができる |
System.Windows.Media.Imaging.RenderTargetBitmap | BitmapSource | 任意のVisualオブジェクトをレンダリングしてビットマップ化するために利用 |
System.Windows.Interop.InteropBitmap | BitmapSource | (※1) |
System.Windows.Media.Imaging.BitmapFrame | BitmapSource | エンコーダーやデコーダーで扱われる画像形式 |
System.Windows.Controls.Image | WPFにおけるコントロールで、PresentationFrameworkの参照が必要(※2) |
※1……例えばクリップボードからビットマップ形式の画像データを取り出した際、
WinFormsでは System.Drawing.Bitmap
で返るが、
WPFでは System.Windows.Interop.InteropBitmap
で返る。
これにどういう意味があるのかよく分かりませんので誰か教えてください><
※2……SourceプロパティにSystem.Windows.Media.ImageSourceを指定する
このことから、例えば BitmapImage
を System.Windows.Controls.Image
にするには
// BitmapImage型の画像
System.Windows.Media.Imaging.BitmapImage bitmapImage;
//
var image = new System.Windows.Controls.Image();
image.Source = bitmapImage;
で十分ですし、 BitmapImage
を BitmapSource
にするには
// BitmapImage型の画像
System.Windows.Media.Imaging.BitmapImage bitmapImage;
// BitmapImage→BitmapSource(型変換を明示する必要はない)
System.Windows.Media.Imaging.BitmapSource bitmapSource = bitmapImage;
// BitmapSource→BitmapImage(型変換を明示する必要がある)
System.Windows.Media.Imaging.BitmapImage bitmapImage2 = (System.Windows.Media.Imaging.BitmapImage)bitmapSource;
でOKです。なお、継承元が同じな BitmapImage
や InteropBitmap
は直接キャストすることができず、必ず BitmapSource
など上位の型に変換してからキャストすることが必要となります。
……上記考察から、 System.Drawing.Bitmap
と System.Windows.Media.Imaging.BitmapSource
との間の変換が重要 だと分かります。
BitmapとBitmapSourceとの相互変換
Bitmap→BitmapSource
一番無難なのは、 System.IO.MemoryStream
を経由することでしょう。
// 変換元の画像
System.Drawing.Bitmap bitmap;
// MemoryStreamを利用した変換処理
using (var ms = new System.IO.MemoryStream()) {
// MemoryStreamに書き出す
bitmap.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp);
// MemoryStreamをシーク
ms.Seek(0, System.IO.SeekOrigin.Begin);
// MemoryStreamからBitmapFrameを作成
// (BitmapFrameはBitmapSourceを継承しているのでそのまま渡せばOK)
System.Windows.Media.Imaging.BitmapSource bitmapSource =
System.Windows.Media.Imaging.BitmapFrame.Create(
ms,
System.Windows.Media.Imaging.BitmapCreateOptions.None,
System.Windows.Media.Imaging.BitmapCacheOption.OnLoad
);
}
また、 BitmapImage
は System.IO.Stream
をプロパティから受け取れますので、そちらから攻める手もあるでしょう。
// 変換元の画像
System.Drawing.Bitmap bitmap;
// MemoryStreamを利用した変換処理
using (var ms = new System.IO.MemoryStream()) {
bitmap.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp);
// MemoryStreamのポジションを設定?
ms.Position = 0;
// BitmapImageを初期化
var bitmapImage = new System.Windows.Media.Imaging.BitmapImage();
// MemoryStreamを書き込むために準備する
bitmapImage.BeginInit();
bitmapImage.CacheOption = System.Windows.Media.Imaging.BitmapCacheOption.OnLoad;
bitmapImage.CreateOptions = System.Windows.Media.Imaging.BitmapCreateOptions.None;
// MemoryStreamを書き込む
bitmapImage.StreamSource = ms;
//
bitmapImage.EndInit();
// ここでFreezeしておくといいらしい(参考資料参照)
bitmapImage.Freeze();
// BitmapImageはBitmapSourceを継承しているのでそのまま渡せばOK
System.Windows.Media.Imaging.BitmapSource bitmapSource = bitmapImage;
}
なお、Unmanagedなメソッドを使っていい場合は、次のようなコードになります。
別の方の記事になりますが、 MemoryStreamよりずっと高速 だそうです。
.NETで画像処理を試してみる OpenCVSharp編 第5回 - A certain engineer "COMPLEX"
[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern bool DeleteObject(System.IntPtr hObject);
(中略)
// 変換元の画像
System.Drawing.Bitmap bitmap;
// HBitmapに変換
var hBitmap = bitmap.GetHbitmap();
// HBitmapからBitmapSourceを作成
try {
var bitmapSource = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
hBitmap,
System.IntPtr.Zero,
System.Windows.Int32Rect.Empty,
System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions()
);
}
finally {
DeleteObject(hBitmap);
}
BitmapSource→Bitmap
色々手段がありますが、Bitmap→BitmapSourceよりこっちの方が面倒くさいように見えます。
// LockBitsとUnlockBitsを使う方法
// フォーマット決め打ちなので、他フォーマットだと別途変換が必要。
// 詳しくは参考資料における「ルーチェ's Homepage」または「nuits.jp blog」のコードを参照すること
// 元画像
System.Windows.Media.Imaging.BitmapSource bitmapSource;
// 処理
var bitmap = new System.Drawing.Bitmap(
bitmapSource.PixelWidth,
bitmapSource.PixelHeight,
System.Drawing.Imaging.PixelFormat.Format32bppPArgb
);
var bitmapData = bitmap.LockBits(
new System.Drawing.Rectangle(System.Drawing.Point.Empty, bitmap.Size),
System.Drawing.Imaging.ImageLockMode.WriteOnly,
System.Drawing.Imaging.PixelFormat.Format32bppPArgb
);
bitmapSource.CopyPixels(
System.Windows.Int32Rect.Empty,
bitmapData.Scan0,
bitmapData.Height * bitmapData.Stride,
bitmapData.Stride
);
bitmap.UnlockBits(bitmapData);
// Encoderを使う方法
// かなり楽だがその分遅い。
// SampleEncoderがSystem.Windows.Media.Imaging.BmpBitmapEncoderなら不透明部分を扱えず、
// System.Windows.Media.Imaging.PngBitmapEncoderなら高性能な分余計に遅くなる
// 元画像
System.Windows.Media.Imaging.BitmapSource bitmapSource;
// 処理
var encoder = new SampleEncoder();
encoder.Frames.Add(System.Windows.Media.Imaging.BitmapFrame.Create(bitmapSource));
System.Drawing.Bitmap bitmap = null;
using (var ms = new System.IO.MemoryStream()){
encoder.Save(ms);
ms.Seek(0, System.IO.SeekOrigin.Begin);
using (var temp = new System.Drawing.Bitmap(ms)){
// このおまじないの意味は参考資料を参照
bitmap = new System.Drawing.Bitmap(temp);
}
}
参考資料
- WPFの画像相互コンバーター。System.Drawing.BitmapからSystem.Windows.Controls.Imageへの変換。
- WPFの画像相互コンバーター。BitmapImageからBitmapSourceへの変換。
- System.Drawing.BitmapをWPF用に変換
- Convert to BitmapImage
- 俺が遭遇したWPFイメージコントロールのメモリーリークと回避法(?)の1つ - C#でプログラミングあれこれ
- .NETで画像処理を試してみる OpenCVSharp編 第5回 - A certain engineer "COMPLEX"
- Bitmap <-> BitmapSource converter
- ぼやきごと/2011-08-02/CopyPixels メソッドを用いた WPF BitmapSource から GDI Bitmap への変換 - ルーチェ's Homepage
- System.Drawing.BitmapとImageSourceを相互変換する - nuits.jp blog