GDI+を利用した画像処理テストのスケルトン2

解説

 これまで使ってきた画像処理用のスケルトンはあくまでも描画テスト用で、 2つ以上の処理を組み合わせた画像処理を試そうと考えるとコード量が多くなり 読みづらいし、試しづらかった。またコーディング自体もミスだらけで、ちょ っと入力画像を誤ると破綻するものだった。
 そのためここではクラス化して少しはまともなスケルトンを作った。これ からはこのコードをベースにクラスCDnpImageを拡張しながら画像処理を実験 する予定。


実行例




ソースコード

 以下のコードを「DnpImageBase.h」というファイル名で保存。 各種画像処理を施したいときはこのクラスCDnpImageにメンバー関数として加える。


#pragma once

#include "DnpImageBase.h"

class	CDnpImage : public CDnpImageBase
{
public:

	bool	GrayScale(void)
	{
		if(IsValidImage() == false)
			return	false;

		int			nStrideBytes;
		bool		ret;
		UINT		x;
		UINT		y;
		UINT		nWidth;
		UINT		nHeight;
		BYTE*		pData;
		RGBTRIPLE*	pRGB;

		nWidth	= GetWidth();
		nHeight	= GetHeight();

		ret = LockBits(&pData,&nStrideBytes);
		if(ret == false)
			return	false;

		for(y = 0; y < nHeight; y++)
		{
			pRGB = (RGBTRIPLE*)pData;
			for(x = 0; x < nWidth; x++)
			{
				//グレースケール化
				pRGB[x].rgbtRed		= (BYTE)((77 * pRGB[x].rgbtRed + 150 * pRGB[x].rgbtGreen + 29 * pRGB[x].rgbtBlue) >> 8);
				pRGB[x].rgbtGreen	= pRGB[x].rgbtRed;
				pRGB[x].rgbtBlue	= pRGB[x].rgbtRed;
			}
			pData += nStrideBytes;
		}

		UnlockBits();

		return	true;
	}
};


 以下のソースコードを「DnpImageBase.h」というファイル名で保存する。 このクラスはGDI+を利用した画像の読み込み、表示を担い、画像処理は派生 クラスなどで行う。


#pragma once


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


//
//	このクラスを利用するには、プログラム起動時にInitialize()を、
//	終了時にUninitialize()を呼び出さないとダメ
//
class	CDnpImageBase
{

	//
	//	表示用サムネイル管理クラス
	//
	//本当はImage::GetThumbnailImage()を使った方がいいのだが、
	//画像処理をしてもその結果が反映されないので::StretchBltを
	//使っている。今後なんとかすべき?
	//
	class	CThumbnail
	{
		HDC		_hThumbnailDC;			//縮小画像用の裏画面
		HBITMAP	_hThumbnailBitmap;		//DC用のビットマップ

		UINT	_nWidth;				//縮小画像の幅
		UINT	_nHeight;				//縮小画像の高さ

	public:

		//
		//	コンストラクタ
		//
		CThumbnail()
		{
			_hThumbnailDC = NULL;
			_hThumbnailBitmap = NULL;
			Init();
		}

		//
		//	デストラクタ
		//
		~CThumbnail()
		{
			Init();
		}

		//
		//	クラスの初期化
		//
		//このクラスに格納されているサムネイル画像の破棄
		//
		void	Init(void)
		{
			if(_hThumbnailDC)
				::DeleteDC(_hThumbnailDC);
			_hThumbnailDC = NULL;

			if(_hThumbnailBitmap)
				::DeleteObject(_hThumbnailBitmap);
			_hThumbnailBitmap = NULL;

			_nWidth = 0;
			_nHeight = 0;
		}


		//
		//	画像の横幅取得
		//
		UINT	GetWidth(void)
		{
			return	_nWidth;
		}

		//
		//	画像の縦幅取得
		//
		UINT	GetHeight(void)
		{
			return	_nHeight;
		}

		//
		//	縮小画像が読み込まれているかチェック
		//
		bool	IsValidImage(void)
		{
			if(_nWidth == 0 || _nHeight == 0 || _hThumbnailDC == NULL || _hThumbnailBitmap == NULL)
				return	false;
			return	true;
		}


