//////////////////////////////////////
// ECCSkelton
// ビットマップクラスCBMP定義ファイル
// Copyright (c) August, 2022
//        By Ysama
//////////////////////////////////////

class CBMP
{
public:
	HWND				m_hWnd;			//ビットマップを使用しているウィンドウ
	HBITMAP				m_hBMP;			//ビットマップのハンドル
	int					m_x;			//最新のビットマップx座標を記録
	int					m_y;			//最新のビットマップy座標を記録
	BITMAP				m_Bmp;			//m_hBMPのBITMAP構造体
	WORD 				m_ClrBits;		//1, 4, 8 ,16, 24又は32のカラービット数
	PBITMAPINFO 		m_PBmi;			//ビットマップ情報構造体ポインター
	PBITMAPFILEHEADER	m_PBmf;			//ビットマップファイル構造体ポインター

	CBMP();								//コンストラクター（１）初期化のみ
	CBMP(HBITMAP);						//コンストラクター（２）ハンドルによる
	CBMP(HWND, LPCTSTR, bool);			//コンストラクター（３）LoadImageによる
	~CBMP();							//デストラクター
	HBITMAP Handle() {return m_hBMP;}	//ハンドルを返す
	int Width() {return m_Bmp.bmWidth;}	//幅を返す
	int Height() {return m_Bmp.bmHeight;}//高さを返す
	int CBits() {return m_ClrBits;}		//カラービットを返す
	//ビットマップを取り込む LoadImag()による（既定値はID、TRUEならファイルから）
	bool LoadBMP(HWND, LPCWSTR, bool);
	//ファイルへビットマップを書き込む
	bool SaveBMP(LPWSTR, int);
	//画面からビットマップを取り込む
	bool GetBMP(HWND, HDC, int, int, int, int);
	//画面にビットマップを表示（１）
	bool PutBMP(HWND, HDC, int, int, int);
	//画面にビットマップを表示（２）
	bool StretchBMP(HWND, HDC, int, int, int, int, int);
	//既存のビットマップがあるかチェックして、あればメンバーを初期化する
	void CheckandInit();
	//登録されたビットマップ（DDB）のビットマップ情報を作成する
	bool GetDDBInfo();
	//ビットマップファイルからビットマップ情報を取得する
	bool GetDIBInfo(LPCWSTR);
	//クリップボードからビットマップハンドルを取得
	bool LoadFromClipboard();
	//保持しているビットマップをクリップボードにコピー
	void SaveToClipboard();
};

//コンストラクター（１）
CBMP::CBMP() {

	m_hBMP = 0;
	m_PBmf = NULL;
	m_PBmi = NULL;
}

//コンストラクター（２）
CBMP::CBMP(HBITMAP hBMP) {

	m_hBMP = hBMP;
	GetDDBInfo();					//表示されたビットマップの情報取得
}

//コンストラクター（３）
CBMP::CBMP(HWND hWnd, LPCWSTR BMPName, bool flag = FALSE) {

	LoadBMP(hWnd, BMPName, flag);
}

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

	CheckandInit();
}

//ビットマップID(flag == FALSE)又はファイル(flag == TRUE)からビットマップと属性を読む
bool CBMP::LoadBMP(HWND hWnd, LPCWSTR BMPName, bool flag = FALSE) {

	//新規、変更の処理
	m_hWnd = hWnd;
	CheckandInit();
	UINT fLoad;
	if(flag)
		fLoad = LR_DEFAULTCOLOR | LR_LOADFROMFILE;
	else
		fLoad = LR_DEFAULTCOLOR;
	//ビットマップの読み込み
	m_hBMP = (HBITMAP)LoadImageW((HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE),
						BMPName,		//リソース名かファイル名
						IMAGE_BITMAP,	//ビットマップのみ
						0, 0,			//x, y軸サイズ・リソースサイズに設定
					    fLoad);
	//ビットマップを読み込めたか
	if(!m_hBMP) {
		MessageBoxW(NULL, L"ビットマップの作成に失敗しました",
					L"エラー!", MB_OK | MB_ICONSTOP);
		return FALSE;
	}
	if(flag)		//ファイルから
		GetDIBInfo(BMPName);
	else			//Device Contextから
		GetDDBInfo();
	return TRUE;
}

