不定形リージョン作成関数・再考編

以前に不定形リージョンを作成する関数のマシン語コードを作成しましたね。高速化を目的としてマシン語で作成したのですが、それでも、画像が大きく複雑になるとかなりの時間が掛かってしまいます。(本当はそのようなリージョンをウィンドウに割り当てるべきではないんですけど。)

今回はリージョン作成の時間をより短くするために、改良をしてみようと思います。具体的には、リージョンオブジェクトの作成に ExtCreateRegion 関数を使用するようにします。この関数は複数の長方形領域からリージョンを作成することができるので、長方形リージョンを1つ1つ作成して結合していくよりも、はるかに高速化できます。


では、ソースコードを書いていきましょう。関数内部で必要となる情報は、

となります。前回と同様に、最初の4つはすべて BMSCR 構造体に含まれる情報であるので、ウィンドウの BMSCR 構造体のアドレスを引数に渡すことにします。また、以前のものとは変えて、今回は3つの API 関数のアドレスをそれぞれ引数として渡します。

今回定義する関数は以下のようになります。

HRGN WINAPI CreateRgnFromBmp2(
    BMSCR *pBmscr,
    DWORD crTransparent,
    HRGN (WINAPI *pfnExtCreateRegion) (XFORM*,DWORD,RGNDATA*),
    int  (WINAPI *pfnCombineRgn) (HRGN,HRGN,HRGN,int),
    BOOL (WINAPI *pfnDeleteObject) (HGDIOBJ)
);

pBmscr パラメータには BMSCR 構造体のアドレスが渡されるようにします。

crTransparent パラメータは、ウィンドウがフルカラーモードの場合は、透過色を RGB 値で指定した値です。パレットモードの場合は透過色のパレットインデックスです。

pfnExtCreateRegion パラメータ、 pfnCombineRgn パラメータ、 pfnDeleteObject パラメータは、それぞれ ExtCreateRegion 関数、 CombineRgn 関数、 DeleteObject 関数のアドレスです。この場合も、関数のプロトタイプ(関数型と引数の型の宣言)を同時に記述しておきます。


新しいAPI関数 ExtCreateRegion について説明しておきましょう。

HRGN ExtCreateRegion(
  CONST XFORM   *pXform,    // 変形データ
  DWORD          nCount,    // リージョンデータサイズ
  CONST RGNDATA *pRgnData   // リージョンデータバッファ
);

pXform パラメータはここでは使用しないので、 NULL を指定しておきます。

pRgnData パラメータには、作成するリージョンオブジェクトの領域を定義した RGNDATA 構造体のアドレスを指定します。このパラメータで指定される RGNDATA 構造体はサイズ可変であるため、そのサイズを nCount パラメータに指定しなければなりません。このサイズは、以下で説明する RGNDATAHEADER 構造体と、 RECT 構造体の配列のすべての要素を足し合わせたバイト数になります。


ExtCreateRegion 関数のパラメータとして指定される RGNDATA 構造体は以下のように定義されています。

typedef struct _RGNDATA {
    RGNDATAHEADER rdh;        // RGNDATAHEADER 構造体
    char          Buffer[1];  // リージョンデータの先頭バイト
} RGNDATA, *PRGNDATA; 

rdh メンバは、 RGNDATAHEADER 構造体です。この構造体は入れ子になっています。この構造体については後で説明します。

Buffer メンバは、リージョンオブジェクトの領域データを定義する RECT 構造体の配列です。ここでは char 型配列として定義されていますが、実際には RECT 構造体の配列になっています。これらの RECT 構造体には、リージョンを生成する長方形領域の座標を格納します。すべての領域を結合したものが新しいリージョンとして生成されることになります。


RGNDATA 構造体の rdh メンバである RGNDATAHEADER 構造体は以下のように定義された構造体です。

typedef struct _RGNDATAHEADER { 
    DWORD dwSize;         // 構造体サイズ
    DWORD iType;          // リージョンの型
    DWORD nCount;         // 長方形の数
    DWORD nRgnSize;       // 0 を指定
    RECT  rcBound;        // リージョン境界
} RGNDATAHEADER, *PRGNDATAHEADER;

dwSize メンバは RGNDATAHEADER 構造体のサイズを指定します。

iType メンバには RDH_RECTANGLES を指定しなければなりません。

nCount メンバには、リージョンを定義する長方形の数を指定します。これは、 RGNDATAHEADER 構造体の後ろに続く RECT 構造体の要素数を指定することになります。

nRgnSize メンバには 0 を指定します。

rcBound メンバは、作成するリージョンの境界を表す長方形の座標を格納した RECT 構造体です。この構造体は入れ子の構造体になっています。


