1.DIB の使い方

DIB は、CreateDIBSection() で、空の DIB を作って操作する。
例えば、以下のように記述すると、幅 640px、高さ 480px、色深度 32 ビットで、
背景が黒一色の DIB が作成される。
これは、作成されるだけで、表示はされない。
高さが、マイナスになっているのは、DIB が下から上に描画される仕組みになって
いるため、これをマイナスにすると上から下に表示されるようになって各ピクセルを
操作するのに都合が良いからだ。
ただし、DIB をファイルに保存する場合は、符号をプラスに変える必要がある。

BITMAPINFO bi;
LPBYTE     buf;

ZeroMemory(&bi, sizeof(bi));
bi.bmiHeader.biSize        = sizeof(BITMAPINFOHEADER);
bi.bmiHeader.biWidth       = 640;
bi.bmiHeader.biHeight      = - 480;
bi.bmiHeader.biPlanes      = 1;
bi.bmiHeader.biBitCount    = 32;
bi.bmiHeader.biCompression = BI_RGB;

HBITMAP hBmp1 = (HBITMAP) CreateDIBSection(NULL, &bi, DIB_RGB_COLORS, (VOID**) &buf, NULL, 0);

メモリデバイスコンテキストを使うと、この DIB に描画が出来る。
LoadImage() で、ビットマップファイルを読み込み、CreateCompatibleDC() で、
それぞれの DIB 用のメモリデバイスコンテキストを作成し、それに、
SelectObject() で DIB を関連付ける。
その後、BitBlt() で、画像ファイルの DIB を 上で作成した黒背景の DIB に描画する。
これは、DIB に描画しただけだから、まだ、描画されない。
使い終わった メモリデバイスコンテキストとコピーして必要が無くなった DIB は削除する。
使い終わった GDIオブジェクトは、削除しないとメモリリークになる。

メモリリークとは、確保したメモリの解放忘れの事だ。
ソフトは、動作をするためにパソコンのメモリを必要とするのだが、このメモリは、
予め確保しないと使えない。
例えば、1メガバイトのメモリを確保したら、そのソフトだけが、そのメモリ領域を使える
ようになるのだが、使い終わった後、そのメモリ領域を解放しなかったら、OS も他のソフトも
使えない領域ができる事になり、これが、累積すると、OS や他のソフトが使えるメモリ領域が
減って、動作不良を起こすようになる。
このメモリリークは、そのソフトが終了した後も残り、パソコンを再起動するまで解放されない。
そのため、滅多に電源を落とさない WEB サーバなどでは、メモリリークは特に危険視される。

HBITMAP	hBmp2 = (HBITMAP) LoadImage(NULL, "sample.bmp", IMAGE_BITMAP, 0, 0,
                                   LR_LOADFROMFILE | LR_CREATEDIBSECTION);
DIBSECTION dib;
GetObject(hBmp2, sizeof(dib), &dib);

HDC     hdcCp1,hdcCp2;
HBITMAP hbmOld1,hbmOld2;

hdcCp1 = CreateCompatibleDC(NULL);
hdcCp2 = CreateCompatibleDC(NULL);
hbmOld1 = (HBITMAP)SelectObject(hdcCp1, hBmp1);
hbmOld2 = (HBITMAP)SelectObject(hdcCp2, hBmp2);
BitBlt(hdcCp1, 0, 0, dib.dsBmih.biWidth, dib.dsBmih.biHeight, hdcCp2, x, y, SRCCOPY);
SelectObject(hdcCp1, hbmOld1);
SelectObject(hdcCp2, hbmOld2);
DeleteDC(hdcCp1);
DeleteDC(hdcCp2);
DeleteObject(hBmp2);

ここで、とりあえず、DIB をウィンドウに表示する。
GetDC() で、表示したいウィンドウのデバイスコンテキストを取得し、後は、
先ほどと同じ要領で描画する。
デバイスコンテキストも使用後は、削除する。
デバイスコンテキストに描画すると、表示されはするが、動作が遅いため、
通常は、メモリデバイスコンテキストに描画し、最後にデバイスコンテキストに描画するようにする。
ここでは、確認のために描画した。

HDC hDC = GetDC(hWnd);
HDC     hdcCp;
HBITMAP hbmOld;

