Tutorial 25: Simple Bitmap

このチュートリアルでは、ビットマップをどのように使用するかを説明する。 正確に言えば、ウィンドウのクライアントエリアにどうやってビットマップを表示するかを説明する。
 ソース   リソーススクリプト   ビットマップ   実行結果 

Theory:

ビットマップはコンピュータに格納された画像だと考えることができる。 画像フォーマットは沢山あるが、Windowsが元来サポートしているのは Windows Bitmap Graphics ファイル(.bmp)だ。 今回説明する「ビットマップ」は、このWindows bitmap graphics のことだ。 これをいちばん簡単に使用するには、リソースとして使用すればよいが、 それには2種類の方法がある。 1つは以下のようにリソース定義ファイル(.rc)にビットマップを格納するやり方だ。

#define IDB_MYBITMAP  100
IDB_MYBITMAP BITMAP "c:\project\example.bmp"

この方法はビットマップだということを表す定数を使用する。 上の行でIDB_MYBITMAPという名前が100になることを定義している。 以下のプログラムでは、このラベルをビットマップを参照する際に使用する。 次の行ではビットマップのリソースを定義しており、 リソースコンパイラにビットマップファイルがどこにあるかを示している。

もう1つの方法はビットマップを以下のような名前で定義する。

MyBitMap BITMAP "c:\project\example.bmp"

この方法は、さっきの方法では定数で識別していたものを、 今度は「MyBitMap」という文字列で識別することになる。

どちらの方法でもきちんと動作するが、 今回のサンプルはリソースファイルにビットマップを格納する方法である。

  1. LoadBitmap関数をCALLしてビットマップのハンドルを取得する。

    LoadBitmap proto hInstance:HINSTANCE, lpBitmapName:LPSTR

    この関数はビットマップハンドルを返す。hInstanceはこのプログラムのインスタンスハンドルだ。lpBitmapNameはビットマップファイル名だ(方法2では)。
    もし IDB_MYBITMAP のように定数でビットマップを参照する場合は、ここにその定数を記述する(ちなみに上の例では100になっている)。短いサンプルを以下に示す。

    ●1番目の方法

    .386
    .model flat, stdcall
    ................
    .const
    IDB_MYBITMAP   equ 100
    ...............
    .data?
    hInstance dd ?
    ..............
    .code
    .............
       invoke GetModuleHandle,NULL
       mov hInstance,eax
    ............
       invoke LoadBitmap,hInstance,IDB_MYBITMAP
    ...........

    ●2番目の方法

    .386
    .model flat, stdcall
    ................
    .data
    BitmapName db "MyBitMap",0
    ...............
    .data?
    hInstance dd ?
    ..............
    .code
    .............
       invoke GetModuleHandle,NULL
       mov hInstance,eax
    ............
       invoke LoadBitmap,hInstance,addr BitmapName
    ...........

  2. デバコンのハンドルを取得する。取得方法はWM_PAINTメッセージに対する処理において、BeginPaint関数をCALLするか、どこかでGetDC関数をCALLするか、だ。

  3. 本当のデバコンと同じ性質を持ったメモリデバコンを作成する。
    このメモリデバコンというものは、ビットマップを描画することができる「隠れた」ドローイングサーフェスと考えることができる。

    一連の処理が終わると、ある関数をCALLして、その隠れたサーフェスに描画した内容を、実際のデバコンへコピーする。これは、ダブルバッファと呼ばれるテクニックで、画像を高速で表示させる方法である。この「隠れた」サーフェスは、CreateCompatibleDC関数をCALLすることにより作成できる。

    CreateCompatibleDC proto hdc:HDC

    この関数が成功すれば、eaxレジスタにメモリデバコンハンドルが返ってくる。引数の hdc は本当のデバコンで、これとコンパチな(互換性のある)メモリデバコンを作成してくれる。

  4. これで、隠れたサーフェスが取得された。
    今度は、そのサーフェスに描画するビットマップを選択するのだが、これは、SelectObject関数をCALLすることによりできる。この関数へは、1番目の引数に、メモリデバコンのハンドルを、2番目の引数には表示したいビットマップを指定する。SelectObject関数は次のようなプロトタイプになっている。

    SelectObject  proto hdc:HDC, hGdiObject:DWORD

  5. メモリデバコンへの描画が完了したはずだ。
    なので、あとやるべきことは本当のデバコンへコピーするだけだが、この方法はBitBlt関数をCALLする方法、もしくはStretchBlt関数をCALLする方法などいくつかある。
    BitBlt関数は単にデバコン間でコピーするだけなのに対して、StretchBlt関数はサイズを伸縮できる分、BitBlt関数より少々時間がかかる。ここでは簡単なBitBlt関数を使用する。

    BitBlt proto hdcDest:DWORD, nxDest:DWORD, nyDest:DWORD, nWidth:DWORD, nHeight:DWORD, 
                 hdcSrc:DWORD, nxSrc:DWORD, nySrc:DWORD, dwROP:DWORD

    • hdcDest
      ビットマップ転送先のデバコンのハンドル
    • nxDest, nyDest
      アウトプット領域の左上の座標値
    • nWidth, nHeight
      アウトプット領域の長さと幅
    • hdcSrc
      ビットマップ転送元のデバコンのハンドル
    • nxSrc, nySrc
      転送元ビットマップの転送矩形領域の左上の座標値
    • dwROP
      ラスタオペレーションコードを指定する。画像データをコピーする際、コピー元とコピー先の色データをどのように結合するかを指定できる。
      ほとんどの場合、新しいデータで上書きしたいだけだろう。
  6. ビットマップに関する処理が終わったら、DeleteObject関数をCALLして削除する。

