ビットマップオブジェクトを作成する

今回は、特に目に見える機能ではなく、他のある機能を実現するためには欠かすことのできない、ビットマップオブジェクトについてです。

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

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メソッド)を使ってビットマップデータを書き換えることもできますし、直接ピクセルデータの格納されている領域にアクセスして、イメージを書き換えることもできるのです。


で、結局何が言いたいのかというと、作成したいビットマップオブジェクトは、DIBセクションオブジェクトなのか、それともDDBオブジェクトなのかということです。

ディスプレイで表示させるためにビットマップオブジェクトを作成するのなら、表示させるディスプレイ互換のDDBを作成するべきです。というのも、例えば、ディスプレイが8ビット色(256色)や16ビット色(ハイカラー)で表示されていえるときに、24ビット色(フルカラー)で作成されたDIBオブジェクトを渡すと、ビットマップが正常に表示されなくなってしまうことがあります。したがって、このような場合にはいったんディスプレイ互換のDDBオブジェクトを作成して、それを渡すようにするのです。

一方、API関数の中には、DDBオブジェクトの代わりにDIBセクションオブジェクトを渡すことができるものも存在します。この場合、DIBセクションを渡せば、オリジナルのイメージのままで保持されることになりますが、いったんDDBを作成してそれを渡すと、それはオリジナルのイメージではなく、いくつかの情報が欠けてしまっている可能性があります。(例えば、ディスプレイが256色表示のときにフルカラーイメージからビットマップオブジェクトを作成したときなど。) このようなときにはDIBセクションの方を作成して渡した方がいい場合もあります。

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

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

  1. CreateDC 関数に "DISPLAY" を渡してディスプレイのデバイスコンテキストのハンドルを取得
  2. CreateCompatibleBitmap 関数でディスプレイ互換のビットマップオブジェクト作成
  3. CreateCompatibleDC 関数でディスプレイ互換のデバイスコンテキスト作成
  4. SelectObject 関数で、ビットマップオブジェクトをデバイスコンテキストに選択
  5. BitBlt 関数で、HSPウィンドウのイメージをビットマップオブジェクトにコピー
  6. 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 パラメータ)にビットマップのハンドルを指定して呼び出すと、デバイスコンテキストに指定したビットマップが選択されます。


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

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


さて、描画するためにビットマップをデバイスコンテキストに選択したのですが、デバイスコンテキストに選択されたままでは、あとでこのビットマップを使用する際にうまくいかないことがあります。また、デバイスコンテキストのためにいくらかのメモリが消費されてもいます。そこで、ビットマップへの描画が終わったら、用済みのデバイスコンテキストは削除してしまいましょう。デバイスコンテキストを削除するためには DeleteDC 関数を呼び出します。

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

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


以上の方法でビットマップは作成できます。作成されたビットマップは、ビットマップのハンドルを引数とする各種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. DeleteDC 関数でデバイスコンテキストを削除

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

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

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

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

ビットマップへの描画

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

ただし、ビットマップを選択するためのメモリデバイスコンテキストを作成するとき、DDBを作成する場合には CreateCompatibleDC 関数にディスプレイのデバイスコンテキストのハンドルを渡していましたが、今回は、コピー元となるHSPウィンドウのメモリデバイスコンテキストのハンドルを渡します。このハンドルは CreateCompatibleBitmap 関数に渡したものと同じものです。

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

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

