ビットマップの表示

前ページではリソースファイルを作成して、ビットマップ画像をリソースに追加する方法を説明しました。
このビットマップをウィンドウ上に表示してみます。

ビットマップの表示は

  • リソースからビットマップを読み込む
  • ビットマップの情報を取得する
    (サイズ情報のみを使用する)
  • メモリデバイスコンテキストを作成する
  • メモリデバイスコンテキストにビットマップをコピー
  • メモリデバイスコンテキストからデバイスコンテキストにビットマップを転送
    (=画面に表示)

という手順で行います。

LoadBitmap関数

リソースからビットマップを取得するにはLoadBitmap関数を使用します。

HBITMAP LoadBitmapW(
 HINSTANCE hInstance,
 LPCWSTR lpBitmapName
);
インスタンスhInstanceからビットマップlpBitmapNameを読み込む。
戻り値はビットマップハンドル。

第一引数hInstanceは、リソースが含まれるモジュールのインスタンスです。
リソースは実行ファイルに含めるので、ここは自分自身のインスタンスを指定します。

第二引数lpBitmapNameはビットマップ名ですが、これはファイル名ではなく識別子を指定します。
識別子はリソーススクリプトに記述されています。


// Microsoft Visual C++ generated resource script.
//
#include "resource.h"

//途中省略

/////////////////////////////////////////////////////////////////////////////
//
// Bitmap
//

IDB_BITMAP1             BITMAP                  "sample.bmp"

IDB_BITMAP1がビットマップの識別子です。
ただしこの識別子をそのまま(あるいは文字列で)指定するのではなく、MAKEINTRESOURCEというマクロを使用する必要があります。


//ダメ
hBmp = LoadBitmap(hInst, IDB_BITMAP1);

//ダメ
hBmp = LoadBitmap(hInst, L"IDB_BITMAP1");

//OK
hBmp = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BITMAP1));

戻り値はHBITMAP型で、ビットマップハンドルです。

リソースを指定する識別子および文字列について

リソーススクリプトの先頭では「resource.h」がインクルードされています。
「resource.h」内ではdefineでIDB_BITMAP1を数値に置き換えています。


//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ で生成されたインクルード ファイル。
// Resource.rc で使用
//
#define IDB_BITMAP1                     101

つまりこの識別子は実際には「101」という数値なわけです。

LoadBitmap関数の第二引数はLPCWSTR型(文字列)を要求しますが、ここには「下位ワードはリソースID、上位ワードはゼロ」という値を持つ文字列型を指定することもできます。
この値はMAKEINTRESOURCEマクロに識別子を指定することで作ることができます。
(このような値を指定可能な関数は他にも結構あります)

識別子は単なる数値ですから、以下の二つはどちらも同じ意味になります。


hBmp = LoadBitmap(hInst, MAKEINTRESOURCE(101));
hBmp = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BITMAP1));

リソーススクリプト内の識別子を手動で書き換えると、defineで置き換えられなくなります。
この場合、識別子はそのままの文字列で扱うことになります。
例えば以下のように書き換えてみます。


/////////////////////////////////////////////////////////////////////////////
//
// Bitmap
//

//IDB_BITMAP1             BITMAP                  "sample.bmp"
MYBITMAP             BITMAP                  "sample.bmp"

このリソースを読み込む場合はMAKEINTRESOURCEマクロは使用せず、識別子をそのまま文字列として指定します。


hBmp = LoadBitmap(hInst, L"MYBITMAP");

もちろん「resource.h」内も同じように編集すれば定数で扱うことができます。

なお、ウィンドウクラスの登録時に使用したLoadIcon関数やLoadCursor関数なども同じ仕組みです。
これらの関数にはIDI_APPLICATIONIDC_ARROWといった値を直接指定しましたが、これらはリソースの識別子ではなく「リソースIDにMAKEINTRESOURCEマクロを使用した値をdefineした定数」なので、改めてMAKEINTRESOURCEマクロを使用する必要はありません。


#define IDI_APPLICATION     MAKEINTRESOURCE(32512)
#define IDC_ARROW           MAKEINTRESOURCE(32512)

DeleteObject関数

作成したビットマップハンドルは、不要になったらDeleteObject関数で破棄する必要があります。

GetObject関数

取得したビットマップからサイズ情報を取得します。
ビットマップの情報はGetObject関数で取得できます。

int GetObject(
 HANDLE h,
 int c,
 LPVOID pv
);
グラフィックオブジェクトhの情報をバッファpvに格納する。
cはバッファpvのサイズ。
戻り値はバッファpvに書き込まれた情報のバイト数。
バッファpvにNULLを指定した場合は情報を格納するのに必要なバイト数を返す。

第一引数hは情報を取得したいGDIオブジェクトのハンドルを指定します。
(ビットマップのほか、ペン、ブラシ、フォント、パレットなど)
ここでは先ほど取得したビットマップハンドルを指定します。

第二引数cは情報を格納する変数のサイズです。
ビットマップの情報はBITMAP構造体に格納されるため、このサイズを指定します。
つまりsizeof(BITMAP)を指定します。

