「Windows API/図形の描画」の版間の差分

出典: フリー教科書『ウィキブックス(Wikibooks)』
削除された内容 追加された内容
polygon関数とPOINT配列
編集の要約なし
186 行 186 行


安全のため、作業が終わったら、メモ帳を閉じてから、Visual Stuido 側でセーブ保存すると安全だろう。
安全のため、作業が終わったら、メモ帳を閉じてから、Visual Stuido 側でセーブ保存すると安全だろう。

== 無い機能 ==
=== 画面のファイル出力の機能は無い ===
Win32APIに無い機能として、画面の画像をビットマップファイルなどに出力する機能は無い。

よって、どうしても、そういう機能の(あなたの制作している)アプリへの組み込みが必要なら、自作するか、ネット上で出回ってる無料ライブラリを使用することになる。

もし .NET Framework ならば SaveImage メソッドいうのがある。しかし Win32API には、そういうのは無い。

Win32APIでも、画面の画像をキャプチャしてメモリなどに蓄える命令( GetShellWindow 命令 )はあるが、しかし、そうやって蓄えた画像情報を外部ファイルに出力して書き込む専用の命令は無い。

なので、C言語のバイナリ書き込み命令などを利用して、自分でビットマップ形式ファイルへの画像エンコーダーを自作することになる。このため、BITMAP構造体の仕様などを把握しておく必要がある。

=== 塗りつぶしの機能は無い ===
ペイントソフトなどでは、どのペイントソフトにも、バケツみたいなアイコンで、閉じた曲線の中を塗りつぶす機能があるだろう。

しかし、win32APIには、そういう命令は無い。三角形や四角形など多角形なら塗りつぶしできる専用の命令があるが、しかし一般の曲線の中を塗りつぶす命令は無い。

なので、塗りつぶし命令すら、自作することになる。

ネット検索で「塗りつぶし アルゴリズム ペイント」などの検索ワードで検索すれば、いくつかアルゴリズムを紹介したページが見つかるので、必要なら調べよう。なお、塗りつぶしのアルゴリズムには種類が複数ある。


なお、基本的な原理は、次のとおりで、「シード・フィル アルゴリズム」という。

まず、塗りつぶしの判定基準となる点を蓄積するスタックを用意する。最初、このスタックには、基準の1点しか入ってない。
# スタックにある1つしかない1点(仮に点Aとする)を基準として、上下左右1ドットとなりのピクセルが塗りつぶし可能か(つまり空白であるか、あるいは基準点と同じ色であるか)どうかを調べる。
# 手順1で調べた点が塗りつぶし可能なら(つまり、点Aの上下左右1ドット隣)、そこは塗りつぶすことに決定し、さらにその点をスタックに加える。塗りつぶし可能かどうかと無関係に、スタック内に最初にあった1点(点A)はスタックから除外される。(なので、最大4点がスタックに追加される。)
# スタックにあるすべての点(基準点)について、その1ピクセルとなりの隣接点が、ぬりつぶし可能かどうかを調べて、塗りつぶせた隣接点はスタックに追加していく。基準点はスタックから除去する。
# 手順3を繰り返す。

これを繰り返していけば、原理的には、境界枠の内部だけの塗りつぶしができる。だが、時間が比較的に掛かるので、改良がいろいろと提案されている。

だが、それら改良したアルゴリズムにも欠点がある。

たとえば、上下左右の1点ずつ調べるのではなく、横一直線に障害物にあたるまで塗りつぶして、障害物の手前の部分の点を入れる方式は、スキャンライン・シードフィル方式というが、このスキャンライン(以下略)方式の場合、結局はもとのアルゴリズムと時間があまり変わらない。なぜならスキャンライン(以下略)方式の場合でも、最終的には領域内のピクセルの個数ぶんだけ調べる必要があるからである。

なので、もとの「シード・フィル アルゴリズム」が塗りつぶし画像の正確さの事だけ考えるなら、一番 正確である。

