(2022) 공부 (Study)/게임 개발 (Development)

[Win32API] Bitmap에 대한 정리 (2) - bitColor 수정 및 출력

수낭 2022. 4. 19. 14:28

bitColor 24비트에서 32비트로 변경해서 출력하는 예제이다.

[중요 포인트]

bitmap은 stride라는 값을 지켜주어야 하는데, (꼭 bitmap만 그런 것은 아니다.)
stackOverFlow 등을 찾아보면 그래픽 메모리가 처리하기 편리한 값으로서
Multiple of Four, 즉, 각 행의 bytes 수가 4의 배수 값이어야 한다는 내용이 있었다.

이번에는 24bits의 bitColor 값을 가진 bitmap 이미지를 32bits로 수정했었기에,
32bits / 4 = 8its로 어차피 잘 나누어떨어져서 stride를 고려할 필요는 없었지만,
추후에 32bits를 24bits로 변경하게 된다면, stride가 4의 배수 값을 지켜줘야 하는 것을
잘 기억해야 할 것 같다.

참고로, 간단하게나마 32bits를 24bits의 이미지로 만들어보다가 시간 관계상 포기한 내용이 있었는데,
맨 마지막(3번째 코드블럭)에 코드를 일단은 남겨두겠다.

 

결과를 미리 보자면 아래와 같다.

 

bitColor 값이 24bits인 bitmap 이미지를 32bits변경한 직후의 모습이다.

 

24bits에 맞게 구성되어 있던 해당 bitmap의 저장된 RGB 형태의 메모리의 값들을
32bitsbitColor 체계에 맞게 ARGB의 형태로 재구성한 뒤, 재출력한 모습이다.

 

Win32API - Bitmap bitColor 24bits 이미지를 로드해서 32bits이미지로 출력하기
[주석 없이 정리한 형태]

#include <windows.h>
#include <stdlib.h>
#include <string.h>
#include <tchar.h>

#include <time.h>
#include <wchar.h>
#include <stdexcept>

static TCHAR szWindowClass[] = _T("DesktopApp");
static TCHAR szTitle[] = _T("Windows Desktop Guided Tour Application");

#define WIDTH	1000
#define HEIGHT	1000
HINSTANCE		hInst;
BITMAPINFO		bmpInfo;
LPDWORD			lpPixel;
HBITMAP			hBitmap;
HDC				hMemDC;

LPDWORD			lpDIBits;
LPBYTE			lpResultTemp;
int				tempReadSize;

void Convert_24to32()
{
	lpResultTemp = new BYTE[(tempReadSize / 3) * 4];

	int cnt = 0;
	LPBYTE lpByteTemp = (LPBYTE)lpDIBits;
	for (int i = 0; i < tempReadSize; i++)
	{
		if ((i + 1) % 3 == 0)
		{
			lpResultTemp[cnt++] = lpByteTemp[i];
			lpResultTemp[cnt++] = 0xff;
		}
		else
		{
			lpResultTemp[cnt++] = lpByteTemp[i];
		}
	}
}

