Windows API/画像の操作

出典: フリー教科書『ウィキブックス(Wikibooks)』

:

※ 直線や長方形など簡単な図名の描画についてはwikibooks『Windows API/図形の描画』で説明してある。

画像の操作[編集]

原理[編集]

Win32APIには、SetPixel 関数という、座標と(点の)色を指定して、ウィンドウ内に点を描画する関数がある。

理論的には、これさえあれば、ウィンドウ内なら、どんな描画もできるので、自分で画像ファイルのファイル形式を設計することすら可能である。

なぜなら、ビットマップやJPEGなどの画像の集まりは、(中学校などで習うように)所詮は画素という点の集まりであるから。


しかし、すでに存在する画像ファイル規格などを再度設計するのは時間の無駄だから、通常は、既存の規格であるビットマップ形式画像などを活用する。


なお、点のほか、線を描ける LineTo 関数や、円を描ける Ellipse 関数、長方形を描ける Rectangle 関数など、いくつかの基本図形には描画の関数が用意されている。

線などの簡単な図形を表示するだけなら、わざわざビットマップで作成する必要は無い。


画像操作の概要[編集]

Win32APIで扱える画像は、標準設定のままでは ビットマップ画像(.bmp)しか扱えない。

もしPNG画像などを扱いたい場合には、GDI+をインクルードする必要がある。


また、ビットマップには、画像の透明化の機能がないため、初等的な方法ではWin32APIで画像を透明化できない。例えば、矢印型マウスカーソル画像のように、矢印画像の外側を透明化するような機能は、ビットマップには無い。

どうしてもWin32APIで画像の透明化を行いたい場合には、MaskBlt 関数などを利用して、透明化の機能を自作する必要が生じてしまう。

しかし、透明化の自作はメンドウだし、せっかく自作しても互換性が悪いから、普通はそういうことはせず、GDI+のインクルードなどをして、PNG画像など透明化の機能をもった画像形式を活用するのが一般的である。

Win32 API での画像表示に必要な実用知識[編集]

画像の編集の宣言[編集]

まず、GDI+をインクルードしてない状態での操作を説明する。当然、ビットマップ形式しか、画像ファイルは扱えない。

なお、表示したいビットマップ画像そのものの作成は、アクセサリ「ペイント」で先にすませておく必要がある。


さて、Win32 API では LoadImage という関数で、ビットマップ画像の読み込みができる。

しかし、事前に、画像操作用のハンドルをHBITMAP 型を使って宣言しないといけない。「ハンドル」とは、なにやら正体が不明だが、ウィンドウプログラミングでファイル操作をするときとかに、宣言する必要になることになる、何かである。

マイクロソフト社の人が、こういう仕様で作ってしまったので、ユーザーは従うしかない。

おそらく、きっとファイル操作などに必要なメモリ領域やハードディスク領域の確保でも、たぶん宣言しているのだろう。画像のハンドルなら、きっと画像ファイルの操作に必要な領域の確保でもしているのだろう。

とにかくコードは、

	HBITMAP hbmp;

	// 画像をファイルから読み込む
	hbmp = (HBITMAP)LoadImage(NULL, TEXT("yomikomitai_gazou.bmp"), IMAGE_BITMAP, 0, 0,
		LR_LOADFROMFILE | LR_CREATEDIBSECTION);

のようになる。このような順序で、宣言することになる。上のコードでは、「yomikomitai_gazou」という名前のビットマップ画像を読み込む。


さて、Win32 API では、ウィンドウ内での画像の表示と、画像データの作成は、別の関数になる。当然だが、ウィンドウ外部には画像を表示できない。

ウィンドウ内への画像の表示には、BitBlt 関数を使う。

さらに、表示先の画面と、画像編集用の仮想的な画面とが、Win32APIでは概念が分かれている。


Win32APIでも表示先の画面上で、直接、画像を編集することも可能であるが、説明の簡単のため、本書では、画像編集用の仮想画面で編集をしてから、その編集結果を、表示先の画面に転送するという仕組みで説明する。


