bdfgdfg

WinAPI 그래픽 본문

게임프로그래밍/Win32 API

WinAPI 그래픽

marmelo12 2021. 9. 25. 18:26
반응형

그래픽

-GDI(Graphics Device Interface)

- 운영체제의 한 부분으로 출력을 담당(Gdi.dll)

 

DC의 개념과 역할

DC

- DC는 출력하기 위한 장치(화면 프린터)의 특성을 저장하는 구조체

- 우리가 화면에 무언가를 출력하기 위해서는 DC를 사용해야한다.

 

우리의 프로그램은 DC를 통해서 작업을 하고 DC가 GDI(OS의 영역)에게 명령을 하는 구조.

 

쉽게 말해 화면이 출력되는 모든 무언가를 이 dc구조체를 사용해서 간편하게 출력할 수 있다.

 

DC를 사용하는 그래픽 오브젝트 - 비트맵 브러쉬 펜 폰트 Path등..

DC의 데이터형 -> 핸들(HDC)

 

화면 DC에 관련된 함수

- BeginPaint(),EndPaint() // WM_PAINT 메시지 처리에서만 사용가능

- GetDC(), ReleaseDC()

보면 알겠지만 DC를 얻어왔다면 항상 사용한것을 반납해야한다.

 

중요하게 볼것은 GetDC와 ReleaseDC

두 함수는 어디서든 호출이 가능하고 사용이 가능하다.

(즉 화면 DC를 어디서든지 얻어와 화면에 무언가를 출력할 수 있다)

다만 얻어온 HDC는 다 사용했다면 반드시 해제해줘야한다.

 

간단하게 클라이언트 영역*에 문자를 써보자.

클라이언트영역 - 나우캠퍼스 유튜브

간단하게 클라이언트 영역에 문자열을 출력하는 함수는 TextOut.

화면에 출력을 위한 함수는 hdc가 필요한것을 알 수 있다.

순서대로 화면dc(hdc),좌표(x,y),넣어줄 문자열, 문자열 길이

 

BeginPaint보다는 GetDc를 통해서 화면에 그려본다.

이렇게 메시지 처리함수에 마우스 왼쪽버튼에 해당하는 메시지를 처리하기 위해 case문을 집어넣어주고.

Test라는 함수를 호출해준다.

1280과 720은 클라이언트 영역의 크기를 임의로 저 크기만큼 잡아줌

Test함수의 내부. 즉 왼쪽 클릭을 할때마다 저 문자열을 화면에 출력한다.

 

출력색상 설정

삼원색 사용

- RED(0 ~ 255) , GREEN(0 ~ 255) , BLUE(0 ~ 255)

 

색상범위

- 0 ~ 255

- DWORD(unsigned long - 4바이트) : 8비트(1바이트)씩 나누어 사용 마지막 1바이트는 알파값(투명도)로 사용

 

관련 함수

-RGB() // 매크로 함수

출처 - 나우캠퍼스

각각의 색상을 알고싶다면 GetRValue, GetGValue,GetBValue등이 있다.

 

점(dot) 출력 함수

 - SetPixel() - 색상값을 사용한 한 픽셀 출력

역시나 hdc를 넘겨줘야한다.

 

반대로 GetPixel을 통해 화면 DC에 출력된 색상값을 구해올 수 있다.

 

똑같이 실습해본다

void Test(HWND hWnd)
{
    HDC hdc = GetDC(hWnd);
    srand(time(nullptr));

    std::wstring str;
    SetPixel(hdc, 10,10, RGB(rand() % 256,rand() % 256,rand() % 256));

    DWORD dwColor = GetPixel(hdc, 10, 10);
    str =  str + std::to_wstring(GetRValue(dwColor)) + L" ";
    str = str  + std::to_wstring(GetGValue(dwColor))  + L" ";
    str =  str + std::to_wstring(GetBValue(dwColor)) + L" ";
    TextOut(hdc, 50, 50, str.c_str(), str.size());

    ReleaseDC(hWnd, hdc);
}

 

문자열 출력 관련 함수

 

SetTextColor() - 출력할 문자열의 색상을 설정

SetBkColor() - 글자의 배경 색상 설정