2019年9月30日 (月) 08:31時点における版

polygon関数とPOINT配列

Win32 API では、polygon() という命令で、任意の多角形を塗りつぶしできる。

polygon命令の使用のさい、その多角形の頂点それぞれの座標位置の格納のために事前に POINT 配列という専用の配列を宣言します。


ただし、長方形と正方形については、これらの四角形を書ける Rectangle という命令でも塗りつぶしできます。


長方形を塗りつぶすには?

図形を塗りつぶしたい場合には、図形描画命令の直前に、下記のように描画のモードをブラシに設定します。

塗りつぶし専用の命令は、無いのです。

Windows API でいう「ブラシ」とは、図形の塗りつぶしの色を設定するためのツールのことです。画像作成ソフトで例えるなら、Windows API でいうブラシとは、画像作成ソフトの「バケツ」ツールに近いです。世間一般の画材でいう「ブラシ」とは、意味が違います。

			HBRUSH brasi_buhin_1;
			brasi_buhin_1 = CreateSolidBrush(RGB(255, 100, 100)); // ピンク色のブラシを作成
			SelectObject(hdc, brasi_buhin_1); // ウィンドウhdcと、さきほど作成したブラシを関連づけ
			Rectangle(hdc, 20, 30, 50, 70); // 図形の描画

上記の2段階のブラシ作成の作業は、CreateSolidBrush はブラシの色作成のためのアドレス確保を意味し、もう片方 SelectObject は、どのウィンドウでどのブラシ色を使うかを定義するための情報を保存するアドレスの確保を意味しています。

「Rectangle」は、単に、四角形を描くための命令です。これが塗りつぶしか、それとも輪郭線だけをペンで描くのかは、このRectangle命令だけでは指定できず、この命令が来る前の設定に依存します。


当然、これらの命令は、WM_PAINT ケースにある

「// TODO: HDC を使用する描画コードをここに追加してください...」

のあるブロックで書く必要があります。


つまり、下記のコードのようになります。

case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            // TODO: HDC を使用する描画コードをここに追加してください...

			HBRUSH brasi_buhin_1;
			brasi_buhin_1 = CreateSolidBrush(RGB(255, 100, 100)); // ピンク色のブラシを作成
			SelectObject(hdc, brasi_buhin_1); // ウィンドウhdcと、さきほど作成したブラシを関連づけ
			Rectangle(hdc, 20, 30, 50, 70); // 図形の描画

            EndPaint(hWnd, &ps);
        }
        break;


さて、上記コードを実際に描画すると、四角形の輪郭が黒色で描画されます。これは、ペンの設定が黒色になってるからです。

図形をピンク色の長方形だけにしたい場合、ペンの色もピンク色にします。

Rectangle 命令の前に、あらかじめ

			HPEN pen_buhin_1;
			pen_buhin_1 = CreatePen (PS_SOLID, 0, RGB(255, 100, 100));
			SelectObject(hdc, pen_buhin_1);

のように、ペンを指定します。 CreatePen 命令の第2引数の「0」は、ペン幅の追加の線幅です。「0」に指定しても、最低でも1ドットぶんの線幅はあります。

よって、今までの話をまとめると、

			HPEN pen_buhin_1; // ペンの設定用
			pen_buhin_1 = CreatePen (PS_SOLID, 0, RGB(255, 100, 100));
			SelectObject(hdc, pen_buhin_1);

			HBRUSH brasi_buhin_1; // ブラシの設定用
			brasi_buhin_1 = CreateSolidBrush(RGB(255, 100, 100));
			SelectObject(hdc, brasi_buhin_1);

			Rectangle(hdc, 20, 30, 50, 70); // 図形の描画

のように、なります。

関数的な処理の活用

複数個の描画を関数にしたい場合は

ソースの冒頭で関数を宣言する際、通常の関数と同じように、

void kansuu(HDC hdc){
    // ここに描画命令を書く
}

