アクティブウィンドウのスクリーンショットを一定間隔で保存する

icon 項目のみ表示/展開表示の切り替え

概要

 本プログラムの開始ボタンをクリックして5回音が鳴った後、アクティブになっているウィンドウのスクリーンショットを一定間隔で保存します。
 中止は、タスクトレイで本プログラムを左クリックすると中止します。重複削除にチェックをいれて削除をクリックするとイメージの内容が同一である重複ファイルは削除されます。
 キャプチャするには本プログラムを起動して開始をクリックした後、5回音が鳴るまでにキャプチャするウィンドウをアクティブにしておく必要があります。
 本プログラムはVistaからサポートしている高DPIに対応するため複雑ことを行っています。
 マニフェストでの高DPIの宣言は不要です。

テスト環境

コンパイラ

Visual C++ 2008/2013 Express 32/64bit UNICODE

動作確認

Windows 7
Windows 8.1
Windows 10 Version 1803

動作例

本プログラムを起動した状態

プログラムソースの概要

intwshotn.cpp

 以下に主要な関数の処理を記述します。

_tWinMain

 高DPIに対応するために以下の処理をしています。
SetThreadDpiAwarenessContext
 Windows 10 Version 1703より古い場合、プログラムの初期時にSetThreadDpiAwarenessContext APIが存在するかチェックし存在する場合、Windows10のバージョンをチェックします。
 バージョンが1607の場合は、非クライアント領域の大きさを変更させるためWM_NCCREATEメッセージ発生時にEnableNonClientDpiScalingを呼び出します。
 バージョンが1703以降の場合は、SetThreadDpiAwarenessContext APIでDPI_AWARENESS_PER_MONITOR_AWARE_V2を指定することにより Windowsに非クライアント領域の大きさの変更を自動にさせます。
SetProcessDPIAware
 SetThreadDpiAwarenessContext APIが使えない場合、SetProcessDPIAware API(Windows Vista以降)が使えるか確認します。
 事前にSetProcessDPIAware API(Windows Vista以降)を実行するとプログラム自身がスケーリング処理をすることになり正常なDPI値が返されます。実行しない場合、常に96DPIが返されます。Windows XPではこのAPIは存在しないので96DPIと仮定します。なおAPIが存在するかどうかは動的にライブラリをロードし関数のエントリポイントが存在するかどうかで判断しています。
 DPIはGetDpiForMonitor API(Windows 8.1以降)が使える場合はこの関数でウィンドウが属するモニターのDPIを、使えない場合GetDeviceCaps(hdc,LOGPIXELSX)によりディスクトップのDPIを取得します。
その他の処理
 GdiplusStartupによりGDI+の初期化をします。
 GetEncoderClsidによりjpegエンコーダを取得します。
 DialogBox APIによりダイアログボックスを表示します
 GdiplusShutdownにより終了処理を実行します。

DlgProc

WM_INITDIALOG
 ダイアログボックスの初期化時に呼び出されます。
 以下の処理はWindows 10 Version 1703以降では実行しません。
  1. ダイアログボックスの座標単位とスクリーン座標の変換に必要な定数をGetActualDialogBaseUnitsで取得
  2. scaleChange関数でダイアログボックス内の各コントロールの大きさ等を変更
WM_DPICHANGED
 引数から現DPIを取得しscaleChange関数でダイアログボックス内の各コントロールの大きさ等を変更します。Windows 10 Version 1703以降では何もせずにWindowsに制御を渡します。
WM_NCCREATE
 Wndows10(Version1607)の時だけ非クライアント領域の処理するためEnableNonClientDpiScaling関数を呼び出します
WM_TIMER
 インターバルタイマーから呼び出されます。
 キャプチャー開始までの時報のような音をBeep APIで鳴らします。
 countが4回までが1kHzで、5回目が2kHz、6回目以降がキャプチャー動作となります。
 キャプチャーはscreenShot関数で行っています。
_WM_TASKTRAL_
 タスクトレーで左クリック時に発生するWM_LBUTTONDOWNメッセージを処理しています。
 インターバルタイマーをKillTimer APIで中止させ、キャプチャーを終了します。
 ShowWindow APIでダイアログボックスをアクティブにします。
WM_COMMAND
 ボタンがクリックされると、WM_COMMANDメッセージが発生します。
IDC_REF(参照(B))
 参照ボタンをクリックすると呼び出されます。
 GetDir関数によりスクリーンショットを保存するフォルダーを取得します。
IDC_STOP(削除(S))
 削除ボタンをクリックすると呼び出されます。
 インターバルタイマーをKillTimer APIで中止させ、キャプチャーを終了します。
 重複削除ラジオボタンがチェックされているときはjpgDupDel関数により重複ファイルを削除します。
IDCANCEL(終了(Q))
 インターバルタイマーをKillTimer APIで中止させ、キャプチャーを終了します。
 EndDialog APIによりダイアログボックスを終了します。
