GDI & GDI+

텍스트 Outline 효과

iwoohaha 2008. 9. 5. 15:59
반응형
텍스트에 Outline 효과를 부여하는 다양한 방법

1. 텍스트 출력 시작 좌표를 이동하는 방법
    for (int x = -m_nOutlineWidth; x <= m_nOutlineWidth; ++x)
        for (int y = -m_nOutlineWidth; y <= m_nOutlineWidth; ++y)
            pDC->TextOut(m_ptTextOut.x + x, m_ptTextOut.y + y, m_strText, m_strText.GetLength());
    pDC->SetTextColor(RGB(255,255,255));
    pDC->TextOut(m_ptTextOut.x, m_ptTextOut.y, m_strText, m_strText.GetLength());

원래의 텍스트를 출력하고자 하는 좌표값은 m_ptTextOut 이다.
m_nOutlineWidth 가 외곽선의 두께를 의미하는데,
두께만큼 텍스트 출력좌표를 이동해가면서 텍스트를 그린 후에
흰색으로 텍스트 색상을 변경한 후 원래 텍스트 출력 좌표에 텍스트를 출력하면 다음과 같은 결과가 나타난다.

2. GDI의 Path 오브젝트를 활용
    pDC->BeginPath();
    pDC->TextOut(m_ptTextOut.x, m_ptTextOut.y, m_strText, m_strText.GetLength());
    pDC->EndPath();

    CPen penOutline;
    penOutline.CreatePen(PS_SOLID, m_nOutlineWidth, m_colorText);

    CPen* pOldPen = pDC->SelectObject(&penOutline);
    pDC->StrokePath();
    pDC->SelectObject(pOldPen);

    pDC->SetTextColor(RGB(255,255,255));

    pDC->TextOut(m_ptTextOut.x, m_ptTextOut.y, m_strText, m_strText.GetLength());
CDC 클래스의 BeginPath 함수를 호출하여 Path 저장을 시작하여 텍스트를 출력한다.
EndPath 호출로 Path 저장을 완료하면 폰트의 외곽선이 Path에 저장된다.
적절한 두께의 Pen 오브젝트를 선택하여 StrokePath 함수로 Path를 그린 후
흰색으로 텍스트 색상을 변경하여 원래의 텍스트를 출력한다.
그 결과는 다음과 같다.

3. GDI+ 의 GraphicsPath 를 활용
참조 : http://support.microsoft.com/kb/307208
    Graphics g(pDC->GetSafeHdc());

    g.SetTextRenderingHint(TextRenderingHintAntiAlias);
    g.SetSmoothingMode(SmoothingModeAntiAlias);

    FontFamily fontFamily(m_strFamilyName);
    double fontSize = /*20.0 **/ m_dFontPoint;
    const StringFormat* pStringFormat = StringFormat::GenericTypographic();    // !!!
    Color colorText;
    colorText.SetFromCOLORREF(m_colorText);

    GraphicsPath path;
    path.AddString(m_strText, m_strText.GetLength(),
        &fontFamily,
        FontStyleRegular,
        fontSize,
        PointF(m_ptTextOut.x, m_ptTextOut.y),
        pStringFormat);

    Pen pen(colorText, m_nOutlineWidth);
    g.DrawPath(&pen, &path);

    pDC->SetTextColor(RGB(255,255,255));

    pDC->TextOut(m_ptTextOut.x, m_ptTextOut.y, m_strText, m_strText.GetLength());
GDI의 Path 를 사용하는 원리와 동일하다.
다만 GDI+ 의 객체를 사용하는 방법을 알아야 한다.
중요한 점은 StringFormat::GenericTypographic() 을 사용하는 부분이다.
우선 결과부터 확인해 보도록 하자.
GDI+에서는 Path를 따라서 그리는 경우에 AntiAliasing 효과를 줄 수 있으므로 훨씬 깔끔한 외곽선 효과가 나타날 수 있다.
앞에서 언급한 StringFormat::GenericTypographic() 을 사용하면 GDI에서 출력하는 글자의 폭과 GDI+에서 출력하는 글자의 폭을 동일시할 수 있게 된다.

예제 프로젝트

4. GDI에서 Path를 구하여 GDI+의 Path에 적용하는 방법
이 방법의 필요성은 다음과 같은 이유때문이다.
(1) GDI와 GDI+를 함께 사용해야 하는 경우
(2) AntiAliasing 효과를 적용시키기 위해서
우선 코드를 살펴보도록 하자(조금 길다).
    pDC->BeginPath();
    pDC->TextOut(m_ptTextOut.x, m_ptTextOut.y, m_strText, m_strText.GetLength());
    pDC->EndPath();

    int nNumPts = pDC->GetPath(NULL, NULL, 0);
    LPPOINT lpPoints = NULL;
    LPBYTE lpTypes = NULL;

    lpPoints = new POINT[nNumPts];
    lpTypes = new BYTE[nNumPts];

    nNumPts = pDC->GetPath(lpPoints, lpTypes, nNumPts);

    Graphics g(pDC->GetSafeHdc());

    g.SetTextRenderingHint(TextRenderingHintAntiAlias);
    g.SetSmoothingMode(SmoothingModeAntiAlias);

    GraphicsPath path;

    Point lastPoint;
    std::vector<Point> vecPoint;
    for (int i = 0; i < nNumPts; ++i)
    {
        Point point(lpPoints[i].x, lpPoints[i].y);

        switch (lpTypes[i])
        {
        case PT_MOVETO:
            lastPoint = point;
            if (i > 0)
                path.CloseFigure();
            path.StartFigure();
            break;
        case PT_LINETO:
            path.AddLine(lastPoint, point);
            lastPoint = point;
            break;
        case PT_BEZIERTO:
            if (vecPoint.empty())
            {
                vecPoint.push_back(lastPoint);
            }
            if (vecPoint.size() < 4)
            {
                vecPoint.push_back(point);
            }
            if (4 == vecPoint.size())
            {
                path.AddBezier(vecPoint[0], vecPoint[1], vecPoint[2], vecPoint[3]);

                vecPoint.clear();
                lastPoint = point;
            }
            break;
        case PT_CLOSEFIGURE:
            path.CloseFigure();
            break;
        }
    }
    path.CloseFigure();

    delete[] lpPoints;
    delete[] lpTypes;

    Color colorText;
    colorText.SetFromCOLORREF(m_colorText);
    Pen pen(colorText, m_nOutlineWidth);
    g.DrawPath(&pen, &path);

핵심 원리는 이렇다.
GDI에서 Path를 구한다.
Path를 이루는 각 포인트를 GDI+의 Path에 적용시킨다.

결과 화면은 다음과 같다.

예제 프로젝트

포스트에서 확인할 수 있는 이미지들은 외곽선의 두께가 모두 1이다.
AntiAliasing 효과를 제대로 확인해보기 위해서 두께값을 10 정도로 적용한 결과는 다음과 같다.
1.
2.
3.
4.
반응형