관리 메뉴

cyphen156

Win32 API #2 GDI 본문

프로그래밍/Win32 API

Win32 API #2 GDI

cyphen156 2024. 9. 25. 01:57

GDI는 화면에 무엇인가를 보여주기 위해 사용되는 하드웨어에 의존하지 않는 가상 그래픽장치 인터페이스로, 2D그래픽을 처리한다.

wingui.h함수 안에 존재하는데 windows.h안에 기본적으로 포함되어 있으므로 따로 호출할 필요가 없다.

DC(Device Context)

윈도우 클라이언트 영역에서 그리기 정보를 의미한다. HDC라는 핸들을 통해 운영체제가 자원을 직접 관리하고 있으므로 호출 했다면 반드시 반납해야 하는 메모리 관리가 필요하다.

다음과 같이 WndProc함수 안에 DC함수 사용이 존재한다. 

case WM_PAINT:
    {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hWnd, &ps);
        // TODO: 여기에 hdc를 사용하는 그리기 코드를 추가합니다...
        EndPaint(hWnd, &ps);
    }
    break;

// *LPPAINTSTRUCT는 경우에 따라서 PAINTSTRUCT, PPAINTSTRUCT, *NPPAINTSTRUCT로 사용된다. (호환성 관련)

WM_PAINT 메세지 사용 함수

DC 호출시 사용하는 HDC BeginPaint(HWND hWnd, LPPAINTSTRUCT lpPaint);

DC 반납시 사용하는 BOOL EndPaint( HWND hWnd, const PAINTSTRUCT *lpPaint);

같은 기능을 하지만 WM_PAINT 외 다른 메세지에서 사용되는 함수

GetDC( HWND hWnd )

ReleaseDC( HWND hWnd, HDC hdc)

좌표 구조체 POINT

익숙한 POINT 구조체다 OpenCV Point_ 자료형과는 다르게 함수 없이 오로지 좌표만을 정수형(LONG)으로 저장하고 있다. 

typedef struct tagPOINT
{
    LONG  x;
    LONG  y;
} POINT, *PPOINT, NEAR *NPPOINT, FAR *LPPOINT;

typedef struct _POINTL      /* ptl  */
{
    LONG  x;
    LONG  y;
} POINTL, *PPOINTL;

typedef struct tagPOINTS
{
#ifndef _MAC
    SHORT   x;
    SHORT   y;
#else
    SHORT   y;
    SHORT   x;
#endif
} POINTS, *PPOINTS, *LPPOINTS;

길이정보 구조체 SIZE

typedef struct tagSIZE
{
    LONG        cx;
    LONG        cy;
} SIZE, *PSIZE, *LPSIZE;

typedef SIZE               SIZEL;
typedef SIZE               *PSIZEL, *LPSIZEL;

사각형 구조체 _RECT

역시 OpenCV와 유사하게 생겻다. 하지만 다른점은 길이 정보 대신 끝좌표에 대한 정보를 받고 있다는 것이다.

POINT 구조체와는 다르게 생성 함수(SetRect())가 WinUser 헤더 안애 존재한다.

typedef struct tagRECT
{
    LONG    left;
    LONG    top;
    LONG    right;
    LONG    bottom;
} RECT, *PRECT, NEAR *NPRECT, FAR *LPRECT;

typedef const RECT FAR* LPCRECT;

typedef struct _RECTL       /* rcl */
{
    LONG    left;
    LONG    top;
    LONG    right;
    LONG    bottom;
} RECTL, *PRECTL, *LPRECTL;

typedef const RECTL FAR* LPCRECTL;

윈도우 크기를 반환하는 GetClientRect()와 사각형 구조체를 생성하는 SetRect()

BOOL WINAPI GetWindowRect(_In_ HWND hWnd, _Out_ LPRECT lpRect);

WINUSERAPI
BOOL
WINAPI
GetClientRect(
    _In_ HWND hWnd,
    _Out_ LPRECT lpRect);

#endif /* WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | WINAPI_PARTITION_GAMES) */
#pragma endregion

#pragma region Desktop Family
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)

WINUSERAPI
BOOL
WINAPI
GetWindowRect(
    _In_ HWND hWnd,
    _Out_ LPRECT lpRect);

#endif /* WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) */

