第四話 先に作ろう保存処理

困った、ネタがないぞ

ファイル読み込みはBitmap::FromFile()であっけなく片づいたけど、ファイル書き込みは面倒なんじゃないの?と、普通の人は思うでしょう。(書く側にとって)困った事に簡単なんですよ。Bitmapクラスに対してファイルネームとエンコードIDを引数にしてSave()しておしまい。困った、何を書けばいいのだ…

エンコーダを取得しよう

前述のようにファイル保存にはエンコーダが必要なのでエンコーダ取得処理をまず実装します。といっても、MSDNにあるのでそれをかっぱらってきましょう。JpgPngTifなんかも書き込めちゃう優れモンです。
https://msdn.microsoft.com/en-us/library/ms533843.aspx
では、それをCGDIStaticクラスに追加しましょう。登録方法は前回までの通り。

// 画像保存エンコーダ検索
// http://msdn.microsoft.com/en-us/library/ms533843.aspx
// image/bmp 
// image/jpeg 
// image/gif 
// image/tiff 
// image/png 
int CGDIStatic::GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
	UINT num = 0; // number of image encoders
	UINT size = 0; // size of the image encoder array in bytes

	ImageCodecInfo* pImageCodecInfo = NULL;

	GetImageEncodersSize(&num, &size);
	if(size == 0)
		return -1; // Failure

	pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
	if(pImageCodecInfo == NULL)
		return -1; // Failure

	GetImageEncoders(num, size, pImageCodecInfo);

	for(UINT j = 0; j < num; ++j)
{
		if( wcscmp(pImageCodecInfo[j].MimeType, format) == 0 )
		{
			*pClsid = pImageCodecInfo[j].Clsid;
			free(pImageCodecInfo);
			return j; // Success
		} 
	}

	free(pImageCodecInfo);
	return -1; // Failure
}

ほら、書く事がない。"image/bmp"formatに投げてやるとCLSIDを返してくれます。
続いて保存処理を実装。

// 保存処理
//
// 引数
// CStringW	strFName	ファイルネーム
// CString	strExt	拡張子
//
BOOL CGDIStatic::savePicture(CStringW strFName, CString strExt)
{

	CStringW strEnc;

	// 大文字に変換
	CString strExtU = strExt = strExt.MakeUpper();

	// エンコーダ文字列
	if(strExtU == "PNG"){
		strEnc = L"image/png";

	} else if(strExtU == "JPG" || strExtU == "JPEG"){
		strEnc = L"image/jpeg";

	} else if(strExtU == "BMP"){
		strEnc = L"image/bmp";

	} else {
		return FALSE;

	}

	// エンコーダ検索
	CLSID encoderClsid;
	INT result = GetEncoderClsid(strEnc, &encoderClsid);

	if(result < 0){
		return FALSE;

	} else {
		// 保存
		if(m_image){
			m_image->Save(strFName, &encoderClsid);
		}
	}

	return TRUE;

}

やってる事と言えば拡張子を判定してエンコーダ名を選択して先ほどのメソッドでエンコーダCLSIDを取得して、指定ファイルネームとエンコーダCLSIDで保存しているだけ。説明するほどの事はやっていない。ちなみにstrExt.MakeUpper()ってなんぞやでしょうけど、CStringで確保されている文字列の小文字を大文字に変換するメソッドです。

続いてダイアログ側に実装しましょう、ダイアログの右のStaticProtectedm_GDIStatic2という名前で変数追加をして、ヘッダファイル上で型を変換して下さい。

// ChromakeyDlg.h : ヘッダー ファイル
//

#pragma once
#include "afxwin.h"
#include "GDIStatic.h"

// CChromakeyDlg ダイアログ
class CChromakeyDlg : public CDialog
{
// コンストラクション
public:
CChromakeyDlg(CWnd* pParent = NULL);	// 標準コンストラクタ

// ダイアログ データ
enum { IDD = IDD_CHROMAKEY_DIALOG };

protected:
virtual void DoDataExchange(CDataExchange* pDX);	// DDX/DDV サポート


// 実装
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();
DECLARE_MESSAGE_MAP()

CGDIStatic m_GDIStatic1;
CGDIStatic m_GDIStatic2;

};
次にダイアログリソースボタン(赤枠)を追加し、IDをIDC_BUTTONSAVEに設定してクリックイベントを実装して下さい。
ソースコードの実装内容はこんな感じ。
001
// 保存ボタン
//
void CChromakeyDlg::OnBnClickedButtonsave()
{
	// TODO: ここにコントロール通知ハンドラ コードを追加します。

	CFileDialog dlg( FALSE,
				"画像保存",
				NULL,OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
				"bmp File|*.bmp|Jpg File|*.jpg;*.jpeg|Png File|*.png|");

	if(IDOK == dlg.DoModal()){

		// ファイルパス取得
		CString strFile(dlg.GetPathName());
		CStringW strWFile(strFile);

		// char型に置換(_splitpath()はCStringに未対応)
		char cFile[MAX_PATH];
		strcpy(cFile, strFile);

		// 拡張子取得
		char cExt[_MAX_EXT];
		_splitpath(cFile, NULL,NULL,NULL, cExt);

		// 拡張子の'.'を削除
		CString strExt(cExt);
		strExt.Replace(".", "");

		// 拡張子があるなら保存処理
		if(strExt.GetLength() > 0){
			m_GDIStatic2.savePicture(strWFile, strExt);

		}
	}
}

