Windows API - グラフ描画クラス編 第2回 〜グラフィックデバイスインターフェース〜

ビットマップ

CreateCompatibleBitmap() API 関数

前回、サブクラス化というものを説明するところで力尽き、肝心の再描画まで たどり着けなかったので、ここでは再描画することを考えます。

前回、少し触れましたが、DC は、画材や画用紙やらのセットというような表現をしました。

この画材やら画用紙やらのことを、グラフィックデバイスインターフェースというわけですね。

で、このビットマップっていうのが、ちょうど画用紙に相当するわけです。そこで、 前回やろうとしていた、スタティックコントロールのクライアント DC から、

HBITMAP hBitmap=CreateCompatibleBitmap(hStaticDc, x, y);

という API 関数を使って hBitmap という GDI のハンドルを発行します。 ここで、第2引数と第3引数は、ビットマップの横幅と縦幅を表す値になります。

もし、スタティックウィンドウのクライアントエリア全体のビットマップが必要ならば、

BOOL GetClientRect(HWND, LPRECT);

という API 関数を用いることによって、スタティックウィンドウの大きさが取得できます。 第2引数は RECT 構造体のポインタで、この構造体の right メンバが横幅、bottom メンバが 縦幅となります。

SelectObject() API 関数と DeleteObject() API 関数

ビットマップのような GDI オブジェクトへのハンドルが発行できたら、 これをメモリ DC に選択させます。

HGDIOBJ oldObj=SelectObject(hMemDc, newObj);

SelectObject() API 関数は引数として、GDI オブジェクトへのハンドルを取り、 選択する前の GDI オブジェクトを返します。

通常、GDI オブジェクトを自分で生成した場合、これを解放するとともに、 元々選択されていた GDI オブジェクトを選択しなおす必要があります。

DeleteObject(SelectObject(hMemDc, oldObj));

こうすることで、もともと選択されていた GDI オブジェクトが選択されると同時に、 自分で生成した GDI オブジェクトが返ってくると同時に、DeleteObject() API 関数によって 解放されます。

PatBlt() API 関数 と BitBlt() API 関数

ビットマップをメモリ DC に選択した後は、いよいよ描画をしていくわけですが、 まず、そのビットマップを背景色で塗りつぶしておきます。 その作業を行うのが、

PatBlt(hMemDc, x, y, width, height, dwRop);

という API 関数になります。4つの int 型は左上の座標と、横幅、縦幅ですが、 ここでは、全体をクリアするので結局、GetClientRect() と同じ値が入ることとなります。

第6引数は、ラスタオペレーションなるものが入りますが、難しいことを考えず、 定数 WHITENESS を入れておくことで、白く塗りつぶされます。

前置きが長くなりましたが、ようやく、再描画の話に入ります。 何をすればいいかといえば、メモリ DC の内容をクライアント DC にそのままコピーするわけですが、 それを行うのが、

BitBlt(hDc, tX, tY, width, height, hMemDc, rX, rY, dwRop);

という API 関数です。PatBlt() に比べ、3つほど引数が増えましたが、 要するに、最初の5つの引数がコピー先である、クライアント DC とそのコピーする範囲になります。 第6引数はコピー元であるメモリ DC のハンドルで、第7,8 引数がコピー元の左上座標です。

何度も言ってるように、全面をそのままコピーするわけですから、 何も考えなくとも、第2,3,7,8 引数は全て 0 となります。

最後の第9引数は PatBlt() と同様にラスタオペレーションですが、 白く塗りつぶしても仕方がないので、今回は定数 SRCCOPY なるものを指定します。

というわけで、再描画の作業をまとめると、BeginPaint() API 関数で スタティックコントロールのクライアント DC を取得した後は、 BitBlt() API 関数でメモリ DC からコピーし、 最後に、EndPaint() API 関数を使えばよいということになります。

論理 GDI と GDI オブジェクト

前回、HDC を第1引数にとる API 関数をいくつか紹介しましたが、 そのいずれも、背景色や境界線の色は白、黒と固定で、また、 境界線の太さなども指定することが出来ませんでした。

このような要素は、それぞれの GDI オブジェクトが DC とは独立して管理を行います。 そして、DC が GDI オブジェクトを選択するために、SelectObject() API 関数を用います。

LOGPEN 構造体

まず、境界線を変更してみます。線の要素として、線の種類、線の幅、そして線の色という 3つが考えられます。この3つの要素を、LOGPEN という構造体が管理します。

typedef struct tagLOGPEN{
 UINT     lopnStyle;
 POINT    lopnWidth;
 COLORREF lopnColor;
} LOGPEN;

もちろん、線の色は lopnColor メンバが持ちます。lopnWidth メンバですが、 線の幅を持つのは、lopnWidth のメンバの中の1つである x となります。 y というメンバもありますが、こちらは使いません。

で、lopnStyle についてですが、次のような定数が定義されます。

PS_SOLID      実線
PS_DASH       破線
PS_DOT        点線
PS_DASHDOT    1点鎖線
PS_DASHDOTDOT 2点鎖線
PS_NULL       NULL ペン

NULL ペンは、線の描画を行いません。また、PS_SOLID 以外は、 lopnWidth.x の値を 0 とします。 逆に言えば、線の太さが指定できるのは、PS_SOLID の時だけとなります。

HPEN ハンドル

LOGPEN 構造体 pen のメンバを初期化すると、この構造体を元にペンオブジェクトを 発行することが出来ます。