RECT관련 함수들

RECT함수는 여러가지가 존재한다.

  • DrawFocusRect() : 강조 영역 그리기
  • FillRect() : 사각형 색상 채우기
  • FrameRect() : 프레임 그리기
  • InvertRect() : 사각형 색상을 반전시키기
  • SetRect() : 사각형 생성
  • SetRectEmpty() : 빈 사각형 객체 만들기
  • CopyRect() : 사각형 깊은 복사하기
  • InflateRect() : 사각형 크기 확장하기
  • IntersectRect() : 두 사각형의 교집합 구하기
  • UnionRect() : 두 사각형의 합집합 구하기
  • SubtractRect() : 두 사각형의 차집합 구하기
  • OffsetRect() : 사각형의 좌표 이동시키기
  • GetWindowRect() : 클라이언트 크기가 아닌 창의 크기와 위치 반환
WINUSERAPI
BOOL
WINAPI
DrawFocusRect(
    _In_ HDC hDC,
    _In_ CONST RECT * lprc);

WINUSERAPI
int
WINAPI
FillRect(
    _In_ HDC hDC,
    _In_ CONST RECT *lprc,
    _In_ HBRUSH hbr);

WINUSERAPI
int
WINAPI
FrameRect(
    _In_ HDC hDC,
    _In_ CONST RECT *lprc,
    _In_ HBRUSH hbr);

WINUSERAPI
BOOL
WINAPI
InvertRect(
    _In_ HDC hDC,
    _In_ CONST RECT *lprc);

WINUSERAPI
BOOL
WINAPI
SetRect(
    _Out_ LPRECT lprc,
    _In_ int xLeft,
    _In_ int yTop,
    _In_ int xRight,
    _In_ int yBottom);

WINUSERAPI
BOOL
WINAPI
SetRectEmpty(
    _Out_ LPRECT lprc);

WINUSERAPI
BOOL
WINAPI
CopyRect(
    _Out_ LPRECT lprcDst,
    _In_ CONST RECT *lprcSrc);

WINUSERAPI
BOOL
WINAPI
InflateRect(
    _Inout_ LPRECT lprc,
    _In_ int dx,
    _In_ int dy);

WINUSERAPI
BOOL
WINAPI
IntersectRect(
    _Out_ LPRECT lprcDst,
    _In_ CONST RECT *lprcSrc1,
    _In_ CONST RECT *lprcSrc2);

WINUSERAPI
BOOL
WINAPI
UnionRect(
    _Out_ LPRECT lprcDst,
    _In_ CONST RECT *lprcSrc1,
    _In_ CONST RECT *lprcSrc2);

WINUSERAPI
BOOL
WINAPI
SubtractRect(
    _Out_ LPRECT lprcDst,
    _In_ CONST RECT *lprcSrc1,
    _In_ CONST RECT *lprcSrc2);

WINUSERAPI
BOOL
WINAPI
OffsetRect(
    _Inout_ LPRECT lprc,
    _In_ int dx,
    _In_ int dy);

텍스트 출력하기

TextOut(고정좌표 생성) : 윈도우의 크기에 영향을 받지 않는 출력

멀티바이트는 TextOutA를, 유니코드라면 TextOutw 함수를 사용한다.

현대 윈도우즈 프로그램은 대부분 유니코드를 사용하여 제작되지만 간혹 멀티바이트를 사용하는 경우도 존재한다. 이것을 대응하기 위해 TextOut함수를 사용하여 프로젝트 설정에 따라 컴파일시 자동으로 함수가 재정의 되어 사용되도록 매크로를 걸어놓은 것이니 코드읽을때 이해만 할 수 있으면 되므로 세 함수의 차이를 알아만 두면 된다. 

사용법은 제각각인데 인자로 전달하는 문자 자료형에 있다.

최대 13길이의 문자열(Hello, World!...)을 hdc윈도우에 x, y좌표에 출력한다.

  • 멀티바이트 사용 : TextOutA(hdc, x, y, "Hello, World!", 13);
  • 유니코드 사용 : TextOutW(hdc, x, y, L"Hello, World!", 13);
  • 유니코드/멀티바이트 자동 대응: TextOut(hdc, x, y, TEXT("Hello, World!"), 13);