IDOK(開始(R))
 開始ボタンをクリックすると呼び出されます。
 Shell_NotifyIcon APIによりタスクバーに本プログラムのアイコンを追加します。何も指定していないのでアイコンが使用されます。
 ShowWindow APIによりダイアログボックスがタスクバーに表示されないようにします。
 SetTimer APIによりインターバルタイマーを起動します。

screenShot

 アクティブウィンドウのスクリーンショットを保存します。
 GetDCによりアクティブウィンドウのデバイスコンテキストを取得します。
 GetWindowRectによりウィンドウサイズを取得します。
 CreateCompatibleDCによりウィンドウと互換性のあるデバイスコンテキストを作成します。
 CreateCompatibleBitmapによりメモリ上に互換性のあるビットマップを作成します。
 PrintWindowによりアクティブウィンドウをメモリ上にコピーします。
 SaveBitmap関数によりメモリ上のイメージをファイルに保存します。

SaveBitmap

 メモリ上のビットマップを使用してGdiplus::Bitmapオブジェクトbを作成します。
 Gdiplus::Bitmapオブジェクトをスクリーンサイズでbmpを作成します。
 Gdiplus::Graphicsオブジェクトをbmpを元にMyGraphics作成します。
 DrawImageでbをMygraphicsに描画します。
 Saveを使用してbmpをファイルに保存します。

jpgDupDel

 スクリーンショットですので、前後のjpgファイルが重複すると仮定しています。
 load_img関数によりメモリーにロードして、まず画像サイズを比較します。サイズが異なれば重複していないと判断します。
 次にメモリー上でmemcmp関数により比較し一致不一致を判断しています。
 重複していた場合はDeleteFIle APIにより削除します。

load_img

 jpgファイルよりGdiplus::Imageオブジェクトを作成します。
 Imageオブジェクトより画像ファイルの縦横サイズを取得し、ビットマップを作成します。(CreateDIBSection)
 ウィンドウのデバイスコンテキストより互換性のあるメモリデバイスコンテキストを作成し(CreateCompatibleDC)、ビットマップを関連付けます。(SelectObject)
 メモリデバイスコンテキストよりGdiplus::Graphicsオブジェクトを作成し、Gdiplus::ImageをGdiplus::Graphicsに描画(DrawImage)します。
 Gdiplus::ImageとGdiplus::Graphics(デストラクタにより自動解放される)オブジェクトを解放します

GetDir

 SHBrowseForFolder APIを呼び出し、フォルダーを選択するダイアログボックスを表示します。
 初期化時にBrowseCallbackProc関数が呼び出されます。BFFM_INITIALIZEDメッセージの場合フォルダー選択の初期時のフォルダーを設定します。
 フォルダー名の選択が終了しOKボタンがクリックされると、SHBrowseForFolderが終了します。
 戻り値からSHGetPathFromIDListにより選択されたフォルダーのフルパスを取得できます。
 その後、SHBrowseForFolderで作成されたメモリをCoTaskMemFreeで解放します。

scaleChange

 現在のフォントを取得しスケールに応じたフォントを新規に作成し設定します。
 スケールからダイアログボックスの大きさを計算し、AdjustWindowRectによりクライアントサイズから必要なウィンドウサイズを計算しSetWindowPosによりダイアログボックスの大きさを変更します。
 各コントロールウィンドウをEnumChildWindowsで列挙します。

EnumWndProc

 EnumChildWindowsから呼び出されるEnumWndProc関数によりスケールに応じた位置、サイズ、文字サイズを設定します。
 スケールからコントロールの大きさを計算し、AdjustWindowRectによりクライアントサイズから必要なウィンドウサイズを計算しSetWindowPosによりダイアログボックスの大きさを変更します。
 コントロールのフォントをWM_SETTEXTメッセージにより変更します。

GetActualDialogBaseUnits

 ダイアログボックス座標の計算に必要な単位を取得します。
 YogaPro3では横が13、縦が31ピクセルが取得されました。

win10ver2

 Windows 10のバージョンを取得する関数です。
 詳細は、Windows 10のバージョン番号をRtlGetVersionのビルド番号から取得するを参照してください。

ソースコード

intwshotn.cpp

// フォアグランドのウィンドウを一定間隔でキャプチャーしてjpgファイルへ保存
// 2018/06/16 重複ファイル削除機能追加

#include <windows.h>
#include <commctrl.h>
#include <direct.h> 
#include <shlobj.h>
#include <shlwapi.h>
#include <stdio.h>
#include <time.h>
#include <tchar.h>
#include <locale.h>
#include <gdiplus.h>
#include <vector>
#include "resource.h"

#pragma comment(lib,"shlwapi.lib")
#pragma comment(lib,"gdiplus.lib")

#ifndef _DPI_AWARENESS_CONTEXTS_
DECLARE_HANDLE(DPI_AWARENESS_CONTEXT);
#endif

