컴퓨터/Win32-API
지난 글에서 마우스를 이용하여 선을 그려보고 다양한 선들을 계속해서 그릴 수 있는 방법에 대해서 알아보았습니다.
하지만 수많은 선을 그리다 보면 어느 순간부터 화면이 깜빡거리는 현상이 보이게 됩니다.
그리고 많은 선을 그리면 그릴수록 이 현상은 더 자주 보이게 됩니다.
본문에서는 이런 현상에 대해서 알아보고 극복할 수 있는 방법을 알아보도록 합시다.
깜빡거리는 현상은 왜 일어나는 걸까요?
이전 글에서 만든 소스코드를 보면 WM_MOUSEMOVE메세시가 발생할 때마다 무효화 영역을 생성합니다.
그리고 WM_PAINT메시지에서 DC를 통해 프로그래머가 의도한 형태로 다양한 그림을 그리게 됩니다.
물론, 이 그리는 과정은 인간에게는 더없이 빠른 속도입니다.
무효화 영역을 계속해서 생성하는 과정에서 WM_PAINT에서의 처리과정이 증가한다면, 사람의 눈에서 그 과정을 순간적으로 포착하게 됩니다. 이러한 원인 대문에 깜빡거리는 현상이 발생하게 됩니다.
원인은 수많은 WM_PAINT메시지의 발생과 많은 양의 처리입니다.
이 방법을 해결하기 크게 2가지 방법이 있습니다.
물론 이 방법이 효과적인 경우도 있습니다. 하지만 많은 경우에서 특정 부분만 다시 그리거나, WM_PAINT의 호출하는 제한을 하기는 쉽지 않습니다.
간단한 게임의 경우에도 전반적으로 전체를 그리는 과정이 적합합니다. 이 때문에 더블 버퍼링이라는 개념을 적용하게 됩니다.
더블 버퍼링이라는 개념은 다음과 같습니다.
위의 방법으로 해결한다면 사용자에게 깜빡이는 현상은 존재할 수 없게 됩니다.
위에서 설명한 것처럼 미리 그림을 그리고 완성된 그림을 전달해주는 방법입니다.
A4용지에 사람이 100개의 선을 그린다면 눈으로 선을 그리는 과정을 다 볼 수 있습니다.
하지만 100개의 선을 그린 A4용지를 전달받는다면 100개의 선이 그려져 있는 장면만을 볼 수 있게 됩니다.
이러한 개념이 더블 버퍼링입니다.
조금 더 프로그래머처럼 말한다면 가상의 메모리에 DC를 이용하여 그린 후 보여주고자 하는 윈도에 그림을 전달해 주는 것입니다. 이 더블 버퍼링을 사용해서 본문에서는 지난 글 예제의 문제점을 해결해 보도록 합시다.
// 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; // 현재 인스턴스입니다.
POINT start_pos,end_pos;
//그리기를 상태를 파악하기 위한변수
int status;
int count;
std::vector<std::pair<POINT,POINT>> list;
// 이 코드 모듈에 포함된 함수의 선언을 전달합니다:
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:
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
hdc = BeginPaint(hWnd, &ps);
//메모리에 DC 가지고 생성
HDC memdc = CreateCompatibleDC(hdc);
//현재 윈도우 창 크기 받아오기
RECT rect;
GetClientRect(hWnd, &rect);
//메모리에 윈도우 창과 동일한 그기에 그릴수 있도록 셋팅하기
HBITMAP memBitmap = CreateCompatibleBitmap(memdc, rect.right, rect.bottom);
//HBITMAP또한 하나의 그리기 도구이므로 선택하기 및 예전 그리기 도구 저장
HBITMAP oldBitmap = (HBITMAP)SelectObject(memdc, memBitmap);
//가상공간 그리는 공간 백그라운드 컬러 설정해주기
FillRect(memdc, &rect, (HBRUSH)GetStockObject(WHITE_BRUSH));
//메모리공간에 그림 그리기
if (status == 1) {
MoveToEx(memdc, start_pos.x, start_pos.y, NULL);
LineTo(memdc, end_pos.x, end_pos.y);
}
for (int i = 0; i < list.size(); i++)
{
MoveToEx(memdc, list[i].first.x, list[i].first.y, NULL);
LineTo(memdc, list[i].second.x, list[i].second.y);
}
//넘겨주고자 하는 메인 윈도우로 전달하기 (메모리공간 그림 --> 메인 화면)
BitBlt(hdc, 0, 0, rect.right, rect.bottom, memdc, 0, 0, SRCCOPY);
//생성해둔 메모리 공간 제거
SelectObject(memdc,oldBitmap);
DeleteObject(memBitmap);
DeleteDC(memdc);
EndPaint(hWnd, &ps);
}
break;
//마우스 동작에 따른 처리
//움직일 때
case WM_MOUSEMOVE:
/*
//예제 3을 위한 소스코드
if (status == 1)
{
end_pos.x = GET_X_LPARAM(lParam);
end_pos.y = GET_Y_LPARAM(lParam);
hdc = GetDC(hWnd);
MoveToEx(hdc, start_pos.x, start_pos.y, NULL);
LineTo(hdc, end_pos.x, end_pos.y);
ReleaseDC(hWnd, hdc);
}
*/
//예제 4를 위한 소스코드
end_pos.x = GET_X_LPARAM(lParam);
end_pos.y = GET_Y_LPARAM(lParam);
InvalidateRect(hWnd, NULL, FALSE);
UpdateWindow(hWnd);
break;
//좌클릭 눌렀을 때
case WM_LBUTTONDOWN:
/*
//예제 1을 위한 소스코드
//WM_PAINT가 아닌 곳에서 DC를 불러오는 방법
hdc = GetDC(hWnd);
MoveToEx(hdc, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam),NULL);
LineTo(hdc, (GET_X_LPARAM(lParam)+100), GET_Y_LPARAM(lParam));
ReleaseDC(hWnd, hdc);
*/
/*
//예제 2,3를 위한 소스코드
start_pos.x = GET_X_LPARAM(lParam);
start_pos.y = GET_Y_LPARAM(lParam);
//예제 3을 위한 소스코드
status = 1;
*/
//예제 4를 위한 소스코드
start_pos.x = GET_X_LPARAM(lParam);
start_pos.y = GET_Y_LPARAM(lParam);
status = 1;
break;
//좌클릭 해제할 때
case WM_LBUTTONUP:
{
/*
//예제 2,3를 위한 소스코드
end_pos.x = GET_X_LPARAM(lParam);
end_pos.y = GET_Y_LPARAM(lParam);
hdc = GetDC(hWnd);
MoveToEx(hdc, start_pos.x, start_pos.y, NULL);
LineTo(hdc, end_pos.x, end_pos.y);
ReleaseDC(hWnd, hdc);
//예제 3을 위한 소스코드
status = 0;
*/
//예제 4를 위한 소스코드
end_pos.x = GET_X_LPARAM(lParam);
end_pos.y = GET_Y_LPARAM(lParam);
std::pair<POINT, POINT> temp = { start_pos,end_pos };
list.push_back(temp);
count++;
status = 0;
break;
}
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
함수들에 대해서는 한번 직접 찾아보시길 바랍니다.
추가로 WM_MOUSEMOVE에서 InvalidateRect에서 배경 다시 그리기는 (FALSE)로 해주어야 됩니다.!
Win32_API_GDI로 자유곡선 그리기2(지우기/지우개 기능 추가) (0) | 2022.03.30 |
---|---|
Win32_API - GDI로 자유 곡선 그리기 (마우스로 글씨 쓰기) (1) | 2022.03.28 |
Win32_API - GDI 선그리기4(Vector 활용해서 여러 선 저장하기) (0) | 2022.03.23 |
Win32_API - GDI로 선그리기3(연속해서 여러 선 그리기) (0) | 2022.03.18 |
Win32_API - GDI로 선그리기2 (마우스 동작을 이용해봅시다.) (0) | 2022.03.15 |
91년생 공학엔지니어의 개발일지
TODAY :
YESTER DAY :
TOTAL :
Commnet