해당 포스트는 "열혈강의 영상처리 프로그래밍" 책의 내용을 요약한 것이다.



※ 대화상자 기반 영상 출력

(github => https://github.com/fbf7290/ImageProcessing/tree/DialogViewer, 저의 미숙함으로 CImage 폴더를 제외한 파일이 날라갔습니다. 이번 예제는 CImage 폴더 내 소스 파일들이 핵심이기 때문에 상관 없을겁니다. )

새로운 대화상자 기반 프로젝트 "DialogViewer" 를 만들고 위와 같이 버튼 두 개를 배치했다. 그리고 이전 예제에서 사용했던 CMyImage 폴더와 폴더 내 파일들을 추가해준다. 먼저 영상 출력 버튼을 클릭했을 시 새로운 창이 떠서 이미지를 보여줘야 한다, 그래서 [클래스 추가] 대화 상자를 통해 CFrameWnd 클래스를 상속받는 CImageFrameWnd 클래스와 CView 클래스를 상속한 CImageView 클래스를 CMyImage 폴더 안에 생성한다. 원래는 CView만 생성해도 되지만 CFrameWnd 까지 생성한 이유는 CView 만 생성할 시 화면만 생성되고 창을 제어하는 닫기/숨기기/최대화 단추들이 없기 때문이다. 



- CImageView 클래스


void CImageView::OnDraw(CDC* pDC)
{
	CDocument* pDoc = GetDocument();
	// TODO: 여기에 그리기 코드를 추가합니다.
	if (m_image.IsEmpty()) return;

	_DrawImage(pDC->m_hDC, m_image,
		0, 0, m_image.GetWidth(), m_image.GetHeight(),
		0, 0, m_image.GetWidth(), m_image.GetHeight());
}

CImageView 클래스는 CView 클래스를 상속한 클래스이기에 자동적으로 OnDraw 메서드가 정의되어 있다. 이전 포스트인 MDI에서 영상 출력에서와 같이 OnDraw() 메서드 내부에서 _DrawImage 메서드를 호출한다. 또한 이전 포스트와 같이 CImageView.cpp 파일 안에 아래의 코드를 추가해준다.


static BYTE bmiBuf[sizeof(BITMAPINFOHEADER) + 1024];
static const RGBQUAD GrayPalette[256] =
{
	{ 0,   0,   0,  0 },{ 1,   1,   1,  0 },{ 2,   2,   2,  0 },{ 3,   3,   3,  0 },{ 4,   4,   4,  0 },{ 5,   5,   5,  0 },{ 6,   6,   6,  0 },{ 7,   7,   7,  0 },{ 8,   8,   8,  0 },{ 9,   9,   9,  0 },
	{ 10,  10,  10,  0 },{ 11,  11,  11,  0 },{ 12,  12,  12,  0 },{ 13,  13,  13,  0 },{ 14,  14,  14,  0 },{ 15,  15,  15,  0 },{ 16,  16,  16,  0 },{ 17,  17,  17,  0 },{ 18,  18,  18,  0 },{ 19,  19,  19,  0 },
	{ 20,  20,  20,  0 },{ 21,  21,  21,  0 },{ 22,  22,  22,  0 },{ 23,  23,  23,  0 },{ 24,  24,  24,  0 },{ 25,  25,  25,  0 },{ 26,  26,  26,  0 },{ 27,  27,  27,  0 },{ 28,  28,  28,  0 },{ 29,  29,  29,  0 },
	{ 30,  30,  30,  0 },{ 31,  31,  31,  0 },{ 32,  32,  32,  0 },{ 33,  33,  33,  0 },{ 34,  34,  34,  0 },{ 35,  35,  35,  0 },{ 36,  36,  36,  0 },{ 37,  37,  37,  0 },{ 38,  38,  38,  0 },{ 39,  39,  39,  0 },
	{ 40,  40,  40,  0 },{ 41,  41,  41,  0 },{ 42,  42,  42,  0 },{ 43,  43,  43,  0 },{ 44,  44,  44,  0 },{ 45,  45,  45,  0 },{ 46,  46,  46,  0 },{ 47,  47,  47,  0 },{ 48,  48,  48,  0 },{ 49,  49,  49,  0 },
	{ 50,  50,  50,  0 },{ 51,  51,  51,  0 },{ 52,  52,  52,  0 },{ 53,  53,  53,  0 },{ 54,  54,  54,  0 },{ 55,  55,  55,  0 },{ 56,  56,  56,  0 },{ 57,  57,  57,  0 },{ 58,  58,  58,  0 },{ 59,  59,  59,  0 },
	{ 60,  60,  60,  0 },{ 61,  61,  61,  0 },{ 62,  62,  62,  0 },{ 63,  63,  63,  0 },{ 64,  64,  64,  0 },{ 65,  65,  65,  0 },{ 66,  66,  66,  0 },{ 67,  67,  67,  0 },{ 68,  68,  68,  0 },{ 69,  69,  69,  0 },
	{ 70,  70,  70,  0 },{ 71,  71,  71,  0 },{ 72,  72,  72,  0 },{ 73,  73,  73,  0 },{ 74,  74,  74,  0 },{ 75,  75,  75,  0 },{ 76,  76,  76,  0 },{ 77,  77,  77,  0 },{ 78,  78,  78,  0 },{ 79,  79,  79,  0 },
	{ 80,  80,  80,  0 },{ 81,  81,  81,  0 },{ 82,  82,  82,  0 },{ 83,  83,  83,  0 },{ 84,  84,  84,  0 },{ 85,  85,  85,  0 },{ 86,  86,  86,  0 },{ 87,  87,  87,  0 },{ 88,  88,  88,  0 },{ 89,  89,  89,  0 },
	{ 90,  90,  90,  0 },{ 91,  91,  91,  0 },{ 92,  92,  92,  0 },{ 93,  93,  93,  0 },{ 94,  94,  94,  0 },{ 95,  95,  95,  0 },{ 96,  96,  96,  0 },{ 97,  97,  97,  0 },{ 98,  98,  98,  0 },{ 99,  99,  99,  0 },
	{ 100, 100, 100,  0 },{ 101, 101, 101,  0 },{ 102, 102, 102,  0 },{ 103, 103, 103,  0 },{ 104, 104, 104,  0 },{ 105, 105, 105,  0 },{ 106, 106, 106,  0 },{ 107, 107, 107,  0 },{ 108, 108, 108,  0 },{ 109, 109, 109,  0 },
	{ 110, 110, 110,  0 },{ 111, 111, 111,  0 },{ 112, 112, 112,  0 },{ 113, 113, 113,  0 },{ 114, 114, 114,  0 },{ 115, 115, 115,  0 },{ 116, 116, 116,  0 },{ 117, 117, 117,  0 },{ 118, 118, 118,  0 },{ 119, 119, 119,  0 },
	{ 120, 120, 120,  0 },{ 121, 121, 121,  0 },{ 122, 122, 122,  0 },{ 123, 123, 123,  0 },{ 124, 124, 124,  0 },{ 125, 125, 125,  0 },{ 126, 126, 126,  0 },{ 127, 127, 127,  0 },{ 128, 128, 128,  0 },{ 129, 129, 129,  0 },
	{ 130, 130, 130,  0 },{ 131, 131, 131,  0 },{ 132, 132, 132,  0 },{ 133, 133, 133,  0 },{ 134, 134, 134,  0 },{ 135, 135, 135,  0 },{ 136, 136, 136,  0 },{ 137, 137, 137,  0 },{ 138, 138, 138,  0 },{ 139, 139, 139,  0 },
	{ 140, 140, 140,  0 },{ 141, 141, 141,  0 },{ 142, 142, 142,  0 },{ 143, 143, 143,  0 },{ 144, 144, 144,  0 },{ 145, 145, 145,  0 },{ 146, 146, 146,  0 },{ 147, 147, 147,  0 },{ 148, 148, 148,  0 },{ 149, 149, 149,  0 },
	{ 150, 150, 150,  0 },{ 151, 151, 151,  0 },{ 152, 152, 152,  0 },{ 153, 153, 153,  0 },{ 154, 154, 154,  0 },{ 155, 155, 155,  0 },{ 156, 156, 156,  0 },{ 157, 157, 157,  0 },{ 158, 158, 158,  0 },{ 159, 159, 159,  0 },
	{ 160, 160, 160,  0 },{ 161, 161, 161,  0 },{ 162, 162, 162,  0 },{ 163, 163, 163,  0 },{ 164, 164, 164,  0 },{ 165, 165, 165,  0 },{ 166, 166, 166,  0 },{ 167, 167, 167,  0 },{ 168, 168, 168,  0 },{ 169, 169, 169,  0 },
	{ 170, 170, 170,  0 },{ 171, 171, 171,  0 },{ 172, 172, 172,  0 },{ 173, 173, 173,  0 },{ 174, 174, 174,  0 },{ 175, 175, 175,  0 },{ 176, 176, 176,  0 },{ 177, 177, 177,  0 },{ 178, 178, 178,  0 },{ 179, 179, 179,  0 },
	{ 180, 180, 180,  0 },{ 181, 181, 181,  0 },{ 182, 182, 182,  0 },{ 183, 183, 183,  0 },{ 184, 184, 184,  0 },{ 185, 185, 185,  0 },{ 186, 186, 186,  0 },{ 187, 187, 187,  0 },{ 188, 188, 188,  0 },{ 189, 189, 189,  0 },
	{ 190, 190, 190,  0 },{ 191, 191, 191,  0 },{ 192, 192, 192,  0 },{ 193, 193, 193,  0 },{ 194, 194, 194,  0 },{ 195, 195, 195,  0 },{ 196, 196, 196,  0 },{ 197, 197, 197,  0 },{ 198, 198, 198,  0 },{ 199, 199, 199,  0 },
	{ 200, 200, 200,  0 },{ 201, 201, 201,  0 },{ 202, 202, 202,  0 },{ 203, 203, 203,  0 },{ 204, 204, 204,  0 },{ 205, 205, 205,  0 },{ 206, 206, 206,  0 },{ 207, 207, 207,  0 },{ 208, 208, 208,  0 },{ 209, 209, 209,  0 },
	{ 210, 210, 210,  0 },{ 211, 211, 211,  0 },{ 212, 212, 212,  0 },{ 213, 213, 213,  0 },{ 214, 214, 214,  0 },{ 215, 215, 215,  0 },{ 216, 216, 216,  0 },{ 217, 217, 217,  0 },{ 218, 218, 218,  0 },{ 219, 219, 219,  0 },
	{ 220, 220, 220,  0 },{ 221, 221, 221,  0 },{ 222, 222, 222,  0 },{ 223, 223, 223,  0 },{ 224, 224, 224,  0 },{ 225, 225, 225,  0 },{ 226, 226, 226,  0 },{ 227, 227, 227,  0 },{ 228, 228, 228,  0 },{ 229, 229, 229,  0 },
	{ 230, 230, 230,  0 },{ 231, 231, 231,  0 },{ 232, 232, 232,  0 },{ 233, 233, 233,  0 },{ 234, 234, 234,  0 },{ 235, 235, 235,  0 },{ 236, 236, 236,  0 },{ 237, 237, 237,  0 },{ 238, 238, 238,  0 },{ 239, 239, 239,  0 },
	{ 240, 240, 240,  0 },{ 241, 241, 241,  0 },{ 242, 242, 242,  0 },{ 243, 243, 243,  0 },{ 244, 244, 244,  0 },{ 245, 245, 245,  0 },{ 246, 246, 246,  0 },{ 247, 247, 247,  0 },{ 248, 248, 248,  0 },{ 249, 249, 249,  0 },
	{ 250, 250, 250,  0 },{ 251, 251, 251,  0 },{ 252, 252, 252,  0 },{ 253, 253, 253,  0 },{ 254, 254, 254,  0 },{ 255, 255, 255,  0 }
};

bool _DrawImage(HDC hdc, const CByteImage& image,
	int nSrcX, int nSrcY, int nSrcW, int nSrcH,
	int nDstX, int nDstY, int nDstW, int nDstH)
{
	if (!image.GetPtr()) return false;

	int nWidth = image.GetWStep() / image.GetChannel();
	int nHeight = image.GetHeight();
	int nBPP = image.GetChannel() * 8;

	ASSERT((BITMAPINFO*)bmiBuf && nWidth > 0 && nHeight > 0 &&
		(nBPP == 8 || nBPP == 24 || nBPP == 32));

	BITMAPINFOHEADER *bmih = &(((BITMAPINFO*)bmiBuf)->bmiHeader);

	memset(bmih, 0, sizeof(*bmih));

	bmih->biSize = sizeof(BITMAPINFOHEADER);
	bmih->biWidth = nWidth;
	bmih->biHeight = -nHeight;
	bmih->biPlanes = 1;
	bmih->biBitCount = nBPP;
	bmih->biCompression = BI_RGB;

	if (nBPP == 8)
		memcpy(((BITMAPINFO*)bmiBuf)->bmiColors, GrayPalette, 256 * sizeof(RGBQUAD));

	SetStretchBltMode(hdc, COLORONCOLOR);
	::StretchDIBits(
		hdc,
		nDstX, nDstY, nDstW, nDstH,
		nSrcX, nSrcY, nSrcW, nSrcH,
		image.GetPtr(),									// lpBits
		(LPBITMAPINFO) &(((BITMAPINFO*)bmiBuf)->bmiHeader),	// lpBitsInfo
		DIB_RGB_COLORS,											// iUsage
		SRCCOPY);												// dwRop

	return true;
}

OnDraw() 메서드 안에서 _DrawImage()를 호출할 때 View상에 그릴 이미지 객체가 필요하므로 CImageView.h 헤더파일에 출력용 영상을 멤버 변수로 선언한다. 

protected:
	CByteImage m_image;

또한 m_image 멤버 변수에 영상을 입력해줄 setImage 메서드를 추가한다. 이 함수는 CImageFrameWnd 클래스가 멤버 변수인 CImageView 클래스에 영상을 지정하는 데 사용한다. 또한 CImageFrameWnd 클래스에서 CImageView 클래스의 뷰를 생성할 수 있게 해야해 CImageVIew 클래스 내부에 외부에서 CImageView를 생성할 수 있도록 하는 함수를 추가해야 한다. 


void CImageView::SetImage(const CByteImage& input)
{
	m_image = input;
}

BOOL CImageView::CreateEx(CWnd * pWnd)
{
	return Create(NULL, NULL, WS_CHILD | WS_VISIBLE | WS_MAXIMIZE, CRect(0, 0, 0, 0), pWnd, AFX_IDW_PANE_FIRST, NULL);
}




- CImageFrameWnd 클래스


먼저 CImageFrameWnd 클래스에서 CImageView를 생성해야 하므로 CImageView에 접근할 CImageView 변수를 선언한다.


protected:
	CImageView m_view;

CImageFrameWnd 클래스에서 CImageView 클래스 생성을 통한 화면에 영상 출력 기능은 생성자에서 구현한다.

static int nFrameWndCount = 0;

CImageFrameWnd::CImageFrameWnd(const CByteImage &image, const char * name)
{
	m_view.SetImage(image);
	CString wndName;
	if (name)
		wndName = name;
	else
		wndName.Format(_T("Image view %d"), nFrameWndCount);

	CRect rect(30 * nFrameWndCount, 30 * nFrameWndCount,
		image.GetWidth() + 30 * nFrameWndCount, image.GetHeight() + 30 * nFrameWndCount);
	Create(NULL, wndName, WS_OVERLAPPEDWINDOW, rect);

	nFrameWndCount++;

	ShowWindow(SW_SHOW);
}

m_view.setImage(image) 를 통해서 CImageView 클래스 멤버 변수인 CByteImage 객체에 영상을 저장한다. 그 다음 wndName 창 이름을 지정하고 ShowWindow를 통해 최종적으로 영상을 출력해줄 창이 보여진다. nFrameWndCount는 영상을 여러 개 출력할 때 출력 뷰의 창 위치를 조정하기 위해서 만든 변수이다. 

CImageFrameWnd * pImageWnd = new CImageFrameWnd(inputImage, "영상 출력");

위와 같이 생성자를 이용해서 inputImage 영상을 출력할 수 있는 창이 보여진다.


CImageFrameWnd를 통해 영상을 출력할 수 있는 창이 보여지지만 아직 영상을 실제로 출력하는 되지는 않는다. 왜나하면 CImageView 클래스에 영상 데이터만 입력했지 실제로 View를 생성하고 화면에 보여지는 우리가 위에서 정의한 CreateEx 메서드를 호출하지 않았기 때문이다. 그래서 CreateEx 메서드를 호출해야 하는 데 이 메서드를 CImageFrameWnd 클래스가 생성하고 나서 호출 돼야 하기 때문에 CimageFrameWnd 클래스의 OnCreate 메서드 내에서 호출하기로 하자. OnCreate() 함수는 CImageFrameWnd 클래스를 생성하고 나서 WM_CREATE 메시지에 의해 호출이 된다. 그래서 OnCreate 메서드를 재정의해야 하는데 재정의 하는 방법은 클래스 마법사 메뉴를 통해서 재정의 할 수 있다. 


int CImageFrameWnd::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
		return -1;

	// TODO:  여기에 특수화된 작성 코드를 추가합니다.
	m_view.CreateEx(this);

	return 0;
}