typedef enum MONITOR_DPI_TYPE {
	MDT_EFFECTIVE_DPI = 0,
	MDT_ANGULAR_DPI = 1,
	MDT_RAW_DPI = 2,
	MDT_DEFAULT = MDT_EFFECTIVE_DPI
} MONITOR_DPI_TYPE;


typedef HRESULT(_stdcall *GetDpiForMonitorFunc)(HMONITOR, MONITOR_DPI_TYPE, UINT *, UINT *);
typedef HRESULT(_stdcall* EnableNonClientDpiScalingFunc)(HWND);
typedef HRESULT(_stdcall* SetThreadDpiAwarenessContextFunc)(DPI_AWARENESS_CONTEXT);
typedef BOOL(_stdcall *SetProcessDPIAwareFunc)(VOID);

HMODULE hModUser32 = 0;
EnableNonClientDpiScalingFunc NcDpifunc = 0;
SetThreadDpiAwarenessContextFunc ThreadAwareFunc = 0;
EnableNonClientDpiScalingFunc NCCfunc = 0;

#ifndef _DPI_AWARENESS_CONTEXTS_

#define WM_DPICHANGED                   0x02E0

typedef enum _DPI_AWARENESS {
	DPI_AWARENESS_INVALID = -1,
	DPI_AWARENESS_UNAWARE = 0,
	DPI_AWARENESS_SYSTEM_AWARE = 1,
	DPI_AWARENESS_PER_MONITOR_AWARE = 2
} DPI_AWARENESS;

#define DPI_AWARENESS_CONTEXT_UNAWARE              ((DPI_AWARENESS_CONTEXT)-1)
#define DPI_AWARENESS_CONTEXT_SYSTEM_AWARE         ((DPI_AWARENESS_CONTEXT)-2)
#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE    ((DPI_AWARENESS_CONTEXT)-3)
#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((DPI_AWARENESS_CONTEXT)-4)
#define DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED    ((DPI_AWARENESS_CONTEXT)-5)

#endif


#define image_buf_num 2

BITMAPINFOHEADER bmih[image_buf_num];
BYTE *pBits[image_buf_num];
HBITMAP hBitmap[image_buf_num];
HDC hdcBMP[image_buf_num];	//ダブルバッファ
static int ix[image_buf_num], iy[image_buf_num];	//イメージサイズ

using namespace Gdiplus;

GdiplusStartupInput	gdiSI;
ULONG_PTR	gdiToken;
CLSID	encoderClsid;

//	ファイルの一覧
std::vector <PTCHAR> files;

//	GDI+で使用するエンコーダーの取得
int GetEncoderClsid(const WCHAR* format, CLSID* pClsid);

//	jpegファイルをメモリデバイスコンテキストにロードする
bool load_img(TCHAR* fname, HDC hdc, int ind);

//	重複ファイルを削除
void jpgDupDel(HDC hdc, TCHAR** files, size_t fmax);

//	画像を拡大縮小してエンコーダーで指定されるフォーマットのファイルとして保存
bool SaveBitmap(const TCHAR* FileName, HBITMAP hbm, CLSID* clsid, int rx, int ry);

//	アクティブウィンドウのスクリーンショットを保存する。
int screenShot(TCHAR* src_file, HWND hWnd);


struct DLG_CHILD_SCALE{
	HWND hDlg;
	SIZE unit;
	UINT oldScale;
	UINT newScale;
	HFONT hFont;
};

//	DPIスケールの変更
HFONT scaleChange(HWND hDlg, int nowScale, int oldScale, SIZE& baseUnit, HFONT hFont);
//	各ウィンドウ(コントロールの移動及び大きさの変更
BOOL CALLBACK EnumWndProc(HWND hWnd, LPARAM lParam);
//	ダイアログ単位をピクセルに変換
BOOL GetActualDialogBaseUnits(HWND hDialog, SIZE *baseUnit);

#define _WM_TASKTRAL_ (WM_USER+10)
#define ID_TRAY 110

#define	TIMER_ID	200

#pragma comment(lib,"comctl32.lib")

LRESULT CALLBACK DlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam);	//	ダイアログボックスプロシージャー
int GetDir(HWND hWnd, TCHAR* path, TCHAR* root);	//	フォルダー名を取得するコモンコントロール

HINSTANCE hInst;
TCHAR def_dir[MAX_PATH];

int win10ver2(void);
int WIN10VER;