void Test(HWND hWnd)
{
    HDC hdc = GetDC(hWnd);
    srand(time(nullptr));

    std::wstring str;
    SetPixel(hdc, 10,10, RGB(rand() % 256,rand() % 256,rand() % 256));

    DWORD dwColor = GetPixel(hdc, 10, 10);
    DWORD dwColor2 = RGB(rand() % 256, rand() % 256, rand() % 256);
    str =  str + std::to_wstring(GetRValue(dwColor)) + L" ";
    str = str  + std::to_wstring(GetGValue(dwColor))  + L" ";
    str =  str + std::to_wstring(GetBValue(dwColor)) + L" ";
    SetBkColor(hdc, dwColor);
    SetTextColor(hdc, dwColor2);
    TextOut(hdc, 50, 50, str.c_str(), str.size());
    
    ReleaseDC(hWnd, hdc);
}

 

InvalidateRect()

사용빈도가 높은 함수.

용도

- 화면의 일부(RECT 구조체를 사용하여 영역을 지정) 또는 전체를 다시 출력(그릴)할 때 사용

- WM_PAINT 메시지 발생

- 무효화 영역 또는 업그레이드 영역이라 한다.

- 다시 그리고자 하는 영역 지정

두번째인자인 RECT값이 주어지면 그 영역에 해당하는 행동을 한다.

만약 NULL이라면 전체영역을 대상으로 함.

마지막 인자를 통해 TRUE라면 지우고 다시 그리고 FALSE라면 그 영역위에 덮어서 그린다.

 

참고로 RECT의 영역은 다음과 같이 좌표가 설정이 된다.

출처 - https://blog.daum.net/big11m/94

 

이제 특정영역을 대상으로 화면을 그려보자.

void Test(HWND hWnd)
{
    HDC hdc = GetDC(hWnd);
    srand(time(nullptr));

    for(int i = 0; i < 10000; ++i)
        SetPixel(hdc, rand() % 300,rand() % 300, RGB(rand() % 256,rand() % 256,rand() % 256));

    ReleaseDC(hWnd, hdc);

}

여기 위에다가 InvalidateRect를 사용하여 위에 덮어보자.

void Test(HWND hWnd)
{
    HDC hdc = GetDC(hWnd);
    srand(time(nullptr));
    for(int i = 0; i < 10000; ++i)
        SetPixel(hdc, rand() % 300,rand() % 300, RGB(rand() % 256,rand() % 256,rand() % 256));

    ReleaseDC(hWnd, hdc);
    
}
void Test2(HWND hWnd)
{
    RECT rect = { 0,0,150,150 };
    InvalidateRect(hWnd, &rect, true);
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_LBUTTONDOWN:
        Test(hWnd);
        break;
    case WM_RBUTTONDOWN:
        Test2(hWnd);
        break;
 }

 

정리해보면

 

우리가 출력하기위해 GDI모듈을 사용해야 하지만 이것은 OS의 영역. 그렇기에 GDI에게 명령을 내리기위해 DC를 사용.

(대부분 화면DC(HDC)사용)

즉 화면에 무언가를 출력하기 위해서는 DC를 사용해야 한다.

 

그래픽 오브젝트

GDI오브젝트

- Pen, Brush, Bitmap, Memory DC(GDI오브젝트는 아님)등이 있다.

- 사용방법 : 운영체제에서 제공, 사용자가 직접 설정

여기서 메모리 DC란 개념이 나오는데. 화면 DC와 비슷하지만 다른 개념이다. (중요)

 

메모리 DC란 비트맵과 같이 크기가 큰 데이터 덩어리를 바로 출력하면 속도가 느리기에 메모리 DC에 먼저 그림을 그린 후 사용자 눈에 그려지는 과정으로 바로 이어지지 않고 메모리 DC에서 작업을 완료한 후 그 결과만 화면으로 복사한다.

(비트맵 출력을 위해선 메모리 DC를 반드시 사용)

-> 함수는 HDC CreateCompatibleDC(HDC hdc)

-> 인수로 화면 DC의 핸들을 주면 이 화면 DC와 동일한 특성을 가지는 DC를 메모리에 만들어 그 핸들값을 리턴해준다.

-> 참고로 GetDC와 같이 화면 DC를 바로 사용하는 경우엔 ReleaseDC로, 메모리 DC를 사용할 경우 DeleteDC를 해줘야함.

 

기본적으로 GDI오브젝트는 메모리를 사용하기 때문에 사용이 끝나면 삭제를 해야한다.

