第4回 GDI+をきちんと使う

これまでの作業ではATL/WTLアプリケーションウイザードの標準設定で「ImageViewer」というプロジェクトを作成、自動生成されたソースコードのうち「ImageViewerView.h」を書き換えて以下のようにした。

// ImageViewerView.h : CImageViewerView クラスのインターフェイス
//
/////////////////////////////////////////////////////////////////////////////

#pragma once

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

class CImageViewerView : public CWindowImpl<CImageViewerView>
{
public:
	DECLARE_WND_CLASS(NULL)

	BOOL PreTranslateMessage(MSG* pMsg)
	{
		pMsg;
		return FALSE;
	}

	BEGIN_MSG_MAP(CImageViewerView)
		MESSAGE_HANDLER(WM_PAINT, OnPaint)
	END_MSG_MAP()

// ハンドラーのプロトタイプ (引数が必要な場合はコメントを外してください):
//	LRESULT MessageHandler(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
//	LRESULT CommandHandler(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
//	LRESULT NotifyHandler(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/)

	LRESULT OnPaint(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	{
		CPaintDC dc(m_hWnd);

		//GDI+初期化
		ULONG_PTR	_nToken;
		GdiplusStartupInput _sGdiplusStartupInput;
		GdiplusStartup(&_nToken,&_sGdiplusStartupInput,NULL);

		//画像読み込み
		Bitmap*		pImage;
		Graphics	cGraphics(dc.m_hDC);

		pImage = Bitmap::FromFile(L"test.jpg",TRUE);

		//画像表示
		RECT	rect;
		GetClientRect(&rect);
		cGraphics.DrawImage(pImage,(REAL)0,(REAL)0,(REAL)(rect.right - rect.left),(REAL)(rect.bottom - rect.top));

		//画像開放
		delete	pImage;

		//GDI+開放処理
//		GdiplusShutdown(_nToken);
		return 0;
	}
};


たったこれだけのソースコードで画像ファイルを読み込んで画面いっぱいに表示できた。しかし前にも述べたように、このソースコードは誤った使い方をしている部分が少なからずある。また描画するごとに画像ファイルを読み込んでいるため効率も悪い。ここではこれらの点を修正する。

まず先頭にある以下の3行。
#include	&quot;gdiplus.h&quot;
#pragma	comment(lib,&quot;Gdiplus.lib&quot;)
using namespace Gdiplus;

これは画像描画に用いるGDI+を利用するための宣言だ。これらはソースコード中のどこからも参照できるようにする。

ImageViewer04_01.gif
画面左側のソリューションエクスプローラにある「stdafx.h」をダブルクリックして開く。このヘッダーファイルはプロジェクト内で共通の宣言を置く場所だ。


ImageViewer04_02.gif
ここに先ほどの3行を移動する。

少し飛んでOnPaint内の以下の2箇所の部分。
		//GDI+初期化
		ULONG_PTR	_nToken;
		GdiplusStartupInput _sGdiplusStartupInput;
		GdiplusStartup(&_nToken,&_sGdiplusStartupInput,NULL);
		//GDI+開放処理
//		GdiplusShutdown(_nToken);

これらはそれぞれGDI+を利用するための初期化と終了処理だ。このソースコードでは描画毎に初期化を行い、終了処理は行っていない。 本来描画ごとに行うべきではない。GDI+の初期化と終了処理はアプリケーションの起動時と終了時に1回ずつ行えばいい。そのためこれらの処理も移動する。

ImageViewer04_03.gif
画面左側のソリューションエクスプローラにある「ImageViewer.cpp」をダブルクリックして開く。このcppファイルはアプリケーションの起動処理が書かれている場所だ。

ImageViewer04_04.gif
ImageViewer.cpp内の_tWinMain()を見ると、「int nRet = Run(lpstrCmdLine, nCmdShow);」という部分がある。このRun()の中でアプリケーションのウインドウが生成・表示されている。そのためこの部分を挟んで初期化と終了処理を行う。

ImageViewer04_05.gif
このとき今まで実行しないようにコメントアウトしていた終了処理のコメントを外しておく。
具体的には以下のようにする。

