第五話 よく考えたら白抜きと白ベタが要るんだな。透明色?そこは自分で考えよう(ヒント、αブレンド)

大ヒント:PixelFormat24bppRGBPixelFormat32bppARGBに差し替えると透過色対応出来る

この機会にちょっとやっておこう

今まで作ってきた実行ファイルでEscキーEnterキーを押してみて下さい。終了しちゃいましたね?ESCキーやEnterキーはデフォルトでは決定/キャンセルとして認識されるので終了してしまう訳ですね。不具合ではありませんが格好悪い、格好悪いのはイケてないので止めましょう。

キーイベントから内部的にOKイベントCANCELイベントが発行されているのですが、OK、CANCELをつぶすとダイアログの終了も出来なくなるのでキーイベントのみをつぶしましょう。クラス右クリックのプロパティからオーバーライドを出しPreTranslateMessage()を選択して以下を追加してくださいな。
001
BOOL CChromakeyDlg::PreTranslateMessage(MSG* pMsg)
{
// TODO: ここに特定なコードを追加するか、もしくは基本クラスを呼び出してください。

	if(pMsg->message == WM_KEYDOWN){
	switch(pMsg->wParam){
		case VK_ESCAPE: // Esc
			return TRUE;

		case VK_RETURN:
			return TRUE;
		}
	}
	return CDialog::PreTranslateMessage(pMsg);
}

PreTranslateMessage()メッセージを先取りするメソッドでここでメッセージを止めたり加工したり出来ます。この場合、戻り値TRUEキーダウンイベントを止めキー入力を殺しています。

まずクロマキー選択を実装しよう

まずはダイアログリソースをこんな風に加工して下さい。

オブジェクト ID キャプション メンバ変数
RadioButton IDC_RADIO1 白抜き(NAND)
RadioButton IDC_RADIO2 白抜き(AND)
RadioButton IDC_RADIO3 透明色→黒
EditBox IDC_EDIT_R m_EditR
EditBox IDC_EDIT_G m_EditG
EditBox IDC_EDIT_B m_EditB
CButton IDC_BUTTONEXEC 実行
OKボタン、キャンセルボタンを削除。

002

ラジオボタンをメンバ変数にする訳ですが、困った事にラジオボタンは表向き、メンバ変数に出来ないんですな。(何故なんだろ?)ではどうするのかというと、CWnd::Attach()で無理矢理?メンバ変数を作って対処するのです。まず、ヘッダにメンバ変数を宣言しましょう。

// 実装
protected:
	HICON m_hIcon;

	// 生成された、メッセージ割り当て関数
	virtual BOOL OnInitDialog();
	afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
	afx_msg void OnPaint();
	afx_msg HCURSOR OnQueryDragIcon();
	afx_msg void OnBnClickedButtonload();
	afx_msg void OnBnClickedButtonsave();
	DECLARE_MESSAGE_MAP()

	CGDIStatic m_GDIStatic1;
	CGDIStatic m_GDIStatic2;
	CButton m_radio1;
	CButton m_radio2;
	CButton m_radio3;

次にOnInitDialog()Attach()を追加しましょう。

