DDB→DIB変換

概要:DDBの描画内容をDIBにコピーする

DDBからDIBに変換する必要があるのなら最初からDIBSectionで作成しろーってのは
前節でも言いましたが、DDBとDIBの相互変換ができれば、色々な便利関数の作成に役立つかもしれません。

DDBからDIBの変換にもDIBSectionを仲介させる方法は使えますが、
前節でやったように、もっと直接的な関数を使った方法を解説します。

■GetDIBits 関数

GetDIBits 関数はDDBの描画内容を既存のDIBにコピーします。
前節の SetDIBits 関数と対になる関数です。

int GetDIBits(
  HDC hdc,         // デバイスコンテキストのハンドル
  HBITMAP hbmp,     // ビットマップのハンドル
  UINT uStartScan,    // 取得対象の最初の走査行
  UINT cScanLines,    // 取得対象の走査行の数
  LPVOID lpvBits,     // ビットマップのビットからなる配列
  LPBITMAPINFO lpbi,  // ビットマップデータのバッファ
  UINT uUsage       // RGB とパレットインデックスのどちらか
);


uStartScan , uScanLines はコピーする縦の範囲です。
lpbi はDIBのBITMAPINFO構造体を指すポインタです。
 関数はこの構造体からDIBの情報を知る事ができるというわけです。

ピクセル列を指すポインタ lpvBits に NULL を指定すると、
ビットマップのサイズと形式が、lpbi パラメータが指すBITMAPINFO構造体に格納される
……らしいのですが、何故か失敗します。
どなたか成功した方がいらっしゃいましたら、ご報告願います。m(_ _)m

また、hbmp パラメータが示すビットマップがデバイスコンテキストで選択されている場合、
この関数を呼び出さないでください……とありますが、選択されていても成功しました(私が試した限りでは)。
しかし、気にする方もいらっしゃると思うので、以下のプログラムでは選択を解除しています。

色々と癖の多い関数ですね。
それでは、GetDIBits 関数を実行するあたりまで見ていきましょう。
コピー先となるDIBは32ビットで、大きさはDDBと同じにします。

#define WIDTH   197
#define HEIGHT  100

HDC hdc;
static LPDWORD lpPixel;
static BITMAPINFO bmpInfo;
static HDC hMemDC;
HBITMAP hBitmap,hOldBitmap;

//DDB作成
hdc=GetDC(hWnd);
hMemDC=CreateCompatibleDC(hdc);
hBitmap=CreateCompatibleBitmap(hdc,WIDTH,HEIGHT);

//灰色の四角形を左半分に描画
hOldBitmap=(HBITMAP)SelectObject(hMemDC,hBitmap);
SelectObject(hMemDC,GetStockObject(GRAY_BRUSH));
Rectangle(hMemDC,0,0,WIDTH/2,HEIGHT);
SelectObject(hMemDC,hOldBitmap);

//DIB作成
lpPixel=(LPDWORD)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,WIDTH*HEIGHT*4);
bmpInfo.bmiHeader.biSize=sizeof(BITMAPINFOHEADER);
bmpInfo.bmiHeader.biWidth=WIDTH;
bmpInfo.bmiHeader.biHeight=HEIGHT;
bmpInfo.bmiHeader.biPlanes=1;
bmpInfo.bmiHeader.biBitCount=32;
bmpInfo.bmiHeader.biCompression=BI_RGB;

//DDB→DIB変換
GetDIBits(hdc,hBitmap,0,HEIGHT,lpPixel,&bmpInfo,DIB_RGB_COLORS);
SelectObject(hMemDC,hBitmap);
DeleteObject(hBitmap);
ReleaseDC(hWnd,hdc);

DDBに描画する為に、デバイスコンテキストに選択していますが、
その時に、以前のビットマップハンドルを保存しておきます。
そして、DDBの選択を解除したくなったら、以前のビットマップハンドルを選択します。
直接に選択を解除する関数はないと思われるので、
このようにして、解除したいオブジェクトを同種の別のオブジェクトで押し出すのです。

これで、左半分が灰色、右半分が黒のDDBと、
その描画内容をコピーしたDIBができあがりました。
次に、それぞれについて更に描画してみましょう。
どちらのビットマップもまだ有効であるという事を証明する為です。

int x,y;

for(y=25;y<50;y++){    //DIBに描画
    for(x=25;x<50;x++){
        lpPixel[x+y*WIDTH]=0x00ff0000;
    }
}
SelectObject(hMemDC,GetStockObject(WHITE_BRUSH));
Rectangle(hMemDC,25,25,50,50);    //DDBに描画

■表示&削除

表示&削除の方法はいつもと同じです。

