< 그리기 정보 비트맵에 저장하기 >

 

윈도우용 프로그램의 그리기는 모두 WM_PAINT 메시지에 모아져야 하며 언제든지 WM_PAINT 메시지가 전달되면 현재

화면에 그려져 있는 상태 그대로 다시 그릴 준비가 되어 있어야 한다.
운영체제에서는 화면에 그려져있는 현재 상태를 알지 못하기 때문에 사용자가 직접 복구할 그리기 정보를 가지고 있어야 한다.

그리기 정보를 저장하는 방법으로는 메모리 비트맵에 그림을 저장하는 방법이 있으며 현업에서 많이 쓰이는 방법이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
HBITMAP hbit;
LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
    HDC hdc, MemDC;
    HBITMAP OldBitMap;
    static int x;
    static int y;
    static BOOL bNowDraw = FALSE;
    PAINTSTRUCT ps;
 
    switch(iMessage) {
    case WM_CREATE:
        hdc = GetDC(hWnd);
        hbit = CreateCompatibleBitmap(hdc, 1024768);
        MemDC = CreateCompatibleDC(hdc);
        OldBitMap = (HBITMAP)SelectObject(MemDC, hbit);
        SelectObject(MemDC, GetStockObject(WHITE_PEN));
        Rectangle(MemDC, 001024768);
        SelectObject(MemDC, OldBitMap);
        DeleteDC(MemDC);
        ReleaseDC(hWnd, hdc);
        return 0;
    case WM_LBUTTONDOWN:
        x = LOWORD(lParam);
        y = HIWORD(lParam);
        bNowDraw = TRUE;
        return 0;
    case WM_MOUSEMOVE:
        if (bNowDraw == TRUE) {
            hdc = GetDC(hWnd);
            MemDC = CreateCompatibleDC(hdc);
            OldBitMap = (HBITMAP)SelectObject(MemDC, hbit);
            MoveToEx(hdc,x,y,NULL);
            MoveToEx(MemDC, x, y, NULL);
            x = LOWORD(lParam);
            y = HIWORD(lParam);
            LineTo(hdc, x, y);
            LineTo(MemDC, x, y);
            SelectObject(MemDC, OldBitMap);
            DeleteDC(MemDC);
            ReleaseDC(hWnd, hdc);
        }
        return 0;
    case WM_LBUTTONUP:
        bNowDraw = FALSE;
        return 0;
    case WM_PAINT:
        hdc = BeginPaint(hWnd, &ps);
        MemDC = CreateCompatibleDC(hdc);
        OldBitMap = (HBITMAP)SelectObject(MemDC, hbit);
        BitBlt(hdc, 001024768, MemDC, 00, SRCCOPY);
        SelectObject(MemDC, OldBitMap);
        DeleteDC(MemDC);
        EndPaint(hWnd, &ps);
        return 0;
    case WM_DESTROY:
        DeleteObject(hbit);
        PostQuitMessage(0);
        return 0;
    }
 
    return(DefWindowProc(hWnd,iMessage,wParam,lParam));
}
cs

 

상기 코드는 마우스를 클릭하면서 이동하면 이동 경로에 따라 선을 그리는 코드이다.
WM_MOUSEMOVE 메시지 수신 시마다 화면에 그림을 그리는데, 이 때 비트맵에도 동일한 그림을 그려 저장시킨다.
그리고 WM_PAINT 메시지 처리 시에는 그리기 정보가 저장된 비트맵 이미지를 화면에 출력함으로써 윈도우가 화면 밖으로

벗어나거나 다른 윈도우가 화면을 가리더라도 화면이 지워지지 않고 보여줄 수 있게 된다.

 

메모리 비트맵을 사용하면 어떤 복잡한 형태의 그림이라도 기억할 수 있는 장점이 있으며 속도도 빠르다.
다만 메모리가 조금 많이 드는 것이 단점이긴 하다.

< BeginPaint >

 

WM_PAINT 선두에서 호출되는 BeginPaint의 동작은 아주 중요하기 때문에 동작 순서를 자세히 알아두는 것이 좋다.

 

1. 그리기를 위해 DC를 발급받는다. 이 DC 핸들은 BeginPaint의 리턴값으로 돌려지기도 하며 PAINTSTRUCT 구조체의 hdc 멤버에도

   대입되는데 이 DC의 핸들을 사용하여 그리기를 하면 된다. 또한, PAINTSTRUCT 구조체의 fErase, rcPaint에 값을 채운다.

 

2. 클리핑 영역을 조사하여 DC에 설정한다. 모든 출력 함수는 DC에 설정된 클리핑 영역을 참고하여 영역 바깥으로 출력되는 것을

   잘라낸다.

 

3. 무효영역을 없애 다시 WM_PAINT 메시지가 호출되지 않도록 한다. 만약 화면만 그리고 무효영역을 없애지 않는다면, 운영체제는

   계속해서 WM_PAINT 메시지를 보낼 것이다.

 

4. 다시 그려지는 영역에 캐럿이 있다면 그리기를 시작하기 전에 숨겨 캐럿이 파괴되지 않도록 한다. 이렇게 숨겨진 캐럿은 그리기를

   끝내는 시점인 EndPaint에 의해 복구된다.

 

5. 윈도우의 배경을 백그라운드 브러시로 지우기 위해 WM_ERASEBKGND 메시지를 보내 처리한다. 또한 WM_NCPAINT 메시지를 보내

   비작업영역을 그리도록 한다.

 

여기서 중요한 것은 무효영역을 없애는 기능이다. 무효영역이 있음으로써 WM_PAINT 메시지가 발생하는데 WM_PAINT 메시지 처리

루틴에서 무효영역을 없애는 것은 생각해보면 당연하다.

그렇기 때문에 WM_PAINT 메시지 처리 루틴에서는 반드시 BeginPaint 함수를 사용하여 DC를 얻어야 하며 GetDC를 사용해서는 안 된다.

앞서 말했듯이 BeginPaint는 단순히 DC를 발급받는 것 이상의 중요한 일을 하기 때문이다.

 

또한, 아무것도 그릴것이 없다고해서 WM_PAINT 메시지 처리 코드를 비워 두어서는 안되는데 이유는 무효영역이 없어지지 않기 때문에

WM_PAINT 메시지가 무한히 날라오게 된다. 그릴 것이 없다면 WndProc에서 WM_PAINT를 아예 명시하지 않고 DefWindowProc으로

보내야 한다. 디폴트 처리 루틴에서 WM_PAINT에 대해 Beginpaint, EndPaint를 차례대로 호출하도록 되어 있기 때문이다.

 