サンプルスクリプト

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

    #include "llmod.as"

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

    ;--------------------------------------------------------------
    ; CreateBitmap  ディスプレイ互換DDBオブジェクト作成
    ;--------------------------------------------------------------
    #deffunc CreateBitmap int, int, int, int
    mref px, 0              ; HSPウィンドウx座標
    mref py, 1              ; HSPウィンドウy座標
    mref sx, 2              ; xサイズ
    mref sy, 3              ; yサイズ
    mref stt, 64            ; stat
    mref bmscr, 67          ; 描画中ウィンドウのBMSCR構造体

    ; ディスプレイのデバイスコンテキストのハンドル取得
    devname = "DISPLAY"
    pm = 0,0,0,0
    getptr pm, devname      ; "DISPLAY"のアドレス
    dllproc "CreateDCA", pm, 4, D_GDI@
    hdcScreen = stat        ; ディスプレイのデバイスコンテキスト

    ; ディスプレイ互換ビットマップオブジェクト作成
    pm = hdcScreen, sx, sy
    dllproc "CreateCompatibleBitmap", pm, 3, D_GDI@
    hbitmap = stat          ; ビットマップオブジェクトのハンドル

    ; ディスプレイ互換デバイスコンテキスト作成
    pm = hdcScreen
    dllproc "CreateCompatibleDC", pm, 1, D_GDI@
    hdcMemory = stat        ; メモリデバイスコンテキストのハンドル

    ; ビットマップをデバイスコンテキストに選択
    pm = hdcMemory, hbitmap
    dllproc "SelectObject", pm, 2, D_GDI@

    ; HSPウィンドウからビットマップにイメージをコピー
    pm = hdcMemory, 0, 0, sx, sy, bmscr.4, px, py, $CC0020
    dllproc "BitBlt", pm, 9, D_GDI@

    ; デバイスコンテキストを削除
    dllproc "DeleteDC", hdcMemory, 1, D_GDI@
    dllproc "DeleteDC", hdcScreen, 1, D_GDI@

    stt = hbitmap           ; ビットマップオブジェクトのハンドル
    return

    ;--------------------------------------------------------------
    ; CreateDIB  DIBセクションオブジェクト作成
    ;--------------------------------------------------------------
    #deffunc CreateDIB int, int, int, int
    mref px, 0              ; HSPウィンドウx座標
    mref py, 1              ; HSPウィンドウy座標
    mref sx, 2              ; xサイズ
    mref sy, 3              ; yサイズ
    mref stt, 64            ; stat
    mref bmscr, 67          ; 描画中ウィンドウのBMSCR構造体

    ; DIBセクションオブジェクト作成
    pm = bmscr.4, sx, sy
    dllproc "CreateCompatibleBitmap", pm, 3, D_GDI@
    hbitmap = stat          ; ビットマップオブジェクトのハンドル

    ; メモリデバイスコンテキスト作成
    pm = bmscr.4
    dllproc "CreateCompatibleDC", pm, 1, D_GDI@
    hdcMemory = stat        ; メモリデバイスコンテキストのハンドル

    ; ビットマップをデバイスコンテキストに選択
    pm = hdcMemory, hbitmap
    dllproc "SelectObject", pm, 2, D_GDI@

    ; HSPウィンドウからビットマップにイメージをコピー
    pm = hdcMemory, 0, 0, sx, sy, bmscr.4, px, py, $CC0020
    dllproc "BitBlt", pm, 9, D_GDI@

    ; デバイスコンテキストを削除
    dllproc "DeleteDC", hdcMemory, 1, D_GDI@

    stt = hbitmap           ; ビットマップオブジェクトのハンドル
    return

    ;--------------------------------------------------------------
    ; DeleteBitmap  ビットマップオブジェクト削除
    ;--------------------------------------------------------------
    #deffunc DeleteBitmap int
    mref hbitmap, 0         ; ビットマップハンドル

    ; ビットマップオブジェクトを削除
    pm = hbitmap
    dllproc "DeleteObject", pm, 1, D_GDI@
    return

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


    onexit *lb_quit         ; 終了時の処理

    bgscr 2, 100, 100

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

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


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

スクリプトを見れば分かるとは思いますが、定義したモジュール命令の説明をしておきましょう。

CreateBitmap p1, p2, p3, p4
p1 : HSPウィンドウのコピー元x座標
p2 : HSPウィンドウのコピー元y座標
p3 : ビットマップの幅
p4 : ビットマップの高さ

描画中HSPウィンドウの指定した領域からビットマップオブジェクトを作成します。

今後、ビットマップオブジェクトを必要とする機能を使う場合には、このモジュールを使うことがあると思います。