위와 같이 재정의한 OnCreate 메서드 내부에 CImageView 클래스의 CreateEx 메서드를 호출했다. 그럼으로써 CImageView에서 View를 생성해 실제로 영상을 출력해준다. 



- DialogViewerDlg 클래스

"영상 출력", "창 닫기" 버튼에서 실제 영상 창을 생성하고 닫기 위해서는 CImageFrameWnd 클래스에 접근해야 하므로 CImageFrameWnd 클래스 포인터 변수를 멤버변수로 선언한다.

	CImageFrameWnd* m_pImageWnd;

그리고 "영상 출력", "창 닫기" 버튼에 대한 이벤트 처리 함수를 만들어 준다.

void CDialogViewerDlg::OnBnClickedButtonView() { // TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다. CByteImage image = LoadImageFromDialog();

m_pImageWnd = new CImageFrameWnd(image, "영상 출력"); } void CDialogViewerDlg::OnBnClickedButtonClose() { // TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다. delete m_pImageWnd;

}




※ 다중 영상 출력 관리하기

위 예제는 영상 하나 출력할 때 마다 CImageFrameWnd 인스턴스를 생성해야 해서 번거롭다. 특히, 여러 개의 영상을 출력하고 닫으려면 여러 개의 CImageFrameWnd 클래스를 관리해야 한다. 따라서 여러 개의 CImageFrameWnd 객체들을 관리하고 한 번에 여러 창을 띄우고 닫을 수 있도록 별도의 CImageFrameWndManager 클래스를 CMyImage 폴더 안에 생성한다. 해당 클래스는 CImageFrameWnd 클래스의 포인터를 입력 받아 배열에 추가하고 특정 CImageFrameWnd 객체를 소멸하고 배열에서 삭제하는 기능을 가진다.


