これまで、グラフィック処理の研究などで何度も32ビットDIBを扱ってきましたが、これまではビットマップの作成や破棄、ピクセルの操作など毎回同じような処理を書いていましたね。ソースを見ても、DIB関連の処理は毎回同じような部分がけっこうあり元々読み易いとは言い難いソースをさらに読みにくくしているような気もします。そこで、DIBに関するデータと処理をDIBクラスSDIB32にまとめ、32ビットDIBの処理をすっきりと書けるようにしてみましょう。今回は、まずDIBのごく基本的な機能を実装してみます。
DIBをクラス化するにあたり、まず外部に公開するインターフェースを考えてみましょう。オブジェクト指向の世界では、オブジェクトを利用する時にはオブジェクトが外部に公開しているインターフェースにメッセージを送り、オブジェクト自身がそのメッセージに応じて処理を行う、という形になります。ですので、まず公開インターフェースを定義してしまえば、そのオブジェクトをどう扱えばよいか、またそのオブジェクトがどう振舞うか、という「全体像」がほぼ見えてくるのです。インターフェースが定まって全体像が出来たら、次に内部で必要なデータや機能を実装してインターフェースへの応答を定義していくわけですね。
とりあえず、今回はビットマップの最低限の機能としてコンストラクタ、幅・高さの取得とピクセルの取得・設定、デバイスコンテキストへの描画、デストラクタといったあたりを盛り込んで見ました。C++ではオブジェクトのインターフェースをオブジェクトを定義するクラス内でpublicな関数として定義するので、
public:
SDIB32(int,int);
void draw(HDC,I32,I32);
int getWidth();
int getHeight();
DWORD getRGB(int,int);
bool setRGB(int,int,int);
~SDIB32();
という感じでしょう。次に、このインターフェースに応答して処理を行うために必要な内部データを考えてみます。まず、DIB自身の実体としてBITMAPINFOとピクセル列バッファが必要です。これらのバッファはまとめて管理した方が楽なので、BITMAPINFOとピクセル列バッファの分のメモリをまとめて管理するポインタも必要でしょう。そして、幅・高さの変数、という辺りでしょうか。これらのデータは、privateなメンバ変数にしておきます。
これで定義すべき関数とデータが用意できたので、クラスの宣言をヘッダにまとめておきましょう。
class SDIB32 {
public:
SDIB32(int,int);
void draw(HDC,int,int);
int getWidth();
int getHeight();
DWORD getRGB(int,int);
bool setRGB(int,int,DWORD);
~SDIB32();
private:
int iWidth,iHeight;
LPBITMAPINFO lpbiInfo;
LPDWORD lpPixel;
LPVOID lpDIB;
};
続いて関数の実装に入ります。まず、オブジェクトを生成するコンストラクタは、引数に幅と高さを指定して呼び出すとDIBオブジェクトのポインタを返します。
SDIB32::SDIB32(int iW,int iH) {
iWidth=iW;
iHeight=iH;
// DIB用メモリ確保
lpDIB=GlobalAlloc(GPTR,sizeof(BITMAPINFO)+iWidth*iHeight*4);
// 確保したメモリのポインタ設定
lpbiInfo=(LPBITMAPINFO)lpDIB;
lpPixel=(LPDWORD)((LPBYTE)lpDIB+sizeof(BITMAPINFO));
// BITMAPINFO設定
lpbiInfo->bmiHeader.biSize=sizeof(BITMAPINFOHEADER);
lpbiInfo->bmiHeader.biWidth=iWidth;
lpbiInfo->bmiHeader.biHeight=iHeight;
lpbiInfo->bmiHeader.biPlanes=1;
lpbiInfo->bmiHeader.biBitCount=32;
lpbiInfo->bmiHeader.biCompression=BI_RGB;
}
コンストラクタでは、ヘッダとピクセル列バッファ用のメモリをまとめてlpDIBに確保し、分配してからヘッダの設定を行っています。これで、lpPixelにピクセル列バッファのポインタが設定されたので、以降はクラス内ならlpPixel[x+y*iWidth]という形で(x, y)のピクセルにアクセスできるようになりました。ただ、lpPixelは外部からはアクセスできないprivateメンバなので、外部からピクセル列バッファにアクセスするインターフェースを定義しておきましょう。
DWORD SDIB32::getRGB(int iX,int iY) { // (iX,iY)の色取得
if (iX<0 || iX>=iWidth || iY<0 || iY>=iHeight)
return 0;
return lpPixel[iX+iY*iWidth];
}
bool SDIB32::setRGB(int iX,int iY,DWORD dwCl) { // (iX,iY)の色設定
if (iX<0 || iX>=iWidth || iY<0 || iY>=iHeight)
return FALSE;
lpPixel[iX+iY*iWidth]=dwCl;
return TRUE;
}
ピクセル列の取得・設定関数では、ついでにエラーチェックも入れておきました。このようなエラーチェックを入れて不正な操作を防止できるのも、データを外部からは直接アクセスできないようにする「カプセル化」の利点ですね。幅・高さの取得に関しても、同様に取得用インターフェースを定義しておきましょう。
int SDIB32::getWidth() { // 横幅取得
return iWidth;
}
int SDIB32::getHeight() { // 高さ取得
return iHeight;
}
描画関数は、描画するデバイスコンテキストと描画位置を受け取って描画します。
void SDIB32::draw(HDC hdc,int iX,int iY) { // DIB描画
StretchDIBits(hdc,iX,iY,iWidth,iHeight,0,0,iWidth,iHeight,
lpPixel,lpbiInfo,DIB_RGB_COLORS,SRCCOPY);
}
デストラクタは、メモリの解放のみ。
SDIB32::~SDIB32() { // デストラクタ
GlobalFree(lpDIB); // DIB用メモリ解放
}
これで、ビットマップの作成・ピクセル操作・解放と一通りの処理が出来ましたね。
S32DIBのインスタンスを生成し、中央に紫の線を引いてみました。