//m_PBmf、 m_PBmiからビットマップデータをファイルに書き込む
bool CBMP::SaveBMP(LPWSTR pszFile, int CBit = 0) { 

	HANDLE hFile = NULL;	// フィルハンドル
	LPBYTE lpBits = NULL;	// メモリーへのポインター
	DWORD dwTmp;			//WriteFile()関数の書込バイト数用
	DWORD BMPSize;			//ビットマップサイズ計算用
	int nSuccess;			//GetDIBits関数の成功判定フラグ
	HDC hDC, hMem;			//デスクトップDCとコンパチビットマップDC
	BITMAP Bmp;				//書込み用
	PBITMAPINFO PBmi;		//書込み用
	PBITMAPFILEHEADER PBmf;	//書込み用

	if(!CBit)	CBit = m_ClrBits;	//CBitが0の場合は現在のカラービットとする
	//PBITMAPINFO PBmiの為のメモリーを確保
	if (CBit <= 8)
		PBmi = (PBITMAPINFO)LocalAlloc(LPTR,	//固定メモリー・ゼロで初期化
				sizeof(BITMAPINFOHEADER) +
				sizeof(RGBQUAD) * (1 << CBit));
	//24bitフォーマットは勿論16, 32bitでBI_RGBの場合にはRGBQUADがない
	else
		PBmi = (PBITMAPINFO)LocalAlloc(LPTR,
				sizeof(BITMAPINFOHEADER));
	if(!PBmi) {	//メモリー確保失敗
			MessageBoxW(NULL, L"PBmiのローカルメモリー確保に失敗しました",
						L"エラー!", MB_OK | MB_ICONSTOP);
			return FALSE;
	}
	//デスティネーションのビットマップ情報構造体を初期化
	GetObject(m_hBMP, sizeof(BITMAP), &Bmp);
	//BITMAPINFOの準備
	PBmi->bmiHeader.biSize		= sizeof(BITMAPINFOHEADER);
	PBmi->bmiHeader.biWidth		= m_Bmp.bmWidth;
	PBmi->bmiHeader.biHeight	= m_Bmp.bmHeight;
	PBmi->bmiHeader.biPlanes	= 1;
	PBmi->bmiHeader.biBitCount	= CBit;
	PBmi->bmiHeader.biCompression	= BI_RGB;		//0x0
	PBmi->bmiHeader.biXPelsPerMeter	= 0;
	PBmi->bmiHeader.biYPelsPerMeter	= 0;
	PBmi->bmiHeader.biClrUsed		= 0;
	PBmi->bmiHeader.biClrImportant	= 0;
	//ビットマップデータサイズを算出
	BMPSize = ((CBit * PBmi->bmiHeader.biWidth + 31) / 32) * 4
				* abs(PBmi->bmiHeader.biHeight);
	PBmi->bmiHeader.biSizeImage	= BMPSize;
	//BITMAPFILEHEADERの準備
	PBmf = (PBITMAPFILEHEADER)LocalAlloc(LPTR,
			sizeof(BITMAPFILEHEADER));
	if(!PBmf) {	//メモリー確保失敗
			MessageBoxW(NULL, L"PBmfのローカルメモリー確保に失敗しました",
						L"エラー!", MB_OK | MB_ICONSTOP);
			LocalFree(PBmi);
			return FALSE;
	}
	PBmf->bfType = 0x4D42;
	PBmf->bfReserved1 = m_PBmf->bfReserved2 = 0;
	if(CBit <= 8)	//BITMAPFILEHEADER + BITMAPINFOHEADER + RGBQUAD
		PBmf->bfOffBits = 54 + (4 * (1 << CBit));
	else			//BITMAPFILEHEADER + BITMAPINFOHEADER
		PBmf->bfOffBits = 54;
	PBmf->bfSize = BMPSize + PBmf->bfOffBits;
	lpBits = (LPBYTE)GlobalAlloc(GMEM_FIXED, BMPSize);
	if(!lpBits) {
		MessageBoxW(NULL, L"グローバルメモリーが確保できませんでした",
					L"エラー", MB_OK | MB_ICONSTOP);
		LocalFree(PBmi);
		LocalFree(PBmf);
		return FALSE;
	}
	//ここで色数を変更
	hDC = GetDC(0);					//デスクトップのデバイスコンテキスト
	hMem = CreateCompatibleDC(hDC);	//そのコンパチDC
	SelectObject(hMem, m_hBMP);		//ビットマップを読ませる
	//ビット配列をGetDIBits関数でグローバルメモリーに取り出す
	nSuccess = GetDIBits(hMem, m_hBMP, 0, Bmp.bmHeight,
							lpBits, PBmi, DIB_RGB_COLORS);
	DeleteDC(hMem) ;				//コンパチDCを削除
	ReleaseDC(0, hDC);				//デスクトップのデバイスコンテキストを開放
	if(!nSuccess) {	//色数変更失敗
		MessageBoxW(NULL, L"色数変更に失敗しました",
					L"エラー", MB_OK | MB_ICONSTOP);
		LocalFree(PBmi);
		LocalFree(PBmf);
		GlobalFree((HGLOBAL)lpBits);
		return FALSE;
	}
	//ファイル作成
	//書き込み用ファイルを開く
	hFile = CreateFileW(pszFile,		//ファイル名
					GENERIC_WRITE,
					(DWORD)0,
					(LPSECURITY_ATTRIBUTES)NULL,
					CREATE_ALWAYS,
					FILE_ATTRIBUTE_NORMAL,
					(HANDLE)NULL);
	if(hFile == INVALID_HANDLE_VALUE) {
		MessageBoxW(NULL, L"ファイルを開くことができませんでした",
					L"エラー", MB_OK | MB_ICONSTOP);
		LocalFree(PBmi);
		LocalFree(PBmf);
		GlobalFree((HGLOBAL)lpBits);
		return FALSE;
	}
	//各ヘッダーの書きこみ
	//BITMAPFILEHEADERをファイルへ書き込む
	if(!WriteFile(hFile, PBmf, sizeof(BITMAPFILEHEADER),
					(LPDWORD)&dwTmp, (LPOVERLAPPED)NULL)) {
		MessageBoxW(NULL, L"ビットマップファイルヘッダーを書き込むことができませんでした",
					L"エラー", MB_OK | MB_ICONSTOP);
		LocalFree(PBmi);
		LocalFree(PBmf);
		GlobalFree((HGLOBAL)lpBits);
		CloseHandle(hFile);
		return FALSE;
	}
	//BITMAPINFOHEADERとRGBQUADをファイルへ書き込む
	if(!WriteFile(hFile, PBmi, PBmf->bfOffBits - 14,	//BITMAPINFOHEADER + RGBQUAD
					(LPDWORD)&dwTmp, (LPOVERLAPPED) NULL)) {
		MessageBoxW(NULL, L"ビットマップ情報とカラーテーブルを書き込むことができませんでした",
					L"エラー", MB_OK | MB_ICONSTOP);
		LocalFree(PBmi);
		LocalFree(PBmf);
		GlobalFree((HGLOBAL)lpBits);
		CloseHandle(hFile);
		return FALSE;
	}
	//ビットマップビット配列をファイルへ書き込む
	if(!WriteFile(hFile, (LPSTR)lpBits, BMPSize,
					(LPDWORD)&dwTmp, (LPOVERLAPPED)NULL)) {
		MessageBoxW(NULL, L"ビット配列を書き込むことができませんでした",
					L"エラー", MB_OK | MB_ICONSTOP);
		LocalFree(PBmi);
		LocalFree(PBmf);
		GlobalFree((HGLOBAL)lpBits);
		CloseHandle(hFile);
		return FALSE;
	}
	//x.BMPファイルをクローズする
	if(!CloseHandle(hFile))
		MessageBoxW(NULL, L"ファイルをクローズできませんでした",
					L"エラー", MB_OK | MB_ICONSTOP);
	//メモリーを解放
	LocalFree(PBmi);
	LocalFree(PBmf);
	GlobalFree((HGLOBAL)lpBits);
	return TRUE;
}

