본문으로 건너뛰기
Previous
Next
MFC GDI+ | CDC·CBitmap·그래픽 처리 완벽 가이드

MFC GDI+ | CDC·CBitmap·그래픽 처리 완벽 가이드

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 멀티스레딩을 다루겠습니다. 🧵