2010年08月14日

DIBファイルの読み込み



DIBファイル(拡張子bmp)は、デバイス独立ビットマップDIB)の形で格納されおり、以下のような構造をしています。

  1. BITMAPFILEHEADER構造体
  2. BITMAPINFO構造体
  3. ピクセルデータ




ビットマップバイト幅を算出するためのマクロ定義です。

//ビットマップバイト幅の算出マクロ
#ifndef WIDTHBYTES
#define WIDTHBYTES(bits) (((bits)+31)/32*4)
#endif//WIDTHBYTES


DIBファイル を開いて、_DIBStreamIn関数を呼び出します。

//DIBファイルの読み込み
HBITMAP __stdcall _LoadDIB(LPCTSTR lpszFileName,LPDWORD lpdwCompression)
{
CFile infile;
if (!infile.Open(lpszFileName,CFile::modeRead|CFile::shareDenyNone)) return FALSE;
HBITMAP hBitmap=_DIBStreamIn(&infile,lpdwCompression);
infile.Close();
if (!hBitmap) return FALSE;
return hBitmap;
}


DIBファイルを読み込んでDIBを作成します。

//DIBファイルストリームの読み込み
HBITMAP __stdcall _DIBStreamIn(CFile* infile,LPDWORD lpdwCompression)
{
ASSERT(infile->IsKindOf(RUNTIME_CLASS(CFile)));

//BITMAPFILEHEADER構造体の読み込み
BITMAPFILEHEADER fh;
//ファイルからBITMAPFILEHEADER構造体を読み込みます。
UINT uSize=infile->Read(&fh,sizeof(BITMAPFILEHEADER));
//ファイルの読み込みに失敗したり、ファイル形式が'BM'でなければ、FALSEを返して終了します。
if ((uSize<sizeof(BITMAPFILEHEADER))||(fh.bfType!=0x4D42)) return FALSE;

//BITMAPINFOの読み込み
UINT sizeOfBitmapInfo=fh.bfOffBits-sizeof(BITMAPFILEHEADER);
//算出したサイズがBITMAPINFOHEADER構造体のサイズより小さい場合は、エラー終了します。
if (sizeOfBitmapInfo<sizeof(BITMAPINFOHEADER)) return FALSE;

//BITMAPINFO構造体を格納するバッファを確保します。
CByteArray arrayBmi;
arrayBmi.SetSize(sizeOfBitmapInfo);
BITMAPINFO* pBmi=(BITMAPINFO*)arrayBmi.GetData();

uSize=infile->Read(pBmi,sizeOfBitmapInfo);
//ファイル読み込みに失敗したり、
//BITMAPINFOHEADER構造体のbiSizeメンバーの値が正しくない場合は、エラー終了します。

if ((uSize<sizeOfBitmapInfo)||(pBmi->bmiHeader.biSize<sizeof(BITMAPINFOHEADER)))
return FALSE;

//DIBの幅
UINT width =pBmi->bmiHeader.biWidth;
//DIBの高さ(絶対値)
UINT height=abs(pBmi->bmiHeader.biHeight);
//DIBのビットカウント
UINT nBpp=pBmi->bmiHeader.biBitCount;

//ビットマップバイト幅の算出
DWORD widthBytes=WIDTHBYTES(nBpp*width);
//ピクセルデータのバイト数の算出
DWORD dwSizeImage=widthBytes*height;

//各チャンネル毎のカラーマスク値を、一時保管する変数を用意します。
DWORD dwRedMask=0;
DWORD dwGreenMask=0;
DWORD dwBlueMask=0;
DWORD dwAlphaMask=0;

//ランレングス圧縮されたDIBの場合は、DIB作成時にエラーにならないように
//BITMAPINFOHEADER構造体のbiCompressionをBI_RGBに戻しておきます。

DWORD dwCompression=pBmi->bmiHeader.biCompression;
if ((dwCompression==BI_RLE4)||(dwCompression==BI_RLE8))
pBmi->bmiHeader.biCompression=BI_RGB;

//マスクビットフィールドが有効な場合。
else if (dwCompression==BI_BITFIELDS){
//BITMAPINFOの長さがBITMAPINFOHEADER+BITFIELDS[3]以上の場合は、
//RGBカラーマスクを取得します。

if (sizeOfBitmapInfo>=sizeof(BITMAPINFOHEADER)+sizeof(DWORD)*3){
dwRedMask =((BITMAPV4HEADER*)pBmi)->bV4RedMask;
dwGreenMask=((BITMAPV4HEADER*)pBmi)->bV4GreenMask;
dwBlueMask =((BITMAPV4HEADER*)pBmi)->bV4BlueMask;
}

//32ビットDIBで、BITMAPINFOの長さがBITMAPINFOHEADER+BITFIELDS[4]以上の場合は、
//アルファチャンネルを取得します。

if ((nBpp==32)&&(sizeOfBitmapInfo>=sizeof(BITMAPINFOHEADER)+sizeof(DWORD)*4))
dwAlphaMask=((BITMAPV4HEADER*)pBmi)->bV4AlphaMask;

//32ビットDIB若しくは、16ビットDIBでRGB565ではない場合は、
//biCompressionをBI_RGBにして、BITMAPINFOHEADERのサイズを正規サイズに戻します。

if ((nBpp==32)||((nBpp==16)&&
((dwRedMask!=0x0000F800)||
(dwGreenMask!=0x000007E0)||
(dwBlueMask!=0x0000001F))))
{
pBmi->bmiHeader.biCompression=BI_RGB;
pBmi->bmiHeader.biSize=sizeof(BITMAPINFOHEADER);
}
}

//読み込んだBITMAPINFO構造体から、DIBセクションを作成します。
LPVOID pvBits=NULL;
HBITMAP hBitmap=::CreateDIBSection(NULL,pBmi,DIB_RGB_COLORS,&pvBits,NULL,NULL);
//DIBの作成に失敗したら、エラー終了します。
if (!hBitmap) return FALSE;

//それぞれの圧縮形式に合わせて、イメージピクセルを読み込みます。
BOOL bResult=FALSE;
switch(dwCompression){
case BI_RGB:
case BI_BITFIELDS:
//圧縮してない場合は、ピクセルデータをそのままコピーします。
uSize=infile->Read(pvBits,dwSizeImage);
if (uSize>=dwSizeImage) bResult=TRUE;
break;
case BI_RLE4:
//4ビットランレングス圧縮の場合は、_DecodeRle4関数を呼び出して解凍します。
bResult=_DecodeRle4(pvBits,dwSizeImage,width,height,infile);
break;
case BI_RLE8:
//8ビットランレングス圧縮の場合は、_DecodeRle8関数を呼び出して解凍します。
bResult=_DecodeRle8(pvBits,dwSizeImage,width,height,infile);
break;
default:
break;
}
//読み込みエラーがある場合は、作成したDIBを削除してからエラー終了します。
if (!bResult){
::DeleteObject(hBitmap);
return FALSE;
}

//32ビットDIBの場合。
if (nBpp==32){
int nPixels=dwSizeImage>>2; //ピクセル数

//マスクビットフィールドが有効で、RGBA8888ではない場合は、
//ピクセルビットをRGBA8888に合わせます。

if ((dwCompression==BI_BITFIELDS)&&
((dwRedMask!=0x00FF0000)||(dwGreenMask!=0x0000FF00)||
(dwBlueMask!=0x000000FF)||(dwAlphaMask!=0xFF000000)))
{
//各チャンネルのカラーマスク値が正しい場合。
DWORD dwMask[4]={dwRedMask,dwGreenMask,dwBlueMask,dwAlphaMask};
if (_IsColorMasks(dwMask,4)){
//各チャンネルのビットシフト量を計算します。
int nShiftsBlue=0;
if (dwBlueMask)
while(((dwBlueMask>>nShiftsBlue)&1)==0) nShiftsBlue++;
int nShiftsGreen=0;
if (dwGreenMask)
while(((dwGreenMask>>nShiftsGreen)&1)==0) nShiftsGreen++;
int nShiftsRed=0;
if (dwRedMask)
while(((dwRedMask>>nShiftsRed)&1)==0) nShiftsRed++;
int nShiftsAlpha=0;
if (dwAlphaMask)
while(((dwAlphaMask>>nShiftsAlpha)&1)==0) nShiftsAlpha++;

//各チャンネルの最大値を算出します。
int maxRed =dwRedMask>>nShiftsRed;
int maxGreen=dwGreenMask>>nShiftsGreen;
int maxBlue =dwBlueMask>>nShiftsBlue;
int maxAlpha=dwAlphaMask>>nShiftsAlpha;

//ピクセルビットを各チャンネルに分解してから、改めて並べなおします。
DWORD* src=(DWORD*)pvBits;
DWORD* dst=src;
for (int i=0;i<nPixels;i++){
DWORD dw=*src++;
DWORD r=(maxRed)? ((dw&dwRedMask)>>nShiftsRed)*0xFF/maxRed :0;
DWORD g=(maxGreen)? ((dw&dwGreenMask)>>nShiftsGreen)*0xFF/maxGreen :0;
DWORD b=(maxBlue)? ((dw&dwBlueMask)>>nShiftsBlue)*0xFF/maxBlue :0;
DWORD a=(maxAlpha)? ((dw&dwAlphaMask)>>nShiftsAlpha)*0xFF/maxAlpha :0;
*dst++=((b&0x000000FF)|
((g<<8)&0x0000FF00)|
((r<<16)&0x00FF0000)|
((a<<24)&0xFF000000));
}
}
}

//アルファチャンネルが、使われているかどうか調べます。
DWORD* src=(DWORD*)pvBits;
DWORD* dst=src;
BOOL bUsed=FALSE;
for (int i=0;i<nPixels;i++){
DWORD dw=*src++;
if (dw&0xFF000000){
bUsed=TRUE;
break;
}
}

//アルファチャンネルが使われていない場合は、24ビットDIBに変更します。
if (!bUsed){
pBmi->bmiHeader.biBitCount=24;
//ビットマップバイト幅の算出
DWORD widthBytes24=WIDTHBYTES(24*width);
//ピクセルデータのバイト数
DWORD dwSizeImage24=widthBytes24*height;
pBmi->bmiHeader.biSizeImage=dwSizeImage24;
//24ビットDIBセクションを作成します。
LPVOID pvBits24=NULL;
HBITMAP hbm24=::CreateDIBSection(NULL,pBmi,DIB_RGB_COLORS,&pvBits24,NULL,NULL);
if (hbm24){
//32ビットDIBのピクセルビットを、24ビットDIBにコピーします。
for(UINT i=0;i<height;i++){
PBYTE src=(PBYTE)pvBits+i*widthBytes;
PBYTE dst=(PBYTE)pvBits24+i*widthBytes24;
for(UINT j=0;j<width;j++){
*dst++=*src++;
*dst++=*src++;
*dst++=*src++;
src++;
}
}
//DIBハンドルの内容を取り替えます。
::DeleteObject(hBitmap);
hBitmap=hbm24;
}
}
}

//第二引数のlpdwCompressionパラメータが有効な場合は、圧縮方法を保存しておきます。
if (!::IsBadWritePtr(lpdwCompression,sizeof(DWORD)))
*lpdwCompression=dwCompression;

return hBitmap;
}


