ファイルからビットマップを読み込む16,256色(1)

ビットマップを読み込む方法は基本的には2つあってリソースに挿入してそこから読み込む 方法と、ファイルから読み込む方法とあります。リソースから読み込んだ方が簡単なのですが ビット情報を直接操作することが難しいので、後々紹介するビットを直接操作して透過色を 実現するという方法が使えません。
またリソースにして読み込むとEXEファイルが膨大に なってしまうので、たくさんのビットマップファイルを使いたいときやファイルを圧縮して 使いたい場合には、ファイルから読み込んだ方がよいでしょう。

前置きはこのへんして、中身に入ります。

16,256色のビットマップは
BITMAPFILEHEADER,BITMAPINFOHEADER,RGBQUADの配列,Color-Indexの配列からなるデータ群です。 読み込んで表示するためにはこれらの変数の意味を理解しなければなりませんので、それぞれを 説明していきます。

BITMAPFILEHEADER構造体

 
 WORD    bfType; //ビットマップを表す数 :0x4D42
 DWORD   bfSize; //ビットマップファイルのサイズ
 WORD    bfReserved1;//予約されている :0
 WORD    bfReserved2;//予約されている :0
 DWORD   bfOffBits;//ビット列が格納されている位置を表す 

ここで、必要になる変数はこのファイルがビットマップかどうかを表すbfType とビット列が格納されている位置を表すbfOffBitsです

BITMAPINFOHEADER構造体

 
 DWORD  biSize; //構造体のサイズ
 LONG   biWidth; //ビットマップの幅(ピクセル)
 LONG   biHeight; //ビットマップの高さ(ピクセル)
 WORD   biPlanes; //常に1
 WORD   biBitCount //1ピクセル辺りのビット数
 DWORD  biCompression;//圧縮形態 無圧縮ならばBI_RGB(16,256色の場合) 
 DWORD  biSizeImage;//画像のバイト数 biCompressionがBI_RGBの場合は0でもよい  
 LONG   biXPelsPerMeter;//X方向の1ピクセル辺りのメートル数 
 LONG   biYPelsPerMeter;//Y方向の1ピクセル辺りのメートル数 
 DWORD  biClrUsed;//カラーテーブルに含まれる色の数(0場合はその型の最大になる) 
 DWORD  biClrImportant; //重要な色の数 0の場合は全部重要

biWidth,biHeightでビットマップの幅と高さを,biPlanesとbiClrUsedで何色のビットマップファイルかが わかります。

RGBQUAD構造体

 
 BYTE    rgbBlue;  
 BYTE    rgbGreen; 
 BYTE    rgbRed; 
 BYTE    rgbReserved; 

青、赤、緑の明るさを指定します。rgbReservedは0にします

ビットの配列は、直前のパレットのインデックスで表します。

ビットマップを読み込んで描画する

ここでは、実際にビットマップを読み込む動作を説明します。
流れを説明しますと

1 BITMAPFILEHEADERを読み込む(ビットマップで無い場合はじく)
2 BITMAPINFOHEADERを読み込む(16,256で無い場合ははじく)
3 BITMAPINFOHEADER構造体とRGBQUAD構造体の配列からなるBITMAPINFO構造体の領域を 動的に確保する
4 CreateDIBSectionを使って、ビットマップビット列の領域を確保する
5 実際に描画する
6 プログラムが終了したときや、上書き読み込みをする場合は確保した領域を開放する

このままでは意味不明なので、順を追って説明します。

1と2はそのままなので分かりますよね。問題は3と4だと思います。
5番の実際に描画するという部分で、今回はBitBltを使います。 そうなるとBITMAPのハンドルを取得してそれをメモリDCに選択するという動作が必要に なりますよね。そこで必要となる関数がCreateDIBSectionなのです。 CreateDIBSectionに3のBITMAPINFO構造体を含むいろいろな引数をいれてやると返戻値として HBITMAPを返し、ビットマップのビット列の領域も確保してくれます。

BitBltを使わない方法としては、StretchDIBitsがあります。ただこちらだとラスタオペレーションが 使えないのと、速度が若干遅い(?)のでやめておきます。

やっとコーディングだ・・・。

えーと、今回はC++のクラスを使いますのであらかじめご了承ください。