< 그리기 메시지 >

 

BeginPaint가 하는 일 중에 WM_NCPAINT 메시지와 WM_ERASEBKGND 메시지를 보내는 것이 있다.

 

WM_NCPAINT는 비작업영역이 그려져야 할 때 운영체제가 보내는 메시지이며 타이틀바, 경계선, 시스템 메뉴 등을 그려야 한다.

이 메시지는 DefWindowProc에서 처리하며 응용 프로그램에서 처리하지 않는다.

 

WM_ERASEBKGND 메시지는 윈도우 클래스에 등록된 배경 브러시로 작업 영역의 배경을 지우는 역할을 한다.

윈도우 클래스 정의문을 보면 배경 브러시를 보통 흰색으로 지정한다.

 

1
    WndClass.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);
cs

 

WM_ERASEBKGND 메시지는 배경 브러쉬가 등록되어 있을 경우 이 브러시로 배경을 칠하는데 이는 곧 배경을 지우는 것이다.

(무효영역이 발생 시 다시 그릴 때 기본 설정된 브러시로 그려진다는 얘기임)

 

만약 배경을 단순한 브러시로 칠하는 방법을 사용하고 싶지 않다면 이 메시지에서 배경을 마음대로 바꿀수 있다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
    HDC hdc;
    int i,j;
    RECT rt;
 
    switch(iMessage) {
    case WM_ERASEBKGND:
        hdc = (HDC)wParam;
        GetClientRect(hWnd, &rt);
        for (i=0; i<rt.right; i+=50) {
            for (j=0; j<rt.bottom; j+=50) {
                Rectangle(hdc, i, j, i+50, j+50);
                Ellipse(hdc, i, j, i+50, j+50);
            }
        }
        return TRUE;
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
 
    return(DefWindowProc(hWnd,iMessage,wParam,lParam));
}
cs

 

상기 코드는 배경을 원과 사각형으로 채운 코드이며 하기와 같이 비트맵 이미지로 배경을 채울 수도 있다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
    HDC hdc, MemDC;
    PAINTSTRUCT ps;
    static HBITMAP MyBitmap;
    HBITMAP OldBitmap;
    int x,y;
    RECT rt;
    BITMAP bit;
    static int ex, ey;
 
    switch(iMessage) {
    case WM_CREATE:
        MyBitmap = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP1));
        ex = 100;
        ex = 100;
        return 0;
    case WM_LBUTTONDOWN:
        ex = LOWORD(lParam);
        ey = HIWORD(lParam);
        InvalidateRect(hWnd, NULL, TRUE);
        return 0;
    case WM_PAINT:
        hdc = BeginPaint(hWnd, &ps);
        Ellipse(hdc, ex-30, ey-30, ex+30, ey+30);
        EndPaint(hWnd, &ps);
        return 0;
    case WM_ERASEBKGND:
        hdc = (HDC)wParam;
        MemDC = CreateCompatibleDC(hdc);
        OldBitmap = (HBITMAP)SelectObject(MemDC,MyBitmap);
        GetObject(MyBitmap, sizeof(BITMAP), &bit);
        GetClientRect(hWnd, &rt);
        for (x=0; x<rt.right; x+=bit.bmWidth) {
            for (y=0; y<rt.bottom; y+=bit.bmHeight) {
                BitBlt(hdc, x, y, x+bit.bmWidth, y+bit.bmHeight, MemDC, 00, SRCCOPY);
            }
        }
 
        SelectObject(MemDC, OldBitmap);
        DeleteDC(MemDC);
        return TRUE;
    case WM_DESTROY:
        DeleteObject(MyBitmap);
        PostQuitMessage(0);
        return 0;
    }
 
    return(DefWindowProc(hWnd,iMessage,wParam,lParam));
}
cs

 

상기 코드는 비트맵을 배경에 연속적으로 채운 후 마우스 클릭 시 타원을 그리는 코드이다.

배경이 비트맵으로 채워졌으며 윈도우의 크기를 변경해도 배경은 전혀 깜박거리지 않는다.

비트맵 자체가 배경이므로 타원의 위치가 바뀔 때 원래 자리에 비트맵으로 덮어 쓰며 타원은 새로운 위치에 그려진다.

 

또한, 이 메시지를 처리할 때는 반드시 wParam으로 전달되는 DC를 얻어서 사용해야 하며 DC를 해제할 필요도 없다.

만약 하기와 같이 GetDC를 얻어서 처리한다면....

1
2
3
4
    case WM_ERASEBKGND:
        hdc = GetDC(hWnd);
        //비트맵 출력
        ReleaseDC(hWnd);
cs

 

윈도우를 움직여 타원이 화면 밖으로 이동했다가 다시 돌아왔을 때 타원이 사라져 있을 것이다.

 

GetDC로 얻은 DC는 클리핑 영역이 설정되어 있지 않아 화면 어느 곳이나 그릴 수 있으며 따라서 원이 배경에 의해 덮여 버린다.

(화면 밖에 나가져 있는 부분까지 비트맵 이미지로 전부 덮어버린다는 소리임.)

게다가 WM_PAINT는 클리핑 영역 안쪽만 복구하므로 원이 다시 그려지지 않는 것이다.

(화면에 보여지는 부분에서만 다시 그린다는 소리임)

결국, GetDC로 얻은 DC는 화면 밖으로 나간 부분까지 비트맵으로 덮어버리고 WM_PAINT는 화면에 보여지는 부분만 그리기 때문에

밖으로 나간 원은 다시 그려지지 않는 것이다.

 

WM_ERASEBKGND의 wParam으로 전달되는 DC는 BeginPaint가 클리핑 영역을 계산해 놓은 DC라 화면에 보여지는 부분만 비트맵

이미지로 덮어버리기 때문에 화면 밖에 나가져 있는 부분은 비트맵 이미지로 덮이지 않아 기존 상태 그대로 유지될 수 있는 것이다.

< PAINTSTRUCT >

 

WM_PAINT의 선두에서는 항상 BeginPaint 함수를 호출한다. 이 함수는 윈도우 핸들과 PAINTSTRUCT 구조체의 포인터를

인수로 취하는데 PAINTSTRUCT 구조체를 잘 활용하면 그리기 속도를 높일 수 있다.

 

1
2
3
4
5
6
7
8
typedef struct tagPAINTSTRUCT {
    HDC         hdc;
    BOOL        fErase;
    RECT        rcPaint;
    BOOL        fRestore;
    BOOL        fIncUpdate;
    BYTE        rgbReserved[32];
} PAINTSTRUCT;
cs

 