まず、表示先の画面は、通常の設定では「hdc」という名前である。

TextOut(hdc, 10, 30, TEXT("テスト"), 3);

のように、通常の設定では「hdc」という名前の画面が、ウィンドウ内の画像表示用の画面である。


さて、画像作成用の仮想的な画面は、自分で宣言する必要がある。

例えば、もし画像作成用の画面の名前を hbackDC にするなら、下記のように宣言する。

	HDC hbackDC = CreateCompatibleDC(hdc);

こうすることで、 hdc と互換性のある、画像作成用の領域が確保される。

なお、大文字のHDCは型名である。小文字の hdc とは意味が違うので、区別するように。


そして、 hbackDC で作成した画像を表示するには、例えば

	BitBlt(hdc, 0, 0, 700, 500, hbackDC, 0, 0, SRCCOPY);

のように、hdcに転写しなければならない。


実は、いちいち hbackDCなどの画像作成用の画面を作成しなくとも、直接的にhdcの上で作成・表示もまとめて出きる。

しかし、hdc上で直接作成した場合には、表示する画像の数が多いと、画像の貼り付け中の画面もそのまま表示されてしまう場合がある。

もし、画像の貼り付け中の過程を隠したい場合などには、hbacKDCのように作成画面を別途用意する必要がある。

後片付けが必要[編集]

使い終わった画像操作用ハンドルは、

        DeleteObject(hbmp);

で削除しなければならない。(そういう仕様になっている。削除しないと、一例として、画像描画の関数ブロックを2回目以降に利用するときに、前回の画像描画で使用した画像がメモリ内に残ってたりして、画像がバグったりする。)


しかし、使用中のほかの画面や画像と関連づけてると削除できない(そういう仕様)ので、

        hbmp = NULL; 
        DeleteObject(hbmp);

のように、NULLを入れてから削除すると、確実に削除できる。

使い終わった、画像作成用の画面を削除する場合は、DeleteDC を使う。これまた、使用中のほかのハンドルと関連づけていると削除できない(そういう仕様)ので、

        SelectObject(hbackDC, NULL);
        DeleteDC(hbackDC);

のように、NULLと関連づけさせてから、削除することで、確実に削除できる。

GDI+[編集]

PNG画像やJPEG画像を描画したい場合、GDI+をインクルードすると、比較的に簡便なコードで済むようになる。

入門[編集]

単に

#pragma	comment(lib,"Gdiplus.lib")
#include <ole2.h>
#include <gdiplus.h>
using namespace Gdiplus;

と冒頭に書くだけで、GDI+をインクルードできる。


なお、原理的には

#include <ole2.h>
#include <gdiplus.h>

だけでもインクルードできるが、しかしMSDN『Image::RemovePropertyItem method』 にあるGDI+のサンプルコードにある関数のいくつかを使う際に、上述の #pragma comment(lib,"Gdiplus.lib")using namespace Gdiplus; が必要になる。


なお、先に ole2.h をインクルードしないと、コンパイルがエラーになってしまい、識別子 IStreamが定義されていないとか警告を出されてしまう。

なので、上述コードの順序どおりに、先に ole2.h をインクルードしてから、あとに gdiplus.h をインクルードする必要がある。

あらかじめ「test.png」などのPNG画像を用意しておき、それを実行ファイルのソースコードのcppファイルのあるフォルダに入れておく必要がある。(けっしてソリューションファイル(拡張子 .sln )のあるフォルダ階層ではない。cppファイル(拡張子 .cpp)のあるフォルダ階層のほうに入れること。)

そして、 WM_PAINT: を下記のように書き換える。

書き換えることになるだろう箇所は、

「 // TODO: HDC を使用する描画コードをここに追加してください...」の下から、
「EndPaint(hWnd, &ps);」の上まで、

である。