WCHAR str[128] = L"Hello World!";
TextOut(hdc, 10, 10, str, 20);

텍스트 관련 함수

SetTextColor(HDC hdc, RGB(R, G, B));

SetBkColor(HDC hdc, RGB(R, G, B));

SetTextAllign(HDC hdc, UNIT fMode); // fMode는 TA_TOP/BOTTOM/CENTER | TA_LEFT/RIGHT/BASELINE

SetTextColor(hdc, RGB(255, 0, 0));
SetBkColor(hdc, RGB(0, 0, 255));
WCHAR str2[128] = L"안녀어어어어엉";
TextOut(hdc, 25, 200, str2, 20);

DrawText() : 윈도우의 크기에 따라 달라지는 적응형 출력

 RECT RT;
 GetClientRect(hWnd, &RT);
 DrawText(hdc, L"텍스트그리기", -1, &RT, DT_CENTER | DT_VCENTER | DT_SINGLELINE);

도형그리기

점그리기

점을 그릴때는 픽셀 값의 색상을 변경하여 그린다.

SetPixel(hdc, x, y, RGB(R, G, B));

기본적으로 모든 도형은 set픽셀을 반복문을 통해 처리함으로써 그릴 수 있다. 하지만 SetPixel은 덧씌우는 개념이기 때문에 배경을 채우거나 하는데 쓰지 않는다.

선 그리기

좌표를 이동시키는 MoveToEx(hdc, x, y, ppt);

직선을 그리는 LineTo(hdc, x, y);

이 둘을 조합하여 선을 그리게 된다.

// 선그리기
LineTo(hdc, 100, 100); // 0, 0 -> 100, 100
Sleep(1000);
LineTo(hdc, 75, 240); // 100, 100 -> 75, 240
Sleep(1000);
MoveToEx(hdc, 25, 300, NULL); // 75, 240 -> 25, 300
Sleep(1000);
LineTo(hdc, 300, 25);

사각형 그리기

Rectangle(hdc, left, top, rignt, bottom);

타원 그리기

사각형의 좌표 범위 안에 내접원을 그린다.

Ellipse(hdc, left, top, rignt, bottom);

 GDI 속성 변경하기

속성을 변경하려면 우선 속성 변경을 위한 객체 선택을 해야 한다.

CreateObject(selectInstance)를 통해 속성변경을 위한 객체를 생성하고

DeleteObject()를 통해 생성된 객체를 제거한다.

한가지 특이한 점은 기본 메세지루프 Switch문 밖에서 먼저 공유 인스턴스를 생성하고,  WM_Create문을 통해 실제 인스턴스를 할당한다. 그리고, WM_Destroy문 안에서 생성된 객체를 파괴하는 식으로 관리한다. 

WM_Create에서 프로그램 전체적으로 사용할  GDI속성을 지정하고, 상황에 따라 인스턴스를 임시로 변경하여 사용함으로 써 인스턴스 생성이 과도하게 호출되는것을 막고, 메모리 사용을 방지한다.

WM_Destroy에서 GDI 속성 객체를 제거함으로써 혹시 모를 메모리 누수를 방지하는 역할을 한다.

+++static 자료형으로 선언하여 정적 변수로 지정할 경우 혹시모를 인스턴스 파괴를 방지 할 수 있기 때문에 항상 습관을 들이자.

