2010年08月17日

DIBのビット深度の変更



DIBビット深度の変更は以下のような流れになります。

  1. ビットマップハンドルからDIBSECTION構造体を取得し、幅と高さを算出します。
  2. BITMAPINFO構造体を作成する。
  3. 空のDIBを作成する。
  4. DIBビット深度を変換したピクセルビットを書き込む



ビットフィールドマスクタイプの識別を簡素化するために、独自の定数を定義しました。

//ビットフィールド識別のための定数値
#define RGB555 0x007C3E1F //赤:0x007C00 緑:0x0003E0 青:0x00001F
#define RGB565 0x00F87E1F //赤:0x00F800 緑:0x0007E0 青:0x00001F
#define RGB888 0x00FFFFFF //アルファ:0x00000000 赤:0x00FF0000 緑:0x0000FF00 青:0x000000FF
#define RGBA8888 0xFFFFFFFF //アルファ:0xFF000000 赤:0x00FF0000 緑:0x0000FF00 青:0x000000FF



DIBセクションをただ単にコピーするだけなら、API関数CopyImage関数のfuFlagsパラメータにLR_CREATEDIBSECTIONを入れて呼び出せば出来ますが、複写先DIBのビット深度はモノクロDIB以外はすべて32ビットRGBになってしまいます。そこでビット深度も忠実に再現するコピー関数を作成しました。

//DIBセクションのコピー(忠実コピー)
HBITMAP __stdcall _CopyDIB(HBITMAP hBitmap)
{
//DIBハンドルからBITMAPINFO構造体を取得します。
DWORD dwSizeOfBitmapInfo=_FillBitmapInfo(hBitmap,NULL,0,NULL);
//BITMAPINFO構造体のサイズの取得に失敗した場合は、CopyImage関数でDIBのコピーを作成して終了します。
if(!dwSizeOfBitmapInfo) return (HBITMAP)CopyImage(hBitmap,IMAGE_BITMAP,0,0,LR_CREATEDIBSECTION);

CByteArray arrayBmi;
arrayBmi.SetSize(dwSizeOfBitmapInfo);
BITMAPINFO* pBmi=(BITMAPINFO*)arrayBmi.GetData();
LPVOID pvBitsSrc=NULL;
_FillBitmapInfo(hBitmap,pBmi,dwSizeOfBitmapInfo,&pvBitsSrc);

//転送先のDIBセクションを作成します。
LPVOID pvBitsDst=NULL;
HBITMAP hbmDIB=::CreateDIBSection(NULL,pBmi,DIB_RGB_COLORS,&pvBitsDst,NULL,NULL);
//作成に失敗したら、エラー終了します。
if (!hbmDIB) return FALSE;

//イメージピクセルをDIBに書き込みます。
::CopyMemory(pvBitsDst,pvBitsSrc,pBmi->bmiHeader.biSizeImage);

return hbmDIB;
}


指定されたビット深度マスクビット(16ビットと32ビットDIBのみ有効)に従って、ビット深度を変更したDIBセクションのコピーを作成します。

//指定されたビット深度とマスクビット(16ビットと32ビットDIBのみ有効)に従って、
//ビット深度を変更したDIBセクションのコピーを作成します。