		//
		//	縮小画像の作成
		//
		//pImageを元にnWidth,nHeightサイズの縮小画像を作る
		//元画像と指定したサイズの縦横比が異なると縦長や横長の画像になるので注意
		//
		bool	CreateThumbnail(Bitmap* pImage,HDC hDC,UINT nWidth,UINT nHeight)
		{
			BOOL	ret;
			HDC		hDCTmp;
			HBITMAP	hBitmap;

			Init();
			if(pImage == NULL)
				return	false;

			pImage->GetHBITMAP(RGB(0,0,0),&hBitmap);
			if(hBitmap == NULL)
				return	false;

			hDCTmp = ::CreateCompatibleDC(hDC);
			::SelectObject(hDCTmp,hBitmap);

			_hThumbnailDC = ::CreateCompatibleDC(hDC);
			_hThumbnailBitmap = ::CreateCompatibleBitmap(hDC,nWidth,nHeight);
			::SelectObject(_hThumbnailDC,_hThumbnailBitmap);

			//ピクセル削除モードで拡大縮小
			::SetStretchBltMode(_hThumbnailDC,COLORONCOLOR);
			ret = ::StretchBlt(_hThumbnailDC,0,0,nWidth,nHeight,hDCTmp,0,0,pImage->GetWidth(),pImage->GetHeight(),SRCCOPY);

			::DeleteDC(hDCTmp);
			::DeleteObject(hBitmap);

			if(ret == FALSE)
			{
				Init();
				return	false;
			}

			_nWidth		= nWidth;
			_nHeight	= nHeight;

			return	true;
		}


		//
		//	縮小画像の表示
		//
		bool	Draw(HDC hDC,int nX,int nY)
		{
			BOOL	ret;

			if(IsValidImage() == false)
				return	false;

			ret = ::BitBlt(hDC,nX,nY,_nWidth,_nHeight,_hThumbnailDC,0,0,SRCCOPY);

			return	ret ? true : false;
		}

	};


	CThumbnail	_cThumbnail;		//表示用の縮小画像管理クラス

protected:
	Bitmap*		_pMainImage;		//読み込んだ画像オブジェクト



	//サムネイルの作成が必要かどうか
	//_pMainImageを画像処理したら_pMainImageと_pThumbnailで画像が異なってしまう。
	//そのため_pMainImageを変更したら必ず_bNeedNewThumb=trueに変更すること!
	//例外!LockBits()とUnlockBits()を利用した画像処理の場合は自動的に処理さ
	//れるためこの変数をいじる必要はない
	bool		_bNeedNewThumb;


	//現在読み込んでいる画像ファイル名とそのファイル更新日時
	FILETIME	_sFileTime;
	CAtlString	_strFile;

	ULONG_PTR	_nToken;			//GDI+の管理値
public:

	//
	//	コンストラクタ
	//
	CDnpImageBase()
	{
		_nToken = 0;
		_pMainImage = NULL;
		_bNeedNewThumb = true;
		_bLockBitmap = false;
		Init();
	}

	//
	//	デストラクタ
	//
	~CDnpImageBase()
	{
		Init();
	}

	//
	//	初期化
	//
	//読み込んでいる画像がある場合はそれを破棄する
	//
	void	Init()
	{
		if(_bLockBitmap)
			UnlockBits();

		_bNeedNewThumb = true;
		ZeroMemory(&_sFileTime,sizeof(FILETIME));
		_strFile = _T("");

		if(_pMainImage)
			delete	_pMainImage;
		_pMainImage = NULL;

		_cThumbnail.Init();
	}


	//
	//	GDI+の初期化
	//
	//この関数はアプリケーション開始時に一度だけ呼ぶこと
	//呼ばないとこのクラス自体が使えません。
	//
	void	Initialize(void)
	{
		//GDI+開始
		GdiplusStartupInput _sGdiplusStartupInput;
		GdiplusStartup(&_nToken,&_sGdiplusStartupInput,NULL);
	}

	//
	//	GDI+の終了処理
	//
	//この関数はアプリケーション終了時に一度だけ呼ぶこと!
	//
	void	Uninitialize(void)
	{
		Init();
		GdiplusShutdown(_nToken);
	}