폰트 객체를 통해 확인하자면 기본사용 방법은 다음과 같다.

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    static HFONT hFont;
    switch (message)
    {
    case WM_COMMAND:
        {
            int wmId = LOWORD(wParam);
            // 메뉴 선택을 구문 분석합니다:
            switch (wmId)
            {
            case IDM_ABOUT:
                DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
                break;
            case IDM_EXIT:
                DestroyWindow(hWnd);
                break;
            default:
                return DefWindowProc(hWnd, message, wParam, lParam);
            }
        }
        break;
    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            // TODO: 여기에 hdc를 사용하는 그리기 코드를 추가합니다...
            //폰트 바꿔서 그리기

            // 기존 폰트가 사라지는것을 방지하기 위한 변수 할당
            // SelectObject는 새로운 객체를 클라이언트에 할당하고
            // 반환 값으로 이전 객체를 반환 하기 때문이다.
            HFONT hOldFont = (HFONT)SelectObject(hdc, hFont);

            EndPaint(hWnd, &ps);
        }
        break;
    case WM_CREATE:
        {
            hFont = CreateFont(
                20,         // 세로 길이
                10,         // 가로 길이 (0으로 두면 자동 조정)
                0,          // 문자열 기울기 (단위: 0.1도)
                0,          // 문자 회전 각도
                FW_NORMAL,  // 두께 (FW_NORMAL, FW_BOLD 등)
                FALSE,      // 이탤릭 여부
                FALSE,      // 밑줄 여부
                FALSE,      // 취소선 여부
                DEFAULT_CHARSET, // 문자셋 (한글은 HANGUL_CHARSET)
                OUT_DEFAULT_PRECIS, // 출력 정밀도
                CLIP_DEFAULT_PRECIS, // 클리핑 정밀도
                DEFAULT_QUALITY,     // 출력 품질
                DEFAULT_PITCH | FF_SWISS, // 글꼴 스타일
                TEXT("Arial"));      // 글꼴 이름
            break;
        }
    case WM_DESTROY:
        DeleteObject(hFont);
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

새로운 폰트 적용하기

//폰트 바꿔서 그리기

// 새로운 폰트가 아닌 기본 폰트를 저장
HFONT holdFont = (HFONT)SelectObject(hdc, hFont);


SetTextColor(hdc, RGB(0, 255, 0));
WCHAR changeFont[128] = L"폰트가 바뀌어서 출력되요";
TextOut(hdc, 150, 150, changeFont, 15);

기존 폰트로 복귀하기

SelectObject(hdc, holdFont);

// 기본 폰트로 복귀
SetTextColor(hdc, RGB(0, 0, 255));
SetBkColor(hdc, RGB(128, 0, 0));
WCHAR defalutFont[128] = L"기본 폰트로 돌아왔어요";
TextOut(hdc, 150, 175, changeFont, 20);

변경된 폰트는 잘 확인해 보면 공백 문자열이 '\0'를 의미하는 공집합 표시가 아니라 아무것도 보여주지 않고 있는 것을 보여주고 있다. 

폰트

CreateFont(세로길이, 가로길이, 문자열 기울기(0.1도), 각 문자 각도, 두께, 문자열 세팅...)

폰트는 생성할 때 엄청 길다. 그러니까 그냥 그때그때 맞춰서  GPT한테 물어보거나 구글링을 통해 알맞는 폰트 코드를 생성하여 쓰도록한다.

HFONT hFont = CreateFont(
    20,         // 세로 길이
    10,         // 가로 길이 (0으로 두면 자동 조정)
    0,          // 문자열 기울기 (단위: 0.1도)
    0,          // 문자 회전 각도
    FW_NORMAL,  // 두께 (FW_NORMAL, FW_BOLD 등)
    FALSE,      // 이탤릭 여부
    FALSE,      // 밑줄 여부
    FALSE,      // 취소선 여부
    DEFAULT_CHARSET, // 문자셋 (한글은 HANGUL_CHARSET)
    OUT_DEFAULT_PRECIS, // 출력 정밀도
    CLIP_DEFAULT_PRECIS, // 클리핑 정밀도
    DEFAULT_QUALITY,     // 출력 품질
    DEFAULT_PITCH | FF_SWISS, // 글꼴 스타일
    TEXT("Arial"));      // 글꼴 이름

 

펜 객체는 도형의 테두리를 설정할 때 사용한다. 

폰트와 다르게 펜 객체는 상당히 간단하다

HPEN hPen = CreatePen(선 속성, 두께, RGB색상);

브러시

브러시 객체는 도형 채우기에 사용한다. 

브러시 객체는 두 가지 종류가 존재한다. 

  • SolidBrush : 단색 채우기 브러시
  • HatchBrush : 다양한 무늬옵션이 존재하는 브러시
// 펜과 브러시 실습하기
HPEN hOldPen = (HPEN)SelectObject(hdc, hPen);
HBRUSH hOldBrush = (HBRUSH)SelectObject(hdc, hBrush);

// 큰 사각형 생성
Rectangle(hdc, 400, 400, 500, 500);

//HBrush2사용
SelectObject(hdc, hOldPen);
SelectObject(hdc, hBrush2);
Ellipse(hdc, 400, 400, 450, 450);