ところで、 ExtCreateRegion 関数は複数の長方形領域からリージョンを作成することができると言いましたが、実はこの関数は、 Windows 95/98/Me では、あまりに多い長方形領域を一度に指定することができないのです。(一度に指定できるのは2000個未満だそうです。)

したがって、それ以上の数の長方形領域からなるリージョンを作成するためには、それらをいくつかに分けてリージョンを作成して、結合していくという手段をとらなければなりません。


では、実際にソースコードを書いてみることにしましょう。

RGNDATA 構造体の領域をスレッドスタック領域から取りたい(ローカル変数として確保したい)のですが、この構造体は任意のサイズで使用できるような定義がされていて不便なので、このプログラム用に RGNDATA 構造体を拡張して、以下のような構造体を新しく定義してみることにしましょう。

#define MAX_RGN_RECTS  250

typedef struct {
    RGNDATAHEADER rdh;
    RECT rcBuffer[MAX_RGN_RECTS];
} RGNDATA_EX;

一度に指定できる長方形領域の数を MAX_RGN_RECTS として定義してあります。ここではとりあえず、一度に指定できる長方形領域の数を250個までとして、その数に達したら、そこまでのデータでいったんリージョンオブジェクトを作成してしまうようにしています。この数でも、以前のものよりはずっと高速化されます。

ソースファイル bmprgn.c

#include <windows.h>
#include "hspdll.h"

// 一度に指定するRECT構造体の最大数
#define MAX_RGN_RECTS  250

// このプログラム用に RGNDATA 構造体を拡張する
typedef struct {
    RGNDATAHEADER rdh;
    RECT rcBuffer[MAX_RGN_RECTS];
} RGNDATA_EX;

HRGN WINAPI CreateRgnFromBmp2(
    const BMSCR *pBmscr,        // ウィンドウのBMSCR構造体アドレス
    DWORD crTransparent,        // 透過色(COLORREFまたはパレットインデックス)
    // 以下 API 関数へのポインタ
    HRGN (WINAPI *pfnExtCreateRegion) (XFORM*,DWORD,RGNDATA_EX*),
    int  (WINAPI *pfnCombineRgn) (HRGN,HRGN,HRGN,int),
    BOOL (WINAPI *pfnDeleteObject) (HGDIOBJ)
    )
{
    HRGN hRgn, hRgnNew;             // リージョンハンドル
    BYTE *pBit;                     // 現在のピクセルへのポインタ
    DWORD crPixel;                  // 現在のピクセルの色
    int x, y;                       // 現在のピクセルの座標
    int x0;                         // 透過色開始位置のx座標を保存
    int nPixelSize;                 // 1ピクセルのバイト数

    // RGNDATA 構造体の領域を確保
    RGNDATA_EX RgnData;             // 拡張された RGNDATA 構造体
    RECT *prcTemp;

    if (pBmscr->palmode)
        nPixelSize = 1;
    else
        nPixelSize = 3;

    RgnData.rdh.dwSize = sizeof(RGNDATAHEADER);
    RgnData.rdh.iType = RDH_RECTANGLES;
    RgnData.rdh.nCount = 0;
    RgnData.rdh.nRgnSize = 0;
    RgnData.rdh.rcBound.left = 0;
    RgnData.rdh.rcBound.top = 0;
    RgnData.rdh.rcBound.right = pBmscr->sx;
    RgnData.rdh.rcBound.bottom = pBmscr->sy;

    hRgn = NULL;
    x = 0;
    y = 0;
    // ポインタを1行目の先頭のピクセルに合わせる
    pBit = pBmscr->pBit + ((pBmscr->sy - 1) * pBmscr->sx * nPixelSize);
    for(;;) {
        if ((RgnData.rdh.nCount >= MAX_RGN_RECTS) || (y >= pBmscr->sy)) {
            hRgnNew = pfnExtCreateRegion( NULL, sizeof(RGNDATA_EX), &RgnData);
            if (hRgnNew == NULL) {
                // 既にhRgnを作成していた場合は解放
                if (hRgn)
                    pfnDeleteObject(hRgn);
                return NULL;
            }

            if (hRgn) {
                pfnCombineRgn(hRgnNew, hRgnNew, hRgn, RGN_OR);
                pfnDeleteObject(hRgn);
            }
            hRgn = hRgnNew;
            RgnData.rdh.nCount = 0;
            if (y >= pBmscr->sy) 
                break;
        }

        // 現在のピクセルの色を取得
        if (pBmscr->palmode)
            crPixel = pBit[0];
        else
            crPixel = RGB(pBit[2], pBit[1], pBit[0]);
        // 透過色かどうかを調べる
        if (crPixel != crTransparent) { // 透過色ではない
            x0 = x;     // x を保存(透過色ではない点の始まりを保存)
                        // 連続した透過色ではない領域を求める
            for (x = x+1; x < pBmscr->sx; x++) {
                // ポインタを次のピクセルへ
                pBit += nPixelSize;

                // 現在のピクセルの色を取得
                if (pBmscr->palmode)
                    crPixel = pBit[0];
                else
                    crPixel = RGB(pBit[2], pBit[1], pBit[0]);

                // 透過色になったら break
                if (crPixel == crTransparent)
                    break;
            }

            prcTemp = &RgnData.rcBuffer[RgnData.rdh.nCount++];
            prcTemp->left = x0;
            prcTemp->top = y;
            prcTemp->right = x;
            prcTemp->bottom = y+1;
        }

        x++;
        if (x >= pBmscr->sx) {
            // ポインタを次の行へ
            x = 0;
            y++;
            pBit = pBmscr->pBit + (( pBmscr->sy - 1 - y ) * pBmscr->sx * nPixelSize);
        } else {
            // ポインタを次のピクセルへ
            pBit += nPixelSize;
        }
    }
    return hRgn;
}