class CImageFrameWndManager
{
public:
	CImageFrameWndManager(void);
	~CImageFrameWndManager(void);

	void Add(CImageFrameWnd* pImageWnd);
	CImageFrameWnd* FindWnd(const char* name);
	void Delete(CImageFrameWnd* pImageWnd);
	void CloseAll();

protected:
	std::vector  m_vpImageWnd;
};

extern void ShowImage(const CByteImage& input, const char *name = NULL);
extern void CloseImage(const char* name);
extern void CloseAllImages();

위 코드는 CImageFrameWndManager의 헤더 파일이다. 전역함수를 비롯해 각 함수에 대해서 알아보자.


CImageFrameWndManager::CImageFrameWndManager(void)
{
}

CImageFrameWndManager::~CImageFrameWndManager(void)
{
	CloseAll();
}

void CImageFrameWndManager::Add(CImageFrameWnd* pImageWnd)
{
	m_vpImageWnd.push_back(pImageWnd);
}

소멸자를 보면 열려있는 영상 출력 창을 모두 닫는 기능이 있는 CloseAll 메서드가 호출된다. Add 메서드는 출력 창을 관리하기 위해서 동적으로 생성된 CImageFrameWnd 포인터를 입력받아 vector 배열에 데이터를 넣는 역할을 한다.  

