이미지 출력

김주현·2021년 7월 30일
0

Win API

목록 보기
5/14

이미지 출력을 공부해 사각형 대신 플레이어 캐릭터를 출력해보겠습니다.

이미지 출력

이미지를 출력하기위해선 우선 프로그램 안에 이미지를 넣어줘야합니다.

그리고 이 이미지파일들은 플레이어,몬스터,총알 등이 직접 가지고있는것이아니라 비트맵클래스를 만들어 그림을 DC에 저장하고 매니저 클래스에 맵 자료구조에 저장하여 사용합니다.

맵 자료구조를 사용하는 이유는 문자열을 이용한 키값으로 쉽게 이미지를 찾을수있기때문입니다 ( Ex. 벡터를 사용한다면 인덱스는 숫자를 이용해 접근해야 할것이고 vec[2] 이는 가독성이 떨어짐 Enum을 이용하여 이를 보완해줄순있지만 이미지는 어차피 한번 삽입해두면 거의 수정할일이없기때문에 맵을 쓰는것이 여러방면에서 효율적)

비트맵 클래스

우선 이미지를 불러와 저장하기위해 비트맵을 사용할것입니다 현재는 그래픽카드를 사용하지않기때문에 비트맵을 통해 이미지를 사용하는것이 효율적입니다.

MyBitmap.h

#pragma once
class CMyBitmap
{
public:
	CMyBitmap();
	~CMyBitmap();
public:
	HDC Get_MemDC() { return m_hMemDC;  }
	// wchar_t*
	void Insert_Bitmap(const TCHAR* pFilePath); 
	void Release_Bitmap(); 
private:
	HDC m_hMemDC; // 다른 도화지에 그림 그려둘 것. 
	HBITMAP m_hBitmap; // 실질적으로 그림에 대한 정보를 가지고 있을놈. 
	HBITMAP m_hOldBitmap; // 추후 지우기 위해 사용할 놈. 
	// HFONT, HBRUSH, HPEN, HBITMAP ... 
	// hDC - 
	// hMemDC - m_hBitmap 
 CMyBitMap 클래스입니다 
 
 멤버변수 HDC m_hMemDC는 현재 보고있는 창이 아닌 다른 창에 그리기위해 DC를 받아와 
 호환형으로 바꿔준뒤 사용하기위한 변수입니다.
 
 HBITMAP m_hBitmap,HBITMAP m_hOldBitmap 는 각각 현재 그림 이전 그림을 저장하기위한 비트맵입니다 
 이전 그림을 굳이 저장해주는 이유는 비트맵을 사용한후 지울때 현재 사용하고있는 비트맵은 지
 울수없기때문에 이전 비트맵을 버리지않고 멤버변수로 가지고있다가 비트맵을 삭제하기전 교
 체해 삭제해주기 위함입니다.

HDC Get_MemDC() { return m_hMemDC; } 를 통해 그림이 그려진 도화지를 반환하고

void Insert_Bitmap(const TCHAR* pFilePath)를 이용해 파일경로를 받아 그림파일 을 넣어줄것입니다.

