EternalWindows
ビットマップ / ビットマップファイル

これまで説明してきたように、ビットマップにはDDBとDIBという2つの形式があり、 後者はデバイスに依存しないことから、ビットイメージを取得できるという大きな利点がありました。 これは一重に、現在の拡張子bmpのビットマップがDIB形式であり、 それ自体がビットイメージを含んでいるからなのですが、 この点ついて深く注目するとある1つの疑問が生じると思われます。 それは、ビットマップファイル事体にビットイメージが含まれているのならば、 ファイルを読み書きする関数でもそれを取得できるのではないかというものです。 このファイルの読み書きというのは、コンソールアプリケーションでいうところの fopenやfwriteであり、これらの関数を呼び出せばどのようなファイルからでも データの読み取りや書き込みが可能となります。

しかし、テキストファイルのような文字列のみで構成されるファイルならともかく、 一般のファイルは独自のフォーマットというものを持っていますから、 そのフォーマットを理解しないことには、たとえデータを取得しても 何を意味するのかを特定できず、使い道が分からないままになってしまいます。 つまり、ファイルから目的のデータを取得するには、 そのデータの形式や位置する場所を事前に把握しておかなければならないのです。 ビットマップファイルの先頭には、BITMAPFILEHEADERという構造体が格納されています。

typedef struct tagBITMAPFILEHEADER { 
  WORD    bfType; 
  DWORD   bfSize; 
  WORD    bfReserved1; 
  WORD    bfReserved2; 
  DWORD   bfOffBits; 
} BITMAPFILEHEADER, *PBITMAPFILEHEADER; 

bfTypeは、BMという文字列を数値化した値(0x4d42)が格納されます。 これは、ファイルがビットマップかどうかを示すために存在しています。 bfSizeは、ビットマップファイルのサイズが格納されます。 BITMAPFILEHEADER構造体やビットイメージのサイズではありません。 bfReserved1とbfReserved2は、常に0が格納されます。 bfOffBitsは、ファイルの先頭からビットイメージまでに要しているサイズが格納されます。

BITMAPFILEHEADER構造体は、ビットマップファイルを表現する全てではありません。 この構造体は、ビットマップファイルを構成する1つのデータであり、 その他のデータ(ビットイメージなど)と合わせたものがビットマップファイルなのです。 BITMAPFILEHEADER構造体の後には、BITMAPINFOHEADER構造体が格納されています。

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

biSizeは、この構造体のサイズが格納されます。 biWidthは、ビットマップの幅が格納されます。 biHeightは、ビットマップの高さが格納されます。 biPlanesは、常に1が格納されます。 biBitCountは、1ピクセルあたりのビット数であり、 24ならばフルカラービットマップ、8ならば256ビットマップとなります。 biCompressionは、ビットマップの圧縮形式が格納されます。 BI_RGB定数(0)が格納されている場合は、圧縮されていないことを示します。 biSizeImageは、ビットイメージのサイズが格納されます。 未圧縮のビットマップの場合、このメンバは0でも構いません。 biXPelsPerMeterとbiYPelsPerMeterは、デバイスの水平および垂直解像度が格納されますが、 基本的に0となります。 biClrUsedは、カラーテーブルのエントリ数が格納されます。 biClrImportantは、ビットマップを表示するのに必要な色数が格納されます。 0ならば、全ての色が必要です。

これらのメンバで重要なのは、biWidthとbiHeight、そしてbiBitCountでしょう。 幅と高さはビットマップを表示するときには必要不可欠ですし、 biBitCountが8であれば、そのビットマップはカラーテーブルを持つということですから、 カラーテーブルを読み取る処理が必要であると分かります。 このようなときはbiClrUsedも参照することになりますが、 それについては次節で説明します。

今回のプログラムは、ファイルを操作する関数を呼び出して BITMAPINFOHEADER構造体を取得し、そのメンバを表示します。 このファイルの操作は、従来のfopenやfwriteではなく専用のWindowsAPIで行っているため、 始めは難しく思うかもしれませんが、慣れてしまえば簡単です。

#include <windows.h>