CImageFrameWnd* CImageFrameWndManager::FindWnd(const char* name)
{
	CImageFrameWnd* ret = NULL;
	if (name)
	{
		for (unsigned int i = 0; i<m_vpImageWnd.size(); i++)
		{
			CString wName;
			m_vpImageWnd.at(i)-<GetWindowText(wName);
			if (wName == name) // 일치하는 문자열 발견
			{
				ret = m_vpImageWnd.at(i);
				break;
			}
		}
	}
	return ret;
}

FindWnd 메서드는 입력된 문자열과 같은 이름을 가진 CImageFrameWnd 객체를 찾는 기능을 한다. 여러 개의 열려있는 창을 관리하려면 특정 창 객체를 찾아서 사용해야 하는 데 그 때 사용되는 메서드이다.


void CImageFrameWndManager::Delete(CImageFrameWnd* pImageWnd)
{
	for (unsigned int i = 0; i<m_vpImageWnd.size(); i++)
	{
		if (m_vpImageWnd.at(i) == pImageWnd)
		{
			delete m_vpImageWnd.at(i);
			m_vpImageWnd.erase(m_vpImageWnd.begin() + i);
			break;
		}
	}
}

void CImageFrameWndManager::CloseAll()
{
	for (int i = m_vpImageWnd.size() - 1; i >= 0; i--)
	{
		delete m_vpImageWnd.at(i);
		m_vpImageWnd.erase(m_vpImageWnd.begin() + i);
	}
}

