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

出典: フリー教科書『ウィキブックス(Wikibooks)』
削除された内容 追加された内容
fix lint error (use script)
321 行 321 行


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


for文とif文による代用の方法には、欠点もあります。

まず、if文を使うと、原理的に、もし今後の編集によって条件分岐用の変数に想定外の変数(上記コード例では変数 j )が代入された場合には、バグの発生原因にもなります。

なのでプログラミングの流儀には、こういう想定外のミスを防止する観点から if 文をなるべく使わないでコードを書くという流儀もあります。

ですが、似たような処理の場合分けのために if 文を使わないでfor文だけで似たようなコードをまとめて処理を書く方式だと、コードが長くなる場合があります。コードが長くなると、今度はその長さにより、バグの発生率が増えます。コードが長ければ長いほど、バグが混入しやすくなるし、仕様変更の際などにも変更の手間が増えたり、仕様変更のための修正箇所が多すぎて変更し忘れのミスをするなど、そういったバグが発生しやすくなります。


かといって、「コードを短くしよう」と思って、いきなり関数ばかり使ってコードを書くと、前の節でも説明したような欠点により、windowsAPIプログラミングでは関数の位置が、呼び出し場所と離れすぎている場合がほとんどなので、それが原因で別のコードの記述ミスをする可能性だって増えてしまいます。また、windowsAPIプログラミングの特殊事情で、関数を呼び出して引数を渡す際に、プログラマーの作成した変数だけを渡すのではなく、さらに各種のAPI用の様々な変数を渡す必要もあったりして、かなり関数を呼び出すことが、(windowsAPIでは)大変です。

こういったAPI的な特殊事情もwindowsAPIプログラミングにはあるので、DOSプロンプトなどのコンソール画面で標準C言語のプログラムを呼び出す場合とは違うのです。よって、必ずしもwindowsAPIのグラフィカルなアプリでは「関数」はプログラマーにとって使いやすいとは、限りません。


ともかく、どんなプログラミング様式も一長一短です。
なので、簡単で短い単純なプログラムだけを書く場合には、似たような処理の繰り返しがあっても、あえて関数もfor文もif文も全く使わずに、逐次的に1行ずつ命令を書いていく場合もあります。
もちろん、この逐次的な方式も、欠点があり、コードがかなり長くなるので、長くなるにつれて記述ミスをしやすくなります。


結局、けっして完璧なプログラミング様式は、ありません。結局、どんなプログラミング様式でコードを書いても、けっしてバグを完全に防ぐことはできないのです。

よって、プログラマーは色々と妥協しながら、テストやデバッグを何度も繰り返して、コードを書くことになります。


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

2021年1月19日 (火) 05:08時点における版

塗りつぶし

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

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

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

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); // 図形の描画

のように、なります。


典型的な画像描画の命令の手順

上記のプログラムにあるような手順の、

Create〇〇
SelectObject
実際の描画命令

といった順序は、長方形以外のほかの図形の描画などでも同様の手順になります。特別な理由の無いかぎり、この順序で命令を宣言することになるでしょう。

polygon関数とPOINT配列

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


さて、Polygon関数について説明する。

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

また、ブラシなどの設定や関連づけもする必要があります。

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

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

			HBRUSH brasi_buhin_1;
			brasi_buhin_1 = CreateSolidBrush(RGB(170, 170, 255)); // うすい青色のブラシを作成
			SelectObject(hdc, brasi_buhin_1); // ウィンドウhdcと、さきほど作成したブラシを関連づけ
			SetPolyFillMode(hdc, WINDING);


			static POINT tenretu[2]; // ここでPOINT配列を宣言している。配列名はtenretuにした。staticを除去するとバグる。

			tenretu[0].x = 100; tenretu[0].y = 70;

			tenretu[1].x = 100 +25 ; tenretu[1].y = 70 ;

			tenretu[2].x = 100 + 35; tenretu[2].y = 70 + 30;
		

			Polygon(hdc, tenretu, 3);


            EndPaint(hWnd, &ps);
        }
        break;


POINT配列は、配列なので配列の順番を0番目から数えるので、三角形を書きたい場合は tenretu[2] のようになります。

なお、Wikibooksおよび一部のサイトでは「POINT配列」と呼んでいますが(実用的には配列の形式で宣言して使う事が多そうなので)、しかしマイクロソフト社や多くのweb解説サイトなどでは「POINT構造体」と呼んでいます。


なお、最新のマイクロソフト社のあるプログラム言語では FillPolygon という多角形の塗りつぶしの関数があるが、しかしWindows32API では FillPolygon は使えない。

任意の図形の塗りつぶし

