///////////////////////////////
//CALBUMクラス（based on GDI+）
//Copyright (c) 2022 by Ysama
//GDI+を使ったイメージファイル
//の管理ユーティリティ
//注意：アルバムの使用にはGDI+、
//フォトクラスのヘッダー取込み
//が必要である。
///////////////////////////////
//イメージファイルのフィルター
#define		IMGFILTER	"イメージファイル\0*.bmp;*.jpeg;*.jpg;*.gif;*.tiff;*.png;*.wmf;*.emf;*.ico\0\0"
//エンコーダーリスト
const WCHAR* g_EncoderList[5] = {L"image/bmp", L"image/jpeg", L"image/gif", L"image/tiff", L"image/png"};
//GDI+のインクルード、ネームスペースの使用
#include	<gdiplus.h>
using namespace Gdiplus;

//ワイド文字使用の為"wchar.h"をインクルード
#include <wchar.h>
//CPHOTOクラスヘッダー
#include	"CPHOTO.h"

//イメージファイルの最大数
#define	MAXIMG	0x200						//512枚の写真−ユーザー変更可

class CALBUM
{
private:
	//GDI+関係メンバー変数
	GdiplusStartupInput m_gdiSI;			//GDI+のスタートアップインプット
	ULONG_PTR m_gdiToken;					//GDI+のトークン
	Graphics* m_pGraphics = 0;				//Graphicsオブジェクトポインター
	Color m_Col = 0;						//Colorオブジェクト
	Pen* m_pPen = 0;						//矩形描画の為のペンオブジェクトポインター
	CPHOTO* m_Photo[MAXIMG] = {0};			//CPHOTOクラスインスタンスポインター配列
	Image* m_Image = 0;						//Imageクラスオブジェクトポインター
	RotateFlipType m_RFType = RotateNoneFlipNone;	//出力用回転、反転タイプ(回転、反転なし)
	//出力先ウィンドウ関係メンバー変数
	HWND m_hWnd;							//出力先ウィンドウのハンドル
	int m_X = 0;							//出力先ウィンドウ有効クライアントエリアX座標
	int m_Y = 0;							//出力先ウィンドウ有効クライアントエリアY座標
	int m_Width;							//出力先ウィンドウ有効クライアントエリア幅
	int m_Height;							//出力先ウィンドウ有効クライアントエリア高さ
	//出力先仮想ウィンドウ関係メンバー変数
	HDC m_hDC;								//出力先ウィンドウのコンパチブルDC
	HBITMAP m_hCanvas;						//仮想ウィンドウであるビットマップ
	int m_Left;								//仮想ウィンドウ（フルスクリーン）左座標
	int m_Top;								//仮想ウィンドウ上座標
	int m_Right;							//仮想ウィンドウ右座標
	int m_Bottom;							//仮想ウィンドウ下座標
	//サムネイル用変数
	int m_Page = 0;							//写真表示ページ数（1〜ベース）
	int m_Column = 0;						//写真の列数（1〜ベース）
	int m_Row = 0;							//写真の列数（1〜ベース）
public:
	//表示写真関係メンバー変数
	char m_FileName[MAX_PATH];				//フルパス写真ファイル名用文字列（MBS）
	WCHAR m_WFName[MAX_PATH];				//フルパス写真ファイル名用文字列(WCS）
	int m_NoI = 0;							//読み込み写真数
	int m_Selected = 0;						//選択されている写真の配列番号
	int m_CurPage = 0;						//サムネイル表示で現在選択されているページ（0〜ベース）
	//メンバー関数
	CALBUM();								//コンストラクター
	~CALBUM();								//デストラクター
	bool Init(HWND);						//Graphicsクラスと出力仮想ウィンドウの初期化
	void GetSize(LPARAM, HWND, HWND);		//出力先ウィンドウのWM_SIZEメッセージ時に実行
	void OnPaint(HDC, int, int);			//出力先ウィンドウのWM_PAINTメッセージ時に実行
	void SetCol(int, int, int, int);		//描画色設定（Alpha, R, G, B）
	bool Cls();								//画面消去（直前のSetCol関数で消去色を設定する）
	void SetPen(int);						//ペン設定（直前のSetCol関数で消去色を設定する）
	bool Rect(int, int, int, int);			//指定（x, y, w, h）の矩形を描画（色、ペン設定後実行）
	bool Add(char*, char*, char*);			//写真を追加(ファイル名がNULLだと「ファイルを開く」ダイアログが開く)
	bool Delete(int);						//写真の削除
	void DeleteAll();						//全写真の削除
	RotateFlipType GetRotateFlip()			//現在の回転、反転状態を返す
		{return m_RFType;}
	void SetRotateFlip(RotateFlipType type)	//写真回転、反転表示指定
		{m_RFType = type;}
	int GetCount() {return m_NoI;}			//登録写真数を返す
	int GetSelected()						//選択写真の配列番号取得（0〜ベース）
		{return m_Selected;}
	void Select(int);						//写真の選択（0〜ベース）
	DWORD GetImgSize();						//選択イメージ幅(LOWORD)、高さ(HIWORD)の取得
	char* GetFileName();					//m_Selectedのファイルのフルパスファイル名(MBS)を返す
	char* GetComment();						//選択イメージのコメントの取得
	void SetComment(char*);					//選択イメージのコメントの設定
	bool ShowImg(int, int, int, int);		//写真の表示(x, yから幅w、高さh内に原寸縦横対比で中央に表示)
	bool ShowImg(bool, bool);				//イメージの表示(第1引数TRUE左上、FALSE中央、第2引数TRUE原寸、FALSEサイズ調整)
	bool ShowThumbnail(int, int);			//サムネイル表示(引数はサムネイルの幅・高さ、ページ)
	int GetPage() {return m_CurPage;}		//現在のページを返す
	int HowManyThumb(int);					//サムネイル表示のページ当り表示写真数
	int GetWhichThumb(int, int, int);		//出力先ウィンドウのx, y座標からサムネイルの番号を得る
};

