ビットマップの表示2
前ページではウィンドウ上にビットマップを表示する方法を説明しました。
ここではこれをもう少し掘り下げて説明します。
ラスタオペレーション
BitBlt関数やStretchBlt関数には、引数にラスタオペレーションというものがあります。
これは既に描画されているコピー先ピクセルと、これから描画するコピー元ピクセルとの色の合成方法の指定です。
前ページで使用した定数SRCCOPY
は、合成は行わずにコピー元のピクセルで上書きするという意味です。
(何も描画していない状態でも、背景色が描画されています)
定数は以下のものがあります。
定数 | 説明 |
---|---|
SRCCOPY | コピー元のピクセルをコピー先のピクセルへそのまま描画する。 |
SRCPAINT | コピー元とコピー先のピクセルを論理OR演算で合成する。 |
SRCAND | コピー元とコピー先のピクセルを論理AND演算で合成する。 |
SRCINVERT | コピー元とコピー先のピクセルを論理XOR演算で合成する。 |
SRCERASE | コピー先の色を反転し、コピー元のピクセルと論理AND演算で合成する。 |
NOTSRCCOPY | コピー元の色を反転し、コピー先に描画する。 |
NOTSRCERASE | コピー元とコピー先のピクセルを論理OR演算で合成し、さらに反転する。 |
MERGECOPY | コピー先のデバイスコンテキストで選択されているブラシの色と、コピー元のピクセルを論理AND演算で合成する。 |
MERGEPAINT | コピー元の色を反転し、コピー先のピクセルと論理OR演算で合成する。 |
PATCOPY | コピー先のデバイスコンテキストで選択されているブラシを出力先のビットマップにコピーする。 |
PATPAINT | コピー先のデバイスコンテキストで選択されているブラシの色と、反転したコピー元のピクセルを論理OR演算で合成する。 それをさらにコピー先のピクセルと論理OR演算で合成する。 |
PATINVERT | コピー先のデバイスコンテキストで選択されているブラシの色と、コピー先のピクセルを論理XOR演算で合成する。 |
DSTINVERT | コピー先のピクセルを反転する。 |
BLACKNESS | 物理パレットのインデックス0番の色(初期値は黒)でコピー先のピクセルを塗りつぶす。 |
WHITENESS | 物理パレットのインデックス1番の色(初期値は白)でコピー先のピクセルを塗りつぶす。 |
NOMIRRORBITMAP | ビットマップのミラーリングを防ぎます。 |
CAPTUREBLT | 自身のウィンドウに重ねられたウィンドウを結果に含める。 |
説明文だけではわかりにくいと思うので、色々と試してみると良いでしょう。
以下はSRCCOPY
、SRCPAINT
、SRCAND
、SRCINVERT
、SRCERASE
を左上から順に描画した例です。
システム定義のビットマップ
ビットマップは自前で用意する以外に、システム(Windows)があらかじめ用意しているビットマップを使用することもできます。
これらはシステムで使用されているアイコンなどの画像です。
システムが定義済みのビットマップを使用するには、windows.hをインクルードする前にOEMRESOURCE
という定数をdefineする必要があります。
また、LoadBitmap関数の第一引数(インスタンスの指定)はNULL
を指定し、識別子に以下の定数を指定します。
(MAKEINTERSOURCE
マクロを使用します)
OBM_BTNCORNERS | OBM_OLD_RESTORE |
OBM_BTSIZE | OBM_OLD_RGARROW |
OBM_CHECK | OBM_OLD_UPARROW |
OBM_CHECKBOXES | OBM_OLD_ZOOM |
OBM_CLOSE | OBM_REDUCE |
OBM_COMBO | OBM_REDUCED |
OBM_DNARROW | OBM_RESTORE |
OBM_DNARROWD | OBM_RESTORED |
OBM_DNARROWI | OBM_RGARROW |
OBM_LFARROW | OBM_RGARROWD |
OBM_LFARROWD | OBM_RGARROWI |
OBM_LFARROWI | OBM_SIZE |
OBM_MNARROW | OBM_UPARROW |
OBM_OLD_CLOSE | OBM_UPARROWD |
OBM_OLD_DNARROW | OBM_UPARROWI |
OBM_OLD_LFARROW | OBM_ZOOM |
OBM_OLD_REDUCE | OBM_ZOOMD |
OBM_OLD_から始まる定数はWindows3.0以前に使用されていたもので、現在のWindowsでは使用できません。
#define OEMRESOURCE
#include <windows.h>
//ウィンドウの生成等は省略
//ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc, hmdc;
PAINTSTRUCT ps;
RECT rect;
HBITMAP hBmp;
BITMAP bmp;
int x = 0, y = 0;
int maxHeight = 0;
switch (message)
{
case WM_PAINT: //ウィンドウの描画
GetClientRect(hWnd, &rect);
hdc = BeginPaint(hWnd, &ps);
for (int i = OBM_LFARROWI; i < OBM_OLD_CLOSE; i++) {
hBmp = LoadBitmap(NULL, MAKEINTRESOURCE(i));
GetObject(hBmp, sizeof(BITMAP), &bmp);
hmdc = CreateCompatibleDC(hdc);
SelectObject(hmdc, hBmp);
if (x > 0 && (x + bmp.bmWidth) > rect.right)
{
x = 0;
y += maxHeight;
maxHeight = bmp.bmHeight;
}
else
{
maxHeight = bmp.bmHeight > maxHeight
? bmp.bmHeight : maxHeight;
}
BitBlt(
hdc, x, y, bmp.bmWidth, bmp.bmHeight,
hmdc, 0, 0, SRCCOPY);
x += bmp.bmWidth;
DeleteDC(hmdc);
DeleteObject(hBmp);
}
EndPaint(hWnd, &ps);
break;
case WM_DESTROY: //ウィンドウの破棄
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
上記の定数をすべて描画した結果です。
(ただし順番はバラバラです)
WM_CREATEメッセージ
今までのサンプルコードでは、ビットマップ表示のために必要な処理をすべてWM_PAINT
メッセージ内で行っています。
WM_PAINTメッセージは割と頻繁に送られてくるので、処理は出来るだけ軽くしておくべきです。
ここであまり重い処理を行うと画面がちらつくことがあります。
メモリデバイスコンテキストへのビットマップの描画は、一度行ってしまえば(画像を差し替えないならば)以降はそれを使いまわすほうが効率が良いです。
そのためにはWM_PAINTメッセージではなくWM_CREATE
メッセージを利用します。
このメッセージはCreateWindow関数などでウィンドウを生成する時に一度だけ送られてくるメッセージで、ウィンドウの描画に関する初期化のための処理はこのメッセージ内で行うことができます。
GetDC関数
メモリデバイスコンテキストを作成するためにはCreateCompatibleDC関数を使用しますが、引数にはウィンドウのデバイスコンテキストが必要です。
いままでデバイスコンテキストの取得に使用していたのはBeginPaint
関数ですが、これはWM_PAINTメッセージ内でしか使用できません。
WM_PAINTメッセージ以外でデバイスコンテキストを取得するにはGetDC
関数を使用します。
- HDC GetDC(
HWND hWnd
); - ウィンドウhWndのデバイスコンテキストを取得する。
hWndがNULLの場合は画面全体のデバイスコンテキストを取得する。
取得に失敗した場合はNULLを返す。
ReleaseDC関数
GetDC関数でデバイスコンテキストを取得した場合はReleaseDC
関数で解放する必要があります。
- int ReleaseDC(
HWND hWnd,
HDC hDC
); - ウィンドウhWndのデバイスコンテキストhDCを解放する。
解放に成功した場合は1を、失敗した場合は0を返す。
プログラムの実行中(ウィンドウの表示中)にメモリデバイスコンテキストを持ち続ける場合はWM_CREATE
メッセージで取得し、WM_DESTROY
メッセージで解放すると良いでしょう。
ただしウィンドウプロシージャ内のローカル変数は、ひとつメッセージを処理する毎に破棄される(関数が終了する)ので、グローバル変数に保存するかstatic変数にしておく必要があります。
サンプルコード
これらのメッセージと関数を実際に使用してみましょう。
#include <windows.h>
#include "resource.h"
//ウィンドウの生成等は省略
//ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
//ウィンドウが存在している間保持したい変数は
//staticにしておく
static HDC hmdc;
static BITMAP bmp;
HDC hdc;
PAINTSTRUCT ps;
HBITMAP hBmp;
switch (message)
{
case WM_CREATE: //ウィンドウ作成
hBmp = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BITMAP1));
GetObject(hBmp, sizeof(BITMAP), &bmp);
hdc = GetDC(hWnd); //デバイスコンテキストの取得
hmdc = CreateCompatibleDC(hdc);
SelectObject(hmdc, hBmp);
DeleteObject(hBmp);
ReleaseDC(hWnd, hdc); //デバイスコンテキストの解放
break;
case WM_PAINT: //ウィンドウの描画
hdc = BeginPaint(hWnd, &ps);
BitBlt(
hdc, 0, 0, bmp.bmWidth, bmp.bmHeight,
hmdc, 0, 0, SRCCOPY);
EndPaint(hWnd, &ps);
break;
case WM_DESTROY: //ウィンドウの破棄
//ここでメモリデバイスコンテキストを破棄する
DeleteDC(hmdc);
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
実行結果は同じなので省略します。
主要な処理をWM_CREATE
メッセージに移動させ、WM_PAINT
メッセージの処理はBitBlt
関数だけになっています。
その分ウィンドウの再描画時の処理が軽くなっています。
PAINTSTRUCT構造体
上記コードでは、再描画が発生するたびにBitBlt関数でビットマップデータの全てをメモリデバイスコンテキストから転送しています。
ウィンドウの一部が欠けただけでもすべてのビットマップの転送を行っているので、処理に無駄があります。
これはPAINTSTRUCT構造体を上手く使うことで軽量化が可能です。
PAINTSTRUCT構造体のrcPaint
メンバには無効領域がRECT構造体で格納されています。
つまりこの無効領域の箇所を転送するだけで問題なく表示ができます。
//ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
//ウィンドウが存在している間保持したい変数は
//staticにしておく
static HDC hmdc;
HDC hdc;
PAINTSTRUCT ps;
HBITMAP hBmp;
//ビットマップ描画の座標
int x, y;
switch (message)
{
case WM_CREATE: //ウィンドウ作成
hBmp = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BITMAP1));
hdc = GetDC(hWnd); //デバイスコンテキストの取得
hmdc = CreateCompatibleDC(hdc);
SelectObject(hmdc, hBmp);
DeleteObject(hBmp);
ReleaseDC(hWnd, hdc); //デバイスコンテキストの解放
break;
case WM_PAINT: //ウィンドウの描画
BeginPaint(hWnd, &ps);
x = y = 0;
BitBlt(
ps.hdc,
ps.rcPaint.left, //x座標
ps.rcPaint.top, //y座標
ps.rcPaint.right - ps.rcPaint.left, //幅
ps.rcPaint.bottom - ps.rcPaint.top, //高さ
hmdc,
ps.rcPaint.left - x, ps.rcPaint.top - y,
SRCCOPY);
EndPaint(hWnd, &ps);
break;
case WM_DESTROY: //ウィンドウの破棄
//ここでメモリデバイスコンテキストを破棄する
DeleteDC(hmdc);
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
今回はどちらも0を指定していますが、変数x
とy
に座標を指定することで任意の位置に表示することができます。
なお、BITMAP構造体は使用しなくなったので変数とGetObject
関数は削除しています。
DeleteDC
関数とReleaseDC
関数は紛らわしいので注意してください。
基本的に、Create○○はDelete○○、Get○○はRelease○○という風に対応しています。
「生成」したものは「削除」、「取得」したものは「解放」です。
こういった「後処理」が必要は関数は、必要なデータをメモリにロードし、それを取り扱うためのハンドルを返します。
ハンドルは変数に格納しますが、適切に後処理をせずに変数の寿命が尽きてしまうとメモリ上のデータにアクセスする手段が無くなり、破棄することができないデータがメモリに残ったままになります。
(メモリリークという)
ちなみにGetObject
関数はGDIオブジェクトの情報を単純に変数に格納する関数で、ハンドルを扱うわけではないのでReleaseObject関数というものはありません。
GetStockObject
関数はストックオブジェクトを取得しますが、これには解放や削除の処理は必要はありません。
CreatePen
関数やCreateBrush
関数などはGDIオブジェクトを生成する関数で、これらはDeleteObject
関数で削除する必要があります。
LoadBitmap
関数で作成されるのもGDIオブジェクトですから、これもDeleteObject
関数で削除します。
外部ビットマップファイルの読み込み
ビットマップの表示にはリソースを利用する方法と、外部のビットマップを読み込んで表示する方法があります。
外部のビットマップを表示するにはLoadBitmap関数ではなくLoadImage
関数を使用します。
(もちろんリソースから取得することも可能)
(→LoadImage関数)
- HANDLE LoadImageW(
HINSTANCE hInst,
LPCWSTR name,
UINT type,
int cx,
int cy,
UINT fuLoad
); - アイコン、カーソル、ビットマップをロードする。
第一引数hInst
は、外部からビットマップを読み込む場合はNULL
を指定します。
第二引数name
はファイルへのパスです。
第三引数type
にはビットマップを表す定数IMAGE_BITMAP
を指定します。
第四引数cx
、第五引数cy
は0で良いです。
そして最後の引数fuLoad
にはLR_LOADFROMFILE
という定数を指定します。
これで引数name
で指定したパスのビットマップファイルを読み込むことができます。
パスの指定は絶対パスでも相対パスでも良いですが、Visual Studioからプログラムを起動している場合は相対パスの起点はプロジェクトフォルダになるので注意してください。
(→絶対パスと相対パス)
#include <windows.h>
//ウィンドウの生成等は省略
//ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static HDC hmdc;
HDC hdc;
PAINTSTRUCT ps;
HBITMAP hBmp;
BITMAP bmp;
switch (message)
{
case WM_CREATE:
//外部ビットマップファイルの読み込み
hBmp = LoadImage(
NULL,
L"C:/sample.bmp",
IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
GetObject(hBmp, sizeof(BITMAP), &bmp);
hdc = GetDC(hWnd); //デバイスコンテキストの取得
hmdc = CreateCompatibleDC(hdc);
SelectObject(hmdc, hBmp);
DeleteObject(hBmp);
ReleaseDC(hWnd, hdc); //デバイスコンテキストの解放
break;
case WM_PAINT: //ウィンドウの描画
BeginPaint(hWnd, &ps);
BitBlt(
ps.hdc,
ps.rcPaint.left, ps.rcPaint.top,
ps.rcPaint.right - ps.rcPaint.left,
ps.rcPaint.bottom - ps.rcPaint.top,
hmdc,
ps.rcPaint.left, ps.rcPaint.top,
SRCCOPY);
EndPaint(hWnd, &ps);
break;
case WM_DESTROY: //ウィンドウの破棄
//ここでメモリデバイスコンテキストを破棄する
DeleteDC(hmdc);
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}