のように宣言する必要があります。引数の型と変数名が、単にハンドル型(HDC)とハンドル変数(hdc)になっただけです。通常のC言語の関数と同じです。


また、呼び出し側(親側)で、

kansuu(hdc);

のように、hdcの値を渡す必要があります(通常のC言語の関数と同じです)。

もし、上述の引数定義の手間を面倒くさがって、hdc の定義を static に変更すると、コードの他の部分の動作結果がバグります。なので、hdc は static に変更せず、ウィザードの初期設定のままにしておいてください。


当然、呼び出し側(親側)の関数のコードのある位置は、

    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            // TODO: HDC を使用する描画コードをここに追加してください...

// TODO: HDC を使用する描画コードをここに追加してください...

より下の部分です。


つまり、

    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            // TODO: HDC を使用する描画コードをここに追加してください...

            (中略)
            kansuu(hdc);
            (中略)

のように書かれます。

For文による代用

WindowsAPIで関数を使う場合、関数の内容を記述する場所が、呼び出し元の場所から、大きく離れた場所に記述しなければならない場合があります。

このような場合、いっそ関数をつかわずに、For文で記述するという方法もあります。

WindowsAPI でも、標準C言語と同様にFor文を使えます。

For文の繰り返しの数(たとえば for (int j = 1; j <= 20; ++j) の20の部分)を十分に大きな自然数にしておいて、使い終わったら break でそのブロックを脱出すればいいだけです。

たとえば、5種類の違う作業の前に、共通する前段階の作業を5回呼び出したい場合、

for (int j = 1; j <= 20; ++j) {

	ここに共通する前段階の作業を記述 ;

	if (j == 1) { 1回目にしたい処理内容 ; }
	if (j == 2) { 2回目にしたい処理内容 ; }
	if (j == 3) { 3回目にしたい処理内容 ; }
	if (j == 4) { 4回目にしたい処理内容 ; }
	if (j == 5) { 5回目にしたい処理内容 ; 
					 break;}
}

のように書くのです。

このFor文による代用の方法は、有限の回数しか呼び出しをしない処理にしか使えません。何度使うのか不特定な場合や、ずっとループ的に処理させたい場合には、むいてない方法です。

しかし、有限の回数しか呼び出さない処理ならば、この方法でも充分なのです。


また、もし関数を使う場合でも、For文で書いた処理を関数に置き換えるのはラクです。なので、関数を作る前に、とりあえず、For文で試しに似たような処理を書くという方法もあります。


構造体は無印C言語とやや異なる

WindowsAPIでは、構造体の宣言場所(いわゆる「グローバル領域」、コード冒頭の変数宣言などの場所)と、構造体の代入場所(wWinMain関数ブロック内など)とは、離れざるを得ない。

なぜなら (WindowsAPIの)wWinMain 関数と (無印C言語の)main関数との仕組みが、違うからである。

wWinMain 関数ブロックで構造体の型を宣言しても、wWinMain 関数はアプリ起動イベント専用のローカルな関数なので、他のイベント(たとえば画面描画の関数など)の関数ブロックからは原則的に、その構造体の型を呼び出せなくなってしまうので、まったく実用的では無くなる。

しかし、一方でもしもグローバル領域で宣言した構造体ならば、その構造体は画像描画の関数などの全てのイベントで(構造体を)呼び出せる。


しかし、グローバル領域では、代入などの操作が出来ないので、構造体の各要素の初期値の代入すら出来ない。

このため、代入などは wWinMain 関数ブロック内で行う必要があるという制約がある。


そして、この制約は、WinodowsAPIにおいては、wWinMain関数周辺にコードが膨大にある状況では、構造体の宣言と初期値代入が離れるのは、とても構造体のコードが一覧しづらくなり不便である。

しかし、不便であるが、他に簡便な方法が少ないので、しかたなく、構造体の宣言は、グローバル領域で行い、構造体の各要素への初期値代入などはwWinMain関数ブロックなどで行うことになる。