이 구조체는 BeginPaint 함수에 의해 채워지며 사용자는 필요에 따라 이 구조체의 정보를 사용하면 된다.

 

HDC hdc : 그리기에 사용될 DC의 핸들값이며 BeginPaint가 리턴하는 값과 동일하다.

BOOL fErase : 배경을 지울 것인가 아닌가를 지정하는 멤버이지만 통산 FALSE로 사용한다.

RECT rcPaint : 그리기를 해야 할 사각영역을 가진다. 이 영역은 클리핑 영역을 포함하는 최소한의 사각영역이다.

 

하기에서 rcPaint를 이용하여 그리기 속도를 줄여볼 것이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
void Swap(int &a, int &b)
{
    int t;
    t=a;
    a=b;
    b=t;
}
 
COLORREF CalcColor()
{
    int r,g,b,i;
    r = rand()%255;
    g = rand()%255;
    b = rand()%255;
    for (i=0; i<10++i) {
        Swap(r,g);
    }
 
    return RGB(r,g,b);
}
 
LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
    PAINTSTRUCT ps;
    HDC hdc;
    RECT rt;
    int x,y;
 
    switch(iMessage) {
    case WM_PAINT:
        hdc = BeginPaint(hWnd, &ps);
        GetClientRect(hWnd, &rt);
        for (y=0; y<rt.bottom; ++y) {
            for (x=0; x<rt.right; ++x) {
                SetPixel(hdc, x, y, CalcColor());
            }
        }
        EndPaint(hWnd, &ps);
        return 0;
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
 
    return(DefWindowProc(hWnd,iMessage,wParam,lParam));
}
cs

 

상기 코드는 화면에 여러 색상으로 점을 차례로 찍는 프로그램이다. 어느정도 Delay를 위해 무의미한 Swap 기능이 들어가 있다.

이러한 프로그램의 문제점은 화면의 일부분이 무효화되어 WM_PAINT가 재 호출될 때 전체 화면을 처음부터 다시 그린다는 것이다.

운영체제에 의해 무효화되지 않은 부분은 SetPixel이 바로 리턴되므로 다시 그려지는 것은 아니지만 어찌되었든 전체 루프를 돌면서

색상을 계산하는 함수가 호출되기 때문에 시간이 오래 소요된다.

운영체제는 다시 그릴 필요가 없다는 것은 알지만 연산 자체가 불필요하다는 것은 판단할 수 없기 때문이다.

 

이런 경우 하기와 같이 PAINTSTRUCT의 rtPaint를 이용하면 루프 자체를 무효화 영역만 반복하기 때문에 속도 차이가 기존에 비해

상당히 빠르다.

 

1
2
3
4
5
6
7
8
9
    case WM_PAINT:
        hdc = BeginPaint(hWnd, &ps);
        for (y=ps.rcPaint.top; y<ps.rcPaint.bottom; ++y) {
            for (x=ps.rcPaint.left; x<ps.rcPaint.right; ++x) {
                SetPixel(hdc,x,y,CalcColor());
            }
        }
        EndPaint(hWnd, &ps);
        return 0;
cs

 

 

< WM_PAINT 호출 >

 

하기 코드는 WM_PAINT가 얼마나 자주 호출되는지 눈으로 확인하는 용도의 프로그램 코드이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
    PAINTSTRUCT ps;
    HDC hdc;
    HBRUSH hBrush;
    static int count = 0;
    TCHAR str[128];
 
    switch(iMessage) {
    case WM_PAINT:
        hdc = BeginPaint(hWnd, &ps);
        hBrush = CreateSolidBrush(RGB(rand()%256, rand()%256, rand()%256));
        FillRect(hdc, &ps.rcPaint, hBrush);
        DeleteObject(hBrush);
        wsprintf(str,TEXT("무효화 회수 = %d"), count++);
        SetWindowText(hWnd, str);
        EndPaint(hWnd, &ps);
        return 0;
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
 
    return(DefWindowProc(hWnd,iMessage,wParam,lParam));
}
cs

< 무효영역 >

 

무효영역이란 화면의 일부가 변경되어 다시 그려져야 할 부분을 말한다.

만약 윈도우 전체가 다 변경되는 경우라면 무효영역은 작업영역과 동일하지만 윈도우의 일부분만 변경되었다면 변경된 부분만

무효영역이 된다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
BOOL bRect = TRUE;
LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
    PAINTSTRUCT ps;
    HDC hdc;
    RECT rt;
    int i;
 
    switch(iMessage) {
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    case WM_PAINT:
        hdc = BeginPaint(hWnd, &ps);
 
        for (i=0; i<1024; i+=5) {
            MoveToEx(hdc, 0, i, NULL);
            LineTo(hdc, 1280, i);
        }
        
        for (i=0; i<1280; i+=5) {
            MoveToEx(hdc, i, 0NULL);
            LineTo(hdc, i, 1024);
        }
 
        if (bRect) {
            Rectangle(hdc, 1010200200);
        }
        else {
            Ellipse(hdc, 1010200200);
        }
 
        EndPaint(hWnd, &ps);
        return 0;
    case WM_LBUTTONDOWN:
        bRect = !bRect;
        SetRect(&rt, 10,10,200,200);
        InvalidateRect(hWnd, &rt, TRUE);
        return 0;
    }
 
    return(DefWindowProc(hWnd,iMessage,wParam,lParam));
}
cs

 

상기 코드는 화면 전체를 다 그리지 않고 다시 그릴 필요가 있는 영역만 무효화 시켜 다시 그리는 기능을 가진 코드이다.

그렇기 때문에 화면 깜빡임이 없다. 그러나 실제로 그려야 할 부분보다 무효영역을 좁게 설정한다면 원하지 않는 그림이

출력되기 때문에 무효영역은 정확하게 계산해야 한다.

 

1
BOOL InvalidateRect(HWND hWnd, CONST RECT* lpRect, BOOL bErase);
cs

lpRect에 RECT의 주소값을 넘기면 RECT 사이즈만큼만 무효영역이 되어 해당 부분만 다시 그린다.

 

< 클리핑 영역 >

 

무효영역은 위에서 말했듯이 다시 그려져야 할 부분이다. 그런데 운영체제가 실제 그리기에 사용하는 영역은 무효영역이

아니라 클리핑 영역이다.

클리핑 영역은 무효영역(다시 그려져야 하는 영역) 중에서도 화면에 보이는 가시 영역을 말한다.

 

동영상 재생기의 경우에는 영상이 진행되는 동안 계속 무효화되면서 다시 그려진다. 그런데 동영상 재생기 위에 다른 윈도우가