//画面からビットマップを取り込む
bool CBMP::GetBMP(HWND hWnd, HDC SourceDC, int x, int y, int w, int h) {

	CheckandInit();
	m_hWnd = hWnd;
	HDC hDC = GetDC(hWnd);							//ウィンドウDCを取得
	HDC memDC = CreateCompatibleDC(hDC);			//コンパチDCを作成
	m_hBMP = CreateCompatibleBitmap(hDC, w, h);		//コンパチビットマップを作成
	SelectObject(memDC, m_hBMP);					//ビットマップを選択
	StretchBlt(memDC, 0, 0, w, h, SourceDC, x, y, w, h, SRCCOPY);
	ReleaseDC(hWnd, hDC);							//ウィンドウDCを開放
	DeleteObject(memDC);							//コンパチDCを削除
	return GetDDBInfo();							//新ビットマップの情報取得
}

//画面にビットマップを表示（１）
bool CBMP::PutBMP(HWND hWnd, HDC TargetDC, int x, int y, int ro = 0) {

	if(!m_hBMP)				//既に読みこまれたビットマップが無ければ
		return FALSE;		//偽を返す
	DWORD dwRop;
	switch(ro) {
		case 1:		dwRop = SRCAND;		break;	//And
		case 2:		dwRop = SRCPAINT;	break;	//Or
		case 3:		dwRop = SRCINVERT;	break;	//Xor
		default:	dwRop = SRCCOPY;	break;	//Pset
	}
	m_hWnd = hWnd;
	HDC hDC = GetDC(hWnd);							//ウィンドウDCを取得
	HDC memDC = CreateCompatibleDC(hDC);			//コンパチDCを作成
	HGDIOBJ oldbm = SelectObject(memDC, m_hBMP);	//ビットマップを選択
	bool success = BitBlt(TargetDC, m_x = x, m_y = y,
					m_Bmp.bmWidth, m_Bmp.bmHeight, memDC, 0, 0, dwRop);
	ReleaseDC(hWnd, hDC);							//ウィンドウDCを開放
	SelectObject(memDC, oldbm);						//元のオブジェクトに戻す
	DeleteObject(memDC);							//コンパチDCを削除
	return success;
}