ヘッダ部分
class CDIB
{
public:
    CDIB();
    ~CDIB();
    BOOL Load(char*,HDC);
    void Draw(HDC hdc,int x,int y);

private:
    void Free();
    BITMAPINFO *pBmpInfo;
    BYTE       *pBits;
    HBITMAP BMP;
    HANDLE h;
    int used;

private:
	int Width(){return pBmpInfo->bmiHeader.biWidth;}
	int Height(){return pBmpInfo->bmiHeader.biHeight;}
         int Num(){return used;}
};

関数部分
CDIB::CDIB()
{
    pBmpInfo=NULL;
    pBits=NULL;
}

CDIB::~CDIB()
{
    Free();
}

void CDIB::Free()
{
    //割り当てあったメモリを解放する
    if(BMP)
    {
        DeleteObject(BMP);
        BMP=NULL;
        pBits=NULL;        
    }
    
    if(pBmpInfo)
    {
        free(pBmpInfo);
        pBmpInfo=NULL;
    }
}

BOOL CDIB::Load(char*filename,HDC hdc)
{   	    
    int PSize;//パレット領域のサイズ
    int BitsSize;//ビット領域のサイズ
    int IPSize;//インフォヘッダーとパレット領域を合わせたサイズ
    DWORD number;
    BITMAPFILEHEADER BmpFileHdr;
    BITMAPINFOHEADER BmpInfoHdr;


    Free();//割り当てた領域の解放

    //ファイルを開く
    h=CreateFile(filename,GENERIC_READ,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);
    if(h==INVALID_HANDLE_VALUE) return FALSE;

    
    //ファイルヘッダーを読み込む
    ReadFile(h,&BmpFileHdr,sizeof BmpFileHdr,&number,NULL);
    
   
    //インフォヘッダーを読み込む
    ReadFile(h,&BmpInfoHdr,sizeof BmpInfoHdr,&number,NULL);
    
    
    //16色と256色しかサポートしません(色数も決め打ちしてます)
    switch (BmpInfoHdr.biBitCount)
    {
    case 8:
        used=256;
        break;
    case 4:
        used=16;
        break;
    default:
        CloseHandle(h);
        return FALSE;
    }

    //パレット領域のサイズ
    PSize=used*sizeof(RGBQUAD);

    //インフォヘッダーと、パレットのサイズ
    IPSize=sizeof(BITMAPINFOHEADER)+PSize;
    
    //画像のビットのサイズ
    BitsSize=BmpFileHdr.bfSize-BmpFileHdr.bfOffBits;
    
    //(ヘッダー+パレット)の領域を確保
    pBmpInfo=(LPBITMAPINFO)malloc(IPSize);
    memcpy(pBmpInfo,&BmpInfoHdr,sizeof(BITMAPINFOHEADER));//ヘッダをコピー
    //パレットを読み込む
    ReadFile(h,((LPBYTE)pBmpInfo)+sizeof BITMAPINFOHEADER,PSize,&number,NULL);
    
    //CreateDIBSectionを使う
    BMP=CreateDIBSection(hdc,pBmpInfo,DIB_RGB_COLORS,(VOID**)&pBits,0,0);

    //ビットの位置までシークして
    SetFilePointer(h,BmpFileHdr.bfOffBits,NULL,FILE_BEGIN);

    //ビットを読み込む
    ReadFile(h,pBits,BitsSize,&number,NULL);

    CloseHandle(h);
    return TRUE;
}

void CDIB::Draw(HDC hdc,int x,int y)
{
    //メモリデバイスコンテキストを作成してビットマップを割り当て描画する
    HDC mem=CreateCompatibleDC(hdc);
    HBITMAP old;
    old=SelectObject(mem,BMP);
    BitBlt(hdc,0,0,Width(),Height(),mem,0,0,SRCCOPY);
    SelectObject(mem,old);
    DeleteDC(mem);
}

使い方
 CDIB dib; //どこかで定義する


 case WM_CREATE:
     
      if(!dib.Load("ou.bmp",GetDC(hwnd))) 
       {
         MessageBox(hwnd,"読み込みに失敗しました、終了します","",MB_OK);
         PostQuitMessage(1);
       }
       break;

 case WM_PAINT:
      hdc=BeginPaint(hwnd,&ps);
      dib.Draw(hdc,0,0);
      EndPaint(hwnd,&ps);
      break;


今回はこの辺にしておきます。このまま使ってもビットマップは描画できますが、パレットの操作をしていないので 画面を256色モードで実行すると色化けしてしまいます。

でもこれってあってんのかな・・・。

戻る