トップ回答者
DIB形式に BITMAPFILEHEADER を付加してImageを生成したい

質問
-
いつもお世話になっております。
あるメーカーが作ったAPIを使い、DIB形式のハンドルを取得しています。
その後、そのハンドルから Bitmap データをメモリ内に生成し、それをそのまま PictureBox にセットして、イメージを表示しようと思っていますが、
PictureBox にセットすると、Bitmap データの生成がなにかおかしいのだと思いますが、PictureBox全体が赤いバツ印で表示されてしまいます。
特に処理中にエラー等は発生しておりません。取得したDIB形式のハンドルには、BITMAPFILEHEADER が含まれていない、という事がAPIリファレンスに書かれてありましたので、
おそらくそれを付加しないといけないのだと思いますが、具体的にどのように処理すればいいのか悩んでいます。
それ以外にも問題点があるのかもしれませんが。。。ヒント等ありましたら、いただけたらと思います。よろしくお願いいたします。
/* ビットマップ形式のデータを返す関数を抜粋 */ Dim phDIB As Integer Try '画像の情報をセット(サンプルソースのため固定値で値をセット) Dim width As Integer = 42037 'ページの横幅。単位は1/100mm Dim height As Integer = 29718 'ページの高さ。単位は1/100mm Dim nHorRes As Integer = 200 '水平方向の解像度。単位はdpi Dim nVerRes As Integer = 200 '垂直方向の解像度。単位はdpi Dim inchwidth As Decimal = CDec(width) / 2540D 'インチに変換 Dim inchheight As Decimal = CDec(height) / 2540D 'インチに変換 Dim pxwidth As Integer = nHorRes * inchwidth 'ピクセルに変換 Dim pxheight As Integer = nVerRes * inchheight 'ピクセルに変換 'イメージのハンドルを取得 phDIB = DIBハンドルを取得する関数() 'ハンドルからビットマップデータ生成 Dim BMP As Bitmap BMP = New Bitmap(pxwidth, pxheight, nHorRes, Imaging.PixelFormat.Format8bppIndexed, phDIB) Return BMP Catch ex As Exception 'エラー処理 Return Nothing Finally 'メモリを解放 Marshal.FreeHGlobal(phDIB) End Try
回答
-
phDIB は、CF_DIB 形式のビットマップのようですね。BITMAPINFOHEADER から始まる Packed DIBとも呼ばれるビットマップデータを HGLOBAL に格納したものとなります。
C言語的には、
VOID* pTop = GlobalLock( phDIB ); BITMAPINFOHEADER* pHeader = (BITMAPINFOHEADER*)pTop; ... GlobalUnlock( phDIB ); GlobalFree( phDIB );
という形でアクセスしていくことになります(各APIの呼び出しは、C形式のままなのでそのままではVBには当てはまらないので要注意)。
とっちゃん@わんくま同盟, Microsoft MVP for Visual C++ (Oct 2005-) http://blogs.wankuma.com/tocchann/
すべての返信
-
Bitmap constructorの解説によると
However, the memory should not be released until the related Bitmap is released.
だそうですが(日本語のページだとshouldの訳が抜けてる…)、Marshal.FreeHGlobalを(このタイミングでは)行わないようにすればどうでしょうか?
// メモリの管理がめんどくさくなりますが…。
-
この Bitmap クラスのコンストラクタの場合、最後は、Scan0 のバッファへのポインタを渡します(詳しくは、リファレンスを参照)。
pxwidth, pxheight は、画像データのピクセル値(それぞれ、幅と高さ)。
nHorRes は、phDIB が次のラインの先頭ピクセルへ移動するためのオフセット値(ボトムアップなら通常マイナスになる)。
PixelFormat.Format8bppIndexed は、phDIB の色深度にあったDIB形式(この場合は、パレットインデックス形式の8ビットカラーとなる)。
phDIB は、画像のビットイメージを格納した配列データの y = 0 を指すポインタ(C言語的意味でのポインタ)。
DIBハンドルを取得する関数がどのようなものかわかりませんが、DIBのハンドルで FreeHGlobal で解放されるとすると、CF_DIB 形式のデータなのではないか?と思われます。もしそうだとすると phDIB をそのまま渡すのではなく、BITMAPINFOHEADER 分や、必要があれば RGBQUAD 分などをスキップしたところにビットマップの画像データの先頭(Scan0ではない。バッファ上の数値的意味での最小値位置)があるので、そこから Stride に沿った形で Scan0 の位置を割り出す必要があります。
とっちゃん@わんくま同盟, Microsoft MVP for Visual C++ (Oct 2005-) http://blogs.wankuma.com/tocchann/
-
DIBハンドルを取得する関数() で返ってくる値ですが、
Win32API の HGLOBAL と同等の型のものが返ってくるようです。
以下、APIリファレンスの説明ですが、イメージに必要なメモリー領域を関数内部で GlobalAlloc() によりアロケートし、それへのハンドルを phDIB を通して帰す。
返されるメモリー領域には BITMAPINFOHEADER から始まる DIB形式でイメージが格納される(BITMAPFILEHEADER は含まれない)。
返されたハンドルは、GlobalFree() により解放しなければならない。との事です。
phDIBはC言語的意味でのポインタだと思っていましたが、CF_DIB形式なるものでしょうか?
画像処理は初めてなものので、コメントいただいた内容をまだ全部呑み込めていないので、少し勉強してまいります。- 編集済み ねむねむ 2014年8月27日 9:44 ×CF_BID ○CF_DIB
-
phDIB は、CF_DIB 形式のビットマップのようですね。BITMAPINFOHEADER から始まる Packed DIBとも呼ばれるビットマップデータを HGLOBAL に格納したものとなります。
C言語的には、
VOID* pTop = GlobalLock( phDIB ); BITMAPINFOHEADER* pHeader = (BITMAPINFOHEADER*)pTop; ... GlobalUnlock( phDIB ); GlobalFree( phDIB );
という形でアクセスしていくことになります(各APIの呼び出しは、C形式のままなのでそのままではVBには当てはまらないので要注意)。
とっちゃん@わんくま同盟, Microsoft MVP for Visual C++ (Oct 2005-) http://blogs.wankuma.com/tocchann/
-
Image.FromHbitmapを使ってHBITMAPからImageに変換できませんか? その後はSaveメソッドを使えるかと。
# 「DIBハンドル」がHBITMAPのことでなかったらごめんなさい。
-
よく読んでませんでした。DIBハンドルではなく(DIBを格納した)HGLOBALですね。
1点、補足で、Marshal.FreeHGlobalメソッドは名前に反してLocalFree()を呼び出します。GlobalFree()ではないので気を付けてください。
-
何をやればいいかは何となく把握できましたが、時間がかかりそうなので、とりあえずとっちゃんさんの回答をマークとして、完了扱いにさせていただきます。
実はAPI集の中に、ビットマップファイル作ってくれるAPIがあるので、今回はそちらに逃げます(^_^;)
ビットマップファイルを作ってまた読み込むよりかは、CF_DIBをImageに変換して表示する方が速度的に早そうなので、チャレンジしてはみましたが、中々難しいですね。以下、作りかけてダメだったソースをUpします。
方向性は合っている・・・と信じたいですがいかがでしょうか。/* ビットマップ形式のデータを返す関数を抜粋 */ Dim phDIB As Integer Dim lpBuffer As Integer Dim iMemSize As Integer Try '画像の情報をセット(サンプルソースのため固定値で値をセット) Dim width As Integer = 42037 'ページの横幅。単位は1/100mm Dim height As Integer = 29718 'ページの高さ。単位は1/100mm Dim nHorRes As Integer = 200 '水平方向の解像度。単位はdpi Dim nVerRes As Integer = 200 '垂直方向の解像度。単位はdpi Dim inchwidth As Decimal = CDec(width) / 2540D 'インチに変換 Dim inchheight As Decimal = CDec(height) / 2540D 'インチに変換 Dim pxwidth As Integer = nHorRes * inchwidth 'ピクセルに変換 Dim pxheight As Integer = nVerRes * inchheight 'ピクセルに変換 'イメージのハンドルを取得 phDIB = DIBハンドルを取得する関数() 'グローバルメモリのロック(GlobalLock()はWindows APIを定義して使用) lpBuffer = GlobalLock(phDIB) 'グローバルメモリのサイズを取得(GlobalSize()はWindows APIを定義して使用) iMemSize = GlobalSize(phDIB) 'BITMAPINFOHEADERの設定(BITMAPINFOHEADER構造体を定義して使用) Dim bmi As BITMAPINFOHEADER WIth bmi .biWidth = pxwidth .biHeight = pxheight .biPlanes = 1 .biBitCount = 8 '256色 .biCompression = 0 '圧縮なし .biSizeImage = 0 .biXPelsPerMeter = 0 .biYPelsPerMeter = 0 .biClrImportant = 0 .biSize = Marshal.SizeOf(bmi) End With Dim mem(iMemSize - 1) As Byte 'CF_DIBをbyte配列に格納 Marshal.Copy(lpBuffer, mem, 0, mem.Length) 'BITMAPINFOHEADERのサイズ分をスキップしてメモリ内に保管し直す Marshal.Copy(mem, bmi.biSize, lpBuffer, mem.Length - bmi.biSize) 'ビットマップデータ生成 Dim BMP As Bitmap BMP = New Bitmap(pxwidth, pxheight, nHorRes, Imaging.PixelFormat.Format8bppIndexed, lpBuffer) Return BMP Catch ex As Exception 'エラー処理 Return Nothing Finally 'グローバルメモリのアンロック(GlobalUnlock()はWindows APIを定義して使用) GlobalUnlock(phDIB) 'メモリを解放 Marshal.FreeHGlobal(phDIB) End Try
-
API集に GDI+ で作成するというオプションはないのでしょうか?もしそれがあるなら、そのほうが現実的かもしれません。
ざっくりとコードを見てみましたが、あっているようで近くない。。。という感じですね。
まず、BITMAPINFOHEADER は自分で作るのではなく、phDIB の先頭にあるものを参照するだけになります。
DIBの作成のために用意した各種値から、Bitmap クラスを構築するのではなく、実際に存在しているDIBの情報を参照してBitmapクラスを作る必要があります。
それ以外の部分では、Strideに当たる値をきちんと求めて、なおかつ、コピーしてデータを渡すのではなく、実際の先頭ラインを渡せるようにすればうまくいくと思います。
言葉で書くと要所だけで大したことはないように見えますが、これをコードにすると最低限(エラーなしのもの)でも、100行程度は必要になるものです。
探していないのでわかりませんが、もしかしたらCF_DIBからBitmapイメージを作るライブラリがどこかに公開されてるかもしれません。
それを探してみるというのも一つの解決策かもしれません。
C++レベルで、簡易的なものですが、ざっとコードを起こしてみて、正直これは .NET でやるものじゃないんじゃね?と思ったんで。
とっちゃん@わんくま同盟, Microsoft MVP for Visual C++ (Oct 2005-) http://blogs.wankuma.com/tocchann/
-
>API集に GDI+ で作成するというオプションはないのでしょうか?
無いのです、残念ながら。。。VBAのマクロですが、私がやりたい事と近そう(?)なソースをWeb上で発見いたしました。
しかし、これを理解しつつ.netに置き換えるのは時間的余裕もないので、出来るという事が分かっただけで今回は良しとします。
一応そのページのリンクを貼らせていただきます。
エクセルの入り口
(トップページにあるExcel関係の仕事の欄の、Personal.xlsをクリック→Save Picture as BMP をクリック)
コメントいただいた皆様、ありがとうございました。