스톡 개체

스톡개체는 미리 제공되는 HDI 옵션으로 SetDCPenColor, SetDCBrushColor를 통해 색상을 설정하고, GetStockObject을 통해 DC객체를 생성할 수 있다.

이미 시스템에서 제공되고 있는 옵션이기 때문에 프로그래머가 따로 할당하거나 해제할 필요 없이 그냥 사용하기만 하면 되서 편하지만 몇가지 없다.

펜과 브러시, 폰트 몇가지가 미리 제공되는데 다음과 같다.

 

  • DC_PEN: 기본 펜 (사용자 지정 색상 가능)
  • DC_BRUSH: 기본 브러시 (사용자 지정 색상 가능)
  • SYSTEM_FONT: 시스템에서 기본으로 사용하는 폰트
  • GRAY_BRUSH: 회색 브러시
  • WHITE_BRUSH: 흰색 브러시
  • BLACK_BRUSH: 검정색 브러시
  • NULL_PEN: 투명한 펜 (테두리 없음)

 

HPEN hPen = (HPEN)GetStockObject(DC_PEN);         // 스톡 펜 사용
HBRUSH hBrush = (HBRUSH)GetStockObject(DC_BRUSH); // 스톡 브러시 사용

// DC 펜과 브러시의 색상 설정
SetDCPenColor(hdc, RGB(0, 0, 255));  // 파란색 펜
SetDCBrushColor(hdc, RGB(0, 255, 0)); // 녹색 브러시

// 사각형 그리기
Rectangle(hdc, 100, 100, 200, 200);

위의 스크린샷은 단순히 이미 렌더링된 이미지 위에 다른 이미지를 덧씌워서 보여주고 있다.

만약 덧씌우는것이 아닌 렌더링 되지 않을 부분을 제거하고 보여질 부분만을 렌더링 하는것클리핑(Clipping)이라고 하며 두가지의 연산 방식이 존재한다.

비트맵(레스터 그래픽)

비트맵 연산은 픽셀 기반의 레스터 그래픽으로 주로 이미지나 텍스쳐를 표현할 때 사용된다고 한다. 고정된 해상도를 가지고 있어서 크기를 조정할 때 화질의 손상이 갈 수 있다는 단점이 있지만 말 그대로 비트연산을 통해 연산을 처리하기 때문에 렌더링을 빠르게 처리할 수 있다는 장점이 있다. 

int SetROP2(hdc, fnDrawMode);

+++* fnDrawMode: 래스터 연산 모드(Raster Operation Mode). 비트연산자 지정

사용되는 주요 인자로는 다음이 있다. 

 

  • R2_NOT : 화면에 출력하지 않음
  • R2_COPYPEN : 펜 색상을 그대로 복사하여 대상 영역에 출력 기본 모드.
  • R2_NOTCOPYPEN : 펜 색상을 반전하여 출력
  • R2_XORPEN : 대상 영역과 펜 색상을 XOR 연산으로 합쳐서 출력.
  • R2_MERGEPEN : 펜 색상과 대상 영역을 OR 연산으로 합쳐서 출력.
  • R2_MASKPEN : 펜 색상과 대상 영역을 AND 연산으로 합쳐서 출력.

 

리전(벡터그래픽)

리전은 보통 벡터 연산을 통해 활성 영역을 계산한다. 비트맵과는 다르게 해상도가 유지되기 때문에 특별이 매우매우매우매우 빠른 성능이 중요한것이 아니라면 리전을 통해 연산한다.

