描画処理

描画処理の基本

GDI

Graphics Device Interface (GDI) は,Windows の描画処理を担当するコンポーネントの一つです。
GDI 関数を用いることで,ディスプレイやプリンタといった出力デバイスに,図形,テキスト,画像等を描画することができます。

GDI 関数による描画は,出力デバイスを問わず,ほぼ同様の記述で行うことができます。
次に示すのは,文字列を描画する TextOut という GDI 関数 を使って,ディスプレイ,プリンタに文字列を描画するコードの例です。

TCHAR szText[] = TEXT("This is some sample text.");
HDC hdc;

// ディスプレイに文字列を描画
hdc = GetDC(hwnd);
TextOut(hdc, 0, 0, szText, lstrlen(szText));
ReleaseDC(hwnd, hdc);

// プリンタに文字列を描画
hdc = GetPrinterDC();
TextOut(hdc, 0, 0, szText, lstrlen(szText));
DeleteDC(hdc);

ここで hdc はデバイスコンテキスト (device context) と呼ばれるデータ構造へのハンドルです。
デバイスコンテキストは GDI が管理するデータ構造であり,出力デバイスの属性が格納されます。

デバイスコンテキスト

本章の残りの部分では,ウィンドウのクライアント領域に描画を行うための基本操作について説明します。

### 作例

次のような長方形を描画するプログラムを作ります。

作例

#include <windows.h>