ファイル選択ダイアログ(書き込み)でファイルパスを取得して、拡張子を切り出し後、保存処理を行う。シンプルですねぇ。

次回への仕込みもかねて

保存処理は出来ました。でも間抜けな問題があります、保存対象がないのです。想定ではStatic1が元画像Static2が加工画像なのですがStatic2で画像を取得する処理がありません。流れ的にStatic1の保持するBitmapをStatic2にコピーしてやる処理が必要になります。
保存の確認もしたいので、この際だから画像コピーを仕込んでしまいましょう。(講座の切り分けに失敗したとも言います)
追加するメソッドは二つ。Bitmap取得Bitmap設定

// 保持しているBitmapのコピーを取得
//
Bitmap* CGDIStatic::getBitmap(void)
{

	if(m_image){
		// クローン
		return m_image->Clone(Rect(0,0,m_image->GetWidth(), m_image->GetHeight()), PixelFormat24bppRGB); 

	} else {
		return NULL;
	}
}



// 外部作成のBitmapを描画用Bitmapにコピーする
//
// 引数
// Bitmap* bmp	コピー元Bitmap
//
void CGDIStatic::setBitmap(Bitmap* bmp)
{

	CSize szImage(bmp->GetWidth(), bmp->GetHeight());

	// クローン
	delete m_image;
	m_image = bmp->Clone(Rect(0,0, szImage.cx, szImage.cy), PixelFormat24bppRGB);

	// 自身のピクセルサイズ取得
	CRect rt;
	GetClientRect(rt);

	// 比率計算
	double dWid1 = (double)m_image->GetWidth();
	double dWid2 = (double)rt.Width();
	double dMagniW = dWid1 / dWid2;

	double dHgt1 = (double)m_image->GetHeight();
	double dHgt2 = (double)rt.Height();
	double dMagniH = dHgt1 / dHgt2;

	// 大きいほうを使用
	m_dMagni = max(dMagniW, dMagniH);

}

処理内容は

  1. CGDIStaticの保持するm_image(画像保持領域)をコピーする
  2. CGDIStaticの保持するm_image(画像保持領域)にコピーする
という処理になります。一応get&set形式です。
C的に考えたら呼び出し元でm_imageコピーすればいいじゃんと思うでしょうがC++では原則的にクラスメンバの変数はProtectedにして直接アクセスはしない方が無難で、get&setが推奨されています。(メソッド内にブレークポイント仕掛けられたりしてデバッグ時に便利だし)
Clone()メソッドはオリジナルの指定領域をコピーするメソッドです。ファイル読み込み処理のようにDrawImage()でコピーしても良いんですが格好良い方法をとりました。ちなみにBitmap::FromFile()で使用したBitmapをCloneするとファイルロックまでコピーするのでしないように。
悩む前に書いておきますがdeletefreeと同じでNULLチェックは不要なので初回時はメモリ破棄されずにスルーされます。

ではダイアログに実装です。次回でクロマキーボタンを実装する予定ですが、取りあえずファイル読み込みボタンにテスト実装しましょう。ハイライト部が追加部分です。

// ファイル読み込みボタン
//
void CChromakeyDlg::OnBnClickedButtonload()
{
	// TODO: ここにコントロール通知ハンドラ コードを追加します。

	// カレントフォルダ退避
	char cPath[MAX_PATH];
	GetCurrentDirectory(MAX_PATH, cPath);

	CFileDialog dlg( TRUE,
			"画像取り込み",
			NULL,OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
			"bmp File|*.bmp|Jpg File|*.jpg;*.jpeg|Png File|*.png|");

	if(IDOK == dlg.DoModal()){
		CStringW strFile(dlg.GetPathName());

		// 画像読み込み
		m_GDIStatic1.loadPicture(strFile);

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

		// 開放
		delete bmp;
		bmp= NULL;

	}

	// カレントフォルダに戻す
	SetCurrentDirectory(cPath);

}

getBitmap()で取得したBitmapは最後に解放しましょう。

実行してファイル読み込みを行うとこうなるはずです。この状態でファイル保存で適当に拡張子を選択して保存してみましょう。選択形式で画像ファイルが作成されるはずです。(VISTA以降だとUACの影響で直下で作るのをOSが嫌がるとかあります)
002

次回はBitmapの直接アクセスによるクロマキー作成をしてみましょう。