 CMyBitMap.cpp
#include "stdafx.h"
#include "MyBitmap.h"


CMyBitmap::CMyBitmap()
{
}


CMyBitmap::~CMyBitmap()
{
	Release_Bitmap(); 
}

void CMyBitmap::Insert_Bitmap(const TCHAR * pFilePath)
{
	HDC hDC = GetDC(g_hWnd); 

	m_hMemDC = CreateCompatibleDC(hDC); //받아온 hdc와 호환되는 dc를 만들어 반환해줍니다.

	ReleaseDC(g_hWnd, hDC); // 용도 다했으면 지우자. 
	// DDB -장치에 의존적인, DIB 
	m_hBitmap = (HBITMAP)LoadImage(NULL, pFilePath, 
 IMAGE_BITMAP//비트맵을 불러오는것을 의미합니다.
 , 0, //불러올 이미지의 높이와 너비입니다 일단 0,0으로 설정합니다
 0, 
 LR_LOADFROMFILE //lpszName 인수를 리소스 대신 파일 경로를 사용해 불러옵니다.
 | LR_CREATEDIBSECTION); // uType 인수에서 IMAGE_BITMAP을 사용한 경우 호환 비트맵이 아닌 DIB 섹션 비트맵으로 불러옵니다.
 //하나 이상의 플래그를 지정합니다. 
	
	m_hOldBitmap = (HBITMAP)SelectObject(m_hMemDC, m_hBitmap);
 // 이전 비트맵을 버리지않고 저장해줍니다.

}

void CMyBitmap::Release_Bitmap()
{
//앞서 말한바와 같이 삭제전 이전 비트맵을 올려주고 삭제해야만 삭제할수있습니다.
	SelectObject(m_hMemDC, m_hOldBitmap); 
	DeleteObject(m_hBitmap); 
	DeleteDC(m_hMemDC); 
}

비트맵 매니저

앞에서 공부한 비트맵 클래스를 관리하기위해 비트맵 매니저를 이용합니다.

Bitmap_Manager.h

#pragma once
class CMyBitmap;
class CBitmap_Manager
{
public:
//싱글톤 패턴을 사용합니다.
	static CBitmap_Manager* Get_Instance()
	{
		if (nullptr == m_pInstance)
			m_pInstance = new CBitmap_Manager;
		return m_pInstance;
	}
	static void Destroy_Instance()
	{
		if (m_pInstance)
		{
			delete m_pInstance;
			m_pInstance = nullptr;
		}
	}
private:
	static CBitmap_Manager* m_pInstance;
private:
	CBitmap_Manager();
	~CBitmap_Manager();
public:
	HDC FindImage(const TCHAR* pImageKey);
	void Insert_Bitmap(const TCHAR* pPath, const TCHAR* pImageKey);
	void Release_Bitmap(); 

private:

	map<const TCHAR*, CMyBitmap* > m_mapBmp; 
    //맵 자료구조를 이용해 이미지를 저장합니다. KEY에는 식별하기위한 문자열이
    Value에는 비트맵클래스 포인터가 들어갑니다.
};

"Bitmap_Manager.cpp"

#include "stdafx.h"
#include "Bitmap_Manager.h"
#include "MyBitmap.h"
CBitmap_Manager* CBitmap_Manager::m_pInstance = nullptr;
//스태틱 변수 를 초기화해줍니다.
CBitmap_Manager::CBitmap_Manager()
{
}


CBitmap_Manager::~CBitmap_Manager()
{
	Release_Bitmap(); 
}

HDC CBitmap_Manager::FindImage(const TCHAR * pImageKey)
{
//키값을 통해 맵에서 이미지를 찾아 반환해주는 함수입니다.

	auto& iter_find = m_mapBmp.find(pImageKey);
    //find함수는 문자열의경우 순수 문자열비교가아닌 주소값비교이기때문에 
    순수 문자열 비교를 하고싶은 경우 find_if를 통한 방법을 사용해야합니다.
    https://baboruri.tistory.com/entry/findif%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-val%EA%B0%92-%EC%B0%BE%EA%B8%B0
    
    //찾지 못했다면 널포인터를 반환해줍니다
	if (iter_find == m_mapBmp.end())
		return nullptr; 
	//찾았을경우는 value값에 접근에 memDC를 반환해줍니다.
	return iter_find->second->Get_MemDC(); 
}

void CBitmap_Manager::Insert_Bitmap(const TCHAR * pPath, const TCHAR * pImageKey)
{
	auto& iter_find = m_mapBmp.find(pImageKey);
    //이미 키값이 있는 경우라면 종료합니다. 맵은 같은 키값을 가질수없음
	if (iter_find != m_mapBmp.end())
		return ;
	CMyBitmap* pBitMap = new CMyBitmap; 
	pBitMap->Insert_Bitmap(pPath); 

//키,밸류 페어를 맵에 추가해줍니다.
	m_mapBmp.emplace(pImageKey, pBitMap); 

// 	pair<const TCHAR*, CMyBitmap*> tPair; 
// 	tPair.first = pImageKey; 
// 	tPair.second = pBitMap; 
// 	m_mapBmp.insert(tPair);

}

void CBitmap_Manager::Release_Bitmap()
{
	for (auto& rPair : m_mapBmp)
		Safe_Delete(rPair.second); 
	m_mapBmp.clear(); 
}

매니저까지 구현했다면 MainApp에서 이미지를 추가해주고 플레이어를 렌더해줄때 Rectangle대신 이미지를 그려주면됩니다.

MainApp.cpp 레디 부분에 추가
CBitmap_Manager::Get_Instance()->Insert_Bitmap(L"../Image/maja2.bmp", L"maja2");
void CPlayer::Render_GameObject(HDC hDC)
{
	CGameObject::Update_Rect_GameObject(); 
	int iScrollX = CScroll_Manager::Get_ScrollX(); 
	int iScrollY = CScroll_Manager::Get_ScrollY();
	HDC hMemDC = CBitmap_Manager::Get_Instance()->FindImage(L"maja2");
    //MainApp에서 넣어준 이미지를 불러옵니다.
	if (nullptr == hMemDC)
		return; 
	BitBlt(hDC // 이미지를 출력할 위치의 핸들입니다
    , (m_tRect.left) + iScrollX, // 스크롤을 적용해주부니다
		m_tRect.top+ iScrollY,
        // 그림을 그리기 시작할 좌측 탑의 좌표를 받습니다
        
       //원본 이미지의 너비 높이를 받습니다.
		m_tInfo.iCX ,
		m_tInfo.iCY ,
		hMemDC,//이미지의 핸들입니다.
		0, 0,//가져올 이미지의 시작지점입니다.
		SRCCOPY); // 원본 이미지를 출력합니다.

// 	Rectangle(hDC, m_tRect.left +iScrollX, m_tRect.top + iScrollY, m_tRect.right + iScrollX, m_tRect.bottom + iScrollY); 
// 	MoveToEx(hDC, m_tInfo.fX, m_tInfo.fY, nullptr);
// 	LineTo(hDC, m_tPosin.x, m_tPosin.y);

}


이를 통해 이제 사각형대신 마자용이 그려집니다!

더블버퍼링

수업시간이 조금 남아 코드만 보았습니다 코드를 좀 더 공부해보겠습니다.

우선 더블버퍼링의 개념을 설명한 그림입니다.

레디부분에서 우선 이미지를 넣어줍니다
CBitmap_Manager::Get_Instance()->Insert_Bitmap(L"../Image/BackBuffer.bmp", L"BackBuffer"); 
CBitmap_Manager::Get_Instance()->Insert_Bitmap(L"../Image/Back.bmp", L"DubleBuffer");
CBitmap_Manager::Get_Instance()->Insert_Bitmap(L"../Image/maja2.bmp", L"maja2");
void CMainApp::Render_MainApp()
{
// 	Rectangle(m_hDC, 0, 0, WINCX, WINCY); 
// 	Rectangle(m_hDC, 100, 100, WINCX - 100, WINCY - 100); 
	HDC hDubleBuffer = CBitmap_Manager::Get_Instance()->FindImage(L"DubleBuffer"); //더블 버퍼에는 수지가들어있습니다.

	HDC hMemDC = CBitmap_Manager::Get_Instance()->FindImage(L"BackBuffer");  //백 버퍼에는 간디가 들어있습니다.
	BitBlt(hDubleBuffer, 0, 0, WINCX, WINCY, hMemDC, 0, 0, SRCCOPY); // 더블버퍼에 간디를 그려 넣어줍니다;

	CGameObject_Manager::Get_Instance()->Render_GameObject_Manager(hDubleBuffer);
	CLine_Manager::Get_Instance()->Render_LineManager(hDubleBuffer);
	//더블 버퍼에 게임오브젝트와 선들을 그려줍니다.
    
    ++m_iFPS;      
	if (m_dwFPSTime + 1000 < GetTickCount())
	{
		swprintf_s(m_szFPS, L"FPS : %d", m_iFPS); 
		m_iFPS = 0; 
		m_dwFPSTime = GetTickCount(); 
	}
	TextOut(hDubleBuffer, 100, 100, m_szFPS, lstrlen(m_szFPS));

	BitBlt(m_hDC, 0, 0, WINCX, WINCY, hDubleBuffer, 0, 0, SRCCOPY); //완성된 더블버퍼(간디 배경에 게임오브젝트 선들이 그려진 그림) 을 그려줍니다.
}

기존의 그려진 화면에 다시 Rectangle을 그려 화면을 초기화 하는 방식대신 BitBlt을 통한 출력을 사용합니다 우선 더블버퍼(수지)에 memdc(간디)를 그려넣어줍니다 다음 더블버퍼에 게임오브젝트와 선도 차례로 그려넣어준뒤 이 더블버퍼를 화면에 그려줍니다 그리고 다음 그림이 완성되면 또 화면에 그려줍니다 이렇게 하면 화면 깜박임이 줄어듭니다!

0개의 댓글

관련 채용 정보