これが全てだ。要約すると以下のようになる。

  1. リソーススクリプトにビットマップを記述する
  2. LoadBitmap関数をCALLして、リソースからビットマップをロードする
  3. ビットマップハンドルを取得する
  4. ビットマップを描画したいデバコンのハンドルを取得する
  5. そのデバコンとコンパチなメモリデバコンを取得する
  6. そのメモリデバコンに描画するビットマップを選択する
  7. メモリデバコンから本当のデバコンへコピーする

Example Code:

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\gdi32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\gdi32.lib

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
IDB_MAIN  equ 1

.data
ClassName db "SimpleWin32ASMBitmapClass",0
AppName db "Win32ASM Simple Bitmap Example",0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hBitmap dd ?

.code
start:
 invoke GetModuleHandle, NULL
 mov   hInstance,eax
 invoke GetCommandLine
 mov   CommandLine,eax
 invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
 invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
 LOCAL wc:WNDCLASSEX
 LOCAL msg:MSG
 LOCAL hwnd:HWND
 mov  wc.cbSize,SIZEOF WNDCLASSEX
 mov  wc.style, CS_HREDRAW or CS_VREDRAW
 mov  wc.lpfnWndProc, OFFSET WndProc
 mov  wc.cbClsExtra,NULL
 mov  wc.cbWndExtra,NULL
 push hInstance
 pop  wc.hInstance
 mov  wc.hbrBackground,COLOR_WINDOW+1
 mov  wc.lpszMenuName,NULL
 mov  wc.lpszClassName,OFFSET ClassName
 invoke LoadIcon,NULL,IDI_APPLICATION
 mov  wc.hIcon,eax
 mov  wc.hIconSm,eax
 invoke LoadCursor,NULL,IDC_ARROW
 mov  wc.hCursor,eax
 invoke RegisterClassEx, addr wc
 INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
          WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
          CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
          hInst,NULL
 mov  hwnd,eax
 invoke ShowWindow, hwnd,SW_SHOWNORMAL
 invoke UpdateWindow, hwnd
 .while TRUE
 invoke GetMessage, ADDR msg,NULL,0,0
 .break .if (!eax)
 invoke TranslateMessage, ADDR msg
 invoke DispatchMessage, ADDR msg
 .endw
 mov    eax,msg.wParam
 ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
  LOCAL ps:PAINTSTRUCT
  LOCAL hdc:HDC
  LOCAL hMemDC:HDC
  LOCAL rect:RECT
  .if uMsg==WM_CREATE
     invoke LoadBitmap,hInstance,IDB_MAIN
     mov hBitmap,eax
  .elseif uMsg==WM_PAINT
     invoke BeginPaint,hWnd,addr ps
     mov   hdc,eax
     invoke CreateCompatibleDC,hdc
     mov   hMemDC,eax
     invoke SelectObject,hMemDC,hBitmap
     invoke GetClientRect,hWnd,addr rect
     invoke BitBlt,hdc,0,0,rect.right,rect.bottom,hMemDC,0,0,SRCCOPY
     invoke DeleteDC,hMemDC
     invoke EndPaint,hWnd,addr ps
 .elseif uMsg==WM_DESTROY
 invoke DeleteObject,hBitmap
 invoke PostQuitMessage,NULL
 .ELSE
 invoke DefWindowProc,hWnd,uMsg,wParam,lParam
 ret
 .ENDIF
 xor eax,eax
 ret
