🎨 WinAPI 텍스트 출력 완벽 가이드 TextOut부터 DrawText까지
🖥️ 위치 기반과 박스 기반 렌더링을 모두 마스터하는 WinAPI 텍스트 처리 비법
C나 C++로 Windows 애플리케이션을 만들다 보면 화면에 텍스트를 출력하는 기능이 필수적으로 필요합니다.
특히 GUI 기반 프로그램에서 글자를 어떻게, 어디에, 어떤 스타일로 표시하느냐에 따라 사용자 경험이 크게 달라집니다.
하지만 처음 WinAPI의 TextOut, DrawText, SetTextColor, SetBkMode 같은 함수를 접하면 각각의 차이와 활용법이 헷갈리기 마련이죠.
이 글에서는 이러한 함수를 사용해 좌표 기반 텍스트 출력과 사각형 영역 기반의 텍스트 렌더링을 구현하는 방법을 단계별로 설명합니다.
또한 배경을 투명하게 처리하는 법, 글자 색상을 바꾸는 법, 줄바꿈과 정렬을 제어하는 옵션까지 모두 다룹니다.
여기에 실제 코드 예제와 함께 함수별 특징, 장단점, 사용 시 주의할 점까지 짚어드립니다.
프로그래밍 초보자도 WinAPI의 텍스트 처리 기능을 확실히 이해하고, 실전 프로젝트에 적용할 수 있도록 정리했으니 끝까지 읽어보시면 큰 도움이 될 것입니다.
📋 목차
🖋️ TextOut 함수로 좌표 기반 출력하기
🧭 TextOut 동작 원리와 좌표계
TextOut은 지정한 HDC에 대해 x, y 좌표를 기준으로 문자열을 즉시 그려 주는 WinAPI 함수입니다.
픽셀 단위의 절대 좌표를 사용하므로, 캔버스상 정확한 위치에 라벨이나 수치를 찍을 때 유용합니다.
줄바꿈이나 정렬을 자동 처리하지 않기 때문에, 한 줄 출력에 최적화되어 있고 복잡한 레이아웃에는 추가 로직이 필요합니다.
GDI의 현재 폰트, 텍스트 색상, 배경 모드 설정값에 따라 결과가 달라지므로 선행 설정이 중요합니다.
🔧 함수 시그니처와 기본 사용 패턴
ANSI와 유니코드 버전이 있으며, 프로젝트가 유니코드라면 TextOutW가 실제로 링크됩니다.
일반적으로 BeginPaint 또는 GetDC로 HDC를 획득하고, 원하는 속성 값을 설정한 뒤 TextOut을 호출합니다.
출력이 끝나면 EndPaint 또는 ReleaseDC로 정리합니다.
좌표는 디바이스 좌표계 기준이며, 스크롤이나 맵 모드 변환을 쓰지 않았다면 좌상단이 원점입니다.
// WM_PAINT 핸들러 예시 (Unicode 프로젝트 가정)
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
// 그리기 속성 설정
HFONT oldFont = (HFONT)SelectObject(hdc, hMyFont); // 미리 만든 폰트가 있다면 선택
COLORREF oldText = SetTextColor(hdc, RGB(34, 34, 34)); // 글자 색
int oldBk = SetBkMode(hdc, TRANSPARENT); // 배경 모드(투명 추천)
// 실제 출력: (x=24, y=32) 좌표에 문자열 1줄
const wchar_t* msg = L"Hello, TextOut!";
TextOutW(hdc, 24, 32, msg, lstrlenW(msg));
// 원상복구 및 정리
SetBkMode(hdc, oldBk);
SetTextColor(hdc, oldText);
SelectObject(hdc, oldFont);
EndPaint(hWnd, &ps);
return 0;
}
좌표는 왼쪽에서 오른쪽, 위에서 아래로 증가하므로, 텍스트의 좌상단 기준점이 (x, y)가 됩니다.
장치나 DPI 설정에 영향을 받을 수 있으므로, DPI 인식 앱이라면 논리 단위를 픽셀로 변환하거나 GetDpiForWindow와 같은 API로 보정하는 전략이 필요합니다.
여러 폰트를 번갈아 사용한다면 SelectObject로 현재 폰트를 바꾸고, 끝나면 반드시 원래 폰트를 되돌려 GDI 리소스 누수를 막아야 합니다.
- 🖊️HDC 획득: BeginPaint 또는 GetDC 사용
- 🎨SetTextColor와 SetBkMode로 색상과 배경 모드 선설정
- 📐출력 좌표 (x, y)는 텍스트의 좌상단 기준점임을 확인
- ♻️그리기 후 GDI 상태와 리소스 원복 필수
💡 TIP: 여러 줄을 출력해야 한다면 TextOut을 반복 호출하거나, 줄바꿈과 정렬을 지원하는 DrawText를 고려하세요.
좌표 기반 세밀 제어가 필요하면 TextOut, 레이아웃을 포함한 영역 기반이면 DrawText가 효율적입니다.
⚠️ 주의: 유니코드 문자열을 다루는 프로젝트에서는 TextOutW를 사용하세요.
ANSI와 혼용하면 글자 깨짐이 발생할 수 있고, 배경을 불투명으로 두면 사각형 네모가 생기므로 SetBkMode(hdc, TRANSPARENT)를 권장합니다.
📦 DrawText로 박스 영역에 글자 표시하기
🗂️ DrawText의 기본 개념과 활용 상황
DrawText는 지정된 사각형 영역(Rect)에 문자열을 출력하는 WinAPI 함수입니다.
TextOut과 달리 영역 내부에서 자동으로 줄바꿈, 정렬, 단어 단위 분할 등의 처리를 해줍니다.
이 덕분에 UI의 레이아웃과 텍스트 배치를 함께 고려해야 하는 경우에 매우 유용합니다.
대화상자, 툴팁, 상태 표시줄, 버튼 내부의 텍스트 출력 같은 상황에서 주로 사용됩니다.
⚙️ 플래그 옵션으로 출력 제어하기
DrawText는 다양한 플래그를 통해 정렬 방식(DT_LEFT, DT_CENTER, DT_RIGHT)과 줄바꿈(DT_WORDBREAK, DT_SINGLELINE)을 조정할 수 있습니다.
또한 DT_CALCRECT를 사용하면 텍스트를 실제로 그리지 않고 필요한 영역 크기만 계산하는 것도 가능합니다.
// DrawText 예시
RECT rc = { 20, 20, 300, 200 };
const wchar_t* msg = L"DrawText를 사용하면 영역 안에서 자동 줄바꿈과 정렬을 처리할 수 있습니다.";
SetTextColor(hdc, RGB(0, 0, 0));
SetBkMode(hdc, TRANSPARENT);
DrawTextW(
hdc,
msg,
-1, // NULL-terminated string
&rc,
DT_WORDBREAK | DT_CENTER
);
위 예시에서 DT_WORDBREAK는 단어 단위로 줄을 바꿔주고, DT_CENTER는 가로 중앙 정렬을 수행합니다.
이처럼 플래그 조합을 통해 다양한 출력 형태를 쉽게 구현할 수 있습니다.
- 📏출력할 RECT 좌표를 정확히 설정하기
- 🖌️SetTextColor와 SetBkMode로 시각 스타일 조정
- 🔍플래그 조합으로 줄바꿈과 정렬 방식을 제어
- 🧮DT_CALCRECT로 출력 전 필요한 영역 크기 계산
💎 핵심 포인트:
DrawText는 단순 출력뿐만 아니라 레이아웃 계산에도 활용됩니다. UI를 동적으로 구성하거나 다국어 지원 시 글자 크기에 따라 자동 배치하는 데 큰 도움이 됩니다.
🎨 SetTextColor로 글자 색상 변경하기
🧩 COLORREF와 RGB 매크로 이해하기
WinAPI에서 텍스트 색상은 COLORREF 형식으로 지정합니다.
일반적으로 RGB(r, g, b) 매크로를 사용하며 내부 저장 순서는 0x00BBGGRR입니다.
즉, 우리가 친숙한 24비트 RGB 값을 안전하게 넘길 수 있고 알파 채널은 지원하지 않습니다.
투명한 글자를 원한다면 색상의 알파가 아니라 배경 모드를 TRANSPARENT로 설정해야 합니다.
🛠️ 함수 시그니처와 반환값
COLORREF SetTextColor(HDC hdc, COLORREF color);
호출에 성공하면 이전에 설정되어 있던 텍스트 색상을 반환하고, 실패 시 CLR_INVALID를 반환합니다.
이전 색상을 보관해 두었다가 그리기 후 원복하면 GDI 상태를 깔끔하게 관리할 수 있습니다.
// SetTextColor 기본 사용 패턴
HFONT oldFont = (HFONT)SelectObject(hdc, hTitleFont);
COLORREF oldText = SetTextColor(hdc, RGB(25, 118, 210)); // 파란색
int oldBkMode = SetBkMode(hdc, TRANSPARENT); // 배경 투명
// (참고) OPAQUE 모드에서는 배경 색을 SetBkColor로 지정
TextOutW(hdc, 24, 24, L"제목 텍스트 (파란색)", 12);
// 상태 원복
SetBkMode(hdc, oldBkMode);
SetTextColor(hdc, oldText);
SelectObject(hdc, oldFont);
텍스트 색은 TextOut, DrawText 등 대부분의 GDI 텍스트 출력 함수에 공통 적용됩니다.
안티앨리어싱이나 ClearType이 활성화되어 있어도 지정한 RGB 값을 기반으로 렌더링되며, 알파 혼합은 글꼴 서브픽셀 처리 과정에서 배경과 시각적으로 블렌딩될 수 있습니다.
완전한 알파 투명 텍스트가 필요하면 GDI+의 Graphics::DrawString 또는 DrawThemeTextEx(DTT_COMPOSITED) 같은 대안을 검토하는 것이 좋습니다.
🎯 상황별 색상 적용 팁
상태 표시, 경고, 강조 텍스트 등 UI 의미를 색으로 표현할 때는 명암비와 배경 모드를 함께 고려해야 합니다.
불투명 배경이 필요하면 SetBkMode(hdc, OPAQUE)와 SetBkColor를 함께 사용하고, 배경을 살리고 싶다면 TRANSPARENT로 설정해 네모난 배경이 깔리는 것을 방지하세요.
다크 테마에서는 밝은 회색 계열을, 라이트 테마에서는 진한 중간톤을 쓰면 시인성이 좋습니다.
- 🎚️SetTextColor 반환값으로 이전 색상 저장 후 그리기 끝에 원복
- 🧪투명 효과는 색상의 알파가 아니라 SetBkMode(TRANSPARENT)로 구현
- 🌗라이트/다크 배경 대비를 고려해 RGB 선택, 필요 시 SetBkColor 병행
- 🔁여러 영역에 서로 다른 색을 그릴 땐 구간별로 색 설정과 원복을 반복
💬 색상은 정보 전달의 강력한 수단입니다.
하지만 시각장애인 접근성이나 색각 이상 사용자를 고려해 색상만으로 의미를 전달하지 않도록 레이아웃, 아이콘, 텍스트 조합을 함께 설계하세요.
⚠️ 주의: SetTextColor는 알파 채널을 지원하지 않습니다.
ARGB 값을 그대로 넘기면 예기치 않은 색으로 해석될 수 있으니 반드시 RGB(r, g, b)로 지정하세요.
팔레트 기반 장치나 프린터 DC에서는 색상 근사치로 렌더링될 수 있습니다.
🔍 SetBkMode로 배경 처리 제어하기
📜 배경 모드의 두 가지 방식
텍스트를 출력할 때, 배경 처리를 어떻게 할지는 SetBkMode 함수로 제어합니다.
모드는 크게 두 가지, OPAQUE와 TRANSPARENT가 있습니다.
OPAQUE는 글자 뒤의 배경을 지정한 배경색으로 칠한 뒤 텍스트를 그리며, TRANSPARENT는 배경을 칠하지 않고 기존 화면 내용을 그대로 둔 채 글자만 출력합니다.
UI 디자인, 가독성, 시각 효과에 따라 적절히 선택해야 합니다.
⚙️ 함수 시그니처와 동작
int SetBkMode(HDC hdc, int mode);
mode에는 OPAQUE 또는 TRANSPARENT를 지정합니다.
성공 시 이전 배경 모드를 반환하므로, 출력이 끝난 후 이를 복원해주는 것이 좋습니다.
배경색은 SetBkColor로 지정할 수 있으며, 이는 OPAQUE 모드일 때만 영향을 미칩니다.
// 배경 모드 설정 예시
COLORREF oldText = SetTextColor(hdc, RGB(255, 255, 255));
COLORREF oldBk = SetBkColor(hdc, RGB(33, 150, 243)); // 파란 배경
int oldBkMode = SetBkMode(hdc, OPAQUE);
TextOutW(hdc, 40, 40, L"OPAQUE 모드의 텍스트", 14);
// TRANSPARENT 모드로 변경
SetBkMode(hdc, TRANSPARENT);
TextOutW(hdc, 40, 80, L"TRANSPARENT 모드의 텍스트", 18);
// 상태 복원
SetBkMode(hdc, oldBkMode);
SetBkColor(hdc, oldBk);
SetTextColor(hdc, oldText);
TRANSPARENT 모드를 사용하면 배경이 그려지지 않아 깔끔하지만, 배경 이미지나 다른 UI 요소 위에서 가독성이 떨어질 수 있습니다.
OPAQUE는 글자가 확실히 보이도록 해 주지만, 주변과의 시각적 조화가 깨질 수 있으니 주의가 필요합니다.
특히 상태 표시줄이나 표 형태 UI에서는 셀 구분을 명확히 하기 위해 OPAQUE 모드가 자주 사용됩니다.
- 🎯가독성이 중요한 영역에는 OPAQUE 모드 사용
- 🌈배경색 변경 시 SetBkColor와 함께 사용
- 🖼️배경 이미지나 패턴 위에는 TRANSPARENT 모드로 자연스럽게
- ♻️출력 후 반드시 이전 모드로 복원
💎 핵심 포인트:
SetBkMode는 단순한 배경 투명 처리뿐 아니라 UI의 시각적 안정감과 가독성을 좌우합니다. 상황에 맞게 OPAQUE와 TRANSPARENT를 선택하는 것이 중요합니다.
⚡ 고급 활용법과 성능 최적화 팁
🚀 깜빡임 없는 텍스트 렌더링: 더블 버퍼링
스크롤이나 잦은 갱신이 있는 화면에서 TextOut과 DrawText를 직접 호출하면 깜빡임이 발생할 수 있습니다.
이때 메모리 DC를 활용한 더블 버퍼링이 효과적입니다.
오프스크린 비트맵에 모든 요소를 먼저 그린 뒤, 최종적으로 BitBlt로 한 번에 전송하면 플리커를 크게 줄일 수 있습니다.
배경 지우기, 선 그리기, 텍스트 출력의 순서를 고정하면 합성 비용과 오버드로를 줄이고 일관된 결과를 얻을 수 있습니다.
// 깜빡임 최소화를 위한 더블 버퍼링 패턴
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
RECT rc; GetClientRect(hWnd, &rc);
HDC memDC = CreateCompatibleDC(hdc);
HBITMAP bmp = CreateCompatibleBitmap(hdc, rc.right, rc.bottom);
HBITMAP oldBmp = (HBITMAP)SelectObject(memDC, bmp);
// 배경 지우기
HBRUSH bg = (HBRUSH)(COLOR_WINDOW+1);
FillRect(memDC, &rc, bg);
// 텍스트 스타일 설정
HFONT oldFont = (HFONT)SelectObject(memDC, hMyFont);
int oldBk = SetBkMode(memDC, TRANSPARENT);
COLORREF oldText = SetTextColor(memDC, RGB(33,33,33));
// 텍스트 출력 (좌표 기반 + 영역 기반 혼용)
TextOutW(memDC, 24, 24, L"좌표 기반 텍스트", 9);
RECT box = {24, 64, rc.right-24, 140};
DrawTextW(memDC, L"박스 기반 텍스트 - 자동 줄바꿈 및 정렬", -1, &box,
DT_WORDBREAK | DT_LEFT);
// 상태 복원
SetTextColor(memDC, oldText);
SetBkMode(memDC, oldBk);
SelectObject(memDC, oldFont);
// 한 번에 화면으로 전송
BitBlt(hdc, 0, 0, rc.right, rc.bottom, memDC, 0, 0, SRCCOPY);
// 해제
SelectObject(memDC, oldBmp);
DeleteObject(bmp);
DeleteDC(memDC);
EndPaint(hWnd, &ps);
📏 텍스트 크기 계산과 줄바꿈 제어
UI 배치에는 정확한 텍스트 치수 계산이 필요합니다.
한 줄 폭이 필요한 경우 GetTextExtentPoint32로 픽셀 폭과 높이를 구하고, 여러 줄 배치는 DrawText의 DT_CALCRECT를 활용해 영역을 먼저 산출합니다.
Alt 키 힌트처럼 앰퍼샌드(&) 문자를 있는 그대로 표시하려면 DT_NOPREFIX를, 긴 문자열을 말줄임표로 처리하려면 DT_END_ELLIPSIS를 조합하세요.
문자열이 영역을 넘어가더라도 잘리지 않게 하려면 적절한 여백과 패딩을 유지하는 것이 중요합니다.
| 상황 | 권장 API/옵션 |
|---|---|
| 정확한 폭 계산 | GetTextExtentPoint32, SelectObject로 폰트 선적용 |
| 레이아웃에 맞춘 높이 계산 | DrawText + DT_CALCRECT |
| 말줄임표 표시 | DrawText + DT_END_ELLIPSIS |
| 접두사(&) 무시 | DrawText + DT_NOPREFIX |
🧱 속도와 품질을 모두 잡는 실전 패턴
빈번한 폰트 생성은 성능을 크게 떨어뜨립니다.
CreateFontIndirect로 필요한 폰트를 초기화 단계에서 한 번만 만들고, 렌더링 시에는 SelectObject로 교체하는 패턴을 사용하세요.
그리기 루프에서는 상태 변경 횟수를 최소화하기 위해 같은 색, 같은 폰트, 같은 배경 모드를 묶어서 처리하는 것이 좋습니다.
또한 DPI 인식(Per-Monitor v2)을 켜고 논리 크기를 픽셀로 변환해 오차를 줄이면 텍스트가 흐릿해지는 문제를 예방할 수 있습니다.
- 🧰더블 버퍼링으로 플리커 최소화 후 BitBlt로 일괄 전송
- 🗜️폰트/색/배경 모드를 구간별로 묶어 상태 변경 비용 절감
- 📐GetTextExtentPoint32와 DT_CALCRECT로 레이아웃 정확도 확보
- 🖥️Per‑Monitor DPI 인식과 스케일 보정 적용
💎 핵심 포인트:
좌표 기반(TextOut)과 박스 기반(DrawText)을 혼용하되, 레이아웃 계산은 DT_CALCRECT, 속도는 버퍼링과 상태 최소화로 확보하세요.
색상은 SetTextColor, 배경은 SetBkMode로 제어하는 기본 원칙을 지키면 복잡한 UI에서도 안정적인 텍스트 품질을 유지할 수 있습니다.
⚠️ 주의: 매 프레임마다 CreateFont/DeleteObject를 반복하면 GDI 핸들이 고갈되고 성능 저하가 일어납니다.
초기화 시 폰트를 생성해 캐시하고, 사용 후에는 반드시 원복과 해제를 철저히 관리하세요.
또한 TRANSPARENT 모드에서 밝은 배경 위에 연한 글자를 쓰면 가독성이 급격히 떨어질 수 있습니다.
❓ 자주 묻는 질문 (FAQ)
TextOut과 DrawText의 차이점은 무엇인가요?
SetTextColor로 투명한 글자를 만들 수 있나요?
DrawText에서 줄바꿈 없이 한 줄로만 출력하려면?
SetBkMode의 OPAQUE 모드와 TRANSPARENT 모드 차이는?
GDI 텍스트 출력 시 폰트 크기가 DPI에 따라 달라지는 이유는?
DrawText에서 말줄임표(…)를 표시하려면 어떻게 하나요?
TextOut으로 여러 줄 출력이 가능한가요?
DrawText와 ExtTextOut의 차이는 무엇인가요?
🧠 WinAPI 텍스트 렌더링 핵심 정리
이 글에서는 WinAPI 환경에서 좌표 기반의 TextOut과 사각형 영역 기반의 DrawText를 활용해 텍스트를 출력하는 전 과정을 정리했습니다.
렌더링 품질과 일관성을 위해 SetTextColor로 색을 지정하고, 배경 처리는 SetBkMode의 TRANSPARENT/OPAQUE를 상황에 맞게 선택하는 것이 핵심입니다.
여러 줄 배치나 정렬, 말줄임표 등 레이아웃 이슈는 DrawText의 플래그 조합과 DT_CALCRECT로 선 계산하는 방식이 안정적입니다.
성능 최적화를 위해서는 더블 버퍼링으로 플리커를 줄이고, 폰트/색/배경 상태 변경을 최소화하는 렌더링 순서를 채택해야 합니다.
텍스트 크기 계산은 GetTextExtentPoint32와 DrawText + DT_CALCRECT를 병행하면 정확도가 높아집니다.
DPI 인식 앱에서는 스케일 보정을 통해 흐림 현상을 방지하고, 필요 시 ExtTextOut이나 GDI+ 같은 대안을 조합해 품질과 제어력을 동시에 확보할 수 있습니다.
🏷️ 관련 태그 : WinAPI, GDI, TextOut, DrawText, SetTextColor, SetBkMode, ExtTextOut, GetTextExtentPoint32, DPI스케일링, Windows프로그래밍