(動作確認: 2020年4月14日に、下記コードで Visual Studio 2019 にて動作することを確認ずみ)
    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            // TODO: HDC を使用する描画コードをここに追加してください...

            // GDI+ の初期化
            GdiplusStartupInput gdiplusStartupInput; // MSDNにそのままのコードがある.
            ULONG_PTR gdiplusToken;
            GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

            // 以上、MSDN からの引用.

            // Graphics 型の関数の読み込みのためにダミー変数 graphics を宣言.
            Graphics graphics(hdc);

            // 画像の読み込み「image」は変数名。
            Image image(L"test.png");
            
            // 画像の描画。 ダミー変数 graphics を仲介して描画する必要がある.
            graphics.DrawImage(&image, 0, 0, image.GetWidth(), image.GetHeight());

            EndPaint(hWnd, &ps);
        }
        break;
※ wikiでは、なんかダミー変数側の「graphics」「image」などが青文字にハイライトされたりして、いかにも型名っぽく見えるかもしれない。だが、Visual Studio では、ここの部分は黒文字になる。

画像を2枚以上表示したい場合[編集]

単に、下記のように、繰り返しで、

            // 画像の読み込み「image」は変数名。
            Image ○○(L"test.png");
            
            // 画像の描画。 ダミー変数 graphics を仲介して描画する必要がある.
            graphics.DrawImage(&○○, 0, 0, ○○.GetWidth(), ○○.GetHeight());

の部分を必要な画像の枚数と同じ回数だけ、繰り返せばいい。


つまり、コード例は、

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


            Rectangle(hdc, 50, 50, 200, 150);  // 描画


            // GDI+ の初期化
            GdiplusStartupInput gdiplusStartupInput; // MSDNにそのままのコードがある.
            ULONG_PTR gdiplusToken;
            GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

            // 以上、MSDN からの引用.

            // Graphics 型の関数の読み込みのためにダミー変数 graphics を宣言.
            Graphics graphics(hdc);


            // 画像の読み込み「image1」は変数名。
            Image image1(L"background.png");

            // 画像の描画。 ダミー変数 graphics を仲介して描画する必要がある.
            graphics.DrawImage(&image1, 0, 0, image1.GetWidth(), image1.GetHeight());


            // 画像の読み込み「image2」は変数名。
            Image image2(L"filter.png");

            // 画像の描画。 ダミー変数 graphics を仲介して描画する必要がある.
            graphics.DrawImage(&image2, 0, 0, image2.GetWidth(), image2.GetHeight());

            EndPaint(hWnd, &ps);
        }
        break;

のようになる。

画像を半透明にしたい場合[編集]

あらかじめ、半透明にさせたい側の画像を、画像編集ソフトで半透明にしておけば、コマンド ○○.DrawImage のときに自動で半透明にしてくれる。


たとえば、もしも、不透明の背景 background.png に、半透明の黒フィルター filter.png を載せたい場合、

png画像を編集できるなんらかの画像編集ソフトで、そのまま半透明の画像を作成しておけば、あとは上記のコードを実行するだけで、半透明フィルターを載せてくれる。


あとから描画された画像のほうが画像レイヤーでは上層に来るので、載せるフタ側の画像(例の場合ではfilter.png)をあとから描画関数をする必要がある。


※ ひょっとしたwin32API側で透明度を調整する事も可能かもしれないが、あまり簡単な方法が普及しておらず、ネットで調べても、(win32API側で透明度を調整する方法については)なかなか見つからない。


初回起動時の初期設定を一括したい場合[編集]

WM_PAINT を毎回読み込む場合にGDIの設定プログラムを起動するのは無駄なので、

初回起動にまとめて処理する場合には、wWinMain 関数 のほうで処理する。


いっぽう、 WM_CREATE で処理しようとしても、なぜかコンパイルエラーになる。


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