case WM_PAINT:
    hdc=BeginPaint(hWnd,&ps);
    BitBlt(hdc,0,0,WIDTH,HEIGHT,hMemDC,0,0,SRCCOPY);    //DDBを表画面へ転送
    StretchDIBits(hdc,250,0,WIDTH,HEIGHT,    //DIBを表画面へ転送
        0,0,WIDTH,HEIGHT,lpPixel,&bmpInfo,DIB_RGB_COLORS,SRCCOPY);
    EndPaint(hWnd,&ps);
    return 0;
case WM_DESTROY:
    DeleteDC(hMemDC);    //DDB削除
    HeapFree(GetProcessHeap(),0,lpPixel);    //DIB削除
    PostQuitMessage(0);
    return 0;

★☆ ダウンロード ☆★

24ビットDIBの場合については、32ビットDIBの場合と同じなので、ソースファイルをご覧下さい。

■8ビットDIBの場合

DDBの描画内容を8ビットDIBにコピーするのは実用的ではありません。
減色によって情報が失われてしまうからです。

以下に実験プログラムを示します。

#define WIDTH   197
#define HEIGHT  100

LRESULT CALLBACK WindowProc(HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;
    static LPBYTE lpPixel;
    static LPBITMAPINFO lpBmpInfo;
    static HDC hMemDC;
    HBITMAP hBitmap,hOldBitmap;
    int x,y;
    static int length;

    switch(uMsg) {
        case WM_CREATE:
            //DDB作成
            hdc=GetDC(hWnd);
            hMemDC=CreateCompatibleDC(hdc);
            hBitmap=CreateCompatibleBitmap(hdc,WIDTH,HEIGHT);

            //グラデーションで塗りつぶす
            hOldBitmap=(HBITMAP)SelectObject(hMemDC,hBitmap);
            for(y=0;y<HEIGHT;y++){
                for(x=0;x<WIDTH;x++){
                    SetPixel(hMemDC,x,y,RGB(x%256,0,0));
                }
            }
            SelectObject(hMemDC,hOldBitmap);

            //4の倍数に補正
            if(WIDTH%4) length=WIDTH+(4-WIDTH%4);
            else length=WIDTH;

            //DIB作成
            lpPixel=(LPBYTE)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,
                length*HEIGHT+sizeof(BITMAPINFOHEADER)+256*4);
            lpBmpInfo=(LPBITMAPINFO)(lpPixel+length*HEIGHT);
            lpBmpInfo->bmiHeader.biSize=sizeof(BITMAPINFOHEADER);
            lpBmpInfo->bmiHeader.biWidth=WIDTH;
            lpBmpInfo->bmiHeader.biHeight=HEIGHT;
            lpBmpInfo->bmiHeader.biPlanes=1;
            lpBmpInfo->bmiHeader.biBitCount=8;
            lpBmpInfo->bmiHeader.biCompression=BI_RGB;

            //DDB→DIB変換
            GetDIBits(hdc,hBitmap,0,HEIGHT,lpPixel,lpBmpInfo,DIB_RGB_COLORS);
            SelectObject(hMemDC,hBitmap);
            DeleteObject(hBitmap);
            ReleaseDC(hWnd,hdc);

            for(y=25;y<50;y++){    //DIBに描画
                for(x=25;x<50;x++){
                    lpPixel[x+y*length]=( (x-25)+(y-25)*25 )%256;
                }
            }
            Rectangle(hMemDC,25,25,50,50);    //DDBに描画
            return 0;
        case WM_DESTROY:
            DeleteDC(hMemDC);    //DDB削除
            HeapFree(GetProcessHeap(),0,lpPixel);    //DIB削除
            PostQuitMessage(0);
            return 0;
        case WM_PAINT:
            hdc=BeginPaint(hWnd,&ps);
            BitBlt(hdc,0,0,WIDTH,HEIGHT,hMemDC,0,0,SRCCOPY);    //DDBを表画面へ転送
            StretchDIBits(hdc,250,0,WIDTH,HEIGHT,    //DIBを表画面へ転送
                0,0,WIDTH,HEIGHT,lpPixel,lpBmpInfo,DIB_RGB_COLORS,SRCCOPY);
            EndPaint(hWnd,&ps);
            return 0;
    }
    return DefWindowProc(hWnd,uMsg,wParam,lParam);
}

近似色の検索と置き換えが行われている事が確認できます。

問題はカラーテーブルの中身ですが、
DDBの描画内容に関係なく、標準カラーテーブルのようなものが割り当てられるみたいです。

カラーテーブル全てを使って描いている四角形を10倍に拡大したのが下図です。



規則性がある事から、ノイズ、または不正な書き込みという可能性はありません。
また、DDBの描画内容を変えても同じになる事から、DDBの描画内容を考慮していない事も明白です。
これで、DDBの描画内容を考慮して適切な色をカラーテーブルに割り当ててくれれば、良かったんですけどね……。

完全なソースファイルは先程のリンク先にあります。

ところで、SetPixel 関数を仕方なく使っていますが、
激遅なので、頻繁に書き換える処理では使わないで下さい。


戻る / ホーム