int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, LPSTR lpsCmdLine, int nCmdShow){
	::hInst = hCurInst;
	WIN10VER = win10ver2();
	hModUser32 = LoadLibrary(L"User32.dll");
	if (hModUser32){
		NCCfunc = (EnableNonClientDpiScalingFunc)GetProcAddress(hModUser32, "EnableNonClientDpiScaling");
		if (WIN10VER == 1607)	//	AnniversaryUpdate
			NCCfunc = (EnableNonClientDpiScalingFunc)GetProcAddress(hModUser32, "EnableNonClientDpiScaling");
		else
			NCCfunc = 0;
		ThreadAwareFunc = (SetThreadDpiAwarenessContextFunc)GetProcAddress(hModUser32, "SetThreadDpiAwarenessContext");
		if (ThreadAwareFunc){
			if (WIN10VER<1703)
				(*ThreadAwareFunc)(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE);
			if (1703 <= WIN10VER && WIN10VER<1809)
				(*ThreadAwareFunc)(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
			if (1809 <= WIN10VER)
				(*ThreadAwareFunc)(DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED);
		}
		else{	//	Windows Vista以降
			SetProcessDPIAwareFunc ProcessAwareFunc = (SetProcessDPIAwareFunc)GetProcAddress(hModUser32, "SetProcessDPIAware");
			if (ProcessAwareFunc)
				(*ProcessAwareFunc)();
		}
	}

	GdiplusStartup(&gdiToken, &gdiSI, NULL);
	if (GetEncoderClsid(L"image/jpeg", &encoderClsid)<0){
		MessageBox(0, TEXT("jpegエンコーダーが取得できませんでした。"), TEXT("エラー"), MB_OK);
		return (int)0;
	}
	DialogBox(hCurInst, TEXT("DLG1"), 0, (DLGPROC)DlgProc);
	GdiplusShutdown(gdiToken);

	return (int)0;
}

#define START_COUNT (5-1)
//	ダイアログボックスプロシージャー

LRESULT CALLBACK DlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam){
	static UINT_PTR tid=0;
	static int count=0;
	static NOTIFYICONDATA ni;
	static HICON hIcon=0;
	static TCHAR dir[MAX_PATH];
	static TCHAR pass[MAX_PATH];

	static TCHAR buf[MAX_PATH];

	static HWND hWndScreen = 0;
	static int timer_int = 1500;
	static HFONT hFont = 0;
	static int nowDPIx = 96;
	static int nowDPIy = 96;
	static UINT nowScale = 100;
	static UINT initScale = 100;
	static int height;
	static HMODULE hMod;
	static GetDpiForMonitorFunc func = 0;
	static SIZE baseUnit;

	switch (msg) {
	case WM_INITDIALOG:{
		if (WIN10VER < 1703){
			HMONITOR hMonitor = MonitorFromWindow(hDlg, MONITOR_DEFAULTTONULL);
			hMod = LoadLibrary(L"Shcore.dll");

			func = (GetDpiForMonitorFunc)GetProcAddress(hMod, "GetDpiForMonitor");
			if (func == 0)
				FreeLibrary(hMod);

			UINT hDPI, vDPI;
			if (func)
				(*func)(hMonitor, MDT_EFFECTIVE_DPI, &hDPI, &vDPI);
			else{
				HDC hdc = GetDC(NULL);	//	ディスクトップ
				hDPI = GetDeviceCaps(hdc, LOGPIXELSX);
				vDPI = GetDeviceCaps(hdc, LOGPIXELSY);
			}
			nowDPIx = hDPI;
			nowDPIy = vDPI;
			nowScale = MulDiv(nowDPIx, 100, 96);
			GetActualDialogBaseUnits(hDlg, &baseUnit);
			UINT oldScale = nowScale;
			hFont = scaleChange(hDlg, nowScale, oldScale, baseUnit, hFont);
		}
		SetWindowText(GetDlgItem(hDlg, IDC_EDIT_TIMER), _TEXT("1500"));
		SetWindowText(GetDlgItem(hDlg, IDC_EDIT_DIR), _TEXT("C:¥¥"));
		hIcon=LoadIcon( NULL,IDI_ERROR );
		return TRUE;
	}
	case WM_DPICHANGED:{
		if (WIN10VER < 1703){
			UINT oldScale = nowScale;
			nowDPIx = LOWORD(wParam);
			nowDPIy = HIWORD(wParam);
			nowScale = MulDiv(nowDPIx, 100, 96);
			hFont = scaleChange(hDlg, nowScale, oldScale, baseUnit, hFont);
		}
		break;
	}
	case WM_NCCREATE:{
		if (NCCfunc)
			(*NCCfunc)(hDlg);
		return FALSE;
	}
	case WM_TIMER:{
		KillTimer(hDlg, tid);
		tid = 0;
		if (count < START_COUNT)
			Beep(1000, 200);
		if (count == START_COUNT){
			hWndScreen = GetForegroundWindow();
			Beep(2000, timer_int);
		}
		if (START_COUNT < count){
			screenShot(buf, hWndScreen);
			SetWindowText(GetDlgItem(hDlg, IDC_LABEL), buf);
		}
		_stprintf_s(buf, sizeof(buf) / sizeof(TCHAR), _TEXT("%s%05d.jpg"), dir, count);
		TCHAR* str=_tcsdup(buf);
		files.push_back(str);
		++count;
		tid = SetTimer(hDlg, TIMER_ID, timer_int, NULL);
		break;
	}
	case _WM_TASKTRAL_ :	//	タスクトレイのアイコンからのメッセージ
		if(lParam==WM_LBUTTONDOWN){	//	マウスの
			if (tid){
				KillTimer(hDlg, tid);
				tid = 0;
			}
			ShowWindow( hDlg, SW_SHOW );	//	ダイアログボックスを表示
			EnableWindow(GetDlgItem(hDlg, IDOK), TRUE);
			EnableWindow(GetDlgItem(hDlg, IDC_STOP), TRUE);
		}
		break;
	case WM_COMMAND:
		switch (LOWORD(wParam)) {
		case IDC_REF:	//	フォルダー参照
			GetDlgItemText(hDlg, IDC_EDIT_DIR, (TCHAR*)def_dir, sizeof(def_dir) / sizeof(TCHAR));
			if(GetDir(hDlg, buf, 0)==TRUE)
				SetWindowText(GetDlgItem(hDlg, IDC_EDIT_DIR), buf);
			break;
		case IDC_STOP:{	//	キャプチャー停止
			if (tid){
				KillTimer(hDlg, tid);
				tid= 0;
			}
			hWndScreen = 0;
			EnableWindow(GetDlgItem(hDlg, IDOK), TRUE);
			EnableWindow(GetDlgItem(hDlg, IDC_STOP), FALSE);
			UINT i;
			if(BST_CHECKED==SendMessage(GetDlgItem(hDlg, IDC_RADIOBUTTON_DUPCHK),BM_GETCHECK,0,0)){
				PTCHAR* ff=new PTCHAR[files.size()];
				for(i=0;i<files.size();i++){
					ff[i]=files[i];
				}
				HDC hdc=GetDC(hDlg);
				jpgDupDel(hdc,ff,files.size());
				ReleaseDC(hDlg,hdc);
				delete []ff;
			}
			for(i = 0; i < files.size(); i++){
				free(files[i]);
			}
			files.clear();
			break;
		}
		case IDCANCEL:	//	ダイアログの終了
			if (tid)
				KillTimer(hDlg, tid);
			EndDialog(hDlg, TRUE);
			return TRUE;
		case IDOK:{
			hWndScreen = 0;
			time_t t;
			time(&t);
			GetDlgItemText(hDlg, IDC_EDIT_TIMER, (TCHAR*)buf, sizeof(buf) / sizeof(TCHAR));
			timer_int=_ttoi(buf);

			GetDlgItemText(hDlg, IDC_EDIT_DIR, (TCHAR*)buf, sizeof(buf) / sizeof(TCHAR));
			struct tm t_st;
			localtime_s(&t_st,&t);
			_stprintf_s(dir,MAX_PATH,_TEXT("%s¥¥%04d%02d%02d%02d%02d%02d_image"),buf,t_st.tm_year+1900,t_st.tm_mon+1,t_st.tm_mday,t_st.tm_hour, t_st.tm_min,t_st.tm_sec);
			EnableWindow(GetDlgItem(hDlg, IDOK), FALSE);
			//	タスクトレイに本プログラムのアイコンを追加
			ni.cbSize=sizeof(ni);
			ni.hIcon=hIcon;
			ni.hWnd=hDlg;
			ni.uCallbackMessage = _WM_TASKTRAL_ ;
			ni.uFlags= NIF_ICON | NIF_MESSAGE | NIF_TIP;
			ni.uID = ID_TRAY;
			_tcscpy_s(ni.szTip , sizeof(ni.szTip)/sizeof(ni.szTip[0]) , _TEXT("キャプチャー中"));
			Shell_NotifyIcon(NIM_ADD,&ni);
			//	ダイアログボックスを非表示にしてタスクバーに表示されるのを防ぐ
			ShowWindow( hDlg, SW_HIDE );
			count = 0;
			tid = SetTimer(hDlg, TIMER_ID, timer_int, NULL);
			break;
		}
		default:
			return FALSE;
		}
	default:
		return FALSE;
	}
	return FALSE;
}


