Win32_API - GDI로 자유곡선 그리기3 (비트맵을 저장해서 쓰자)

컴퓨터/Win32-API

728x90
반응형

서론

지난 글들을 통해서 마우스를 통해 자유롭게 그리고 지우는 과정을 배워봤습니다.

이때 그림을 그린 좌표를 기억해서 계속 다시 그려주는 방식을 채택해서 사용했었습니다.

본문에서는 비트맵을 기억하는 방식을 채택해서 조금 더 쉽게 구현을 하는 방법에 대해서 알아보도록 합시다.

 

개요

구현 방식은 생각보다 간단합니다. 더블 버퍼링의 개념을 응용해서 사용하면 됩니다. 

  • WM_CREATE 과정에서 비트맵을 생성한다.
  • 마우스 클릭 과정들에 대한 처리
    • 전 글들과 유사함
  • 마우스 움직이는 과정의 처리 WM_MOUSEMOVE
    • 이 과정에서 생성한 비트맵에 그림을 그리고 고속 복사의 과정을 한다.
  • WM_PAINT 처리
    • 이 과정또한 이미 기억하고 있는 비트맵을 고속 복사한다.
  • 여러 처리 변수 목록
    • 마우스 상태를 알기 위한 변수
    • 마우스 포지션을 기억하는 변수

 

0. 전역 변수 목록

int status;     // 0:비활성화 1:그리기ON 2:지우기ON
HDC memdc;      // 메모리 DC 값
POINT st_pos;   // 시작 POINT좌표
HBITMAP memBitmap;  //메모리 DC에서 사용할 Bitmap 값
  • status : 마우스 상태를 변화시켜 추적하기 위한 값
  • memdc - 메모리 상태의 DC 핸들 값
  • st_pos - 그리기 또는 지우기 시작 좌표를 인식하기 위한 값
  • memBitmap - 메모리 DC에서 사용할 비트맵(도화지 또는 종이)

 

1. 초기 메모리 DC와 bitmap 설정 - WM_CREATE

초기 윈도를 생성하는 과정에서 계속해서 우리가 컨트롤할 가상공간에서의 메모리 DC와 비트맵 설정을 해주도록 합시다.

    case WM_CREATE: {
        // memBitmap 그리고 memDC 생성하기 위한 과정
        hdc = GetDC(hWnd);
        memdc = CreateCompatibleDC(hdc);
        RECT rect;
        GetClientRect(hWnd, &rect);
        memBitmap = CreateCompatibleBitmap(memdc, rect.right, rect.bottom);
        SelectObject(memdc, memBitmap);
        FillRect(memdc, &rect, (HBRUSH)GetStockObject(WHITE_BRUSH));
        BitBlt(hdc, 0, 0, rect.right, rect.bottom, memdc, 0, 0, SRCCOPY);
        //주석처리된 과정은 사용하지 않습니다. 프로그램이 존재하는 동안 계속해서 메모리상태에서 구현을 해야되기 때문입니다.
        //SelectObject(memdc, oldBitmap); 
        //DeleteDC(memdc);
        ReleaseDC(hWnd,hdc);
        break;
    }

 

2. 마우스 클릭에 따른 동작 처리

기존 글들의 내용과 유사합니다.

    case WM_LBUTTONDOWN:
    {
        //마우스 상태 활성화
        status = 1;
        //시작 좌표 저장
        st_pos.x = GET_X_LPARAM(lParam);
        st_pos.y = GET_Y_LPARAM(lParam);
        break;
    }
    case WM_LBUTTONUP:
    {
        status = 0;
        break;
    }
    case WM_RBUTTONDOWN:
    {
        //마우스 상태 활성화
        status = 2;
        //지우는 좌표 받아오기

        st_pos.x = GET_X_LPARAM(lParam);
        st_pos.y = GET_Y_LPARAM(lParam);
        break;
    }
    case WM_RBUTTONUP:
    {
        status = 0;
        break;
    }

 

3. 마우스 움직임에 대한 처리  - WM_MOUSEMOVE

기존 글들과 다르게 무효화 영역을 통한 WM_PAINT 갱신을 하지 않습니다. 