//コンストラクター
CALBUM::CALBUM() {

	//ワイド文字使用のためロケールの初期化（日本語）
	setlocale(LC_CTYPE, "JPN");
	//GDI+の開始
	GdiplusStartup(&m_gdiToken, &m_gdiSI, NULL);
}

//デストラクター
CALBUM::~CALBUM() {

	//スクリーンビットマップの削除
	DeleteObject(m_hCanvas);
	//コンパチDCの削除
	DeleteDC(m_hDC);
	//CPHOTOオブジェクトの削除（CPHOTOのメンバーはそのデストラクターで解放）
	for(int i = m_NoI - 1; i >= 0; i--)
		delete m_Photo[i];
	//Penオブジェクトの終了
	if(m_pPen)
		delete m_pPen;
	//Graphicsオブジェクトの終了
	if(m_pGraphics)
		delete m_pGraphics;
	//GDI+の終了
	GdiplusShutdown(m_gdiToken);
}

//Album(Graphics)の初期化（1DCに1回だけ実施する）
bool CALBUM::Init(HWND hWnd) {

	//ウィンドウ関係
	m_hWnd = hWnd;									//ウィンドウハンドルを記録
	HDC hDC = GetDC(hWnd);							//対象ウィンドウのDCを取得
	m_hDC = CreateCompatibleDC(hDC);				//コンパチDCを作成
	m_Left = m_Top = 0;								//フルスクリーンに拡大した
	m_Right = GetSystemMetrics(SM_CXFULLSCREEN);	//時のキャンヴァスサイズを
	m_Bottom = GetSystemMetrics(SM_CYFULLSCREEN);	//記録しておく
	m_hCanvas = CreateCompatibleBitmap(hDC,			//m_hDCではないので注意
				m_Right, m_Bottom);					//m_hDCにするとモノクロとなる
	SelectObject(m_hDC, m_hCanvas);					//キャンバスの実体
	PatBlt(m_hDC, m_Left, m_Top, m_Right, m_Bottom, PATCOPY);	//初期描画
	ReleaseDC(hWnd, hDC);							//hDCを開放
	if(m_pGraphics = new Graphics(m_hDC))
		return TRUE;
	else
		return FALSE;
}

