不定形リージョン作成関数

前回までは Windows システムから呼び出されるコールバック関数を作成していましたが、今回はスクリプト中から直接呼び出す関数を作成してみましょう。といっても、作成方法は前回までと変わりません。

今回作成する関数は、 HSP ウィンドウに描画されているビットマップと同じ形のリージョンを作成し、これをウィンドウに設定することで、ウィンドウの形をビットマップと同じ形にしてしまうというものです。これは loadlib.dll のみで実現させることができますが、高速化させることを考えるなら、マシン語コードで実現したいところです。


リージョンについての詳しい説明はなしにして、さっそくソースコードを書いていきましょう。今回のように関数を直接呼び出す場合には、マシン語コードの中に情報を埋め込む必要はありません。必要な情報はすべて関数の引数として渡せばいいのです。

今回はビットマップデータに直接アクセスして、指定された色(透過色)以外の部分にリージョンが設定されるようにしていきます。また、ウィンドウ初期化モードがパレットモードの場合でもフルカラーモードの場合でも実行させることができるように、両方のモードに対応させましょう。そこで、関数内部で必要となる情報は、

といったところでしょう。これらのうち、最初の4つはすべて BMSCR 構造体に含まれる情報であるので、ウィンドウの BMSCR 構造体のアドレスを引数に渡すことにします。また、3つの API 関数のアドレスは以下のように定義された構造体に格納して、そのアドレスを渡すことにしましょう。(別々の引数として渡してもいいのですが。)

typedef struct {
    HRGN (WINAPI *pfnCreateRectRgn) (int,int,int,int);
    int  (WINAPI *pfnCombineRgn) (HRGN,HRGN,HRGN,int);
    BOOL (WINAPI *pfnDeleteObject) (HGDIOBJ);
} RGN_FUNC;

それぞれのメンバ名には、対応する関数名に“pfn”をつけてあります。(関数名と同じ名前を使うと、問題が発生する場合があるので。) また、コンパイラの型チェックを使用するために、今回は FARPROC 型ではなく、関数のプロトタイプ(関数型と引数の型の宣言)も同時に記述してあります。

そして、今回定義する関数は以下のようになります。関数呼び出し時の形式を合わせるために、プレフィックス WINAPI を付けましょう。 WINAPI は、ヘッダファイルの中で、呼び出し形式を表すプレフィックス __stdcall につけられている別名です。ですから、 WINAPI の代わりに __stdcall を指定してもよいです。

HRGN WINAPI CreateRgnFromBmp(
    BMSCR *pBmscr,         // ウィンドウのBMSCR構造体アドレス
    DWORD  crTransparent,  // 透過色(COLORREFまたはパレットインデックス)
    RGN_FUNC *pFunc        // RGN_FUNC構造体アドレス
);

pBmscr パラメータには BMSCR 構造体のアドレスが渡されるようにします。 crTransparent パラメータは、ウィンドウがフルカラーモードの場合は、透過色を RGB 値で指定した値です。パレットモードの場合は透過色のパレットインデックスです。 pFunc は、 HSP スクリプト側で取得した API 関数のアドレスを格納した RGN_FUNC 構造体のアドレスです。この関数は作成したリージョンのハンドルを返します。


実際のソースコードは以下のようになります。

ソースファイル bmprgn.c

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

// 関数アドレスを格納する構造体
typedef struct {
    HRGN (WINAPI *pfnCreateRectRgn) (int,int,int,int);
    int  (WINAPI *pfnCombineRgn) (HRGN,HRGN,HRGN,int);
    BOOL (WINAPI *pfnDeleteObject) (HGDIOBJ);
} RGN_FUNC;