//	ディレクトリ名を取得するコモンダイアログを表示する

int __stdcall BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData);


int GetDir(HWND hWnd, TCHAR* path, TCHAR* root){
	BROWSEINFO bInfo;
	LPITEMIDLIST pIDList;
	bInfo.hwndOwner = hWnd; // ダイアログの親ウインドウのハンドル 
	bInfo.pidlRoot = NULL; // ルートフォルダをデスクトップフォルダとする 
	bInfo.pszDisplayName = path; //szDisplayName; // フォルダ名を受け取るバッファへのポインタ 
	bInfo.lpszTitle = TEXT("保存するフォルダの選択"); // ツリービューの上部に表示される文字列 
	bInfo.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE; // 表示されるフォルダの種類を示すフラグ 
	bInfo.lpfn = BrowseCallbackProc; // BrowseCallbackProc関数のポインタ 
	bInfo.lParam = NULL;
	pIDList = SHBrowseForFolder(&bInfo);
	if (pIDList == NULL){
		path[0] = _TEXT('¥0');
		return FALSE; //何も選択されなかった場合 
	}else{
		if (!SHGetPathFromIDList(pIDList, path))
			return FALSE;//変換に失敗 
		CoTaskMemFree(pIDList);// pIDListのメモリを開放 
		return TRUE;
	}
}
//	上記ダイアログのコールバック関数

