.net と CreateDIBSection

win32 APIでは、CreateDIBSection() というAPIで、デバイス独立ビットマップ(DIB)を作ることが出来ます。

画像処理なんかを書くときには、結構重宝するんで、C++/CLR .net(こんな呼び方で通じる?C++/CLR + .net Frameworkってこと) で同じようなことが出来ないのか調べてみました。

C++/CLR .net 上からwin32 APIを呼び出すのは、結構簡単で、普通に"windows.h"をインクルードしてあげて、必要なライブラリを依存関係に追加するだけです。

...でも、折角.net Frameworkを使っているのにそれじゃ面白くないんで、Drawing::Bitmap を使って同じようなことをしてみました。


Drawing::Bitmapを簡単に表示するには、フォームにPictureBoxを貼り付けて、

bmpBaseImage = gcnew Drawing::Bitmap( this->pictboxColor->Width, this->pictboxColor->Height,
                                      Drawing::Imaging::PixelFormat::Format32bppArgb );

this->pictboxColor->Image = bmpBaseImage;
this->pictboxColor->Invalidate();

こんな感じにしてあげると、Bitmapを32bit ARGBフォーマットで作成して、PictureBox上に表示することが出来ます。

さて、Bitmapには、SetPixel() なるメソッドが用意されているので、こいつを使ってピクセルを書き込むことは出来ますが、それじゃ面白くも無いんで、CreateDIBSection()と同じように、イメージの先頭のポインタを取ってきて、自由に書き込みが行えるようにしてみます。

そのために、BitmapにはLockBits()というメソッドが用意されています。

Drawing::Rectangle a_rect = Drawing::Rectangle( 0, 0, i_bmpTarget->Width, i_bmpTarget->Height );
Drawing::Imaging::BitmapData^ a_bmpdata = i_bmpTarget->LockBits( a_rect, Drawing::Imaging::ImageLockMode::ReadWrite,                                                                  i_bmpTarget->PixelFormat );

ロック対象の矩形を指定して、LockBits() を呼び出すことで、対象の矩形領域の先頭を示すポインタを取得することが出来ます。

u8* a_pPtr = (u8*)a_bmpdata->Scan0.ToInt32();
s32 a_nStride = a_bmpdata->Stride;

for( s32 y = 0; y < i_bmpTarget->Height; y++ ){
    u32* a_pLine = (u32*)(a_pPtr + y * a_nStride);
    for( s32 x = 0; x < i_bmpTarget->Width; x++ ){
        *a_pLine = i_Color;
        a_pLine++;
    }
}

s32とかs8とかは気にすんな。
単に int と unsigned char が typedef してあるだけです。

BitmapData::Scan0 はイメージの先頭を示す、IntPtrなので、通常のポインタとして利用できるようにToInt32()を使ってキャストしちゃいます。

BitmapData::Stride は、次のラインまでのストライドを示しています。

十分に書き込んだ後は、ロックを解除しておしまいです。

i_bmpTarget->UnlockBits( a_bmpdata );

LockBits() によって、対象領域をシステムメモリにロックして、UnlockBits()によって、システムメモリにロックされた空間をマネージド領域に描き戻すって感じだと思われます。

これで、一応フレームバッファを自分で確保して描画処理を行う準備は整いました。

...でも、LockBits() / UnlockBits() がとても重いみたいです。

私の環境で、CreateDIBSection したものと、Drawing::Bitmap を利用したものの速度差を比較してみたら、257x207の領域を塗りつぶすのに、Drawing::Bitmap対DIBSection = 500ms対2ms ぐらいの速度差が出てしまいました。

さて、そんなわけで、CreateDIBSectionの作成方法についても書き残しておきます。

今度は、PictureBoxにBitmapを貼り付ける代わりに、PictureBoxからウインドウハンドル(HWND)を取り出して、それを利用して win32 APIを呼び出してみます。

PictureBoxに限らず、.netのコントロールからHWNDさえ取り出せちゃえば、win32 APIの世界に戻ってくることが出来るはずです。

HWNDを取り出すのには、次のようにします

HWND a_hWnd = (HWND)this->pictboxColor->Handle.ToPointer();

PictureBox::Handleアクセッサでは、IntPtrが取得できるので、ToPointer() を使って生のポインタに変換(?)してHWND型にキャストしてしまいます。

肝はこれだけなんですけど、もうちょっとコードを書いておくと

HDC a_hDc  = ::GetDC( i_hWnd );
m_hDC = ::CreateCompatibleDC( a_hDc );
m_hBmp = ::CreateDIBSection( m_hDC, (BITMAPINFO*)m_pHeader, DIB_RGB_COLORS, &m_pBuf, NULL, 0);

こんな感じに、CreateDIBSectionを作成するだけです。

あとは、作成したDIBをImaging::Bitmapに変換してあげて、PictureBoxに貼り付けてあげれば画面描画が行えます。

DIBをImaging::Bitmapに変更するのには、DIBを作成したときのHBITMAPハンドルを使って、Drawing::Bitmap::FromHbitmap() を行います。

Drawing::Bitmap^ a_Bmp = Drawing::Bitmap::FromHbitmap( (IntPtr)m_pBmp->GetDIBHandle() );
pictboxMainView->Image = a_Bmp;

極力マネージドコードだけで作業してみたかったんですけど、早速アンマネージドコードのお世話になることになってしまいました。
...だってこの速度差じゃねぇ

これでフレームバッファも確保できたことだし、あとはレイトレやろうが、ソフトウェアレンダラ作ろうが自由にしてくださいって感じですね。

コントロールからHWNDがとってこれるのってのは結構使い勝手がよくって、コントロール上にOpenGLを使った描画が行えたりします。

次回は、この辺りを書いてみようかと思います