DIB(デバイス独立ビットマップ)をファイルに書き込む場合は、以下のような流れになります。
- DIBハンドルからDIBSECTION構造体を取得し、必要な各値を算出する。
- BITMAPINFO構造体のサイズを算出する。
- BITMAPFILEHEADER構造体をファイルに書き込む。
- BITMAPINFOHEADER構造体をファイルに書き込む。
- カラーテーブルがあればファイルに書き込む。
- マスクビットフィールドがあればファイルに書き込む。
- 圧縮形式に従ってピクセルビットをファイルに書き込む。
新規にファイルを作成して、_DIBStreamOut関数を呼び出します。
//DIBをファイルに書き出します。
BOOL __stdcall _SaveDIB(LPCTSTR lpszFileName,HBITMAP hbmDIB,DWORD dwCompression)
{
CFile outfile;
if (!outfile.Open(lpszFileName,CFile::modeCreate|CFile::modeWrite)) return FALSE;
BOOL bResult=_DIBStreamOut(&outfile,hbmDIB,dwCompression);
outfile.Close();
//書き込みに失敗した場合は、ファイルを削除します。
if (!bResult) ::DeleteFile(lpszFileName);
return bResult;
}
DIBをファイルに書き出します。
//DIBファイルストリームの書き込み
BOOL __stdcall _DIBStreamOut(CFile* outfile,HBITMAP hbmDIB,DWORD dwCompression)
{
ASSERT(outfile->IsKindOf(RUNTIME_CLASS(CFile)));
//現在のファイル位置を取得しておきます。
ULONGLONG posStart=outfile->GetPosition();
//DIBハンドルからBITMAPINFO構造体を取得します。
DWORD dwSizeOfBitmapInfo=_FillBitmapInfo(hbmDIB,NULL,0,NULL);
if (!dwSizeOfBitmapInfo) return FALSE;
CByteArray arrayBmi;
arrayBmi.SetSize(dwSizeOfBitmapInfo);
BITMAPINFO* pBmi=(BITMAPINFO*)arrayBmi.GetData();
LPVOID pvBits=NULL;
_FillBitmapInfo(hbmDIB,pBmi,dwSizeOfBitmapInfo,&pvBits);
//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;
//引数dwCompressionの内容をBITMAPINFO構造体に反映します。
//ランレングス圧縮の場合はイメージサイズを0にします。
if ((dwCompression==BI_RLE4)&&(nBpp==4)){
pBmi->bmiHeader.biSizeImage=0;
pBmi->bmiHeader.biCompression=BI_RLE4;
}
else if ((dwCompression==BI_RLE8)&&(nBpp==8)){
pBmi->bmiHeader.biSizeImage=0;
pBmi->bmiHeader.biCompression=BI_RLE8;
}
//32ビットDIBの場合
else if (nBpp==32){
//カラーマスクのない32ビットDIBから、カラーマスクのある32ビットDIBに変更する場合。
if ((dwCompression==BI_BITFIELDS)&&
(pBmi->bmiHeader.biCompression==BI_RGB))
{
dwSizeOfBitmapInfo=sizeof(BITMAPINFOHEADER)+sizeof(DWORD)*4;
arrayBmi.SetSize(dwSizeOfBitmapInfo);
pBmi=(BITMAPINFO*)arrayBmi.GetData();
pBmi->bmiHeader.biSize=dwSizeOfBitmapInfo;
((BITMAPV4HEADER*)pBmi)->bV4RedMask =0x00FF0000;
((BITMAPV4HEADER*)pBmi)->bV4GreenMask=0x0000FF00;
((BITMAPV4HEADER*)pBmi)->bV4BlueMask =0x000000FF;
((BITMAPV4HEADER*)pBmi)->bV4AlphaMask=0xFF000000;
}
//カラーマスクのある32ビットDIBから、カラーマスクのない32ビットDIBに変更する場合。
else if ((dwCompression==BI_RGB)&&
(pBmi->bmiHeader.biCompression==BI_BITFIELDS))
dwSizeOfBitmapInfo=sizeof(BITMAPINFOHEADER);
}
//BITMAPFILEHEADER構造体を書き込みます。
BITMAPFILEHEADER fh;
fh.bfType=0x4D42; //DIBファイルの形式 ’BM’
fh.bfOffBits=sizeof(BITMAPFILEHEADER)+dwSizeOfBitmapInfo;
fh.bfSize=fh.bfOffBits+dwSizeImage;
fh.bfReserved1=fh.bfReserved2=0;
outfile->Write(&fh,sizeof(BITMAPFILEHEADER));
//BITMAPINFO構造体を書き込みます。
outfile->Write(pBmi,dwSizeOfBitmapInfo);
//ピクセルビットを書き込みます。
switch(pBmi->bmiHeader.biCompression){
case BI_RLE4:
//4ビットランレングス圧縮の場合は、_EncodeRle4関数でRLE4圧縮して書き込みます。
dwSizeImage=(DWORD)_EncodeRle4(pvBits,dwSizeImage,width,height,outfile);
break;
case BI_RLE8:
//8ビットランレングス圧縮の場合は、_EncodeRle8関数でRLE8圧縮して書き込みます。
dwSizeImage=(DWORD)_EncodeRle8(pvBits,dwSizeImage,width,height,outfile);
break;
case BI_RGB:
case BI_BITFIELDS:
default:
//圧縮されていない場合は、そのまま書き込みます。
outfile->Write(pvBits,dwSizeImage);
return TRUE;
}
//ランレングス圧縮で書き込めなかった場合は、エラー終了します。
if (!dwSizeImage) return FALSE;
//ファイルサイズを取得して、BITMAPFILEHEADER構造体のbfSizeに格納します。
fh.bfSize=(DWORD)outfile->GetLength();
//ファイル先頭に戻ってからBITMAPFILEHEADERを書き直します。
outfile->Seek(posStart,CFile::begin);
outfile->Write(&fh,sizeof(BITMAPFILEHEADER));
return TRUE;
}
//DIBハンドルからBITMAPINFO構造体を取得します。
DWORD __stdcall _FillBitmapInfo(HBITMAP hbmDIB,BITMAPINFO* pBmi,DWORD dwMemSize,VOID** ppvBits)
{
//DIBSECTION構造体の取得
DIBSECTION ds={0};
//入力されたDIBが正しくないか、DIBではない場合は、エラー終了します。
if ((!::GetObject(hbmDIB,sizeof(DIBSECTION),&ds))||
(ds.dsBmih.biSize!=sizeof(BITMAPINFOHEADER))) return FALSE;
if (!::IsBadWritePtr(ppvBits,sizeof(LPVOID)))
*ppvBits=ds.dsBm.bmBits;
//DIBの幅
UINT width =ds.dsBmih.biWidth;
//DIBの高さ(絶対値)
UINT height=abs(ds.dsBmih.biHeight);
//DIBのビット深度
UINT nBpp=ds.dsBmih.biBitCount;
//ビットマップバイト幅の算出
DWORD widthBytes=WIDTHBYTES(nBpp*width);
//ピクセルデータのバイト数
DWORD dwSizeImage=widthBytes*height;
//カラーテーブルのサイズを算出します。
DWORD dwClrUsed=ds.dsBmih.biClrUsed;
UINT nColors=0;
//カラーテーブルがあって使用色数が0でないなら、エントリー数は使用色数を、
//それ以外は2のnBpp乗を代入します。
if (nBpp<=8) nColors=(dwClrUsed)? dwClrUsed:1<<nBpp;
DWORD dwSizeOfColorTable=sizeof(RGBQUAD)*nColors;
//マスクビットフィールドがあれば、そのサイズを算出します。
DWORD dwSizeOfMaskBit=0;
if (ds.dsBmih.biCompression==BI_BITFIELDS){
if (nBpp==16) dwSizeOfMaskBit=sizeof(DWORD)*3;
else if (nBpp==32){
dwSizeOfMaskBit=sizeof(DWORD)*4;
ds.dsBmih.biSize+=dwSizeOfMaskBit;
}
}
//BITMAPINFO構造体のサイズを算出します。
DWORD dwSizeOfBitmapInfo=sizeof(BITMAPINFOHEADER)+dwSizeOfColorTable+dwSizeOfMaskBit;
//引数のバッファのメモリーが十分ではない場合は、BITMAPINFO構造体のサイズをそのまま返して終了します。
if ((dwMemSize<dwSizeOfBitmapInfo)||(::IsBadWritePtr(pBmi,dwMemSize)))
return dwSizeOfBitmapInfo;
//BITMAPINFOHEADERを書き込みます。
ds.dsBmih.biSizeImage=dwSizeImage;
::ZeroMemory(pBmi,dwMemSize);
::CopyMemory(&pBmi->bmiHeader,&ds.dsBmih,sizeof(BITMAPINFOHEADER));
//カラーテーブルがあれば、書き込みます。
if (dwSizeOfColorTable){
CByteArray arrayRgbq;
arrayRgbq.SetSize(dwSizeOfColorTable);
RGBQUAD* pColorTable=(RGBQUAD*)arrayRgbq.GetData();
//確保した領域にDIBのカラーテーブルを読み出します。
_GetDIBColorTable(hbmDIB,0,nColors,pColorTable);
::CopyMemory(pBmi->bmiColors,pColorTable,dwSizeOfColorTable);
}
//マスクビットフィールドがあれば、書き込みます。
else if(dwSizeOfMaskBit>=sizeof(DWORD)*3){
::CopyMemory(&pBmi->bmiColors,&ds.dsBitfields,sizeof(DWORD)*3);
if (dwSizeOfMaskBit>sizeof(DWORD)*3)
((BITMAPV4HEADER*)pBmi)->bV4AlphaMask=0xFF000000;
}
return dwSizeOfBitmapInfo;
}
//DIBのカラーテーブルからRGB(赤、緑、青)カラーの値を取得
UINT __stdcall _GetDIBColorTable(HBITMAP hBitmap,UINT iStart,UINT cEntries,RGBQUAD *prgbq)
{
HDC hMemDC=::CreateCompatibleDC(0);
HGDIOBJ hOldObj=::SelectObject(hMemDC,hBitmap);
UINT uResult=::GetDIBColorTable(hMemDC,iStart,cEntries,prgbq);
::SelectObject(hMemDC,hOldObj);
::DeleteDC(hMemDC);
return uResult;
}
戻り値がTRUEなら、ファイルへの書き込みは成功です。
サンプルプログラム(VisualC++.Net 2003ソリューション)DIBFileTest04.zip
上記のzipファイルをダウンロードしてからWinRAR等で解凍し、DIBFileTest04フォルダー内にある「DIBFileTest.sln」を開きます。「F5」キーを押すと、ビルド確認のダイヤログが表示されるので「Yes」を選択してソリューションをビルドします。
ビルドが終わると直ちにプログラムが自動起動してウィンドウが表示されます。ウィンドウのメニューで「ファイル」/「開く」を選択して、「ファイルを開く」ダイヤログを表示させ、resフォルダー内の「Luchi8bit.bmp」を読み込みます。
画像が表示されたところで「ファイル」/「名前を付けて保存」で「ファイル名を付けて保存」ダイヤログを表示させ、「Luchi8bit-Copy.bmp」と名づけて「保存」ボタンを押します。
ツールバーの「新規」で画像を一旦を消しておいてから、先ほど作成した「Luchi8bit-Copy.bmp」を読み込んで、元の画像と同じ画像が表示されるか確認します。