hdcCp = CreateCompatibleDC(NULL);
hbmOld = (HBITMAP)SelectObject(hdcCp, hBmp1);
BitBlt(hdc, 0, 0, dib.dsBmih.biWidth, dib.dsBmih.biHeight, hdcCp, x, y, SRCCOPY);
SelectObject(hdcCp, hbmOld);
DeleteDC(hdcCp);
ReleaseDC(hDC);

次に、メモリデバイスコンテキストを使わずに、直接、DIB の各ピクセルを操作してみる。
赤一色で、DIB を塗りつぶす事にする。
DIB のピクセル値は、GetObject() で取得される DIBSECTION 構造体の dsBm.bmBit に入っている。
これは、ピクセル値が入っているメモリ領域のポインタだ。
確保されたメモリ領域は、ポインタという変数が使われるのだが、これは、値とアドレスの2つの値を持つ。

例えば、以下の pb の場合は、pb が先頭アドレスで、*pb がその先頭アドレスに入っている値になる。
ポインタは、配列みたいなもので、値が、配列の値で、アドレスは、配列の添え字である。
ただし、ただの添え字ではなく、メモリのアドレスだ。
これは、メインメモリやハードディスクなどの記憶領域に直接、つけられている物理アドレスではなく、
OS が利用する仮想アドレスだ。
物理アドレスに、OS が使いやすいように別のアドレスを付けているのだ。
確保されたメモリには、必ず、その最初から最後の仮想アドレスまで、連続した番号が付いている。
整数型変数のインクリメントは、値が増えるが、アドレスのインクリメントは、アドレスが次に移る。
これは、仮想アドレスがインクリメントされているのではなく、ポインタ変数 pb が記憶している
仮想アドレスがインクリメントされるのだ。
pb は、最初、先頭アドレスだったが、インクリメントすると、次のアドレスに移るから、pb は
先頭アドレスではなくなる。
例えば、(*pb) ++ とすると、例えば、*pb に 5 という値が入っていた場合、アドレスはそのままで、
値が、6 になる。
しかし、*pb ++ とすると、これは、*(pb ++) の事だから、アドレスがインクリメントされている事になる。
配列で言えば、a[0] が、a[1] になるようなものだ。
例えば、*pb に、15 という数値が入っていて、*(pb + 1) に、20 という数値が入っているとしたら、
*pb ++ とすると、*pb には、20 という数値が入っている事になる。
このアドレスのインクリメントの増加幅は、そのポインタのデータ型で決まる。
下の例では、BYTE 型にしているから、インクリメントするごとに、1バイト次のアドレスに移る事になる。
32 ビット DIB のピクセル値は、透明度、赤色、緑色、青色の順に並んでおり、それぞれ、1バイトのメモリに
格納されているから、インクリメントするごとに、透明→赤→緑→青→透明・・・とアドレスが移る事になる。
つまり、dib.dsBmih.biWidth * dib.dsBmih.biHeight * 4 回インクリメントすれば、全てのピクセル値を
回った事になる。
再び、先頭アドレスから、ピクセル値を操作して行きたいときは、もう一度、ポインタを宣言し直す必要がある。
例えば、BYTE *pb2 など。

int x,y;

BYTE *pb = (BYTE*) dib.dsBm.bmBits;
for(y = 0; y < dib.dsBmih.biHeight; y++){
    for(x = 0; x < dib.dsBmih.biWidth; x ++){
        *pb = 0;   // 透明度
        pb ++;
	*pb = 255; // 赤色
        pb ++;
	*pb = 0;   // 緑色
        pb ++;
	*pb = 0;   // 青色
        pb ++;
    }
}

1バイト×4=4バイト=32ビット=DWORD型だから、32ビットDIBの場合は、
BYTEの代わりにDWORD型が使える。
1ピクセルが、4バイトで、ARGBの順に並び、10進数の255は、16進数のFFだから、
赤色は、0x00FF0000になる。
もちろん、DWORD型ポインタのアドレスは、インクリメントすると、4バイトずつ増える。
dib.dsBmih.biWidth * dib.dsBmih.biHeight 回インクリメントすれば、全てのピクセルを回れる。

int x,y;

DWORD *pb = (DWORD*) dib.dsBm.bmBits;
for(y = 0; y < dib.dsBmih.biHeight; y++){
    for(x = 0; x < dib.dsBmih.biWidth; x ++){
        *pb = 0x00ff0000;
        pb ++;
    }
}

これも、確認のため、先ほどと同様にデバイスコンテキストに描画する。
ビットマップハンドルは、使い終わったら、削除する。

DeleteObject(hBmp1);

2.

 

3.

 

4.


5.