ビットマップオブジェクト

ビットマップオブジェクト

Windowsの提供する機能の中には、画像を使うものが多数あります。例えば、ツールバーのボタン上に描かれている画像などがそうです。これらの画像はたいていの場合、GDIオブジェクトの1つである、ビットマップオブジェクトとしてWindowsが管理しています。したがって、これらの機能を使うためにはビットマップオブジェクトを作成しなければならないのです。そこでビットマップオブジェクトを作成する方法を紹介します。ただし今回は、作成したビットマップオブジェクトを用いて何かをするということはしません。

DDBとDIB

一口にビットマップといっても、それには『デバイス依存ビットマップ(device-dependent bitmap;DDB)』と『デバイス独立ビットマップ(device-independent bitmap;DIB)』の2種類があります。

デバイス依存ビットマップ(以下、DDBと記載)は、デバイスの仕組みや性能に依存するビットマップのことです。デバイスの性能や現在の状態によって、扱うことのできる色の種類や数などが制限されてきてしまいます。また、ピクセルのビット値がどのような意味を持つのかに関してもデバイスに依存するため、ピクセルのビット値を取得しただけでは、それが特定の色を一意的に表すと決め付けることができません。通常、ビットマップオブジェクトと言ったとき、それはDDBオブジェクトを表します。

一方、デバイス独立ビットマップ(以下、DIBと記載)とは、DDBとは逆に、デバイスの仕組みや性能に依存しないビットマップ、すなわち、ビットマップを扱うデバイスによって、ピクセルの色情報などが変化したりはしないビットマップのことを言います。DIBは、ピクセルの色が単純な配列としてまとめられていて、プログラムの中で直接配列を使うのと同じような感じで扱う事が可能になっています。HSPのウィンドウ画面のイメージも実はDIBとして保持されているので、ピクセルデータに直接アクセスしたりもできます。

ビットマップとビットマップオブジェクト

ところで、もともと『ビットマップ』とは点の集合(=配列)で表されたイメージあるいはその配列のデータのことを表していて、そのイメージをWindows上で1つの『オブジェクト』として扱ったものが『ビットマップオブジェクト』なのです。ところが、『ビットマップオブジェクト』のことを単に『ビットマップ』と呼ぶことも多いので、『ビットマップ』と書かれていた場合に、それがイメージデータとしてのビットマップを表しているのか、オブジェクトとしてのビットマップを表しているのかを的確に判断しなければなりません。

ビットマップオブジェクトは他のWindowsオブジェクトと同様、ハンドルによって識別されるので、「ビットマップのハンドル」とあったら、それはビットマップオブジェクトのハンドルであることが分かります。一方、「DDB(デバイス依存ビットマップ)」・「DIB(デバイス独立ビットマップ)」と言ったとき、それらはイメージデータとしてのビットマップを表します。(実際には、DDBと言った場合にビットマップオブジェクトを指すこともありますが。)

DIBセクション

もともと16ビットWindowsではDDBのみが使われていて、描画用のAPI関数(GDIメソッドとも呼ばれる)でもDDBだけしか使用することができませんでした。

32ビットWindowsになると、それまでのDDBと同じような操作方法でDIBを扱うことができるように、新しい機能が追加されました。それは、DIBセクション(DIB section)と呼ばれるもので、それまでのビットマップオブジェクト(DDBオブジェクト)に対応する、DIBセクションオブジェクト(DIBオブジェクト)を作成して、GDIメソッドを呼び出して直接DIBに描画していくことができます。(DIBセクションを使わない場合には、イメージデータのビット配列に直接書き込んでいかなくてはいけない。)

先ほど、HSPではイメージをDIBとして保持していると書きましたが、実際には、このDIBセクションとして保持されているのです。したがって、描画関数(GDIメソッド)を使ってビットマップデータを書き換えることもできますし、直接ピクセルデータの格納されている領域にアクセスして、イメージを書き換えることもできるのです。

API関数の中には、DDBオブジェクトの代わりにDIBセクションオブジェクトを渡すことができるものが多く存在します。この場合、DIBセクションを渡すと、DDBでは欠落してしまうような、オリジナルのイメージ情報を完全に残した状態で渡すことができます。

ディスプレイ互換のDDBオブジェクト

まずはDDBオブジェクトを作成する方法からです。手順は以下のようになります。

  1. CreateDC関数に "DISPLAY" を渡してディスプレイのデバイスコンテキストのハンドルを取得
  2. CreateCompatibleBitmap関数でディスプレイ互換のビットマップオブジェクト作成
  3. CreateCompatibleDC関数でディスプレイ互換のデバイスコンテキスト作成
  4. SelectObject関数で、ビットマップオブジェクトをデバイスコンテキストに選択
  5. BitBlt関数で、HSPウィンドウのイメージをビットマップオブジェクトにコピー
  6. SelectObject関数を再度呼び出し、デバイスコンテキストの選択ビットマップオブジェクトを元に戻す
  7. DeleteDC関数でデバイスコンテキストを削除