스톡오브젝트

- OS의 자원 핸들에 대한 내용을 얻는다.

- 펜,브러시,폰트,팔레트등의 GDI 오브젝트를 얻을 수 있다.

다만 반환형이 HDGIOBJ인것을 알 수 있다. 만약 브러시를 얻고싶다면 그에 맞는 자료형 형변환을 해줘야한다.

 

SelectObject함수와 DeleteObject함수

- SelectObject함수. ->

 위와같이 GDI오브젝트를 얻었다면 사용하겠다는 설정.

- DeleteObject -> 다 사용했다면 반환해주는 함수.

 

Get~Object -> SelectObject -> DeleteObject..

GetStockObject는 DeleteObject를 할 필요가없다고 한다.

(윈도우가 기본적으로 제공해주는 GDI오브젝트이므로)

 

- 선 색상, 선 굵기, 스타일 지정

(직접 Pen 그래픽 오브젝트를 생성한것이니 DeleteObject를 해야함)

위와 같이 펜 오브젝트를 생성할 수 있고 펜을 이용한 선을 그릴수 있는 함수도 존재하는데

바로 MoveToEX함수와 LineTo함수.

시작점(MoveToEx)와 끝점(LineTO)까지 선을 긋는다.

 

void Test(HWND hWnd)
{
    HDC hdc = GetDC(hWnd);
   
    HPEN hPen;
    HPEN hOldPen;

    hPen = CreatePen(PS_SOLID, 5, RGB(0, 0, 0));
    hOldPen = (HPEN)SelectObject(hdc, hPen); // 기존의 핸들값을 저장.
    MoveToEx(hdc, 10, 30, NULL);
    LineTo(hdc, 100, 30);
    DeleteObject(hPen); // Create했으므로 삭제.
	
    // 스톡오브젝트 (Delete할필요 x)
    hPen = (HPEN)GetStockObject(BLACK_PEN);
    SelectObject(hdc, hPen);
    MoveToEx(hdc, 10, 60, NULL);
    LineTo(hdc, 100, 60);
    SelectObject(hdc, hOldPen);

    ReleaseDC(hWnd, hdc);
}

 

hOldPen은 마지막에 다시 지정을 해주고있는데 원래 상태로 복구해준것. (최초의 펜을 옮겨놓고 그린다음 다시 셋업)

(원래상태를 복구하고 해제해줘야함. 현재 DC가 사용중인 GDI오브젝트는 삭제할 수가 없기에 OldBrush를 다시 선택하고 삭제)

 

참고로 SelectObject의 리턴값은 이전에 선택된 핸들(같은 종류의 GDI 오브젝트의 핸들)값.

즉! SelectObject(hdc,hPen)은 DC에 두번째 인자로 들어온 GDI오브젝트 핸들을 선택해주고 리턴하는 값은 새로 선택되는 오브젝트 이전에 선택되어 있던 같은 종류의 오브젝트 핸들

 

그렇기에 순서가

1. Create GDI 오브젝트 핸들 생성

2. SelectObject. GDI오브젝트 선택 Old변수(같은 GDI오브젝트)에 기존의 값 저장

3. Delete하기전 DC를 교체(Old변수) -> DC가 선택되어 있는 GDI오브젝트는 삭제가 불가능.

4. Delete

 

밑은 테스트

void Test(HWND hWnd)
{
    HDC hdc = GetDC(hWnd);
    // 스톡오브젝트
    HPEN hPen;
    HPEN hOldPen;

    hPen = CreatePen(PS_SOLID, 5, RGB(0, 255, 0));
    SelectObject(hdc, hPen); // 따로 저장안하고 선택
    MoveToEx(hdc, 10, 30, NULL);
    LineTo(hdc, rand()% 500, rand() % 300);
    //SelectObject(hdc, hOldPen);
    DeleteObject(hPen); // 그냥 삭제
    MoveToEx(hdc, 10, 30, NULL);
    LineTo(hdc, rand() % 500, rand() % 300);
    
    ReleaseDC(hWnd, hdc);
}

즉 위 코드는 펜 오브젝트를 생성하고 삭제가 제대로 되는지를 확인해본다.

(삭제가 되었따면 밑의 LineTo는 펜의 영향을 받지말아야함)

