今回作るプログラムでは左の図のように256色のビットマップでも
表示することができます。しかし、今回作るビットマップ表示プログラムで
すべてのビットマップを正しく表示できるわけではありません。
ビットマップカラーをカラーテーブルにRGBカラー値で
指定されているものを正しく表示します。ほかに、どのような形式の
ビットマップがあるかというとカラーテーブルを16ビット符号なし整数の
配列として、この配列に格納されている整数が現在の論理パレットのインデックスとするものです。
この場合SetDIBitsToDevice関数の最後の引数はDIB_PAL_COLORSとなります。
パレットについては後述します。
メニューの「表示」「情報表示」を選択すると左のような
メッセージボックスが出現します。実際のアプリケーションでは
このような情報を表示する必要はありませんが、プログラムを作っていく上で
役に立つことが多いです。たとえば、表示しているビットマップファイルの大きさは
16738バイトあります。BITMAPFILEHEADER構造体の大きさは14バイト、
BITMAPINFOHEADER構造体の大きさは40バイト、RGBQUAD構造体の大きさは4バイトあります。
256色を使うとすると256*4=1024バイトのRGBQUAD構造体配列があります。
したがってファイルの先頭からビットマップデータまでは14+40+1024=1078バイトあります。
これは、左の図のbOffBits=1078で矛盾がありません。
また、1ピクセルあたり使用するビット数はbiBitCountビットとなります。 左の図は178 * 87ピクセルあるのでイメージに使用するビットマップデータは 178 * 87 * 8 / 8 バイトとなるでしょうか?答えはNoです。実は最左列のピクセルから 最右列のピクセルまでのデータの合計が4バイトの倍数になっていない時は 0を補う約束になっています。この場合横1列のピクセルは178個で 178 * 8 / 8バイトで4の倍数になっていません。0が補われて180バイトになります。 したがってイメージに使用するビットマップデータは180 * 87 * 8 / 8 =15660バイトになります。 これは、上の図のbiSizeImageとなり計算が合います。
こういう計算になれておくと、ビットマップがうまく表示されない時に バグを探すのに大変役に立ちます。
さて、本題に入りますが、まずSetDIBitsToDevice関数の引数について考えてみます。
最後から2番目の引数はBITMAPINFO構造体へのポインタでした。
BITMAPINFO構造体は第169章に解説がありますが、復習のため
もう一度示すと
となっていました。bmiColorsメンバの配列は1個しかないので どうしたらいいのだ?という疑問が生じると思います。 この手の構造体は他にもあります。たいていはGlobalAllocで必要な分だけ メモリを確保してこれに、データをセットします。そして、割り当てた メモリに対するポインタをその構造体へのポインタに型キャストして使います。typedef struct tagBITMAPINFO { // bmi BITMAPINFOHEADER bmiHeader; RGBQUAD bmiColors[1]; } BITMAPINFO;
たとえば256色のカラーデータをもつビットマップでは
sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD)
バイトのメモリを確保してこれに、データをセットします。
カラーテーブルを使用するビットマップでは次に論理パレットを作成しなくてはいけません。
この関数でパレットハンドルを取得します。LOGPALETTE構造体はHPALETTE CreatePalette( CONST LOGPALETTE *lplgpl // LPGPALETTE構造体へのポインタ );
のように定義されています。typedef struct tagLOGPALETTE { // lgpl WORD palVersion; WORD palNumEntries; PALETTEENTRY palPalEntry[1]; } LOGPALETTE;
palVersionはシステムのバージョン番号です。0x300を指定して下さい。
palNumEntriesはpalPalEntry配列にあるエントリ数を指定します。 palPalEntry[1]はPALETTEENTRY構造体の配列を指定します。 この構造体もPALETTEENTRY構造体配列が1つしか定義されていないので 扱いはBITMAPINFO構造体と同じです。
PALETTEENTRY構造体は
のように定義されています。typedef struct tagPALETTEENTRY { // pe BYTE peRed; BYTE peGreen; BYTE peBlue; BYTE peFlags; } PALETTEENTRY;
peRed, peGreen, peBlueは各色の輝度をあらわします。
peFlagsはパレットエントリの使用方法を指定します。NULLまたは 次の中の1つを指定します。
PC_EXPLICITは論理パレットエントリの下位ワードはハードウェアパレットインデックス を示します。ハードウェアパレットについては後述します。
PC_NOCOLLAPSEは、カラーをシステムパレットの未使用エントリに配置します。
PC_RESERVEDはパレットアニメーションのために使用します。
さて、CreatePalette関数を実行してパレットハンドルを取得しただけでは何も起こりません。 論理パレットをデバイスコンテキストに選択しなくてはいけません。
hdcにはデバイスコンテキストハンドルを指定します。HPALETTE SelectPalette( HDC hdc, // デバイスコンテキストハンドル HPALETTE hpal, // パレットハンドル BOOL bForceBackground );
hpalにはCreatePalette関数で取得したパレットハンドルを指定します。
bForceBackgroundをFALSEにするとアプリケーションがフォアグラウンドにある時に 論理パレットがデバイスパレットにコピーされます。TRUEの場合は論理パレットは すでにシステムパレットに存在する色に最善の形でマップされます。
戻り値は以前のパレットハンドルが返されます。失敗した時はNULLです。
次にRealizePalette関数を実行して選択されている論理パレットを システムパレットにマップします。
成功した場合は、システムパレットにマップされたエントリ数、 失敗した時はGDI_ERRORが返されます。UINT RealizePalette( HDC hdc // デバイスコンテキストハンドル );
パレットハンドルは不要になったらDeleteObject関数で破棄します。
いろいろ前置きが長くなりましたが、実際のプログラムを見てみることにします。
普通のメニューのリソース・スクリプトですが「表示」「情報表示」が増えました。// bmpfile03.rcの一部 ///////////////////////////////////////////////////////////////////////////// // // Menu // MYMENU MENU DISCARDABLE BEGIN POPUP "ファイル(&F)" BEGIN MENUITEM "開く(&O)", IDM_OPEN MENUITEM SEPARATOR MENUITEM "終了(&O)", IDM_END END POPUP "表示(&V)" BEGIN MENUITEM "情報表示(&I)", IDM_INFO END END
WinMain関数はいつもと同じです。グローバル変数、今回新規に作った 関数のプロトタイプ宣言などが増えました。// bmpfile03.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); HPALETTE SetPalette(HWND, LPBITMAPINFOHEADER); int MakeBMPInfo(LPBITMAPINFOHEADER); char szClassName[] = "bmpfile03"; //ウィンドウクラス char szFileName[128]; //オープンするビットマップファイル HANDLE hMem, hMemInfo; LONG wx, wy; //ビットマップの幅、高さ char *szBuffer; DWORD dwFileSize, dwOffBits, dwSizeImage, /*dwResult,*/dwClrUsed; WORD wBitCount; LPBITMAPINFO lpbmp_info; BOOL bLoad = FALSE; HPALETTE hPalette; 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; }
WM_PAINTメッセージの処理でカラーテーブルを使う時は//ウィンドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { int id; OPENFILENAME ofn; HDC hdc; PAINTSTRUCT ps; char str[256]; switch (msg) { case WM_PAINT: if (bLoad) { hdc = BeginPaint(hWnd, &ps); if (dwClrUsed) { SelectPalette(hdc, hPalette, FALSE); RealizePalette(hdc); } SetDIBitsToDevice(hdc, 0, 0, //転送先座標 wx, wy, //幅、高さ 0, 0, //転送元座標 0, wy, //走査開始番号、走査線の本数 //ビットマップデータ開始のアドレス (char *)szBuffer + dwOffBits - sizeof(BITMAPFILEHEADER), lpbmp_info, //BITMAPINFO構造体へのポインタ DIB_RGB_COLORS); EndPaint(hWnd, &ps); } else { return (DefWindowProc(hWnd, msg, wp, lp)); } break; 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; case IDM_INFO: wsprintf(str, "ファイル名 = %s\n" "BMP幅 = %d, BMP高さ = %d\n" "biBitCount = %d, biClrUsed = %d\n" "biSizeImage = %d, bOffBits = %d", szFileName, wx, wy, wBitCount, dwClrUsed, dwSizeImage, dwOffBits); MessageBox(hWnd, str, "BITMAP情報", MB_OK); break; } break; case WM_CLOSE: id = MessageBox(hWnd, "終了してもよいですか", "終了確認", MB_YESNO | MB_ICONQUESTION); if (id == IDYES) { if (hMem) GlobalFree(hMem); if (hMemInfo) GlobalFree(hMemInfo); if (hPalette) DeleteObject(hPalette); DestroyWindow(hWnd); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0; }
メニューでIDM_INFO(「情報表示」)が選択された時は、 wsprintf関数でstrにそれぞれの情報をセットして MessageBox関数で表示します。
プログラム終了時にはメモリの後始末と、パレットハンドルの破棄をしておきます。
dwClrUseが0でないか、wBitCountが0でない時は自作のSetPalette関数を 呼んでいます。int ReadDIB(HWND hWnd) { DWORD dwResult; HANDLE hF, hMem1, hMem2; LPBITMAPFILEHEADER lpBf; LPBITMAPINFOHEADER lpBi; char szFType[3]; RECT rc; int x, y; bLoad = TRUE; 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; GlobalUnlock(hMem1); if (strcmp(szFType, "BM") != 0) { MessageBox(hWnd, "ビットマップではありません", "Error", MB_OK); GlobalFree(hMem1); CloseHandle(hF); return -1; } 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; dwSizeImage = lpBf->bfSize - lpBf->bfOffBits; hMemInfo = GlobalAlloc(GHND, sizeof(BITMAPINFO) + dwClrUsed * sizeof(RGBQUAD)); lpbmp_info = (LPBITMAPINFO)GlobalLock(hMemInfo); if (hMem) { if(GlobalFree(hMem)) MessageBox(hWnd, "GlobalFree失敗", "Error", MB_OK); } hMem = GlobalAlloc(GHND, dwFileSize - sizeof(BITMAPFILEHEADER)); szBuffer = (char *)GlobalLock(hMem); //読みこむのはBITMAPINFOHEADER, RGBQUAD, ビットデータ SetFilePointer(hF, sizeof(BITMAPFILEHEADER), 0, FILE_BEGIN); ReadFile(hF, szBuffer, dwSizeImage + dwClrUsed * sizeof(RGBQUAD) + sizeof(BITMAPINFOHEADER), &dwResult, NULL); if (dwClrUsed != 0 || wBitCount != 24) { hPalette = SetPalette(hWnd, lpBi); } //BITMAPINFO構造体をセットする lpbmp_info->bmiHeader = *lpBi; MakeBMPInfo(lpBi); GetWindowRect(hWnd, &rc); x = rc.left; y = rc.top; rc.right = x + wx; rc.bottom = y + wy; AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, TRUE); MoveWindow(hWnd, x, y, rc.right - rc.left, rc.bottom - rc.top, TRUE); InvalidateRect(hWnd, NULL, TRUE); GlobalUnlock(hMemInfo); GlobalUnlock(hMem); if (GlobalFree(hMem1)) MessageBox(hWnd, "GlobalFree(hMem1)失敗", "Error", MB_OK); GlobalUnlock(hMem2); if (GlobalFree(hMem2)) MessageBox(hWnd, "GlobalFree(hMem2)失敗", "Error", MB_OK); CloseHandle(hF); return 0; }
BITMAPINFO構造体のセットでbmiColorsメンバはMakeBMPInfo関数(自作)に 任せることにしました。あとは、ゆっくりソースを読めばわかると思います。
論理パレットをセットします。LOGPALETTE構造体のpalPalEntry[1]メンバの 配列は1つしかないのでGlobalAlloc関数で必要なメモリを確保しています。 RGBQUAD構造体へのポインタはBITMAPINFOHEADER構造体のアドレスより BITMAPINFOHEADER構造体の大きさ分後ろに位置します。 RGBQUAD構造体のアドレスがわかったら順番にpalPalEntryメンバに コピーしていきます。HPALETTE SetPalette(HWND hWnd, LPBITMAPINFOHEADER lpBi) { LPLOGPALETTE lpPal; LPRGBQUAD lpRGB; HANDLE hPal; WORD i; DWORD dwClrUsed; dwClrUsed = lpBi->biClrUsed; hPal = GlobalAlloc(GHND, sizeof(LOGPALETTE) + dwClrUsed * sizeof(PALETTEENTRY)); lpPal = (LPLOGPALETTE)GlobalLock(hPal); lpPal->palVersion = 0x300; lpPal->palNumEntries = (WORD)dwClrUsed; lpRGB = (LPRGBQUAD)((LPSTR)lpBi + lpBi->biSize); for (i = 0; i < dwClrUsed; i++, lpRGB++) { lpPal->palPalEntry[i].peRed = lpRGB->rgbRed; lpPal->palPalEntry[i].peGreen = lpRGB->rgbGreen; lpPal->palPalEntry[i].peBlue = lpRGB->rgbBlue; lpPal->palPalEntry[i].peFlags = 0; } GlobalUnlock(hPal); hPalette = CreatePalette(lpPal); if (hPalette == NULL) { MessageBox(hWnd, "パレット作成失敗", "Error", MB_OK); } GlobalFree(hPal); return hPalette; }
LOGPALETTE構造体のセットが終わったらCreatePalette関数で論理パレットを 作成します。
カラーテーブルを順番にBITMAPINFO構造体のbmiColorsメンバにコピーしています。int MakeBMPInfo(LPBITMAPINFOHEADER lpBi) { LPRGBQUAD lpRGB; int i; lpRGB = (LPRGBQUAD)(szBuffer + sizeof(BITMAPINFOHEADER)); for (i = 0; i < (int)lpBi->biClrUsed; i++) { lpbmp_info->bmiColors[i].rgbBlue = lpRGB->rgbBlue; lpbmp_info->bmiColors[i].rgbGreen = lpRGB->rgbGreen; lpbmp_info->bmiColors[i].rgbRed = lpRGB->rgbRed; lpbmp_info->bmiColors[i].rgbReserved = 0; lpRGB++; } return 0; }
さて、今回は結構めんどうなプログラムでした。プログラムを少し簡単にするために GlobalAllocの第1引数をGMEM_FIXEDにしてみてください。16ビット時代では メモリのごみが出るのでGMEM_FIXEDはなるべく使わない、というのが原則でしたが 32ビットでは大丈夫です。
Update 24/Jan/1999 By Y.Kumei