HRGN 구조체를 통해 선언되어 있으며, 사용되는 함수는 다음과 같다. 

  • CreateRectRgn() : 사각형 리전
  • CreateRectRgnIndirect() : 간접지정 리전, 이미 생성된 객체를 전달하여 리전으로 사용
  • CreateEllipticRgn() : 타원형 리전
  • CreateEllipticRgnIndirect() : 간접지정 리전, 이미 생성된 객체를 전달하여 리전으로 사용
  • CreatePolygonRgn() : 다각형 리전, 좌표 배열에 따라 다각형을 정의
    CreatePolygonRgn(const POINT *lppt, int cPoints, int fnPolyFillMode);
    • lppt: 다각형을 이루는 점의 배열.
    • cPoints: 점의 개수.
    • fnPolyFillMode: 다각형 채우기 방식 (ALTERNATE 또는 WINDING).
  • CombineRgn() : 두 리전의 연산 결과를 새로운 리전으로 생성한다
    int CombineRgn(HRGN hrgnDest, HRGN hrgnSrc1, HRGN hrgnSrc2, int fnCombineMode);
    • hrgnDest: 결과를 저장할 리전.
    • hrgnSrc1: 첫 번째 리전.
    • hrgnSrc2: 두 번째 리전.
    • fnCombineMode: 결합 방식 (RGN_AND, RGN_OR, RGN_XOR, RGN_DIFF, RGN_COPY)
  • SelectClipRgn() : 클리핑 영역으로 설정하기
  • PtInRegion() : 리전 안에 특정 점이 존재하는지 확인하기

전체 코드는 다음과 같습니다.

// Win32API.cpp : 애플리케이션에 대한 진입점을 정의합니다.
//

#include "framework.h"
#include "Win32API.h"
#include "iostream"

#define MAX_LOADSTRING 100

// 전역 변수:
HINSTANCE hInst;                                // 현재 인스턴스입니다.
WCHAR szTitle[MAX_LOADSTRING];                  // 제목 표시줄 텍스트입니다.
WCHAR szWindowClass[MAX_LOADSTRING];            // 기본 창 클래스 이름입니다.

// 이 코드 모듈에 포함된 함수의 선언을 전달합니다:
ATOM                MyRegisterClass(HINSTANCE hInstance);
BOOL                InitInstance(HINSTANCE, int);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK    About(HWND, UINT, WPARAM, LPARAM);

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,  // main(str argc, srargv)
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    // TODO: 여기에 코드를 입력합니다.

    // 전역 문자열을 초기화합니다.
    LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    LoadStringW(hInstance, IDC_WIN32API, szWindowClass, MAX_LOADSTRING);
    MyRegisterClass(hInstance);

    // 애플리케이션 초기화를 수행합니다:
    if (!InitInstance (hInstance, nCmdShow))
    {
        return FALSE;
    }

    HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WIN32API));

    MSG msg;

    // 기본 메시지 루프입니다:
    while (GetMessage(&msg, nullptr, 0, 0))
    {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    return (int) msg.wParam;
}



//
//  함수: MyRegisterClass()
//
//  용도: 창 클래스를 등록합니다.
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEXW wcex;

    wcex.cbSize = sizeof(WNDCLASSEX);

    wcex.style          = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc    = WndProc;
    wcex.cbClsExtra     = 0;
    wcex.cbWndExtra     = 0;
    wcex.hInstance      = hInstance;
    wcex.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WIN32API));
    wcex.hCursor        = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
    wcex.lpszMenuName   = MAKEINTRESOURCEW(IDC_WIN32API);
    wcex.lpszClassName  = szWindowClass;
    wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

    return RegisterClassExW(&wcex);
}

//
//   함수: InitInstance(HINSTANCE, int)
//
//   용도: 인스턴스 핸들을 저장하고 주 창을 만듭니다.
//
//   주석:
//
//        이 함수를 통해 인스턴스 핸들을 전역 변수에 저장하고
//        주 프로그램 창을 만든 다음 표시합니다.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   hInst = hInstance; // 인스턴스 핸들을 전역 변수에 저장합니다.

   HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);

   if (!hWnd)
   {
      return FALSE;
   }

   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   return TRUE;
}