ExtFloodFill 命令で、たとえるならペイントソフトのバケツ塗りのような、線などで囲まれた領域の塗りつぶしをできる。詳しくは外部サイトを参照せよ。

その他の描画

線を引く

べつに塗りつぶしをせずに、単に線を描くだけの機能も存在している。

直線を描くだけなら、 MoveToEx() 命令と LineTo() 命令で描ける。

なお、 MoveToEx() で起点を指定しており、引数で起点を指定する。LineTo() 命令では終点を指定しており、引数で終点を指定すり。


そのほか、円や楕円を塗りつぶしで描くなら Ellipse() 命令で書ける。

また、この円・楕円の引数では、その円を内接円とする長方形を指定する方式で、その長方形の左上の座標と右下の座標を指定する。

このため、ナナメに傾いた楕円は書けない。(後述する SetPixel()など別の命令に頼ることになるだろう。)


そのほか、円弧および楕円弧を塗りつぶさずに弧だけ描くなら Arc() 命令で書ける。

そのほか、円弧および楕円を塗りつぶして描くなら Pie() 命令で書ける。

これら Arc() や Pie() もまた、内接円を囲む長方形を引数として指定する方式で、その長方形の左上の座標と右下の座標を指定する。


点を打つ

また、点を打つだけの命令もあり、それは SetPixel() 命令である。

原理的には、パソコン画面に表示されるどんな画像もピクセル単位の点の集まりにすぎないので、原理的には SetPixel だけで、どんな画像も書ける。


だが、もちろん実用では、上述に紹介したように、線を描くなら線を引くための専用の命令があるし、塗りつぶしをしたいなら専用の命令があるので、そういう専用の命令を使うことで、プログラマーの作業を簡略化するのが常識的である。

ちなみに、1文字ちがいの GetPixel() 命令は、これは指定した座標の色を取得する命令である。


無い機能

数学的な曲線グラフ

さて、Win32APIに無さそうな機能として(※ あったら本文を訂正してください)、数学の関数の、二次関数や三次関数のグラフ、あるいは三角関数や指数関数などのグラフなどの曲線は、特に用意はされてないようなので、SetPixel()などで描くことになるだろう。

なお、単に数値を計算するだけなら、標準C言語の math.h などで数学の関数が用意されているので、それをインクルードして関数を使用してパソコンに計算してもらえばいい。

一例として、三角関数のグラフは、たとえば次のようなコードで実際にグラフを書ける。( case WM_PAINT 部分の抜粋)

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

			int bunkainou = 50;
			int sinpuku = 70; // 振幅のつもり

			for (int xCount = 100; xCount < bunkainou * 10; ++xCount) {
				yPixel = sinpuku * sin((double)xCount / (double)bunkainou);
				SetPixel(hdc, xCount, 100 + yPixel, RGB(170, 170, 255));
			}
			
            EndPaint(hWnd, &ps);
        }
        break;

なお、冒頭で math.h をインクルードしておくのを忘れないように。なお、説明の単純化のため、上記コードを実行しても座標軸などの描画は無く、描かれるのは三角関数の正弦波のグラフだけである。


さて、SetPixel でグラフを書いた場合、計算のケタ落ちなどの問題からか、グラフが 飛び飛び の 途切れ途切れ になるような場合がある。(実際、上記のコードを windows10 で実行したところ(2019年に実行)、三角関数の曲線がところどころ途切れ途切れ になっている。)


また、SetPixel によるグラフ描画は、計算回数も多いので、用途によっては不適切である。

もし、図形が飛び飛びになってしまうと、グラフに沿って塗りつぶしをしたい場合、飛び飛びだと塗りつぶしの境界枠にできない。

なので、用途によっては SetPixel ではなく、近似的に LineTo 命令などを使って描画する例も、ネット上では、よく見かける。

得られる図形の大まかな形状があらかじめ分かっているなら、LineTo() 命令などで描画するのがよいだろう。

画面クリアの専用の命令は無い

Windows32API には、画面を初期状態に戻すようなクリア機能の専用の関数は、無いようです。(※ 参考文献:林晴彦『明快入門 visual C++ 2010』、ソフトバンククリエイティブ(出版社)、2011年3月8日 初版 第1刷、179ページ。 )

※ 参考文献のソフトバンクの本の説明対象は、正確にはMFCプログラミングの場合である。だが、 Win32API専用でも同様であろう。

Win32APIで もし画面をクリアしたい場合、さきほどの節で紹介した長方形の描画の機能などを用いて、単にウィンドウ内を塗りつぶせばいい。


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

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

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

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

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

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


関数的な処理の活用

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

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

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

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