	//
	//	画像ファイル読み込み
	//
	//pszFileで指定された画像を読み込む
	//もしも前回、同じファイルを読んでいた場合は読み込まない
	//bForce=trueを指定すれば強制的に読み込む
	//
	bool	LoadFile(LPCTSTR pszFile,bool bForce=false)
	{
		//////////////////////////////
		//	ファイル更新時刻取得
		//
		HANDLE			hFind;
		WIN32_FIND_DATA	FindFileData;

		hFind = ::FindFirstFile(pszFile,&FindFileData);
		if(hFind == INVALID_HANDLE_VALUE)
			return	false;
		::FindClose(hFind);

		if(bForce == false)
		{
			if(IsValidImage() && _strFile == pszFile)
			{
				if(::CompareFileTime(&_sFileTime,&FindFileData.ftLastWriteTime) == 0)
					return	true;		//既に同じ画像が読み込まれている
			}
		}

		//////////////////////////////
		//	初期化
		//
		Init();


		//////////////////////////////
		//	画像読込
		//
		#ifdef	_UNICODE
			_pMainImage = Bitmap::FromFile(pszFile,TRUE);
		#else
			_pMainImage =  Bitmap::FromFile((CAtlStringW)pszFile,TRUE);
		#endif


		if(IsValidImage() == false)
			return	false;			//読み込み失敗!

		//読み込むファイル名と更新日時の保存
		_strFile = pszFile;
		memcpy(&_sFileTime,&FindFileData.ftLastWriteTime,sizeof(FILETIME));

		return	true;
	}



	//
	//	画像の幅取得(単位ピクセル)
	//
	UINT	GetWidth(void)
	{
		if(IsValidImage() == false)
			return	0;

		return	_pMainImage->GetWidth();
	}

	//
	//	画像の高さ取得(単位ピクセル)
	//
	UINT	GetHeight(void)
	{
		if(IsValidImage() == false)
			return	0;

		return	_pMainImage->GetHeight();
	}


	//
	//	画像が読み込まれているかチェック
	//
	//このクラスに現在画像が読み込まれているかを調べる。
	//
	bool	IsValidImage(void)
	{
		if(_pMainImage == NULL || _pMainImage->GetWidth() == 0 || _pMainImage->GetHeight() == 0)
		{
			Init();
			return	false;
		}

		return	true;
	}



	//
	//	クラスの複製
	//
	//このクラスに現在読み込まれている画像をcCopyToにコピーする
	//あくまでも"コピー"になることに注意。参照ではないのでcCopyTo
	//に画像処理を施してもこのクラス内の画像に影響はない。
	//
	bool	Clone(CDnpImageBase& cCopyTo)
	{
		bool	ret;
		Bitmap*	pBitmap;

		pBitmap = NULL;
		ret = CloneBitmap(pBitmap);
		if(ret == false)
			return	false;

		return	cCopyTo.SetBitmap(pBitmap);
	}


	//
	//	ビットマップ指定
	//
	//現在読み込まれている画像を破棄して、pBitmapのコピーを読み込む。
	//関数内部でBitmapをコピーしていることに注意!
	//
	//	CDnpImageBase	cImage;
	//	Bitmap*		pSrc;
	//
	//	pSrc = Bitmap::LoadFromFile(...)
	//	cImage.SetBitmap(pSrc);				//←ここで画像読み込み
	//	delete	pSrc;						//←不要なら削除しなければならない!
	//
	//	cImage.Draw(...)
	//
	//
	bool	SetBitmap(Bitmap* pBitmap)
	{
		if(pBitmap == NULL || pBitmap->GetWidth() == 0 || pBitmap->GetHeight() == 0)
			return	false;

		Init();

		//Cloneしたものを渡す!
		_pMainImage = pBitmap->Clone(Rect(0,0,pBitmap->GetWidth(),pBitmap->GetHeight()),PixelFormatDontCare);

		return	IsValidImage();
	}


	//
	//	ビットマップの複製取得
	//
	//現在読み込まれている画像のBitmapを複製して渡す。
	//取得したBitmapを画像処理しても、このクラス内の画像には影響しない。
	//
	bool	CloneBitmap(Bitmap*& pBitmap)
	{
		pBitmap = NULL;
		if(IsValidImage() == false)
			return	false;

		pBitmap = _pMainImage->Clone(Rect(0,0,_pMainImage->GetWidth(),_pMainImage->GetHeight()),PixelFormatDontCare);
		if(pBitmap == NULL)
			return	false;
		if(pBitmap->GetWidth() != _pMainImage->GetWidth() || pBitmap->GetHeight() != _pMainImage->GetHeight())
		{
			delete	pBitmap;
			pBitmap = NULL;
			return	false;
		}

		return	true;
	}