올라가져 있어 영상을 일정 부분 가리고 있다면, 가려진 부분을 제외하고 화면에 보여지는 영상 부분이 클리핑 영역이 되는 것이다.

 

< 윈도우 스타일 >

 

화면에 버튼이나 에디트 박스 등의 여러 차일드 컨트롤이 있을 것이다. 만약 화면에 어떤 도형을 그린다고 할 때, 도형이 그려지는

영역과 컨트롤이 있는 영역이 겹친다면 컨트롤 영역에는 도형이 그려지지 않게 해야한다.

이러한 기능을 원한다면 윈도우 스타일에 WS_CLIPCHILDREN을 추가함으로써 차일드의 영역을 클리핑 영역에서 제외하여 차일드가

불필요하게 다시 그려지지 않도록 하면 된다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
BOOL bRect = TRUE;
LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
    PAINTSTRUCT ps;
    HDC hdc;
    HPEN hPen, OldPen;
    HBRUSH OldBrush;
 
    switch(iMessage) {
    case WM_CREATE:
        CreateWindow(TEXT("button"), TEXT("Child Button"),WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 
            5050200100, hWnd, (HMENU)0, g_hInst, NULL);
        CreateWindow(TEXT("listbox"), NULL, WS_CHILD | WS_VISIBLE | WS_BORDER | LBS_NOTIFY, 
            50200200100, hWnd, (HMENU)1, g_hInst, NULL);
        CreateWindow(TEXT("edit"), TEXT("에디트 컨트롤입니다."),WS_CHILD | WS_VISIBLE | WS_BORDER, 
            30012020080, hWnd, (HMENU)2, g_hInst, NULL);
        hWndMain = hWnd;
        return 0;
    case WM_LBUTTONDOWN:
        hdc = GetDC(hWnd);
        OldBrush = (HBRUSH)SelectObject(hdc, GetStockObject(NULL_BRUSH));
        hPen = CreatePen(PS_SOLID, 4, RGB(255,0,0));
        OldPen = (HPEN)SelectObject(hdc, hPen);
        
        Ellipse(hdc, LOWORD(lParam)-50, HIWORD(lParam)-40, LOWORD(lParam)+50, HIWORD(lParam)+40);
 
        DeleteObject(SelectObject(hdc, OldPen));
        SelectObject(hdc, OldBrush);
        ReleaseDC(hWnd, hdc);
        return 0;
    case WM_PAINT:
        hdc = BeginPaint(hWnd, &ps);
        EndPaint(hWnd, &ps);
        return 0;
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
 
    return(DefWindowProc(hWnd,iMessage,wParam,lParam));
}
cs

 

해당 코드만 가지고 실행한다면 버튼 클릭 시 차일드 컨트롤 위에도 타원이 그려질 것이다.

위에서 설명한 것과 같이 윈도우 스타일에 WS_CHILDREN을 추가한 후에 다시 실행하면 컨트롤 위에는 타원이 그려지지 않는다.

 

1
2
    hWnd=CreateWindow(lpszClass,lpszClass,WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN, CW_USEDEFAULT,CW_USEDEFAULT,
        CW_USEDEFAULT,CW_USEDEFAULT,NULL,(HMENU)NULL,hInstance,NULL);
cs

< 폰트 >

 

폰트도 GDI 오브젝트이며 폰트를 만들고 DC로 전송한 후 문자열을 출력하면 DC에 선택된 폰트를 사용하여 문자열을 출력한다.

 

1
2
3
4
HFONT CreateFont(int cHeight, int cWidth, int cEscapement, int cOrientation, int cWeight, 
                 DWORD bItalic, DWORD bUnderline, DWORD bStrikeOut, DWORD iCharSet, 
                 DWORD iOutPrecision, DWORD iClipPrecision, DWORD iQuality, DWORD iPitchAndFamily,
                 LPCTSTR pszFaceName);
cs

 

폰트를 만들기 위해서는 CreateFont 함수를 사용하며 이 함수가 리턴하는 핸들을 HFONT형의 변수에 대입한다.

많은 인자 중 실질적으로 변경되는 인자는 문자의 크기를 지정하는 cHeight와 글꼴 모양을 지정하는 pszFaceName 정도이며

나머지 인자는 디폴트로 사용한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;
    HFONT hFont, OldFont;
    TCHAR *str = TEXT("폰트 Test 1234");
 
    switch(iMessage) {
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    case WM_PAINT:
        hdc = BeginPaint(hWnd, &ps);
        hFont = CreateFont(50,0,0,0,0,0,0,0,HANGEUL_CHARSET,0,0,0,
            VARIABLE_PITCH | FF_ROMAN, TEXT("궁서"));
        OldFont = (HFONT)SelectObject(hdc, hFont);
        TextOut(hdc, 100100, str,lstrlen(str));
        SelectObject(hdc, OldFont);
        DeleteObject(hFont);
        EndPaint(hWnd, &ps);
        return 0;
    }
 
    return(DefWindowProc(hWnd,iMessage,wParam,lParam));
}
cs

 

궁서체의 50픽셀 높이를 가지는 폰트를 출력하는 코드이다.

그러나 인자가 많기 때문에 LOGFONT 구조체를 사용하여 폰트를 정의하고 CreateFontIndirect 함수로 폰트를

만드는 방법도 있다.

 

1
HFONT CreateFontIndirect(CONST LOGFONT* lplf);
cs

 

LOGFONT 구조체를 사용하면 인수의 개수가 적으므로 호출 속도가 빠르고, 여러 개의 폰트를 만들어야 할 때 LOGFONT의

멤버 중 일부만을 변경하여 재사용할 수도 있다.  또한 , LOGFONT 구조체 배열을 사용하면 사용할 폰트의 목록을 미리

작성해 놓을 수 있다는 장점이 있다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;
    HFONT hFont, OldFont;
    TCHAR *str = TEXT("폰트 Test 1234");
    LOGFONT lf;
 
    switch(iMessage) {
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    case WM_PAINT:
        hdc = BeginPaint(hWnd, &ps);
        
        lf.lfHeight = 50;
        lf.lfWidth = 0;
        lf.lfEscapement = 0;
        lf.lfOrientation = 0;
        lf.lfWeight = 0;
        lf.lfItalic = 0;
        lf.lfUnderline = 0;
        lf.lfStrikeOut = 0;
        lf.lfCharSet = HANGEUL_CHARSET;
        lf.lfOutPrecision = 0;
        lf.lfClipPrecision = 0;
        lf.lfQuality = 0;
        lf.lfPitchAndFamily = VARIABLE_PITCH | FF_ROMAN;
        lstrcpy(lf.lfFaceName, TEXT("궁서"));
        hFont = CreateFontIndirect(&lf);
 
        OldFont = (HFONT)SelectObject(hdc, hFont);
        TextOut(hdc, 100100, str,lstrlen(str));
        SelectObject(hdc, OldFont);
        DeleteObject(hFont);
        EndPaint(hWnd, &ps);
        return 0;
    }
 
    return(DefWindowProc(hWnd,iMessage,wParam,lParam));
}
cs

 