HPEN CreatePenIndirect(const LOGPEN*);

このようにして発行したペンは、

HGDIOBJ oldPen=SelectObject(hMemDc, CreatePenIndirect(&pen));

などとして選択します。当然、ペンを利用し終えたら、

DeleteObject(SelectObject(hMemDc, oldPen));

などとして解放する必要があります。

また、LOGPEN 構造体のメンバを変更した場合も、

DeleteObject(SelectObject(hMemDc, CreatePenIndirect(&pen)));

とすれば新しいペンを選択することが出来ます。

LOGBRUSH 構造体

一方、背景を塗りつぶすブラシというものがあり、要素として、 ブラシのスタイル、ブラシの色、ブラシのハッチという3つを持ちます。

typedef struct tagLOGBRUSH{
 UINT     lbStyle;
 COLORREF lbColor;
 LONG     lbHatch;
};

何も考えずに、lbColor で塗りつぶす場合は、lbStyle を定数 BS_SOLID とします。

また、塗りつぶさない場合、定数 BS_NULL を指定すれば NULL ブラシとなります。

ここで、lbStyle に定数 BS_HATCHED を指定すると、中を連続した直線で塗りつぶす ハッチブラシとなります。このとき、lbHatch に指定可能なものには 以下のようなものが挙げられます。

HS_HORIZONTAL  -----
HS_VERTICAL    |||||
HS_FDIAGONAL   \\\\\
HS_BDIAGONAL   /////
HS_CROSS       +++++
HS_DIAGCROSS   xxxxx

HBRUSH ハンドル

要領は、HPEN の場合と同様で、

HBRUSH CreateBrushIndirect(const LOGBRUSH*);

という API 関数を呼び出せば、ブラシオブジェクトのハンドルが発行されます。

HGDIOBJ hOldBrush=SelectObject(
 hMemDc, CreateBrushIndirect(&brush)
);

などと選択し、描画が終わったら、

DeleteObject(SelectObject(hMemDc, hOldBrush));

とします。

フォントとメトリックス

LOGFONT 構造体と HFONT ハンドル

DrawText() などで描画する文字列に関しては、文字色や背景色が API 関数で 変更出来ましたが、文字の大きさや太さなどという、フォントに関する情報は、 フォントオブジェクトという GDI が管理します。

で、その情報を持つ構造体が、LOGFONT 構造体ですが、やたらとメンバがたくさんあります。

全部初期化の段階で指定してもかまいませんが、通常、 ほとんどのメンバを 0 としても差し支えないので、構造体を初期化する段階では、

LOGFONT font={0};

などとした方がスムーズに済みます。

代表的なメンバを以下に挙げます。

LONG lfHeight         文字の大きさ
LONG lfEscapement     文字送りの角度
LONG lfWeight         文字の太さ
BYTE lfItalic         斜体
BYTE lfUnderline      下線
BYTE lfCharSet        キャラクタセット
BYTE lfPitchAndFamily ピッチとファミリ

文字の大きさは、pt ではなく px が単位になります。文字の太さに関しては、 定数 ... FW_LIGHT, FW_NORMAL, FW_MEDIUM, FW_SEMIBOLD, FW_BOLD ... などと、 100 刻みで定義されています。lfItalic, lfUnderline などは、1 を指定すると、 斜体、下線が引かれます。

キャラクタセットは余程の事情がない限り、SHIFTJIS_CHARSET とします。

lfPitchAndFamily に関しては、ピッチに関するフラグ、

DEFAULT_PITCH  デフォルト
FIXED_PITCH    固定幅
VARIABLE_PITCH 可変幅

と、フォントファミリに関するフラグ、

FF_DONTCARE    一般的なフォント
FF_ROMAN       セリフありのプロポーショナルフォント
FF_SWISS       セリフなしのプロポーショナルフォント
FF_MODERN      モノスペースフォント
FF_SCRIPT      手書き風のフォント
FF_DECORATIVE  装飾付きフォント

を論理和で指定します。

実際に、フォントオブジェクトを発行する場合は、 ペンやブラシの場合と全く要領は同じです。

HFONT CreateFontIndirect(const LOGFONT*);

CreateFontIndirect() API 関数でハンドルを発行して、これを SelectObject() します。そして、描画が終われば、DeleteObject() します。

TEXTMETRIC 構造体

このようにして、フォントが選択できるわけですが、実際の文字の幅や高さなどを 取得するためには、TEXTMETRIC 構造体というものを使用します。 この構造体は、フォントを選択した状態で、メモリ DC を引数にとる、

BOOL GetTextMetrics(HDC, LPTEXTMETRIC);

という API 関数を使います。第2引数は、TEXTMETRIC 構造体へのポインタとなります。

この TEXTMETRIC 構造体もやたらとメンバが多いわけですが、代表的なものを以下に挙げます。

LONG tmHeight       文字の高さ
LONG tmAveCharWidth 小文字の平均幅(1バイト文字の幅)
LONG tmMaxCharWidth 文字の最大幅(2バイト文字の幅)
LONG tmWeight       文字の太さ

ここまでで、一応のグラフィックの取り扱いに関する説明を中断します。

ビットマップに関しては非常に奥が深く、DC に関しても説明が全く足りません。 ついでに言うと、まだ説明してない GDI がもう1つありますが、知りません。

次回より、ここまでの応用として、グラフ描画クラスを設計・実装していきたいと思います。


文章作成 : yukki-ts (-+-twilight serenade-+- [stage])