第三引数pvは取得した情報を格納する変数へのポインタです。
ここではBITMAP構造体の変数のポインタを指定します。
LPVOID型はvoid*型のことで、特定のデータ型に依存しないポインタ型です。
(malloc関数の戻り値などでも使用されています)


HBITMAP	hBmp;	//ビットマップハンドル
BITMAP bmp;		//ビットマップ情報を保存する構造体

hBmp = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BITMAP1));

//ビットマップ情報の取得
GetObject(hBmp, sizeof(BITMAP), &bmp);

BITMAP構造体

typedef struct tagBITMAP {
 LONG bmType;
 LONG bmWidth;
 LONG bmHeight;
 LONG bmWidthBytes;
 WORD bmPlanes;
 WORD bmBitsPixel;
 LPVOID bmBits;
} BITMAP, *PBITMAP, *NPBITMAP, *LPBITMAP;
ビットマップ情報を保存する構造体。

bmTypeメンバはビットマップのタイプですが、これは常に0です。
bmWidthメンバはビットマップの幅、bmHeightメンバは高さです。
bmWidthBytesメンバは各スキャンライン(行)のバイト数で、必ず偶数になります。
bmPlanesメンバはカラープレーン数で、標準的なビットマップでは常に1です。
(カラープレーン=色情報を持つ「面」の枚数。普通は1枚ですべての色情報を持つので値は1)
bmBitsPixelメンバは1ピクセルのビット数で、フルカラーなら32です。
bmBitsメンバは画像の実データへのポインタです。

サイズ情報はbmWidthメンバとbmHeightメンバです。
これ以外の情報は今回は使用しません。

CreateCompatibleDC関数

文字列を描画するTextOut関数DrawText関数などは、描画対象のデバイスコンテキストを指定してそこに描画していました。
ビットマップの場合は直接デバイスコンテキストに描画ということはできず、いったんメモリ上の仮想的な画面に描画してからデバイスコンテキストに転送します。
この仮想的な画面をメモリデバイスコンテキストと言います。

メモリデバイスコンテキストを作成するにはCreateCompatibleDC関数を使用します。

HDC CreateCompatibleDC(
 HDC hdc
);
デバイスコンテキストhdcと互換性のあるメモリデバイスコンテキストを作成する。

この関数は引数に指定したデバイスコンテキストと互換の、つまりデータを転送可能なメモリデバイスコンテキストを作ります。

SelectObject関数

LoadBitmap関数で作成したビットマップハンドルはSelectObject関数でメモリデバイスコンテキストに割り当てることができます。
(直接デバイスコンテキストに割り当てることはできません)


//メモリデバイスコンテキストの取得
hmdc = CreateCompatibleDC(hdc);

//メモリデバイスコンテキストにビットマップハンドルをセット
SelectObject(hmdc, hBmp);

これで「仮想的な画面」にビットマップが描画された状態になります。
ただしこれはメモリ上でのことで、実際の画面にはまだ描画されません。

DeleteDC関数

作成したメモリデバイスコンテキストは、不要になったらDeleteDC関数で破棄する必要があります。

BOOL DeleteDC(
 HDC hdc
);
デバイスコンテキストハンドルhdcを削除する。
成功した場合は0以外を、失敗した場合は0を返す。

この関数は自ら「作成」したデバイスコンテキストを削除する場合にのみ使用します。
BeginPaint関数などで「取得」したデバイスコンテキストは削除してはなりません。

BitBlt関数

メモリデバイスコンテキストからデバイスコンテキストにビットマップを転送するにはBitBlt関数を使用します。

BOOL BitBlt(
 HDC hdc,
 int x,
 int y,
 int cx,
 int cy,
 HDC hdcSrc,
 int x1,
 int y1,
 DWORD rop
);
デバイスコンテキストhdcSrcからデバイスコンテキストhdcに矩形のピクセルデータを転送する。
成功した場合は0以外を返す。

少し引数が多いですが、それほど難しくはありません。

第一引数hdcはデータ転送の宛先、つまりコピー先となるウィンドウのデバイスコンテキストです。
第二引数xはコピー先のx座標(横位置)、第三引数yはy座標(縦位置)です。
第四引数cxはコピー先の幅、第五引数cyは高さです。
幅と高さはビットマップから取得した幅と高さを指定します。

第六引数hdcSrcはデータの転送元、つまりメモリデバイスコンテキストです。
第七引数x1はコピー元のx座標、第八引数y1はy座標です。

最後の引数ropラスタオペレーションというものの指定です。
これは描画しようとしている領域に既に描画されているデータ(ピクセル)との重ね合わせ方の指定です。
詳しくは改めて説明しますが、単純に表示するだけならSRCCOPYという定数を指定します。


BitBlt(
	hdc, 0, 0, bmp.bmWidth, bmp.bmHeight,
	hmdc, 0, 0, SRCCOPY);

これで実際の画面にビットマップが表示されるようになります。

サンプルコード

以上の関数を使用してビットマップを表示するサンプルコードです。