LOGFONT를 이용하여 글자를 출력한 코드이다.

 

< 텍스트 색상 >

 

폰트 오브젝트외에 출력되는 문자열에 영향을 주는 세 함수가 있다.

 

1
2
3
COLORREF SetTextColor(HDC hdc, COLORREF color);
COLORREF SetBkColor(HDC hdc, COLORREF color);
int SetBkMode(HDC hdc, int mode);
cs

 

SetTextColor는 Text의 색상을 Set하는 함수이며, SetBkColor는 글자 뒤쪽의 배경 색상을 Set하는 함수이다.

두 함수의 반대 함수는 GetTextColor, GetBKColor이며 현재 설정된 문자색과 배경색을 조사할 수 있는 함수이다.

 

SetBKMode 함수는 배경색상을 사용할 방법을 설정한다. OPAQUE와 TRANSPARENT 두 옵션이 있고 디폴트는 OPAQUE이다.

OPAQUE : 불투명한 배경을 사용한다. 그래서 배경 색상에 의해 뒷쪽의 그림이 지워진다.

TRANSPARENT : 투명한 배경을 사용한다. 그래서 배경이 뒷쪽의 그림이 지워지지 않는다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;
    HBRUSH MyBrush, OldBrush;
    HFONT hFont, OldFont;
    TCHAR *str = TEXT("폰트 Test 1234");
 
    switch(iMessage) {
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    case WM_PAINT:
        hdc = BeginPaint(hWnd, &ps);
 
        MyBrush = CreateHatchBrush(HS_CROSS, RGB(0,0,255));
        OldBrush = (HBRUSH)SelectObject(hdc, MyBrush);
        Rectangle(hdc, 5050400200);
        SelectObject(hdc, OldBrush);
        
        hFont = CreateFont(30,0,0,0,0,0,0,0,HANGEUL_CHARSET,0,0,0,
            VARIABLE_PITCH | FF_ROMAN, TEXT("궁서"));
 
        OldFont = (HFONT)SelectObject(hdc, hFont);
        SetTextColor(hdc, RGB(255,0,0));
        SetBkColor(hdc, RGB(255,255,0));
        TextOut(hdc, 100100, str,lstrlen(str));
 
        SetBkMode(hdc, TRANSPARENT);
        TextOut(hdc, 100150, str,lstrlen(str));
 
        SelectObject(hdc, OldFont);
        DeleteObject(MyBrush);
        DeleteObject(hFont);
        EndPaint(hWnd, &ps);
        return 0;
    }
 
    return(DefWindowProc(hWnd,iMessage,wParam,lParam));
}
cs

 

프로그램을 실행시키면 SetBKMode에 따른 결과를 확인할 수 있다.

비트맵이나 그림 위에 문자열을 출력한다면 투명 모드를 사용하면 좋을 것이다.

 

< 글자 회전시키기 >

 

CreateFont의 세 번째 인수인 nEscapement를 변경하면 문자열의 각도를 바꾸어 가며 회전시킬 수 있다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;
    int i;
    TCHAR *str = TEXT("                      Beautiful Korea");
    HFONT MyFont, OldFont;
 
    switch(iMessage) {
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    case WM_PAINT:
        hdc = BeginPaint(hWnd, &ps);
        SetBkMode(hdc, TRANSPARENT);
        for (i=0; i<900; i+=100) {
            MyFont = CreateFont(50,0,i,0,FW_NORMAL,FALSE,FALSE,FALSE,ANSI_CHARSET,OUT_DEFAULT_PRECIS,
                CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY, VARIABLE_PITCH | FF_SWISS, TEXT("Times New Roman"));
            OldFont = (HFONT)SelectObject(hdc, MyFont);
            TextOut(hdc,100,450,str,lstrlen(str));
            SelectObject(hdc, OldFont);
            DeleteObject(MyFont);
        }
        EndPaint(hWnd, &ps);
        return 0;
    }
 
    return(DefWindowProc(hWnd,iMessage,wParam,lParam));
}
cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include "resource.h"
 
LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
    HDC hdc, MemDC;
    PAINTSTRUCT ps;
    HBITMAP MyBitMap, OldBitMap;
 
    switch(iMessage) {
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    case WM_PAINT:
        hdc = BeginPaint(hWnd, &ps);
        MemDC = CreateCompatibleDC(hdc);
        MyBitMap = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP1));
        OldBitMap = (HBITMAP)SelectObject(MemDC, MyBitMap);
        BitBlt(hdc, 00153126, MemDC, 00, SRCCOPY);
        SelectObject(MemDC, OldBitMap);
        DeleteObject(MyBitMap);
        DeleteDC(MemDC);
        EndPaint(hWnd, &ps);
        return 0;
    }
 
    return(DefWindowProc(hWnd,iMessage,wParam,lParam));
}
cs

 

상기 코드는 BitMap File을 화면에 출력하는 코드입니다.

 

< 메모리 DC >

 

윈도우즈는 비트맵을 곧바로 화면 DC로 출력하는 함수를 제공하지 않는다.

비트맵은 대용량이므로 메모리 DC에 미리 그림을 그려놓고 화면에 출력하는 방식을 사용함으로써 출력 속도를 빠르게 한다.

메모리 DC란 화면 DC와 동일한 특성을 가지며 그 내부에 출력 표면을 가진 메모리 영역이다.

 

1
HDC CreateCompatibleDC(HDC hdc);
cs

 

상기 함수를 통해 메모리 DC를 생성할 수 있으며 인자로 화면 DC의 핸들을 넘겨주어 화면 DC와 동일한 특성을 가지는 DC를

메모리에 만든 후 그 핸들을 리턴한다.

 

1
HBITMAP LoadBitmap(HINSTANCE hInstance, LPCTSTR lpBitmapName);
cs

 

또한, 비트맵을 읽어올 때는 상기의 LoadBitmap 함수를 사용한다.