これらの離れた場所にあるコードを同時に見たいなら、Visual Stuido で作業中のコードを開きつつ、メモ帳などで別のアプリでコードをテキストファイルなふどとして開いて、メモ帳のほうを離れた場所の側のコードにあわせて閲覧しよう。ただし、メモ帳側で上書き保存などをしないように。

安全のため、作業が終わったら、メモ帳を閉じてから、Visual Stuido 側でセーブ保存すると安全だろう。

無い機能

画面のファイル出力の機能は無い

Win32APIに無い機能として、画面の画像をビットマップファイルなどに出力する機能は無い。

よって、どうしても、そういう機能の(あなたの制作している)アプリへの組み込みが必要なら、自作するか、ネット上で出回ってる無料ライブラリを使用することになる。

もし .NET Framework ならば SaveImage メソッドいうのがある。しかし Win32API には、そういうのは無い。

Win32APIでも、画面の画像をキャプチャしてメモリなどに蓄える命令( GetShellWindow 命令 )はあるが、しかし、そうやって蓄えた画像情報を外部ファイルに出力して書き込む専用の命令は無い。

なので、C言語のバイナリ書き込み命令などを利用して、自分でビットマップ形式ファイルへの画像エンコーダーを自作することになる。このため、BITMAP構造体の仕様などを把握しておく必要がある。

塗りつぶしの機能は無い

ペイントソフトなどでは、どのペイントソフトにも、バケツみたいなアイコンで、閉じた曲線の中を塗りつぶす機能があるだろう。

しかし、win32APIには、そういう命令は無い。三角形や四角形など多角形なら塗りつぶしできる専用の命令があるが、しかし一般の曲線の中を塗りつぶす命令は無い。

なので、塗りつぶし命令すら、自作することになる。

ネット検索で「塗りつぶし アルゴリズム ペイント」などの検索ワードで検索すれば、いくつかアルゴリズムを紹介したページが見つかるので、必要なら調べよう。なお、塗りつぶしのアルゴリズムには種類が複数ある。


なお、基本的な原理は、次のとおりで、「シード・フィル アルゴリズム」という。

まず、塗りつぶしの判定基準となる点を蓄積するスタックを用意する。最初、このスタックには、基準の1点しか入ってない。

  1. スタックにある1つしかない1点(仮に点Aとする)を基準として、上下左右1ドットとなりのピクセルが塗りつぶし可能か(つまり空白であるか、あるいは基準点と同じ色であるか)どうかを調べる。
  2. 手順1で調べた点が塗りつぶし可能なら(つまり、点Aの上下左右1ドット隣)、そこは塗りつぶすことに決定し、さらにその点をスタックに加える。塗りつぶし可能かどうかと無関係に、スタック内に最初にあった1点(点A)はスタックから除外される。(なので、最大4点がスタックに追加される。)
  3. スタックにあるすべての点(基準点)について、その1ピクセルとなりの隣接点が、ぬりつぶし可能かどうかを調べて、塗りつぶせた隣接点はスタックに追加していく。基準点はスタックから除去する。
  4. 手順3を繰り返す。

これを繰り返していけば、原理的には、境界枠の内部だけの塗りつぶしができる。だが、時間が比較的に掛かるので、改良がいろいろと提案されている。

だが、それら改良したアルゴリズムにも欠点がある。

たとえば、上下左右の1点ずつ調べるのではなく、横一直線に障害物にあたるまで塗りつぶして、障害物の手前の部分の点を入れる方式は、スキャンライン・シードフィル方式というが、このスキャンライン(以下略)方式の場合、結局はもとのアルゴリズムと時間があまり変わらない。なぜならスキャンライン(以下略)方式の場合でも、最終的には領域内のピクセルの個数ぶんだけ調べる必要があるからである。

なので、もとの「シード・フィル アルゴリズム」が塗りつぶし画像の正確さの事だけ考えるなら、一番 正確である。