S-JIS[2003-01-05]

ビットマップ(DIB)の使い方

ウィンドウズの標準的な画像であるビットマップは、MFCの場合、CBitmapクラスで扱います。 これで描画は簡単に行えるのですが、デバイスに依存しているビットマップ(DDB)なので、色数を自分で指定したりできません。 したがって、ファイルへの書き込みはまだしも、 読み込んだ場合に現在の画面と整合性のある色数にすることは難しいです。

そこで、デバイスに依存しないビットマップ(DIB)の出番です。 これはデバイスに依存しないと謳っているだけあって、現在の画面の色数にかかわらず、色数を自分で指定できます。 ファイルとのやりとりだと、色数はファイル内で決まっているので、DIBでないと不便です。
ただ、MFCではサポートしていないので、APIを呼んで操作します。 しかしクラス化した方が便利なので、最後にCDibというクラスを作ってみました。


DIBはCreateDibSectionというAPIで作成します。

CreateDibSection関数
第1引数HDC hdc デバイスコンテキストのハンドル デバイスコンテキストのパレットでDIBを作りたい場合は指定する。
それ以外は0でよい。
第2引数BITMAPINFO *pbmi ビットマップ情報 ビットマップの基本情報を指定する。
第3引数UINT iUsage 色データの種類 HDCのパレットを使う場合はDIB_PAL_COLORS、
BITMAPINFOのパレットを使う場合はDIB_RGB_COLORSを指定する。
第4引数void* *ppvBits 画像データのポインタ 画像データの先頭位置を返す変数を指定する。返されたポインタが指すデータを書き換えることで、画像を変更できる。
第5引数HANDLE hSection ファイルマップハンドル 画像データを保持するメモリのハンドルを指定する。メモリの確保をOSに任せる場合は0でよい。
第6引数DWORD dwOffset ファイルマップの位置 ファイルマップハンドルのオブジェクトの使用位置?
返り値HBITMAP ビットマップハンドル ビットマップのハンドル。使い終わったら、DeleteObject関数で解放すること!

今回の場合、ファイルとのやりとりを主眼に置くので、HDCは使いません。 また、メモリの管理を自分でするのも面倒なので、ファイルマップはOSに任せます。

BITMAPINFO構造体
BITMAPINFOHEADER bmiHeader ビットマップ情報ヘッダーです。…説明に なりゃしない(苦笑)
RGBQUAD bmiColors[1] パレット1つ分の領域が確保されています。
指定したいパレットが複数有る場合は拡張する必要がありますし、パレットを1つも使わない場合は不要です。 なので、この宣言はちょっと邪魔な気がしたり…
BITMAPINFOHEADER構造体
DWORD biSize この構造体のサイズを指定します。
何故こんなことが必要かというと、プログラムのバージョンによっては、同じ構造体名でも内容が違ったりするからです。 したがって、この構造体をファイルから読み込んだ場合は、ファイルのこの数値が自分のプログラムのこの構造体のサイズより小さいかどうかチェックし、 データが存在する箇所しか使わないようにしなければならないらしいです、本来は。
LONG biWidth ビットマップの幅(ドット単位)
LONG biHeight ビットマップの高さ(ドット単位)
正の数を指定した場合、画像データは、実際に表示される画像とは上下反転して保持されます。
WORD biPlanes プレーン数とのことですが、必ず1を指定するようです。
WORD biBitCount 1ドット当たりのビット数(色数)を指定します。1,4,8,16,24,32しか使えません(重要!)。
DWORD biCompression ビットマップの圧縮形式らしいですが、とりあえず無圧縮(BI_RGB)を指定しておけばいいでしょう。
DWORD biSizeImage 画像データのサイズ(バイト単位)
0が入っていることもあるらしいので、当てにはならない…。
LONG biXPelsPerMeter 水平解像度。何だか分からないが、0でよい。
LONG biYPelsPerMeter 垂直解像度。何だか分からないが、0でよい。
DWORD biClrUsed パレット(BITMAPINFOのRGBQUAD)の個数。
DWORD biClrImportant パレット(BITMAPINFOのRGBQUAD)のうち、重要なパレットの個数。
とりあえず0にしておけば、「全部重要」として扱われる。

以上を念頭に置いてDIBを作成します。
下記のCreateDIBは、ビットマップのサイズ・色数(ビット数)・パレットを 指定すると、そのDIBを作成する自作関数です。 16bit色以上の場合は、パレットは不要です。