void InputBitmapFile(HWND _hWnd, const WCHAR* _filename)
{
	HANDLE hFile;
	hFile = CreateFile(_filename, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
	if (hFile == INVALID_HANDLE_VALUE)
		throw std::runtime_error("File Error");

	BITMAPFILEHEADER bmFileHeader;
	DWORD dwReadBytes;
	if (false == ReadFile(hFile, &bmFileHeader, sizeof(BITMAPFILEHEADER), &dwReadBytes, NULL))
		throw std::runtime_error("File Error");

	int iBitmapInfoSize = bmFileHeader.bfOffBits - sizeof(BITMAPFILEHEADER);
	BITMAPINFO* pBitmapInfo = (BITMAPINFO*) new BYTE[iBitmapInfoSize];
	if (false == ReadFile(hFile, (LPVOID)pBitmapInfo, iBitmapInfoSize, &dwReadBytes, NULL))
		throw std::runtime_error("File Error");

	pBitmapInfo->bmiHeader.biBitCount = 32;
	//pBitmapInfo->bmiHeader = bmpInfo.bmiHeader;
	//pBitmapInfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
	//pBitmapInfo->bmiHeader.biWidth = WIDTH;
	//pBitmapInfo->bmiHeader.biHeight = HEIGHT;
	//pBitmapInfo->bmiHeader.biPlanes = 1;
	//pBitmapInfo->bmiHeader.biCompression = BI_RGB;

	HDC hdc = GetDC(_hWnd);
	hBitmap = CreateDIBSection(hdc, pBitmapInfo, DIB_RGB_COLORS, (void**)&lpDIBits, NULL, 0);
	hMemDC = CreateCompatibleDC(hdc);
	SelectObject(hMemDC, hBitmap);
	if (false == ReadFile(hFile, lpDIBits, pBitmapInfo->bmiHeader.biSizeImage, &dwReadBytes, NULL))
		throw std::runtime_error("File Error");

	tempReadSize = dwReadBytes;
	Convert_24to32();
	
	ReleaseDC(_hWnd, hdc);
	if (hFile)
		CloseHandle(hFile);
}

void DestroyDib()
{
	DeleteDC(hMemDC);
	DeleteObject(hBitmap);
}

void Play()
{

}

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int CALLBACK WinMain(
	_In_ HINSTANCE hInstance,
	_In_opt_ HINSTANCE hPrevInstance,
	_In_ LPSTR     lpCmdLine,
	_In_ int       nCmdShow
)
{
	srand((unsigned int(time(NULL))));
	WNDCLASSEX wcex;

	wcex.cbSize = sizeof(WNDCLASSEX);
	wcex.style = CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc = WndProc;
	wcex.cbClsExtra = 0;
	wcex.cbWndExtra = 0;
	wcex.hInstance = hInstance;
	wcex.hIcon = LoadIcon(hInstance, IDI_APPLICATION);
	wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
	wcex.lpszMenuName = NULL;
	wcex.lpszClassName = szWindowClass;
	wcex.hIconSm = LoadIcon(wcex.hInstance, IDI_APPLICATION);

	if (!RegisterClassEx(&wcex))
	{
		MessageBox(NULL,
			_T("Call to RegisterClassEx failed!"),
			_T("Windows Desktop Guided Tour"),
			NULL);

		return 1;
	}

	hInst = hInstance;

	HWND hWnd = CreateWindow(
		szWindowClass,
		szTitle,
		WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT, CW_USEDEFAULT,
		700, 500,
		NULL,
		NULL,
		hInstance,
		NULL);

	if (!hWnd)
	{
		MessageBox(NULL,
			_T("Call to CreateWindow failed!"),
			_T("Windows Desktop Guided Tour"),
			NULL);

		return 1;
	}

	ShowWindow(hWnd, nCmdShow);
	UpdateWindow(hWnd);

	MSG msg;
	while (true)
	{
		if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
		{
			if (msg.message == WM_QUIT)
				break;

			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		else
		{
			Play();
		}
	}

	return (int)msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	PAINTSTRUCT ps;
	HDC hdc;

	switch (message)
	{
	case WM_CREATE:
		SetTimer(hWnd, 1, 1, NULL);
		break;

	case WM_PAINT:
		hdc = BeginPaint(hWnd, &ps);
		BitBlt(hdc, 0, 0, WIDTH, HEIGHT, hMemDC, 0, 0, SRCCOPY);
		EndPaint(hWnd, &ps);
		break;

	case WM_MBUTTONDOWN:
		InputBitmapFile(hWnd, L"sample.bmp");
		//InputBitmapFile(hWnd, L"bigImage.bmp");

		InvalidateRgn(hWnd, NULL, false);
		break;

	case WM_LBUTTONDOWN:
		break;

	case WM_RBUTTONDOWN:
	{
		LPDWORD lpDwBuffer = (LPDWORD)lpResultTemp;
		for (int i = 0; i < tempReadSize / 4; i++)
		{
			lpDIBits[i] = lpDwBuffer[i];
		}

		InvalidateRgn(hWnd, NULL, false);
	}
		break;

	case WM_DESTROY:
		//KillTimer(hWnd, 1);
		DestroyDib();
		PostQuitMessage(0);
		break;
	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
		break;
	}
	return 0;
}

 

 

Win32API - Bitmap bitColor 24bits 이미지를 로드해서 32bits이미지로 출력하기
[주석이 있는 형태]

#include <windows.h>
#include <stdlib.h>
#include <string.h>
#include <tchar.h>

#include <time.h>
#include <wchar.h>
#include <stdexcept>

static TCHAR szWindowClass[] = _T("DesktopApp");
static TCHAR szTitle[] = _T("Windows Desktop Guided Tour Application");

// DibSection [1] - Noise Print
HINSTANCE hInst;
#define WIDTH	1000
#define HEIGHT	1000
BITMAPINFO		bmpInfo;
LPDWORD			lpPixel;
HBITMAP			hBitmap;
HDC				hMemDC;

// 추가한 코드
LPDWORD			lpDIBits;
BYTE*			lpResultTemp;		// (@) 계속 작업하는 코드	!!!
int				tempReadSize;
//LPDWORD lpdwDIBits;

// DibSection [2] - File I/O
void InputBitmapFile(HWND _hWnd, const WCHAR* _filename)
{
	HANDLE hFile;
	hFile = CreateFile(_filename, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
/*
	HANDLE WINAPI CreateFileW(
		_In_ LPCWSTR lpFileName,
		_In_ DWORD dwDesiredAccess,
		_In_ DWORD dwShareMode,
		_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
		_In_ DWORD dwCreationDisposition,
		_In_ DWORD dwFlagsAndAttributes,
		_In_opt_ HANDLE hTemplateFile
	);	
*/
	if (hFile == INVALID_HANDLE_VALUE)
		throw std::runtime_error("File Load Error");

	BITMAPFILEHEADER bmFileHeader;
	DWORD dwReadBytes;
/*
	typedef struct tagBITMAPFILEHEADER {
		WORD    bfType;
		DWORD   bfSize;
		WORD    bfReserved1;
		WORD    bfReserved2;
		DWORD   bfOffBits;
	} BITMAPFILEHEADER, FAR* LPBITMAPFILEHEADER, * PBITMAPFILEHEADER;
*/
	// 1. BITMAPFILEHEADER 구조체 만큼 파일에서 읽어서 bmFileHeader로 넣는다.
	if(false == ReadFile(hFile, &bmFileHeader, sizeof(BITMAPFILEHEADER), &dwReadBytes, NULL))
		throw std::runtime_error("File Load Error");
/*
	BOOL WINAPI ReadFile(
		_In_ HANDLE hFile,
		_Out_writes_bytes_to_opt_(nNumberOfBytesToRead, *lpNumberOfBytesRead) __out_data_source(FILE) LPVOID lpBuffer,
		_In_ DWORD nNumberOfBytesToRead,
		_Out_opt_ LPDWORD lpNumberOfBytesRead,
		_Inout_opt_ LPOVERLAPPED lpOverlapped
	);
*/
	// 2. DIB에 사용할 BITMAPINFO 구조체를 파일에서 읽어서 pBitmapInfo에 넣는다.
	// BITMAPINFO는 BITMAPINFOHEADER + RGBQUAD의 구조체
	int iBitmapInfoSize = bmFileHeader.bfOffBits - sizeof(BITMAPFILEHEADER);
	BITMAPINFO* pBitmapInfo = (BITMAPINFO*) new BYTE[iBitmapInfoSize];
/*
	typedef struct tagBITMAPINFO {
		BITMAPINFOHEADER    bmiHeader;
		RGBQUAD             bmiColors[1];
	} BITMAPINFO, FAR* LPBITMAPINFO, * PBITMAPINFO;
*/
/*
	typedef struct tagBITMAPINFOHEADER{
        DWORD      biSize;
        LONG       biWidth;
        LONG       biHeight;
        WORD       biPlanes;
        WORD       biBitCount;
        DWORD      biCompression;
        DWORD      biSizeImage;
        LONG       biXPelsPerMeter;
        LONG       biYPelsPerMeter;
        DWORD      biClrUsed;
        DWORD      biClrImportant;
	} BITMAPINFOHEADER, FAR *LPBITMAPINFOHEADER, *PBITMAPINFOHEADER;
*/
	if(false == ReadFile(hFile, (LPVOID)pBitmapInfo, iBitmapInfoSize, &dwReadBytes, NULL))
		throw std::runtime_error("File Load Error");

	// 3. Pixel Data를 받기 위한 메모리를 만들고 File에서 Pixel Data를 받아서 DIB에 복사한다.
	HDC hdc = GetDC(_hWnd);


	// ========== [*] 추가된 코드 ==========
	pBitmapInfo->bmiHeader.biBitCount = 32;
	//pBitmapInfo->bmiHeader = bmpInfo.bmiHeader;
	pBitmapInfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
	//pBitmapInfo->bmiHeader.biWidth = WIDTH;
	//pBitmapInfo->bmiHeader.biHeight = HEIGHT;
	pBitmapInfo->bmiHeader.biPlanes = 1;
	pBitmapInfo->bmiHeader.biCompression = BI_RGB; // BI_BITFIELDS
	// ====================================


	//HBITMAP hBitmap = CreateDIBSection(hdc, pBitmapInfo, DIB_RGB_COLORS, &lpDIBits, NULL, 0);
	hBitmap = CreateDIBSection(hdc, pBitmapInfo, DIB_RGB_COLORS, (void**)&lpDIBits, NULL, 0);
	hMemDC = CreateCompatibleDC(hdc);
	if(false == ReadFile(hFile, lpDIBits, pBitmapInfo->bmiHeader.biSizeImage, &dwReadBytes, NULL))
		throw std::runtime_error("File Load Error");

	// ========== [*] 추가된 코드 ==========
	tempReadSize = dwReadBytes;
	lpResultTemp = new BYTE[(dwReadBytes / 3) * 4];
	//LPDWORD lpDwTemp = new DWORD[dwReadBytes];

	LPBYTE lpByteTemp = (LPBYTE)lpDIBits;

	int _24BytesCnt = 0;
	int _32BytesCnt = 0;

	int cnt = 0;
	int cycleCnt = 1;
	for (int i = 0; i < dwReadBytes; i++)
	{
		_24BytesCnt++;

		if (cycleCnt == 3)
		{
			lpResultTemp[cnt++] = lpByteTemp[i];
			lpResultTemp[cnt++] = 0xff;
			cycleCnt = 0;
		}
		else
		{
			lpResultTemp[cnt++] = lpByteTemp[i];
		}
		cycleCnt++;
	}

	SelectObject(hMemDC, hBitmap);
	ReleaseDC(_hWnd, hdc);
	// 4. 반드시 파일을 열었으면 꼭 닫는다. (메모리 누수 주의)
	if (hFile)
		CloseHandle(hFile);
}

int InputFile(HWND _hWnd, const WCHAR* _filename)
{
	HDC hdc;
	HANDLE hFile;
	hdc = GetDC(_hWnd);
	hFile = CreateFile(_filename, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (INVALID_HANDLE_VALUE == hFile)
	{
		MessageBox(_hWnd, TEXT("파일을 열 수 없습니다."), TEXT("에러"), MB_OK);
		//throw std::runtime_error("File Load Error");
		return -1;
	}
	MessageBox(_hWnd, TEXT("파일을 성공적으로 열었습니다."), TEXT("성공"), MB_OK);
	CloseHandle(hFile);
	ReleaseDC(_hWnd, hdc);
	return 0;
}

void OutputBitmapFile(HWND _hWnd)
{
	HDC hdc = GetDC(_hWnd);
	hMemDC = CreateCompatibleDC(hdc);
	hBitmap = CreateCompatibleBitmap(hdc, 0, 0);
	SelectObject(hMemDC, hBitmap);
	ReleaseDC(_hWnd, hdc);
}

void SetNewDib(HWND _hWnd)
{
	/*bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
	bmpInfo.bmiHeader.biWidth = WIDTH;
	bmpInfo.bmiHeader.biHeight = HEIGHT;

	bmpInfo.bmiHeader.biPlanes = 1;
	bmpInfo.bmiHeader.biBitCount = 24;
	bmpInfo.bmiHeader.biCompression = BI_RGB;

	HDC hdc = GetDC(_hWnd);
	hBitmap = CreateDIBSection(hdc, &bmpInfo, DIB_RGB_COLORS, (void**)&lpPixel, NULL, 0);
	hMemDC = CreateCompatibleDC(hdc);
	SelectObject(hMemDC, hBitmap);
	ReleaseDC(_hWnd, hdc);*/
}

void SetDib(HWND _hWnd)
{
	bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
	bmpInfo.bmiHeader.biWidth = WIDTH;
	bmpInfo.bmiHeader.biHeight = HEIGHT;

	bmpInfo.bmiHeader.biPlanes = 1;
	bmpInfo.bmiHeader.biBitCount = 32;
	bmpInfo.bmiHeader.biCompression = BI_RGB;

	HDC hdc = GetDC(_hWnd);
	hBitmap = CreateDIBSection(hdc, &bmpInfo, DIB_RGB_COLORS, (void**)&lpPixel, NULL, 0);
	hMemDC = CreateCompatibleDC(hdc);
	SelectObject(hMemDC, hBitmap);
	ReleaseDC(_hWnd, hdc);
}

void DestroyDib()
{
	DeleteDC(hMemDC);
	DeleteObject(hBitmap);
}

void DrawDib(int _pX, int _pY)
{
	for (int y = HEIGHT - 10; y > 300; y--)
	{
		for (int x = 0; x < 700; x++)
		{			                  // A R G B
			lpPixel[(y * WIDTH) + x] = 0xff00ff00;
		}
	}
}

void SetPixel(int _pX, int _pY, DWORD _color)
{
	_pY = HEIGHT - _pY;
	lpPixel[(_pY * WIDTH) + _pX] = _color;
	//lpdwDIBits[(_pY * WIDTH) + _pX] = _color;
}

void Play()
{

}

void DrawNoisePattern()
{
	int r, g, b;
	int color = 0;

	for (int y = HEIGHT - 1; y > 0; y--)
	{
		for (int x = 0; x < 800; x++)
		{
			color = 0xff000000;

			r = rand() % 256; // 2^8 = 1111 1111 b = 0xFF
			g = rand() % 256;
			b = rand() % 256;

			color |= r << 16;
			color |= g << 8;
			color |= b;

			lpPixel[(y * WIDTH) + x] = color;
		}
	}
}

void ClearScreen()
{
	for (int y = HEIGHT - 1; y > 0; y--)
	{
		for (int x = 0; x < WIDTH; x++)
		{			                  // A R G B
			lpPixel[(y * WIDTH) + x] = 0xff000000;
		}
	}
}

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int CALLBACK WinMain(
	_In_ HINSTANCE hInstance,
	_In_opt_ HINSTANCE hPrevInstance,
	_In_ LPSTR     lpCmdLine,
	_In_ int       nCmdShow
)
{
	srand((unsigned int(time(NULL))));
	WNDCLASSEX wcex;

	wcex.cbSize = sizeof(WNDCLASSEX);
	wcex.style = CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc = WndProc;
	wcex.cbClsExtra = 0;
	wcex.cbWndExtra = 0;
	wcex.hInstance = hInstance;
	wcex.hIcon = LoadIcon(hInstance, IDI_APPLICATION);
	wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
	wcex.lpszMenuName = NULL;
	wcex.lpszClassName = szWindowClass;
	wcex.hIconSm = LoadIcon(wcex.hInstance, IDI_APPLICATION);

	if (!RegisterClassEx(&wcex))
	{
		MessageBox(NULL,
			_T("Call to RegisterClassEx failed!"),
			_T("Windows Desktop Guided Tour"),
			NULL);

		return 1;
	}

	hInst = hInstance;

	HWND hWnd = CreateWindow(
		szWindowClass,
		szTitle,
		WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT, CW_USEDEFAULT,
		1000, 1000,
		NULL,
		NULL,
		hInstance,
		NULL);

	if (!hWnd)
	{
		MessageBox(NULL,
			_T("Call to CreateWindow failed!"),
			_T("Windows Desktop Guided Tour"),
			NULL);

		return 1;
	}

	ShowWindow(hWnd, nCmdShow);
	UpdateWindow(hWnd);

	MSG msg;
	while (true)
	{
		if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
		{
			if (msg.message == WM_QUIT)
				break;

			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		else
		{
			Play();
		}
	}

	return (int)msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	PAINTSTRUCT ps;
	HDC hdc;

	static int nowX = 0, nowY = 0;
	static bool bLBtnDown = false;

	switch (message)
	{
	case WM_CREATE:
		SetTimer(hWnd, 1, 1, NULL);
		SetNewDib(hWnd);
		//SetDib(hWnd);
		break;

	case WM_PAINT:
		hdc = BeginPaint(hWnd, &ps);
		// . . .
		EndPaint(hWnd, &ps);
		break;

	case WM_MOUSEMOVE:
		if (bLBtnDown)
		{
			nowX = LOWORD(lParam);
			nowY = HIWORD(lParam);

			//DrawDib(pX, pY);
			//SetPixel(nowX, nowY, 0xff00ff00);
			//InvalidateRgn(hWnd, NULL, TRUE);
		}
		break;

	case WM_MBUTTONDOWN:
		//InputBitmapFile(hWnd, L"C:\\__win32api_fileIO_\\SuperMario.bmp");
		InputBitmapFile(hWnd, L"sample.bmp");
		//InputFile(hWnd, L"C:\\__win32api_fileIO_\\test.txt");
		//OutputBitmapFile(hWnd);
		break;

	case WM_LBUTTONDOWN:
		bLBtnDown = true;
		break;

	case WM_LBUTTONUP:
		bLBtnDown = false;
		break;

	case WM_RBUTTONDOWN:
	{
		LPDWORD lpDwBuffer = (LPDWORD)lpResultTemp;
		for (int i = 0; i < tempReadSize / 4; i++) // bitColorTempSize
		{
			lpDIBits[i] = lpDwBuffer[i];//lpTemp2[i];
		}
	}
		//lpDIBits = (LPDWORD)lpTemp;
		break;

	case WM_TIMER:
		//DrawDib();
		hdc = GetDC(hWnd);
		//DrawNoisePattern();
		BitBlt(hdc, 0, 0, WIDTH, HEIGHT, hMemDC, 0, 0, SRCCOPY);
		ReleaseDC(hWnd, hdc);
		//InvalidateRgn(hWnd, NULL, TRUE);
		break;

	case WM_DESTROY:
		DestroyDib();
		//KillTimer(hWnd, 1);
		PostQuitMessage(0);
		break;
	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
		break;
	}
	return 0;
}

 

위에서 언급했던,
간단하게나마 32bits를 24bits의 이미지로 만들어보다가 시간 관계상 포기한 내용이 있었는데,
이 아래에 기록해두기로 하겠다.

 

[32bits to 24bits 일부만 진행된 코드]

#include <windows.h>
#include <stdlib.h>
#include <string.h>
#include <tchar.h>

#include <time.h>
#include <wchar.h>
#include <stdexcept>

static TCHAR szWindowClass[] = _T("DesktopApp");
static TCHAR szTitle[] = _T("Windows Desktop Guided Tour Application");

// DibSection [1] - Noise Print
HINSTANCE hInst;
#define WIDTH	1000
#define HEIGHT	1000
BITMAPINFO		bmpInfo;
LPDWORD			lpPixel;
HBITMAP			hBitmap;
HDC				hMemDC;

// 추가한 코드
LPDWORD			lpDIBits;
BYTE*			lpTemp;				// (@) 계속 작업하는 코드	!!!
int				bitColorTempSize;
//LPDWORD lpdwDIBits;

// DibSection [2] - File I/O
void InputBitmapFile(HWND _hWnd, const WCHAR* _filename)
{
	HANDLE hFile;
	hFile = CreateFile(_filename, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
/*
	HANDLE WINAPI CreateFileW(
		_In_ LPCWSTR lpFileName,
		_In_ DWORD dwDesiredAccess,
		_In_ DWORD dwShareMode,
		_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
		_In_ DWORD dwCreationDisposition,
		_In_ DWORD dwFlagsAndAttributes,
		_In_opt_ HANDLE hTemplateFile
	);	
*/
	if (hFile == INVALID_HANDLE_VALUE)
		throw std::runtime_error("File Load Error");

	BITMAPFILEHEADER bmFileHeader;
	DWORD dwReadBytes;
/*
	typedef struct tagBITMAPFILEHEADER {
		WORD    bfType;
		DWORD   bfSize;
		WORD    bfReserved1;
		WORD    bfReserved2;
		DWORD   bfOffBits;
	} BITMAPFILEHEADER, FAR* LPBITMAPFILEHEADER, * PBITMAPFILEHEADER;
*/
	// 1. BITMAPFILEHEADER 구조체 만큼 파일에서 읽어서 bmFileHeader로 넣는다.
	ReadFile(hFile, &bmFileHeader, sizeof(BITMAPFILEHEADER), &dwReadBytes, NULL);
/*
	BOOL WINAPI ReadFile(
		_In_ HANDLE hFile,
		_Out_writes_bytes_to_opt_(nNumberOfBytesToRead, *lpNumberOfBytesRead) __out_data_source(FILE) LPVOID lpBuffer,
		_In_ DWORD nNumberOfBytesToRead,
		_Out_opt_ LPDWORD lpNumberOfBytesRead,
		_Inout_opt_ LPOVERLAPPED lpOverlapped
	);
*/
	// 2. DIB에 사용할 BITMAPINFO 구조체를 파일에서 읽어서 pBitmapInfo에 넣는다.
	// BITMAPINFO는 BITMAPINFOHEADER + RGBQUAD의 구조체
	int iBitmapInfoSize = bmFileHeader.bfOffBits - sizeof(BITMAPFILEHEADER);
	BITMAPINFO* pBitmapInfo = (BITMAPINFO*) new BYTE[iBitmapInfoSize];
/*
	typedef struct tagBITMAPINFO {
		BITMAPINFOHEADER    bmiHeader;
		RGBQUAD             bmiColors[1];
	} BITMAPINFO, FAR* LPBITMAPINFO, * PBITMAPINFO;
*/
/*
	typedef struct tagBITMAPINFOHEADER{
        DWORD      biSize;
        LONG       biWidth;
        LONG       biHeight;
        WORD       biPlanes;
        WORD       biBitCount;
        DWORD      biCompression;
        DWORD      biSizeImage;
        LONG       biXPelsPerMeter;
        LONG       biYPelsPerMeter;
        DWORD      biClrUsed;
        DWORD      biClrImportant;
	} BITMAPINFOHEADER, FAR *LPBITMAPINFOHEADER, *PBITMAPINFOHEADER;
*/
	ReadFile(hFile, (LPVOID)pBitmapInfo, iBitmapInfoSize, &dwReadBytes, NULL);

	// 3. Pixel Data를 받기 위한 메모리를 만들고 File에서 Pixel Data를 받아서 DIB에 복사한다.
	HDC hdc = GetDC(_hWnd);


	// ========== [*] 추가된 코드 ==========
	//pBitmapInfo->bmiHeader.biBitCount = 24;
	//pBitmapInfo->bmiHeader = bmpInfo.bmiHeader;
	pBitmapInfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
	//pBitmapInfo->bmiHeader.biWidth = WIDTH;
	//pBitmapInfo->bmiHeader.biHeight = HEIGHT;
	pBitmapInfo->bmiHeader.biPlanes = 1;
	pBitmapInfo->bmiHeader.biCompression = BI_RGB; // BI_BITFIELDS
	// ====================================


	//HBITMAP hBitmap = CreateDIBSection(hdc, pBitmapInfo, DIB_RGB_COLORS, &lpDIBits, NULL, 0);
	hBitmap = CreateDIBSection(hdc, pBitmapInfo, DIB_RGB_COLORS, (void**)&lpDIBits, NULL, 0);
	hMemDC = CreateCompatibleDC(hdc);
	ReadFile(hFile, lpDIBits, pBitmapInfo->bmiHeader.biSizeImage, &dwReadBytes, NULL);



	bitColorTempSize = dwReadBytes;


	// ========== [*] 추가된 코드 ==========
	//memset(lpDIBits, 0xffff0000, dwReadBytes);
	lpTemp = new BYTE[dwReadBytes];			// BYTE인데 개수 조절안해도 되는 이유 ???
	int _32BytesCnt = 0;
	int _24BytesCnt = 0;
	for (int i = 0; i < (dwReadBytes / 4); i++)
	{
		_32BytesCnt++;


		//lpTemp[i] = lpDIBits[i];
		//lpTemp = lpDIBits[0];
		lpDIBits[0] = 0x12345678; // [VALUE TEST]
		BYTE r_value = (lpDIBits[i] & 0x00ff0000) >> 16;	// 0x00ff0000	// 나중에 "리틀엔디안" 맞춰서 수정해야 할듯 ???		'A' R G B ? 	B G R 'A' ?
		BYTE g_value = (lpDIBits[i] & 0x0000ff00) >> 8;		// 0x0000ff00
		BYTE b_value = (lpDIBits[i] & 0x000000ff);			// 0x000000ff

		BYTE tempValue[3] = {};
		tempValue[0] = r_value;
		tempValue[1] = g_value;
		tempValue[2] = b_value;

		for (int c = 0; c < 3; c++)
		{
			lpTemp[(i * 3) + (3 - c - 1)] = tempValue[c];

			_24BytesCnt++;
		}

		/*BYTE b3Temp[3] = {};
		b3Temp[0] = r_value;
		b3Temp[1] = g_value;
		b3Temp[2] = b_value;

		lpTemp[i] = *b3Temp;*/
		int a = 1;
	}
	
	//================ [작업중] =====================================================================================
	// 읽은 bytes 수 동일
	_32BytesCnt = _32BytesCnt;
	_24BytesCnt = _24BytesCnt / 3;

	// 끝 지점 동일하게 도달
	lpDIBits + dwReadBytes;
	lpTemp + (dwReadBytes / 4) * 3;



	// 0xff ff ff ff
	// 1111 1111 1111 1111 1111 1111 1111 1111

	// ========== [*] 추가된 코드 ==========
	SelectObject(hMemDC, hBitmap);
	//lpPixel = (LPDWORD)lpDIBits;
	//lpdwDIBits = static_cast<LPDWORD>(lpDIBits);
	// ====================================


	ReleaseDC(_hWnd, hdc);
	// 4. 반드시 파일을 열었으면 꼭 닫는다. (메모리 누수 주의)
	if (hFile)
		CloseHandle(hFile);
}

int InputFile(HWND _hWnd, const WCHAR* _filename)
{
	HDC hdc;
	HANDLE hFile;
	hdc = GetDC(_hWnd);
	hFile = CreateFile(_filename, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (INVALID_HANDLE_VALUE == hFile)
	{
		MessageBox(_hWnd, TEXT("파일을 열 수 없습니다."), TEXT("에러"), MB_OK);
		//throw std::runtime_error("File Load Error");
		return -1;
	}
	MessageBox(_hWnd, TEXT("파일을 성공적으로 열었습니다."), TEXT("성공"), MB_OK);
	CloseHandle(hFile);
	ReleaseDC(_hWnd, hdc);
	return 0;
}

void OutputBitmapFile(HWND _hWnd)
{
	HDC hdc = GetDC(_hWnd);
	hMemDC = CreateCompatibleDC(hdc);
	hBitmap = CreateCompatibleBitmap(hdc, 0, 0);
	SelectObject(hMemDC, hBitmap);
	ReleaseDC(_hWnd, hdc);
}

void SetNewDib(HWND _hWnd)
{
	/*bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
	bmpInfo.bmiHeader.biWidth = WIDTH;
	bmpInfo.bmiHeader.biHeight = HEIGHT;

	bmpInfo.bmiHeader.biPlanes = 1;
	bmpInfo.bmiHeader.biBitCount = 24;
	bmpInfo.bmiHeader.biCompression = BI_RGB;

	HDC hdc = GetDC(_hWnd);
	hBitmap = CreateDIBSection(hdc, &bmpInfo, DIB_RGB_COLORS, (void**)&lpPixel, NULL, 0);
	hMemDC = CreateCompatibleDC(hdc);
	SelectObject(hMemDC, hBitmap);
	ReleaseDC(_hWnd, hdc);*/
}

void SetDib(HWND _hWnd)
{
	bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
	bmpInfo.bmiHeader.biWidth = WIDTH;
	bmpInfo.bmiHeader.biHeight = HEIGHT;

	bmpInfo.bmiHeader.biPlanes = 1;
	bmpInfo.bmiHeader.biBitCount = 32;
	bmpInfo.bmiHeader.biCompression = BI_RGB;

	HDC hdc = GetDC(_hWnd);
	hBitmap = CreateDIBSection(hdc, &bmpInfo, DIB_RGB_COLORS, (void**)&lpPixel, NULL, 0);
	hMemDC = CreateCompatibleDC(hdc);
	SelectObject(hMemDC, hBitmap);
	ReleaseDC(_hWnd, hdc);
}

void DestroyDib()
{
	DeleteDC(hMemDC);
	DeleteObject(hBitmap);
}

void DrawDib(int _pX, int _pY)
{
	for (int y = HEIGHT - 10; y > 300; y--)
	{
		for (int x = 0; x < 700; x++)
		{			                  // A R G B
			lpPixel[(y * WIDTH) + x] = 0xff00ff00;
		}
	}
}

void SetPixel(int _pX, int _pY, DWORD _color)
{
	_pY = HEIGHT - _pY;
	lpPixel[(_pY * WIDTH) + _pX] = _color;
	//lpdwDIBits[(_pY * WIDTH) + _pX] = _color;
}

void Play()
{

}

void DrawNoisePattern()
{
	int r, g, b;
	int color = 0;

	for (int y = HEIGHT - 1; y > 0; y--)
	{
		for (int x = 0; x < 800; x++)
		{
			color = 0xff000000;

			r = rand() % 256; // 2^8 = 1111 1111 b = 0xFF
			g = rand() % 256;
			b = rand() % 256;

			color |= r << 16;
			color |= g << 8;
			color |= b;

			lpPixel[(y * WIDTH) + x] = color;
		}
	}
}

void ClearScreen()
{
	for (int y = HEIGHT - 1; y > 0; y--)
	{
		for (int x = 0; x < WIDTH; x++)
		{			                  // A R G B
			lpPixel[(y * WIDTH) + x] = 0xff000000;
		}
	}
}

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int CALLBACK WinMain(
	_In_ HINSTANCE hInstance,
	_In_opt_ HINSTANCE hPrevInstance,
	_In_ LPSTR     lpCmdLine,
	_In_ int       nCmdShow
)
{
	srand((unsigned int(time(NULL))));
	WNDCLASSEX wcex;

	wcex.cbSize = sizeof(WNDCLASSEX);
	wcex.style = CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc = WndProc;
	wcex.cbClsExtra = 0;
	wcex.cbWndExtra = 0;
	wcex.hInstance = hInstance;
	wcex.hIcon = LoadIcon(hInstance, IDI_APPLICATION);
	wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
	wcex.lpszMenuName = NULL;
	wcex.lpszClassName = szWindowClass;
	wcex.hIconSm = LoadIcon(wcex.hInstance, IDI_APPLICATION);

	if (!RegisterClassEx(&wcex))
	{
		MessageBox(NULL,
			_T("Call to RegisterClassEx failed!"),
			_T("Windows Desktop Guided Tour"),
			NULL);

		return 1;
	}

	hInst = hInstance;

	HWND hWnd = CreateWindow(
		szWindowClass,
		szTitle,
		WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT, CW_USEDEFAULT,
		1000, 1000,
		NULL,
		NULL,
		hInstance,
		NULL);

	if (!hWnd)
	{
		MessageBox(NULL,
			_T("Call to CreateWindow failed!"),
			_T("Windows Desktop Guided Tour"),
			NULL);

		return 1;
	}

	ShowWindow(hWnd, nCmdShow);
	UpdateWindow(hWnd);

	MSG msg;
	while (true)
	{
		if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
		{
			if (msg.message == WM_QUIT)
				break;

			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		else
		{
			Play();
		}
	}

	return (int)msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	PAINTSTRUCT ps;
	HDC hdc;

	static int nowX = 0, nowY = 0;
	static bool bLBtnDown = false;

	switch (message)
	{
	case WM_CREATE:
		SetTimer(hWnd, 1, 1, NULL);
		SetNewDib(hWnd);
		//SetDib(hWnd);
		break;

	case WM_PAINT:
		hdc = BeginPaint(hWnd, &ps);
		// . . .
		EndPaint(hWnd, &ps);
		break;

	case WM_MOUSEMOVE:
		if (bLBtnDown)
		{
			nowX = LOWORD(lParam);
			nowY = HIWORD(lParam);

			//DrawDib(pX, pY);
			//SetPixel(nowX, nowY, 0xff00ff00);
			//InvalidateRgn(hWnd, NULL, TRUE);
		}
		break;

	case WM_MBUTTONDOWN:
		//InputBitmapFile(hWnd, L"C:\\__win32api_fileIO_\\SuperMario.bmp");
		InputBitmapFile(hWnd, L"sample.bmp");
		//InputFile(hWnd, L"C:\\__win32api_fileIO_\\test.txt");
		//OutputBitmapFile(hWnd);
		break;

	case WM_LBUTTONDOWN:
		bLBtnDown = true;
		break;

	case WM_LBUTTONUP:
		bLBtnDown = false;
		break;

	case WM_RBUTTONDOWN:
	{
		LPDWORD lpTemp2 = (LPDWORD)lpTemp;
		for (int i = 0; i < 900000; i++) // bitColorTempSize
		{
			lpDIBits[i] = lpTemp2[i];//lpTemp2[i];
		}
	}
		//lpDIBits = (LPDWORD)lpTemp;
		break;

	case WM_TIMER:
		//DrawDib();
		hdc = GetDC(hWnd);
		//DrawNoisePattern();
		BitBlt(hdc, 0, 0, WIDTH, HEIGHT, hMemDC, 0, 0, SRCCOPY);
		ReleaseDC(hWnd, hdc);
		//InvalidateRgn(hWnd, NULL, TRUE);
		break;

	case WM_DESTROY:
		DestroyDib();
		//KillTimer(hWnd, 1);
		PostQuitMessage(0);
		break;
	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
		break;
	}
	return 0;
}