WndProc endp
end start

;---------------------------------------------------------------------
;                           The resource script
;---------------------------------------------------------------------
#define IDB_MAIN 1
IDB_MAIN BITMAP "tweety78.bmp"

Analysis:

このチュートリアルの解説は、十分ではないことを先に言っておく。 ;-p

#define IDB_MAIN 1
IDB_MAIN BITMAP "tweety78.bmp"

IDB_MAIN という定数を定義して、その値を 1 とする。 そして、以降リソース識別子として使用する。 リソースに含まれているビットマップファイルは、 "tweety78.bmp" というファイル名で、 リソーススクリプトと同じフォルダにある。

.if uMsg==WM_CREATE
   invoke LoadBitmap,hInstance,IDB_MAIN
   mov hBitmap,eax

WM_CREATEメッセージに対する処理として、 リソースからビットマップファイルをロードするため、 2番目の引数にビットマップのリソースIDをセットしてLoadBitmap関数をCALLする。 そうすると、ビットマップハンドルが返ってくる。

ビットマップがロードされれば、次に、メインウィンドウのクライアントエリアに描画する。

.elseif uMsg==WM_PAINT
   invoke BeginPaint,hWnd,addr ps
   mov   hdc,eax
   invoke CreateCompatibleDC,hdc
   mov   hMemDC,eax
   invoke SelectObject,hMemDC,hBitmap
   invoke GetClientRect,hWnd,addr rect
   invoke BitBlt,hdc,0,0,rect.right,rect.bottom,hMemDC,0,0,SRCCOPY
   invoke DeleteDC,hMemDC
   invoke EndPaint,hWnd,addr ps

今度は、WM_PAINTメッセージに対しての処理で、 ビットマップを描画するかどうかを選択する。 まず最初に、BeginPaint関数をCALLしてデバコンのハンドルを取得する。 そして、CreateCompatibleDC関数をCALLしてデバコンとの互換のある、 メモリデバイスコンテキストを作成する。 次に、SelectObject関数により、 そのメモリデバコンのオブジェクトにビットマップを選択する。 GetClientRect関数をCALLして、クライアントエリアの描画領域を決定し、 メモリデバコンから本当のデバコンへビットマップを転送するBitBlt関数により、 クライアントエリアにビットマップを表示させる。 描画が完了したら、メモリデバコンは必要ないので、 DeleteDC関数により削除する。 最後に、EndPaint関数で描画セッションを終了させる。

.elseif uMsg==WM_DESTROY
  invoke DeleteObject,hBitmap
  invoke PostQuitMessage,NULL

ビットマップが必要なくなると、DeleteObject関数で削除する。


[戻る]