//1行当たりのバイト数を算出
long CalcPitch(WORD BitCount,LONG Width)
{
	long lPitch;
	if(BitCount<8){
		int n=8/BitCount;
		lPitch=(Width+n-1)/n;
	}else{
		BitCount/=8;
		lPitch=Width*BitCount;
	}
	return (lPitch+3)&~3;	//4バイト境界に合わせる
}

//BITMAPINFOを作成する。
BITMAPINFO* CreateBITMAPINFO(const CSize &sz,WORD BitCount,CPalette *pPal)
{
	int nBI =sizeof BITMAPINFO - sizeof RGBQUAD;

	int nPal= pPal? pPal->GetEntryCount() : 0;
	nBI+=nPal*sizeof RGBQUAD;
	BITMAPINFO *pBI=(BITMAPINFO*)new BYTE[nBI];
	::memset(pBI,0,nBI);	//0クリア

	BITMAPINFOHEADER *pIH=&pBI->bmiHeader;
	pIH->biSize=sizeof BITMAPINFOHEADER;
	pIH->biWidth =sz.cx;	//ドット単位
	pIH->biHeight=sz.cy;	//ドット単位(下から上)
	pIH->biPlanes=1;		//常に1
	pIH->biBitCount=BitCount;	//ドット当たりのビット数
	pIH->biCompression=BI_RGB;	//圧縮形式
	pIH->biSizeImage=CalcPitch(BitCount,sz.cx)*sz.cy;	//画像のバイト数
	pIH->biXPelsPerMeter=0;
	pIH->biYPelsPerMeter=0;
	pIH->biClrUsed=nPal;	//パレットの個数
	pIH->biClrImportant=0;	//全て重要な色

	if(nPal>0){	//パレット部の作成
		PALETTEENTRY *pPE=new PALETTEENTRY[nPal];
		pPal->GetPaletteEntries(0,nPal,pPE);
		PALETTEENTRY *pp=pPE;
		RGBQUAD *qp=pBI->bmiColors;
		for(int i=0;i<nPal;i++,pp++,qp++){
			qp->rgbRed  =pp->peRed;
			qp->rgbGreen=pp->peGreen;
			qp->rgbBlue =pp->peBlue;
			qp->rgbReserved=0;
		}
		delete[] pPE;
	}

	return pBI;
}

//DIBを作成する。
//sz      :作成するビットマップのサイズ
//BitCount:作成するビットマップの色数(bit単位)
//pPal    :作成するビットマップのパレット(不要ならNULLでよい)
//pBitsDib:作成されたDIBの画像データ領域へのポインタが返る
CBitmap* CreateDIB(const CSize &sz,WORD BitCount,CPalette *pPal,BYTE* &pBitsDib)
{
	BITMAPINFO *pBI=CreateBITMAPINFO(sz,BitCount,pPal);
	void *pvBitsDib;

	HBITMAP hDIB= ::CreateDIBSection((HDC)0,pBI,DIB_RGB_COLORS,(void**)&pBitsDib,NULL,0);
	delete[] (BYTE*)pBI;
	if(hDIB==(HBITMAP)0) return NULL;

	CBitmap *pBmp=new CBitmap;
	pBmp->Attach(hDIB);
	return pBmp;
}

このCreateDIBが返すCBitmapは、 普通のCBitmapと同じように デバイスコンテキスト(CDCクラス)で使うことができます。


画像データの参照・変更は、pBitsDibに返ってくる領域を操作することで行います。 具体的な方法は色数によって少しだけ異なります。

//4bit色のDIB(サイズ(cx,cy))で、(x,y)の色コードを取得する。
BYTE GetColor04(BYTE *pBitsDib,int x,int y,int cx,int cy)
{
	pBitsDib+=CalcPitch(4,cx)*(cy-1-y);
	pBitsDib+=x/2;
	if(x%2==0){
		return *pBitsDib>>4;
	}else{
		return *pBitsDib&0xf;
	}
}

//16bit色のDIB(サイズ(cx,cy))で、(x,y)に色コードをセットする。
void SetColor16(BYTE *pBitsDib,int x,int y,int cx,int cy,WORD color)
{
	pBitsDib+=CalcPitch(16,cx)*(cy-1-y);
	WORD *wp=(WORD*)pBitsDib;
	wp[x]=color;
}

このCreateDIBが返すCBitmapを解放するには、以下のようにします。

