Win32 - API - Grapic
[TOC]
그래픽
GDI (Graphics Device Interface)
- 운영체제의 한 부분으로 출력을 담당(Gdi.dll)
- 그래픽 드라이버와 관련된 처리를 담당
- 직접 사용할 수 없고 DC에 원하는 값을 설정하면 GDI가 이를 이용해 화면을 구성함
DC (Device Context)
- 출력에 필요한 모든 정보를 가지는 데이터 구조체
- GDI 모듈에 의해 관리됨
- 출력하기 위한 장치(화면, 프린터)의 특성을 저장하는 구조체
- 어플리케이션과 GDI를 연결해주는 것으로 어플리케이션은 DC를 통해 GDI에 명령을 전달
- Application — DC — GDI
- 데이터형
- HDC(핸들)
- DC를 사용하는 그래픽 오브젝트
- 비트맵, 브러쉬, 펜, 팔레트, 폰트, Region, Path 등
DC 관련 함수
BeginPaint()
- HDC를 가져오는 역할
- WM_PAINT 메시지 처리에서만 사용 가능
- 기존에 존재하는 DC를 얻어온다.
/*
HWND hWnd : 윈도우 핸들
LPPAINTSTRUCT lpPaint : 출력 영역에 대한 정보를 저장할 PAINTSTRUCT 구조체의 포인터
*/
HDC BeginPaint(HWND hWnd, LPPAINTSTRUCT lpPaint);
PAINTSTRUCT 구조체
- 출력 영역에 대한 상세 정보가 저장된다.
- fErase 값이 true인 경우
- 응용 프로그램이 직접 배경을 삭제해야 함
- 이를 위해서 hbrBackground를 NULL로 설정해야 함
typedef struct tagPAINTSTRUCT {
HDC hdc; // 출력 영역(DC)에 대한 핸들 값
BOOL fErase; // 배경 삭제 여부
RECT rcPaint; // RECT 구조체
BOOL fRestore; // 시스템에서 사용
BOOL fIncUpdate; // 시스템에서 사용
BYTE rgbReserved[32]; // 시스템에서 사용
} PAINTSTRUCT, *PPAINTSTRUCT, *NPPAINTSTRUCT, *LPPAINTSTRUCT;
EndPaint()
- WM_PAINT 메시지 처리에서만 사용 가능
- BeginPaint()를 사용한 경우 반드시 EndPaint()를 호출해 끝을 알려야한다.
/*
HWND hWnd : 윈도우 핸들
LPPAINTSTRUCT lpPaint : 출력 영역에 대한 정보를 저장할 PAINTSTRUCT 구조체의 포인터
*/
HDC EndPaint(HWND hWnd, const PAINTSTRUCT *lpPaint);
- BeginPaint는 두 번째 파라미터가 LPPAINTSTRUCT lpPaint인데 왜 EndPaint는 PAINTSTRUCT *lpPaint일까?
- lpPaint를 포인터 상수(pointer to constant)로 받아 내부에서 변경하지 않음을 명확히 해려하는데 PAINTSTRUCT의 포인터 상수(pointer to constant)가 정의되어있지 않기 때문에 const PAINTSTRUCT *lpPaint로 선언된 듯하다.
GetDC()
- WM_PAINT 메시지 처리 영역 이외의 곳에서 DC를 얻어 올 수 있다.
- 호출 시 윈도우 핸들 값을 입력하면 윈도우 클라이언트 영역에 대한 디바이스 콘텍스트를 반환한다.
- 기존에 존재하는 DC를 얻어온다.
/*
HWND hWnd : 윈도우 핸들
return HDC: 윈도우 클라이언트 영역에 대한 DC(Device Context)
*/
HDC GetDC(HWND hWnd);
ReleaseDC()
- GetDC()로 얻은 DC를 해제한다.
- GetDC()를 사용했다면 반드시 ReleaseDC()를 호출하여 자원을 반납해야한다.
/*
HWND hWnd : 윈도우 핸들
HDC hDC : GetDC()로 얻은 DC
return int : ?
*/
int ReleaseDC(HWND hWnd, HDC hDC);
GDI 오브젝트
- GDI가 그래픽 출력을 위해 사용하는 도구
- 사용자가 GDI 출력을 조정할 수 있는 도구
- GDI 오브젝트를 만들면 핸들을 발급 받고 이 핸들로 GDI 오브젝트를 사용한다.
Stock Object
- OS가 사용빈도가 높은 GDI 오브젝트 자원을 미리 만들어 놓는데 이 자원을 Stock Resource 또는 Stock Object라 한다.
- OS가 제공해주므로 생성/파괴를 할 필요가 없다.
GetStockObject
- Stock Object를 얻기 위한 함수
- 얻고 싶은 자원의 아이디를 인자로 넘기면 HGDIOBJ 형식으로 반환하므로 원하는 핸들 타입으로 형변환하여 사용한다.
HGDIOBJ GetStockObject(int i);
wcex.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
GDI 오브젝트 생성
GDI 오브젝트 사용
SelectObject()
- GDI 오브젝트를 사용하기 위해 HDC가 선택하도록 한다.
/*
HDC hdc : DC
HGDIOBJ h : DC에 선택할 GDI 오브젝트 핸들러
return : 현재 선택되어있던 GDI 오브젝트 핸들러
*/
HGDIOBJ SelectObject(HDC hdc, HGDIOBJ h);
DeleteObject()
- 생성된 GDI 오브젝트를 삭제한다.
- 삭제하지 않으면 컴퓨터의 자원을 계속 점유하므로 메모리 누수가 발생한다.
- DC에 선택되어 현재 사용중인 GDI는 삭제되지 않으므로 다른 GDI를 선택시키고 삭제해야한다.
- SelectObject()의 반환값이 이전 GDI이기 때문에 이 값을 저장했다가 새로 생성하여 선택시킨 GDI를 삭제하기 전에 이전 GDI를 선택시키고 선택 해제된 GDI를 삭제하는 방식으로 사용한다.
/*
HGDIOBJ ho : 삭제할 GDI 오브젝트 핸들러
return : 0 - 현재 선택됐거나 유효하지 않은 GDI 핸들, 0 이외 - 삭제 성공
*/
BOOL DeleteObject(HGDIOBJ ho);
todo - 현재 선택된 펜 핸들을 삭제해도 1이 리턴되는 이유는?
// Pen 생성
HPEN hPen = CreatePen(PS_DASHDOT, 1, RGB(255, 0, 0));
// DC에 펜 지정
HPEN hOldPen = (HPEN)SelectObject(hdc, hPen);
MoveToEx(hdc, 100, 100, NULL);
LineTo(hdc, 150, 150);
//SelectObject(hdc, hOldPen);
BOOL bDeletePenResult = DeleteObject(hPen); // 1이 리턴된다.
GDI 오브젝트 사용 절차
- 핸들 선언
- HPEN hPen, …
- GDI 오브젝트 생성
- CreatePen(), …
- 생성된 GDI 오브젝트를 DC에 선택, 이때 이전 핸들을 저장
- SelectObject()
- 사용
- Rectangle(), Ellipse(), MoveToEx(), LineTo(), …
- 선택을 해제, 이때 저장된 이전 핸들을 선택되도록 한다.
- SelectObject()
- 생성된 GDI 오브젝트를 삭제
- DeleteObject()
GDI 오브젝트 종류
펜(Pen)
- 선을 그을 때 사용되는 GDI 오브젝트
윈도우 기본 제공 펜
- GetStockObject()를 이용하여 받아온다.
- BLACK_PEN, DC_PEN, NULL_PEN, WHITE_PEN
- 흰색, 검은색, 투명색 세가지만 제공하므로 다른 색은 생성해서 사용해야한다.
펜 생성
/*
int iStyle : 선의 모양 정의(PS_SOLID, PS_DASH, PS_DASHDOT...)
int nWidth : 선 굵기, 디폴트 1, 0일 경우 1로 지정됨
COLORREF color : 선 색상
*/
HPEN CreatePen(int iStyle, int nWidth, COLORREF color);
선 그리기
// DC에 펜 지정
HPEN hOldPen = (HPEN)SelectObject(hdc, hPen);
MoveToEx(hdc, 100, 100, NULL);
LineTo(hdc, 200, 100);
사각형 그리기
Rectangle(hdc, 200, 100, 250, 150);
원 그리기
Ellipse(hdc, 250, 150, 300, 200);
브러시(Brush)
- 도형의 내부를 색상과 패턴으로 채우는 역할
브러시 생성
HBRUSH CreateSolidBrush(COLORREF color);
// CreateHatchBrush, CreatePatternBrush, CreateBrushIndirect, CreateDIBPatternBrushPt
비트맵
이미지 종류
- bmp, jpg, gif, tga
이미지를 다루는 두 가지 방법
- 비트맵을 리소스에 등록하여 사용
- LoadImage() 함수를 이용하여 파일로부터 읽어 사용
비트맵 사용법
// ========== Bitmap ==========
// 비트맵 헤더
BITMAP resBitmapHeader; // 리소스 비트맵 헤더
BITMAP fileBitmapHeader; // 파일 비트맵 헤더
// 리소스에 등록된 비트맵 불러오기
HBITMAP hResBitmap = LoadBitmap(g_hInstance, MAKEINTRESOURCE(IDB_BITMAP1));
// 파일의 비트맵 불러오기, 파일은 프로젝트 폴더가 기준이다.(프로젝트 폴더 밖의 Debug 폴더에 넣지 않는다)
// 마지막 파라미터를 LR_LOADFROMFILE로 지정해야한다.
HBITMAP hFileBitmap = (HBITMAP)LoadImage(NULL, TEXT("zzal.bmp"), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
// 비트맵 헤더를 읽어들임
GetObject(hResBitmap, sizeof(BITMAP), &resBitmapHeader);
GetObject(hFileBitmap, sizeof(BITMAP), &fileBitmapHeader);
// Memory DC 생성
HDC hResMemDC = CreateCompatibleDC(hdc);
HDC hFileMemDC = CreateCompatibleDC(hdc);
// Memory DC에 비트맵 선택
SelectObject(hResMemDC, hResBitmap);
SelectObject(hFileMemDC, hFileBitmap);
// Memory DC를 화면 DC에 복사
BitBlt(hdc, 300, 300, resBitmapHeader.bmWidth, resBitmapHeader.bmHeight, hResMemDC, 0, 0, SRCCOPY);
BitBlt(hdc, 300 + resBitmapHeader.bmWidth, 300, fileBitmapHeader.bmWidth, fileBitmapHeader.bmHeight, hFileMemDC, 0, 0, SRCCOPY);
// 생성했던 비트맵과 Memory DC를 제거
DeleteObject(hResBitmap);
DeleteObject(hFileBitmap);
DeleteDC(hResMemDC);
DeleteDC(hFileMemDC);
// ========== Bitmap ========== end
LoadBitmap()
- 리소스에 있는 비트맵을 읽어온다
/*
HINSTANCE hInstance : 인스턴스 핸들
LPCSTR lpBitmapName : 리소스 비트맵 이름(MAKEINTRESOURCE를 이용한다)
*/
HBITMAP LoadBitmap(HINSTANCE hInstance, LPCSTR lpBitmapName);
LoadImage()
- icon, cursor, animated cursor, bitmap 등을 로드한다.
- LoadIcon, LoadCursor, LoadBitmap의 모든 기능을 가지며 추가 기능도 같는다.
- 이미지를 파일에서 읽고 DIB 색션으로 읽을 수 있다.
/*
HINSTANCE hIns : 인스턴스 핸들
LPCSTR name : 파일 이름(경로)
UINT type : 이미지 타입 - IMAGE_BITMAP, IMAGE_CURSOR, IMAGE_ICON
int cx : 아래에서 설명
int cy : 상동
UINT fuLoad : 이미지를 읽어올 방법
*/
HANDLE LoadImage(HINSTANCE hIns, LPCSTR name, UINT type, int cx, int cy, UINT fuLoad);
HBITMAP hFileBitmap = (HBITMAP)LoadImage(NULL, TEXT("zzal.bmp"), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
- 파라미터 설명 추가
- cx, cy
- 아이콘과 커서를 읽을 경우 리소스에 있는 이미지 중 어떤 크기의 이미지를 읽을 것인가를 지정
- 이 값이 0이고 fuLoad에 LR_DEFAULTSIZE 플래그가 지정되어있으면 시스템 매트릭스에 정의되어 있는 SM_CXICON, SM_CXCURSOR의 크기 이미지를 읽는다.
- 이 값이 0이고 fuLoad에 LR_DEFAULTSIZE 플래그가 지정되어있지 않으면 리소스의 실제 크기로 읽는다.
- fuLoad
- LR_LOADFROMFILE
- LPCSTR name 인수를 리소스 이름 대신 파일명으로 인식하여 파일에서 리소스를 읽어온다.
- LR_LOADFROMFILE
- cx, cy
DDB & DIB
- DDB(Device Dependent Bitmap)
- Win3.0 이전에 사용하던 방식
- 이미지에 대한 기본 정보만 가지므로 많은 제약을 가지고 있다.
- 하위 호환성 때문에 아직도 사용됨
- DIB
- DDB보다 더 많은 정보(색상 테이블, 해상도 등)를 가진다.
- 장치 독립적이다.
- .bmp 파일은 모두 DIB 포맷이다.
- DC의 색상 포맷이 고정되어 있어서 DIB는 DC에 직접 선택될수 없기 때문에 DDB로 교체되어야 한다.
DIB Section
- DIB를 DC에 사용하기 위해 DDB로 변환을 최초 한번만 하고 사용하기 위한 포맷
- DIB 포맷의 형태를 갖추었으므로 장치 독립적이고 DDB 포맷과 호환되므로 BitBlt, TransparentBlt로 출력이 가능하다.
GetObject()
- GDI 오브젝트에 대한 정보를 구한다.
- GDI 오브젝트에 따라 적절한 헤더 정보를 기록할 구조체 변수를 생성하고 크기와 함께 포인터를 넘기면 핸들로 전달된 GDI 오브젝트에서 헤더 정보를 읽어 구조체에 기록한다.
/*
HANDLE h : GDI 오브젝트의 핸들
int c : pv로 전달될 데이터 타입의 크기
LPVOID pv : 읽은 데이터를 기록할 버퍼
*/
int GetObject(HANDLE h, int c, LPVOID pv)
메모리 DC
- 화면 DC와 동일한 특성을 가지며 그 내부에 출력 표면을 가진 메모리 영역이다.
- 비트맵은 크기가 큰 데이터이기 때문에 바로 화면에 그리면 사용자가 중간과정을 보게되어 사용성에 좋지 않은 영향을 미친다.
- 메모리 DC 없이 바로 메인 DC에 비트맵을 그리니 그려지지 않았다. 원래 그런지 잘 못한건지 모르겠다.
- 따라서 비트맵 그리기가 완료된 후 실제 화면에 출력하기 위해 화면 DC와 동일하게 화면을 구성할 수 있는 기능을 가진 메모리 DC에 비트맵을 그리고 완료 후 화면 DC에 복사해준다.
- CreateCompatibleDC() 함수로 생성한다.
/*
HDC hdc : 복사 원본이 될 DC
return : 복사된 DC
*/
HDC CreateCompatibleDC(HDC hdc)
BitBlt()
- bit-block transfer
- DC간에 고속 복사를 수행
/*
HDC hdc : 비트맵이 복사될 DC
int x : 비트맵이 복사될 좌표
int y : 비트맵이 복사될 좌표
int cx : 비트맵이 복사될 넓이
int cy : 비트맵이 복사될 높이
HDC hdcSrc : 복사될 비트맵을 가지고 있는 DC
int x1 : 복사될 비트맵의 좌측 좌표
int y1 : 복사될 비트맵의 상단 좌표
DWORD rop : raster-operation code. 브러쉬와 복사원, 복사처의 비트맵 색상이 논리 연산될 방법을 지정
*/
BOOL BitBlt(HDC hdc, int x, int y, int cx, int cy, HDC hdcSrc, int x1, int y1, DWORD rop);
DeleteDC()
- DC를 삭제한다.
- 새로운 DC를 생성해서 사용한 경우 DeleteDC를 호출한다.
/*
HDC hdc : 삭제할 DC
return : 삭제 성공 여부
*/
BOOL DeleteDC(HDC hdc);
StretchBlt()
- 비트맵을 확대/축소하여 복사할 수 있다.
TransparentBlt()
- 비트맵을 확대/축소하여 복사할 수 있다.
- 지정한 색상을 제외하고 출력할 수 있다.
- msimg32.lib 라이브러리를 링크 시켜줘야한다.
- 프로젝트 속성 > 링커 > 입력 > 추가 종속성에 추가한다.
TransparentBlt(hdc, 300 + resBitmapHeader.bmWidth, 300, fileBitmapHeader.bmWidth, fileBitmapHeader.bmHeight, hFileMemDC, 0, 0, fileBitmapHeader.bmWidth, fileBitmapHeader.bmHeight, RGB(231, 223, 220));
SetROP2
- 현재 비트맵 위에 새로 그려지는 그림의 겹치는 방식을 설정
SetROP2(hdc, R2_MASKPEN); // 외곽은 검은색, 내부는 비어있는 사각형
색상 매크로 함수
RGB()
- 개별 r, g, b 값을 받아서 COLORREF(DWORD) 데이터 타입에 저장하여 반환한다.
COLORREF RGB(BYTE r, BYTE g, BYTE b);
COLORREF 구조체
- R, G, B 색상값을 저장하는 데이터 형식
- 상위 8비트는 쓰이지 않으며 하위 비트에서부터 R, G, B 값이 저장되므로 다음과 메모리 구조에서는 B, G, R 순서로 저장된다.
// COLORREF를 만드는 매크로 함수
#define RGB(r,g,b) ((COLORREF)(((BYTE)(r)|((WORD)((BYTE)(g))<<8))|(((DWORD)(BYTE)(b))<<16)))
GetRValue(), GetGValue(), GetBValue()
- 채널별 색상을 BYTE로 반환한다.
BYTE GetRValue(DWORD rgb);
BYTE r = GetRValue(color);
SetPixel
- 특정 픽셀에 색상 출력
COLORREF SetPixel(HDC hdc, int x, int y, COLORREF color);
SetPixel(hdc, 150, 150, RGB(255, 0, 0));
GetPixel
- 특정 픽셀의 색상 가져오기
COLORREF GetPixel(HDC hdc, int x, int y);
COLORREF color1 = GetPixel(hdc, 150, 150);
SetTextColor
- 출력할 문자열 색상 설정 함수
COLORREF SetTextColor(HDC hdc, COLORREF color);
SetTextColor(hdc, RGB(255, 0, 0));
SetBkColor
- 글자의 배경 색상 설정
COLORREF SetBkColor(HDC hdc, COLORREF color);
SetBkColor(hdc, RGB(0, 255, 255));
InvalidateRect()
- 화면의 일부 또는 전체를 무효화해서 다시 그릴(업데이트) 때 사용
- WM_PAINT 메시지를 발생 시킨다
/*
HWND hWnd : 무효화 대상이 되는 윈도우, NULL일 경우 전체 윈도우
RECT *lpRect : 무효화 영역, NULL(0)일 경우 전체 영역
BOOL bErase : TRUE : 지우고 다시 그림, FALSE : 지우지 않고 겹쳐그림
*/
BOOL InvalidateRect(HWND hWnd, RECT *lpRect, BOOL bErase);
무효영역
- 화면의 일부 또는 전체가 변경되어 다시 그려야할 필요가 있는 영역
- 무효영역이 있을 경우 OS는 응용 프로그램에 WM_PAINT 메시지를 보내 다시 그리도록 한다.
- 응용 프로그램에서 화면을 변경시켰을 경우 InvalidateRect() 함수를 이용하여 강제로 무효화시킬 수 있다.