컴퓨터/Win32-API
지난 글들을 통해서 마우스를 통해 자유롭게 그리고 지우는 과정을 배워봤습니다.
이때 그림을 그린 좌표를 기억해서 계속 다시 그려주는 방식을 채택해서 사용했었습니다.
본문에서는 비트맵을 기억하는 방식을 채택해서 조금 더 쉽게 구현을 하는 방법에 대해서 알아보도록 합시다.
구현 방식은 생각보다 간단합니다. 더블 버퍼링의 개념을 응용해서 사용하면 됩니다.
int status; // 0:비활성화 1:그리기ON 2:지우기ON
HDC memdc; // 메모리 DC 값
POINT st_pos; // 시작 POINT좌표
HBITMAP memBitmap; //메모리 DC에서 사용할 Bitmap 값
초기 윈도를 생성하는 과정에서 계속해서 우리가 컨트롤할 가상공간에서의 메모리 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;
}
기존 글들의 내용과 유사합니다.
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;
}
기존 글들과 다르게 무효화 영역을 통한 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;
}
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;
}
메모리 누수의 위험을 안전하게 제거하기 위해 계속해서 사용하고 있던 메모리 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;
}
Win32_API - 체크버튼과 토글버튼 구현하기 (0) | 2022.04.11 |
---|---|
Win32_API - 아이콘 버튼 (버튼에 이미지를 설정하기) (0) | 2022.04.08 |
Win32_API_GDI로 자유곡선 그리기2(지우기/지우개 기능 추가) (0) | 2022.03.30 |
Win32_API - GDI로 자유 곡선 그리기 (마우스로 글씨 쓰기) (1) | 2022.03.28 |
Win32_API - GDI로 선그리기5(깜빡이는 현상 제거/더블버퍼링) (0) | 2022.03.24 |
91년생 공학엔지니어의 개발일지
TODAY :
YESTER DAY :
TOTAL :
Commnet