「ハンドル型」とか「ハンドル変数」とは何か、はっきりしませんが、要するにWinApi専用のそういう型あるいは変数があると思えば十分です。
いちおう、歴史的・学問的にはコンピュータアーキテクチャ理論に「割り込みコントローラ」(PIC)という部品があり(2010年以降の現在ではCPU内部にPIC互換品が内蔵されている)、そのPICによるCPUなどへのシステムコールとして「割り込みハンドラ」という概念があり、よくイベントドリブンの概念と関連づけて「割り込みハンドラ」が語られますが(たとえばウィキペディア w:割り込みハンドラ で「イベントハンドラ」(イベントドリブン方式のハンドラのこと)などと関連づけて語られている)、
しかしマイクロソフト社がOSのソースを非公開にしてることもあり、Windows API でいう「ハンドラ」とは何かが、あまり仕様がハッキリしません。


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

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文で試しに似たような処理を書くという方法もあります。


for文とif文による代用の方法には、欠点もあります。

まず、if文を使うと、原理的に、もし今後の編集によって条件分岐用の変数に想定外の変数(上記コード例では変数 j )が代入された場合には、バグの発生原因にもなります。

なのでプログラミングの流儀には、こういう想定外のミスを防止する観点から if 文をなるべく使わないでコードを書くという流儀もあります。

ですが、似たような処理の場合分けのために if 文を使わないでfor文だけで似たようなコードをまとめて処理を書く方式だと、コードが長くなる場合があります。コードが長くなると、今度はその長さにより、バグの発生率が増えます。コードが長ければ長いほど、バグが混入しやすくなるし、仕様変更の際などにも変更の手間が増えたり、仕様変更のための修正箇所が多すぎて変更し忘れのミスをするなど、そういったバグが発生しやすくなります。


かといって、「コードを短くしよう」と思って、いきなり関数ばかり使ってコードを書くと、前の節でも説明したような欠点により、windowsAPIプログラミングでは関数の位置が、呼び出し場所と離れすぎている場合がほとんどなので、それが原因で別のコードの記述ミスをする可能性だって増えてしまいます。また、windowsAPIプログラミングの特殊事情で、関数を呼び出して引数を渡す際に、プログラマーの作成した変数だけを渡すのではなく、さらに各種のAPI用の様々な変数を渡す必要もあったりして、かなり関数を呼び出すことが、(windowsAPIでは)大変です。

こういったAPI的な特殊事情もwindowsAPIプログラミングにはあるので、DOSプロンプトなどのコンソール画面で標準C言語のプログラムを呼び出す場合とは違うのです。よって、必ずしもwindowsAPIのグラフィカルなアプリでは「関数」はプログラマーにとって使いやすいとは、限りません。


ともかく、どんなプログラミング様式も一長一短です。 なので、簡単で短い単純なプログラムだけを書く場合には、似たような処理の繰り返しがあっても、あえて関数もfor文もif文も全く使わずに、逐次的に1行ずつ命令を書いていく場合もあります。 もちろん、この逐次的な方式も、欠点があり、コードがかなり長くなるので、長くなるにつれて記述ミスをしやすくなります。


結局、けっして完璧なプログラミング様式は、ありません。結局、どんなプログラミング様式でコードを書いても、けっしてバグを完全に防ぐことはできないのです。

よって、プログラマーは色々と妥協しながら、テストやデバッグを何度も繰り返して、コードを書くことになります。

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

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

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

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

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


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

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


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

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


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

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

画像ファイルを作成したい場合

CreateDIBSection 関数を使うことになる。

MSDN公式 によると、シンタックスは

HBITMAP CreateDIBSection(
  HDC              hdc,
  const BITMAPINFO *pbmi,
  UINT             usage,
  VOID             **ppvBits,
  HANDLE           hSection,
  DWORD            offset
);

である。


また、BITMAPINFO構造体の設定は、

BITMAPINFO bmpInfo; // 構造体変数の宣言。慣習的に構造体変数名は小文字 bmpinfo がよく使われる

bmpInfo.bmiHeader.biSize=sizeof(BITMAPINFOHEADER);
bmpInfo.bmiHeader.biWidth = teikeiX; // 関数などによって与えられる定型値
bmpInfo.bmiHeader.biHeight = teikeV; // 定型値
bmpInfo.bmiHeader.biPlanes = 1;
bmpInfo.bmiHeader.biBitCount = 32;
bmpInfo.bmiHeader.biCompression = BI_RGB;

のようになる。

その後、・・・(※ 調査中)

だが、実際はまず、作成しようとする画像を目視しながら編集するためのプログラムが先に必要である。このため、下記のような、バイナリファイルの知識が必要になるだろう。