//カラーマスク値が正しいかどうか
// dwMasks[]:カラーマスク値の配列
// nMask:カラーマスク値の配列数

BOOL __stdcall _IsColorMasks(const DWORD dwMasks[],int nMask=3)
{
//全マスク値の論理和を格納する変数
LONG maskSum=0;

for(int i=0;i<nMask; ++i){
//ビットが連続しているかどうかチェックします。
LONG mask=dwMasks[i];
LONG n=mask+(mask&-mask);

//連続していないか、ビットが重なっている場合は、エラー終了します。
if (((n&(n-1))!=0)||(maskSum&mask)) return FALSE;

//論理和していきます。
maskSum|=mask;
}
return TRUE;
}



hBitmapの内容が、読み込んだDIBファイルから作成したDIBのハンドルになります。

サンプルプログラム(VisualC++net2003ソリューション)DIBFileTest01.zip


上記のzipファイルをダウンロードしてからWinRAR等で解凍し、DIBFileTest01フォルダー内にある「DIBFileTest.sln」を開きます。
「F5」キーを押すと、ビルド確認のダイヤログが表示されるので「Yes」を選択してソリューションをビルドします。



ビルドが終わると直ちにプログラムが自動起動してウィンドウが表示されます。
ウィンドウのメニューで「ファイル」/「開く」を選択して、「ファイルを開く」ダイヤログを表示させます。



resフォルダー内の「mlucc1.bmp」を選択し、「開く」ボタンを押すと、下のような画像が表示されるか確認します。







posted by ひろし at 23:52| Comment(0) | DIBファイルフォーマット | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。
×

この広告は180日以上新しい記事の投稿がないブログに表示されております。