//出力先ウィンドウのWM_SIZEで実行
void CALBUM::GetSize(LPARAM lParam, HWND TBar, HWND SBar) {

	m_Width = LOWORD(lParam);
	m_Height = HIWORD(lParam);
	RECT rec;
	if(TBar) {
		GetWindowRect(TBar, &rec);
		m_Y = rec.bottom - rec.top;
		m_Height -= m_Y;
	}
	if(SBar) {
		GetWindowRect(SBar, &rec);
		m_Height -= rec.bottom - rec.top;
	}
}

//m_hWndウィンドウのWM_PAINTメッセージで実行
void CALBUM::OnPaint(HDC hDC, int x = 0, int y = 0) {
	//ウィンドウを仮想ウィンドウ（Bitmap）で描画
	BitBlt(hDC, m_Left, m_Top, m_Right, m_Bottom, m_hDC, x, y, SRCCOPY);
}

//描画色設定（Alpha, R, G, B）
void CALBUM::SetCol(int Alpha, int R, int G, int B) {

	m_Col = Color(Alpha, R, G, B);		//Alpha, R, G, B
}

//画面消去（直前のSetCol関数で消去色を設定する）
bool CALBUM::Cls() {

	if(!m_pGraphics)
		return FALSE;
	m_pGraphics->Clear(m_Col);
	//再描画を要求
	InvalidateRect(m_hWnd, NULL, TRUE);
	return TRUE;
}

//ペン設定（直前のSetCol関数で消去色を設定する）
void CALBUM::SetPen(int width) {

	if(m_pPen)
		delete m_pPen;
	m_pPen = new Pen(m_Col, width);
}

//指定（x, y, w, h）の矩形を描画（要色、ペン設定）
bool CALBUM::Rect(int x, int y, int w, int h) {

	if(!m_pGraphics)
		return FALSE;
	m_pGraphics->DrawRectangle(m_pPen, x, y, w, h);
	//再描画を要求
	InvalidateRect(m_hWnd, NULL, TRUE);
	return TRUE;
}

//フォトを追加(ファイル名がNULLだと「ファイルを開く」ダイアログが開く)
bool CALBUM::Add(char* fn, char* ftr = IMGFILTER, char* cmt = "") {

	if(*fn)	//ファイル名が与えられた場合
		lstrcpy(m_FileName, fn);
	else {
		//イメージファイルを選択
		OPENFILENAME ofn;				//オープンファイルダイアログ構造体
		ZeroMemory(&ofn, sizeof(ofn));	//ゼロ初期化
		ofn.lStructSize		= sizeof(ofn);
		ofn.hwndOwner		= m_hWnd;
		ofn.lpstrFilter		= ftr;		//引数でフィルタリングする
		ofn.nFilterIndex	= 1;
		ofn.lpstrFile		= m_FileName;
		ofn.nMaxFile		= MAX_PATH;
		ofn.lpstrTitle		= "イメージファイルの選択";
		ofn.lpstrInitialDir = ".";
		ofn.Flags			= OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT;
		ofn.lpstrDefExt		= NULL;
		if(!GetOpenFileName(&ofn)) {
			return FALSE;
		}
	}
	//写真を追加
	m_Photo[m_NoI] = new (CPHOTO);	//写真（CPH0TOインスタンス）の作成
	m_Photo[m_NoI]->SetPhoto(m_FileName, cmt);
	//写真数を増やす
	m_NoI++;
	if(m_NoI > MAXIMG) {
		MessageBox(m_hWnd, "これ以上のイメージ読み込みはできません", "警告", MB_OK | MB_ICONERROR);
		m_NoI--;
		return FALSE;
	}
	return TRUE;
}

//フォトの削除
bool CALBUM::Delete(int n) {

	if(n >= m_NoI || n < 0)	//違法な引数
		return FALSE;
	m_Photo[n]->Delete();
	for(int i = n; i < m_NoI - 1; i++) {
		m_Photo[i] = m_Photo[i + 1];
	}
	if(m_NoI > 0) {
		--m_NoI;
		m_Photo[m_NoI] = 0;
	}
	if(m_Selected && m_Selected >= n)
		--m_Selected;
	return TRUE;
}

