LoginSignup
53
80

More than 5 years have passed since last update.

C#における「ビットマップ形式の画像データを相互変換」まとめ

Last updated at Posted at 2017-11-18

概要

 「ビットマップ形式」……いわゆるラスターグラフィックスは、最も典型的な画像の表現方法でしょう。
 それをプログラム上で表現するため、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を指定する

 このことから、例えば BitmapImageSystem.Windows.Controls.Image にするには

// BitmapImage型の画像
System.Windows.Media.Imaging.BitmapImage bitmapImage;
// 
var image = new System.Windows.Controls.Image();
image.Source = bitmapImage;

で十分ですし、 BitmapImageBitmapSource にするには

// 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です。なお、継承元が同じな BitmapImageInteropBitmap は直接キャストすることができず、必ず 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
        );
}

 また、 BitmapImageSystem.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);
    }
}

参考資料

53
80
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
53
80