int __stdcall BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData){
	if (uMsg == BFFM_INITIALIZED){
		SendMessage(hwnd, BFFM_SETSELECTION, (WPARAM)TRUE, (LPARAM)def_dir);	//	コモンダイアログの初期ディレクトリ
	}
	return 0;
}


//	jpegファイルをメモリデバイスコンテキストにロードする

bool load_img(TCHAR* fname, HDC hdc,int ind){
	Image* imageP;
#ifndef UNICODE
	WCHAR wTitle[MAX_PATH];
	MultiByteToWideChar(932, 0, szFile, -1, wTitle, sizeof(wTitle) / sizeof(TCHAR));
	imageP = Bitmap::FromFile(wTitle);
#else
	imageP = Bitmap::FromFile(fname);
#endif
	if (imageP == 0){
		ix[ind] = 0;
		iy[ind] = 0;
		hdcBMP[ind] = 0;
		return false;
	}
	bmih[ind].biSize = sizeof(bmih[ind]);
	bmih[ind].biWidth = ix[ind] = imageP->GetWidth();
	bmih[ind].biHeight = iy[ind] = imageP->GetHeight();
	bmih[ind].biPlanes = 1;
	bmih[ind].biBitCount = 32;
	bmih[ind].biCompression = BI_RGB;
	bmih[ind].biSizeImage = 0;
	bmih[ind].biXPelsPerMeter = 0;
	bmih[ind].biYPelsPerMeter = 0;
	bmih[ind].biClrUsed = 0;
	bmih[ind].biClrImportant = 0;
	hBitmap[ind] = CreateDIBSection(NULL, (BITMAPINFO*)&bmih[ind], 0, (void**)&pBits[ind], NULL, 0);
	if (pBits == NULL){
		MessageBox(0, _TEXT("メモリが不足しています。"), _TEXT("Message"), MB_YESNO | MB_ICONQUESTION);
		DeleteObject(hBitmap[ind]);
		delete imageP;
		ix[ind] = 0;
		iy[ind] = 0;
		return false;
	}
	hdcBMP[ind] = CreateCompatibleDC(hdc);
	SelectObject(hdcBMP[ind], hBitmap[ind]);
	Graphics MyGraphics(hdcBMP[ind]);
	MyGraphics.DrawImage(imageP, 0, 0, imageP->GetWidth(), imageP->GetHeight());
	delete imageP;
	return true;
}
//	DPIスケールの変更
HFONT scaleChange(HWND hDlg, int nowScale, int oldScale, SIZE& baseUnit, HFONT hFont){
	LOGFONT lf;
	HWND hWnd;
	hWnd = GetWindow(hDlg, GW_CHILD);
	//hWnd = GetDlgItem(hDlg, IDC_END_ADR_EDIT);
	HDC hdc = GetDC(hWnd);
	HFONT hfont9 = (HFONT)SendMessage(hWnd, WM_GETFONT, 0, 0);
	GetObject(hfont9, sizeof(LOGFONT), &lf);
	int height;
	height = lf.lfHeight * 100 / oldScale;
	lf.lfHeight = (height)*nowScale;
	lf.lfHeight /= 100;
	HFONT hFont2 = CreateFontIndirect(&lf);
	LOGFONT lfont;
	HFONT hFontOld = (HFONT)SelectObject(hdc, hFont2);

	TEXTMETRIC metrics;
	GetTextMetrics(hdc, &metrics);
	SelectObject(hdc, hFontOld);

	HFONT htemp = (HFONT)SendMessage(hDlg, WM_GETFONT, 0, 0);
	GetObject(htemp, sizeof(LOGFONT), &lfont);
	ReleaseDC(hWnd, hdc);

	RECT rc;
	GetClientRect(hDlg, &rc);
	int w = rc.right - rc.left;
	int h = rc.bottom - rc.top;
	w = MulDiv(w, nowScale, oldScale);
	h = MulDiv(h, nowScale, oldScale);

	rc.left = 0;
	rc.top = 0;
	rc.right = w;
	rc.bottom = h;

	LONG style = GetWindowLong(hDlg, GWL_STYLE);
	AdjustWindowRectEx(&rc, style, FALSE, 0);

	SetWindowPos(hDlg, NULL, 0, 0, rc.right - rc.left, rc.bottom - rc.top, SWP_NOMOVE | SWP_NOZORDER);

	DLG_CHILD_SCALE d;
	d.hDlg = hDlg;
	d.hFont = hFont2;
	d.newScale = nowScale;
	d.oldScale = oldScale;
	d.unit = baseUnit;

	EnumChildWindows(hDlg, EnumWndProc, (LPARAM)&d);

	InvalidateRect(hDlg, NULL, FALSE);
	UpdateWindow(hDlg);
	if (hFont)
		DeleteObject(hFont);
	return hFont = hFont2;
}
//	各ウィンドウ(コントロールの移動及び大きさの変更
BOOL CALLBACK EnumWndProc(HWND hWnd, LPARAM lParam){
	POINT pt;
	TCHAR name[16];
	GetClassName(hWnd, name, sizeof(name) / sizeof(name[0]));//リストビューのヘッダは孫ウィンドウ
	if (_tcscmp(name, _TEXT("SysHeader32")) == 0)
		return TRUE;
	DLG_CHILD_SCALE* d = (DLG_CHILD_SCALE*)lParam;
	pt.x = 0;
	pt.y = 0;
	ClientToScreen(hWnd, &pt);
	ScreenToClient(d->hDlg, &pt);
	RECT rc;
	GetClientRect(hWnd, &rc);
	int cx, cy, w, h;
	cx = MulDiv(pt.x, 4, d->unit.cx);
	cy = MulDiv(pt.y, 8, d->unit.cy);

	cx = MulDiv(cx, d->newScale, d->oldScale);
	cy = MulDiv(cy, d->newScale, d->oldScale);

	cx = MulDiv(cx, d->unit.cx, 4);
	cy = MulDiv(cy, d->unit.cy, 8);

	w = MulDiv(rc.right, d->newScale, d->oldScale);
	h = MulDiv(rc.bottom, d->newScale, d->oldScale);

	rc.left = cx;
	rc.top = cy;
	rc.right = cx + w;
	rc.bottom = cy + h;
	LONG style = GetWindowLong(hWnd, GWL_STYLE);
	LONG exStyle = GetWindowLong(hWnd, GWL_EXSTYLE);
	AdjustWindowRectEx(&rc, style, FALSE, exStyle);
	SetWindowPos(hWnd, NULL, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, SWP_NOZORDER);
	SendMessage(hWnd, WM_SETFONT, (WPARAM)d->hFont, MAKELPARAM(TRUE, 0));
	UpdateWindow(hWnd);

	return TRUE;
}