このソースコード、作成後のマシン語コードのサイズなどを意識して作成したため、ちょっと読みづらい構造になってます。Cのソースコードとしてはちょっと汚いですね。


あとは、コンパイルしてマシン語を作成します。今回も長いので、オブジェクトファイルをバイナリエディタで開いて、マシン語ファイルとして保存してしまうのがよいと思います。

ただ、一応、変数に格納した場合にはどうなるかを示しておきましょう。以下は、VC++でコンパイルした場合のものです。

xdim fnBmpRgn, 97
fnBmpRgn.0 = $81ec8b55, $000fccec, $57565300, $c7087d8b, $fff03485, $000020ff
fnBmpRgn.6 = $3885c700, $01fffff0, $8b000000, $478b0c5f, $08778b04, $f04c8589
fnBmpRgn.12 = $dbf7ffff, $b589db1b, $fffff050, $83fee383, $c93303c3, $f03c8d89
fnBmpRgn.18 = $8d89ffff, $fffff040, $f0448d89, $8d89ffff, $fffff048, $89fc4d89
fnBmpRgn.24 = $4d89084d, $af0f4ef8, $f3af0ff0, $81147703, $fff03cbd, $0000faff
fnBmpRgn.30 = $3b057300, $4b7c084f, $f034858d, $6850ffff, $00000fc0, $55ff006a
fnBmpRgn.36 = $89c08510, $840ff445, $000000d0, $00fc7d83, $026a1374, $50fc75ff
fnBmpRgn.42 = $1455ff50, $fffc75ff, $458b1855, $f84d8bf4, $f03ca583, $3b00ffff
fnBmpRgn.48 = $4589084f, $af8d0ffc, $83000000, $74000c7f, $06b60f05, $b60f10eb
fnBmpRgn.54 = $c0330256, $468a268a, $08e0c101, $453bc20b, $8b5d740c, $45890845
fnBmpRgn.60 = $0845fff4, $3904478b, $227d0845, $7f83f303, $0574000c, $eb06b60f
fnBmpRgn.66 = $56b60f10, $8ac03302, $01468a26, $0b08e0c1, $0c453bc2, $858bd375
fnBmpRgn.72 = $fffff03c, $c1f4558b, $85ff04e0, $fffff03c, $5405848d, $89fffff0
fnBmpRgn.78 = $08558b10, $8d085089, $48890151, $0c508904, $8b0845ff, $45390447
fnBmpRgn.84 = $83127c08, $8b000865, $89410877, $f12bf84d, $ffff00e9, $e9f303ff
fnBmpRgn.90 = $ffffff03, $00fc7d83, $75ff0674, $1855fffc, $5e5fc033, $14c2c95b
fnBmpRgn.96 = $00000000

実際にサンプルを書いてみましょう。今回はモジュール命令として定義してあります。

このサンプルでは、マシン語コードを直接変数に格納していますが、ファイルに保存されたコードを使う場合には、それを読み込むように書き換えてください。