HBITMAP __stdcall _CopyDIB(HBITMAP hBitmap,UINT nBitsPerPixel,UINT nMaskType)
{
//入力されたDIBハンドルが無効な値の場合、エラー終了します。
DIBSECTION ds={0};
if (!::GetObject(hBitmap,sizeof(DIBSECTION),&ds)) return FALSE;
HBITMAP hbmDIB=hBitmap;
//指定したビット深度が適正値ではないか、入力したビットマップがDIBではない場合は、新たにDIBを作成します。
if ((!_IsDIBitCount(nBitsPerPixel))||
(ds.dsBmih.biSize!=sizeof(BITMAPINFOHEADER)))
{
hbmDIB=(HBITMAP)::CopyImage(hBitmap,IMAGE_BITMAP,0,0,LR_CREATEDIBSECTION);
//新たに作成したDIBのDIBSECTION構造体を取得します。
if (!::GetObject(hbmDIB,sizeof(DIBSECTION),&ds)) return FALSE;
//指定したビット深度が適正値ではない時は、作成したDIBをそのまま返して終了します。
if (!_IsDIBitCount(nBitsPerPixel)) return hbmDIB;
}

//元のDIBのビット深度と変更するビット深度が同じ場合で
//ビット深度が16ビットでも32ビットでもなく、
//マスクタイプが変更するマスクタイプと同じなら
//新たに作成したDIBを返して終了します。

if ((ds.dsBmih.biBitCount==nBitsPerPixel)&&
(((nBitsPerPixel!=16)&&(nBitsPerPixel!=32))||
(nMaskType==_GetColorMaskType(ds.dsBitfields))))
{
//新しいDIBを作成していない場合は、DIBの忠実コピー関数で作成します。
if (hbmDIB==hBitmap) hbmDIB=_CopyDIB(hBitmap);
return hbmDIB;
}

//DIBの幅
UINT width =ds.dsBmih.biWidth;
//DIBの高さ(絶対値)
UINT height=abs(ds.dsBmih.biHeight);
//DIBのビット深度
UINT nBpp=ds.dsBmih.biBitCount;

//転送先のカラーテーブルのエントリー数の算出
UINT uColors=(nBitsPerPixel>8)?0:1<<nBitsPerPixel;

//転送先のカラーテーブルのサイズの算出
DWORD dwSizeOfColorTable=(uColors)?sizeof(RGBQUAD)*uColors:0;

//マスクビットフィールドのサイズを算出
DWORD dwSizeOfMaskBit=0;
if ((nBitsPerPixel==16)&&(nMaskType==RGB555))
dwSizeOfMaskBit=sizeof(DWORD)*3;
else if ((nBitsPerPixel==32)&&(nMaskType==RGBA8888)){
dwSizeOfMaskBit=sizeof(DWORD)*4;
ds.dsBmih.biSize+=dwSizeOfMaskBit;
}

//転送先のBITMAPINFO構造体のサイズを算出する
DWORD dwSizeOfBitmapInfo=sizeof(BITMAPINFOHEADER)+dwSizeOfColorTable+dwSizeOfMaskBit;

//転送先のBITMAPINFO構造体を作成する
CByteArray arrayBmi;
arrayBmi.SetSize(dwSizeOfBitmapInfo);
BITMAPINFO* pBmi=(BITMAPINFO*)arrayBmi.GetData();
::ZeroMemory(pBmi,dwSizeOfBitmapInfo);

//転送元のBITMAPINFOHEADER構造体をコピー
::CopyMemory(&pBmi->bmiHeader,&ds.dsBmih,sizeof(BITMAPINFOHEADER));
pBmi->bmiHeader.biCompression=(dwSizeOfMaskBit)?BI_BITFIELDS:BI_RGB; //圧縮形式
pBmi->bmiHeader.biBitCount =nBitsPerPixel; //ビット深度
DWORD widthBytes=WIDTHBYTES(nBitsPerPixel*width); //ビットマップバイト幅の算出
DWORD dwSizeImage=widthBytes*height; //ピクセルデータのバイト数
pBmi->bmiHeader.biSizeImage=widthBytes*height; //ピクセルデータの算出
pBmi->bmiHeader.biClrUsed=(uColors>=256)? 0:uColors; //使用色数
//マスクビット領域がある場合
if (dwSizeOfMaskBit)
{
if (nBitsPerPixel==16){
if (nMaskType==RGB565){
((BITMAPV4HEADER*)pBmi)->bV4RedMask =0xF800; //RGB565
((BITMAPV4HEADER*)pBmi)->bV4GreenMask=0x07E0;
((BITMAPV4HEADER*)pBmi)->bV4BlueMask =0x001F;
}
else{
((BITMAPV4HEADER*)pBmi)->bV4RedMask =0x7C00; //RGB555
((BITMAPV4HEADER*)pBmi)->bV4GreenMask=0x03E0;
((BITMAPV4HEADER*)pBmi)->bV4BlueMask =0x001F;
}
}
else if (nBitsPerPixel==32){
((BITMAPV4HEADER*)pBmi)->bV4RedMask =0x00FF0000; //RGBA8888
((BITMAPV4HEADER*)pBmi)->bV4GreenMask=0x0000FF00;
((BITMAPV4HEADER*)pBmi)->bV4BlueMask =0x000000FF;
((BITMAPV4HEADER*)pBmi)->bV4AlphaMask=0xFF000000;
}
}

//DIBセクションを作成します。
LPVOID pvBits;
HBITMAP hbmDst=::CreateDIBSection(NULL,pBmi,DIB_RGB_COLORS,&pvBits,0,0);
if (!hbmDst) {
//新たに転送元DIBを作成していたら、削除する。
if (hbmDIB!=hBitmap) ::DeleteObject(hbmDIB);
return FALSE;
}

//マスクビット領域があってビット深度が16ビットの場合
if((pBmi->bmiHeader.biCompression==BI_BITFIELDS)&&(nBitsPerPixel==16))
{
//デバイスコンキスト作成
HDC hDstDC=::CreateCompatibleDC(NULL);
HDC hSrcDC=::CreateCompatibleDC(NULL);

HGDIOBJ hDstObj=::SelectObject(hDstDC, hbmDst);
HGDIOBJ hSrcObj=::SelectObject(hSrcDC, hbmDIB);

//RGB565へ転送して変換する
::BitBlt(hDstDC,0,0,width,height,hSrcDC,0,0,SRCCOPY);

::SelectObject(hDstDC, hDstObj);
::SelectObject(hSrcDC, hSrcObj);

::DeleteDC(hDstDC);
::DeleteDC(hSrcDC);
::GetObject(hbmDst,sizeof(DIBSECTION),&ds);

}
else{
HDC hDC=::GetDC(NULL);
//hbmDIBのピクセルビットをpBmiで指定された形式に変換して、pvBitsにコピーする
int nLine=::GetDIBits(hDC,hbmDIB,0,height,pvBits,pBmi,DIB_RGB_COLORS);
::ReleaseDC(NULL,hDC);

//カラーテーブルがある時は、カラーテーブルをDIBに設定する
if (nBitsPerPixel<=8) _SetDIBColorTable(hbmDst,0,uColors,pBmi->bmiColors);
}
//ほかの形式からアルファビット付32ビットDIBへ変換する場合で、
//アルファビットがすべて0x00の時は、アルファビットを0xFFにする

if ((nBitsPerPixel==32)&&(pBmi->bmiHeader.biCompression==BI_BITFIELDS))
{
UINT uNumOfBits=dwSizeImage>>2;
BOOL bAlphaBit=FALSE;
DWORD* chk=(DWORD*)pvBits;
//アルファビットがすべて0x00かどうか、スキャンする
for(UINT i=0;i<uNumOfBits;i++){
if ((*chk++)&0xFF000000){
bAlphaBit=TRUE;
break;
}
}
DWORD* src=(DWORD*)pvBits;
DWORD* dst=src;
//すべて0x00ならアルファビットを0xFFにする
if (!bAlphaBit){
for(UINT i=0;i<uNumOfBits;i++)
*dst++=((*src++)|0xFF000000);
}
}

//新たに転送元DIBを作成していたら、削除する。
if (hbmDIB!=hBitmap) ::DeleteObject(hbmDIB);
//作成した転送先DIBを返して終了する。
return hbmDst;
}