//	ダイアログ単位をピクセルに変換
BOOL GetActualDialogBaseUnits(HWND hDialog, SIZE *baseUnit){
	RECT rect;
	BOOL result;

	rect.left = 4;
	rect.top = 8;
	if (result = MapDialogRect(hDialog, &rect)) {
		baseUnit->cx = rect.left;
		baseUnit->cy = rect.top;
	}
	return result;
}


//	DLL内の関数へのポインタ型を定義
typedef void (WINAPI *RtlGetVersion_FUNC)(OSVERSIONINFOEXW*);

int win10ver2(void){
	int ver = 0;
	HMODULE hMod;
	OSVERSIONINFOEXW osw;
	hMod = LoadLibrary(TEXT("ntdll.dll"));
	RtlGetVersion_FUNC func;
	if (hMod){
		func = (RtlGetVersion_FUNC)GetProcAddress(hMod, "RtlGetVersion");
		if (func == 0){
			FreeLibrary(hMod);
			return FALSE;
		}
		ZeroMemory(&osw, sizeof(osw));
		osw.dwOSVersionInfoSize = sizeof(osw);
		func(&osw);
		FreeLibrary(hMod);
		if (osw.dwMajorVersion == 10){
			switch (osw.dwBuildNumber){
			case 10240: ver = 1507; break;
			case 10586: ver = 1511; break;
			case 14393: ver = 1607; break;
			case 15063: ver = 1703; break;
			case 16299: ver = 1709; break;
			case 17134: ver = 1803; break;
			case 17763: ver = 1809; break;
			default: ver = 1809; break;
			}
			return ver;
		}
	}
	return -1;
}

//	重複ファイルを削除

void jpgDupDel(HDC hdc, TCHAR** files, size_t fmax){
	int i;
	TCHAR* p;
	int dtc_num = 0;
	int n = 0;
	for (i = 0; i < (int)fmax; i++){
		TCHAR* src_file = files[i];
		p = PathFindFileName(src_file);
		if (load_img(src_file, hdc, n % 2) == true){
			bool eq = false;
			if (0 < n){
				int srci = (n + 1) % 2;
				int dtci = n % 2;
				if (ix[srci] == ix[dtci] && iy[srci] == iy[dtci]){	//	画像サイズが一致
					UINT sz = ix[srci] * iy[srci] * 4;
					if (pBits[0] == 0 || pBits[1] == 0){
						eq = false;
					}
					else{
						if (memcmp(pBits[0], pBits[1], sz) == 0){	//	一致した場合
							eq = true;
						}
					}
				}
				DeleteObject(hBitmap[srci]);
				DeleteDC(hdcBMP[srci]);
				hdcBMP[srci] = 0;
			}
			if (eq == false){
				++dtc_num;
			}
			else{
				DeleteFile(src_file);
			}
			++n;
		}
	}
	for (i = 0; i < image_buf_num; i++){
		if (hBitmap[i]){
			DeleteObject(hBitmap[i]);
			DeleteDC(hdcBMP[i]);
			hdcBMP[i] = 0;
		}
	}
}