画像ファイルのバイナリ


バイナリエディタで見た場合のビットマップの機械語の配置
バージョンによって少々異なる場合もあるが、おおむね、こういう感じである。


たとえばビットマップ画像を作りたい場合、

機械語で、いわゆる「ビットマップ構造体」の仕様で決められた様式で、機械語をファイルに書き込む。

すると、たいていのデスクトップ用OS(ウィンドウズも含む)では、ビットマップ形式の機械語をサポートしているので、その機械語を画像ファイルとして認識してくれるので、クリックなどをするとOSが画像表示などをしてくれる。

なお、ネットや専門書の解説では画像フォーマットの仕様を説明するためによくC言語の「構造体」の書式が説明されるが、しかし、その構造体を入力しても、画像は生成されない。


さて、たとえば何らかのビットマップ画像をバイナリエディタで読み込むと、ファイルの先頭は必ず「42 4D」という16進数である。この数字は、アスキーコードで翻訳すると「BM」になる。

この「42 4D」という冒頭の数字を識別子とすることで、ファイルの種類を認識している。

誤解しないでほしいが、けっして「BM」と機械語で書かれているのでなく(そもそも十六進数に「M」は無い)、書かれているのは あくまで「42 4D」である。

ネット検索で調べる場合は「42 4D」というキーワードを付け加えて「ビットマップ 42 4D」などで調べれば、ビットマップ画像のバイナリでの扱いに関する有益な情報が入手しやすいだろう。


たとえば、Windows用のフリーのバイナリエディタ stiring で3×5ピクセルの白色だけのビットマップファイルを見ると、下記のようになっている。

 ADDRESS   00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F   0123456789ABCDEF 
