画像のRGB->HSV変換とHSV->RGB変換

解説

 通常用いるRGB配列の画像を一度HSVに変換し、輝度を示すVを少し増やして再びRGB配列に 戻した。
 変換公式は...微妙に複雑なので知りたい人は「HSV色空間」や「RGB HSV」などの単語 で検索をかけて調べてほしい。


実行例




ソースコード

 WTLのプロジェクトウイザードを使ってSDI Apllicationプロジェクトを作成し、View用の ヘッダーファイルを以下のように変える。ここではプロジェクト名を「ImageEffect111」と しています。


// ImageEffect111View.h : interface of the CImageEffect111View class
//
/////////////////////////////////////////////////////////////////////////////

#pragma once

#include 
#pragma	comment(lib,"Gdiplus.lib")


class CImageEffect111View : public CWindowImpl
{
public:
	DECLARE_WND_CLASS(NULL)

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

	BEGIN_MSG_MAP(CImageEffect111View)
		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*/)


	double	Min(double a,double b,double c)
	{
		if(a <= b && a <= c)
			return	a;
		if(b <= a && b <= c)
			return	b;
		return	c;
	}

	double	Max(double a,double b,double c)
	{
		if(a >= b && a >= c)
			return	a;
		if(b >= a && b >= c)
			return	b;
		return	c;
	}




	void	RGBtoHSV(RGBTRIPLE rgb,double& dH,double& dS,double& dV)
	{
		double	dR;
		double	dG;
		double	dB;
		double	dMax;
		double	dMin;
		double	dDiff;

		//RGB->HSV変換時のR,G,Bは 0.0~1.0
		dR = rgb.rgbtRed	/ 255.0;
		dG = rgb.rgbtGreen	/ 255.0;
		dB = rgb.rgbtBlue	/ 255.0;

		dMax = Max(dR,dG,dB);
		dMin = Min(dR,dG,dB);

		if(dMax == 0)		//巷のコードではdMax==dMinで処理していることがあるがそれはNG
		{
			dS = 0.0;
			dV = 0.0;
			return;
		}

		dDiff = dMax - dMin;
		dV = dMax;
		dS = dDiff / dMax;
		//dS = dMax - dMin;			//円錐色空間時のS

		if(dR == dMax)
			dH = 60 * (dG - dB) / dDiff;
		else if(dG == dMax)
			dH = 60 * (dB - dR) / dDiff + 120;
		else
			dH = 60 * (dR - dG) / dDiff + 240;

		//上の計算ではdHが負になることがある
		if(dH < 0)
			dH = dH + 360;

		return;
	}



	void	HSVtoRGB(double dH,double dS,double dV,RGBTRIPLE& rgb)
	{
		if(dS == 0.0)
		{
			rgb.rgbtRed		= (BYTE)(dV * 255);
			rgb.rgbtGreen	= rgb.rgbtRed;
			rgb.rgbtRed		= rgb.rgbtRed;
			return;
		}

		double	f;
		double	p;
		double	q;
		double	t;
		int		nHi;

		nHi = (int)(dH / 60) % 6;
		if(nHi < 0)
			nHi *= -1;		//dHが負のときの対策

		f = dH / 60 - nHi;
		p = dV * (1 - dS);
		q = dV * (1 - f * dS);
		t = dV * (1 - (1 - f) * dS);

		//計算結果のR,G,Bは0.0~1.0なので255倍
		dV = dV * 255;
		p = p * 255;
		q = q * 255;
		t = t * 255;

		if(dV > 255)
			dV = 255;
		if(t > 255)
			t = 255;
		if(p > 255)
			p = 255;
		if(q > 255)
			q = 255;

		if(nHi == 0)
		{
			rgb.rgbtRed		= (BYTE)dV;
			rgb.rgbtGreen	= (BYTE)t;
			rgb.rgbtBlue	= (BYTE)p;
			return;
		}
		if(nHi == 1)
		{
			rgb.rgbtRed		= (BYTE)q;
			rgb.rgbtGreen	= (BYTE)dV;
			rgb.rgbtBlue	= (BYTE)p;
			return;
		}
		if(nHi == 2)
		{
			rgb.rgbtRed		= (BYTE)p;
			rgb.rgbtGreen	= (BYTE)dV;
			rgb.rgbtBlue	= (BYTE)t;
			return;
		}
		if(nHi == 3)
		{
			rgb.rgbtRed		= (BYTE)p;
			rgb.rgbtGreen	= (BYTE)q;
			rgb.rgbtBlue	= (BYTE)dV;
			return;
		}
		if(nHi == 4)
		{
			rgb.rgbtRed		= (BYTE)t;
			rgb.rgbtGreen	= (BYTE)p;
			rgb.rgbtBlue	= (BYTE)dV;
			return;
		}

		//if(nHi == 5)
		//{
			rgb.rgbtRed		= (BYTE)dV;
			rgb.rgbtGreen	= (BYTE)p;
			rgb.rgbtBlue	= (BYTE)q;
			return;
		//}
	}



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


		////////////////////////////
		//GDI+初期化
		//本当はアプリケーションの初期化時に行うべきだがソースコードの簡易化のためOnPaintに配置
		ULONG_PTR	_nToken;
		Gdiplus::GdiplusStartupInput _sGdiplusStartupInput;
		Gdiplus::GdiplusStartup(&_nToken,&_sGdiplusStartupInput,NULL);


		////////////////////////////
		//画像読み込み
		//ソースコード簡易化のため描画毎に読み出す
		Gdiplus::Bitmap*	pImage;
		Gdiplus::Graphics	cGraphics(dc.m_hDC);

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


		////////////////////////////
		//画像表示(画像処理前)
		cGraphics.DrawImage(pImage,(Gdiplus::REAL)0,(Gdiplus::REAL)0
			,(Gdiplus::REAL)pImage->GetWidth(),(Gdiplus::REAL)pImage->GetHeight());


		////////////////////////////
		//画像処理
		//
		UINT		x;
		UINT		y;
		UINT		nPos;
		RGBTRIPLE*	pTmp;
		Gdiplus::BitmapData		bitmapData;

		pImage->LockBits(&Gdiplus::Rect(0,0,pImage->GetWidth(),pImage->GetHeight())
			,Gdiplus::ImageLockModeRead | Gdiplus::ImageLockModeWrite,PixelFormat24bppRGB,&bitmapData);

		pTmp = (RGBTRIPLE*)bitmapData.Scan0;


		{
			struct	HSV
			{
				double	dH;
				double	dS;
				double	dV;
			};

			HSV*	pHSV;

			pHSV = new HSV[pImage->GetWidth() * pImage->GetHeight()];

			////////////////////////////////////
			//RGB->HSV変換
			//ソースコードが分かりやすいようにforを3箇所に分離している
			//
			nPos = 0;
			for(y = 0; y < pImage->GetHeight(); y++)
			{
				for(x = 0; x < pImage->GetWidth(); x++, nPos++)
				{
					RGBtoHSV(pTmp[nPos],pHSV[nPos].dH,pHSV[nPos].dS,pHSV[nPos].dV);
				}
			}

			////////////////////////////////////
			//輝度(V)アップ
			//
			nPos = 0;
			for(y = 0; y < pImage->GetHeight(); y++)
			{
				for(x = 0; x < pImage->GetWidth(); x++, nPos++)
				{
					pHSV[nPos].dV += 0.2;
					if(pHSV[nPos].dV > 1)
						pHSV[nPos].dV = 1.0;
				}
			}

			////////////////////////////////////
			//HSV->RGB変換
			//
			nPos = 0;
			for(y = 0; y < pImage->GetHeight(); y++)
			{
				for(x = 0; x < pImage->GetWidth(); x++, nPos++)
				{
					HSVtoRGB(pHSV[nPos].dH,pHSV[nPos].dS,pHSV[nPos].dV,pTmp[nPos]);
				}
			}

			delete	pHSV;
		}

		pImage->UnlockBits(&bitmapData);


		////////////////////////////
		//画像表示(画像処理後)
		cGraphics.DrawImage(pImage,(Gdiplus::REAL)pImage->GetWidth() + 2,(Gdiplus::REAL)0
			,(Gdiplus::REAL)pImage->GetWidth(),(Gdiplus::REAL)pImage->GetHeight());

		////////////////////////////
		//画像開放
		delete	pImage;

		////////////////////////////
		//GDI+開放処理
		//本当はアプリケーションの終了時に行うべきだがソースコードの簡易化のため行わない
		//Gdiplus::GdiplusShutdown(_nToken);

		return 0;
	}
};

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