#include <windows.h>
#include "resource.h"

//ウィンドウの生成等は省略

//ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	HDC hdc, hmdc;	//メモリデバイスコンテキスト
	PAINTSTRUCT ps;

	HBITMAP	hBmp;	//ビットマップハンドル
	BITMAP bmp;		//ビットマップ情報を保存する構造体

	switch (message)
	{
	case WM_PAINT: //ウィンドウの描画
		hdc = BeginPaint(hWnd, &ps);

		//ビットマップの読み込み
		hBmp = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BITMAP1));

		//ビットマップ情報の取得
		GetObject(hBmp, sizeof(BITMAP), &bmp);

		//メモリデバイスコンテキストの取得
		hmdc = CreateCompatibleDC(hdc);

		//メモリデバイスコンテキストにビットマップハンドルをセット
		SelectObject(hmdc, hBmp);

		//メモリデバイスコンテキストから
		//デバイスコンテキストにデータを転送
		BitBlt(
			hdc, 0, 0, bmp.bmWidth, bmp.bmHeight,
			hmdc, 0, 0, SRCCOPY);

		EndPaint(hWnd, &ps);

		//メモリデバイスコンテキストと
		//ビットマップハンドルの削除
		DeleteDC(hmdc);
		DeleteObject(hBmp);
		break;

	case WM_DESTROY: //ウィンドウの破棄
		PostQuitMessage(0);
		break;

	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

上記コードの実行結果です。
リソースを利用するには先頭で「resource.h」をインクルードする必要があります。
ビットマップ画像の表示1

最後にDeleteObject関数でビットマップハンドルを、DeleteDC関数でメモリデバイスコンテキストを削除しておくのを忘れないようにしましょう。
これを忘れると、変数自体は破棄されますが確保されたメモリは破棄されずに残り続けメモリリークが発生します。

ビットマップ転送の際にはサイズと座標の指定ができますから、例えば一枚のビットマップから必要な箇所だけを表示する、ということも可能です。


int w = bmp.bmWidth;
int h = bmp.bmHeight;

BitBlt(
	hdc, 0, 0, w / 2, h / 2,
	hmdc, w / 2, h / 2, SRCCOPY);

BitBlt(
	hdc, w / 2, 0, w / 2, h / 2,
	hmdc, 0, h / 2, SRCCOPY);

BitBlt(
	hdc, 0, h / 2, w / 2, h / 2,
	hmdc, w / 2, 0, SRCCOPY);

BitBlt(
	hdc, w / 2, h / 2, w / 2, h / 2,
	hmdc, 0, 0, SRCCOPY);

ビットマップ画像の表示2

StretchBlt関数

BitBlt関数はビットマップを原寸のまま表示するだけでした。
実際のプログラムでは原寸では困ることも多いので、拡大や縮小の処理が必要になります。
それにはStretchBlt関数を使用します。

BOOL StretchBlt(
 HDC hdcDest,
 int xDest,
 int yDest,
 int wDest,
 int hDest,
 HDC hdcSrc,
 int xSrc,
 int ySrc,
 int wSrc,
 int hSrc,
 DWORD rop
);
デバイスコンテキストhdcSrcからデバイスコンテキストhdcに矩形のピクセルデータを拡大または縮小して転送する。
成功した場合は0以外を返す。

これも引数が多いですが、BitBlt関数の引数にコピー元(hdcSrc)の幅と高さの指定(wSrchSrc)が追加されているだけです。
このサイズ情報を利用して拡大/縮小を行います。


#include <windows.h>
#include "resource.h"

//ウィンドウの描画等は省略

//ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	HDC hdc, hmdc;	//メモリデバイスコンテキスト
	PAINTSTRUCT ps;

	HBITMAP	hBmp;	//ビットマップハンドル
	BITMAP bmp;		//ビットマップ情報を保存する構造体

	switch (message)
	{
	case WM_PAINT: //ウィンドウの描画
		hdc = BeginPaint(hWnd, &ps);

		hBmp = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BITMAP1));
		GetObject(hBmp, sizeof(BITMAP), &bmp);

		hmdc = CreateCompatibleDC(hdc);
		SelectObject(hmdc, hBmp);

		//通常の描画
		BitBlt(
			hdc, 0, 0, bmp.bmWidth, bmp.bmHeight,
			hmdc, 0, 0, SRCCOPY);
		
		//2倍に拡大して描画
		StretchBlt(
			hdc, bmp.bmWidth, 0, bmp.bmWidth * 2, bmp.bmHeight * 2,
			hmdc, 0, 0, bmp.bmWidth, bmp.bmHeight, SRCCOPY);

		EndPaint(hWnd, &ps);

		DeleteDC(hmdc);
		DeleteObject(hBmp);
		break;

	case WM_DESTROY: //ウィンドウの破棄
		PostQuitMessage(0);
		break;

	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

このコードの実行結果です。
BitBlt関数で原寸のビットマップを描画した隣に、StretchBlt関数で二倍に拡大したビットマップを描画しています。
ビットマップを2倍にして表示