//
//  함수: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  용도: 주 창의 메시지를 처리합니다.
//
//  WM_COMMAND  - 애플리케이션 메뉴를 처리합니다.
//  WM_PAINT    - 주 창을 그립니다.
//  WM_DESTROY  - 종료 메시지를 게시하고 반환합니다.
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    static HFONT hFont;
    static HPEN hPen;
    static HBRUSH hBrush, hBrush2;

    switch (message)
    {
    case WM_COMMAND:
        {
            int wmId = LOWORD(wParam);
            // 메뉴 선택을 구문 분석합니다:
            switch (wmId)
            {
            case IDM_ABOUT:
                DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
                break;
            case ID_32771:
            {
                HWND hDlg = CreateDialog(hInst, MAKEINTRESOURCE(IDD_32771), hWnd, About);
                if (hDlg != NULL)
                {
                    ShowWindow(hDlg, SW_SHOW);
                }
                else
                {
                    MessageBox(hWnd, L"CreateDialog 실패", L"오류", MB_OK | MB_ICONERROR);
                }
            }
            break;
            case IDM_EXIT:
                DestroyWindow(hWnd);
                break;
            default:
                return DefWindowProc(hWnd, message, wParam, lParam);
            }
        }
        break;
    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            // TODO: 여기에 hdc를 사용하는 그리기 코드를 추가합니다...
            WCHAR str[128] = L"Hello World!";
            TextOut(hdc, 10, 10, str, 20);
            SetTextColor(hdc, RGB(255, 0, 0));
            SetBkColor(hdc, RGB(0, 0, 255));
            WCHAR str2[128] = L"안녀어어어어엉";
            TextOut(hdc, 25, 200, str2, 20);
            RECT RT;
            GetClientRect(hWnd, &RT);
            DrawText(hdc, L"텍스트그리기", -1, &RT, DT_CENTER | DT_VCENTER | DT_SINGLELINE);

            // 선그리기
            LineTo(hdc, 100, 100); // 0, 0 -> 100, 100
            //Sleep(1000);
            LineTo(hdc, 75, 240); // 100, 100 -> 75, 240
            //Sleep(1000);
            MoveToEx(hdc, 25, 300, NULL); // 75, 240 -> 25, 300
            //Sleep(1000);
            LineTo(hdc, 300, 25);
            // 사각형 그리기
            Rectangle(hdc, 50, 50, 150, 150);

            // 타원그리기
            Ellipse(hdc, 200, 200, 300, 300);
            

            //폰트 바꿔서 그리기

            // 기존 폰트가 사라지는것을 방지하기 위한 변수 할당
            // SelectObject는 새로운 객체를 클라이언트에 할당하고
            // 반환 값으로 이전 객체를 반환 하기 때문이다.            
            HFONT holdFont = (HFONT)SelectObject(hdc, hFont);


            SetTextColor(hdc, RGB(0, 255, 0));
            WCHAR changeFont[128] = L"폰트가 바뀌어서 출력되요";
            TextOut(hdc, 150, 150, changeFont, 15);
            
            SelectObject(hdc, holdFont);

            // 기본 폰트로 복귀
            SetTextColor(hdc, RGB(0, 0, 255));
            SetBkColor(hdc, RGB(128, 0, 0));
            WCHAR defalutFont[128] = L"기본 폰트로 돌아왔어요";
            TextOut(hdc, 150, 175, changeFont, 20);

            // 펜과 브러시 실습하기
            HPEN hOldPen = (HPEN)SelectObject(hdc, hPen);
            HBRUSH hOldBrush = (HBRUSH)SelectObject(hdc, hBrush);

            // 큰 사각형 생성
            Rectangle(hdc, 400, 400, 500, 500);
            
            //HBrush2사용
            SelectObject(hdc, hOldPen);
            SelectObject(hdc, hBrush2);
            Ellipse(hdc, 400, 400, 450, 450);


            // 스톡 실습
            HPEN hPen = (HPEN)GetStockObject(DC_PEN);         // 스톡 펜 사용
            HBRUSH hBrush = (HBRUSH)GetStockObject(DC_BRUSH); // 스톡 브러시 사용

            // DC 펜과 브러시의 색상 설정
            SetDCPenColor(hdc, RGB(0, 0, 255));  // 파란색 펜
            SetDCBrushColor(hdc, RGB(0, 255, 0)); // 녹색 브러시

            // 사각형 그리기
            Rectangle(hdc, 100, 100, 200, 200);

            // 레스터 그래픽 실습
            SetROP2(hdc, R2_XORPEN);

            // XOR 연산용 색상 설정
            HPEN xorPen = CreatePen(PS_SOLID, 3, RGB(255, 0, 0));  // 빨간색 펜
            HPEN oldPen = (HPEN)SelectObject(hdc, xorPen);

            // 선 그리기
            MoveToEx(hdc, 50, 50, NULL);
            LineTo(hdc, 200, 200);

            // 펜 복구
            SelectObject(hdc, oldPen);
            DeleteObject(xorPen);


            // 벡터 그래픽 실습
            HRGN hRgn = CreateEllipticRgn(300, 300, 450, 450); // 타원형 리전 생성
            SelectClipRgn(hdc, hRgn);  // 리전 클리핑 설정

            // 벡터 그래픽용 색상 설정
            HPEN vectorPen = CreatePen(PS_SOLID, 2, RGB(128, 0, 255));  // 보라색 계열 펜
            HBRUSH vectorBrush = CreateSolidBrush(RGB(255, 128, 0));    // 주황색 계열 브러시

            HPEN oldVectorPen = (HPEN)SelectObject(hdc, vectorPen);
            HBRUSH oldVectorBrush = (HBRUSH)SelectObject(hdc, vectorBrush);

            // 타원 그리기
            Ellipse(hdc, 300, 300, 450, 450);  // 리전 내에서만 그리기

            // 원래의 펜과 브러시 복구
            SelectObject(hdc, oldVectorPen);
            SelectObject(hdc, oldVectorBrush);
            DeleteObject(hRgn);
            DeleteObject(vectorPen);
            DeleteObject(vectorBrush);


            EndPaint(hWnd, &ps);
        }
        break;
    case WM_CREATE:
        {
            hFont = CreateFont(
                20,         // 세로 길이
                10,         // 가로 길이 (0으로 두면 자동 조정)
                0,          // 문자열 기울기 (단위: 0.1도)
                0,          // 문자 회전 각도
                FW_NORMAL,  // 두께 (FW_NORMAL, FW_BOLD 등)
                FALSE,      // 이탤릭 여부
                FALSE,      // 밑줄 여부
                FALSE,      // 취소선 여부
                DEFAULT_CHARSET, // 문자셋 (한글은 HANGUL_CHARSET)
                OUT_DEFAULT_PRECIS, // 출력 정밀도
                CLIP_DEFAULT_PRECIS, // 클리핑 정밀도
                DEFAULT_QUALITY,     // 출력 품질
                DEFAULT_PITCH | FF_SWISS, // 글꼴 스타일
                TEXT("Arial"));      // 글꼴 이름

            // 펜 설정
            hPen = CreatePen(PS_DASH, 2, RGB(127, 127, 127));
            
            // 브러쉬 설정
            hBrush = CreateSolidBrush(RGB(255, 0, 255));
            hBrush2 = CreateHatchBrush(HS_BDIAGONAL, RGB(0, 255, 255));
            break;
        }
    case WM_DESTROY:
        DeleteObject(hFont);
        DeleteObject(hPen);
        DeleteObject(hBrush);
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