ここで示されている手順のうち、デスクトップ画面のデバイスコンテキストのハンドルの取得や、デバイスコンテキストを使った操作は、以前にも一度やっているので、あまり詳しく説明する必要はないでしょう。

ディスプレイのデバイスコンテキストの取得およびデバイスコンテキストへの操作については、『スクリーンキャプチャ』の項を参照してください。

ビットマップオブジェクトの作成

ビットマップを作成する場合には、まずディスプレイのデバイスコンテキストを取得し、そのデバイスコンテキストからデバイス互換ビットマップを作成する、という手順を取ります。

ディスプレイデバイスコンテキスト取得

ディスプレイのデバイスコンテキストを取得するには、以前にも説明したように、CreateDC関数を呼び出します。このとき、関数の第1引数には文字列 "DISPLAY" のアドレスを指定し他の3つのパラメータにはすべて0 (NULL) を指定します。

ディスプレイ互換ビットマップ作成

次に、ビットマップオブジェクトを作成します。ビットマップオブジェクトを作成するには、CreateCompatibleBitmap関数を使います。

HBITMAP CreateCompatibleBitmap(
    HDC hDC,          // デバイスコンテキストのハンドル
    int nWitdh,       // 幅
    int nHeight       // 高さ
);

この関数は、指定されたデバイスコンテキストに関連するデバイスと互換性のあるDDBビットマップオブジェクトを作成するものです。このとき、ビットマップの色形式などが指定したデバイスコンテキストのものと同じになります。したがって、この関数にディスプレイのデバイスコンテキストのハンドルを渡すことで、ディスプレイ互換のビットマップオブジェクトが作成されるわけです。

ビットマップオブジェクトがWindowsオブジェクトとして管理されているからには、それを識別するためのハンドルも存在します。この関数は、戻り値として、作成したビットマップオブジェクトのハンドルを返します。

ビットマップへの描画

以上でビットマップを作成することはできましたが、ビットマップを作成した直後はそのビットマップに何が描画されているのかは定義されていません。すなわち、作成されたビットマップの画像は真っ黒な状態だとか、逆に真っ白な状態だとかは決められていないということです。実際に、作成直後のビットマップの画像はランダムなビットデータです。

そこで、次は作成したビットマップに画像を描画しなければなりません。最も簡単な方法は、HSPウィンドウに描画されている画像をそっくりそのままコピーしてくるというものでしょう。ビットマップに画像をコピーするには(あるいは画像コピー以外の描画をする場合も同様ですが)、描画対象となるデバイスコンテキストを指定して、GDI関数を呼び出す必要があります。

そこで、まずはデバイスコンテキストを作成し、ビットマップをそのデバイスコンテキストに割り当てます。そのあとGDI関数で描画を行ないます。これによってビットマップに描画をすることができます。

メモリデバイスコンテキスト作成

今回作成するデバイスコンテキストは、メモリデバイスコンテキストと呼ばれるものです。メモリデバイスコンテキストは、実際にディスプレイに表示されているウィンドウではなく、メモリ上にあるビットマップオブジェクトに描画するために使われるデバイスコンテキストです。メモリデバイスコンテキストを作成するにはCreateCompatibleDC関数を呼び出します。

HDC CreateCompatibleDC(
    HDC hDC       // デバイスコンテキストのハンドル
);

この関数は、第1引数(hDCパラメータ)に指定されたデバイスコンテキストと関連するデバイスと互換性のあるメモリデバイスコンテキストを作成します。今回は、先ほど作成されたビットマップオブジェクトに対して描画するので、再びディスプレイのデバイスコンテキストのハンドルを指定して、ディスプレイ互換のメモリデバイスコンテキストを作成します。

この関数は作成されたデバイスコンテキストのハンドルを返します。GDIの描画関数を呼び出してビットマップに描画する際にはこのハンドルを指定する必要があります。

メモリデバイスコンテキストへのビットマップ選択

デバイスコンテキストを作成できたら、このデバイスコンテキストに対してGDI関数を呼び出したときにビットマップに描画されるようにするために、このデバイスコンテキストにビットマップを選択します。デバイスコンテキストにビットマップを選択するにはSelectObject関数を使います。

HGDIOBJ SelectObject(
    HDC     hDC,     // デバイスコンテキストのハンドル
    HGDIOBJ hObject  // 選択するオブジェクトのハンドル
);

この関数の第2引数(hObjectパラメータ)にビットマップのハンドルを指定して呼び出すと、デバイスコンテキストに指定したビットマップが選択されます。

