- 追加された行はこの色です。
- 削除された行はこの色です。
#blog2navi()
*CopyPixels メソッドを用いた WPF BitmapSource から GDI Bitmap への変換 [#m65c9b37]
グダグダと書く前にまず答えから。~
次の @code{Convert}; メソッドで @code{System.Windows.Media.Imaging.BitmapSource}; クラスのオブジェクトを @code{System.Drawing.Bitmap}; クラスのオブジェクトに変換できます。
#code(csharp){{
using System;
using System.Runtime.InteropServices;
using System.Drawing;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Imaging = System.Drawing.Imaging;
namespace BitmapSourceSample
{
class Sample
{
/// <summary>
/// BitmapSource をARGB形式の Bitmap に変換する。
/// </summary>
/// <param name="src">BitmapSource 。</param>
/// <returns>Bitmap 。</returns>
public static Bitmap Convert(BitmapSource src)
{
// フォーマットが異なるならば変換
BitmapSource s = src;
if (s.Format != PixelFormats.Bgra32)
{
s = new FormatConvertedBitmap(
s,
PixelFormats.Bgra32,
null,
0);
s.Freeze();
}
// ピクセルデータをコピー
int width = (int)s.Width;
int height = (int)s.Height;
int stride = width * 4;
byte[] datas = new byte[stride * height];
s.CopyPixels(datas, stride, 0);
// Bitmap へピクセルデータ書き出し
Bitmap dest = new Bitmap(
width,
height,
Imaging::PixelFormat.Format32bppArgb);
Imaging::BitmapData destBits = null;
try
{
destBits = dest.LockBits(
new Rectangle(0, 0, width, height),
Imaging::ImageLockMode.WriteOnly,
Imaging::PixelFormat.Format32bppArgb);
Marshal.Copy(datas, 0, destBits.Scan0, datas.Length);
}
catch
{
dest.Dispose();
dest = null;
throw;
}
finally
{
if (dest != null && destBits != null)
{
dest.UnlockBits(destBits);
}
}
return dest;
}
}
}
}}
この例では、必ずARGB形式となるように必要に応じてソースのフォーマットを変換しています。~
任意のフォーマットで変換したい場合はソースのフォーマットに応じた処理を行う必要があります。
GoogleでWPFの @code{BitmapSource}; からGDIの @code{Bitmap}; への変換について検索すると、 @code{BmpBitmapEncoder}; を用いる方法が多く紹介されています(2011年8月現在)。~
この方法は、一旦エンコーダによってBMP形式へ落とし込むことにより、ソースのフォーマットを気にすることなく変換できるのが利点といえます。
しかしこの方法は言ってしまえば一度BMPファイルにしてから読み込み直すのと変わらないため、速度面でのコストが気になります。~
また、BMP形式では不透明度を扱えない為、不透明度を保持したい場合は @code{PngBitmapEncoder}; 等を用いることになり、更に処理時間が掛かることになります。
「フォーマットは固定でも構わないのでより高速に変換したい」という要求がある場合、今回紹介した @code{CopyPixels}; メソッドを用いる方法に軍配が上がります。~
どれほど速度に差が出るのか、次のようなベンチマークプログラムで実験してみました。~
なお、上述の @code{Sample}; クラスが定義済みであるものとします。
#code(csharp){{
using System;
using System.IO;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace BitmapSourceSample
{
class Program
{
/// <summary>
/// BitmapSource を Bitmap に変換する。
/// </summary>
/// <typeparam name="TEncoder">
/// 中間形式の生成に用いる BitmapEncoder 型。
/// </typeparam>
/// <param name="src">BitmapSource 。</param>
/// <returns>Bitmap 。</returns>
static Bitmap ConvertBy<TEncoder>(BitmapSource src)
where TEncoder : BitmapEncoder, new()
{
// エンコーダを作成してフレーム追加
var encoder = new TEncoder();
encoder.Frames.Add(BitmapFrame.Create(src));
// ストリームを介して Bitmap に変換
Bitmap dest = null;
using (var s = new MemoryStream())
{
encoder.Save(s);
s.Seek(0, SeekOrigin.Begin);
using (var temp = new Bitmap(s))
{
// ストリームを閉じた後の Save メソッド呼び出し等で
// GDI+例外が発生しないように、別の Bitmap へコピー
dest = new Bitmap(temp);
}
}
return dest;
}
/// <summary>
/// 変換ソースとなる BitmapSource を作成する。
/// </summary>
/// <param name="format">フォーマット。</param>
/// <returns>BitmapSource 。</returns>
static BitmapSource MakeSource(PixelFormat format)
{
// 文字列作成
var ft = new FormattedText(
"あいうえお\nABCDEFG 12345",
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface("MS ゴシック"),
48,
new LinearGradientBrush(Colors.Blue, Colors.Red, 0));
// DrawingVisual へ文字列描画
DrawingVisual dv = new DrawingVisual();
using (var dc = dv.RenderOpen())
{
dc.DrawText(ft, new System.Windows.Point());
}
// RenderTargetBitmap へ描画
var bmp =
new RenderTargetBitmap(640, 480, 96, 96, PixelFormats.Pbgra32);
bmp.Render(dv);
bmp.Freeze();
// 目的のフォーマットへ変換
var dest = new FormatConvertedBitmap(bmp, format, null, 0);
dest.Freeze();
return dest;
}
/// <summary>
/// ベンチマーク処理を行う。
/// </summary>
/// <param name="name">
/// ベンチマーク項目名。保存する画像ファイルの名前にも使われる。
/// </param>
/// <param name="loopCount">ループ回数。</param>
/// <param name="func">ベンチマーク処理デリゲート。</param>
static void DoBenchmark(
string name,
int loopCount,
Func<Bitmap> func)
{
// 処理実施
var sw = new Stopwatch();
sw.Start();
for (int i = 0; i < loopCount; ++i)
{
using (var temp = func()) { }
}
sw.Stop();
// 結果出力
Console.WriteLine(
"{0,-25} : {1,12:F6}",
name,
sw.ElapsedTicks * 1000 / (decimal)Stopwatch.Frequency);
// 一応、PNGで保存してみる
using (var bmp = func())
{
bmp.Save(name + ".png");
}
}
/// <summary>
/// 全ベンチマーク処理を行う。
/// </summary>
/// <param name="format">ソース画像のフォーマット。</param>
static void DoBenchmarkAll(PixelFormat format)
{
// ソース画像作成
var src = MakeSource(format);
// ベンチマーク処理
DoBenchmark(
format.ToString() + "-CopyPixels",
100,
() => Sample.Convert(src));
DoBenchmark(
format.ToString() + "-BmpBitmapEncoder",
100,
() => ConvertBy<BmpBitmapEncoder>(src));
DoBenchmark(
format.ToString() + "-PngBitmapEncoder",
100,
() => ConvertBy<PngBitmapEncoder>(src));
}
/// <summary>
/// メインエントリポイント。
/// </summary>
static void Main()
{
DoBenchmarkAll(PixelFormats.Bgra32);
DoBenchmarkAll(PixelFormats.Bgr24);
DoBenchmarkAll(PixelFormats.Indexed8);
}
}
}
}}
このプログラムは、
-32ビットBGRA形式
-24ビットBGR形式
-8ビットパレット形式
の各形式の @code{BitmapSource}; について、
-@code{CopyPixels}; メソッドを用いる方法。
-@code{BmpBitmapEncoder}; クラスを用いる方法。
-@code{PngBitmapEncoder}; クラスを用いる方法。
の各方法による @code{Bitmap}; への変換処理を各100回実行し、その処理時間を出力するものです。~
また、ついでにその際生成される @code{Bitmap}; をPNG画像ファイルとして保存しています。
このプログラムを私の環境(Windows7 64bit)で実行した結果は次の通りです。~
数値の単位はミリ秒です。
#pre{{
Bgra32-CopyPixels : 126.398125
Bgra32-BmpBitmapEncoder : 989.100697
Bgra32-PngBitmapEncoder : 1797.793503
Bgr24-CopyPixels : 227.051113
Bgr24-BmpBitmapEncoder : 986.993156
Bgr24-PngBitmapEncoder : 1504.336953
Indexed8-CopyPixels : 689.379903
Indexed8-BmpBitmapEncoder : 1367.630629
Indexed8-PngBitmapEncoder : 1188.354228
}}
ご覧の通り、元となる @code{BitmapSource}; のフォーマットによって比率に差はあるものの、すべてにおいて @code{CopyPixels}; メソッドを用いる方法が最も高速になりました。~
特にフォーマット変換不要な32ビットBGRA形式においては、 @code{BmpBitmapEncoder}; を用いる場合の約8倍、 @code{PngBitmapEncoder}; を用いる場合の約15倍高速になりました。
今まで @code{BmpBitmapEncoder}; クラスを用いていた方は、検討の余地があるのではないでしょうか。
RIGHT:Category: [[[C#>ぼやきごと/カテゴリ/C#]]][[[プログラミング>ぼやきごと/カテゴリ/プログラミング]]] - 2011-08-02 09:50:51
----
RIGHT:&blog2trackback();
#comment(above)
#blog2navi()