最後にResizerProc.hを見てみましょう。
//////////////////////////////////////////
// ResizerProc.h
// Copyright (c) 03/27/2022 by BCCSkelton
//////////////////////////////////////////
/////////////////////////////////
//主ウィンドウCRESIZERの関数の定義
//ウィンドウメッセージ関数
/////////////////////////////////
bool CRESIZER::OnInit(WPARAM wParam, LPARAM lParam) {
//ワイド文字使用のためロケールの初期化(日本語)
setlocale(LC_CTYPE, "JPN");
//GDI+の開始
GdiplusStartup(&m_gdiToken, &m_gdiSI, NULL);
//(解説:前にも書きましたが、GDI+を利用し始める前の手続きはコンストラクターやWM_CREATE、WM_INITDIALOGで行います。)
//リストビューウィンドウハンドル
m_ListView.m_hWnd = GetDlgItem(m_hWnd, IDC_LISTVIEW);
//リストビュー親ウィンドウハンドル
m_ListView.m_hParent = m_hWnd;
//リストビューインスタンス
m_ListView.m_hInstance = m_hInstance;
//リストビューコントロールID
m_ListView.m_hMenu = (HMENU)IDC_LISTVIEW;
//(解説:ここまではCLISTVIEWクラスのm_ListViewがダイアログコントロールのリストビューを包含できるように必要なパラメーターを取得します。)
//リストビュー列名と幅の設定
m_ListView.InsertColumn(0, 30, "No", LVCFMT_CENTER);
m_ListView.InsertColumn(1, 184, "ファイル名");
m_ListView.InsertColumn(2, 100, "幅");
m_ListView.InsertColumn(3, 100, "高さ");
m_ListView.InsertColumn(4, 100, "修正幅");
m_ListView.InsertColumn(5, 100, "修正高さ");
//リストビュー拡張スタイルの設定
INITCOMMONCONTROLSEX ic;
ic.dwSize = sizeof(ic);
ic.dwICC = ICC_LISTVIEW_CLASSES;
InitCommonControlsEx(&ic);
DWORD lvStyle = SendItemMsg(IDC_LISTVIEW, LVM_GETEXTENDEDLISTVIEWSTYLE, 0, 0);
lvStyle |= LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES;
SendItemMsg(IDC_LISTVIEW, LVM_SETEXTENDEDLISTVIEWSTYLE, LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES, lvStyle);
//(解説:次にリストビュ―コントロールの初期化をm_ListViewを使って行います。ここでは6つの列、そのタイトルを設定し、表示に枠線を入れて、選択をセルではなく、列にしています。)
return TRUE;
}
/////////////////////////////////
//主ウィンドウCRESIZERの関数の定義
//メニュー項目、コントロール関数
/////////////////////////////////
bool CRESIZER::OnSelsrc() {
char* cp = g_Cmndlg.GetPath(m_hWnd, "ソースファイルパスの選択");
if(!cp) {
MessageBox(m_hWnd, "キャンセルされました", "エラー", MB_OK | MB_ICONERROR);
return FALSE;パス
}
//ソースファイルパス(最後の'\'付)
m_SrcPath = cp;
m_SrcPath = m_SrcPath + "\\";
SendItemMsg(IDC_SOURCE, WM_SETTEXT, 0, (LPARAM)m_SrcPath.ToChar());
//(解説:ソースファイルパスをフォールダー選択ダイアログで選択します。)
//リストビューにファイルを列挙
SetFileNames(cp);
//(解説:そのフォールダーにあるGDI+対象ファイルを列挙します。)
return TRUE;
}
bool CRESIZER::OnOverwrite() {
switch(SendItemMsg(IDC_OVERWRITE, BM_GETCHECK, 0, 0)) {
case BST_CHECKED:
//ソースの内容をデスティネーションにコピー
m_DstPath = m_SrcPath;
//ディスティネーションエディットボックスに表示
SendItemMsg(IDC_DIST, WM_SETTEXT, 0, (LPARAM)m_DstPath.ToChar());
//デスティネーション選択ボタンを無効化
EnableWindow(GetDlgItem(m_hWnd, IDC_SELDIST), FALSE);
break;
case BST_UNCHECKED:
//デスティネーションエディットボックスを消去
SendItemMsg(IDC_DIST, WM_SETTEXT, 0, (LPARAM)"");
//ディスティネーションファイルパスクリア
m_DstPath = "";
//デスティネーション選択ボタンを有効化
EnableWindow(GetDlgItem(m_hWnd, IDC_SELDIST), TRUE);
break;
default:
return FALSE;
}
//(解説:「上書き」チェックボックスが選択された場合の処理です。内容はコメント通りです。)
return TRUE;
}
bool CRESIZER::OnSeldist() {
char* cp = g_Cmndlg.GetPath(m_hWnd, "コピー先フォールダーの選択");
if(!cp) {
MessageBox(m_hWnd, "キャンセルされました", "エラー", MB_OK | MB_ICONERROR);
return FALSE;
}
//ディスティネーションファイルパス(最後の'\'付)
m_DstPath = cp;
m_DstPath = m_DstPath + "\\";
SendItemMsg(IDC_DIST, WM_SETTEXT, 0, (LPARAM)m_DstPath.ToChar());
//(解説:コピーの場合のディスティネーションフォールダー選択処理です。)
return TRUE;
}
bool CRESIZER::OnIdgo() {
//(解説:以下は「実行」ボタンが押された場合の処理です。)
//縮小コピー処理数
m_Noc = 0; //(解説:毎回0初期化します。)
//作業用文字列バッファ
char buff[MAX_PATH];
//ソースファイルフォールダーの取得チェック
if(!*m_SrcPath.ToChar()) {
//(解説:これをしないと落ちます。)
MessageBox(m_hWnd, "ソースファイルパスが未入力です", "エラー",
MB_OK | MB_ICONERROR);
return FALSE;
}
//一辺の最大値を取得チェック
SendItemMsg(IDC_MAX, WM_GETTEXT, MAX_PATH, (LPARAM)buff);
if(!*buff || !IsDigitStr(buff)) {
//(解説:IsDigitStrは、CのIsDigit関数の文字列版です。)
MessageBox(m_hWnd, "一辺の最大値が未入力または不正です", "エラー",
MB_OK | MB_ICONERROR);
return FALSE;
}
m_Max = atoi(buff);
//(解説:m_Maxに一辺の最大値が入ります。)
//ディスティネーションファイルフォールダーの取得チェック
if(!*m_DstPath.ToChar()) {
MessageBox(m_hWnd, "ディスティネーションファイルパスが未入力です", "エラー",
MB_OK | MB_ICONERROR);
//(解説:上書きの場合もソースファイルパスがコピーされていますので。)
return FALSE;
}
//上書き(TRUE)か否かの確認
bool overwrite = (SendItemMsg(IDC_OVERWRITE, BM_GETCHECK, 0, 0) == BST_CHECKED);
//リストビューのファイル名でイメージをコピーする
for(int i = 0; i < m_NoOfFiles; i++)
if(CopyImages(i, overwrite)) //(解説:↑で採ったoverwriteフラグを使います。)
m_Noc++; //(解説:処理数です。)
//終了通知
wsprintf(buff, "条件を満たした%d個のファイルを縮小処理しました。", m_Noc);
MessageBox(m_hWnd, buff, "終了", MB_OK | MB_ICONINFORMATION);
return TRUE;
}
bool CRESIZER::OnIdok() {
EndModal(m_Noc); //(解説:処理数を返します。)
return TRUE;
}
bool CRESIZER::OnIdcancel() {
EndModal(FALSE);
return TRUE;
}
bool CRESIZER::OnClose(WPARAM wParan, LPARAM lParam) {
GdiplusShutdown(m_gdiToken);
//(解説:GDI+の開始処理をWN_INITDIALOGで行ったので、終了処理をここで行います。)
return TRUE;
}
//////////////////////////////////
//ユーザー定義関数 - SetFileNames
//・pathで指定するフォールダー内の
// ファイル名をリストビューに登録
//・m_NoOfFilesにファイル数を代入
//・エラーまたはファイルが無い場合
// FALSEを返す
//////////////////////////////////
bool CRESIZER::SetFileNames(char* path) {
//リストビューをクリアーし、ファイル数を0にする
m_ListView.DeleteAllItems();
m_NoOfFiles = 0;
//(解説:毎回初期化されます。)
//ファイル検索
char buff[MAX_PATH]; //作業用文字列バッファ
CSTR FileName(path);
FileName = FileName + "\\*.*"; //ファイル名検索用パス + ワイルドカード
//(解説:以下のFindFirstFile、FindNextFileを使う際の検索ファイル名はワイルドカードでなくてはなりません。)
WIN32_FIND_DATA fd;
HANDLE hFind = FindFirstFile(FileName.ToChar(), &fd);
if(hFind == INVALID_HANDLE_VALUE) {
MessageBox(m_hWnd, "ファイル情報取得失敗", "エラー", MB_OK | MB_ICONERROR);
return FALSE;
}
else {
do {
//ディレクトリーの場合何もしない
if(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
//ファイルの場合
else {
//GDI+の対象となる画像ファイルの場合
if(g_ExtChk.CheckExt(fd.cFileName, IMGFILTER)) {
//(解説:ここでCEXTCHKクラスのCheckExt関数を使うことでGDI+対象ファイルか否かをチェックします。)
//リストボックスに表示(番号)
wsprintf(buff, "%d", m_NoOfFiles);
m_ListView.InsertItem(m_NoOfFiles, buff, -1, m_NoOfFiles);
//ファイル名
m_ListView.SetItem(m_NoOfFiles, 1, fd.cFileName);
m_NoOfFiles++;
//(解説:ファイル番号とファイル名をリストボックスに入れます。)
}
}
} while(FindNextFile(hFind, &fd));
}
FindClose(hFind);
return TRUE;
}
//////////////////////////////////
//ユーザー定義関数 - IsDigitStr
//・strで指定する文字列が'0' -'9'
// 迄の数字文字か否かチェックする
//・そうであればTRUE、数字以外があ
// ればFALSEを返す
//////////////////////////////////
bool CRESIZER::IsDigitStr(char* str) {
for(char* cp = str; *cp; cp++) {
if(*cp > '9' || *cp < '0')
return FALSE;
}
//(解説:IsDigit関数と同じことを文字列の各文字に対して行います。)
return TRUE;
}
/////////////////////////////////////////////////
//ユーザー定義関数 - CopyImages
//・m_ListViewの引数n行、第2列のファイル名を取得
//・m_SrcPath + ファイル名の画像インスタンスを生
// 成して、幅、高さを取得
//・m_Max と比較して、一辺がそれより大きければ長
// い辺をm_Max とし、短い辺を画像の縦横比に応じ
// て修正
//・修正幅、高さのビットマップフレームを生成し、
// そこに縮小画像を書き込む
//・書き込まれたビットマップを、オリジナル画像の
// エンコーダーを用いてm_DstPath + ファイル名で
// 書き込む
//・「上書き」モードの場合は、ファイル名を"*.bak"
// とし、オリジナルを削除、新ファイルの名前をオ
// リジナルのものに変える
/////////////////////////////////////////////////
bool CRESIZER::CopyImages(int n, bool overwrite) {
//(解説:以下はGDI+のイメージコピー処理です。既にAlbumで説明していますね。)
//変数宣言
int Width; //イメージの幅
int Height; //イメージの高さ
int AdjW; //修正後幅
int AdjH; //修正後高さ
CSTR SrcFile, DstFile; //ソースファイル名、書き込みファイル名
WCHAR WFName[MAX_PATH]; //フルパスファイル名用ワイド文字列(WCS)
char name[MAX_PATH]; //リストボックスのファイル名
char buff[MAX_PATH]; //作業用文字列バッファ
//リストビューから読み込みファイル名を取得
m_ListView.GetItemText(n, 1, name, MAX_PATH);
//読み込みファイル名の作成
SrcFile = m_SrcPath;
SrcFile = SrcFile + name; //m_SrcPathは既に'\'付
//(解説:ここまででソースファイル名を準備します。)
//書き込みファイル名の作成
if(overwrite) //nameの拡張子を"bak"に変える
lstrcpy(strstr(name, "."), ".bak"); //g_ChkExtを経由しており'.'がある
DstFile = m_DstPath;
DstFile = DstFile + name; //m_DstPathは既に'\'付
//(解説:ここまででディスティネーションファイル名を準備します。上書きの場合の拡張子はbakです。)
//Imageインスタンスの生成(ファイル名はワイド文字化)
mbstowcs(WFName, SrcFile.ToChar(), MAX_PATH);
m_pImage = new (Image)(WFName);
//(解説:GDI+では必ずワイド文字を使います。)
//イメージの幅、高さを取得
Width = m_pImage->GetWidth();
Height = m_pImage->GetHeight();
//(解説:やっと幅と高さが求められます。)
//リストボックスの表示(1)
wsprintf(buff, "%d", Width); //イメージ幅
m_ListView.SetItem(n, 2, buff);
wsprintf(buff, "%d", Height); //イメージ高さ
m_ListView.SetItem(n, 3, buff);
//(解説:リストボックスに書き込みます。)
//一片の最大値から原寸縦横比のまま修正幅、高さを算出
if(Width > m_Max || Height > m_Max) {
if(Width > Height) {
AdjH = Height * ((double)m_Max / (double)Width);
AdjW = m_Max;
}
else {
AdjW = Width * ((double)m_Max / (double)Height);
AdjH = m_Max;
}
}
//縦横共にm_Maxを超えない場合はFALSEで戻る
else {
if(!overwrite) //上書きでなければ
//条件に適合しないファイルはそのままコピーする
CopyFile(SrcFile.ToChar(), DstFile.ToChar(), FALSE);
delete m_pImage;
return FALSE;
}
//(解説:原寸縦横比の処理もAlbumと同じですが、上書きでない場合、一辺の最大値以下の画像ファイルもコピーします。)
//リストボックスの表示(2)
wsprintf(buff, "%d", AdjW); //修正幅
m_ListView.SetItem(n, 4, buff);
wsprintf(buff, "%d", AdjH); //修正高さ
m_ListView.SetItem(n, 5, buff);
//(解説:リストビューに修正した幅、高さを書き込みます。従って書き込まれたファイルが処理されたことになります。)
//縮小画像用のBitmapオブジェクトをオリジナルの縦横比で生成
Bitmap* pBitmap = new Bitmap(AdjW, AdjH);
//Bitmapオブジェクトから出力用のGraphicsフレームオブジェクトを作成
Graphics* pFrame = Graphics::FromImage(pBitmap);
//Rectオブジェクトのフレームの矩形を生成
Gdiplus::Rect rtFrame(0, 0, AdjW, AdjH);
//画像の全体をフレームのrtFrameで表した矩形領域へ描画
pFrame->DrawImage(m_pImage, rtFrame, 0, 0, Width, Height, UnitPixel); //1ポイント == 1/72インチ
//(解説:縮小イメージの書き込み処理もAlbumと同じです。ビットマップに書き込むだけです。)
//書き込み用エンコーダのCLSIDを取得
int i = g_ExtChk.CheckExt(SrcFile.ToChar(), IMGFILTER); //画像形式をチェック
//(解説:ここでCEXTCHKクラスの活躍です。エンコーダー型をソースファイル名からチェックします。)
if(!i) { //(解説:該当がなかった場合です。)
MessageBox(m_hWnd, "対象ファイルではありません", "エラー", MB_OK | MB_ICONERROR);
//ビットマップのメモリーを解放
delete pBitmap;
//m_Imageインスタンスを解放
delete m_pImage;
return FALSE;
}
else if(i > 2) //(解説:jpegファイルが、jpegとjpgの二種類の表記があるからです。)
i--;
//(解説:。)
CLSID encoderClsid;
//g_EncoderList配列のエンコーダー型を使ってCLSIDを取り出す
if(GetEncoderClsid(g_EncoderList[i - 1], &encoderClsid) == -1) {
//(解説:このCEXTCHKクラスのCheckExt関数で採った値で、外部エンコーダー型配列に適用します。)
MessageBox(m_hWnd, DstFile.ToChar(), "エンコーダーCLSIDの取得失敗", MB_OK | MB_ICONERROR);
//ビットマップのメモリーを解放
delete pBitmap;
//m_Imageインスタンスを解放
delete m_pImage;
return FALSE;
}
//EncoderParameters配列の第1オブジェクトを使って品質を指定
LONG lQuality = 100; //品質レベルは100の最高品質
//EncoderParameters配列オブジェクト
EncoderParameters EncoderParams;
//先頭のEncoderParameterオブジェクト
EncoderParams.Parameter[0].Guid = EncoderQuality; //品質であることを指定
EncoderParams.Parameter[0].NumberOfValues = 1; //Valueの変数数は1つ
EncoderParams.Parameter[0].Type = EncoderParameterValueTypeLong; //Valueの変数の長さ
EncoderParams.Parameter[0].Value = (VOID*) &lQuality; //品質指定
EncoderParams.Count = 1; //EncoderParameters配列は一つだけ
//書き込みファイル名のワイド文字版を作る
mbstowcs(WFName, DstFile.ToChar(), MAX_PATH);
//フレームとして使ったBitmapを指定エンコーダー、品質でファイルに保存
pBitmap->Save(WFName, &encoderClsid, &EncoderParams);
//(解説:最後に品質レベルを指定して、ディスティネーションファイル名をワイド文字にしてファイルを書き込みます。)
//ビットマップのメモリーを解放
delete pBitmap;
//m_Imageインスタンスを解放
delete m_pImage;
//(解説:newでつくったインスタンスは必ず消しましょう。)
//上書き処理
if(overwrite) {
DeleteFile(SrcFile.ToChar()); //オリジナルファイルを削除
MoveFile(DstFile.ToChar(), SrcFile.ToChar()); //縮小ファイルをオリジナル名に変更
//(解説:ImageクラスのSave関数は上書きができないので、bakで書き込んで、オリジナルを削除して、bakを変更することで上書きしています。)
}
return TRUE;
}
////////////////////////////////////////////////////
//ユーザー定義関数
//Image CODEC CLSIDの取得(出展:Microsoft Doc)
//formatに合致するCODECのCLSIDアドレスをpClsidに渡す
//戻り値:成功-Image CODEC配列(ImageCodecInfo)引数
// 失敗- -1
////////////////////////////////////////////////////
int CRESIZER::GetEncoderClsid(const WCHAR* format, CLSID* pClsid) {
//イメージCODECの数、サイズ取得
UINT num = 0; //image encoderの数
UINT size = 0; //image encoder配列のサイズ(バイト数)
GetImageEncodersSize(&num, &size);
if(size == 0) {
return -1; //イメージCODECサイズ取得失敗
}
//イメージCODEC情報用クラス(ImageCodecInfo)
ImageCodecInfo* pImageCodecInfo = NULL;
pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
if(pImageCodecInfo == NULL) {
return -1; //メモリ確保失敗
}
//イメージCODEC情報クラス取得
GetImageEncoders(num, size, pImageCodecInfo);
for(UINT j = 0; j < num; ++j) {
//formatがCODECのMIME(Multipurpose Internet Mail Extensions)型なら
if(wcscmp(pImageCodecInfo[j].MimeType, format) == 0) {
//J番目のCODECのグローバル一意識別子(CLSID)を渡す
*pClsid = pImageCodecInfo[j].Clsid;
free(pImageCodecInfo);
return j; //成功-
}
}
free(pImageCodecInfo);
return -1; // 失敗 - 要求した種類のエンコーダが存在しなかった
}
//(解説:これは前にも書きましたが、Microsoft Docのコードをベースにちょっと手を入れたエンコーダーCLSID取得関数です。)
これでResizerの解説は終了です。スタンドアローンのプログラム(exeファイル)、DLL(dllファイル)いずれもBatchGoodでコンパイルできます。試してみてください。