SelectObject関数は、戻り値として以前に選択されていたオブジェクトのハンドルを返します。今回はhObjectパラメータにビットマップオブジェクトのハンドルを指定しているので、戻り値はそれまで選択されていたビットマップハンドルになります。このハンドルは、後でデバイスコンテキストの状態を元に戻す時に必要になるため、変数に格納して保持しておく必要があります。

デバイスコンテキストへの描画(ビットマップイメージのコピー)

この後に、このデバイスコンテキストに対してGDI描画関数を呼び出せば、ビットマップに描画されます。今回はBitBlt関数を使って、HSPウィンドウの画像をコピーすることにします。このとき、HSPウィンドウのデバイスコンテキストのハンドルが必要になりますが、これはシステム変数hdcで取得することができますので、それを使用します。

ちなみに、BitBlt関数は、コピー元のデバイスコンテキストとコピー先のデバイスコンテキストの色形式が異なる場合には、コピー先の色でできるだけ近い色で表示されるように、ビット値を変更してくれます。

デバイスコンテキストの状態復元

デバイスコンテキストに対するすべての描画操作が完了したら、デバイスコンテキストで選択されているオブジェクトをすべて元の状態に戻しておく必要があります。

今回はメモリデバイスコンテキストにおいてビットマップの選択を変更したので、元のビットマップが選択されるように、再度SelectObjectを呼び出して、元のオブジェクトのハンドルを指定する必要があります。元のオブジェクトのハンドルは、最初のSelectObject呼び出しで戻り値として返されているものです。

デバイスコンテキスト削除

描画の最後の処理として、使用済みのデバイスコンテキストを削除します。デバイスコンテキストを削除するためにはDeleteDC関数を呼び出します。

BOOL DeleteDC(
    HDC hDC     // デバイスコンテキストのハンドル
);

今回は、CreateCompatibleDCで作成されたメモリデバイスコンテキストのほかにも、最初にCreateDC関数で取得したデバイスコンテキストのハンドルもDeleteDC関数に渡して削除しなくてはいけませんので、忘れないようにしましょう。


以上の方法でビットマップは作成できました。作成したビットマップは、ビットマップのハンドルを引数とする各種Windows APIで使用することができます。

ビットマップオブジェクトの削除

最後にもう1つだけ注意すべきことがあります。作成したビットマップは、不要になった時点でアプリケーションが削除しなければいけません。作成したビットマップは終了時までに削除するようにしておきましょう。ビットマップを削除するにはDeleteObject関数を使います。

BOOL DeleteObject(
    HGDIOBJ hObject    // GDIオブジェクトのハンドル
);

DIBセクションオブジェクト

次に、DIBセクションオブジェクトを作成する方法を説明しましょう。DDBオブジェクトを作成するのよりもわずかに簡単です。

  1. CreateCompatibleBitmap関数にHSPウィンドウのメモリデバイスコンテキストのハンドルを渡し、ビットマップ(DIBセクション)オブジェクト作成
  2. CreateCompatibleDC関数にHSPウィンドウのメモリデバイスコンテキストのハンドルを渡し、メモリデバイスコンテキスト作成
  3. SelectObject関数で、ビットマップ(DIBセクション)オブジェクトをデバイスコンテキストに選択
  4. BitBlt関数で、HSPウィンドウのイメージをビットマップオブジェクトにコピー
  5. SelectObject関数を再度呼び出し、デバイスコンテキストの選択ビットマップオブジェクトを元に戻す
  6. DeleteDC関数でデバイスコンテキストを削除

ビットマップオブジェクトの作成

DDB作成のときと同じく、ビットマップオブジェクトを作成するにはCreateCompatibleBitmap関数を呼び出します。

ただし、今回はディスプレイのデバイスコンテキストのハンドルを指定するのではありません。今回指定するのは、イメージのコピー元となるHSPウィンドウのメモリデバイスコンテキストのハンドルを取得して、それをこの関数に渡すようにします。このハンドルはシステム変数hdcで取得することができます。

CreateCompatibleBitmap関数は、渡されたデバイスコンテキストに関連するデバイスと互換性のあるDDBを作成するものですが、渡されたハンドルがメモリデバイスコンテキストであり、そのデバイスコンテキストにDIBセクションオブジェクトが選択されている場合には、同じ形式のDIBセクションオブジェクトを作成するのです。HSPウィンドウのイメージがDIBセクションとして保持されていることは先ほど説明しましたが、そのために、このようにすることでDIBセクションを作成できます。

ビットマップへの描画

これ以降はDDB作成とほとんど変わりません。まず、CreateCompatibleDC関数でメモリデバイスコンテキストを作成し、SelectObject関数でビットマップをデバイスコンテキストに選択します。BitBlt関数でHSPウィンドウのイメージをビットマップオブジェクトにコピーした後、再度SelectObject関数を呼び出して選択ビットマップを元の状態に戻し、最後にDeleteDC関数を呼び出してメモリデバイスコンテキストを削除します。