//画面にビットマップを表示（２）
bool CBMP::StretchBMP(HWND hWnd, HDC TargetDC, int x, int y, int w, int h, int ro = 0) {

	if(!m_hBMP)				//既に読みこまれたビットマップが無ければ
		return FALSE;		//偽を返す
	DWORD dwRop;
	switch(ro) {
		case 1:		dwRop = SRCAND;		break;	//And
		case 2:		dwRop = SRCPAINT;	break;	//Or
		case 3:		dwRop = SRCINVERT;	break;	//Xor
		default:	dwRop = SRCCOPY;	break;	//Pset
	}
	m_hWnd = hWnd;
	HDC hDC = GetDC(hWnd);							//ウィンドウDCを取得
	HDC memDC = CreateCompatibleDC(hDC);			//コンパチDCを作成
	HGDIOBJ oldbm = SelectObject(memDC, m_hBMP);	//ビットマップを選択
	bool success = StretchBlt(TargetDC, m_x = x, m_y = y, w, h,
					memDC, 0, 0, m_Bmp.bmWidth, m_Bmp.bmHeight, dwRop);
	ReleaseDC(hWnd, hDC);							//ウィンドウDCを開放
	SelectObject(memDC, oldbm);						//元のオブジェクトに戻す
	DeleteObject(memDC);							//コンパチDCを削除
	return success;
}