//CreateDIBで作ったビットマップの解放
BOOL ReleaseDIB(CBitmap *pBmp)
{
	BOOL ret= ::DeleteObject(pBmp->Detach());
	delete pBmp;
	return ret;
}

CBitmap::Detach()が返すのは、CBitmap::Attach()でアタッチしたハンドル、 すなわちCreateDIBSectionで返されたHBITMAPです。 これをDeleteObject()で解放します。


ビットマップをファイルに保存する場合、BITMAPINFOと画像データの他に、 ビットマップファイルヘッダーが必要となります。 ファイルには、

  1. ファイルヘッダー
  2. ビットマップ情報ヘッダー
  3. パレット
  4. 画像データ

の順でデータを書き込みます。

BITMAPFILEHEADER構造体
WORD bfType ファイルの先頭に書く識別子。テキストエディタでファイルを見たときに「BM」という文字列になるようにする。
DWORD bfSize ファイルサイズ。
WORD bfReserved1 将来使用予定。今は0を指定する。
WORD bfReserved2 将来使用予定。今は0を指定する。
DWORD bfOffBits ファイルの先頭から、画像データの位置までのオフセット(バイト単位)。
//pBI     :BITMAPINFO(パレット領域含む)のアドレス
//nBI     :pBIのデータの長さ(バイト単位)
//pBitsDib:画像データのアドレス
//nBT     :pBitsDibのデータの長さ(バイト単位)
BOOL SaveDIB(CFile *fp,BITMAPINFO *pBI,int nBI,BYTE *pBitsDib,int nBT)
{
	BITMAPFILEHEADER fh;
	::memset(&fh,0,sizeof fh);
	fh.bfType=0x4d42;		//ID
	fh.bfSize   =sizeof(fh)+nBI+nBT;	//ファイルの大きさ
	fh.bfOffBits=sizeof(fh)+nBI;	//画像の開始オフセット

	fp->Write(&fh,sizeof fh);
	fp->Write(pBI,nBI);
	fp->Write(pBitsDib,nBT);
	return TRUE;
}

ビットマップをファイルから読み込む場合は、

  1. ファイルの中のビットマップ情報を用い、 CreateDibSectionでDIBを作成する。
  2. CreateDibSectionで返されたpBitsDibの位置に ファイルの画像データをコピーする。

という手順でDIBを作成します。

//pData   :読み込んだファイルデータの先頭アドレス
//DataLen :読み込んだファイルの長さ
//pBitsDib:作成されたDIBの画像データ領域へのポインタが返る
CBitmap* LoadDIB(const BYTE *pData,UINT DataLen,BYTE* &pBitsDib)
{
	BITMAPFILEHEADER *pDataFH=(BITMAPFILEHEADER*)pData;
	if(DataLen<sizeof BITMAPFILEHEADER){
		DataLen=pDataFH->bfSize;
		if(DataLen<sizeof BITMAPFILEHEADER) return NULL;
	}
	BITMAPINFO       *pDataBI=(BITMAPINFO*)&pData[sizeof(*pDataFH)];
	BITMAPINFOHEADER *pDataIH=&pDataBI->bmiHeader;
	const BYTE       *pDataBT=pData+pDataFH->bfOffBits;
	if((BYTE*)pDataIH>=pDataBT) return NULL;
	if(pDataBT>=pData+DataLen) return NULL;

	HBITMAP hDIB= ::CreateDIBSection((HDC)0,pDataBI,DIB_RGB_COLORS,(void**)&pBitsDib,NULL,0);
	if(hDIB==(HBITMAP)0) return NULL;

	long nBT=CalcPitch(pDataIH->biBitCount,pDataIH->biWidth)*pDataIH->biHeight;
	::memcpy(pBitsDib,pDataBT,nBT);

	CBitmap *pBmp=new CBitmap;
	pBmp->Attach(hDIB);
	return pBmp;
}

以上がDIBの使い方です。が、実際に使おうとすると ビットマップのサイズや構造体の長さがあちこちで必要となり、 それらを保持したり引数で渡したりするのは面倒になってきます。
そこで、これらをまとめたCDibクラスというのを作ってみました。 誰でも作っているでしょうが…。 速度は全然重視していませんが、そこそこ便利な使い方ができると思います。 透明色の指定もできたりします。ちょっとだけ自慢


VC++ページへ戻る / 技術メモへ戻る
メールの送信先:ひしだま