// 정보 대화 상자의 메시지 처리기입니다.
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    UNREFERENCED_PARAMETER(lParam);
    switch (message)
    {
    case WM_INITDIALOG:
        return (INT_PTR)TRUE;

    case WM_COMMAND:
        if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
        {
            EndDialog(hDlg, LOWORD(wParam));
            return (INT_PTR)TRUE;
        }
        break;
    }
    return (INT_PTR)FALSE;
}

어우 쓰다보니 엄청길어졌다. 단번에 외울필요 절대 없다. 어차피 기초이기때문에 반복 숙달로 익히고, 까먹으면 와서 다시 보고 정리하면 된다.

책을 뒤지는거보다 내 블로그에 그래도 한번 정리한걸 보는게 편하더라

공부하는데 참고한 책은 다음과 같습니다. 

::: 도서출판 가메 ::: (kame.co.kr)

 

::: 도서출판 가메 :::

 

www.kame.co.kr

모든 실습 예제는 제 깃허브에 있습니다.

Workspace/C++/Win32API/Win32API at main · cyphen156/Workspace · GitHub

 

Workspace/C++/Win32API/Win32API at main · cyphen156/Workspace

Studying . Contribute to cyphen156/Workspace development by creating an account on GitHub.

github.com

 

 

 

 

 

 

 

'프로그래밍 > Win32 API' 카테고리의 다른 글

Win32 API #3 미리 컴파일된 헤더 추가하기  (0) 2024.10.10
문자 집합(MBCS VS UniCode)  (0) 2024.08.22
Win32 API #1  (1) 2024.07.20