リージョンの作成に使用するビットマップファイル test.bmp を各自で準備してください。このサンプルでは、ビットマップイメージの白で表示される部分を透明化して表示します。それ以外の色を透過色にする場合は、透過色の指定を書き換えてください。

    #include "llmod.as"
    #include "xdim.as"

    #module ;########## 不定形リージョン作成モジュール #############

    ;===============================================================
    ; 描画中ウィンドウのイメージから不定形リージョン作成
    ; CreateBmpRgn  p1
    ;     p1 : 透過色を表すRGB値またはパレットインデックス
    ;   stat : 作成されたリージョンのハンドルが返る
    ;===============================================================
    #deffunc CreateBmpRgn int
    mref crTransparent, 0   ; 透過色
    mref stt, 64            ; stat
    mref bmscr, 67          ; 描画中ウィンドウのBMSCR構造体

    if pfunc == 0 {
        ; 関数のマシン語コード
        dim fnBmpRgn, 97
        fnBmpRgn.0 = $81ec8b55, $000fccec, $57565300, $c7087d8b, $fff03485, $000020ff
        fnBmpRgn.6 = $3885c700, $01fffff0, $8b000000, $478b0c5f, $08778b04, $f04c8589
        fnBmpRgn.12 = $dbf7ffff, $b589db1b, $fffff050, $83fee383, $c93303c3, $f03c8d89
        fnBmpRgn.18 = $8d89ffff, $fffff040, $f0448d89, $8d89ffff, $fffff048, $89fc4d89
        fnBmpRgn.24 = $4d89084d, $af0f4ef8, $f3af0ff0, $81147703, $fff03cbd, $0000faff
        fnBmpRgn.30 = $3b057300, $4b7c084f, $f034858d, $6850ffff, $00000fc0, $55ff006a
        fnBmpRgn.36 = $89c08510, $840ff445, $000000d0, $00fc7d83, $026a1374, $50fc75ff
        fnBmpRgn.42 = $1455ff50, $fffc75ff, $458b1855, $f84d8bf4, $f03ca583, $3b00ffff
        fnBmpRgn.48 = $4589084f, $af8d0ffc, $83000000, $74000c7f, $06b60f05, $b60f10eb
        fnBmpRgn.54 = $c0330256, $468a268a, $08e0c101, $453bc20b, $8b5d740c, $45890845
        fnBmpRgn.60 = $0845fff4, $3904478b, $227d0845, $7f83f303, $0574000c, $eb06b60f
        fnBmpRgn.66 = $56b60f10, $8ac03302, $01468a26, $0b08e0c1, $0c453bc2, $858bd375
        fnBmpRgn.72 = $fffff03c, $c1f4558b, $85ff04e0, $fffff03c, $5405848d, $89fffff0
        fnBmpRgn.78 = $08558b10, $8d085089, $48890151, $0c508904, $8b0845ff, $45390447
        fnBmpRgn.84 = $83127c08, $8b000865, $89410877, $f12bf84d, $ffff00e9, $e9f303ff
        fnBmpRgn.90 = $ffffff03, $00fc7d83, $75ff0674, $1855fffc, $5e5fc033, $14c2c95b
        fnBmpRgn.96 = $00000000
        ; マシン語関数アドレス取得
        getptr pfunc, fnBmpRgn

        ; API 関数アドレス取得
        dll_getfunc pfunc.1, "ExtCreateRegion", D_GDI@
        dll_getfunc pfunc.2, "CombineRgn", D_GDI@
        dll_getfunc pfunc.3, "DeleteObject", D_GDI@
    }

    ; リージョン作成
    getptr pm.0, bmscr          ; BMSCR 構造体のアドレス
    pm.1 = crTransparent        ; 透過色
    pm.2 = pfunc.1, pfunc.2, pfunc.3    ; API 関数のアドレス
    ll_callfunc@ pm, 5, pfunc   ; 関数コード呼び出し
    stt = dllret@               ; 作成されたのリージョンハンドル
    return

    ;===============================================================
    ; 描画中ウィンドウにウィンドウリージョンを設定します。
    ; SetWndRgn  p1
    ;     p1 : リージョンハンドル
    ;   stat : 0:正常  1:エラー
    ;===============================================================
    #deffunc SetWndRgn int
    mref hrgn, 0            ; リージョンハンドル
    mref stt, 64            ; stat
    mref bmscr, 67          ; 描画中ウィンドウのBMSCR構造体

    ; ウィンドウリージョンを設定
    pm = bmscr.13, hrgn, 1
    dllproc "SetWindowRgn", pm, 3, D_USER@
    if stat : stt = 0 : else : stt = 1
    return

    #global ;############# モジュール終わり ########################


    ; 画像のロード
    bgscr 2,0,0,0
    picload "test.bmp"

    ; リージョンオブジェクトの作成
    CreateBmpRgn $ffffff        ; 白を透過色に指定
    hrgn = stat                 ; リージョンハンドル

    ; リージョンをウィンドウに割り当てる
    SetWndRgn hrgn
    stop