첫 번째 인자는 비트맵 리소스를 가진 인스턴스의 핸들이며, 두 번째 인자는 비트맵 리소스의 이름이다.

읽어온 비트맵을 SelectObject 함수로 메모리 DC에 선택하면 메모리 DC의 표면에는 리소스에서 읽어온 비트맵이 그려져 있을

것이다.

 

1
BOOL BitBlt(HDC hdc, int x, int y, int cx, int cy, HDC hdcSrc, int x1, int y1, DWORD rop);
cs

 

BitBlt 함수는 DC간의 영역끼리 고속 복사를 수행한다. 메모리 DC의 표면에 그려져 있는 비트맵을 화면 DC로 복사함으로써

비트맵을 화면으로 출력한다.

첫 번째 인자는 복사 대상 화면 DC이고, 다음 네 개의 인자(x, y, cx, cy)는 화면에 표시할 x, y 좌표와 width, height 값이다.

그 다음 여섯 번째 인자는 비트맵이 그려져 있는 메모리 DC이며 그 다음 2개 인자(x1, y1)은 메모리 DC에 그려져 있는

비트맵 이미지 중 복사할 시작 좌표를 의미한다.

즉, x1 = 0, y1 = 0이면 전체 이미지를 복사해오는 것이고 x1 = 30, y1 = 30이면 비트맵 이미지의 30,30 좌표부터 복사를 한다는

의미이다. width, height는 앞에서 지정한 것과 동일한 값을 가지므로 별도로 width, heigth 값을 지정하진 않는다.

마지막 인자인 rop은 래스터 연산 방법을 지정하며 SRCCOPY를 쓰면 이미지를 그대로 복사한다.

이 외에도 BLACKNESS / DSTINVERT / MERGECOPY / MERGEPAINT / WHITENESS가 있다.

 

비트맵 출력이 끝난 후에는 비트맵 자체와 메모리 DC를 해제해야 한다.

비트맵도 GDI 오브젝트이므로 펜, 브러쉬와 동일한 방법으로 OldBitMap을 메모리 DC에 Select 해준 후에 DeleteObject를 호출한다.

또한, 메모리 DC는 DeleteDC라는 별도의 함수를 통해 메모리 DC를 해제하면 된다.

 

< ScretchBlt >

 

DC 간에 비트맵을 전송하는데 확대 및 축소가 가능한 함수이다.

 

1
2
BOOL StretchBlt(HDC hdcDest, int xDest, int yDest, int wDest, int hDest,
                HDC hdcSrc, int xSrc, int ySrc, int wSrc, int hSrc, DWORD rop);
cs

 

상기 코드에서 BitBlt 부분만 해당 함수로 변경하면 된다.

BitBlt와 인자가 조금 다른데 StrechBit의 네 번째, 다섯 번째 인자(wDest, hDest)에는 화면에 출력하고자 하는 이미지의

Width, Heigth 값을 넣어준다. 원본 이미지보다 크다면 확대될 것이고 작다면 축소되서 화면에 표시된다.

또한, 아홉 번째, 열 번째 인자에는 원본 이미지의 Width, Height 값을 입력하면 된다.

 

그러나 상기와 같이 작성된 코드는 매우 비효율적인 코드이다.

WM_PAINT 메시지는 그리기를 할 때마다 호출되는데 이때마다 비트맵을 읽어 와서 출력하고 해제하는 것을 반복한다면 그리기

속도가 매우 느릴것이다.

 

원칙적으로 비트맵은 WM_CREATE에서 미리 읽어 두고 WM_PAINT에서는 출력만 해야 한다.

그리고 비트맵을 출력하기가 너무 번거로운 문제가 있기 때문에 비트맵을 간단하게 출력할 수 있는 래퍼 함수를 만들어서 사용하는

것이 좋다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
void DrawBitmap(HDC hdc, int x, int y, HBITMAP hBitmap)
{
    HDC MemDC;
    HBITMAP OldBitMap;
    int bx, by;
    BITMAP bitmap;
 
    MemDC = CreateCompatibleDC(hdc);
    OldBitMap = (HBITMAP)SelectObject(MemDC, hBitmap);
 
    GetObject(hBitmap, sizeof(BITMAP), &bitmap);
    bx = bitmap.bmWidth;
    by = bitmap.bmHeight;
 
    BitBlt(hdc, x, y, bx, by, MemDC, 00, SRCCOPY);
 
    SelectObject(MemDC, OldBitMap);
    DeleteDC(MemDC);
}
 
LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;
    static HBITMAP MyBitMap;
 
    switch(iMessage) {
    case WM_CREATE:
        MyBitMap = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP1));
        return 0;
    case WM_DESTROY:
        DeleteObject(MyBitMap);
        PostQuitMessage(0);
        return 0;
    case WM_PAINT:
        hdc = BeginPaint(hWnd, &ps);
        DrawBitmap(hdc, 1010, MyBitMap);
        EndPaint(hWnd, &ps);
        return 0;
    }
 
    return(DefWindowProc(hWnd,iMessage,wParam,lParam));
}
cs

 

GetObject 함수를 통해 전달된 비트맵의 크기를 자동으로 조사해서 비트맵 전체를 출력한다.

GetObject는 핸들로부터 펜, 브러시, 비트맵 등의 GDI 오브젝트 정보를 조사하는 함수인데 BITMAP 구조체의

bmWidth, bmHeigth 멤버를 읽으면 멤버를 읽으면 비트맵 크기를 구할 수 있다.

 

우선 WM_CREATE에서 비트맵을 미리 읽어 두었으며 WM_PAINT에서는 비트맵을 출력만 하고 해제는 WM_DESTROY

에서 한다.

< 투명 오브젝트 >

 

만약 도형의 테두리나 내부를 그리고 싶지 않을 경우에는 투명 오브젝트를 사용하면 된다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;
    int i;
    HBRUSH MyBrush, OldBrush;
    HPEN MyPen, OldPen;
 
    switch(iMessage) {
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    case WM_PAINT:
        hdc = BeginPaint(hWnd, &ps);
        for (i=0; i<250; i+=5) {
            MoveToEx(hdc, 0, i, NULL);
            LineTo(hdc, 600, i);
        }
 
        MyBrush = CreateSolidBrush(RGB(0,255,0));
        OldBrush = (HBRUSH)SelectObject(hdc, MyBrush);
        MyPen = CreatePen(PS_SOLID, 5, RGB(255,0,0));
        OldPen = (HPEN)SelectObject(hdc, MyPen);
        Ellipse(hdc, 2020150150);
 
        SelectObject(hdc, GetStockObject(NULL_BRUSH));
        Ellipse(hdc, 22020350150);
 
        SelectObject(hdc, MyBrush);
        SelectObject(hdc, GetStockObject(NULL_PEN));
        Ellipse(hdc, 42020550150);
 
        DeleteObject(SelectObject(hdc, OldPen));
        DeleteObject(SelectObject(hdc, OldBrush));
        EndPaint(hWnd, &ps);
        return 0;
    }
 
    return(DefWindowProc(hWnd,iMessage,wParam,lParam));
}
cs

 