Delete 메서드는 특정 창을 닫을 때 사용하는 메서드이고 CloseAll 메서드는 모든 창을 닫을 때 사용하는 메서드이다.


CImageFrameWndManager gImageFrameWndManager;
void ShowImage(const CByteImage& input, const char *name)
{
	CImageFrameWnd *pImageWnd = gImageFrameWndManager.FindWnd(name);
	if (pImageWnd) // 같은 이름의 창 발견
	{
		pImageWnd->GetImageView().SetImage(input);	 // 기존 창의 영상 갱신
		pImageWnd->GetImageView().Invalidate(false); // 화면 갱신 함수 호출
	}
	else // 같은 이름의 창이 없을 시 새 창을 생성하고 배열에 추가
	{
		gImageFrameWndManager.Add(new CImageFrameWnd(input, name));
	}
}

void CloseImage(const char* name)
{
	CImageFrameWnd* pImageWnd = gImageFrameWndManager.FindWnd(name);
	gImageFrameWndManager.Delete(pImageWnd);
}

void CloseAllImages()
{
	gImageFrameWndManager.CloseAll();
}

영상을 출력하는 모든 부분에서 CImageFrameWndManager 클래스에 접근해야 하므로 편리성을 위해 CImageFrameWndManager 전역 변수를 선언했다. 그리고 나머지 ShowImage, CloseImage, CloseAllImages 메서드 또한 편리성을 위해 전역변수로 정의했다. 



-DialogViewDlg 클래스

void CDialogViewerDlg::OnBnClickedButtonView() { // TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다. CByteImage image = LoadImageFromDialog(); ShowImage(image, "Image"); } void CDialogViewerDlg::OnBnClickedButtonClose() { // TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다. CloseImage("Image"); }

최종적으로 CImageFrameWndManager을 다 구현했을 시 "영상 출력", "창 닫기" 버튼 이벤트 처리기 구현이다.

+ Recent posts