HRGN WINAPI CreateRgnFromBmp(
    BMSCR *pBmscr,              // ウィンドウのBMSCR構造体アドレス
    DWORD crTransparent,        // 透過色(COLORREFまたはパレットインデックス)
    RGN_FUNC *pFunc)            // RGN_FUNC構造体アドレス
{
    HRGN hRgn, hRgnTemp;        // リージョンハンドル
    BYTE *pBit;                 // 現在のピクセルへのポインタ
    DWORD crPixel;              // 現在のピクセルの色
    int x, y;                   // 現在のピクセルの座標
    int xx;                     // 透過色開始位置のx座標を保存
    int nPixelSize;             // 1ピクセルのバイト数

    BYTE *pBitmap = pBmscr->pBit;       // ビットマップデータへのポインタ
    BOOL bPalette = pBmscr->palmode;    // パレットモードかどうか
    int nWidth = pBmscr->sx;            // x サイズ
    int nHeight = pBmscr->sy;           // y サイズ

    // 各関数のアドレス
    HRGN (WINAPI *pfnCreateRectRgn) (int,int,int,int) = pFunc->pfnCreateRectRgn;
    int  (WINAPI *pfnCombineRgn) (HRGN,HRGN,HRGN,int) = pFunc->pfnCombineRgn;
    BOOL (WINAPI *pfnDeleteObject) (HGDIOBJ)          = pFunc->pfnDeleteObject;

    // 1ピクセルのデータサイズ
    if (bPalette)
        nPixelSize = 1;
    else
        nPixelSize = 3;

    // 空のリージョン作成
    hRgn = pfnCreateRectRgn(0,0,0,0);

    // 縦横にビットが透過色かを調べ、透過色でない部分をリージョンに追加
    for (y = 0; y < nHeight; y++) {     // 縦方向
        // ポインタをその行の先頭のピクセルに合わせる
        pBit = pBitmap + (( nHeight - 1 - y ) * nWidth * nPixelSize);

        for (x = 0; x < nWidth; x++) {  // 横方向
            // 現在のピクセルの色を取得
            if (bPalette)
                crPixel = pBit[0];
            else
                crPixel = RGB(pBit[2], pBit[1], pBit[0]);

            // 透過色かどうかを調べる
            if (crPixel != crTransparent) {
                // 透過色ではない場合
                xx = x;     // x を保存(透過色ではない点の始まりを保存)
                            // 連続した透過色ではない領域を求める
                for (x = x + 1; x < nWidth; x++) {
                    // ポインタを次のピクセルへ
                    pBit += nPixelSize;

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

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

                // 一時的な透過色でない領域のリージョンを作成
                hRgnTemp = pfnCreateRectRgn(xx ,y ,x ,y+1);
                // hRgn と合成(ビットマップ全体のリージョンを作成)
                pfnCombineRgn(hRgn, hRgn, hRgnTemp, RGN_OR);
                // 一時的なリージョンを削除
                pfnDeleteObject(hRgnTemp);
            }
            // ポインタを次のピクセルへ
            pBit += nPixelSize;
        }
    }
    return hRgn;
}

ソースコードは、前回までと比べてかなり長くなりましたね。前回までと同様に、コンパイルしてからダンプをして見ましょう。出力されるコードの長さも半端じゃなくなります。

C:\WINDOWS>d:

D:\>cd hspmcn

D:\hspmcn>mcnvc bmprgn

D:\hspmcn>cl /c /O1 bmprgn.c
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 12.00.8804 for 80x86
Copyright (C) Microsoft Corp 1984-1998. All rights reserved.

bmprgn.c

D:\hspmcn>dumpbin /section:.text /rawdata:,16 /out:bmprgn.txt bmprgn.obj
Microsoft (R) COFF Binary File Dumper Version 6.00.8447
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.


D:\hspmcn>strat bmprgn.txt
D:\hspmcn>
(…………………… 省   略 ……………………)

RAW DATA #2
  00000000: 55 8B EC 83 EC 20 8B 45 08 53 56 57 8B 78 0C 8B 
  00000010: 48 14 8B 58 04 8B 40 08 89 7D F8 89 45 FC 8B 45 
  00000020: 10 89 4D F0 F7 DF 8B 08 8B 50 04 8B 40 08 89 4D 
  00000030: E8 1B FF 33 F6 56 56 83 E7 FE 56 56 89 55 E4 89 
  00000040: 45 E0 83 C7 03 FF D1 89 45 10 33 C0 39 75 FC 89 
  00000050: 45 F4 0F 8E A8 00 00 00 8B 75 FC 83 65 08 00 2B 
  00000060: F0 4E 0F AF F7 0F AF F3 03 75 F0 85 DB 0F 8E 80 
  00000070: 00 00 00 83 7D F8 00 74 05 0F B6 0E EB 10 0F B6 
  00000080: 56 02 33 C9 8A 2E 8A 4E 01 C1 E1 08 0B CA 3B 4D 
  00000090: 0C 74 56 8B 4D 08 89 4D EC FF 45 08 39 5D 08 7D 
  000000A0: 22 03 F7 83 7D F8 00 74 05 0F B6 0E EB 10 0F B6 
  000000B0: 56 02 33 C9 8A 2E 8A 4E 01 C1 E1 08 0B CA 3B 4D 
  000000C0: 0C 75 D6 8D 48 01 51 FF 75 08 50 FF 75 EC FF 55 
  000000D0: E8 6A 02 50 FF 75 10 89 45 EC FF 75 10 FF 55 E4 
  000000E0: FF 75 EC FF 55 E0 8B 45 F4 03 F7 FF 45 08 39 5D 
  000000F0: 08 7C 80 40 3B 45 FC 89 45 F4 0F 8C 58 FF FF FF 
  00000100: 8B 45 10 5F 5E 5B C9 C2 0C 00 

  Summary

         10A .text

一言で言えば、長いです。長すぎます。こんなの、一字一字抜き出して変換してられるかってほどに。

根性がある人は、コードを抜き出して変換してみましょう。他にも、矩形の選択範囲を指定できるテキストエディタ(秀丸エディタのBOX範囲選択機能など)ならば、とっても簡単にコード部分だけを抜き出して、前回までのバイナリ変換プログラムに掛けることができてしまいます。最終的にはこうなりますが。

xdim fncode, 67
fncode.0 = $83ec8b55, $458b20ec, $57565308, $8b0c788b, $588b1448, $08408b04
fncode.6 = $89f87d89, $458bfc45, $f04d8910, $088bdff7, $8b04508b, $4d890840
fncode.12 = $33ff1be8, $835656f6, $5656fee7, $89e45589, $c783e045, $89d1ff03
fncode.18 = $c0331045, $89fc7539, $8e0ff445, $000000a8, $83fc758b, $2b000865
fncode.24 = $af0f4ef0, $f3af0ff7, $85f07503, $808e0fdb, $83000000, $7400f87d
fncode.30 = $0eb60f05, $b60f10eb, $c9330256, $4e8a2e8a, $08e1c101, $4d3bca0b
fncode.36 = $8b56740c, $4d89084d, $0845ffec, $7d085d39, $83f70322, $7400f87d
fncode.42 = $0eb60f05, $b60f10eb, $c9330256, $4e8a2e8a, $08e1c101, $4d3bca0b
fncode.48 = $8dd6750c, $ff510148, $ff500875, $55ffec75, $50026ae8, $891075ff
fncode.54 = $75ffec45, $e455ff10, $ffec75ff, $458be055, $fff703f4, $5d390845
fncode.60 = $40807c08, $89fc453b, $8c0ff445, $ffffff58, $5f10458b, $c2c95b5e
fncode.66 = $0000000c

ただ、これだとスクリプトが少々長くなってしまいますし、メモ帳などのように矩形選択機能のないエディタを使ってる人にはどうしようもありませんね。そこで、今回はちょっとズルをすることにします。

ソースコードをコンパイルすると、オブジェクトファイル(拡張子 .obj )が出力されます。このオブジェクトファイルには、上のマシン語コードがバイナリ形式で含まれているので、それをそのまま使用することにします。ただし、このオブジェクトファイル全体が作成した関数のコードというわけではなく、実際の関数コードはこのファイルの一部に過ぎません。したがって、このファイルの中から関数コード部分を見つけ出して、その部分を抜き出してくる必要があります。

まずは、オブジェクトファイルをバイナリエディタで開きましょう。基本的にはどのバイナリエディタでも問題ないと思います。ちなみに、筆者は Stirling を使用しています。

本来なら、オブジェクトファイルのヘッダ部分から順にたどっていかなくてはならないのですが、なにぶん面倒ですよね。そこで、上にリスティングファイルを見てみると、関数コードの先頭部分が 55 8B EC 83 EC 20 8B 45 …… となっていることが分かりますね。そして、一番最後に書かれている『10A .text』の表示は、このコード全体のサイズが $10A (266) バイトあることを表わしています。

そこで、オブジェクトファイルをバイナリエディタで開いたら、いきなり 55 8B EC 83 EC 20 8B 45 を検索してしまいましょう。(検索時のデータ種別は文字列ではなく16進データ(バイナリ形式)で。) すぐに見つかると思います。2つ以上見つかることはまずないと思いますが、もしも見つかった場合は、その後に続くコードから、どちらが該当するコードなのかを判断しましょう。

コードの先頭が見つかったら、そこから 266 バイトまでの領域を選択します。たいていのバイナリエディタでは、選択領域のサイズがステータスバーに表示されると思いますので、それを見ながら行ないましょう。(10進数と16進数を間違えないように。) 少し余裕を持たせて多めに選択してしまってもかまいません。

後は、その部分をコピーしてから、新しいファイルとして貼り付けて保存すれば OK です。 Stirling を使用している場合は、選択領域を右クリックするとメニューが表示されるので、その中の「選択領域をファイルに保存」でも行なうことができます。ファイル名は何でもいいですが、ここでは rgn.mcn という名前を付けて保存することにします。


これで、マシン語の関数コードを含むファイルが作成されました。あとは、 HSP 側でこのファイルをいったん変数に呼び込んでから、この関数コードを呼び出せばいいのです。


実際にサンプルを書いてみましょう。ビットマップファイル test.bmp を各自で準備してください。このビットマップの、白で表示される部分を透明化して表示します。

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

    screen 0, 300, 200

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

    ; 関数コードの読み込み  
    xdim fncode, 67
    bload "rgn.mcn", fncode, 266

    ; 関数アドレス取得
    getptr pfunc, fncode

    ; RGN_FUNC 構造体にAPI関数アドレスを格納
    dll_getfunc rgnfunc.0, "CreateRectRgn", D_GDI
    dll_getfunc rgnfunc.1, "CombineRgn",    D_GDI
    dll_getfunc rgnfunc.2, "DeleteObject",  D_GDI

    ; 指定された色を透過色としてリージョン作成
    mref bmscr, 67              ; BMSCR構造体を割り当てる
    getptr pm.0, bmscr          ; BMSCR構造体のアドレス
    pm.1 = $FFFFFF              ; 透過色を白に指定(RGB値 $FFFFFF)
    getptr pm.2, rgnfunc        ; RGN_FUNC構造体のアドレス
    ll_callfunc pm, 3, pfunc    ; 関数コード呼び出し
    hRgn = dllret               ; 作成されたリージョンハンドル

    ; リージョンをウィンドウに割り当てる
    pm = bmscr.13, hRgn, 1
    dllproc "SetWindowRgn", pm, 3, D_USER
    stop