//既存のビットマップがあるかチェックして、あればメンバーを初期化する
void CBMP::CheckandInit() {

	if(m_hBMP) {				//既存のビットマップオブジェクトの削除
		DeleteObject(m_hBMP);
		m_hBMP = 0;
	}
	if(m_PBmf) {	//m_PBmfにメモリーが与えられていた場合はこれを開放する
		if(LocalFree(m_PBmf)) {	//エラーでなければNULL
			MessageBoxW(NULL, L"m_PBmfのローカルメモリーの削除に失敗しました",
						L"エラー!", MB_OK | MB_ICONSTOP);
		}
		else {
			m_PBmf = NULL;
		}
	}
	if(m_PBmi) {	//m_PBmiにメモリーが与えられていた場合はこれを開放する
		if(LocalFree(m_PBmi)) {	//エラーでなければNULL
			MessageBoxW(NULL, L"m_PBmiのローカルメモリーの削除に失敗しました",
						L"エラー!", MB_OK | MB_ICONSTOP);
		}
		else {
			m_PBmi = NULL;
		}
	}
}

//登録されたビットマップ（DDB）のビットマップ情報を作成する
bool CBMP::GetDDBInfo() {

	if(!m_hBMP)					//既に読みこまれたビットマップが無ければ
		return (PBITMAPINFO)0;	//失敗サインの0を返す
	//ビットマップのカラーフォーマット、縦、横サイズを取得 
	if (!GetObject(m_hBMP, sizeof(BITMAP), (LPSTR)&m_Bmp)) {
		MessageBoxW(NULL, L"ビットマップ情報が読めませんでした",
					L"エラー", MB_OK | MB_ICONSTOP);
		return FALSE;			//失敗サインを返す
	}
	//カラーフォーマットを計算(bmPlanes * bmBitsPixel)
	m_ClrBits = (WORD)(m_Bmp.bmPlanes * m_Bmp.bmBitsPixel);
	//PBITMAPINFO m_PBmiの為のメモリーを確保(1, 4, 8bit)
	if (m_ClrBits <= 8)
		m_PBmi = (PBITMAPINFO)LocalAlloc(LPTR,	//固定メモリー・ゼロで初期化
				sizeof(BITMAPINFOHEADER) +
				sizeof(RGBQUAD) * (1 << m_ClrBits));
	//24bitフォーマットは勿論16, 32bitでBI_RGBの場合にはRGBQUADがない
	else
		m_PBmi = (PBITMAPINFO)LocalAlloc(LPTR,
				sizeof(BITMAPINFOHEADER));
	//BITMAPINFO構造体の要素を入れる
	m_PBmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
	m_PBmi->bmiHeader.biWidth = m_Bmp.bmWidth;
	m_PBmi->bmiHeader.biHeight = m_Bmp.bmHeight;
	m_PBmi->bmiHeader.biPlanes = m_Bmp.bmPlanes;
	m_PBmi->bmiHeader.biBitCount = m_Bmp.bmBitsPixel;
	if (m_ClrBits <= 8)
		m_PBmi->bmiHeader.biClrUsed = (1 << m_ClrBits);
	else	//フルカラー時には0
		m_PBmi->bmiHeader.biClrUsed = 0;
	//ビットマップが圧縮されていなかったならばBI_RGB(0)フラグを立てる
	m_PBmi->bmiHeader.biCompression = BI_RGB;
	//色配列のバイト数を計算してbiSizeImageにしまう
	m_PBmi->bmiHeader.biSizeImage = ((((m_PBmi->bmiHeader.biWidth * m_ClrBits) +
									31) & ~31) >> 3)
									* m_PBmi->bmiHeader.biHeight;
	//biClrImportantを0にする（全ての色が重要、を意味する）
	m_PBmi->bmiHeader.biClrImportant = 0;
	//ビットマップファイルヘッダーを作成する
	m_PBmf = (PBITMAPFILEHEADER)LocalAlloc(LPTR,
				sizeof(BITMAPFILEHEADER));
	m_PBmf->bfType = 0x4d42;	//0x42と0x4dは"BM"でビットマップの意味
	//ファイルサイズの計算（BITMAPINFOHEADERへのポインターを作成）
	PBITMAPINFOHEADER PBmih = (PBITMAPINFOHEADER)m_PBmi;
	m_PBmf->bfSize = (DWORD) (sizeof(BITMAPFILEHEADER) +
							PBmih->biSize + PBmih->biClrUsed
							* sizeof(RGBQUAD) + PBmih->biSizeImage);
	m_PBmf->bfReserved1 = m_PBmf->bfReserved2 = 0;
	//カラー情報までのオフセットを計算
	m_PBmf->bfOffBits = (DWORD) sizeof(BITMAPFILEHEADER) +
								PBmih->biSize + PBmih->biClrUsed
								* sizeof (RGBQUAD);
	return TRUE;
}

