24ビットDIBを作る

概要:24ビットDIBの作成と注意

24ビットDIBはちょっと扱いづらいのですが、
32ビットDIBの無駄な1バイトを節約できるという利点があります。
ファイルに保存する時も24ビットを使うのが普通です。

■ビットマップの制約

24ビットDIBの作り方は32ビットDIBと同じなのですが、
32ビットDIBの時は意識しなくてもよかったビットマップの制約を意識する必要があります。
その制約とは「横幅が4の倍数バイトでなければならない」というものです。
1画素32ビット(4バイト)なら必ず横幅も4の倍数バイトになるのですが、
1画素24ビットでは必ずしもそうなりません。
ただし、半端な横幅のビットマップを使うことができないという意味ではありません。

■横幅を補正する

制約を正確に言えば「ピクセル列の一行分のバイト数が4の倍数でなければならない」です。
例えば、横幅 197 ピクセルの24ビットDIBを使いたいとします。
しかし 197*3=591 は4の倍数バイトではないので、これを 592 バイトに補正します。
592 という値は一行分のバイト数を表しています。

ピクセル列の一行分のバイト数は4の倍数に補正されているものとして解釈されます。
つまり、今の例では行末の余分な 1 バイトの存在を知りながら無視してくれます。
本来の値と補正された値は BITMAPINFOHEADER の
横幅 197 ピクセル、24ビットから知る事ができるでしょう。

本来の値と補正した値の使い分けに注意して下さい。
補正された値を使うのはピクセル列のアドレス計算をする時だけです。

#define WIDTH       197
#define HEIGHT      100
#define BITCOUNT    24

static LPBYTE lpPixel;
static BITMAPINFO bmpInfo;
static int length;

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

lpPixel=(LPBYTE)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,length*HEIGHT);
//DIBの情報を設定する
bmpInfo.bmiHeader.biSize=sizeof(BITMAPINFOHEADER);
bmpInfo.bmiHeader.biWidth=WIDTH;
bmpInfo.bmiHeader.biHeight=HEIGHT;
bmpInfo.bmiHeader.biPlanes=1;
bmpInfo.bmiHeader.biBitCount=BITCOUNT;
bmpInfo.bmiHeader.biCompression=BI_RGB;

//表画面へ転送
StretchDIBits(hdc,0,0,WIDTH,HEIGHT,
    0,0,WIDTH,HEIGHT,lpPixel,&bmpInfo,DIB_RGB_COLORS,SRCCOPY);

■リトルエンディアン

ピクセル列が1バイト型なので、
色の指定はRGB成分ごとに別々に行うことになります。
3倍しているのも lpPixel が1バイト型だからです。
またピクセル列のアドレス計算に WIDTH ではなく length を用いている事に注意して下さい。
ビットマップ自体は横幅 length まであるからです。
でも必要なのは WIDTH までなので、描画範囲は WIDTH 未満です。

//描画
for(y=0;y<HEIGHT;y++){
    for(x=0;x<WIDTH;x++){
        lpPixel[(x*3  )+y*length]=0;            //B
        lpPixel[(x*3+1)+y*length]=0;            //G
        lpPixel[(x*3+2)+y*length]=x%256;        //R
    }
}

もしRGB成分をいっぺんに指定したいなら CopyMemory 関数を使えばいいでしょう。

int x,y,r,g,b,rgb;

//描画
for(y=0;y<HEIGHT;y++){
    for(x=0;x<WIDTH;x++){
        r=x%256; g=0; b=0;
        rgb=(r<<16)+(g<<8)+b;
        CopyMemory(lpPixel+(x*3)+y*length,&rgb,3);
    }
}

この二つを比べるとRGB成分の順序が逆になっている気がしませんか?
実は同じなのですが、これがややこしい。

複数バイトから構成される値において、
上位バイトを下位アドレスに格納するか(リトルエンディアン)、
上位アドレスに格納するか(ビッグエンディアン)
という二つの方法があります。
素直なのはビッグエンディアンで、0x11223344 を 0x11223344 のままメモリーに格納します。
しかしリトルエンディアンは 0x11223344 を 0x44332211 としてメモリーに格納します。
人間に理解しやすいのはビッグエンディアンです。
しかし Intel & AMD のCPUはリトルエンディアンです。
従って随所でプログラムが変換してくれているのですが、
複数バイトをあえて分割してバイトごとに値を格納する場合は、
リトルエンディアンの順番で値を格納しなければなりません。

○数値: 0x00112233 0x44556677
○メモリーの内部状態: 33221100 77665544
(右の方がアドレスが大きい)

ピクセル列の解放の仕方は同じです。

★☆ ソースファイル表示 ☆★


戻る / ホーム