DDBの場合と異なるのは、メモリデバイスコンテキストを作成するとき、DDBを作成する場合にはCreateCompatibleDC関数にディスプレイのデバイスコンテキストのハンドルを渡していましたが、今回は、コピー元となるHSPウィンドウのメモリデバイスコンテキストのハンドルを渡します。このハンドルはCreateCompatibleBitmap関数に渡したものと同じく、システム変数hdcを指定します。

ビットマップオブジェクトの削除

作成したDIBセクションオブジェクトは、不要になった時点で削除します。これには、DDBのときと同じくDeleteObject関数を使います。

サンプルスクリプト

ビットマップオブジェクトを作成するスクリプトです。今後、ビットマップを必要とする機能を使うときのために、モジュールとして定義することにします。今回のスクリプトはただビットマップオブジェクトを作成するだけで、他には何もしていないので、外見からは何もわかりません。

#module ;======================================================

#uselib "gdi32.dll"
#cfunc CreateDC "CreateDCA" sptr, sptr, sptr, int
#cfunc CreateCompatibleDC "CreateCompatibleDC" int
#cfunc CreateCompatibleBitmap "CreateCompatibleBitmap" int, int, int
#func  SelectObject "SelectObject" int, int
#func  BitBlt "BitBlt" int, int, int, int, int, int, int, int, int
#func  DeleteDC "DeleteDC" int
#func  DeleteObject "DeleteObject" int

#define NULL 0
#define SRCCOPY     0x00CC0020

;--------------------------------------------------------------
; CreateBitmap  ディスプレイ互換DDBオブジェクト作成
;--------------------------------------------------------------
#defcfunc CreateBitmap int px, int py, int sx, int sy

; ディスプレイのデバイスコンテキストのハンドル取得
hdcScreen = CreateDC("DISPLAY", NULL, NULL, NULL)

; ディスプレイ互換ビットマップオブジェクト作成
hBitmap = CreateCompatibleBitmap(hdcScreen, sx, sy)

; ディスプレイ互換デバイスコンテキスト作成
hdcMemory = CreateCompatibleDC(hdcScreen)

; ビットマップをデバイスコンテキストに選択
SelectObject hdcMemory, hBitmap
hOldBitmap = stat

; HSPウィンドウからビットマップにイメージをコピー
BitBlt hdcMemory, 0, 0, sx, sy, hdc, px, py, SRCCOPY

; デバイスコンテキストの選択ビットマップを戻す
SelectObject hdcMemory, hOldBitmap

; デバイスコンテキストを削除
DeleteDC hdcMemory
DeleteDC hdcScreen

; ビットマップオブジェクトのハンドルを返す
return hBitmap

;--------------------------------------------------------------
; CreateDIB  DIBセクションオブジェクト作成
;--------------------------------------------------------------
#defcfunc CreateDIB int px, int py, int sx, int sy

; DIBセクションオブジェクト作成
hBitmap = CreateCompatibleBitmap(hdc, sx, sy)

; メモリデバイスコンテキスト作成
hdcMemory = CreateCompatibleDC(hdc)

; ビットマップをデバイスコンテキストに選択
SelectObject hdcMemory, hBitmap
hOldBitmap = stat

; HSPウィンドウからビットマップにイメージをコピー
BitBlt hdcMemory, 0, 0, sx, sy, hdc, px, py, SRCCOPY

; デバイスコンテキストの選択ビットマップを戻す
SelectObject hdcMemory, hOldBitmap

; デバイスコンテキストを削除
DeleteDC hdcMemory

; ビットマップオブジェクト(DIBセクション)のハンドルを返す
return hBitmap

;--------------------------------------------------------------
; DeleteBitmap  ビットマップオブジェクト削除
;--------------------------------------------------------------
#deffunc DeleteBitmap int hbmp

; ビットマップオブジェクトを削除
DeleteObject hbmp
return

#global ;======================================================


onexit *lb_quit         ; 終了時の処理

bgscr 2, 100, 100

;=================================================
;
;この部分でHSPウィンドウにイメージを描画します
;
;=================================================

; ビットマップオブジェクトの作成してハンドルを取得
hbmp = CreateBitmap(0, 0, 100, 100)

gsel 0
if hbmp {
    mes "ビットマップオブジェクトを作成しました"
    mes "ビットマップハンドル= "+hbmp
} else {
    mes "ビットマップ作成に失敗しました"
}
stop


; 終了処理(ビットマップオブジェクト削除)
*lb_quit
if hbmp {
    DeleteBitmap hbmp
}
end