//ビットマップファイルからビットマップ情報を取得する
bool CBMP::GetDIBInfo(LPCWSTR BMPName) {

	HANDLE hFile;
	DWORD dwResult;
	//CreateFile関数でビットマップファイルをオープン
	hFile = CreateFileW(BMPName, GENERIC_READ, 0, NULL, OPEN_EXISTING,
					FILE_ATTRIBUTE_NORMAL, NULL);
	if (hFile == INVALID_HANDLE_VALUE) {
		return FALSE;
	}
	//ファイルからBITMAPFILEHEADERのバッファを作成
	m_PBmf = (PBITMAPFILEHEADER)LocalAlloc(LPTR, sizeof(BITMAPFILEHEADER));
	//ReadFile関数でビットマップのBITMAPFILEHEADERの部分を読み出し
	ReadFile(hFile, (LPBITMAPFILEHEADER)m_PBmf, sizeof(BITMAPFILEHEADER),
			&dwResult, NULL);
	//bfTypeが0x4d42であることを確認（0x4d='M' 0x42='B'） 
	if(LOBYTE(m_PBmf->bfType) != 0x42 || HIBYTE(m_PBmf->bfType) != 0x4D) {
		return FALSE;
	}
	//ReadFile関数でビットマップのBITMAPINFOHEADER部分を読み出し 
	m_PBmi = (PBITMAPINFO)LocalAlloc(LPTR, sizeof(BITMAPINFOHEADER));
	ReadFile(hFile, (LPBITMAPINFOHEADER)m_PBmi, sizeof(BITMAPINFOHEADER),
			&dwResult, NULL);
	PBITMAPINFOHEADER PBmih = (PBITMAPINFOHEADER)m_PBmi;
	m_ClrBits = (WORD)(PBmih->biPlanes * PBmih->biBitCount);
	//ファイルのクローズ
	CloseHandle(hFile);
	//最後にm_Bmpにデータをセット（これはDDBデータなのでファイルデータと異なる）
	GetObject(m_hBMP, sizeof(BITMAP), &m_Bmp);
	return TRUE;
}

//クリップボードからビットマップハンドルを取得
bool CBMP::LoadFromClipboard() {

	if(IsClipboardFormatAvailable(CF_BITMAP)) {
		OpenClipboard(NULL);
		HBITMAP temp = (HBITMAP)GetClipboardData(CF_BITMAP);
		CloseClipboard();
		if(!temp) {
			//DIBSECTIONを作成してハンドルを渡す
			CheckandInit();
			m_hBMP = (HBITMAP)CopyImage(temp, IMAGE_BITMAP, 0, 0,
								LR_COPYRETURNORG | LR_COPYDELETEORG);
			GetDDBInfo();
			return TRUE;
		}
		else
			return FALSE;
	}
	return FALSE;
}

//保持しているビットマップをクリップボードにコピー
void CBMP::SaveToClipboard() {

	if(m_hBMP) {
		if(!OpenClipboard(NULL)) {
			MessageBoxW(m_hWnd, L"クリップボードを開けませんでした", L"エラー", MB_OK | MB_ICONSTOP);
			return;
		}
		else
			EmptyClipboard();
		SetClipboardData(CF_BITMAP, (HBITMAP)CopyImage(m_hBMP, IMAGE_BITMAP, 0, 0, LR_COPYRETURNORG));
		CloseClipboard();	
	}
}