//全フォトの削除
void CALBUM::DeleteAll() {

	for(int i = m_NoI - 1; i >= 0; i--)
		delete m_Photo[i];
	m_NoI = 0;					//読み込みImage数
	m_Selected = 0;				//選択されているイメージの配列番号
	m_CurPage = 0;				//サムネイル表示で現在選択されているページ
	m_RFType = RotateNoneFlipNone;	//回転、反転なし
}

//選択イメージの設定（0〜ベース）
void CALBUM::Select(int n) {

	if(n < 0)
		m_Selected = 0;
	else {
		if(n < m_NoI)
			m_Selected = n;
		else
			m_Selected = m_NoI - 1;
	}
}

//選択イメージ幅(LOWORD)、高さ(HIWORD)の取得
DWORD CALBUM::GetImgSize() {

	//エラー対応
	if(!m_Photo[m_Selected])
		return 0;
	//現在選択されている写真のファイル名をワイド文字にする
	mbstowcs(m_WFName, m_Photo[m_Selected]->m_FName, MAX_PATH);
	//現在選択されている写真のファイル名でイメージクラスインスタンスを生成
	m_Image = new (Image)(m_WFName);
	//m_Imageインスタンスの生成に失敗した場合
	if(!m_Image)
		return 0;
    //イメージ幅(LOWORD)、高さ(HIWORD)を取得
	DWORD size = MAKELONG((WORD)m_Image->GetWidth(), (WORD)m_Image->GetHeight());
	//m_Imageインスタンスを解放し、ポインターをゼロ初期化
	delete m_Image;
	m_Image = 0;
	return size;
}

//m_Selectedのファイルのフルパスファイル名を返す
char* CALBUM::GetFileName() {

	return m_Photo[m_Selected]->m_FName;
}

//選択イメージのコメントの取得
char* CALBUM::GetComment() {

	return m_Photo[m_Selected]->m_Comment;
}

//選択イメージのコメントの設定
void CALBUM::SetComment(char* cmt) {

	m_Photo[m_Selected]->SetComment(cmt);
}

//イメージの表示(x, yからw幅、h高さの矩形に原寸表示比率で表示)
bool CALBUM::ShowImg(int x, int y, int w, int h) {

	//エラー対応
	if(!m_Photo[m_Selected])
		return FALSE;
	//現在選択されている写真のファイル名をワイド文字にする
	mbstowcs(m_WFName, m_Photo[m_Selected]->m_FName, MAX_PATH);
	//現在選択されている写真のファイル名でイメージクラスインスタンスを生成
	m_Image = new (Image)(m_WFName, FALSE);
	//画像を回転させる−RotateFlipType：Rotate<A>Flip<B> <A:None, 90, 180, 270>, <B:X, Y, XY>
	m_Image->RotateFlip(m_RFType);
	//イメージの原寸を取得
	int imgw = m_Image->GetWidth();
	int imgh = m_Image->GetHeight();
	//m_Imageインスタンスの生成に失敗した場合imgw、imghはゼロになる
	if(!imgw || !imgh) {
		MessageBox(m_hWnd, "ファイルが読み込めませんでした。\nファイルパスを見直してください。",
					"エラー", MB_OK | MB_ICONERROR);
		//m_Imageインスタンスを解放
		delete m_Image;
		m_Image = 0;
		return FALSE;
	}
	//w、h内で原寸の縦横対比のイメージを中央に配置
	double asp1, asp2;
	asp1 = (double)imgh / (double)imgw;
	asp2 = (double)h / (double)w;
	if(asp1 > asp2) {
		imgh = h;
		imgw = h / asp1;
	}
	else {
		imgw = w;
		imgh = w * asp1;
	}
	x += (w - imgw) / 2;
	y += (h - imgh) / 2;
	//m_Imageインスタンスを表示
	m_pGraphics->DrawImage(m_Image, x, y, imgw, imgh);
	//m_Imageインスタンスを解放
	delete m_Image;
	m_Image = 0;
	//再描画を要求
	InvalidateRect(m_hWnd, NULL, TRUE);
	return TRUE;
}

