DIB ファイルヘッダ
デバイス独立ビットマップ
以前、ビットマップを扱った時に DDB について説明しました
DDB はプログラムがグラフィカルな動作を行うために必須であり
メモリにビットマップを作成して描画する手段を提供します
しかし、DDB はデバイスに強く依存します
そのため、通常はディスクファイルに書き出すようなことはしません
(例えディスクに保存しても、それは他のデバイスでは意味のない情報となる)
そこで、デバイスに依存しない形でビットマップをファイルとして保存するために
Windows 3.0 で導入された DIB (device-independent bitmap) を使います
DIB デバイス独立ビットマップは、デバイスに依存せずに画像データを保存する手段です
私たちがディスクに保存していた拡張子 .BMP の画像データは DIB です
DIB の誕生は Microsoft と IBM が仲良しのころに共同制作した OS/2 が起源です
Adobe Photoshop でビットマップを保存するときの選択に OS/2 があるのはこのためです
Windows はこれを導入し、さらに新たな拡張をプラスしています
複雑な DIB の歴史を歩むのは多少面倒ではありますが
OS/2 時代からのファイル仕様を一つずつ見ていきましょう
これがわからなければ、Windows の本質的なグラフィック処理が見えません
そのため、この章の多くはプログラムではなくビットマップのバイナリ操作を解説します
さて、最初に開発された OS/2 の DIB の解説から始めましょう
現在の DIB もこの OS/2 スタイルの DIB を拡張したものなので、これは重要です
拡張された DIB も含め、DIB ファイルは次のような構造になっています
- ファイルヘッダ
- 情報ヘッダ
- RGB カラーテーブル
- ピクセルビット
それぞれのバイナリデータは、構造体、または構造体の配列で表現されます
まず、ファイルヘッダの構造体から見てみましょう
C言語では、ファイルヘッダは BITMAPFILEHEADER 構造体で表現されます
typedef struct tagBITMAPFILEHEADER { // bmfh
WORD bfType;
DWORD bfSize;
WORD bfReserved1;
WORD bfReserved2;
DWORD bfOffBits;
} BITMAPFILEHEADER;
bfType はビットマップファイルを示す情報で、"BM" という2バイトの文字です
つまり、ASCII コードの 0x4D42 となります
Intel x86 系 CPU はリトルエンディアンなので注意してください
リトルエンディアンとは、処理単位の下位バイトから順にアドレスが割り当てられるもので
AB CD というデータは、リトルエンディアンでは CD AB と逆に格納されるのです
バイト単位で扱えば B は 0x42、M は 0x4D ですが、ワード単位の場合は 0x4D42 となります
bfSize にはファイル全体のサイズがバイト単位で格納されます
bfReserved1 と bfReserved2 は 0 でなければなりません
bfOffBits には、ピクセルビットが格納されているアドレスをオフセットで表します
さて、次のバイナリデータは OS/2 DIB 仕様の 10 * 10 ピクセルのモノクロビットマップです
ファイルの先頭 14 バイトをれば、このファイルサイズなどがわかります
アドレス | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F
|
---|
00000000 | 42 | 4D | 48 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 20 | 00 | 00 | 00 | 0C | 00
|
---|
00000010 | 00 | 00 | 0A | 00 | 0A | 00 | 01 | 00 | 01 | 00 | FF | FF | FF | 00 | 00 | 00
|
---|
00000020 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00
|
---|
00000030 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00
|
---|
00000040 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00
|
---|
これは、Intel 486 互換 CPU でのバイナリデータです
最初の2バイトはビットマップであることを表す 0x424D であることが確認できますね
その次のダブルワードはファイルサイズ全体のバイト数が格納されています
オフセット 03 番地から 05 までの 0x48000000 がファイルサイズを表します
リトルエンディアンなので、逆にひっくり返して 0x48 がファイルサイズになります
この次の4バイトは、必ず 0 でなければなりません
そして、0A 番地から 0D までは、ピクセルビットが格納されているオフセットが格納されます
このデータでは 0x20000000 となっているので、0x20 がピクセルビットの先頭アドレスです
BITMAPFILEHEADER 構造体に各情報を振り分けるとわかりやすいでしょう
以下のように、色分けをしてみました
typedef struct tagBITMAPFILEHEADER { // bmfh
WORD bfType;
DWORD bfSize;
WORD bfReserved1;
WORD bfReserved2;
DWORD bfOffBits;
} BITMAPFILEHEADER;
アドレス | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F
|
---|
00000000 | 42 | 4D | 48 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 20 | 00 | 00 | 00 | 0C | 00
|
---|
00000010 | 00 | 00 | 0A | 00 | 0A | 00 | 01 | 00 | 01 | 00 | FF | FF | FF | 00 | 00 | 00
|
---|
00000020 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00
|
---|
00000030 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00
|
---|
00000040 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00
|
---|
このようになっています、これがビットマップのファイルヘッダです
ファイルヘッダのバイナリレベルの仕組みがわかれば、用意された関数に依存せずに
独自にこれを解析して、ビットマップの情報を取得できます
次のプログラムは、コマンドライン引数で指定したビットマップのファイルヘッダを表示します
#include <windows.h>
BITMAPFILEHEADER bmpFileHeader;
LRESULT CALLBACK WndProc(HWND hWnd , UINT msg , WPARAM wp , LPARAM lp) {
HDC hdc;
PAINTSTRUCT ps;
RECT rect;
static TCHAR chStr[1024];
switch (msg) {
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_CREATE:
wsprintf(chStr ,
TEXT("bfType = %X\nbfSize = %X\nbfReserved1 = %X\n")
TEXT("bfReserved2 = %X\nbfOffBits = %X") ,
bmpFileHeader.bfType , bmpFileHeader.bfSize ,
bmpFileHeader.bfReserved1 , bmpFileHeader.bfReserved2 ,
bmpFileHeader.bfOffBits
);
return 0;
case WM_PAINT:
hdc = BeginPaint(hWnd , &ps);
GetClientRect(hWnd , &rect);
DrawText(hdc , chStr , -1 , &rect , DT_LEFT);
EndPaint(hWnd , &ps);
return 0;
}
return DefWindowProc(hWnd , msg , wp , lp);
}
int WINAPI WinMain(HINSTANCE hInstance , HINSTANCE hPrevInstance ,
PSTR lpCmdLine , int nCmdShow) {
HWND hWnd;
MSG msg;
WNDCLASS winc;
HANDLE hFile;
DWORD dwBytes;
hFile = CreateFile(lpCmdLine , GENERIC_READ , 0 , NULL ,
OPEN_EXISTING , FILE_ATTRIBUTE_NORMAL , NULL);
if (hFile == INVALID_HANDLE_VALUE) return 1;
ReadFile(hFile , &bmpFileHeader , sizeof (BITMAPFILEHEADER) , &dwBytes , NULL);
if (bmpFileHeader.bfType != 0x4D42) {
MessageBox(NULL , TEXT("This is not a bitmap file") , NULL , MB_OK);
return 1;
}
CloseHandle(hFile);
winc.style = CS_HREDRAW | CS_VREDRAW;
winc.lpfnWndProc = WndProc;
winc.cbClsExtra = winc.cbWndExtra = 0;
winc.hInstance = hInstance;
winc.hIcon = LoadIcon(NULL , IDI_APPLICATION);
winc.hCursor = LoadCursor(NULL , IDC_ARROW);
winc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
winc.lpszMenuName = NULL;
winc.lpszClassName = TEXT("KITTY");
if (!RegisterClass(&winc)) return 1;
hWnd = CreateWindow(
TEXT("KITTY") , TEXT("Kitty on your lap") ,
WS_OVERLAPPEDWINDOW | WS_VISIBLE ,
CW_USEDEFAULT , CW_USEDEFAULT ,
CW_USEDEFAULT , CW_USEDEFAULT ,
NULL , NULL , hInstance , NULL
);
if (hWnd == NULL) return 1;
while (GetMessage(&msg , NULL , 0 , 0 )) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
先ほどのバイナリデータ(DIB) をこのプログラムで読み取ったものです
コマンドライン引数でファイル名を指定すると、プログラムはファイルを読みこみ
最初のワードを調べ "BM" という文字かどうかチェックします
もし、0x4D42 でなければビットマップではないという証明になります
ビットマップだった場合は、ウィンドウを生成してファイルヘッダを表示します
ビットマップファイルがない場合は、上のバイナリデータをバイナリエディタで作成して
それを *.bmp ファイルとして保存して実験してください