LRESULT CALLBACK WndProc(
    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(
    HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpszCmdLine, int nCmdShow)
{
    TCHAR szAppName[] = TEXT("TestApp");
    WNDCLASS wc;
    HWND hwnd;
    MSG msg;

    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc   = WndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = szAppName;

    if (!RegisterClass(&wc)) return 0;

    hwnd = CreateWindow(
        szAppName, TEXT(""),
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT,
        CW_USEDEFAULT, CW_USEDEFAULT,
        NULL, NULL,
        hInstance, NULL);

    if (!hwnd) return 0;

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    while (GetMessage(&msg, NULL, 0, 0) > 0) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;
}

LRESULT CALLBACK WndProc(
    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;

    switch (uMsg) {
    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);       // 描画開始
        Rectangle(hdc, 50, 50, 200, 150);  // 描画
        EndPaint(hwnd, &ps);               // 描画終了
        return 0;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

更新領域

次の図のようにウィンドウを移動すると,隠れて見えていなかった部分の再描画が必要となります。
図の斜線部分は,再描画が必要な領域であることから,更新領域 (update region) と呼ばれます。
更新領域は,ウィンドウが初めて表示される時や,ウィンドウのサイズが変更された時などにも発生します。

更新領域の説明

更新領域が発生すると,システムは WM_PAINT メッセージを発行し,メッセージキューにポストします。

メッセージコード説明
WM_PAINTウィンドウの再描画が必要な時に発生

WM_PAINT メッセージは比較的優先度の低いメッセージです。
GetMessage 関数は,他のメッセージがメッセージキューから無くなるまで,WM_PAINT メッセージを取り出しません。

描画処理

ウィンドウのクライアント領域の描画は,WM_PAINT メッセージを受け取った時に行うのが基本です。

今回のプログラムの描画に関係する処理は,次のように書かれています。

case WM_PAINT:
    hdc = BeginPaint(hwnd, &ps);       // 描画開始
    Rectangle(hdc, 50, 50, 200, 150);  // 描画
    EndPaint(hwnd, &ps);               // 描画終了
    return 0;

BeginPaint は描画を開始する関数,EndPaint は描画を終了する関数です。
WM_PAINT メッセージに対する応答では,実際の描画処理を行う前後で,BeginPaint 関数と EndPaint 関数を呼び出す必要があります。

BeginPaint 関数 [MSDN]

描画を開始します。この関数は更新領域を有効化します。

HDC BeginPaint(
    HWND hWnd,             // ウィンドウハンドル
    LPPAINTSTRUCT lpPaint  // PAINTSTRUCT 構造体
);

EndPaint 関数 [MSDN]

描画を終了します。

BOOL EndPaint(
    HWND hWnd,                  // ウィンドウハンドル
    CONST PAINTSTRUCT *lpPaint  // PAINTSTRUCT 構造体
);

PAINTSTRUCT 構造体は,GDI が使用する描画情報を保持する構造体です。

PAINTSTRUCT 構造体 [MSDN]

typedef struct tagPAINTSTRUCT {
    HDC  hdc;              // デバイスコンテキスト
    BOOL fErase;           // 背景を消去するか
    RECT rcPaint;          // 再描画を行う矩形領域
    BOOL fRestore;         // 予約済
    BOOL fIncUpdate;       // 予約済
    BYTE rgbReserved[32];  // 予約済
} PAINTSTRUCT;

fErase メンバの値が TRUE の時には,BeginPaint 関数は WM_ERASEBKGND メッセージを送信し,背景の消去を行います。

他の場所での描画

WM_PAINT メッセージに対する応答以外の場所でクライアント領域の描画処理を行うには,次のように,BeginPaint, EndPaint 関数の代わりに GetDC, ReleaseDC 関数を呼び出します。

hdc = GetDC(hwnd);                 // DC 取得
Rectangle(hdc, 50, 50, 200, 150);  // 描画
ReleaseDC(hwnd, hdc);              // DC 解放

各関数のプロトタイプは次の通りです。

GetDC 関数 [MSDN]

デバイスコンテキストを取得します。

HDC GetDC(
    HWND hWnd  // ウィンドウハンドル
);

ReleaseDC 関数 [MSDN]

デバイスコンテキストを解放します。

int ReleaseDC(
    HWND hWnd,  // ウィンドウハンドル
    HDC hdc     // デバイスコンテキストへのハンドル
);

長方形の描画

長方形の描画に用いた関数は,次の Rectangle 関数です。

Rectangle 関数 [MSDN]

長方形を描画します。

BOOL Rectangle(
    HDC hdc,    // デバイスコンテキストへのハンドル
    int left,   // 左上端の x 座標
    int top,    // 左上端の y 座標
    int right,  // 右下端の x 座標
    int bottom  // 右下端の y 座標
);

詳細は 図形の描画 で説明します。

領域の無効化

図のように,マウスの左ボタンをクリックすると,カーソル位置を中心に正方形を描画するプログラムを作ります。

作例

#include <windows.h>

LRESULT CALLBACK WndProc(
    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(
    HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpszCmdLine, int nCmdShow)
{
    // 省略
}

LRESULT CALLBACK WndProc(
    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    static int x, y;
    HDC hdc;
    PAINTSTRUCT ps;

    switch (uMsg) {
    case WM_LBUTTONDOWN:
        y = HIWORD(lParam);
        x = LOWORD(lParam);
        InvalidateRect(hwnd, NULL, TRUE);  // 領域無効化
        return 0;

    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);
        Rectangle(hdc, x - 10, y - 10, x + 10, y + 10);
        EndPaint(hwnd, &ps);
        return 0;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

WM_LBUTTONDOWN メッセージへの応答では,座標の更新と,クライアント領域の無効化のみを行い,実際の描画処理は WM_PAINT メッセージへの応答に記述しています。

InvalidateRect 関数は,矩形で指定した領域を無効化する関数です。
今回は第 2 引数に NULL を指定し,クライアント領域全体を無効化しています。

InvalidateRect 関数 [MSDN]

矩形領域を更新領域に追加します。

BOOL InvalidateRect(
    HWND hWnd,           // ウィンドウハンドル
    CONST RECT *lpRect,  // 無効化する矩形領域
    BOOL bErase          // 背景を消去するか
);
lpRect
無効化する領域を矩形で指定します。クライアント領域全体を無効化するには NULL を指定します。
bErase
背景を消去するかを指定します。TRUE を指定すると,BeginPaint 関数によって背景が消去されます。

矩形領域の代わりに任意の形状のリージョンを指定できる InvalidateRgn 関数もあります。
ここでは関数のプロトタイプのみ紹介します。

InvalidateRgn 関数 [MSDN]

リージョンを更新領域に追加します。

BOOL InvalidateRgn(
    HWND hWnd,   // ウィンドウハンドル
    HRGN hRgn,   // 無効化するリージョンへのハンドル
    BOOL bErase  // 背景を消去するか
);

ペンとブラシ

作例

次の図のような,青色の枠を持ち黄色で塗り潰された長方形を描画します。

作例

#include <windows.h>

LRESULT CALLBACK WndProc(
    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(
    HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpszCmdLine, int nCmdShow)
{
    TCHAR szAppName[] = TEXT("TestApp");
    WNDCLASS wc;
    HWND hwnd;
    MSG msg;

    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc   = WndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = szAppName;

    if (!RegisterClass(&wc)) return 0;

    hwnd = CreateWindow(
        szAppName, TEXT(""),
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT,
        CW_USEDEFAULT, CW_USEDEFAULT,
        NULL, NULL,
        hInstance, NULL);

    if (!hwnd) return 0;

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    while (GetMessage(&msg, NULL, 0, 0) > 0) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;
}

LRESULT CALLBACK WndProc(
    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;
    static HPEN hpen, hpenPrev;
    static HBRUSH hbr, hbrPrev;

    switch (uMsg) {
    case WM_CREATE:
        // ペンとブラシを作成
        hpen = CreatePen(PS_SOLID, 5, RGB(0x00, 0x7f, 0xff));
        hbr = CreateSolidBrush(RGB(0xff, 0xbf, 0x00));
        return 0;

    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);

        // ペンとブラシを選択
        hpenPrev = (HPEN) SelectObject(hdc, hpen);
        hbrPrev = (HBRUSH) SelectObject(hdc, hbr);

        Rectangle(hdc, 50, 50, 200, 150);

        // 元のペンとブラシを選択
        SelectObject(hdc, hpenPrev);
        SelectObject(hdc, hbrPrev);

        EndPaint(hwnd, &ps);
        return 0;

    case WM_DESTROY:
        // ペンとブラシを削除
        DeleteObject(hpen);
        DeleteObject(hbr);

        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

GDI オブジェクト

GDI オブジェクト

長方形を描画する Rectangle 関数には,線の色や塗り潰しの色を指定するパラメータがありません。
これらの描画属性を設定するには,線の色やスタイルを表すペン (pen),塗り潰しの色やスタイルを表すブラシ (brush) を,デバイスコンテキストに対して関連付ける必要があります。
ペンやブラシは GDI オブジェクトと呼ばれるものであり,代表的な GDI オブジェクトには次のような種類があります。

GDI オブジェクトを使用する代表的な手順は,ペンを例に取ると次の通りです。

  1. 新しいペンを作成する (CreatePen 関数)。
  2. ペンをデバイスコンテキストに関連付ける (SelectObject 関数)。
  3. デバイスコンテキストの関連付けを元に戻す (SelectObject 関数)。
  4. ペンを削除する (DeleteObject 関数)。

GDI オブジェクトを作成する関数は,CreatePen,CreateFont など,作成する GDI オブジェクトの種類ごとに用意されています。
一方,SelectObject 関数,DeleteObject 関数は,GDI オブジェクトの種類を問わず共通して利用できます。

### ペンの利用

ペンオブジェクトを作成するには,次の関数が利用できます。

今回の作例で使用しているのは,次の CreatePen 関数です。

CreatePen 関数 [MSDN]

論理ペンを作成します。

HPEN CreatePen(
    int fnPenStyle,   // スタイル
    int nWidth,       // 幅
    COLORREF crColor  // 色
);
fnPenStyle
ペンのスタイルを指定します。

ペンスタイル [MSDN]

説明
PS_SOLID実線 実線
PS_DASH破線 破線
PS_DOT点線 点線
PS_DASHDOT一点鎖線 一点鎖線
PS_DASHDOTDOT二点鎖線 二点鎖線
PS_NULL空のペン 空のペン
PS_INSIDERFRAME実践 実線

PS_INSIDERFRAME スタイルは,幅が 2 以上のペンで長方形等を描画する場合に,線が枠内に収まるように図形を縮小します。
次の図は,式 Rectangle(hdc, 2, 2, 12, 10) で描画される長方形を比較したものです。
長方形の描画例
nWidth
ペンの幅を指定します。PS_NULL, PS_SOLID, PS_INSIDERFRAME 以外のスタイルでは 1 を指定する必要があります。
返り値
作成された論理ペンへのハンドルを返します。失敗時には NULL を返します。

crColor パラメータには COLORREF 値を指定しますが,これについてはすぐ後に説明します。

ペン等の GDI オブジェクトをデバイスコンテキストに関連付けるには,SelectObject 関数を呼び出します。

SelectObject 関数 [MSDN]

デバイスコンテキストに論理オブジェクトを関連付けます。同じ種類の以前の論理オブジェクトは,関連付けが解除されます。

HGDIOBJ SelectObject(
    HDC hdc,         // デバイスコンテキストへのハンドル
    HGDIOBJ hgdiobj  // 論理オブジェクトへのハンドル
);
返り値
同じ種類の以前の論理オブジェクトへのハンドルを返し,失敗時には NULL を返します。論理オブジェクトの種類がリージョンの場合には,ハンドルの代わりに次のいずれかの値を返します。
説明
SIMPLEREGION単一の矩形
COMPLEXREGION複雑な形状
NULLREGION空のリージョン
GDI_ERROR関数失敗

型 HGDIOBJ は,GDI オブジェクト全般に使えるハンドル型です。

SelectObject で GDI オブジェクトを関連付けると,それまでデバイスコンテキストに関連付けられていた同じ種類のオブジェクトへのハンドルが返ります。
新しく関連付けたオブジェクトを使い終えたら,このハンドルを使って関連付けを元に戻す必要があります。

CreatePen 等の関数で作成した GDI オブジェクトは,DeleteObject 関数によって削除する必要があります。
今回のプログラムでは,WM_CREATE への応答でペンとブラシを作成し,WM_DESTROY への応答でこれらを削除しています。

DeleteObject 関数 [MSDN]

論理オブジェクトを削除します。

BOOL DeleteObject(
    HGDIOBJ hObject  // 論理オブジェクトへのハンドル
);

COLORREF 型

COLORREF 型は次のように定義された整数型で,RGB 色を表すのに使います。

typedef DWORD COLORREF;

例えば 0x00ffbb77 という COLORREF 値は,(R, G, B) = (0x77, 0xbb, 0xff) という色を表します。

COLORREF 値を作るには,次の RGB マクロが便利です。

#define RGB(r, g, b) \
    ((COLORREF) (((BYTE) (r) \
    | ((WORD)((BYTE)(g)) << 8)) \
    | (((DWORD) (BYTE) (b)) << 16)))

また,COLORREF 値から R, G, B 個々の値を取り出すには,GetRvalue マクロ,GetGValue マクロ,GetBValue マクロが使えます。

#define GetRValue(rgb)  (LOBYTE(rgb))
#define GetGValue(rgb)  (LOBYTE(((WORD) (rgb)) >> 8))
#define GetBValue(rgb)  (LOBYTE((rgb) >> 16))

ブラシの利用

ブラシオブジェクトを作成するには,次のような関数が利用できます。

今回の作例では,単色のブラシを作成する CreateSolidBrush 関数を使っています。

CreateSolidBrush 関数 [MSDN]

単色の論理ブラシを作成します。

HBRUSH CreateSolidBrush(
    COLORREF crColor  // 色
);

CreateHatchBrush 関数を使えば,ハッチパターン (網掛けパターン) を持つブラシを作成できます。

CreateHatchBrush 関数 [MSDN]

ハッチブラシを作成します。

HBRUSH CreateHatchBrush(
  int fnStyle,     // ハッチスタイル
  COLORREF clrref  // 色
);
fnStyle
ブラシのハッチスタイルを次のいずれかから指定します。

ハッチスタイル [MSDN]

説明
HS_HORIZONTALHS_HORIZONTAL
HS_VERTICALHS_VERTICAL
HS_FDIAGONALHS_FDIAGONAL
HS_BDIAGONALHS_BDIAGONAL
HS_CROSSHS_CROSS
HS_DIAGCROSSHS_DIAGCROSS

ストックオブジェクト

システムが定義済の GDI オブジェクトのことを,ストックオブジェクト (stock object) といいます。
GetStockObject 関数を使うことで,ストックオブジェクトを取得できます。

GetStockObject 関数 [MSDN]

ストックオブジェクトを取得します。この関数で取得したオブジェクトは削除する必要はありません。

HGDIOBJ GetStockObject(
    int fnObject
);
GetStockObject
ストックオブジェクトのタイプを指定します。

ストックオブジェクト [MSDN]

値 (ブラシ)説明
WHITE_BRUSH白色のブラシ
LTGRAY_BRUSH薄い灰色のブラシ
GRAY_BRUSH灰色のブラシ
DKGRAY_BRUSH濃い灰色のブラシ
BLACK_BRUSH白色のブラシ
NULL_BRUSH
HOLLOW_BRUSH
空のブラシ
値 (ペン)説明
WHITE_PEN白色のペン
BLACK_PEN黒色のペン
NULL_PEN空のペン
値 (フォント)説明
OEM_FIXED_FONTOEM 固定幅フォント
ANSI_FIXED_FONTANSI 固定幅フォント
ANSI_VAR_FONTANSI 可変幅フォント
SYSTEM_FONTシステムフォント
DEVICE_DEFAULT_FONTデバイスの既定のフォント
DEFAULT_GUI_FONT既定の GUI フォント
値 (パレット)説明
DEFAULT_PALETTE既定のカラーパレット

今回の作例におけるペンとブラシにストックオブジェクトを使用するなら,ウィンドウプロシージャは次のようになるでしょう。

LRESULT CALLBACK WndProc(
    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;
    static HPEN hpen, hpenPrev;
    static HBRUSH hbr, hbrPrev;

    switch (uMsg) {
    case WM_CREATE:
        hpen = (HPEN) GetStockObject(BLACK_PEN);
        hbr = (HBRUSH) GetStockObject(GRAY_BRUSH);
        return 0;

    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);
        hpenPrev = (HPEN) SelectObject(hdc, hpen);
        hbrPrev = (HBRUSH) SelectObject(hdc, hbr);
        Rectangle(hdc, 50, 50, 200, 150);
        SelectObject(hdc, hpenPrev);
        SelectObject(hdc, hbrPrev);
        EndPaint(hwnd, &ps);
        return 0;

    case WM_DESTROY:
        // ストックオブジェクトの削除は不要
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

状態の保存

デバイスコンテキストの状態を保存する関数 SaveDC,復元する関数 RestoreDC を利用すると,GDI オブジェクトの関連付けを元に戻す操作の負担を軽減できます。

SaveDC 関数 [MSDN]

デバイスコンテキストの現在の状態 (オブジェクトの関連付けやグラフィックモード) を保存します。

int SaveDC(
    HDC hdc   // デバイスコンテキストへのハンドル
);
返り値
保存された状態を一意に示す値を返します。失敗時に 0 を返します。

RestoreDC 関数 [MSDN]

デバイスコンテキストの状態を復元します。

BOOL RestoreDC(
    HDC hdc,       // デバイスコンテキストへのハンドル
    int nSavedDC   // 状態の識別値
);
nSavedDC
復元すべき状態を示す識別値または負の値を指定します。負の値 -n (≤ -1) を指定すると n 回前に保存された状態を復元します。

パラメータ nSavedDC には,SaveDC 関数の返り値か,負の値を指定します。
最後に SaveDC を実行した時点の状態を復元するには,値 -1 を指定します。

SaveDC 関数,RestoreDC 関数を用いると,今回のプログラムは次のように書き換えることができます。

#include <windows.h>

LRESULT CALLBACK WndProc(
    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(
    HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpszCmdLine, int nCmdShow)
{
    // 省略
}

LRESULT CALLBACK WndProc(
    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;
    static HPEN hpen;
    static HBRUSH hbr;

    switch (uMsg) {
    case WM_CREATE:
        hpen = CreatePen(PS_SOLID, 5, RGB(0x00, 0x7f, 0xff));
        hbr = CreateSolidBrush(RGB(0xff, 0xbf, 0x00));
        return 0;

    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);
        SaveDC(hdc);         // 状態を保存
        SelectObject(hdc, hpen);
        SelectObject(hdc, hbr);
        Rectangle(hdc, 50, 50, 200, 150);
        RestoreDC(hdc, -1);  // 状態を復元
        EndPaint(hwnd, &ps);
        return 0;

    case WM_DESTROY:
        DeleteObject(hpen);
        DeleteObject(hbr);
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

図形の描画

図形の描画関数

図形の描画関数には次のようなものがあります。
表の中の図は,青色のペン,黄色のブラシの時の描画例です。

関数図形の種類描画例
Rectangle長方形長方形
FillRect長方形 (輪郭なし)長方形 (輪郭なし)
FrameRect長方形 (輪郭のみ)長方形 (輪郭のみ)
Ellipse楕円楕円
RoudRect角の丸い長方形角の丸い長方形
Pieパイ形パイ形
Chord弦形弦形
Polygon, PolyPolygon多角形,複数の多角形多角形,複数の多角形

長方形の描画

次のような長方形を描画するプログラムを作ります。

作例

#include <windows.h>

LRESULT CALLBACK WndProc(
    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(
    HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpszCmdLine, int nCmdShow)
{
    TCHAR szAppName[] = TEXT("TestApp");
    WNDCLASS wc;
    HWND hwnd;
    MSG msg;

    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc   = WndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = szAppName;

    if (!RegisterClass(&wc)) return 0;

    hwnd = CreateWindow(
        szAppName, TEXT(""),
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT,
        CW_USEDEFAULT, CW_USEDEFAULT,
        NULL, NULL,
        hInstance, NULL);

    if (!hwnd) return 0;

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    while (GetMessage(&msg, NULL, 0, 0) > 0) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;
}

LRESULT CALLBACK WndProc(
    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;

    switch (uMsg) {
    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);       // 描画開始
        Rectangle(hdc, 50, 50, 200, 150);  // 描画
        EndPaint(hwnd, &ps);               // 描画終了
        return 0;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

長方形の描画

長方形の描画に用いた関数は,次の Rectangle 関数です。

Rectangle 関数 [MSDN]

長方形を描画します。

BOOL Rectangle(
    HDC hdc,    // デバイスコンテキストへのハンドル
    int left,   // 左上端の x 座標
    int top,    // 左上端の y 座標
    int right,  // 右下端の x 座標
    int bottom  // 右下端の y 座標
);

第 1 引数の hdc には,BeginPaint 関数などで取得したデバイスコンテキストへのハンドルを指定します。

第 2 引数以降には,長方形の左上端,右下端の座標をクライアント座標で指定します。

クライアント座標系

次の図は,式 Rectangle(hdc, 2, 2, 8, 6) によって描画される長方形です。
このように,GDI 関数で描画される図形は,始点を含み終点を含みません。

長方形の例

曲線の描画

曲線の描画関数

曲線の描画関数には次のようなものがあります。

関数曲線の種類描画例
LineTo直線直線
Polyline, PolylineTo複数の直線複数の直線
PolyPolyline複数の複数の直線複数の複数の直線
Arc, ArcTo, AngleArc円弧円弧
PolyBezier, PolyBezierTo複数のベジェ曲線複数のベジェ曲線

直線の描画

次のような図を描画するプログラムを作ります。

作例

#include <windows.h>

LRESULT CALLBACK WndProc(
    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(
    HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpszCmdLine, int nCmdShow)
{
    TCHAR szAppName[] = TEXT("TestApp");
    WNDCLASS wc;
    HWND hwnd;
    MSG msg;

    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc   = WndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = szAppName;

    if (!RegisterClass(&wc)) return 0;

    hwnd = CreateWindow(
        szAppName, TEXT(""),
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT,
        CW_USEDEFAULT, CW_USEDEFAULT,
        NULL, NULL,
        hInstance, NULL);

    if (!hwnd) return 0;

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    while (GetMessage(&msg, NULL, 0, 0) > 0) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;
}

LRESULT CALLBACK WndProc(
    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;

    switch (uMsg) {
    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);
        MoveToEx(hdc, 50, 50, NULL);   // (50, 50) まで移動
        LineTo(hdc, 150, 50);   // (150, 50) まで直線を描画
        LineTo(hdc, 50, 150);   // (50, 150) まで直線を描画
        LineTo(hdc, 150, 150);  // (150, 150) まで直線を描画
        EndPaint(hwnd, &ps);
        return 0;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

このプログラムでは,現在位置 (current position) を移動する関数 MoveToEx と,直線を描画する関数 LineTo が使われています。

MoveToEx 関数 [MSDN]

現在位置を設定します。

BOOL MoveToEx(
    HDC hdc,      // デバイスコンテキストへのハンドル
    int X,        // x 座標
    int Y,        // y 座標
    LPPOINT lppt  // 以前の現在位置 (出力用)
);
lppt
更新前の現在位置を取得する場合に,POINT 構造体へのポインタを指定します。不要な場合は NULL を指定します。

LineTo 関数 [MSDN]

現在位置を始点として,指定の終点までの直線を描画をします。また,現在位置を終点に移動します。

BOOL LineTo(
    HDC hdc,  // デバイスコンテキストへのハンドル
    int x,    // 終点の x 座標
    int y     // 終点の y 座標
);

また,連続した複数の直線 (ポリライン) を描画する Polyline 関数,PolylineTo 関数があります。

Polyline 関数 [MSDN]

連続した複数の直線を描画します。現在位置は使用しません。

BOOL Polyline(
    HDC hdc,           // デバイスコンテキストへのハンドル
    CONST POINT *apt,  // 端点の配列
    int cpt            // 配列内の点の数
);

PolylineTo 関数 [MSDN]

現在位置を始点として,連続した複数の直線を描画します。また,現在位置を終点に移動します。

BOOL PolylineTo(
    HDC hdc,           // デバイスコンテキストへのハンドル
    CONST POINT *apt,  // 端点の配列
    int cpt            // 配列内の点の数
);

現在位置 (50, 50), 端点の配列 apt = { { 100, 50 }, { 50, 100 }, { 100, 100 } }, 配列内の点の数 cpt = 3 のとき,Polyline, PolylineTo 関数で描画される図形は次の通りです。

描画例

さらに,複数のポリラインを描画する PolyPolyline という関数もあります。

PolyPolyline 関数 [MSDN]

複数のポリラインを描画します。現在位置は使用しません。

BOOL PolyPolyline(
    HDC hdc,           // デバイスコンテキストへのハンドル
    CONST POINT *apt,  // 端点の配列
    CONST DWORD *asz,  // ポリラインごとの端点数からなる配列
    DWORD csz          // ポリラインの数
);

円弧の描画

次のような円弧を描画するプログラムを作ります。

作例

#include <windows.h>

LRESULT CALLBACK WndProc(
    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(
    HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpszCmdLine, int nCmdShow)
{
    // 省略
}

LRESULT CALLBACK WndProc(
    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;

    switch (uMsg) {
    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);
        Arc(hdc, 50, 50, 150, 150, 100, 200, 0, 100);
        EndPaint(hwnd, &ps);
        return 0;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

円弧を描画するには,Arc 関数,ArcTo 関数,AngleArc 関数のいずれかを利用します。
このうち Arc 関数,ArcTo 関数のプロトタイプは次の通りです。

Arc 関数 [MSDN]

楕円の弧を描画します。現在位置は使用しません。

BOOL Arc(
    HDC hdc,         // デバイスコンテキストへのハンドル
    int x1, int y1,  // 矩形の左上端の座標
    int x2, int y2,  // 矩形の右下端の座標
    int x3, int y3,  // 放射直線の端点の座標
    int x4, int y4   // 放射直線の端点の座標
);

ArcTo 関数 [MSDN]

現在位置から弧の始点までの直線と,楕円の弧を描画します。また,現在位置を弧の終点に移動します。

BOOL ArcTo(
    HDC hdc,         // デバイスコンテキストへのハンドル
    int x1, int y1,  // 矩形の左上端の座標
    int x2, int y2,  // 矩形の右下端の座標
    int x3, int y3,  // 放射直線の端点の座標
    int x4, int y4   // 放射直線の端点の座標
);

各関数のパタメータは,次の図に示すように指定します。

パラメータの説明

次のプログラムは,AngleArc 関数を使って図のような円弧を描画します。

描画例

#include <windows.h>

LRESULT CALLBACK WndProc(
    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(
    HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpszCmdLine, int nCmdShow)
{
    // 省略
}

LRESULT CALLBACK WndProc(
    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;

    switch (uMsg) {
    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);
        MoveToEx(hdc, 100, 100, NULL);
        AngleArc(hdc, 100, 100, 50, 45, 270);
        EndPaint(hwnd, &ps);
        return 0;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

AngleArc 関数 [MSDN]

現在位置から円弧の始点までの直線と,円弧を描画します。また,現在位置を円弧の終点に移動します。

BOOL AngleArc(
    HDC hdc,            // デバイスコンテキストへのハンドル
    int x,              // 中心の x 座標
    int y,              // 中心の y 座標
    DWORD r,            // 半径
    FLOAT eStartAngle,  // 開始角
    FLOAT eSweepAngle   // 描画角
);

この関数のパタメータは,次の図に示すように指定します。

パラメータの説明

テキストの描画

TextOut 関数

次の図のように,クライアント領域にテキストを描画するプログラムを作ります。

作例

#include <windows.h>

LRESULT CALLBACK WndProc(
    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(
    HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpszCmdLine, int nCmdShow)
{
    TCHAR szAppName[] = TEXT("TestApp");
    WNDCLASS wc;
    HWND hwnd;
    MSG msg;

    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc   = WndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = szAppName;

    if (!RegisterClass(&wc)) return 0;

    hwnd = CreateWindow(
        szAppName, TEXT(""),
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT,
        CW_USEDEFAULT, CW_USEDEFAULT,
        NULL, NULL,
        hInstance, NULL);

    if (!hwnd) return 0;

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    while (GetMessage(&msg, NULL, 0, 0) > 0) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;
}

LRESULT CALLBACK WndProc(
    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    static TCHAR szText[] = TEXT("This is some sample text.");
    static int nTextLen = sizeof szText / sizeof(TCHAR) - 1;
    HDC hdc;
    PAINTSTRUCT ps;

    switch (uMsg) {
    case WM_CREATE:
        return 0;

    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);
        TextOut(hdc, 0, 0, szText, lstrlen(szText));
        EndPaint(hwnd, &ps);
        return 0;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

TextOut 関数は,テキストを描画する関数の中で最も単純な関数です。

TextOut 関数 [MSDN]

テキストを描画します。

BOOL TextOut(
    HDC hdc,           // デバイスコンテキストへのハンドル
    int nXStart,       // 基準点の x 座標
    int nYStart,       // 基準点の y 座標
    LPCTSTR lpString,  // 文字列
    int cbString       // 文字数
);

Windows API における "文字数" とは,lstrlen 関数の返す文字数のことです。
lstrlen 関数は (文字列のバイト数) / sizeof(TCHAR) に等しい値を返します (*1)。

sizeof(TEXT("aiuあいう"))lstrlen(TEXT("aiuあいう"))
UNICODE 未定義109
UNICODE 定義済146

(*1) lstrlen 関数は,記号 UNICODE が未定義の時 strlen に,定義済の時 wcslen にそれぞれ等価です。詳しくは Unicode 対応 にて説明しています。

文字色と背景色

次のプログラムは,テキストを赤色で描画します。また,テキストの背景を薄い赤色で塗り潰します。

作例

#include <windows.h>

LRESULT CALLBACK WndProc(
    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(
    HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpszCmdLine, int nCmdShow)
{
    // 省略
}

LRESULT CALLBACK WndProc(
    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    static TCHAR szText[] = TEXT("This is some sample text.");
    HDC hdc;
    PAINTSTRUCT ps;

    switch (uMsg) {
    case WM_CREATE:
        return 0;

    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);
        SetTextColor(hdc, RGB(0xff, 0x00, 0x00));  // 文字色を設定
        SetBkColor(hdc, RGB(0xff, 0xdf, 0xdf));    // 背景色を設定
        TextOut(hdc, 0, 0, szText, lstrlen(szText));
        EndPaint(hwnd, &ps);
        return 0;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

デバイスコンテキストの文字色を変更するには SetTextColor 関数を使います。
この関数で設定された文字色は,TextOut 関数,ExtTextOut 関数で反映されます。

SetTextColor 関数 [MSDN]

デバイスコンテキストの文字色を設定します。

COLORREF SetTextColor(
    HDC hdc,          // デバイスコンテキストへのハンドル
    COLORREF crColor  // 色
);
返り値
以前の文字色を返します。失敗時には CLR_INVALID を返します。

文字列の背景色を変えるには,SetBkColor 関数でデバイスコンテキストの背景色を設定します。

SetBkColor 関数 [MSDN]

デバイスコンテキストの背景色を設定します。

COLORREF SetBkColor(
    HDC hdc,          // デバイスコンテキストへのハンドル
    COLORREF crColor  // 色
);
返り値
以前の背景色を返します。失敗時には CLR_INVALID を返します。

この関数で設定した背景色は,次のような場所で使用されます。

この作例では使用していませんが,デバイスコンテキストの背景モードを設定する SetBkMode もあります。
背景モードを変更すれば,テキスト等の背景を塗り潰さずに透過させることができます。

SetBkMode 関数 [MSDN]

デバイスコンテキストの背景モードを設定します。

int SetBkColor(
    HDC hdc,     // デバイスコンテキストへのハンドル
    int iBkMode  // 背景モード
);
iBkMode

背景モード [MSDN]

説明
OPAQUE背景を塗り潰す
TRANSPARENT背景を塗り潰さない

なお,SetTextColor, SetBkColor, SetBkMode の各関数と対になる,GetTextColor, GetBkColor, GetBkMode 関数も提供されています。

テキストの配置

次のプログラムは,文字列を右揃えで描画します。

作例

#include <windows.h>

LRESULT CALLBACK WndProc(
    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(
    HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpszCmdLine, int nCmdShow)
{
    // 省略
}

LRESULT CALLBACK WndProc(
    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    static TCHAR szText1[] = TEXT("This is some");
    static TCHAR szText2[] = TEXT("sample text.");
    static int cxClient;
    HDC hdc;
    PAINTSTRUCT ps;

    switch (uMsg) {
    case WM_CREATE:
        return 0;

    case WM_SIZE:
        cxClient = LOWORD(lParam);
        return 0;

    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);
        SetTextAlign(hdc, TA_RIGHT | TA_TOP);  // 右揃えに設定
        TextOut(hdc, cxClient, 0,  szText1, lstrlen(szText1));
        TextOut(hdc, cxClient, 20, szText2, lstrlen(szText2));
        EndPaint(hwnd, &ps);
        return 0;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

テキストの配置を変更するには SetTextAlign 関数を使います。

SetTextAlign 関数 [MSDN]

デバイスコンテキストの配置設定を設定します。

COLORREF SetTextColor(
    HDC hdc,          // デバイスコンテキストへのハンドル
    COLORREF crColor  // 色
);
fMode
テキストの配置方法を指定します。次に示す値はビット論理和で組み合わせて指定できます。

テキスト配置 [MSDN]

値 (現在位置)説明
TA_NOUPDATECP現在位置を更新しない (既定)
TA_UPDATECP現在位置を更新する
値 (水平方向)説明
TA_LEFT左寄せ (既定)
TA_RIGHT右寄せ
TA_CENTER中央揃え
値 (垂直方向)説明
TA_TOP上に揃える (既定)
TA_BOTTOM下に揃える
TA_BASELINEベースラインを揃える
返り値
以前の配置を返します。失敗時には GDI_ERROR を返します。

テキストの配置設定を変更すると,TextOut 関数,ExtTextOut 関数の基準点が変わります。
次の図は,各テキスト配置を適用した時の基準点の位置を赤色で示したものです。

基準点の位置

なお,現在の配置を取得できる GetTextAlign 関数もあります。

### フォントの利用

このプログラムでは,固定幅フォントを使って文字列を描画します。

作例

#include <windows.h>

LRESULT CALLBACK WndProc(
    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(
    HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpszCmdLine, int nCmdShow)
{
    // 省略
}

LRESULT CALLBACK WndProc(
    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    static TCHAR szText[] = TEXT("This is some sample text.");
    static HFONT hfon, hfonPrev;
    HDC hdc;
    PAINTSTRUCT ps;

    switch (uMsg) {
    case WM_CREATE:
        hfon = (HFONT) GetStockObject(OEM_FIXED_FONT); // フォントを取得
        return 0;

    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);
        hfonPrev = (HFONT) SelectObject(hdc, hfon);  // フォントを選択
        TextOut(hdc, 0, 0, szText, lstrlen(szText));
        SelectObject(hdc, hfon);                     // 元に戻す
        EndPaint(hwnd, &ps);
        return 0;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

今回のプログラムは,ペンとブラシ の章で紹介した,ストックオブジェクトを利用しています。
フォントへのハンドルの型は HFONT 型です。