//ビット深度が適正な値かどうか?
BOOL __stdcall _IsDIBitCount(UINT nBitsPerPixel)
{
UINT nBpp[6]={1,4,8,16,24,32};
for (int i=0;i<6;i++)
if (nBpp[i]==nBitsPerPixel) return TRUE;
return FALSE;
}

//DIBのカラーテーブルにRGB(赤/緑/青)カラーの値を設定
UINT __stdcall _SetDIBColorTable(HBITMAP hBitmap,UINT iStart,
UINT cEntries,const RGBQUAD *prgbq)
{
HDC hMemDC=::CreateCompatibleDC(NULL);
HGDIOBJ hOldObj=::SelectObject(hMemDC,hBitmap);
UINT uResult=::SetDIBColorTable(hMemDC,iStart,cEntries,prgbq);
::SelectObject(hMemDC,hOldObj);
::DeleteDC(hMemDC);
return uResult;
}

//カラーマスク値のカラーマスクタイプを取得します。
// 戻り値:RGB555/RGB565/RGB888/RGBA8888、エラー値:FALSE

int __stdcall _GetColorMaskType(LPDWORD lpColorMasks)
{
if (::IsBadReadPtr(lpColorMasks,sizeof(DWORD)*3)) return FALSE;
DWORD r=lpColorMasks[0];
DWORD g=lpColorMasks[1];
DWORD b=lpColorMasks[2];
if (b==0x00001F&&g==0x0003E0&&r==0x007C00) return RGB555;
else if (b==0x00001F&&g==0x0007E0&&r==0x00F800) return RGB565;
else if (b==0x0000FF&&g==0x00FF00&&r==0xFF0000){
if (::IsBadReadPtr(lpColorMasks,sizeof(DWORD)*4)) return RGB888;
else if (lpColorMasks[3]==0xFF000000) return RGBA8888;
}
return FALSE;
}



hbmDstの内容が、ビット深度を変更して作成したDIBのハンドルになります。

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

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



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



24ビットフルカラー画像が表示されます。



画像が表示されたところで「編集」/「ビット深度の変更」で「ビット深度の変更」ダイヤログを表示させ、「ビット深度」コンボボックスで8ビットを選択します。



256色パレットDIBに減色した画像が表示されます。



次に「Ctrl+Z」で24ビットフルカラー画像に戻しておいてから、再び「編集」/ビット深度の変更」で「ビット深度の変更」ダイヤログを表示させ、「ビット深度」コンボボックスで今度は4ビットを選択します。



16色パレットDIBに減色した画像が表示されます。



最後に「Ctrl+Z」で24ビットフルカラー画像に戻しておいてから、再び「編集」/ビット深度の変更」で「ビット深度の変更」ダイヤログを表示させ、「ビット深度」コンボボックスで今度は1ビットを選択します。



モノクロDIBに減色した画像が表示されます。



尚16/24/32ビットに変更する場合、画像の変化は肉眼では判別できないので、「ビット深度の変更」ダイヤログの初期設定値で確かめてください。




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

メールアドレス:

ホームページアドレス:

コメント:

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


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

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