MFC GDI+ | CDC·CBitmap·그래픽 처리 완벽 가이드
이 글의 핵심
MFC GDI는 CDC 클래스로 그리기 작업을 수행합니다. CPaintDC는 OnPaint에서, CClientDC는 다른 곳에서 사용합니다. OnDraw는 Document/View 아키텍처에서 사용하며, 더블 버퍼링으로 깜빡임을 방지합니다.
들어가며
MFC GDI는 Windows GDI를 C++ 클래스로 감싼 것입니다. CDC(Device Context)로 그리기 작업을 수행하며, CPen·CBrush·CFont 등의 GDI 객체를 사용합니다. 2000년대 Windows 애플리케이션의 그래픽 처리 핵심 기술이었습니다.
// 가장 간단한 MFC 그리기
void CMyView::OnDraw(CDC* pDC)
{
// 텍스트
pDC->TextOut(10, 10, _T("Hello, MFC GDI!"));
// 선
pDC->MoveTo(10, 50);
pDC->LineTo(200, 50);
// 사각형
pDC->Rectangle(10, 70, 100, 150);
// 원
pDC->Ellipse(120, 70, 210, 150);
}
1. Device Context (CDC)
1.1 주요 CDC 클래스
// 1. CPaintDC - WM_PAINT 메시지 처리용
void CMyWnd::OnPaint()
{
CPaintDC dc(this); // BeginPaint/EndPaint 자동 호출
dc.TextOut(10, 10, _T("OnPaint 그리기"));
}
// 2. CClientDC - 일반 그리기용
void CMyWnd::OnLButtonDown(UINT nFlags, CPoint point)
{
CClientDC dc(this);
dc.SetPixel(point, RGB(255, 0, 0)); // 빨간 점
CWnd::OnLButtonDown(nFlags, point);
}
// 3. CWindowDC - 윈도우 전체 (비클라이언트 영역 포함)
void CMyWnd::DrawBorder()
{
CWindowDC dc(this);
CRect rect;
GetWindowRect(&rect);
rect.OffsetRect(-rect.left, -rect.top);
dc.Draw3dRect(rect, RGB(255, 0, 0), RGB(0, 0, 255));
}
// 4. CMemoryDC - 더블 버퍼링용
void CMyWnd::OnPaint()
{
CPaintDC dc(this);
CRect rect;
GetClientRect(&rect);
// 메모리 DC
CDC memDC;
memDC.CreateCompatibleDC(&dc);
CBitmap bitmap;
bitmap.CreateCompatibleBitmap(&dc, rect.Width(), rect.Height());
CBitmap* pOldBitmap = memDC.SelectObject(&bitmap);
// 메모리 DC에 그리기
memDC.FillSolidRect(&rect, RGB(255, 255, 255));
memDC.TextOut(10, 10, _T("더블 버퍼링!"));
// 한 번에 복사
dc.BitBlt(0, 0, rect.Width(), rect.Height(), &memDC, 0, 0, SRCCOPY);
memDC.SelectObject(pOldBitmap);
}
2. GDI 객체
2.1 CPen (펜)
void CMyView::OnDraw(CDC* pDC)
{
// 기본 펜
CPen pen1;
pen1.CreatePen(PS_SOLID, 1, RGB(255, 0, 0)); // 빨간 실선, 두께 1
CPen* pOldPen = pDC->SelectObject(&pen1);
pDC->MoveTo(10, 10);
pDC->LineTo(200, 10);
// 점선
CPen pen2;
pen2.CreatePen(PS_DASH, 1, RGB(0, 0, 255));
pDC->SelectObject(&pen2);
pDC->MoveTo(10, 30);
pDC->LineTo(200, 30);
// 굵은 선
CPen pen3;
pen3.CreatePen(PS_SOLID, 5, RGB(0, 255, 0));
pDC->SelectObject(&pen3);
pDC->MoveTo(10, 50);
pDC->LineTo(200, 50);
pDC->SelectObject(pOldPen);
}
2.2 CBrush (브러시)
void CMyView::OnDraw(CDC* pDC)
{
// 단색 브러시
CBrush brush1;
brush1.CreateSolidBrush(RGB(255, 200, 200));
CBrush* pOldBrush = pDC->SelectObject(&brush1);
pDC->Rectangle(10, 10, 100, 100);
// 해치 브러시 (패턴)
CBrush brush2;
brush2.CreateHatchBrush(HS_CROSS, RGB(0, 0, 255));
pDC->SelectObject(&brush2);
pDC->Ellipse(120, 10, 210, 100);
pDC->SelectObject(pOldBrush);
}
2.3 CFont (폰트)
void CMyView::OnDraw(CDC* pDC)
{
// 폰트 생성
CFont font1;
font1.CreatePointFont(120, _T("맑은 고딕")); // 12포인트
CFont* pOldFont = pDC->SelectObject(&font1);
pDC->TextOut(10, 10, _T("맑은 고딕 12pt"));
// 굵은 폰트
CFont font2;
font2.CreateFont(
30, // 높이
0, // 너비 (자동)
0, // escapement
0, // orientation
FW_BOLD, // 굵기
FALSE, // italic
FALSE, // underline
FALSE, // strikeout
DEFAULT_CHARSET,
OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS,
DEFAULT_QUALITY,
DEFAULT_PITCH | FF_SWISS,
_T("Arial")
);
pDC->SelectObject(&font2);
pDC->TextOut(10, 40, _T("Arial Bold 30px"));
pDC->SelectObject(pOldFont);
}
3. 기본 그리기 함수
3.1 도형 그리기
void CMyView::OnDraw(CDC* pDC)
{
// 선
pDC->MoveTo(10, 10);
pDC->LineTo(100, 100);
// 사각형
pDC->Rectangle(120, 10, 220, 110);
// 원/타원
pDC->Ellipse(240, 10, 340, 110);
// 둥근 사각형
pDC->RoundRect(360, 10, 460, 110, 20, 20);
// 다각형
CPoint points[] = {
CPoint(10, 130),
CPoint(60, 180),
CPoint(110, 130),
CPoint(85, 230),
CPoint(35, 230)
};
pDC->Polygon(points, 5);
// 호 (Arc)
pDC->Arc(120, 130, 220, 230, 170, 130, 220, 180);
// 파이 (Pie)
pDC->Pie(240, 130, 340, 230, 290, 130, 340, 180);
}
3.2 텍스트 그리기
void CMyView::OnDraw(CDC* pDC)
{
// 기본 텍스트
pDC->TextOut(10, 10, _T("TextOut 함수"));
// 사각형 안에 텍스트
CRect rect(10, 40, 300, 100);
pDC->DrawText(_T("DrawText는 여러 줄 텍스트를 그립니다.\n자동 줄바꿈도 지원합니다."),
&rect, DT_LEFT | DT_WORDBREAK);
// 텍스트 색상과 배경
pDC->SetTextColor(RGB(255, 0, 0)); // 빨간색
pDC->SetBkColor(RGB(255, 255, 0)); // 노란 배경
pDC->SetBkMode(OPAQUE); // 불투명
pDC->TextOut(10, 110, _T("색상 텍스트"));
// 투명 배경
pDC->SetBkMode(TRANSPARENT);
pDC->TextOut(10, 130, _T("투명 배경"));
// 정렬
pDC->SetTextAlign(TA_CENTER | TA_TOP);
pDC->TextOut(150, 150, _T("중앙 정렬"));
}
4. 비트맵 처리
4.1 CBitmap
class CMyView : public CView
{
protected:
CBitmap m_bitmap;
public:
void LoadBitmap()
{
m_bitmap.LoadBitmap(IDB_BITMAP1); // 리소스에서 로드
}
virtual void OnDraw(CDC* pDC)
{
if (m_bitmap.GetSafeHandle() == NULL) {
LoadBitmap();
}
// 메모리 DC에 비트맵 그리기
CDC memDC;
memDC.CreateCompatibleDC(pDC);
CBitmap* pOldBitmap = memDC.SelectObject(&m_bitmap);
// 비트맵 크기 얻기
BITMAP bm;
m_bitmap.GetBitmap(&bm);
// 복사
pDC->BitBlt(10, 10, bm.bmWidth, bm.bmHeight, &memDC, 0, 0, SRCCOPY);
memDC.SelectObject(pOldBitmap);
}
};
4.2 CImage (파일에서 이미지 로드)
class CMyView : public CView
{
protected:
CImage m_image;
public:
void LoadImage(const CString& filePath)
{
m_image.Destroy();
HRESULT hr = m_image.Load(filePath);
if (SUCCEEDED(hr)) {
Invalidate();
} else {
AfxMessageBox(_T("이미지 로드 실패!"));
}
}
virtual void OnDraw(CDC* pDC)
{
if (!m_image.IsNull()) {
// 원본 크기로 그리기
m_image.Draw(pDC->m_hDC, 10, 10);
// 크기 조절해서 그리기
CRect rect(200, 10, 400, 210);
m_image.StretchBlt(pDC->m_hDC, rect, SRCCOPY);
// 투명 그리기 (알파 채널)
m_image.AlphaBlend(pDC->m_hDC, 450, 10, 0.5f); // 50% 투명도
}
}
};
5. 더블 버퍼링
5.1 기본 더블 버퍼링
void CMyView::OnDraw(CDC* pDC)
{
CRect rect;
GetClientRect(&rect);
// 메모리 DC 생성
CDC memDC;
memDC.CreateCompatibleDC(pDC);
// 비트맵 생성
CBitmap bitmap;
bitmap.CreateCompatibleBitmap(pDC, rect.Width(), rect.Height());
CBitmap* pOldBitmap = memDC.SelectObject(&bitmap);
// 배경 칠하기
memDC.FillSolidRect(&rect, RGB(255, 255, 255));
// 메모리 DC에 그리기
for (int i = 0; i < 100; i++) {
int x = rand() % rect.Width();
int y = rand() % rect.Height();
int r = rand() % 256;
int g = rand() % 256;
int b = rand() % 256;
CPen pen;
pen.CreatePen(PS_SOLID, 2, RGB(r, g, b));
CPen* pOldPen = memDC.SelectObject(&pen);
memDC.Ellipse(x, y, x + 50, y + 50);
memDC.SelectObject(pOldPen);
}
// 한 번에 화면에 복사 (깜빡임 없음!)
pDC->BitBlt(0, 0, rect.Width(), rect.Height(), &memDC, 0, 0, SRCCOPY);
memDC.SelectObject(pOldBitmap);
}
5.2 더블 버퍼링 헬퍼 클래스
class CMemoryDC : public CDC
{
private:
CDC* m_pDC;
CBitmap m_bitmap;
CBitmap* m_pOldBitmap;
CRect m_rect;
public:
CMemoryDC(CDC* pDC, const CRect& rect)
: m_pDC(pDC), m_rect(rect)
{
CreateCompatibleDC(pDC);
m_bitmap.CreateCompatibleBitmap(pDC, rect.Width(), rect.Height());
m_pOldBitmap = SelectObject(&m_bitmap);
// 좌표 이동
SetViewportOrg(-rect.left, -rect.top);
}
~CMemoryDC()
{
// 화면에 복사
m_pDC->BitBlt(m_rect.left, m_rect.top, m_rect.Width(), m_rect.Height(),
this, m_rect.left, m_rect.top, SRCCOPY);
// 정리
SelectObject(m_pOldBitmap);
}
};
// 사용
void CMyView::OnDraw(CDC* pDC)
{
CRect rect;
GetClientRect(&rect);
CMemoryDC dc(pDC, rect); // 생성자에서 메모리 DC 준비
// dc에 그리기 (pDC 대신)
dc.FillSolidRect(&rect, RGB(255, 255, 255));
dc.TextOut(10, 10, _T("더블 버퍼링!"));
// 소멸자에서 자동으로 화면에 복사됨
}
6. 실전 예제: 간단한 그림판
// DrawView.h
class CDrawView : public CView
{
protected:
CArray<CPoint> m_points;
BOOL m_bDrawing;
COLORREF m_color;
int m_penWidth;
public:
CDrawView();
protected:
virtual void OnDraw(CDC* pDC);
DECLARE_MESSAGE_MAP()
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
afx_msg void OnColorRed();
afx_msg void OnColorBlue();
afx_msg void OnPenThin();
afx_msg void OnPenThick();
afx_msg void OnEditClear();
};
// DrawView.cpp
CDrawView::CDrawView()
: m_bDrawing(FALSE)
, m_color(RGB(0, 0, 0))
, m_penWidth(1)
{
}
void CDrawView::OnDraw(CDC* pDC)
{
CRect rect;
GetClientRect(&rect);
// 더블 버퍼링
CMemoryDC dc(pDC, rect);
// 배경
dc.FillSolidRect(&rect, RGB(255, 255, 255));
// 선 그리기
if (m_points.GetSize() > 1) {
CPen pen;
pen.CreatePen(PS_SOLID, m_penWidth, m_color);
CPen* pOldPen = dc.SelectObject(&pen);
dc.MoveTo(m_points[0]);
for (int i = 1; i < m_points.GetSize(); i++) {
dc.LineTo(m_points[i]);
}
dc.SelectObject(pOldPen);
}
}
void CDrawView::OnLButtonDown(UINT nFlags, CPoint point)
{
m_bDrawing = TRUE;
m_points.RemoveAll();
m_points.Add(point);
CView::OnLButtonDown(nFlags, point);
}
void CDrawView::OnLButtonUp(UINT nFlags, CPoint point)
{
m_bDrawing = FALSE;
CView::OnLButtonUp(nFlags, point);
}
void CDrawView::OnMouseMove(UINT nFlags, CPoint point)
{
if (m_bDrawing) {
m_points.Add(point);
// 실시간 그리기
CClientDC dc(this);
CPen pen;
pen.CreatePen(PS_SOLID, m_penWidth, m_color);
CPen* pOldPen = dc.SelectObject(&pen);
if (m_points.GetSize() > 1) {
CPoint prev = m_points[m_points.GetSize() - 2];
dc.MoveTo(prev);
dc.LineTo(point);
}
dc.SelectObject(pOldPen);
}
CView::OnMouseMove(nFlags, point);
}
void CDrawView::OnColorRed()
{
m_color = RGB(255, 0, 0);
}
void CDrawView::OnColorBlue()
{
m_color = RGB(0, 0, 255);
}
void CDrawView::OnPenThin()
{
m_penWidth = 1;
}
void CDrawView::OnPenThick()
{
m_penWidth = 5;
}
void CDrawView::OnEditClear()
{
m_points.RemoveAll();
Invalidate();
}
BEGIN_MESSAGE_MAP(CDrawView, CView)
ON_WM_LBUTTONDOWN()
ON_WM_LBUTTONUP()
ON_WM_MOUSEMOVE()
ON_COMMAND(ID_COLOR_RED, &CDrawView::OnColorRed)
ON_COMMAND(ID_COLOR_BLUE, &CDrawView::OnColorBlue)
ON_COMMAND(ID_PEN_THIN, &CDrawView::OnPenThin)
ON_COMMAND(ID_PEN_THICK, &CDrawView::OnPenThick)
ON_COMMAND(ID_EDIT_CLEAR, &CDrawView::OnEditClear)
END_MESSAGE_MAP()
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- MFC 기초 | Microsoft Foundation Class 시작 가이드
- Windows API GDI | 그래픽·비트맵 완벽 가이드
- C++ 메모리 관리 완벽 가이드 | RAII·스마트 포인터
이 글이 도움이 되셨나요? MFC GDI+ 그래픽 처리를 마스터하는 데 도움이 되었기를 바랍니다!
다음 글에서는 MFC 멀티스레딩을 다루겠습니다. 🧵