계속해서 활성화되어있는 메모리 상의 DC에서 그리고 BitBlt를 통한 고속 복사를 통해 화면을 갱신합니다.

    case WM_MOUSEMOVE:
    {
        //만약 좌클릭중인 상태이면
        if (status == 1)
        {
            //좌표를 받아서 값을 추가합니다.
            POINT pos;
            pos.x = GET_X_LPARAM(lParam);
            pos.y = GET_Y_LPARAM(lParam);
            //현재 윈도우 창 크기 받아오기
            RECT rect;
            GetClientRect(hWnd, &rect);

            hdc = GetDC(hWnd);
            //memdc = CreateCompatibleDC(hdc);
            HBITMAP oldBitmap = (HBITMAP)SelectObject(memdc, memBitmap);  
            MoveToEx(memdc, st_pos.x, st_pos.y, NULL);
            LineTo(memdc, pos.x, pos.y);
            BitBlt(hdc, 0, 0, rect.right, rect.bottom, memdc, 0, 0, SRCCOPY);
            //SelectObject(memdc,oldBitmap);
            //DeleteDC(memdc);
            ReleaseDC(hWnd, hdc);
            st_pos.x = pos.x;
            st_pos.y = pos.y;
        }

        //만약 우클릭중인 상태이면
        if (status == 2)
        {
            POINT pos;
                pos.x = GET_X_LPARAM(lParam);
                pos.y = GET_Y_LPARAM(lParam);

                //현재 윈도우 창 크기 받아오기
                RECT rect;
                GetClientRect(hWnd, &rect);

                hdc = GetDC(hWnd);
                //memdc = CreateCompatibleDC(hdc);
                HBITMAP oldBitmap = (HBITMAP)SelectObject(memdc, memBitmap);
                
                //그리기 색상 설정
                HPEN newPen = CreatePen(PS_SOLID, 10, RGB(255, 255, 255));
                HPEN oldPen = (HPEN)SelectObject(memdc, newPen);

                MoveToEx(memdc, st_pos.x, st_pos.y, NULL);
                LineTo(memdc, pos.x, pos.y);
                BitBlt(hdc, 0, 0, rect.right, rect.bottom, memdc, 0, 0, SRCCOPY);
                //SelectObject(memdc,oldBitmap);
                //DeleteDC(memdc);
                SelectObject(memdc, oldPen);
                DeleteObject(newPen);

                ReleaseDC(hWnd, hdc);
                st_pos.x = pos.x;
                st_pos.y = pos.y;


        }
        //이후 그리기 갱신 (사용할 필요가 없습니다 !)
        //InvalidateRect(hWnd, NULL, FALSE);
        //UpdateWindow(hWnd);
        break;
    }

4.WM_PAINT 처리

WM_PAINT의 경우에도 전연 변수로 사용하고 있는 메모리 상의 DC를 이용해서 고속 복사를 통해 이루어져 간결합니다.

    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            hdc = BeginPaint(hWnd, &ps);
            //현재 윈도우 창 크기 받아오기
            RECT rect; 
            GetClientRect(hWnd, &rect);
            HBITMAP oldBitmap = (HBITMAP)SelectObject(memdc, memBitmap);
            BitBlt(hdc, 0, 0, rect.right, rect.bottom, memdc, 0, 0, SRCCOPY);
            //SelectObject(memdc,oldBitmap);
            EndPaint(hWnd, &ps);
            break;
    }

5. 프로그램 종료 시 할당된 메모리 DC 해제하기 - WM_DESTROY

메모리 누수의 위험을 안전하게 제거하기 위해 계속해서 사용하고 있던 메모리 DC를 제거해 주도록 합시다.

    case WM_DESTROY:
    {
        DeleteObject(memBitmap);
        DeleteDC(memdc);
        PostQuitMessage(0);
        break;
    }
반응형

전체 소스코드 및 이전 글들과 비교

사실 어떤 방법이 정답이라고는 말할 수 없습니다. 장단점도 물론 존재합니다.

이전 글들의 경우 자유로운 곡선을 하나의 데이터로 취급하기 위해서는 훨씬 유리합니다.

하지만 단순하게 그림을 그린다의 개념에서는 본문의 소스코드가 더 적합합니다. 

 

또한, 이전 글들은 마우스를 움직이는 과정에서 WM_PAINT를 계속해서 호출을 하고 있습니다.

하지만 본문의 소스코드는 계속해서 memDC를 가지고 있어야만 합니다. 

이렇게 비슷한 구현을 해도 구현을 하는 방식에는 차이가 있기 때문에 어떠한 것이 더 효율적인지 잘 생각해보세요.

// Windows 헤더 파일
#include <windows.h>
// C 런타임 헤더 파일입니다.
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>
#include <tchar.h>
#include <Windowsx.h>
#include <vector>
#include <algorithm>







// 전역 변수:
HINSTANCE hInst;                                // 현재 인스턴스입니다.
//그리기를 상태를 파악하기 위한변수
int status;     // 0:비활성화 1:그리기ON 2:지우기ON
HDC memdc;      // 메모리 DC 값
POINT st_pos;   // 시작 POINT좌표
HBITMAP memBitmap;  //메모리 DC에서 사용할 Bitmap 값