다만 밑의 선긋기도 적용이 되어있음 -> 삭제가 안되었음

그럼이제 SelectObejct를 하고 이전의 핸들값을 저장한 뒤 삭제하기전 교체를 진행해본다

void Test(HWND hWnd)
{
    HDC hdc = GetDC(hWnd);

    HPEN hPen;
    HPEN hOldPen;
    
    srand(time(nullptr));
    
    hPen = CreatePen(PS_SOLID, 5, RGB(0, 255, 0));
    hOldPen = (HPEN)SelectObject(hdc, hPen); // 이전의 핸들값 저장
    MoveToEx(hdc, 10, 30, NULL);
    LineTo(hdc, rand()% 500, rand() % 300);
    SelectObject(hdc, hOldPen); // 원래상태 복구
    DeleteObject(hPen); // 복구 후 삭제
    MoveToEx(hdc, 10, 30, NULL);
    LineTo(hdc, rand() % 500, rand() % 300);
    
    ReleaseDC(hWnd, hdc);
}

제대로 출력이 되는 모습

비트맵

이미지 종류

- bmp, jpg, gif등.. (주로 사용하는것은 bmp)

 

비트맵을 다루는 두 가지 방법

- 비트맵을 리소스에 등록하여 사용

- LoadImage() 함수를 이용하여 파일로부터 읽어내는 방법

 

비트맵을 읽어 출력하는 순서

1) 비트맵의 핸들을 얻는다 : LoadBitmap(), LoadImage() -> (이것은 조금 옵션이 있음)등

2) 메모리 DC생성 : CreateCompatibleDC()

3) 메모리 DC에 비트맵 적용 : SelectObject()

4) 비트맵 출력 : BitBlt()

5) 메모리 DC와 비트맵 제거 : DeleteDC(), DeleteObject() -> GDI오브젝트니 당연히 메모리를 날려줘야함.

 

앞에서 설명했듯이 비트맵 출력을 위해선 메모리 DC를 사용한다.

이미지(비트맵)을 화면에다가 바로 뿌릴순없다. 그렇기에 메모리에 올린상태로 갔다가 출력을 하는 형태다.

 

CreateCompatibleDC를 통해 메모리 DC를 생성한 후

SelectObject의 인자에 메모리 DC와, 비트맵 핸들(GDI 오브젝트)를 넘기면 메모리 DC에서 그 데이터가 들어가게 된다.

그 후 BitBlt함수를 통해 비트맵을 출력

 

BITMAP이라는 구조체가 있다. 비트맵의 정보(사이즈라던지)를 함수를 통해서(반환하는 타입이 BITMAP)간단하게 비트맵의 정보를 얻어올 수 있다.

HBITMAP bitmap = LoadBitmap~ OR (HBITMAP)LoadImage
BITMAP info;
GetObject(hBitmap, sizeof(info), &info);

자주 읽고자 하는 정보가 가로 세로 크기.

실제로 실습을 해보면


void Test()
{
    HDC hdc = GetDC(g_hwnd);
    HDC memDC = CreateCompatibleDC(hdc); // DC메모리 생성
    
    // 리소스추가를 할 시 자동으로 비트맵이 DEFINE되어진다.
    // 그 아이디를 매크로함수 MAKEINTRESOURCE인자에 넘겨주면된다.
    HBITMAP bitmap = LoadBitmap(hInst,MAKEINTRESOURCE(IDB_BITMAP2));
    
    HBITMAP oldBitmap = (HBITMAP)SelectObject(memDC, bitmap);
    BITMAP info;
    GetObject(bitmap, sizeof(info), &info);
    BitBlt(hdc, 0, 0, info.bmWidth, info.bmHeight, memDC, 0, 0, SRCCOPY);
	// Bitblt의 인자 순서 중요. 먼저 화면dc, 뒤에 메모리 dc
    
    SelectObject(memDC, oldBitmap);
    DeleteObject(bitmap);

    DeleteDC(memDC);
    ReleaseDC(g_hwnd, hdc);
    
}

 

 

반응형

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

키보드와 마우스  (0) 2021.09.26
Winapi 구조 - 2  (0) 2021.09.25
PeekMessage와 GetMessage, 키입력  (0) 2021.09.22
Win32 API 구조 - 1  (0) 2021.09.13
Win32 API 프로그래밍  (0) 2021.09.12
Comments