	//GDI+初期化
	ULONG_PTR	_nToken;
	GdiplusStartupInput _sGdiplusStartupInput;
	GdiplusStartup(&_nToken,&_sGdiplusStartupInput,NULL);

	int nRet = Run(lpstrCmdLine, nCmdShow);

	//GDI+開放処理
	GdiplusShutdown(_nToken);

場所を戻って「ImageViewerView.h」内の以下の項目。
	BEGIN_MSG_MAP(CImageViewerView)
		MESSAGE_HANDLER(WM_PAINT, OnPaint)
	END_MSG_MAP()

// ハンドラーのプロトタイプ (引数が必要な場合はコメントを外してください):
//	LRESULT MessageHandler(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
//	LRESULT CommandHandler(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
//	LRESULT NotifyHandler(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/)


ImageViewer04_06.gif
この部分はメッセージマップと呼ばれる。ここではWM_PAINTというメッセージの処理が定義されている。
WM_PAINTは描画(ペイント)用のメッセージで、ウインドウの表示内容を描画しなおさなければならないときに送られるメッセージだ。そのWM_PAINTメッセージをOnPaint()という関数で処理することが定義されている。

ImageViewer04_07.gif
このメッセージマップの部分にウインドウが生成されるときと破壊されるときのメッセージ処理を定義する。
ウインドウが生成するときに送られるメッセージはWM_CREATE、破壊するときはWM_DESTROYになる。それぞれOnCreate()とOnDestroy()で処理する。

	BEGIN_MSG_MAP(CImageViewerView)
		MESSAGE_HANDLER(WM_PAINT, OnPaint)
		MESSAGE_HANDLER(WM_CREATE, OnCreate)
		MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
	END_MSG_MAP()

// ハンドラーのプロトタイプ (引数が必要な場合はコメントを外してください):
//	LRESULT MessageHandler(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
//	LRESULT CommandHandler(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
//	LRESULT NotifyHandler(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/)

	//ウインドウ生成時処理
	LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	{
		return	0;
	}

	//ウインドウ破壊時処理
	LRESULT OnDestroy(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	{
		return	0;
	}


ImageViewer04_08.gif
ウインドウ生成時と破壊時処理用の関数を用意しただけでは意味がない。次にウインドウの生成時に画像を読み込み、破壊時に画像を開放する処理を入れる。これによりOnPaint()内もだいぶスマートになった。

	Bitmap*		pImage;

	//ウインドウ生成時処理
	LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	{
		//画像読み込み
		pImage = Bitmap::FromFile(L"test.jpg",TRUE);

		return	0;
	}

	//ウインドウ破壊時処理
	LRESULT OnDestroy(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	{
		//画像開放
		delete	pImage;

		return	0;
	}

	LRESULT OnPaint(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	{
		CPaintDC dc(m_hWnd);

		Graphics	cGraphics(dc.m_hDC);

		//画像表示
		RECT	rect;
		GetClientRect(&rect);
		cGraphics.DrawImage(pImage,(REAL)0,(REAL)0,(REAL)(rect.right - rect.left),(REAL)(rect.bottom - rect.top));

		return 0;
	}


ImageViewer04_09.gif
これでソースコードの修正が済んだ。ビルドを行い、デバッグする。


ImageViewer04_10.gif
ソースコードではGDI+を起動時、画像読み込みをウインドウ生成時、GDI+終了処理を最後に行うようにし、処理効率が上がった。しかしウインドウサイズを変更すると...まだちらついてしまう。
次回はこの"ちらつき"を解消する。

プロジェクトファイルをダウンロード


カテゴリー「画像表示ソフトを作る」 のエントリー