第169章 ビットマップの構造


今まで何回かリソースのビットマップを表示するようなプログラムを 作ってきました。しかし、ちょっと手抜きがありました。 今回からビットマップの構造を解説し、ビットマップをファイルから 読み出したり、ファイルに書きこむ方法を考えます。



目的を達成するにはいろいろと新しい事柄が出てくるので、少しずつ 前に進むことにします。

ビットマッブは、Windows3.0以降DIB(device-independent bitmap)といわれる フォーマット形式が使われます。(それ以前はDDB(device-dependent bitmap)) DIB形式のビットマップはファイルの先頭から次のような構造になっています。

BITMAPFILEHEADER構造体 BITMAPINFOHEADER構造体 RGBQUARD構造体 ビット配列

ファイルの先頭に来るBITMAPFILEHEADER構造体は次のように定義されています。

typedef struct tagBITMAPFILEHEADER { // bmfh WORD bfType; DWORD bfSize; WORD bfReserved1; WORD bfReserved2; DWORD bfOffBits; } BITMAPFILEHEADER;

bfTypeには、"BM"の2バイトが収められています。 ファイルを読みこんだ時これを調べて"BM"を確認する必要があります。

bfSizeはファイルのサイズです。

bfOffBitsはファイルの先頭からビット配列までのオフセット値が入ります。

typedef struct tagBITMAPINFOHEADER{ // bmih DWORD biSize; LONG biWidth; LONG biHeight; WORD biPlanes; WORD biBitCount; DWORD biCompression; DWORD biSizeImage; LONG biXPelsPerMeter; LONG biYPelsPerMeter; DWORD biClrUsed; DWORD biClrImportant; } BITMAPINFOHEADER;

biSizeには、構造体の大きさが入ります。

biWidthは、ビットマップの幅。

biHeightは、ビットマップの高さ。

biPlanesは、プレーンの数ですが1でなくてはいけません。

biBitCountは、ピクセルあたりの色数を示します。1,4,8,24のいずれかです。

biCompressionは、圧縮方式を示します。0で圧縮なし。

biSizeImageは、ビットマップビットのサイズ。圧縮の時のみ必要。

biXPelsPerMeterは、水平解像度を示します。1メートルあたりのピクセル数。

biYPelsPerMeterは、垂直解像度を示します。

biClrUsedは、イメージで使われている色数を示します。

biClrImportantは、イメージで使われている重要な色の数を示します。

さて、Windows95ではBITMAPV4HEADER、Windows98ではBITMAPV5HEADERが 定義されています。各自で調べてみてください。

次にRGBQUAD構造体が来ます。
biClrUsedが0でなおかつ、biBitCountが1なら2つ、4なら16、8なら256個のRGBQUAD構造体が 来ます。フルカラーの時はRGBQUAD構造体はありません。 また、biClrUsedが0以外の時はその数だけRGBQUAD構造体が来ます。 これらをカラーテーブルなどと呼びます。

typedef struct tagRGBQUAD { // rgbq BYTE rgbBlue; BYTE rgbGreen; BYTE rgbRed; BYTE rgbReserved; } RGBQUAD;

メンバは、それぞれ青、緑、赤の強さを表します。

RGBQUAD構造体のあとには、ビットマップデータが続きます。 左下から右に、下から上にデータが入っています。

次に紛らわしいのが、BITMAPINFO構造体というのがあります。 これは、BITMAPINFOHEADER構造体とRGBQUAD構造体をまとめて 管理するものです。

typedef struct tagBITMAPINFO { // bmi BITMAPINFOHEADER bmiHeader; RGBQUAD bmiColors[1]; } BITMAPINFO;

bmiHeaderは、BITMAPINFOHEADER構造体を指定します。

bmiColors[1]は、RGBQUAD構造体または、DWORD型データの配列を指定します。

さて、今回はビットマップファイルを読みこんでヘッダー情報を 調べるプログラムを作ってみます。これで、いろいろなビットマップを 調べてみてください。

// bmpfile01.rcの一部 ///////////////////////////////////////////////////////////////////////////// // // Menu // MYMENU MENU DISCARDABLE BEGIN POPUP "ファイル(&F)" BEGIN MENUITEM "開く(&O)", IDM_OPEN MENUITEM SEPARATOR MENUITEM "終了(&O)", IDM_END END END

これは、普通のメニューのリソース・スクリプトです。

// bmpfile01.cpp #ifndef STRICT #define STRICT #endif #include <windows.h> #include "resource.h" LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); ATOM InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); int ReadDIB(HWND); char szClassName[] = "bmpfile01"; //ウィンドウクラス char szFileName[128]; //オープンするビットマップファイル

オープンするビットマップファイルはグローバル変数にしておきます。

int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, LPSTR lpsCmdLine, int nCmdShow) { MSG msg; if (!InitApp(hCurInst)) return FALSE; if (!InitInstance(hCurInst, nCmdShow)) return FALSE; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } //ウィンドウ・クラスの登録 ATOM InitApp(HINSTANCE hInst) { WNDCLASSEX wc; wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; //プロシージャ名 wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst; //インスタンス wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszMenuName = "MYMENU"; //メニュー名 wc.lpszClassName = (LPCSTR)szClassName; wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); return (RegisterClassEx(&wc)); } //ウィンドウの生成 BOOL InitInstance(HINSTANCE hInst, int nCmdShow) { HWND hWnd; hWnd = CreateWindow(szClassName, "猫でもわかるBMP",//タイトルバーにこの名前が表示されます WS_OVERLAPPEDWINDOW,//ウィンドウの種類 CW_USEDEFAULT, //X座標 CW_USEDEFAULT, //Y座標 CW_USEDEFAULT, //幅 CW_USEDEFAULT, //高さ NULL, //親ウィンドウのハンドル、親を作るときはNULL NULL, //メニューハンドル、クラスメニューを使うときはNULL hInst, //インスタンスハンドル NULL); if (!hWnd) return FALSE; ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); return TRUE; }

このへんはいつもとたいして変わりません。

//ウィンドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { int id; OPENFILENAME ofn; switch (msg) { case WM_COMMAND: switch (LOWORD(wp)) { case IDM_END: SendMessage(hWnd, WM_CLOSE, 0, 0); break; case IDM_OPEN: memset(&ofn, 0, sizeof(OPENFILENAME)); ofn.lStructSize = sizeof(OPENFILENAME); ofn.hwndOwner = hWnd; ofn.lpstrFilter = "Bitmap (*.BMP)\0*.BMP\0\0"; ofn.nFilterIndex = 1; ofn.lpstrFile = szFileName; ofn.nMaxFile = 128; ofn.Flags = OFN_HIDEREADONLY; if (GetOpenFileName((LPOPENFILENAME)&ofn)) ReadDIB(hWnd); break; } break; case WM_CLOSE: id = MessageBox(hWnd, "終了してもよいですか", "終了確認", MB_YESNO | MB_ICONQUESTION); if (id == IDYES) { DestroyWindow(hWnd); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0; }

メニューで「開く」(IDM_OPEN)が来たら、「ファイルを開く」コモンダイアログ を出して、ファイル名を取得します。そしてDIBRead関数を呼びます。 OPENFILENAME構造体について忘れてしまった人は 第73章を参照して下さい。

int ReadDIB(HWND hWnd) { HANDLE hF; HANDLE hMem1, hMem2; LPBITMAPFILEHEADER lpBf; LPBITMAPINFOHEADER lpBi; DWORD dwResult; LONG wx, wy; DWORD dwFileSize, dwOffBits; WORD wBitCount; DWORD dwClrUsed, dwClrImportant; char str[256]; char szFType[3]; hF = CreateFile(szFileName, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hF == INVALID_HANDLE_VALUE) { MessageBox(hWnd, "ファイルのオープンに失敗しました", "Error", MB_OK); return -1; } hMem1 = GlobalAlloc(GHND, sizeof(BITMAPFILEHEADER)); lpBf = (LPBITMAPFILEHEADER)GlobalLock(hMem1); ReadFile(hF, (LPBITMAPFILEHEADER)lpBf, sizeof(BITMAPFILEHEADER), &dwResult, NULL); dwFileSize = lpBf->bfSize; szFType[0] = LOBYTE(lpBf->bfType); szFType[1] = HIBYTE(lpBf->bfType); szFType[2] = '\0'; dwOffBits = lpBf->bfOffBits; wsprintf(str, "dwFileSize = %d, szFType = %s dwOffBits = %d", dwFileSize, szFType, dwOffBits); MessageBox(hWnd, str, "BMP情報その1", MB_OK); GlobalUnlock(hMem1); GlobalFree(hMem1); hMem2 = GlobalAlloc(GHND, sizeof(BITMAPINFOHEADER)); lpBi = (LPBITMAPINFOHEADER)GlobalLock(hMem2); ReadFile(hF, (LPBITMAPINFOHEADER)lpBi, sizeof(BITMAPINFOHEADER), &dwResult, NULL); wx = lpBi->biWidth; wy = lpBi->biHeight; wBitCount = lpBi->biBitCount; dwClrUsed = lpBi->biClrUsed; dwClrImportant = lpBi->biClrImportant; wsprintf(str, "wx = %d, wy = %d\nwBitCount = %d, dwClrUsed = %d, dwClrImportant = %d", wx, wy, wBitCount, dwClrUsed, dwClrImportant); MessageBox(hWnd, str, "BMP情報その2", MB_OK); GlobalUnlock(hMem2); GlobalFree(hMem2); CloseHandle(hF); return 0; }

さて、これが今回のメインのビットマップの情報を調べる関数です。

CreateFile関数でビットマップファイルをオープンします。 この関数を忘れてしまった人は第74章を参照して下さい。 次にファイルからBITMAPFILEHEADERをバッファに読み取ります。この時バッファは 動的に確保します。GlobalAlloc関数については 第82章を参照して下さい。

次にReadFile関数でビットマップの最初の部分(BITMAPFILEHEADER)を 読み出します。ここで注意すべき点はbfTypeです。ここには"BM"の2バイトが 格納されているはずです。BITMAPFILEHEADER構造体からbfTypeメンバのLOBYTE をszFTypeの先頭にコピーします。次のバイトにはHIBYTEをコピーします。 そうするとszFTypeは"BM"をあらわします。もしくはbfTypeが0x4d42であることを 確認して下さい。(0x4d='M' 0x42='B')

BITMAPFILEHEADER構造体から情報を読み終わったら、同じように BITMAPINFOHEADER構造体から情報を読み取ります。

今回作ったプログラムでいろいろなビットマップの情報を調べると いろいろなことがわかります。実際に試してみてください。 紛らわしい構造体がいろいろ出てくるので、しっかり整理しておいて下さい。


[SDK第2部 Index] [総合Index] [Previous Chapter] [Next Chapter]

Update 11/Jan/1999 By Y.Kumei
当ホーム・ページの一部または全部を無断で複写、複製、 転載あるいはコンピュータ等のファイルに保存することを禁じます。