//イメージの表示(第1引数TRUE左上、FALSE中央、第2引数TRUE原寸、FALSEサイズ調整)
bool CALBUM::ShowImg(bool lefttop, bool asis) {

	//エラー対応
	if(!m_Photo[m_Selected])
		return FALSE;
	//現在選択されている写真のファイル名をワイド文字にする
	mbstowcs(m_WFName, m_Photo[m_Selected]->m_FName, MAX_PATH);
	//現在選択されている写真のファイル名でイメージクラスインスタンスを生成
	m_Image = new (Image)(m_WFName);
	//画像を回転させる−RotateFlipType：Rotate<A>Flip<B> <A:None, 90, 180, 270>, <B:X, Y, XY>
	m_Image->RotateFlip(m_RFType);
	//イメージの原寸を取得
	int imgw = m_Image->GetWidth();
	int imgh = m_Image->GetHeight();
	//m_Imageインスタンスの生成に失敗した場合imgw、imghはゼロになる
	if(!imgw || !imgh) {
		MessageBox(m_hWnd, "ファイルが読み込めませんでした。\nファイルパスを見直してください。",
					"エラー", MB_OK | MB_ICONERROR);
		//m_Imageインスタンスを解放
		delete m_Image;
		m_Image = 0;
		return FALSE;
	}
	//サイズ調整の場合は比率に応じて調整
	if(!asis) {
		double asp1, asp2;
		asp1 = (double)imgh / (double)imgw;
		asp2 = (double)m_Height / (double)m_Width;
		if(asp1 > asp2) {
			imgh = m_Height;
			imgw = m_Height / asp1;
		}
		else {
			imgw = m_Width;
			imgh = m_Width * asp1;
		}
	}
	//左上に表示
	int x = m_X;
	int y = m_Y;
	//中央に表示
	if(!lefttop) {
		x += (m_Width - imgw) / 2;
		y += (m_Height - imgh) / 2;
	}
	//m_Imageインスタンスを表示
	m_pGraphics->DrawImage(m_Image, x, y, imgw, imgh);
	//m_Imageインスタンスを解放
	delete m_Image;
	m_Image = 0;
	//再描画を要求
	InvalidateRect(m_hWnd, NULL, TRUE);
	return TRUE;
}

//サムネイル表示(引数はサムネイルの幅・高さ、ページ)
bool CALBUM::ShowThumbnail(int size, int page = -1) {

	//エラー対応
	if(!m_NoI)
		return FALSE;
	//イメージ表示の為の画面数、横表示数、縦表示数設定
	int sel = m_Selected;						//m_Selectedを退避保存
	m_Column = m_Width / size;					//列数（1〜ベース）
	m_Row = m_Height / size;					//行数（1〜ベース）
	if(!(m_Column * m_Row)) {
		MessageBox(m_hWnd, "ウィンドウサイズが小さすぎます",
					"警告", MB_OK | MB_ICONERROR);
		return FALSE;							//"Div. by zero" error
	}
	m_Page = m_NoI / (m_Column * m_Row);		//ページ数（1〜ベース）
	if(page == -1)								//現在選択されている写真のページ
		m_CurPage = page = m_Selected / (m_Column * m_Row);
	else
		m_CurPage = page;
	if(page > m_Page)							//page引数が大きすぎて表示できない
		return FALSE;
	for(int i = 0; i < m_Row; i++) {
		for(int j = 0; j < m_Column; j++) {
			int n = page * (m_Column * m_Row) + i * m_Column + j;
			if(n < m_NoI) {
				Select(n);
				ShowImg(size * j, size * i + m_Y, size, size);
				if(n == sel) {
					Rect(size * j, size * i + m_Y, size, size);
				}
			}
			else {
				m_Selected = sel;
				return TRUE;
			}
		}
	}
	m_Selected = sel;
	return TRUE;
}

//サムネイル表示のページ当り表示写真数
int CALBUM::HowManyThumb(int size) {

	m_Column = m_Width / size;	//列数（1〜ベース）
	m_Row = m_Height / size;	//行数（1〜ベース）
	return (m_Column * m_Row);
}

//出力先ウィンドウのx, y座標からサムネイルの番号を得る
int CALBUM::GetWhichThumb(int size, int x, int y) {

	int col = x / size;
	int row = y / size;
	return (m_CurPage * (m_Column * m_Row) + row * m_Column + col);
}