// 이 코드 모듈에 포함된 함수의 선언을 전달합니다:
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{

    //윈도우 창 구조체 정의 및 적용
    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 = NULL;
    wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wcex.lpszMenuName = NULL;
    wcex.lpszClassName = L"Test";
    wcex.hIconSm = NULL;
    RegisterClassExW(&wcex);
    //적용한 윈도우 생성 및 업데이트
    hInst = hInstance; // 인스턴스 핸들을 전역 변수에 저장합니다.
    HWND hWnd = CreateWindowW(L"Test", L"Test", WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
    if (!hWnd)
    {
        return FALSE;
    }
    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);
    MSG msg;
    // 기본 메시지 루프입니다:
    while (GetMessage(&msg, nullptr, 0, 0))
    {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
    }
    return (int) msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    HDC hdc;
    switch (message)
    {
    case WM_CREATE: {
        // memBitmap 그리고 memDC 생성하기 위한 과정
        hdc = GetDC(hWnd);
        memdc = CreateCompatibleDC(hdc);
        RECT rect;
        GetClientRect(hWnd, &rect);
        memBitmap = CreateCompatibleBitmap(memdc, rect.right, rect.bottom);
        SelectObject(memdc, memBitmap);
        FillRect(memdc, &rect, (HBRUSH)GetStockObject(WHITE_BRUSH));
        BitBlt(hdc, 0, 0, rect.right, rect.bottom, memdc, 0, 0, SRCCOPY);
        //주석처리된 과정은 사용하지 않습니다. 프로그램이 존재하는 동안 계속해서 메모리상태에서 구현을 해야되기 때문입니다.
        //SelectObject(memdc, oldBitmap); 
        //DeleteDC(memdc);
        ReleaseDC(hWnd,hdc);
        break;
    }
    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            hdc = BeginPaint(hWnd, &ps);
            //현재 윈도우 창 크기 받아오기
            RECT rect; 
            GetClientRect(hWnd, &rect);
            HBITMAP oldBitmap = (HBITMAP)SelectObject(memdc, memBitmap);
            BitBlt(hdc, 0, 0, rect.right, rect.bottom, memdc, 0, 0, SRCCOPY);
            //SelectObject(memdc,oldBitmap);
            EndPaint(hWnd, &ps);
            break;
    }
    case WM_MOUSEMOVE:
    {
        //만약 좌클릭중인 상태이면
        if (status == 1)
        {
            //좌표를 받아서 값을 추가합니다.
            POINT pos;
            pos.x = GET_X_LPARAM(lParam);
            pos.y = GET_Y_LPARAM(lParam);
            //현재 윈도우 창 크기 받아오기
            RECT rect;
            GetClientRect(hWnd, &rect);

            hdc = GetDC(hWnd);
            //memdc = CreateCompatibleDC(hdc);
            HBITMAP oldBitmap = (HBITMAP)SelectObject(memdc, memBitmap);  
            MoveToEx(memdc, st_pos.x, st_pos.y, NULL);
            LineTo(memdc, pos.x, pos.y);
            BitBlt(hdc, 0, 0, rect.right, rect.bottom, memdc, 0, 0, SRCCOPY);
            //SelectObject(memdc,oldBitmap);
            //DeleteDC(memdc);
            ReleaseDC(hWnd, hdc);
            st_pos.x = pos.x;
            st_pos.y = pos.y;
        }

        //만약 우클릭중인 상태이면
        if (status == 2)
        {
            POINT pos;
                pos.x = GET_X_LPARAM(lParam);
                pos.y = GET_Y_LPARAM(lParam);

                //현재 윈도우 창 크기 받아오기
                RECT rect;
                GetClientRect(hWnd, &rect);

                hdc = GetDC(hWnd);
                //memdc = CreateCompatibleDC(hdc);
                HBITMAP oldBitmap = (HBITMAP)SelectObject(memdc, memBitmap);
                
                //그리기 색상 설정
                HPEN newPen = CreatePen(PS_SOLID, 10, RGB(255, 255, 255));
                HPEN oldPen = (HPEN)SelectObject(memdc, newPen);

                MoveToEx(memdc, st_pos.x, st_pos.y, NULL);
                LineTo(memdc, pos.x, pos.y);
                BitBlt(hdc, 0, 0, rect.right, rect.bottom, memdc, 0, 0, SRCCOPY);
                //SelectObject(memdc,oldBitmap);
                //DeleteDC(memdc);
                SelectObject(memdc, oldPen);
                DeleteObject(newPen);

                ReleaseDC(hWnd, hdc);
                st_pos.x = pos.x;
                st_pos.y = pos.y;


        }
        //이후 그리기 갱신 (사용할 필요가 없습니다 !)
        //InvalidateRect(hWnd, NULL, FALSE);
        //UpdateWindow(hWnd);
        break;
    }
    case WM_LBUTTONDOWN:
    {
        //마우스 상태 활성화
        status = 1;
        //시작 좌표 저장
        st_pos.x = GET_X_LPARAM(lParam);
        st_pos.y = GET_Y_LPARAM(lParam);
        break;
    }
    case WM_LBUTTONUP:
    {
        status = 0;
        break;
    }
    case WM_RBUTTONDOWN:
    {
        //마우스 상태 활성화
        status = 2;
        //지우는 좌표 받아오기

        st_pos.x = GET_X_LPARAM(lParam);
        st_pos.y = GET_Y_LPARAM(lParam);
        break;
    }
    case WM_RBUTTONUP:
    {
        status = 0;
        break;
    }
    case WM_DESTROY:
    {
        DeleteObject(memBitmap);
        DeleteDC(memdc);
        PostQuitMessage(0);
        break;
    }

    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}
728x90
반응형

Commnet

G91개발일지

Gon91(지구일)

91년생 공학엔지니어의 개발일지

TODAY :

YESTER DAY :

TOTAL :