Win32
ビットマップからリージョンを作る


HomeProgramming TipsWin32SDK Tips[SDK-001]
※ 2005/11/03 メモリリークバグ/RGB取り間違えミス修正しました。

ビットマップからリージョンを作成するには様々な方法が提案されていますが、
どの方法を選択するにしても、ある程度長いプログラムになってしまうことは避けられません。
直接ビットマップからリージョンを作成する方法がないため仕方がないところです。

今回、このビットマップリージョンの解説のため、左の画像を使用します。

画像の著作権(pochi.gif / pochi.bmp)
かねき 富士塗料店 / Kaneki Fuji Paint Store
http://www5a.biglobe.ne.jp/~lowning8/index.htm

考え方としては、一行ずつ、透明と非透明部分の変化点を抽出します。
そして非透明部分の長さが分かったら高さが1の四角形リージョンして積算します。

さて、ポチの頭の部分に着目して理屈を解説していくことにしましょう。
こいつの頭は以下のようになっています。

最初の行は全て透明であるため、リージョンは作りません。
次の行も同じく作る必要は無いです。
さて、3行目です。透明部分が X=14 で途切れました。
この場所を記憶します。
次に透明になるまで検索します。
この場合は X=16 で透明になりました。

四角形を (x1, y1, x2, y2) で表すと、(14, 2, 16, 3) という四角形が非透明部分です。
※この座標系は説明用であり実際の座標とは異なります。

この四角形をリージョンとして登録します。そして、その積算された RECT を元に
ExtCreateRegion() で HRGN を取り出します。

このリージョンハンドルを元に SetWindowRgn() にてウィンドウ形状を切り出せば OK です。

さて、画像をくまなくスキャンしなければならないことは分かりましたが、問題は実行速度です。
普通に GetPixel() とかすると、かなり遅いです。

デスクトップマスコットを作ろうと思ったら高速化が必要です。
そこで、bmp 画像を1ラインずつ DIB 配列として抜き出すことにします。
32bit カラーとして抜き出してやれば、あとは、配列の添え字変更で色情報が読み出せます。
そのために GetDIBits() という API を使います。

なお、ウィンドウの左上から切り出しますので、ベースとなるウィンドウはダイアログとして、
そのプロパティは「とことんFALSE」設定がよいかと思います。唯一 Visible だけが TRUE ですね。
Style はポップアップとしています。Border もなしです。
Application Window はサンプルでは TRUE にしていますが、好みによって使い分けてください。

サンプルでは形状が変わるたびに CreateRgnFromBitmap() を呼び出していますが、
本当に実行速度を優先したいのであれば、RECT を積み重ねたデータを画像毎に覚えておき、
画像が切り替わる時に、その RECT を元に ExtCreateRegion() を呼び出してやれば、
おそらく、ビットマップリージョン処理の考えうる最高速度になります。
※ OSVVで実装しました。

 ダウンロード : ビットマップリージョンサンプル(2005/11/03) 

※ご注意!
透明色指定はビットマップ情報ですので、0x00RRGGBB が必要となります。


// ------------------------------------------------------------------
// ビットマップ形状に合わせてリージョンを作成する
// ------------------------------------------------------------------
HRGN CtestBitmapRgnDlg::CreateRgnFromBitmap(HBITMAP hBitmap, DWORD dwTransColor)
{
    // リージョンハンドル
    HRGN hRgn = NULL;

    // 四角形リージョンの数
    int nCntRect = 0;

    // 画面と同じデバイスコンテキストを作成する
    HDC hDC = CreateCompatibleDC(NULL);
    if (!hDC) return NULL;

    // ビットマップを取得する
    BITMAP bm;
    GetObject(hBitmap, sizeof(BITMAP), &bm);

    // 最大要素数で動的配列を作る
    LPRGNDATA  pRgnData  = (LPRGNDATA)new BYTE[sizeof(RGNDATAHEADER) + sizeof(RECT) * bm.bmWidth * bm.bmHeight];
    LPCOLORREF pScanData = new COLORREF[bm.bmWidth];
    if (pRgnData && pScanData){
        LPRECT pRect = (LPRECT)pRgnData->Buffer;
        BITMAPINFOHEADER bi;                            // ビットマップ情報を作る
        ZeroMemory(&bi, sizeof(BITMAPINFOHEADER));
        bi.biSize        = sizeof(BITMAPINFOHEADER);
        bi.biWidth       = bm.bmWidth;
        bi.biHeight      = bm.bmHeight;
        bi.biPlanes      = 1;
        bi.biBitCount    = 32;                          // 扱うのは 32bit カラー
        bi.biCompression = BI_RGB;
        for (int y = 1; y < bm.bmHeight; y++){

            // 一行分画像データを DIB で取り出す(bmpなので下から取り出す)
            GetDIBits(hDC, hBitmap, bm.bmHeight - y, 1, pScanData, (LPBITMAPINFO)&bi, DIB_RGB_COLORS);

            // 一行分の透明/非透明の変化点を調査する
            for (int x = 0; x < bm.bmWidth; x++){
                if (pScanData[x] != dwTransColor){                  // 透明色でなければ
                    pRect->left = x;                                // その位置から
                    for (; x < bm.bmWidth; x++)                     // 透明までの
                        if (pScanData[x] == dwTransColor) break;    // 長さをカウントする
                    pRect->right  = x;                              // その情報を四角形情報として追加する
                    pRect->top    = y - 1;
                    pRect->bottom = y;
                    pRect++;
                    nCntRect++;
                }
            }
        }
        // 書きためた四角形情報を元にリージョンを作成する
        pRgnData->rdh.dwSize         = sizeof(RGNDATAHEADER);
        pRgnData->rdh.iType          = RDH_RECTANGLES;
        pRgnData->rdh.nRgnSize       = sizeof(RGNDATAHEADER) + sizeof(RECT) * nCntRect;
        pRgnData->rdh.nCount         = nCntRect;
        pRgnData->rdh.rcBound.top    = 0;
        pRgnData->rdh.rcBound.left   = 0;
        pRgnData->rdh.rcBound.bottom = bm.bmHeight;
        pRgnData->rdh.rcBound.right  = bm.bmWidth;
        hRgn = ExtCreateRegion(NULL, pRgnData->rdh.nRgnSize, pRgnData);
    }
    delete[] pScanData;  //** 2005/11/03 バグ修正
    delete[] pRgnData;   //** 2005/11/03 バグ修正
    DeleteDC(hDC);

    return hRgn;
}




 Copyright 2005 VALGUS. All Rights Reserved.