//	GDI+で使用するエンコーダーの取得
int GetEncoderClsid(const WCHAR* format, CLSID* pClsid){
	UINT  num = 0;
	UINT  size = 0;
	ImageCodecInfo* pImageCodecInfo;
	GetImageEncodersSize(&num, &size);
	if (size == 0)
		return -1;
	pImageCodecInfo = (ImageCodecInfo*)new char[size];
	if (pImageCodecInfo == NULL)
		return -1;
	GetImageEncoders(num, size, pImageCodecInfo);
	for (UINT n = 0; n<num; ++n) {
		if (wcscmp(pImageCodecInfo[n].MimeType, format) == 0) {
			*pClsid = pImageCodecInfo[n].Clsid;
			delete pImageCodecInfo;
			return n;
		}
	}
	delete pImageCodecInfo;
	return -1;
}

//	画像を拡大縮小してエンコーダーで指定されるフォーマットのファイルとして保存

bool SaveBitmap(const TCHAR* FileName, HBITMAP hbm, CLSID* clsid, int rx, int ry){
	Bitmap b(hbm, (HPALETTE)GetStockObject(DEFAULT_PALETTE));
	Image* srcImage = &b;
	Bitmap* bmp = new Bitmap(rx, ry);
	Graphics* MyGraphics = Graphics::FromImage(bmp);
	Point pt[] = { Point(0, 0), Point(rx, 0), Point(0, ry) };
	MyGraphics->DrawImage(srcImage, pt, 3);

	ULONG qualityValue = 100;

	Gdiplus::EncoderParameters params = { 0 };
	params.Count = 1;
	params.Parameter[0].Guid = Gdiplus::EncoderQuality;
	params.Parameter[0].Type = Gdiplus::EncoderParameterValueTypeLong;
	params.Parameter[0].NumberOfValues = 1;
	params.Parameter[0].Value = &qualityValue;

	bool r = (bmp->Save(FileName, clsid, &params) == Gdiplus::Ok) ? true : false;
	MyGraphics->~Graphics();
	delete bmp;
	return r;
}

//	アクティブウィンドウのスクリーンショットを保存する。
int screenShot(TCHAR* src_file, HWND hWnd){
	HDC hdc = GetDC(hWnd);
	RECT rect;
	GetWindowRect(hWnd, &rect);
	UINT w = rect.right - rect.left;
	UINT h = rect.bottom - rect.top;
	HDC mhdc = CreateCompatibleDC(hdc);
	HBITMAP mbmp = CreateCompatibleBitmap(hdc, w, h);
	HBITMAP oldbmp = (HBITMAP)SelectObject(mhdc, mbmp);

	PrintWindow(hWnd, mhdc, 0);

	SelectObject(mhdc, oldbmp);

	SaveBitmap(src_file, mbmp, &encoderClsid, w, h);
	DeleteObject(mbmp);
	DeleteDC(mhdc);
	ReleaseDC(hWnd, hdc);
	return 0;
}

resource.h

#define IDC_LABEL		100
#define IDC_EDIT_TIMER	110
#define IDC_EDIT_DIR	120
#define IDC_REF	130
#define IDC_STOP	140
#define	IDC_RADIOBUTTON_DUPCHK	150

resource.rc

#include <windows.h>
#include "resource.h"

DLG1 DIALOG DISCARDABLE 0, 0, 256, 149
EXSTYLE WS_EX_DLGMODALFRAME
STYLE WS_POPUP  | WS_CAPTION | DS_SETFONT 
CAPTION "キャプチャー"
FONT 9, "MS Shell Dlg"
{
 CONTROL "0000", IDC_LABEL, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY, 7, 7, 242, 14
 CONTROL "間隔(m秒)", -1, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY, 7, 28, 64, 14
 EDITTEXT IDC_EDIT_TIMER, 7, 49, 128, 14, ES_AUTOHSCROLL | ES_NUMBER
 CONTROL "保存フォルダー", -1, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY, 7, 72, 96, 14
 EDITTEXT IDC_EDIT_DIR, 7, 91, 242, 14, ES_AUTOHSCROLL
 CONTROL "参照(&B)", IDC_REF, "BUTTON", WS_CHILD | WS_VISIBLE, 95, 70, 32, 14
 
 CONTROL "重複削除(&D)" , IDC_RADIOBUTTON_DUPCHK, "BUTTON", WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX,136,70, 64, 14

 CONTROL "開始(&R)", IDOK, "BUTTON", WS_CHILD | WS_VISIBLE , 42, 128,54, 14
 CONTROL "削除(&S)", IDC_STOP, "BUTTON", WS_CHILD | WS_VISIBLE , 101, 128,54, 14
 CONTROL "終了(&Q)", IDCANCEL, "BUTTON", WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 160, 128, 54, 14
}

END

実行ファイルとソースファイルのダウンロード(69.1kByte)