------------------------------------------------------------------------------
 00000000  42 4D 82 00 00 00 00 00 00 00 76 00 00 00 28 00   BM........v...(. 
 00000010  00 00 05 00 00 00 03 00 00 00 01 00 04 00 00 00   ................ 
 00000020  00 00 0C 00 00 00 C4 0E 00 00 C4 0E 00 00 00 00   ......ト...ト..... 
 00000030  00 00 00 00 00 00 00 00 00 00 00 00 80 00 00 80   ................ 
 00000040  00 00 00 80 80 00 80 00 00 00 80 00 80 00 80 80   ................ 
 00000050  00 00 80 80 80 00 C0 C0 C0 00 00 00 FF 00 00 FF   ......タタタ....... 
 00000060  00 00 00 FF FF 00 FF 00 00 00 FF 00 FF 00 FF FF   ................ 
 00000070  00 00 FF FF FF 00 FF FF F0 00 FF FF F0 00 FF FF   ................ 
 00000080  F0 00  

stiring に限らず一般にバイナリエディタの画面の構成は、上記のようになっている。(なお、「ファイル」→「ダンプイメージの保存」などの操作で、上記のような機械語のイメージをテキストエディタ(Windowsの場合は『ワードパッド』など)で見られる形式に変換して出力できる。)

機械語はそのままでは、テキスト表示できない。なので、それをテキスト化したり、見やすくするために書式を整えたりして、機械語の内容を出力したものをダンプ イメージという。

また、そのようなダンプイメージを出力することを、俗に(ぞくに)「ダンプする」などとIT業界では言う。


さて、このダンプイメージのうち、機械語の本体は、真ん中のブロックである、下記の

42 4D 82 00 00 00 00 00 00 00 76 00 00 00 28 00
00 00 05 00 00 00 03 00 00 00 01 00 04 00 00 00
00 00 0C 00 00 00 C4 0E 00 00 C4 0E 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 80 00 00 80
00 00 00 80 80 00 80 00 00 00 80 00 80 00 80 80
00 00 80 80 80 00 C0 C0 C0 00 00 00 FF 00 00 FF
00 00 00 FF FF 00 FF 00 00 00 FF 00 FF 00 FF FF
00 00 FF FF FF 00 FF FF F0 00 FF FF F0 00 FF F
F0 00  

の部分である。


一番左のブロック、

BM........v...(. 
................ 
......ト...ト..... 
................ 
................ 
......タタタ....... 
................ 
................ 

は、ファイル内容の機械語をアスキーコードに変換して表示している。

このアスキーは、機械語のファイル内容ではなく、バイナリエディタ側がユーザーが内容を探しやすくする目的などのために表示している。


一番上の行の

00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F   0123456789ABCDEF 
-------------------------------------------------------------------

は、単に何バイト目かをあらわしているだけである。これは、機械語のファイル内容ではなく、バイナリエディタ側がユーザーが見やすくするために表示している。


また、一番左の列の

 ADDRESS  
-----------
 00000000  
 00000010  
 00000020 
 00000030 
 00000040  
 00000050  
 00000060 
 00000070  
 00000080 

は、アドレスをあらわしている。


各ヘッダの名称


バイナリエディタで見た場合のビットマップの機械語の配置
バージョンによって少々異なる場合もあるが、おおむね、こういう感じである。


赤い背景色のブロックで表した部分を「BITMAP FILE HEADER」といい、これの長さは14バイトで固定である。「42 4D」で2バイト、同じように数えて12バイトの長さ。「BITMAP FILE HEADER」とは、つまり、「42 42」「画像のファイルサイズ」「予約領域」「予約領域」「画像の場所の指定」の部分である。

日本語では、「BITMAP FILE HEADER」のことを、ビットマップの「ファイルヘッダ」と言う場合もある。 つまり、ビットマップのファイルヘッダの長さは14バイトで固定である。


さて、青い背景色のブロックで表した部分を「BITMAP INFO HEADER」という。

つまり、「BITMAP INFO HEADER」とは、

「ヘッダサイズ」
「画像の横幅」「画像の高さ」「プレーン数」「色ビット数」「圧縮形式」
「画像データのサイズ」「ヨコ方向の解像度」「タテ方向の解像度」「パレット数」
「重要な色数」

の部分である。日本語では、「BITMAP INFO HEADER」のことを、ビットマップの「情報ヘッダ」と言う場合もある。

ビットマップ情報ヘッダの長さは40バイトで固定である。 機械語本体の右上にあるビットマップ情報ヘッダの「28 00」と次の段の「00 00」は、ヘッダサイズに相当するが、28は十進数に直すと40である(16×2+8=40)。 リトルエンディアンなので、あとの00の3回くりかえしは無視していい。


各部の内容

さて、ビットマップ構造体の図を、ダンプイメージと照らし合わせてみよう。

(再掲)バイナリエディタで見た場合のビットマップの機械語の配置


 ADDRESS   00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F   0123456789ABCDEF 
------------------------------------------------------------------------------
 00000000  42 4D 82 00 00 00 00 00 00 00 76 00 00 00 28 00   BM........v...(. 
 00000010  00 00 05 00 00 00 03 00 00 00 01 00 04 00 00 00   ................ 
 00000020  00 00 0C 00 00 00 C4 0E 00 00 C4 0E 00 00 00 00   ......ト...ト..... 
 00000030  00 00 00 00 00 00 00 00 00 00 00 00 80 00 00 80   ................ 
 00000040  00 00 00 80 80 00 80 00 00 00 80 00 80 00 80 80   ................ 
 00000050  00 00 80 80 80 00 C0 C0 C0 00 00 00 FF 00 00 FF   ......タタタ....... 
 00000060  00 00 00 FF FF 00 FF 00 00 00 FF 00 FF 00 FF FF   ................ 
 00000070  00 00 FF FF FF 00 FF FF F0 00 FF FF F0 00 FF FF   ................ 
 00000080  F0 00  


「42 4D」のあとに 「82 00 00 00」と4バイトぶんの数字が続くが、これはファイルサイズを表している。

なお、このファイルのサイズは130バイトである。16進数の「82」は 10進数で「130」である。算数で10進数の計算で 8×16+2=130 である。

読者のWindowsパソコンでもバージョンによるかもしれないが、『ペイント』などで3×5ピクセルの16色ビットマップ画像を作成すれば、おおむね、このあたりのファイルサイズになるだろう。

なお、ペイントのキャンバスサイズの変更は、左上のメニューアイコンから「プロパティ」を選ぶと「イメージのプロパティ」設定ウィンドウが開き、下のほうにキャンバス幅とキャンバス高さの指定欄があるので、そこで数値を入力してサイズを変形できる。


この「82 00 00 00」の意味は、10進数にすれば

130 + 2561×0 + 2562×0 + 2563×0

という意味である。こういう数え方を「リトル エンディアン」という。

なお「256」の由来は 16×16=256 である。

けっして、8200万バイトでないし、130万バイトでもないので、勘違いしないように。


次の、2つの 予約領域 は、ゼロで埋める決まりになっており、なので「00 00 」「00 00」である。


1段目アドレスのABCDの部分にある「 76 00 00 00」は、まず意味は10進数で118である。7×16+6=118 より。

で、「機械語の118バイト目から、画像データが始まります」と宣言している(ファイル先頭から画像デーマまでの「オフセット」という)。1バイトは256で、16×16=256なので16進数では2ケタぶんです。画像データの先頭のバイトが118バイト目であると、を指し示している。

なお、数え方は左上の「42 4D」ですでに2バイトである。 この調子で118バイト目を探すと

118÷16 = 7 あまり 6

なので、8行目(商7にプラス1)の7列目(あまり「6」+1)のバイト(6)目が、開始位置である。下図の太字にした部分のバイトである。

ADDRESS   00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F   0123456789ABCDEF 
------------------------------------------------------------------------------
00000000  42 4D 82 00 00 00 00 00 00 00 76 00 00 00 28 00   BM........v...(. 
00000010  00 00 05 00 00 00 03 00 00 00 01 00 04 00 00 00   ................ 
00000020  00 00 0C 00 00 00 C4 0E 00 00 C4 0E 00 00 00 00   ......ト...ト..... 
00000030  00 00 00 00 00 00 00 00 00 00 00 00 80 00 00 80   ................ 
00000040  00 00 00 80 80 00 80 00 00 00 80 00 80 00 80 80   ................ 
00000050  00 00 80 80 80 00 C0 C0 C0 00 00 00 FF 00 00 FF   ......タタタ....... 
00000060  00 00 00 FF FF 00 FF 00 00 00 FF 00 FF 00 FF FF   ................ 
00000070  00 00 FF FF FF 00 FF FF F0 00 FF FF F0 00 FF FF   ................ 
00000080  F0 00  


よくみると、この太字の部分からFが5個続き、「000」というのが3回数くりかえすので、合計でFが15回繰り返される。これは、この画像が横5×3であることに対応している。

さて、「画像の横幅」「画像の縦幅」に対応するのは「05 00 00 00」「 03 00 00 00」である。これは、横5ピクセル×縦3ピクセルという意味である。数値の数え方はリトルエンディアンである。


ビットマップ情報ヘッダの終わりから画像の開始までは、カラーパレットが16個、続いています。

上ダンプの太字の直前の「FF FF FF 00」とは、16番目のビットマップのカラーパレットです。カラーパレットは合計で16個あります。また、この16番目パレットを呼び出すのに、画像データ部分のF5回の繰り返しの部分では「F」でパレットを呼び出しています。


その直前のパレットの「FF FF 00 00」が15番目のパレットです。このパレットは「E」で呼び出されますが、今回の画像データでは使われてないです。



「プレーン数」と「色ビット数」の「01 00 」「04 00」は。プレーン数が十進数で1で、色ビット数が十進数で4という意味である。

「圧縮形式」とあるが、無圧縮にする場合はゼロにする。つまり「00 00 00 00」で無圧縮である。ビットマップは、仕様上では、実は圧縮も可能な仕様である。


解像度「C4 0E 00 00」は、印刷などをする際の1メートルあたりのピクセル数ですが、今回は印刷をしないので、気にしないことにします。


「パレット数」は、仕様上は策定されていますが、実際には利用されていません。マイクロソフトの『ペイント』アプリで作ったファイルですらも、ここはゼロのままの「00 00 00 00」です。

「重要な色数」も、仕様上は策定されていますが、やはり実際には利用されていません。これまたマイクロソフトの『ペイント』アプリで作ったファイルですらも、ここはゼロのままの「00 00 00 00」です。

なお、ビットマップの仕様を策定した起業は、昔のIBMとマイクロソフトです。


なお、マイクロソフトのサイトでは


typedef struct tagBITMAPINFOHEADER {
  DWORD biSize;
  LONG  biWidth;
  LONG  biHeight;
  WORD  biPlanes;
  WORD  biBitCount;
  DWORD biCompression;
  DWORD biSizeImage;
  LONG  biXPelsPerMeter;
  LONG  biYPelsPerMeter;
  DWORD biClrUsed;
  DWORD biClrImportant;
} BITMAPINFOHEADER, *LPBITMAPINFOHEADER, *PBITMAPINFOHEADER;

のようにC言語っぽいコードのようなモノが書いてあるが、これは単に構造を説明しているだけであり、このコードをコンパイラなどに入力しても何も起きない。

外部リンク: MSDN BITMAPINFOHEADER structure


実例

背景が白色だと分かりづらいので、黄色にしてみましょう。


それを横5×縦3ピクセルにしたファイルの機械語ダンプは、次のようになります。

 ADDRESS   00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F   0123456789ABCDEF 
------------------------------------------------------------------------------
 00000000  42 4D 82 00 00 00 00 00 00 00 76 00 00 00 28 00   BM........v...(. 
 00000010  00 00 05 00 00 00 03 00 00 00 01 00 04 00 00 00   ................ 
 00000020  00 00 0C 00 00 00 00 00 00 00 00 00 00 00 00 00   ................ 
 00000030  00 00 00 00 00 00 00 00 00 00 00 00 80 00 00 80   ................ 
 00000040  00 00 00 80 80 00 80 00 00 00 80 00 80 00 80 80   ................ 
 00000050  00 00 80 80 80 00 C0 C0 C0 00 00 00 FF 00 00 FF   ......タタタ....... 
 00000060  00 00 00 FF FF 00 FF 00 00 00 FF 00 FF 00 FF FF   ................ 
 00000070  00 00 FF FF FF 00 BB BB B0 00 BB BB B0 00 BB BB   ......ササー.ササー.ササ 
 00000080  B0 00  


「BB BB B0 00 」と3回繰り返すことで、パレットBでピクセルを塗るのを横5マスを3行続けていることが分かります。

末尾の「0」や「00」はおそらく改行のようなものでしょう。


また、キャンバスサイズを拡大し、横6×縦9ピクセルで黄色一色のビットマップ画像にすると、ダンプは下記のようになります。


 ADDRESS   00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F   0123456789ABCDEF 
------------------------------------------------------------------------------
 00000000  42 4D 9A 00 00 00 00 00 00 00 76 00 00 00 28 00   BM........v...(. 
 00000010  00 00 06 00 00 00 09 00 00 00 01 00 04 00 00 00   ................ 
 00000020  00 00 24 00 00 00 00 00 00 00 00 00 00 00 00 00   ..$............. 
 00000030  00 00 00 00 00 00 00 00 00 00 00 00 80 00 00 80   ................ 
 00000040  00 00 00 80 80 00 80 00 00 00 80 00 80 00 80 80   ................ 
 00000050  00 00 80 80 80 00 C0 C0 C0 00 00 00 FF 00 00 FF   ......タタタ....... 
 00000060  00 00 00 FF FF 00 FF 00 00 00 FF 00 FF 00 FF FF   ................ 
 00000070  00 00 FF FF FF 00 BB BB BB 00 BB BB BB 00 BB BB   ......サササ.サササ.ササ 
 00000080  BB 00 BB BB BB 00 BB BB BB 00 BB BB BB 00 BB BB   サ.サササ.サササ.サササ.ササ 
 00000090  BB 00 BB BB BB 00 BB BB BB 00                     サ.サササ.サササ.      


よくみると後半「BB BB BB 00」が9回(※ この画像のタテのピクセル数が9である)、繰り返されています。

また、Bの個数も、6回ずつ(※ この画像の横幅は6である)です。

最後の「00」で、このことから、00バイトで改行を表すのだろう事が分かります。


とすると、パレット番号に「0」や「00」は使えないハズです。



なお、画像データの読み方は、機械語の情報が、画像では左下から右下に対応します。

なので、2色以上を使った画像で、調べてみましょう。

下記の画像は、ややサイズが大きい画像ですが(サイズが小さいとビューワーの性能によっては色ボケしたりして不正確になるので)、

幅30×高さ10 の画像です。白地を基調としておりますが、左上に赤い帯があります。下側に青い帯があります。右上に短く青い帯があります。


ADDRESS 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 0123456789ABCDEF


00000000  42 4D 16 01 00 00 00 00 00 00 76 00 00 00 28 00   BM........v...(. 
00000010  00 00 1E 00 00 00 0A 00 00 00 01 00 04 00 00 00   ................ 
00000020  00 00 A0 00 00 00 00 00 00 00 00 00 00 00 00 00   ................ 
00000030  00 00 00 00 00 00 00 00 00 00 00 00 80 00 00 80   ................ 
00000040  00 00 00 80 80 00 80 00 00 00 80 00 80 00 80 80   ................ 
00000050  00 00 80 80 80 00 C0 C0 C0 00 00 00 FF 00 00 FF   ......タタタ....... 
00000060  00 00 00 FF FF 00 FF 00 00 00 FF 00 FF 00 FF FF   ................ 
00000070  00 00 FF FF FF 00 CC CC CC CC CC CC CC CC CC CC   ......フフフフフフフフフフ 
00000080  CC CC CC CC CC 00 6C CC CC CC CC CC CC CC CC CC   フフフフフ.lフフフフフフフフフ 
00000090  CC CC CC CC C6 00 FF FF FF FF FF FF FF FF FF FF   フフフフニ........... 
000000A0  FF FF FF FF FF 00 FF FF FF FF FF FF FF FF FF FF   ................ 
000000B0  FF FF FF FF FF 00 FF FF FF FF FF FF FF FF FF FF   ................ 
000000C0  FF FF FF FF FF 00 FF FF FF FF FF FF FF FF FF FF   ................ 
000000D0  FF FF FF FF FF 00 FF FF FF FF FF FF FF FF FF FF   ................ 
000000E0  FF FF FF FF FF 00 FF FF FF FF FF FF FF FF FF FF   ................ 
000000F0  FF FF FF FF FF 00 99 99 99 99 99 99 99 99 99 99   ......劔劔劔劔劔 
00000100  9F FF CC CF FF 00 99 99 99 99 99 99 99 99 99 99   ..フマ..劔劔劔劔劔 
00000110  9F FF 6C CF FF 00                                 ..lマ..           
 

太字にしたところから、画像の始まりです。ほぼパレット「C」が30回繰り返されています。2行目の最初が「6C」になってるのは、ペイントが勝手にそういう色に変えただけです。2行目は6のあと「C」が28回繰り返され、最後にパレット「6」番の色です。


パレットCは、斜線にした 「FF 00 00 00」の部分です。「赤色(256段階), 緑色(256段階), 青色(256段階)」の順番になっています。

なので、

「FF 00 00 00」なら、真っ赤な赤色、
「00 FF 00 00」なら、ま緑色、
「00 00 FF 00」なら、完全な青色

です。

「FF FF FF 00」なら、完全な白色、
「00 00 00 00」なら、完全な黒色です。


さて、幅30×高さ10 の画像は、画像で見ると、青い帯は下側にあります。しかし機械語では、青い帯は、最初に書かれています。

このように、上下が反転しています。そういう仕様だからです。


Linux のxxdコマンド

Linux だと、この機械語のテキスト形式のダンプ内容を、機械語のビットマップ画像形式に変換できるコマンドがある。

xxd というコマンドを使えばいい。Fedora 31 では標準では、この xxd がついていない。なのでFedoraにこのコマンドをインストールするために vim-common をインストールする必要がある。Fedora ではvim-common に xxd が含まれている。

xxdはもともと、機械語を16進ダンプするためのコマンドであるが、このソフトウェアには、さらに16進ダンプされた内容のテキストを機械語に戻す機能もある。


引数に -r をつける事により、16進ダンプを機械語に戻せる。

たとえば、なんらかのテキストエディタ(Fedora標準の gedit でもよい)に、下記のテキスト(これはstirringで出力したテキストから、上2行の見出しを除去しただけの物である)をそのままコピーペーストしてテキストエディタに貼り付けし、普通に「test」などの名前で保存しよう。この段階では、ファイル「test」は、まだ単なるテキストファイルである。

00000000  42 4D 16 01 00 00 00 00 00 00 76 00 00 00 28 00   BM........v...(. 
00000010  00 00 1E 00 00 00 0A 00 00 00 01 00 04 00 00 00   ................ 
00000020  00 00 A0 00 00 00 00 00 00 00 00 00 00 00 00 00   ................ 
00000030  00 00 00 00 00 00 00 00 00 00 00 00 80 00 00 80   ................ 
00000040  00 00 00 80 80 00 80 00 00 00 80 00 80 00 80 80   ................ 
00000050  00 00 80 80 80 00 C0 C0 C0 00 00 00 FF 00 00 FF   ......タタタ....... 
00000060  00 00 00 FF FF 00 FF 00 00 00 FF 00 FF 00 FF FF   ................ 
00000070  00 00 FF FF FF 00 CC CC CC CC CC CC CC CC CC CC   ......フフフフフフフフフフ 
00000080  CC CC CC CC CC 00 6C CC CC CC CC CC CC CC CC CC   フフフフフ.lフフフフフフフフフ 
00000090  CC CC CC CC C6 00 FF FF FF FF FF FF FF FF FF FF   フフフフニ........... 
000000A0  FF FF FF FF FF 00 FF FF FF FF FF FF FF FF FF FF   ................ 
000000B0  FF FF FF FF FF 00 FF FF FF FF FF FF FF FF FF FF   ................ 
000000C0  FF FF FF FF FF 00 FF FF FF FF FF FF FF FF FF FF   ................ 
000000D0  FF FF FF FF FF 00 FF FF FF FF FF FF FF FF FF FF   ................ 
000000E0  FF FF FF FF FF 00 FF FF FF FF FF FF FF FF FF FF   ................ 
000000F0  FF FF FF FF FF 00 99 99 99 99 99 99 99 99 99 99   ......劔劔劔劔劔 
00000100  9F FF CC CF FF 00 99 99 99 99 99 99 99 99 99 99   ..フマ..劔劔劔劔劔 
00000110  9F FF 6C CF FF 00                                 ..lマ..           
 

だが、コマンド端末で

xxd -r test > testout

と入力して実行すると、なんと『testout』というファイル名で、画像ファイルが新規作成されている。