상기 코드를 실행시키면 첫 번째 원은 테두리와 내부 색도 채워져 있고, 두 번째 원은 내부가 투명이라 뒤에 배경이 보이며,

세 번째 원은 내부는 채워져 있으나 태두리가 그려져 있지 않다.

GDI의 그리기 함수들은 항상 선택된 오브젝트를 무조건 사용하도록 되어 있으며 테두리만 따로 그리는 함수나 도형 내부만

채우는 함수는 별도로 제공되지 않는다. 그래서 둘 중 하나만 그리고 싶은 경우에 투명 오브젝트를 사용한다.

 

< 그리기 모드 >

 

윈도우즈에서 사용하는 디폴트 그리기 모드는 R2_COPYPEN 모드이다. 그래서 그려지는 그림이 기존 그림을 덮어버린다.

그리기 모드를 변경하는 함수와 현재 설정된 그리기 모드를 구하는 함수는 다음과 같다.

 

1
2
int SetROP2(HDC hdc, int rop2);
int GetROP2(HDC hdc);
cs

 

HDC hdc 인자는 그리기 모드를 변경하고자 하는 DC의 핸들이며, int rop2 인자는 그리기 모드값이다.

여러가지 종류가 있지만 대부분 사용하지 않으며 R2_NOT (반전 모드)만 중간 중간 사용된다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
    static int sx, sy, oldx, oldy;
    static BOOL bNowDraw = FALSE;
    HDC hdc;
 
    switch(iMessage) {
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    case WM_LBUTTONDOWN:
        sx = LOWORD(lParam);
        sy = HIWORD(lParam);
        oldx = sx;
        oldy = sy;
        bNowDraw = TRUE;
        return 0;
    case WM_MOUSEMOVE:
        if (bNowDraw == TRUE) {
            hdc = GetDC(hWnd);
            SetROP2(hdc, R2_NOT);
            MoveToEx(hdc,sx,sy,NULL);
            LineTo(hdc, oldx, oldy);
            oldx = LOWORD(lParam);
            oldy = HIWORD(lParam);
            MoveToEx(hdc, sx,sy,NULL);
            LineTo(hdc, oldx, oldy);
            ReleaseDC(hWnd, hdc);
        }
        return 0;
    case WM_LBUTTONUP:
        bNowDraw = FALSE;
        hdc = GetDC(hWnd);
        MoveToEx(hdc,sx,sy,NULL);
        LineTo(hdc, oldx, oldy);
        ReleaseDC(hWnd, hdc);
        return 0;        
    }
 
    return(DefWindowProc(hWnd,iMessage,wParam,lParam));
}
cs

 

상기 코드는 파워포인트에서 선을 만들 때 마우스 움직임에 따라 임시 선이 생기고 마우스 버튼을 때면 최종 선이 화면에

그려지는 코드이다.

왼쪽 마우스를 클릭한 상태에서 마우스를 움직이면 R2_NOT 모드로 변경하여 선을 그리는데 기존에 그려졌던 임시 선을 지워주기

위해서 반전 모드로 그려주는 것이다.

흑백 화면에서는 R2_NOT이 이런 작을 가능하게 해주는 가장 편리한 방법이며 컬러 환경에서는 R2_NOTXORPEN이 가장 편리하다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
    static int sx, sy, oldx, oldy;
    static BOOL bNowDraw = FALSE;
    HPEN MyPen, OldPen;
    HBRUSH MyBrush, OldBrush;
    HDC hdc;
    PAINTSTRUCT ps;
 
    switch(iMessage) {
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    case WM_LBUTTONDOWN:
        sx = LOWORD(lParam);
        sy = HIWORD(lParam);
        oldx = sx;
        oldy = sy;
        hdc = GetDC(hWnd);
        MyBrush = CreateSolidBrush(RGB(255,0,255));
        MyPen = CreatePen(PS_SOLID, 5, RGB(15604121));
        OldBrush = (HBRUSH)SelectObject(hdc, MyBrush);
        OldPen = (HPEN)SelectObject(hdc, MyPen);
        SetROP2(hdc, R2_NOTXORPEN);
        Ellipse(hdc, sx-50, sy-50, sx+50, sy+50);
        bNowDraw = TRUE;
        SelectObject(hdc, OldBrush);
        SelectObject(hdc, OldPen);
        DeleteObject(MyBrush);
        DeleteObject(MyPen);
        ReleaseDC(hWnd, hdc);
        return 0;
    case WM_MOUSEMOVE:
        if (bNowDraw == TRUE) {
            hdc = GetDC(hWnd);
            MyBrush = CreateSolidBrush(RGB(255,0,255));
            MyPen = CreatePen(PS_SOLID, 5, RGB(15604121));
            OldBrush = (HBRUSH)SelectObject(hdc, MyBrush);
            OldPen = (HPEN)SelectObject(hdc, MyPen);
            SetROP2(hdc, R2_NOTXORPEN);
            Ellipse(hdc, oldx-50, oldy-50, oldx+50, oldy+50);
 
            oldx = LOWORD(lParam);
            oldy = HIWORD(lParam);
            Ellipse(hdc, oldx-50, oldy-50, oldx+50, oldy+50);
            SelectObject(hdc, OldBrush);
            SelectObject(hdc, OldPen);
            DeleteObject(MyBrush);
            DeleteObject(MyPen);
            ReleaseDC(hWnd, hdc);
        }
        return 0;
    case WM_LBUTTONUP:
        bNowDraw = FALSE;
        hdc = GetDC(hWnd);
        MyBrush = CreateSolidBrush(RGB(255,0,255));
        MyPen = CreatePen(PS_SOLID, 5, RGB(15604121));
        OldBrush = (HBRUSH)SelectObject(hdc, MyBrush);
        OldPen = (HPEN)SelectObject(hdc, MyPen);
        Ellipse(hdc, oldx-50, oldy-50, oldx+50, oldy+50);
        SelectObject(hdc, OldBrush);
        SelectObject(hdc, OldPen);
        DeleteObject(MyBrush);
        DeleteObject(MyPen);
        ReleaseDC(hWnd, hdc);
        return 0;        
    case WM_PAINT:
        hdc = BeginPaint(hWnd, &ps);
        EndPaint(hWnd, &ps);
    }
 
    return(DefWindowProc(hWnd,iMessage,wParam,lParam));
}
cs

 