	//
	//	画像の描画
	//
	//指定したDCに対して画像を描画する。
	//幅と高さの比が元画像と異なる場合は...画像が縦長や横長になって表示される!
	//
	bool	Draw(HDC hDC,int nX,int nY,int nWidth,int nHeight)
	{
		//サムネイルを作成してそれを表示する

		if(_bNeedNewThumb || _cThumbnail.IsValidImage() == false || _cThumbnail.GetWidth() != nWidth || _cThumbnail.GetHeight() != nHeight)
		{
			//サムネイル作成
			bool	ret;

			if(IsValidImage() == false)
				return	false;

			ret = _cThumbnail.CreateThumbnail(_pMainImage,hDC,nWidth,nHeight);
			if(ret == false)
				return	false;

			_bNeedNewThumb = false;
		}

		return	_cThumbnail.Draw(hDC,nX,nY);
	}




private:
	bool		_bLockBitmap;
	BitmapData	_sLockBitmapData;


public:

	//
	//	画像編集用にRGB配列取得
	//
	//ppcbRGBDataに読み込まれている画像の生データ、pnStrideBytesに画像の横一列に使われているバイト数が返る。
	//
	//取り出した画像*ppcbRGBDataはRGBTRIPLE配列で並んでいる。ただし *pnStrideBytes != nWidth * sizeof(RGBTRIPLE)
	//のことがあるので注意!(*pnStrideBytesに負の値が入っても処理できるようにすること!)
	//
	//画像編集が終わったら必ずUnlockBits()を呼び出すこと!
	//
	bool	LockBits(BYTE** ppcbRGBData,int* pnStrideBytes)
	{
		Status		ret;

		if(ppcbRGBData)
			*ppcbRGBData = NULL;
		if(pnStrideBytes)
			*pnStrideBytes = 0;
		if(_bLockBitmap || IsValidImage() == false || ppcbRGBData == NULL || pnStrideBytes == NULL)
			return	false;

		ret = _pMainImage->LockBits(&Rect(0,0,_pMainImage->GetWidth(),_pMainImage->GetHeight()),ImageLockModeRead | ImageLockModeWrite,PixelFormat24bppRGB,&_sLockBitmapData);
		if(ret != Ok)
			return	false;

		_bLockBitmap = true;

		*ppcbRGBData = (BYTE*)_sLockBitmapData.Scan0;
		*pnStrideBytes = _sLockBitmapData.Stride;

		return	true;
	}


	//
	//	画像編集終了
	//
	//LockBits()と組みにして利用すること!
	//
	bool	UnlockBits(void)
	{
		if(_bLockBitmap == false || IsValidImage() == false)
			return	false;

		_pMainImage->UnlockBits(&_sLockBitmapData);
		_bLockBitmap = false;
		_bNeedNewThumb = true;

		return	true;
	}
};



 WTLのSDI Applicationでプロジェクトを作成し、...View.hに太字で示したコードを 追加する。この例ではプロジェクト名を「ImageEffect114」にしている。


// ImageEffect114View.h : interface of the CImageEffect114View class
//
/////////////////////////////////////////////////////////////////////////////


#pragma	once

#include "DnpImage.h"


class CImageEffect114View : public CWindowImpl
{
	CDnpImage	_cImage1;
	CDnpImage	_cImage2;

public:
	DECLARE_WND_CLASS(NULL)

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

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

// Handler prototypes (uncomment arguments if needed):
//	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*/)

	CImageEffect114View()
	{
		//GDI+初期化処理
		//本当はアプリケーション起動時にすべき
		_cImage1.Initialize();

		//画像ファイルの読み込みとコピー
		_cImage1.LoadFile(_T("c:\\test.jpg"));
		_cImage1.Clone(_cImage2);

		//画像処理
		_cImage2.GrayScale();
	}

	~CImageEffect114View()
	{
		//画像の破棄
		_cImage1.Init();
		_cImage2.Init();


		//GDI+終了処理
		//本当はアプリケーション終了時にすべき
		_cImage1.Uninitialize();
	}

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

		_cImage1.Draw(dc.m_hDC,0,0,300,200);
		_cImage2.Draw(dc.m_hDC,302,0,300,200);

		return 0;
	}
};

カテゴリー「VC++ TIPS」 のエントリー