BOOL CChromakeyApp::InitInstance()
{
BOOL CChromakeyDlg::OnInitDialog()
{
	CDialog::OnInitDialog();

	// "バージョン情報..." メニューをシステム メニューに追加します。

	// IDM_ABOUTBOX は、システム コマンドの範囲内になければなりません。
	ASSERT((IDM_ABOUTBOX &0xFFF0) == IDM_ABOUTBOX);
	ASSERT(IDM_ABOUTBOX < 0xF000);

	CMenu* pSysMenu = GetSystemMenu(FALSE);
	if (pSysMenu != NULL)
	{
		BOOL bNameValid;
		CString strAboutMenu;
		bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
		ASSERT(bNameValid);
		if (!strAboutMenu.IsEmpty())
		{
			pSysMenu->AppendMenu(MF_SEPARATOR);
			pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
		}
	}

	// このダイアログのアイコンを設定します。アプリケーションのメイン ウィンドウがダイアログでない場合、
	// Framework は、この設定を自動的に行います。
	SetIcon(m_hIcon, TRUE); // 大きいアイコンの設定
	SetIcon(m_hIcon, FALSE); // 小さいアイコンの設定

	// TODO: 初期化をここに追加します。
	m_radio1.Attach(::GetDlgItem(this->m_hWnd,IDC_RADIO1));
	m_radio2.Attach(::GetDlgItem(this->m_hWnd,IDC_RADIO2));
	m_radio3.Attach(::GetDlgItem(this->m_hWnd,IDC_RADIO3));

	// 第一ラジオをチェック状態
	m_radio1.SetCheck(1);

	return TRUE; // フォーカスをコントロールに設定した場合を除き、TRUE を返します。
}

これで他のモジュール変数と同じように使えるようになりましたが、アタッチしたらデタッチもしなければなりません。WM_CLOSEイベントを選択してOnClose()を実装しましょう。

void CChromakeyDlg::OnClose()
{
// TODO: ここにメッセージ ハンドラ コードを追加するか、既定の処理を呼び出します。

	// 開放
	m_radio1.Detach();
	m_radio2.Detach();
	m_radio3.Detach();

	CDialog::OnClose();
}

ここからは駆け足で解説

ではクロマキー処理の実装。単に全ピクセルチェックして指定色だったら色を変えるというだけです。GetPixelで指定座標の色情報を拾えます。ColorクラスはCOLORREF構造体の賢い版で今回はやっていませんがGetAlpha()で透過率を拾えます。
(2013/6/1修正しました)

// 保持したBitmapをクロマキー画像に変換する
//
// 引数
// int mode モード
// unsigned char R R
// unsigned char G G
// unsigned char B B
//
void CGDIStatic::CreateChroma(
	int mode,
	unsigned char R,
	unsigned char G,
	unsigned char B)
{
	for(DWORD y = 0; y < m_image->GetHeight(); y++){
		for(DWORD x= 0; x < m_image->GetWidth(); x++){
			Color COL;

			// 指定座標のRGB値を取得
			m_image->GetPixel(x, y, &COL);

			BYTE cr = COL.GetR();
			BYTE cg = COL.GetG();
			BYTE cb = COL.GetB();

			switch(mode){
			case 0:
				if(cr == R && cg == G && cb == B){
					m_image->SetPixel(x, y, Color(255,255,255));
				} else {
					m_image->SetPixel(x, y, Color(0,0,0));
				}
				break;
			case 1:
				if(!(cr == R && cg == G && cb == B)){
					m_image->SetPixel(x, y, Color(255,255,255));
				} else {
					m_image->SetPixel(x, y, Color(0,0,0));
				}
				break;
			}
		}
	}
}

続いて変換対象色の取得処理、ダブルクリックで拾うのが素直でしょう。RGBを拾って「拾った」と親であるCChromakeyDlgに送信します。

GdiStatic.hヘッダに追加
// RGB値
BYTE m_r;
BYTE m_g;
BYTE m_b;

コンストラクタに追加
m_r = 0;
m_g = 0;
m_b = 0;


// ダブルクリックイベント(NOTIFYプロパティをTRUEにすること)
void CGDIStatic::OnLButtonDblClk(UINT nFlags, CPoint point)
{
	// TODO: ここにメッセージ ハンドラ コードを追加するか、既定の処理を呼び出します。

	if(m_image){
		Color COL;
		// 指定座標のRGB値を取得
		m_image->GetPixel(point.x * m_dMagni, point.y * m_dMagni, &COL);

		m_r = COL.GetR();
		m_g = COL.GetG();
		m_b = COL.GetB();

		GetParent()->SendMessage(WM_USER + 1);
	}

	CWnd::OnLButtonDblClk(nFlags, point);
}

では本体側、受信イベント作ることも出来ますがWinProcでやる方が面倒がないのでそれで。オーバーライドからDefWindowProcで作成して下さい。やってる事はCGIDStaticからRGB値を取得してCEditに出力するだけです。

LRESULT CChromakeyDlg::DefWindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
	// TODO: ここに特定なコードを追加するか、もしくは基本クラスを呼び出してください。

	if(message == WM_USER + 1){
		// RGB値の表示
		CString strOut;
		strOut.Format("%d", m_GDIStatic1.m_r);
		m_EditR.SetWindowTextA(strOut);
		strOut.Format("%d", m_GDIStatic1.m_g);
		m_EditG.SetWindowTextA(strOut);
		strOut.Format("%d", m_GDIStatic1.m_b);
		m_EditB.SetWindowTextA(strOut);
	}

	return CDialog::DefWindowProc(message, wParam, lParam);
}

そして最後の実装、実行処理です。CEditに書かれた数値を拾ってラジオボタンの選択状態を元にクロマキー処理を実行します。

void CChromakeyDlg::OnBnClickedButtonexec()
{
	// TODO: ここにコントロール通知ハンドラ コードを追加します。

	// RGB取得
	CString strOut;
	BYTE R,G,B;
	m_EditR.GetWindowTextA(strOut);
	R = (BYTE)atoi(strOut);

	m_EditG.GetWindowTextA(strOut);
	G = (BYTE)atoi(strOut);

	m_EditB.GetWindowTextA(strOut);
	B = (BYTE)atoi(strOut);

	// Static2に転送
	Bitmap* bmp = m_GDIStatic1.getBitmap();
	m_GDIStatic2.setBitmap(bmp);
	m_GDIStatic2.RedrawWindow();

	// クロマキー作成
	int mode = -1;
	if(m_radio1.GetCheck()){
		mode = 0;
	} else if(m_radio2.GetCheck()){
		mode = 1;
	} else if(m_radio3.GetCheck()){
	mode = 2;
	}
	m_GDIStatic2.CreateChroma(mode, R, G, B);
	m_GDIStatic2.RedrawWindow();
}
以上を仕込んでビルドをして実行してみましょう、こんな事が出来てれば完成です。
003

如何でしたでしょうか、MFCってこの程度のことなのです。