winMain側
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
	_In_opt_ HINSTANCE hPrevInstance,
	_In_ LPWSTR    lpCmdLine,
	_In_ int       nCmdShow)
{
	UNREFERENCED_PARAMETER(hPrevInstance);
	UNREFERENCED_PARAMETER(lpCmdLine);

	// TODO: ここにコードを挿入してください。

						 // GDI+ の初期化
	GdiplusStartupInput gdiplusStartupInput; // MSDNにそのままのコードがある.
	ULONG_PTR gdiplusToken;
	GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

	// 以上、MSDN からの引用.


WM_PAINT 側
			Graphics graphics(hdc);

			// 画像の読み込み「image2」は変数名。
			Image image2(L"filter.png");

			// 画像の描画。 ダミー変数 graphics を仲介して描画する必要がある.
			graphics.DrawImage(&image2, 0, 0, image2.GetWidth(), image2.GetHeight());

画像ファイル出力[編集]

※ 動作は未確認

画像を、表示画像ではなく、外部ファイルとして受け渡しできるようにファイル出力したい場合、CImage.Save で可能です。つまり CImage クラスのsave メソッドという機能を使います。

GDI+を使った画像ファイル出力の方法もあるらしいが、しかしC++の場合、ファイル容量の計算やピクセル位置の計算などが必要になるようでコードが難解でやや長く、初心者にとって簡便な方法は知られていない。あったとしても、少なくとも日本のネットで検索した限りはGDI+による画像ファイルの簡便な見つからない。なので、CImageによる方法が良いだろう。

CImageを使うには、"MFC"というものをインストールする必要がある。標準では、MFCはインストールされていないので、Visual Studioのインスト-ラでMFCビルドツールを追加インストールする必要がある。もし

#include <afxwin.h>

を書いてエラーになるようならMFCがインストールされていないので、プログラミングにおけるCImageの利用のためにはMFCビルドツールをインストールしなければならない。

MFC[編集]

MFCのインストール方法[編集]

あらかじめVisual Studio を最新版に更新しておく必要がある(最新版でないと、MFCのインストールを開始できない) 。更新に長い時間が掛かる場合があるので、帰宅前や就寝前にでも更新を行っておく。


Visual Studio を最新版にしたあと、Visual Studioインスト-ラで「C++によるデスクトップ開発」をチェックすると表れるインストールメニューの一覧から「最新の v142 ビルドツール用 C++ MFC (x86 および x64)」にチェックを入れ(バージョンは時期によって異なる)、「変更」ボタンをクリックすることで、MFCのビルドツールのインストールが開始する。これもインストールに長い時間が掛かるので、あらかじめ時間の段取りを工夫しておこう。


プログラミングのための設定[編集]

Visual studioで「MFCアプリ」の開発を起動した場合と、既存のwindowsデスクトップアプリケーションにMFC関連の関数を組み込むための方法とは異なる。

この単元では、既存のwindowsデスクトップアプリケーションにMFC関連の関数を組み込むための方法を説明する。

include文[編集]

さてMFCのインストールが完了したとします。プログラミング方法については、MFCをどういう方法で使うかによってinclude方法が異なる可能性はありますが、おおむね冒頭のinclude文で、まずは

#include "stdafx.h"
#define _AFXDLL
#include <afxwin.h> // MFC に必要
#include "atlimage.h" // CImage に必要

のように宣言する必要が生じるかと思います。Windowsの実装上の都合により、_AFXDLL という定数のdefine関数が必要になります。

このほか、ソリューションのプロパティ設定により、

『構成プロパティ』>『詳細』>『MFCの使用』>『スタティック ライブラリで MFC を使用する』と設定したり、
『構成プロパティ』>『C/C++』>『コード生成』>『ランタイムライブラリ』>『マルチスレッド DLL(/MD)』と設定したり、

する必要があるかもしれません。

どうプロパティ設定するかは用途によって個々人で違う可能性があるので、コンパイラのエラーメッセージなどを参考に都度、ネット検索などで調べてください。

MFCは元来、今まで習ってきた「Windowsデスクトップアプリケーション」でプログラミングするようにはできておらず、このため、「Windowsデスクトップアプリケーション」で扱う際に必要になる設定がなかなか難しいのが難点です。

ビルド時に windows.h が 2回読み込まれてエラーになってコンパイルができない場合があるので、その場合は作成プロジェクトの framework.h ファイルにある #include <windows.h> を、コメントアウトして

// #include <windows.h>

のようにする必要があるかもしれません。