상기 코드는 아까 선 그리던 코드와 동일한 기능이지만 색상이 있는 원을 대상으로 그리는 코드이다.

< GDI 오브젝트 >

 

GDI 오브젝트(GDI Object)란 그래픽 출력에 사용되는 도구를 말하며 펜, 브러시, 비트맵, 폰트 등등 이 모두 GDI 오브젝트이다.

선을 그을 때는 펜으로 그리며 면은 브러쉬로 채우고 문자열을 출력할 때는 폰트를 사용하는 것이다.

이러한 GDI 오브젝트를 모아 놓은 것이 DC이며 GDI는 현재 DC에 선택되어 있는 GDI 오브젝트를 사용한다.

그래서 사용자는 그래픽을 그리기 전에 DC에 적절할 오브젝트를 선택함으로써 원하는 모양과 속성으로 그래픽을 출력할 수 있다.

GDI 오브젝트는 모두 핸들로 관리되므로 GDI 오브젝트를 생성하는 함수를 호출하고 이 함수가 리턴하는 핸들을 받아서 사용하기만 하면 된다.

 

< 펜 >

 

선을 그을 때 사용되는 GDI 오브젝트이다. 아래는 펜을 만들 때 사용되는 함수이다.

 

1
HPEN CreatePen(int iStyle, int cWidth, COLORREF color);
cs

 

iStyle 에는 그려질 선의 모양을 지정한다. 단, 선의 모양은 굵기가 1일 때만 효과가 있으며 굵기가 2 이상이면 무조건 실선으로 그려진다.

PS_SOLID / PS_DASH / PS_DOT / PS_DASHDOT / PS_DASHDOTDOT

 

cWidth 에는 선의 폭을 지정한다. 디폴트는 1이지만 변경 가능하며, 만약 값이 0이라면 무조건 1픽셀 두께의 선이 만들어진다.

 

color 에는 선의 색상을 지정한다. COLORREF 형이므로 RGB 매크로 함수를 사용하여 색상을 지정하면 된다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;
    HPEN MyPen, OldPen;
 
    switch(iMessage) {
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    case WM_PAINT:
        hdc = BeginPaint(hWnd, &ps);
        MyPen = CreatePen(PS_SOLID, 5, RGB(0,0,255));
        OldPen = (HPEN)SelectObject(hdc, MyPen);
        Rectangle(hdc, 5050300200);
        SelectObject(hdc, OldPen);
        DeleteObject(MyPen);
        EndPaint(hWnd, &ps);
        return 0;
    }
    return(DefWindowProc(hWnd,iMessage,wParam,lParam));
}
cs

 

GDI 오브젝트도 메모리를 사용하기 때문에 더 이상 사용하지 않을 때에는 DeleteObject를 통해서 메모리를 해제해야 한다.

 

1
BOOL DeleteObject(HGDIOBJ ho);
cs

 

삭제하고자 하는 GDI 오브젝트의 핸들만 인수로 넘겨주면 된다. 주의할 사항은 DC에 현재 선택되어 있는 GDI 오브젝트는

삭제할 수 없다는 것이다. 이는 현재 사용되고 있는 객체를 함부로 삭제할수 없도록 만든 안정 규정때문이다.

그래서 삭제하기 전에 DC에 선택된 객체를 선택 해제해야 하므로 다음과 같은 방법을 사용한다.

 

우선 OldPen이라는 같은 종류의 GDI 오브젝트 핸들을 선언하고 이 OldPen 핸들에 MyPen이 선택되기 전의 펜 핸들을 저장해 둔다.

그리고 DC가 OldPen을 선택하게 하면 MyPen은 선택 해제가 되고 삭제할 수 있게 된다.

SelectObject의 리턴 값은 이전에 선택된 핸들값이므로 상기 코드와 같이 항상 OldPen을 이용하여 삭제하면 된다.

모든 GDI 오브젝트를 만들어 사요알 때는 위와 같은 방법을 사용한다.

 

< 브러시 >

 

채워지는 면을 채색하는 용도로 사용되는 GDI 오브젝트이다.

 

1
2
HBRUSH  CreateSolidBrush(COLORREF color);
HBRUSH  CreateHatchBrush(int iHatch, COLORREF color);
cs

 

브러시 생성 함수는 상기 두 가지가 있으며 CreateSolidBrush 함수는 색상만 인자로 받아 도형 내부를 선택한 색상으로 채운다.

CreateHatchBrush 함수는 색상과 무늬도 같이 지정할 수 있다.

지정할 수 있는 무늬는 HS_BDIAGONAL (좌하향 줄무늬) / HS_CROSS (바둑판 모양) / HS_DIAGCROSS (체크 무늬) /

HS_FDIAGONAL (우하향 줄무늬) / HS_HORIZONTAL (수평선) / HS_VERTICAL (수직선) 이 있다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;
    HBRUSH MyBrush, OldBrush;
    HPEN MyPen, OldPen;
 
    switch(iMessage) {
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    case WM_PAINT:
        hdc = BeginPaint(hWnd, &ps);
        MyPen = CreatePen(PS_SOLID, 5, RGB(0,0,255));
        MyBrush = CreateHatchBrush(HS_DIAGCROSS, RGB(255,0,255));
        OldPen = (HPEN)SelectObject(hdc, MyPen);
        OldBrush = (HBRUSH)SelectObject(hdc, MyBrush);
 
        Rectangle(hdc, 5050300200);
 
        SelectObject(hdc, OldPen);
        SelectObject(hdc, OldBrush);
        DeleteObject(MyPen);
        DeleteObject(MyBrush);
        EndPaint(hWnd, &ps);
        return 0;
    }
 
    return(DefWindowProc(hWnd,iMessage,wParam,lParam));
}
cs

 

< Old의 의미 >

 

GDI 오브젝트를 저장하는 리소스 영역은 메인 메모리와는 다른 특수한 영역인데 이 여역의 크기가 그리 크지 못해 오브젝트를

많이 만들면 금방 가득차 버린다. 그러므로 상기에서 설명했던 것과 같이 SelectObject 함수가 리턴하는 핸들을 Old 변수에 저장해

두었다가 다 사용한 후에는 DC에 Old 변수로 저장해두었던 핸들을 다시 집어넣고, Create한 객체를 삭제해주어야 한다.

+ Recent posts