ビットマップの表示
前ページではリソースファイルを作成して、ビットマップ画像をリソースに追加する方法を説明しました。
このビットマップをウィンドウ上に表示してみます。
ビットマップの表示は
- リソースからビットマップを読み込む
- ビットマップの情報を取得する
(サイズ情報のみを使用する) - メモリデバイスコンテキストを作成する
- メモリデバイスコンテキストにビットマップをコピー
- メモリデバイスコンテキストからデバイスコンテキストにビットマップを転送
(=画面に表示)
という手順で行います。
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_APPLICATION
やIDC_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」をインクルードする必要があります。
最後に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);
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
)の幅と高さの指定(wSrc
とhSrc
)が追加されているだけです。
このサイズ情報を利用して拡大/縮小を行います。
#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関数で二倍に拡大したビットマップを描画しています。
≫