BOOL ReadBitmap(LPTSTR lpszFileName, LPBITMAPINFOHEADER lpbmiHeader);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR            szBuf[256];
	BITMAPINFOHEADER bmiHeader; 
	
	if (ReadBitmap(TEXT("sample.bmp"), &bmiHeader)) {
		wsprintf(szBuf, TEXT("ビット数 %d  幅 %d  高さ %d"), bmiHeader.biBitCount, bmiHeader.biWidth, bmiHeader.biHeight);
		MessageBox(NULL, szBuf, TEXT("ビットマップファイル"), MB_OK);
	}
	
	return 0;
}

BOOL ReadBitmap(LPTSTR lpszFileName, LPBITMAPINFOHEADER lpbmiHeader)
{
	HANDLE           hFile;
	DWORD            dwResult;
	BITMAPFILEHEADER bmfHeader;
	BITMAPINFOHEADER bmiHeader;

	hFile = CreateFile(lpszFileName, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hFile == INVALID_HANDLE_VALUE) {
		MessageBox(NULL, TEXT("ファイルのオープンに失敗しました。"), NULL, MB_ICONEXCLAMATION | MB_OK);
		return FALSE;
	}
	
	ReadFile(hFile, &bmfHeader, sizeof(BITMAPFILEHEADER), &dwResult, NULL);
	if (bmfHeader.bfType != *(LPWORD)"BM") {
		MessageBox(NULL, TEXT("ビットマップファイルではありません。"), NULL, MB_ICONEXCLAMATION | MB_OK);
		CloseHandle(hFile);
		return FALSE;
	}
	
	ReadFile(hFile, &bmiHeader, sizeof(BITMAPINFOHEADER), &dwResult, NULL);

	*lpbmiHeader = bmiHeader;
	
	CloseHandle(hFile);
	
	return TRUE;
}

プログラムはReadBitmapという自作関数を呼び出してBITMAPINFOHEADER構造体を初期化し、 ビット数と幅、高さをメッセージボックスで表示しています。 ReadBitmapの内部を順に見ていきます。

hFile = CreateFile(lpszFile, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
	MessageBox(NULL, TEXT("ファイルのオープンに失敗しました。"), NULL, MB_ICONEXCLAMATION | MB_OK);
	return FALSE;
}

CreateFileは、ファイルをオープンしてそのハンドルを返す関数です。 第1引数はファイル名、第2引数は読み取りオープンを示すGENERIC_READ、 第5引数はファイルのオープンを示すOPEN_EXISTINGを指定します。 残りの引数は今回の場合、重要な意味を持ちません。 ファイルハンドルはHANDLE型で表し、値がINVALID_HANDLE_VALUEでなければ、 正常にファイルをオープンできたことになります。

ReadFile(hFile, &bmfHeader, sizeof(BITMAPFILEHEADER), &dwResult, NULL);
if (bmfHeader.bfType != *(LPWORD)"BM") {
	MessageBox(NULL, TEXT("ビットマップファイルではありません。"), NULL, MB_ICONEXCLAMATION | MB_OK);
	CloseHandle(hFile);
	return FALSE;
}

ReadFileは、第3引数に指定されたサイズ分、第2引数にデータを読み取ります。 先に述べたように、ビットマップファイルの先頭にはBITMAPFILEHEADER構造体が 格納されているので、ますこれを読み取ることになります。 BITMAPFILEHEADER構造体のbfTypeメンバは、常にBMという文字列を数値化した値になるため、 この値を確かめることによって、読み取ったデータが本当にビットマップファイルのものかを 判断できることになります。

ReadFile(hFile, &bmiHeader, sizeof(BITMAPINFOHEADER), &dwResult, NULL);

ReadFileは、第3引数で指定されたサイズだけファイルポインタを進めるため、 先ほどのBITMAPFILEHEADER構造体の読み取りの時点で、 ファイルポインタはBITMAPINFOHEADER構造体の先頭を指していることになります。 よって、単純にReadFileを呼び出すだけでBITMAPINFOHEADER構造体を取得できます。

*lpbmiHeader = bmiHeader;
	
CloseHandle(hFile);

呼び出し側のBITMAPINFOHEADER構造体のアドレスに、読み取ったデータをコピーします。 この*lpbmiHeaderは、ReadFileに直に指定してもよかったのですが、 次節のプログラムとの整合性を考えた結果、上記のようにすることにしました。 CloseHandleは、ファイルを閉じる関数です。


戻る