2.GDI(Graphics Device Interface)

본장에서는 화면에 글자 및 선,박스,원, 다각형을 출력하는 방법에 대해서 설명합니다. 윈도우는 OS자체가 GUI(Graphics User Interface)이기 때문에 선,박스,원등을 출력하는 방법이 매우 간단하고 쉽습니다. 본장에서는 제일먼저 윈도우 GUI환경의 기본 개념을 이해하고 텍스트트를 출력하는 여러 방법과 DC(Device Context)를 얻는 방법. 선,박스,원다각형을 출력하는 방법, 다양한 그래픽 기법등을 설명합니다.

본장은 윈도우 프로그래밍에서 화면을 구성하는 가장 기본적인 단계의 프로그래밍입니다. 본장을 통해서 GUI를 이용하는 다양한 방법을 습득하게 될것입니다.

1.GDI의 개념

외적으로 보이는 화면이 윈도우가 아니다!

지금까지 설명으로 우리는 윈도우 프로그램을 하기 위해서는 하나의 윈도우를 만들어야 하고 그윈도우를 출력한다음 그안에 필요한 여러 요소들을 출력하는 것을 어렴풋이나마 이해를 했을것입니다. 보통 일반적인 개념으로 보자면 윈도우를 만들면 그냥 윈도우 안에 여러 문자를 출력하고 그윈도우 안에 도형을 그리면 된다고 생각할수 있습니다. 간단한 예로

hwnd=CreateWindow(어쩌구);

위와 같이 했다면 글자를 출력하고자 할 경우

TextOut(hwnd,글자를 출력하는 옵션);

하면 글자를 출력할수 있을것으로 생각할수 있다는 이야기입니다.

그러나 지금까지 우리가 글자를 출력할 때 이상한 인자를 하나 보게 됩니다.

TextOut(hdc,0,0,temp,strlen(temp));

여기에서 hdc의 형은 HDC로 다음과 같이 설정되어 있었습니다.

HDC hdc;

필자도 처음에 이부분에 대해서 사실 굉장히 의하해 했으며 이상하게 생각했습니다. □윈도우를 만들었다면 인자가 hwnd정도로 되어 있어야 하지 않을까? 그런데 난데없이 HDC는 무엇이고 이것은 어디서 나타난것일까?□ 이런 의문입니다.

어쩌면 이부분부터 윈도우 프로그래밍의 난해함이 시작되었을는지 모릅니다. 그래서 속으로 이런 생각을 가질수도 있었을거고요. □음 윈도우 프로그램은 기본적 지식이 없으면 프로그래밍 하기 어려운거구나□ 라고 말입니다.

이부분 또한 윈도우 개념을 버리고 하나의 예로 시작해 보겠습니다.

만득이는 오늘 데이트 하는 날입니다. 아침부터 목욕재개 하고 온갖 폼을 다잡습니다. 오늘은 좀 유식하게 보이기 위해서 안경도 준비했고 케주얼 틱하면서도 무언가 신사적인 모습을 나타내기 위해 케주얼한 신사복 세트를 준비했습니다. 여기에서 좀더 세부적으로 본다면 안경은 만득이 얼굴에 그리고 바지는 만득이 다리에 셔츠는 만득이 몸과 팔에 입어야 합니다. 이것을 우리는 그냥 만득이가 옷을 입고 안경을 쓴다고 합니다. 그렇지만 세부적으로 본다면 얼굴이 만득이가 아니요, 다리가 만득이가 아니라는 사실입니다. 만득이란 사람든 성격,외모,팔,다리 모두를 포함한 어떤 형이상학적인 객체이지 무엇이라고 딱 설정할 형태가 없는것입니다. 즉 외모에 치장을 하는 것은 만득이 라는 객체의 어느 한부분에 치장을 하는것이고 그치장하는 목적 형태가 있어야 합니다. 프로그램으로 본다면 다음과 같을것입니다.

만득이 얼굴->안경

만득이 다리->바지

이런 형태라는 것입니다.

마찬가지로 윈도우 프로그램에서 어떤 윈도우를 만들었다고 하였을 경우 그윈도우는 윈도우의 모든 통합적인 객체를 가르키는 것입니다. 이것을 HWND라는 형으로 나타낼수 있느것이지요. 그럼 여기에서 윈도우의 화면이 그윈도우의 전체라고 말할 수가 있을까요? 그렇지는 않습니다. 윈도우 화면은 그윈도우의 외적 요소일 뿐입니다. 뒤장들을 공부해 나가면서 윈도우는 여러 정보를 함께 가지고 있는 종합적인 구조체라는 것을 느끼실수 있을것입니다. 윈도우에서 그래픽을 출력하고자 할 때 그윈도우의 요소에 출력을 해야 합니다. 만득이의 얼굴이라는 요소에 안경을 씌우듯이요. 이요소를 받는 구조체가 바로 HDC라는 것입니다.

만득이 예와 비교해 본다면 다음과 같을 것입니다.

만득이의 얼굴->안경

윈도우의 화면->그래픽 출력

HWND의 HDC-> 다양한 그래픽 출력

여기에서 우리는 하나의 결론을 내리고 다음으로 넘어값시다.

HWND는 하나의 윈도우의 여러 정보를 가지는 구조체이고 화면은 그 HWND와 연결되어있는 HDC라는 구조체이라는 것을 말입니다.

윈도우에서 GDI

GDI란 Graphics Device Interface의 약자입니다. 해설을 한다면 윈도우 내부에 설정되어 있는 그래픽 디바이스와 상호 연결한다는 의미입니다. 윈도우를 설치하면 비디오 카드와 맞는 드라이버를 윈도우가 설정해 줍니다. 또한 비디오카드를 생산하는 업체들은 자신의 비디오 카드와 함께 윈도우에서 사용할수 있는 드라이버를 제공해줍니다. 자신의 윈도우안에 어떤 비디오 드라이버가 들어 있는 가를 확인해 보자면 제어판에서 디스플레이를 선택하시고 설정을 누르신다음 고급 등록 정보를 클릭하시면 그림 1과 비슷한 화면이 나타날것입니다.

그림 1에서 본다면 현재 비디오 카드는 Matrox MGA Mystrique PowerDesk이며

이 비디오 카드를 핸들링하는 드라이버파일들이 mgapda64.drv등등이 있다는 정보를 볼수 있습니다. 이상황에서 비디오 카드를 새로 바꾸었다면 변경이라는 버튼을 클릭하고 새로 구입한 비디오 카드와 함께 제공되는 드라이버 파일을 설치하면 그 비디오 카드와 맞게 화면이 갱신될것입니다. 그냥 표준을 정하고 모든 카드가 그 표준에 맞추면 되지 왜 이렇게 각회사 비디오 카드별로 드라이버를 따로 설정해야 하는가? 이문제에 대해서는 필자가 보기에는 어쩔수 없다고 생각됩니다. 요즘 나오는 비디오 카드들은 보통 1024x768에 24비트 칼라를 기본으로 제공해주고 기본 메모리를 2메가 정도하는 것들입니다. 1024x768이란 화면에 가로 점이 1024개 그리고 세로 점이 768개라는 의미이지요.또한 어떤 카드들은 800x600모드에서 24비트 칼라를 제공하며 1024x768에서는 256밖에 지원을 못하는 카드들도 있습니다. 또한 어떤 카드들은 비디오 메모리가 4메가에 1280x1024 이상에서 24 비트 칼라를 제공하여 주는 카드도 있으며 비디오 카드이면서 동영상을 지원하는 다중 카드도 나오고 있습니다.

(그림 1) 고급 디스플레이 등록 정보

이런 모든 카드들의 동작법이 같을수는 없는것입니다. 모든 카드가 자신의 특징에 따라 동작이 다르기 때문에 카드이 구동방법은 카드 제작사에게 맞기고 윈도우는 단지 그 디바이스드라이버를 로드하여 화면 출력은 디바이스드라이버에 의존하게 됩니다. 윈도우 시스템은 현재 화면의 전체 크기는 어떻게 출력되는가와 현재 화면에 출력되는 칼라수가 어떤것이라는 정보를 디바이스 드라이버를 통해서 얻고 또한 디바이스 드라이버에 설정해 주는 역할을 하게 됩니다. 이렇게 보면 윈도우시스템은 디바이스 드라이버와 서로 인터페이스를 한다고 볼수 있는 것이지요. 그래서 GDI 즉 Graphics Device Interface라고 볼수 있습니다.

그래픽 디바이스 컨텍스트 (GDC)

윈도우에서는 여러개의 윈도우를 한 모니터에 출력할수 있습니다. 쉽게 보면 멀티 테스킹을 할수 있다는 의미이고 이런 의미 때문에 데스크탑 환경이라고 말도 합니다. 그림2는 여러 윈도우가 화면에 출력된 윈도우의 예입니다.

윈도우 시스템에서는 모니터 화면을 전체화면으로 볼수 없습니다. 또한 윈도우가 또다른 윈도우에 의해서 일부분이 가려질수도 있고 앞에 있는 윈도우가 사라지면 새롭게 윈도우가 다시 나타날수도 있습니다. 이렇게 본다면 모니터 화면 자체는 무의미 하며 우리가 초점을 맞추어야 할부분이 바로 각윈도우의 화면입니다. 윈도우 화면도 윈도우크기가 변화되면서 일부분은 가려지고 스크롤 바에의해서 가려진 부분을 다시 화면에 출력할수 있게 됩니다. 현재 화면이 1204x768이라고 하더라도 우리는 10000x10000의 크기의 화면을 보유하는 윈도우를 만들 수 있습니다. 물론 이화면을 다보기 위해서는 스크롤바를 두어서 화면 위치를 이동하거나 또는 축소 형태로 볼수 있겠지만요.

(그림 2)여러개의 윈도우가 있는 윈도우 시스템

중요한 것은 각윈도우의 화면과 모니터 화면과는 별개라는 것입니다. 또한 각윈도우 화면이 모니터에 다 나타나지 않을수도 있다는 것입니다. 이것은 바로 각윈도우마다 해당되는 화면이 따로 존재하고 이것은 화면에 나타나지 않아도 어느 메모리를 점유하며 존재한다는 것입니다. 각윈도우는 각윈도우에 해당하는 메모리가 있고 이것을 윈도우 시스템에서는 조화있게 화면에 출력하는 기능을 합니다. 우리가 프로그램을 작성할경우에는 단순히 현재 윈도우와 연결되어 있는 HDC를 이용하여 글자를 기록하고 도형을 기록하지만 그것은 모니터 화면에 기록하는 것이 아니라 윈도우와 연결되어 있는 화면 메모리에 기록을 하는것입니다. 윈도우가 만들어지면 시스템에서는 그윈도우의 화면 메모리 영역을 할당합니다. 그리고 그 화면에 메모리영역에서 모니터에 출력할 부분을 읽어다가 해당위치에 출력을 해줍니다. 이런 컨트롤을 하는 프로그램이 윈도우 시스템에 있는 □GDI32.DLL"입니다.

그래픽 디바이스 인터페이스를 모두 관장하는 프로그램이 바로 GDI32.DLL입니다.우리는 GDI32.DLL이 하는 복잡한 모든 내용을 다 이해할 필요는 없습니다. 다만 알아야 하는 것은 윈도우와 연결되어 있는 화면은 모니터 화면이 아니라는 것입니다. 그 윈도우 시스템에서 설정한 화면 메모리라는 것이고 이 메모리를 컨트롤 하는 것이 바로 HDC라는 핸들입니다. 따라서 그래픽을 출력하고자 할경우에는 HDC핸들을 얻어야 합니다. 이것을 디바이스 컨텍스트라고 합니다. 보통 DC라고 합니다. HDC란 디바이스 컨텍스 핸들이라는 말입니다. 디바이스 컨텍스트라는 것은 바로 현재 윈도우와 연결된 화면 메모리 관리자라고 볼수 있습니다.

HDC핸들 얻기 첫 번째 방법

HDC핸들을 얻는 방법은 크게 3가지로 볼수 있습니다. 이중에 첫 번째 방법입니다.

지금까지 윈도우 프로그램을 작성했을 때 메시지 처리 루틴에서 WM_PAINT라는 항목을 보았을것입니다. WM_PAINT라는 항목과 함께 다음과 같은 내용이 있습니다.

hdc = BeginPaint (hwnd, &ps) ;

EndPaint (hwnd, &ps) ;

WM_PAINT라는 메시지는 화면을 제출력할 필요가 있을 때 사용되는 메시지입니다. 이메시지와 함게 Bgeinpaint함수와 EndPaint함수는 쌍으로 호출되는 것이 기본입니다. 화면을 재출력하고자 할 경우 먼저 HDC를 받는 것은 지금까지 읽어보았다면 이해를 하였을것입니다. BgingPaint함수의 첫 번째 인자는 윈도우 핸들러 HWND입니다. 즉 윈도우와 연계된 화면 출력 메모리 핸들러를 받아야 하기 때문에 필수적으로 넘겨주어야 합니다. ps는 PAINTSTRUCT  구조체입니다. 이구조체는 윈도우의 실제 출력하는 화면 메모리의 영역에 대한 정보를 가지고 있습니다.

WM_PAINT메세지에 의해서 화면을 출력할 경우 실제 출력하는 부분이 있으며 출력하지 않아도 될부분이 있습니다. 화면영역은 10000x10000인데 현재 모니터에 보이는 실제 영역은 100x100이라면 모니터에 나오는 화면의 실제 영역은 전체 화면의 일부입니다. 즉 새로 출력할 영역 또는 유효화 하는 영역의 정보가 필요하고

그이외의 여러 정보가 필요하기 때문에 이정보를 얻을수 있는 구조체가 필요한것입니다. BeginPaint메시지를 호출하게 되면 현재 윈도우핸들러와 함께 연결된 HDC와 화면 영역의 정보를 PAINTSTRUCT구조체에 얻게 됩니다. 그리고 이정보를 가지로 화면을 출력하면 됩니다.

필요할때마다 아무데서나 그냥 화면을 출력하면 되지 왜 이런 메시지를 만들고 이메세지에 의해서 화면을 출력해야 하는가? 전에 말씀드렸듯이 모니터 화면에 여러 윈도우들이 함께 겹쳐져있습니다. 그러나가 상위에 있는 윈도우가 사라지면 그 하부의 윈도우들은 나타나는 부분을 다시 출력해야 합니다. 이것은 어떤 메시지에 의해서 윈도우가 자신의 화면을 재출력할 필요를 감지 하게 됩니다. 자신의 화면ㅇ에 변화가 생겼을 경우 시스템의 해당 윈도우에 WM_PAINT를 보내게 되고 유효화 해야할 영역이 어떤 영역인가를 BeginPaint함수에 의해 PAINTSTRUCT구조체에 넘겨주게 됩니다. 그다음은 프로그램 내부에서 필요한 부분을 다시 출력하게 되는것입니다. DOS에서 프로그램을 제작한 분이라면 이런 부분이 다소 생소할지모르나 이런 방법을 이용하지 않는다면 프로그램을 제작할때마사 항상 자신의 윈도우가 변경되었는지를 계속적으로 체크해야 합니다. 그렇다면 프로그램 자체가 매우 복잡하게 이루어 지는 것입니다.

표1은 PAINTSTRUCT구조체의 맴버들의 설명입니다.

(표1) PAINTSTRUCT 구조체

구조 체명	내용
HDC hdc	화면 출력을 하기 위한 HDC핸들러
BOOL fErase	화면의 백경색을 다시 칠할것인가 칠하지 않는가의 정보 fErase가 1이면 배경색을 다시칠하며 0일경우에는 칠하지 않는다.
RECT rcPaint	새로 출력할 좌측상단과 우측하단의 좌표 정보
BOOL fRestore BOOL fIncUpdate BYTE rgbReserved[16]	예약된 멤버,이 변수는 윈도우 내부에서 사용함

HDC를 얻은후에 이 HDC에 여러 그래픽 내용을 출력하면 화면에 출력하게 됩니다. 이렇게 출력을 한다음에 출력종료를 의미하는 함수가 바로 EndPaint (hwnd, &ps) ;입니다.

WM_PAINT메세지는 외부에서도 호출할수 있지만 내부에서도 호출할수 있습니다. 이것을 호출하는 방법은 InvalidateRgn이라는 함수를 사용합니다.

BOOL InvalidateRgn(

HWND hWnd, // 윈도우 핸들

HRGN hRgn, // 새로 출력할 화면 영역

BOOL bErase // 배경색을 다시칠하는 유무 플러그

);

HRGN이라는 형은 지금까지 보지 못한 형입니다. 이형은 바로 다시 출력할 화면의 영역을 정보를 가지고 있는 영역 핸들러입니다. 조금 후에 이구조체를 사용하는 방법에 대해서 설명을 할것입니다.

윈도우에서 화면 출력 프로그래밍 방법

InvalidateRgn이라는 함수를 실행하면 WM_PAINT라는 메시지가 수행되고 그안에 있는 내용들이 출력된다는 것을 느끼실수가 있을것입니다.

도스에서 프로그래밍을 하였을때와는 다른점이 많습니다. 즉 화면에 출력하는 형태가 다르다는 것입니다. 일반 도스때는 내용을 출력하고자 한다면 printf이나 자체적으로 만든 그래픽 출력함수를 아무곳에서나 출력을 할수 있지만 윈도우에서는 위의 첫방법에 의하면 모든 화면 출력하는 부분이 WM_PAINT에 하게 됩니다.

결국 출력방법이 바뀌어 합니다. 윈도우에서 화면에 출력하는 방법은 다음과 같은 형태가 일반적입니다.

출력할 데이터 변수설정

WM_PAINT:

출력 데이터 변수를 화면에 출력:

break;

다른 메시지 처리 루틴:

출력 데이터 변경

break;

한가지 예를 들어서 이름과 전화번호를 출력하는 프로그램이라고 한다면 다음과 같은 형태를 취할수 있습니다.

char name[80];

char tel[80]

WM_PAINT:

hdc = BeginPaint (hwnd, &ps) ;

TextOut(0,0,name,strlen(name));

TextOut(0,10,name,strlen(tel));

EndPaint (hwnd, &ps) ;

break;

다른 메시지

strcpy(name,"이상엽□);

strcpy(tel,"이구팔-마구돌려□);

InvalidateRgn(hwnd,NULL,TRUE);

break;

위의 예에서 보듯이 화면을 출력할 기본 형태를 설정한다음 이형태의 포맷을 그대로 WM_PAINT에 설정하며 다른 메시지에서 이형태에 들어가는 데이터만 바꾸는 형태가 됩니다. 그리고 InvalidateRgn함수를 호출하여 WM_PAINT를 새로 호출하는 것입니다.

HDC핸들 얻기 두 번째 방법

윈도우에서도 필요할때마다 HDC핸들러를 얻어서 화면을 출력할 수가 있습니다. 다음은 이방법의 기본 형태입니다.

hdc=GetDC(hwnd);

ReleaseDC(hwnd,hdc);

GetDC를 하게 되면 출력 디바이스 핸들러 가 넘어오게 되고 이 DC를 이용하여 그래픽을 출력하면 됩니다. GetDC를 이용하면 일시적인 출력입니다. 만일 화면에 감추어져있다가 나타나거나 프로그램 내에서 InvalidateRgn이라는 함수를 호출하게 되면 WM_PAINT가 호출되고 그리고 새로 화면을 출력하기 때문에 GetDC와 ReleaseDC를 이용하여 출력한 내용은 사라지게 됩니다. 보통 GetDC와 ReleaseDC를 이용할때는 잠시 출력하는 데이터나 또는 DC의 정보를 얻기위한 수단으로 많이 이용합니다.

예를 들어서 화면의 가로 픽셀과 세로 픽셀의 수 픽셀당 비트수 칼라수등을 얻고자 할 경우 이때도 DC를 이용한 GetDeviceCaps라는 함수를 사용하게 됩니다.

int GetDeviceCaps(

HDC hdc,  //디바이스 컨텍스트 핸들

int nIndex //얻고자 하는 정보 번호

);

hdc=GetDC(hwnd);

Dvnum=GetDeviceCaps(hdc,HORZRES );//전체화면 가로픽셀

Dvnum=GetDeviceCaps(hdc,VERTRES );//전체화면 세로 픽셀

Dvnum=GetDeviceCaps(hdc,BITSPIXEL );//픽셀당 비트수

Dvnum=GetDeviceCaps(hdc,NUMCOLORS );//사용하는 칼라수

ReleaseDC(hwnd,hdc);

HDC핸들 얻기 세 번째 방법

지금까지는 윈도우 핸들을 과함께 해당되는 DC를 얻었습니다. 그러나 필요에 따라서 DC만 따로 만들 필요가 있습니다. 화면에 출력을 하지 않고 우선은 DC를 만들고 그안에 여러 정보를 출력한다음 이것을 화면에 전송할경우도 있으며 bmp파일을 읽어서 적당하게 처리한다음 그것을 DC로 가지고 있다고 화면에 붙일수도 있기 때문입니다. 이런 방법들은 좀더 난해하기 때문에 뒤에 가면서 차츰차츰 설명하겠습니다. 윈도우와 연결되어 있지 않는 독립적인 DC를 얻는 함수는 CreateDC입니다.

HDC CreateDC(

LPCTSTR lpszDriver, // 선택할 특정 드라이버 이름

LPCTSTR lpszDevice, // 선택할 특정 디바이스 이름

LPCTSTR lpszOutput, // 사용안함 NUL을 사용

CONST DEVMODE *lpInitData // 프린터 출력시 사용합니다.

);

한가지 예로써 전체 화면의 DC를 얻고자 할 경우 CreateDC를 이용하여 다음과 같이 만들 수 있습니다.

hdc=CreateDC("DISPLAY",NULL,NULL,NULL);

wsprintf(temp,"윈도우 전체 화면 DC를 얻어서 출력");

TextOut(hdc,0,0,temp,strlen(temp));

DeleteDC(hdc);

CreateDC의 드라이버이름을 "DISPLAY"라는 명을 사용하게 되면 전체 화면 DC를 엇게 됩니다. 이렇게 하여 이 DC를 이용하여 여러 내용을 출력하면 됩니다.

CreateDC와 연계된 쌍은 DeleteDC입니다. 두 번째 방법에서 GetDC와 ReleaseDC처럼 CreateDC는 DeleteDC를 이용하게 됩니다.

윈도우의 특정 HDC얻기

WM_PAINT의 메시지후에 BeginPaint함수에 의해서 얻어진 DC는 클라이언트 영역의 DC입니다. 그림 3에서 보면 윈도우는 실제 데이터를 출력하는 영역과 이영역을 감싸고 있는 타이틀 바와 축소확대 버튼 종료 버튼그리고 윈도우 경계선들이 있습니다. WM_PAINT에서 얻어진 DC는 타이틀 바와 경계선을 제외합니다. 결국 타이틀 바와 경계부분의 영역의 DC는 WM_PAINT로는 받을수가 없다는 것입니다. 보통 프로그램을 작성할 경우 타이틀 바와 경계선의 DC를 받을 필요가 사실 없기 때문에 자주 사용을 하지 않습니다만 만일 이런 비클라이언트 영역을 포함한 전체 DC를  받고자 할 경우 이때사용하는 함수가 GetWindowDC입니다. 이함수의 쌍은 다음과 같습니다.

(그림 3) 윈도우 형태

hdc=GetWindowDC(hwnd);

:

ReleaseDC(hwnd,hdc);

GetWindowDC를 호출하게 되면 윈도우외곽선을 포함한 모든 영역 즉 윈도우 전체 영역을 DC로 받을수 있습니다. 보통 외곽이 없는 프로그램을 작성하고자 할 경우 또는 특정형태의 윈도우(보통 타이틀 윈도우)일경우에 이 DC를 간혹 이용하기도 합니다.

디바이스 컨텍스트 예제 GetDeviceEx

GetDeviceEx는 지금까지 설명한 모든 내용을 사용한 예제입니다. 이예제는 현재 전체 화면의 정보를 화면에 출력하며 마우스 우측버튼을 누르면 전체 화면 DC를 얻어서 이곳에 문자를 출력하고 마우스좌측 버튼을 누르게 되면 전체 윈도우의 DC를 얻어서 출력을 합니다. 또한 문자키를 눌렀을경우에는 GetDC를 이용하여 화면에 문자를 출력합니다.

(프로그램 소스)

//디바이스 정보 와 DC얻기

//GetDeviceEx.c

#include <windows.h>

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

PSTR szCmdLine, int iCmdShow)

{

static char szAppName[] = "GetDeviceEx" ;

HWND        hwnd ;

MSG         msg ;

WNDCLASSEX  wndclass ;

wndclass.cbSize        = sizeof (wndclass) ;

wndclass.style         = CS_HREDRAW | CS_VREDRAW ;

wndclass.lpfnWndProc   = WndProc ;

wndclass.cbClsExtra    = 0 ;

wndclass.cbWndExtra    = 0 ;

wndclass.hInstance     = hInstance ;

wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;

wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;

wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;

wndclass.lpszMenuName  = NULL ;

wndclass.lpszClassName = szAppName ;

wndclass.hIconSm       = LoadIcon (NULL, IDI_APPLICATION) ;

RegisterClassEx (&wndclass) ;

hwnd = CreateWindow (szAppName,

"DC와 DC정보얻기 예제:GetDeviceEx",

WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

NULL,

NULL,

hInstance,

NULL) ;

ShowWindow (hwnd, iCmdShow) ;

UpdateWindow (hwnd) ;

while (GetMessage (&msg, NULL, 0, 0))

{

TranslateMessage (&msg) ;

DispatchMessage (&msg) ;

}

return msg.wParam ;

}

LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)

{

HDC         hdc ;

PAINTSTRUCT ps ;

int Dvnum;

char temp[80];

switch (iMsg)

{

case WM_CREATE :

return 0 ;

//화면 출력 시에

case WM_PAINT :

//디바이스 정보를 얻는 방법

hdc = BeginPaint (hwnd, &ps) ;

Dvnum=GetDeviceCaps(hdc,HORZRES );

wsprintf(temp,"HORZRES(수평픽셀크기) = %d",Dvnum);

TextOut(hdc,0,0,temp,strlen(temp));

Dvnum=GetDeviceCaps(hdc,VERTRES );

wsprintf(temp,"VERTRES(수직픽셀크기) = %d",Dvnum);

TextOut(hdc,0,20,temp,strlen(temp));

Dvnum=GetDeviceCaps(hdc,BITSPIXEL );

wsprintf(temp,"BITSPIXEL(픽셀당 비트) = %d",Dvnum);

TextOut(hdc,0,40,temp,strlen(temp));

Dvnum=GetDeviceCaps(hdc,NUMCOLORS );

wsprintf(temp,"NUMCOLORS(칼라수) = %d",Dvnum);

TextOut(hdc,0,60,temp,strlen(temp));

EndPaint (hwnd, &ps) ;

return 0 ;

//문자키를 눌렀을때

case WM_CHAR:

//GetDC를 이용하여 윈도우 클라이언트를 얻는 방법

hdc=GetDC(hwnd);

wsprintf(temp,"문자키를 눌렀을때 출력함");

TextOut(hdc,0,80,temp,strlen(temp));

ReleaseDC(hwnd,hdc);

break;

//좌측 마우스 버튼을 눌렀을때

case WM_LBUTTONDOWN:

//GetWindowDC를 이용하여 윈도우 전체 영역을 얻는 방법

hdc=GetWindowDC(hwnd);

wsprintf(temp,"GetWindowDC를 이용하여 출력");

TextOut(hdc,0,0,temp,strlen(temp));

ReleaseDC(hwnd,hdc);

break;

//우측 마우스 버튼을 누렀을때

case WM_RBUTTONDOWN:

//윈도우즈 화면 전체의 DC 를 얻는방법

hdc=CreateDC("DISPLAY",NULL,NULL,NULL);

wsprintf(temp,"윈도우 전체 화면 DC를 얻어서 출력");

TextOut(hdc,0,0,temp,strlen(temp));

DeleteDC(hdc);

break;

case WM_DESTROY :

PostQuitMessage (0) ;

return 0 ;

}

return DefWindowProc (hwnd, iMsg, wParam, lParam) ;

}

(프로그램 소스끝)

본예에서는 sprintf대신에 wsprintf를 사용하였습니다. sprintf 사용법은 같습니다. 다만 wsprintf는 윈도우에서 자주 사용하는 형태이며 wsprintf 는 설정되는 문자열의 전체 길이를 리턴하기 때문에 다양한 방법으로 사용할 수가 있습니다. 앞으로 자주 사용을 할것입니다. 사용하시면서 wsprintf의 기능을 익히실수가 있을것입니다. GetDeviceEX의 출력결과는 그림 4와 같습니다.

(그림 4)GetDeviceEx출력 결과

GetDeviceEx에서 새로운 메시지 들을 보셨을겁니다. WM_CHAR은 키보드에서 문자를 출력하였을 때 발생하는 메시지입니다.  GetDeviceEx프로그램에서 키보드에서 문자키를 누르면 하단부에 □문자키를 눌렀을 때 출력함□ 이라는 글자가 나오게 됩니다. WM_LBUTTONDOWN의 메시지는 좌측 마우스 버튼을 클릭하였을경우이며 WM_RBUTTONDOWN은 우측 마우스 버튼을 눌렀을 때 메시지입니다. 우측 마우스 버튼을 눌렀을 경우 아마 모니터 화면의 (0,0)좌표에 글자가 출력되는 것을 보실수가 있을겁니다. 이런 메시지에 대해서는 뒷부분에서 자세히 설명하겠습니다.

2.텍스트 출력하기

기본적인 텍스트 출력 TextOut

지금부터는 HDC핸들을 받고 이 HDC를 이용하여 문자를 출력하는 내용을 설명합니다. TextOut함수는 지금까지 가장 많이 사용한 함수입니다. 이제 이곳에서 그정체를 밝혀 보겠습니다.

BOOL TextOut(

HDC hdc, // 디바이스 컨텍스 핸들

int nXStart, // 문자 출력 시작점 x좌표

int nYStart, // 문자 출력 시작점 y좌표

LPCTSTR lpString, // 문자열

int cbString // 문자열의 길이

);

hdc란 전항목의 방법으로 얻은  디바이스 컨텍스 핸들입니다. nXStart와 nYStart는 좌표를 이야기 합니다. 이좌표는 HDC가 컨트롤 하는 DC의 좌표를 이야기 합니다. WM_PAINT를 이용하여 클라이언트를 얻었다면 좌표는 클라이언트 영역 좌표를 이야기 합니다. lpString는 문자열 포인터입니다.  LPCTSTR이라는 형태는 long pointer const char* 라는 의미입니다. 아직 이해하기 힘드므로 그냥 단순하게 char * 또는 char의 배열을 설정할수 있다고 생각하시면 됩니다. cbString는 문자열의 길입니다. 전에도 설명하였듯이 출력할 문자의 길이값을 설정하면 그에 해당하는 문자길이만 출력을 합니다.

박스 영역에 출력 함수 DrawText

int DrawText(

HDC hdc, //디바이스 컨텍스트 핸들

LPCSTR lpString,//출력할 문자

int nLength, //출력할 문자의 수

LPRECT lpRect, //출력할 영역

UINT Flags);// 출력 형식 플러그

DrawText함수는 문자를 출력할 때 어느 영역을 정하고 이영역에 출력하는 함수입니다. hdc는 디바이스 컨텍스트 핸들이며 lpString 는 출력할 문자입니다. nLength란 문자의 길이를 말합니다. 만일 -1로 설정하였을 경우 NULL로 종료되는 쉽게 의미하면 전체 문자를 출력하게 됩니다. lpRect는 출력할 영역을 의미합니다. Flags는 어떻게 출력할것인가의 형식을 말합니다.

이함수를 이해하기 전에 잘이해가 되지 않는 형이 있습니다. 이것이 바로 LPRECT일것입니다. 이것은 사각형 구조체입니다. LPRECT란 RECT의 LONG POINT라는 의미입니다. RECT* 라는 개념으로 보셔도 될것입니다. RECT의 구조체는 다음과 같습니다.

typedef struct tagRECT {

LONG left;

LONG top;

LONG right;

LONG bottom;

} RECT;

좌측  상단과 우측 하단의 좌표값을 가지고 있는 left,top,right,bottom을 맴버로 가지고 있는 구조체입니다. 이구조체의 변수인 lpRect의 인자가 있다는 의미는 출력할 곳이 좌표가 아니라 어떤 박스 영역이라는 의미입니다.

출력 형식 플러그는 매우 많습니다. 표2는 Flags의 리스트들입니다.

예를 들어 화면의 0,0,400,400영역의 중앙에 글자를 출력하고자 한다면 다음과 같이 할수 있습니다.

RECT rect={0,0,400,400};

DrawText(hdc,temp,strlen(temp),&rect,

DT_SINGLELINE | DT_CENTER | DT_VCENTER);

DT_SINGLELINE | DT_CENTER | DT_VCENTER 란의미는 표2에서 보듯이

값	내용
DT_TOP DT_BOTTOM DT_LEFT DT_RIGHT	설정된 lpRect영역에 상단으로(DT_TOP) 하단으로(DT_BOTTOM) 좌측으로(DT_LEFt)우측으로(DT_RIGHT) 문자를 출력 한다. DT_SINGLELINE가 함께 지정되어 있어야 한다.
DT_CENTER	설정된 영역의 가로중앙에 정렬시킨다
DT_VCENTER	설정된 영역의 세로 중앙에 정렬 시킨다.
	DT_
DT_EXPANDTABS	문자열에 탭이 포함되어 있을 때 공백으로 띄어 준다. 기본폭은 8이다.
DT_CALCRECT	텍스트가 출력할 사각형의 영역을 계산한다. 이때 문자는 출력되지 않는다.
DT_EXTERNALLEADING	문자의 높이를 계산할 때 extenal leading sapce를 호함시킨다.
DT_SINGLELINE	행바꿈과 라인 피드를 무지하고 한줄로 출력한다.
DT_NOCLIP	클리핑 없이 문자를 출력한다.
DT_NOPREFIX	&문자를 표식어로 상용하지 않는다. 일반적으로& 코드는 출력하지 않고 다음 문자에 밑줄을 그린다.
DT_TABSTOP	Flat 상위 8비트에 값을 넣어서 탭간격을 변화시킨다.
DT_WORDBREAK	문자열이 영역밖으로 출력되면 개행하여 출력한다.

(표2) Flags의 값들

한줄로 쓰고 화면 가로 세로 중앙에 쓰라는 의미입니다. □|□연산자를 이용하여 위의 값을 모두 혼합하여 사용할수 있습니다. 그러나 혼합하여 사용할 때 문제가 되는것도 있습니다. 예를 들어서 DT_TOP|DT_BOTTOM 이라고 하면 이것은 화면 상단에 정렬하면서 하단에 정렬하라는 의미입니다. 결국 출력 값이 무의미가 됩니다.

이럴 경우 어떻게 될까요? 상위차순 우선이 됩니다. DT_BOTTOM이 비트 플러그가 DT_TOP보다 우선하기 때문에 하단부에 써지게 됩니다. 그러나 이런 값들의 의미는 사실 모순된 의미입니다.

박스 영역 및 좌표 설정 문자출력 ExtTextOut

BOOL ExtTextOut(

HDC hdc, // 디바이스 컨텍스 핸들

int X, // 출력 X좌표

int Y, //출력 Y좌표

UINT fuOptions, // 출력 옵션

CONST RECT *lprc, //출력 박스 영역

LPCTSTR lpString, // 출력 문자열

UINT cbCount, // 출력 문자 길이

CONST INT *lpDx // 문자열의 원점으로부터 출력 값의 배열

);

ExtTextOut함수는 좌표와 영역을 함께 사용하는 출력 함수입니다.

fuOptions를 ETO_CLIPPED로 설정하였을경우에는 X,Y값이 무시되고  lprc박스 영역이 유효화 하여 이영역에 문자열을 출력시킵니다. 또한 Option중에 ETO_OPAQUE는 출력할 영역의 배경을 현재 설정된 배경색으로 설정할수 있습니다.  만일 ExtTextOut함수이전에 SetBkColor 함수를 사용하여 배경색으로 특정 색을 설정하면 lprc영역이 해당색으로 출력됩니다.

(Tips)

SetBkColor

COLORREF SetBkColor(

HDC hdc, // 디바이스 컨텍스트 핸들

COLORREF crColor // 배경색 값

);

hdc영역의 배경색을 설정할 때 사용하는 함수입니다. COLORREF는 RGB값을 가지고 있는 정보 구조체입니다. 본장 뒷부분에 이값에 대해서 자세히 설명할것입니다. 만일 배경을 적색으로 하고자 할 경우 다음과 같이 할수 있습니다.

SetBkColor(hdc,RGB(255,0,0));

(Tips끝)

탭키에 대응하여 문자출력 함수 TabbedTextOut

LONG TabbedTextOut(

HDC hDC, // 디바이스 컨텍스트 핸들

int X, // 출력 x좌표

int Y, // 출력 y좌표

LPCTSTR lpString, // 출력 문자열

int nCount, //출력할 문자열의 길이

int nTabPositions, // 탭값의 정수형 배열 수

LPINT lpnTabStopPositions, // 탭 폭의 정수형 배열

int nTabOrigin //  탭이 시작되는 x좌표의 위치

);

TabbedTextOut함수는 탭키 (□\t') 값이 들어 있는 문자열에서 탭키만큼 공백을띄울 때 사용하는 함수입니다. nTapPositios가 0일경우에는 설정된 배열이 없기 때문에 기본적으로 설정된 8의 값을 가지고 문자의 평균 가로크기 곱하기 8의 값만큼 사이를 띄어 줍니다. 만일 탭키의 배열값이 1개일 경우 이때는 nTabPositons는 1이 설정됩니다. 그렇게 되면 lpnTabStopPositons의 첫 번째값만큼 가로 크기를 정규적으로 띄어 줍니다.

int tabstop[4]={8,16,24,32};

wsprintf(temp,"TabbedTextOut\t를\t이용한\t출력");

TabbedTextOut(hdc,0,20,temp,strlen(temp),4,tabstop,0);

위와 같이 하였을 경우 첫 번째 탭에서는 설정된 문자열의 평균 가로크기의 8배 두 번째 탭에서는 16 배  3번째 탭에서는 24배순으로 공백으로 띄어 줍니다.

만일

TabbedTextOut(hdc,0,20,temp,strlen(temp),1,tabstop,0);

이라고 하였다면 탭키를 만날 때 마다 tabstop의 첫 번째 값 즉 8값만큼 균일적으로 공백으로 띄어 주게 됩니다.

색 정의 구조체 COLORREF

컴퓨터에서 사용하는 색은 R,G,B 3원색입니다. 즉 적색(Red),녹색(Green),청색(Blue) 의 3원색을 사용하여 일반적인 색을 표현합니다. COLORREF는 이 RGB값을 32비트 안에 저장하는 색값입니다. R,G,B값을 각각 24비트씩 나누어서 값을 저장합니다. 실제고 이값중에서 8비트씩만 사용합니다. 그이유는 각각의 색정보가 0-255까지 이기 때문에 8비트만 유효하기 때문입니다. 만약 R값이 255이면  적색값이 가장 최대이고 0이면 최하입니다. R,G,B모든 값이 255일경우에는 하얀색이며 0일경우에는 검정색이됩니다. 적색의 칼라값을 COLORREF로 만들고자 한다면 다음과 같을것입니다.

COLORREF co=RGB(255,0,0);

RGB함수는 BYTE단위로 첫 번째 인자는 적색(Rec) 두 번째 인자는 녹색(Green)세번째 인자는 청색(Blue)의 강도를 나타냅니다. 위의 예에서는 녹색과 청색의 값은 0이고 적색의 값이 255인 최대치 이므로 적색의 값이 설정되게 됩니다. 그래픽을 출력할 때 색을 설정할 경우 대부분이 COLORREF의 값을 이용하므로 기억해 두셔야 할 형입니다.

문자출력 색설정하기 SetTextColor

COLORREF SetTextColor(

HDC hdc, //디바이스 컨텍스트 핸들

COLORREF crColor //문자열 출력 칼라

);

SetTextColor은 출력할 문자의 색을 설정하는 함수입니다. 위의 함수를 설정하면 이후에 출력되는 모든 글자의 색은 crColor값으로 전환됩니다. 예를 들어 출력되는 문자를 청색으로 하고자 한다면 다음과 같이 하면 됩니다.

SetTextColor(hdc,RGB(0,0,255));

택스트 출력 예제 ExamText

ExamText는 지금까지 배운 텍스트 출력 함수를 이용한 예제입니다.

(프로그램 소스)

//텍스트 출력 예제

//ExamText.c

#include <windows.h>

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

PSTR szCmdLine, int iCmdShow)

{

static char szAppName[] = "문자출력 예제:ExamText" ;

HWND        hwnd ;

MSG         msg ;

WNDCLASSEX  wndclass ;

wndclass.cbSize        = sizeof (wndclass) ;

wndclass.style         = CS_HREDRAW | CS_VREDRAW ;

wndclass.lpfnWndProc   = WndProc ;

wndclass.cbClsExtra    = 0 ;

wndclass.cbWndExtra    = 0 ;

wndclass.hInstance     = hInstance ;

wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;

wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;

wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;

wndclass.lpszMenuName  = NULL ;

wndclass.lpszClassName = szAppName ;

wndclass.hIconSm       = LoadIcon (NULL, IDI_APPLICATION) ;

RegisterClassEx (&wndclass) ;

hwnd = CreateWindow (szAppName,

"문자출력예제:ExamText",

WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

NULL,

NULL,

hInstance,

NULL) ;

ShowWindow (hwnd, iCmdShow) ;

UpdateWindow (hwnd) ;

while (GetMessage (&msg, NULL, 0, 0))

{

TranslateMessage (&msg) ;

DispatchMessage (&msg) ;

}

return msg.wParam ;

}

LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)

{

HDC         hdc ;

PAINTSTRUCT ps ;

char temp[80];

int tabstop[4]={8,16,24,32};

int extdistance[5]={1,1,1,1,1};

RECT rect={0,0,400,400};

RECT extrect={0,40,100,80};

switch (iMsg)

{

case WM_CREATE :

return 0 ;

case WM_PAINT :

hdc = BeginPaint (hwnd, &ps) ;

//DrawText를 이용한 출력

wsprintf(temp,"DrawText를 이용한 출력");

DrawText(hdc,temp,strlen(temp),&rect,

DT_SINGLELINE | DT_CENTER | DT_VCENTER);

//TextOut를 이용한 출력

wsprintf(temp,"TextOut를 이용한 출력");

TextOut(hdc,0,0,temp,strlen(temp));

wsprintf(temp,"TabbedTextOut\t를\t이용한\t출력");

//TabbedTextOut를 이용한 출력

TabbedTextOut(hdc,0,20,temp,strlen(temp),4,tabstop,0);

wsprintf(temp,"ExtTextOut This is test");

//배경색 설정

SetBkColor(hdc,RGB(255,0,255));

//ExtTextOut함수를 이용한 출력

ExtTextOut(hdc,0,40, ETO_CLIPPED |ETO_OPAQUE,

&extrect,temp,strlen(temp),NULL);

SetBkColor(hdc,RGB(0,0,255));

//텍스트 색 설정

SetTextColor(hdc,RGB(255,0,0));

wsprintf(temp,"setcolor red");

TextOut(hdc,0,100,temp,strlen(temp));

EndPaint (hwnd, &ps) ;

return 0 ;

case WM_DESTROY :

PostQuitMessage (0) ;

return 0 ;

}

return DefWindowProc (hwnd, iMsg, wParam, lParam) ;

}

(프로그램 소스끝)

ExamText의 출력 결과는 그림5와 같습니다.

(그림 5) ExamText출력 결과

3.텍스트 스타일 설정하기

GUI 환경에서는 문자를 출력하고자 할 경우 많은 방법들이 이용됩니다.

GUI 환경에서는 문자를 출력할 때 다양한 폰트를 가지고 사용알수 있으며 또한 크기도 다양하게 변경시킬수 있습니다. 그저 단순하게 출력하는 텍스트 형태이라면 GUI 환경이라는 말이 사용되지 않을지도 모르겠습니다.

폰트 만들기

윈도우에는 기본적인 시스템 폰트가 있습니다. 그리고 그외에 여러 폰트들이 있으며 워드프로세서를 구입하여 설치하면 워드프로세서 제조업체들이 제공하는 여러 폰트들이 있습니다. 윈도우를 사용하시다보면 글씨체를 바꿀대 □바탕체□,□신명조□ 등의 여러 글자체를 보신적이 있을것입니다. 프로그램에서 폰트를  설정하지 않으면 기본적인 시스템 폰트를 설정합니다. 그러나 우리는 때때로 다양한 폰트를 로드하고 그폰트로 글자를 출력하고 싶을경우가 있을것입니다. 이때 폰트를 설정하기 위해서는 폰트를 로드하여 이것을 특정 핸들러에 연결시켜야 합니다. 폰트를 핸들링하는 형은 HFONT입니다.

HFONT hFont;

라고 선언을 하면 hFont는 윈도우 시스템에 등록되어 있는 폰트들을 로드할수 있습니다. 윈도우 시스템에서 특정 폰트를 로드하여 폰트 핸들러를 만들고자 한다면 CreateFont함수를 사용합니다.

HFONT CreateFont(

int nHeight, // 폰트의 높이

int nWidth, // 폰트의 가로크기

int nEscapement, // escapement 백터 각도

int nOrientation, // 기본 선(보통 수평선)에서의 각도

int fnWeight, // 폰트 가중치

DWORD fdwItalic, // 이탤릭 속성 플러그

DWORD fdwUnderline, // 밑줄 속성 플러그

DWORD fdwStrikeOut, // 중앙 선 속성 플러그

DWORD fdwCharSet, // 문자열셋 명칭

DWORD fdwOutputPrecision, // 출력 정밀도

DWORD fdwClipPrecision, //클리핑 정밀도

DWORD fdwQuality, // 출력 문자의 질

DWORD fdwPitchAndFamily, // 글꼴의 피치의 종류

LPCTSTR lpszFace //폰트체의 이름

);

nHeight와 nWidth는 폰트의 가로와 세로의 높이를 말합니다. nEscapement란 글자의 출력의 각도를 말합니다. 보통 0이면 일반 수평방향으로 글자가 출력됩니다.

그림 6의 90 값의 방향으로 글자가 출력되는 형태입니다.

(그림 6) nEscapememt값이 90일 경우

이때 90의 의미는 1/10의 각도 단위이므로 9도정도 비껴서 출력이 됩니다.

nOrientation 은 장치의 기본선(baseline)에서의 x축과의 기울어진 각도를 의미합니다. 이각도는 1/10도 단위입니다.

fnWeight는 폰트의 가중치입니다. 굵게 또는 가늘게 등의 여러 옵션이 있습니다. 해당하는 옵션은 다음과 같습니다.

값	내용
FW_DONTCARE	상관하지 않음
FW_THIN	가늘게
FW_BOLD	진하게
FW_LIGHT	약간 밝게
FW_NORMAL	보통 문자
FW_HEAVY	매우 두껍게

(표 3) fnWeight 의 값들

이외에도 FW_EXTRABOLD, FW_EXTRALIGHT ,FW_MEDIUM,FW_MEDIUM등의 플러그가 있습니다.

fdwItalic,fdwUnderline,fdwStrikeOut, 들은 각각 이탤릭 밑줄 중앙 줄에 대한 플러그입니다. 해당 변수들의 값을 1로 설정하면 활성화 되며 0으로 설정하면 비활성화 됩니다. fdwCharSet 은 문자열 셋의 명칭입니다. 문제세트는 일반적으로 사용하는 DEFAULT_CHARSET와  안시 문자코드를 사용하는 ANSI_CHARSET그리고 OEM문자 세트를 사용하는 OEM_CHARSET 또한 한글 문자세트를 사용하는 HANGEUL_CHARSET 등이 있습니다. OEM 문자세트란 IBM에서 만든 문자세트를 말합니다. ANSI_CHARSET란 일반적으로 윈도우즈에서 사용하는 문자세트를 말합니다.

fdwOutputPrecision은 출력의 정밀도를 의미하는데 출력문자의 높이와 가로크기 문자의 각도와 피치등의 정밀도를 어떻게 할것인가를 정의하는 값입니다. 이값은 다음의 값들중 하나를 설정할수 있습니다.

OUT_DEFAULT_PRECIS

OUT_STRING_PRECIS

OUT_CHARACTER_PRECIS

OUT_STROKE_PRECIS :윈도우95에서는  백터폰트에서 사용됨

OUT_DEVICE_PRECIS

OUT_TT_PRECIS

OUT_OUTLINE_PRECIS : 윈도우95에서는 사용하지 않음

OUT_RASTER_PRECIS

fdwClipPrecision은 클리핑의 정밀도를 의미합니다. fdwQuality 는 GDI가 논리적 글형태의 속성을 맞출 때 사용하는 옵션입니다.

fdwPitchAndFamily 는 글꼴 피치의 종류를 의미하는 것으로 DEFAULT_PITCH와 FIXED_PITCH,VARIABLE_PITCH를 설정할수 있습니다.

lpszFace는 설정할 폰트의 이름의 문자열 입니다.

폰트하나 설정하는데 매우 인자가 많을것입니다. 그리고 옵션 또한 많구요. 그만큼 다양하고 섬세하게 글자를 출력할수 있다는 의미입니다. 이제 막 윈도우 프로그램에 들어선 지금에서 폰트를 하나 DC에 등록하는데 이모든 인자를 이해하고자 한다면 욕심일것입니다. 위에서 중요한 인자는 nHeight와 nWidth그리고 lpszFace 일것입니다. 나머지 옵션중에서 많은 부분들은 DEFAULT옵션을 사용하시면 됩니다.

다음은 폰트를 하나 만드는 예입니다.

HFONT hFont;

hFont=CreateFont(10,10,//가로 세로 크기

0,0,//기울어짐 각도

FW_DONTCARE,//폰트의 굵기

FALSE,//이탤릭 플러그

FALSE,//언더라인 플러그

FALSE,//STRIKEOUT 플러그

DEFAULT_CHARSET,//CHAR SET

OUT_DEFAULT_PRECIS,

CLIP_DEFAULT_PRECIS,

DEFAULT_QUALITY,

DEFAULT_PITCH,

"바탕체");

이탤릭이나 언더라인 중앙선등은 FALSE할 경우 비활성 되고 TRUE할 경우 활성화 합니다. 그이외의 내용은 처음에는 DEFAULT플러그 값을 선택하시고 계속적인 프로그램을 하면서 값을 변경시키면서 확인해 보시면 이해하실수 있을것입니다.

DC에 폰트 등록

DC안에는 위와 폰트를 설정하지 않아도 기본적으로 시스템 폰트가 설정되어 있습니다. 그렇기 때문에 지금까지 예제에서 폰트를 만들지 않아도 TextOut같은 함수로 글자를 출력할수 있는것입니다. 만일 현재 기본적으로 설정된 폰트를 사용하지 않고 바로 전항목에서 처럼 폰트를 만들어서 이폰트를 사용하고자 한다면 SelectObject라는 함수를 사용하시면 됩니다.

HGDIOBJ SelectObject(

HDC hdc, //디바이스 컨텍스트

HGDIOBJ hgdiobj 설정 Object

);

SelectObject는 폰트외에서 여러 그리기 객체들을 등록할 때 다양하게 사용합니다. SelectObject함수를 이용하여 새로운 폰트를 설정하면 이전 폰트가 리턴됩니다.

위에서 만든 hFont를 SelectObject함수를 이용하여 설정하면 다음과 같을 것입니다.

SelectObject(hFont);

위와 같이 하면 이전에설정된 즉 시스템 폰트는 사라지게 됩니다. 그리고 이 폰트를 사용한 후에 DeleteObject함수를 이용하여 만든 폰트 핸들러를 삭제합니다.

DeleteObject(hFont);

여기에서 한번 생각해 볼것이 있습니다. 현재 설정된 DC안에는 hFont가 있습니다. 그런데 그 폰트를 삭제해 버리면 DC안에는 어떤 폰트가 있을까요? 정답은 폰트가 없다는 것입니다. 이와 같이 한후에 다시 글자를 출력하고자 한다면 어떻게 될까요? 아마 화면에 글자가 이상하게 깨져서 나오는 증상이 있을것입니다. 물론 간단한 프로그램에서는 이런일이 없이 깨끗하게 출력이 될 수도 있으나 복잡하게 많은 그래픽 요소를 사용하는 프로그램에서는 문제가 발생합니다. DC안에 설정된 폰트가 없기 때문이지요. 결국 폰트를 삭제하기 이전에 이전에 폰트를 다시 설정해주어야 합니다. 그렇기 위해서는 이전폰트의 포인터를 받아두었다가 설정할 필요가 있습니다. 폰트를 설정할 때 기본적처리는 다음과 같습니다.

HFONT hFont,oldFont;

hFont=CreateFont(10,10,//가로 세로 크기

0,0,//기울어짐 각도

FW_DONTCARE,//폰트의 굵기

FALSE,//이탤릭 플러그

FALSE,//언더라인 플러그

FALSE,//STRIKEOUT 플러그

DEFAULT_CHARSET,//CHAR SET

OUT_DEFAULT_PRECIS,

CLIP_DEFAULT_PRECIS,

DEFAULT_QUALITY,

DEFAULT_PITCH,

"바탕체");

oldFont= SelectObject(hFont);//이전 폰트를 oldFont에 저장한다.

: 문자출력

SelectObject(oldFont);//이전폰트를 다시 설정

DeleteObject(hFont);//만든 폰트를 삭제한다.

폰트 뿐만 아니라 그리기 객체모두 SelectObject를 이용하여 설정하고 사용후에는 이전 객체를 다시 설정하고 그리고 만든 객체를 DeleteObject함수를 이용하여 삭제하는 것이 기본입니다. 뒤에서 설명할 브러쉬나 펜들도 이런 루틴을 가지게 됩니다.

글자 출력의 정렬 방법

글자를 출력할 때 글자의 정렬 순이 있을것입니다. 보통 우리는 좌측을 기준으로 상위를 기준으로 출력을 합니다. 예를 들어 □abcd"글자를 화면 (0,0)에 출력을 하고자 한다면 좌표(0,0)에 a가가 맞추어 지고 그다음부터 연속적으로 bcd가 붙어서 출력이 됩니다. 이런 정열을 좌측 상단을 기준으로 글자를 출력한다고 할수 있습니다. 그런데 때로는 우측을 기준으로 글자를 출력할경우가 있습니다. □abcd"라는 글자를 (200,0)이라는 위치에서 우측을 기준으로 출력한다면 좌표(200,0)위치에 d가 맞추어지고 x좌표가 감소하면서 글자가 출력되게 됩니다. 윈도우 프로그램시에 이렇게 문자를 출력할 때 정렬하는 방법을 설정하는 함수가 있습니다. 이함수가 SetTextAlign입니다.

UINT SetTextAlign(

HDC hdc, // 디바이스 컨텍스 핸들

UINT fMode // 텍스트 정렬 모드

);

SetTextAlign함수의 hdc는 디바이스 컨텍스 핸들이며 fMode는 정렬에 대한 모드입니다. 이모드값은 표4와 같습니다.

(표4)출력 모드 플러그 리스트

모드	내용
TA_BASELINE	기본 베이스 라인에 의거하여 출력
TA_BOTTOM	설정된 좌표를 문자 하단부에 맞추어서 출력
TA_TOP	설정된 좌표를 문자 상단부에 맞추어서 출력
TA_CENTER	설정된 좌표를 문자 중앙에 맞추어서 출력
TA_LEFT	설정된 좌표를 문자 좌측에 맞추어서 출력
TA_RIGHT	설정된 좌표를 문자 우측에 맞추어서 출력

예를 들어서 문자출력을 설정된 좌표를 우측 하단부에 기준으로 맞추어서 출력하고자 한다면 SetTextAlign을 다음과 같이 설정할수 있습니다.

SetTextAlign(hdc,TA_RIGHT | TA_BOTTOM);

필요한 플러그를 '|'기호에 의해서 결합하여 텍스트를 출력할 수가 있습니다.

글자 정보 얻기

(그림 7) 문자의 정보 형태

폰트를 설정하였을 경우 그 크기가 고정되어 있는 형태는 이제 거히 쓰지 않습니다. 글자에 따라 가변폭으로 변화됩니다. 예를 든다면 I와 A가 가로 크기가 같다는 것은 보기에 그렇게 좋지 않기 때문입니다. 실제로 폰트를 설정하였을 경우 그림 7과 같은 각폰트이 기본정보가 있습니다. 어떤 글자를 출력할 때 한글자 한글자가 어떤 바둑판 모양에 출력할 필요가 있을 경우 또는 글자를 고정된 형태로 출력하고자 할 경우 필요한 정보들입니다. 그림 7과 같은 정보를 얻고자 할 경우 GetTextMetrics를 사용합니다. 이것을 사용하면 위의 6개의 값과 함께 tmAveCharWidth,와 tmMaxCharWidth를 얻을수 있습니다.

BOOL GetTextMetrics(

HDC hdc, // 디바이스 컨텍스트 핸들

LPTEXTMETRIC lptm // 텍스트 메트릭스 구조체 정보

);

GetTextMetrics에 설정해주는 두 번째 인자 LPTEXTMETRIC구조체는 다음과 같습니다.

typedef struct tagTEXTMETRIC { // tm

LONG tmHeight;

LONG tmAscent;

LONG tmDescent;

LONG tmInternalLeading;

LONG tmExternalLeading;

LONG tmAveCharWidth;

LONG tmMaxCharWidth;

LONG tmWeight;

LONG tmOverhang;

LONG tmDigitizedAspectX;

LONG tmDigitizedAspectY;

BCHAR tmFirstChar;

BCHAR tmLastChar;

BCHAR tmDefaultChar;

BCHAR tmBreakChar;

BYTE tmItalic;

BYTE tmUnderlined;

BYTE tmStruckOut;

BYTE tmPitchAndFamily;

BYTE tmCharSet;

} TEXTMETRIC;

위의 구조체에서 그림7에 해당하는 값들 또한 얻을수 있습니다.

GetTextMetrics 함수를 이용하여 여러 정보를 얻고 이 정보를 출력하는 예를 다음과 같이 할수 있습니다.

TEXTMETRIC tm;

GetTextMetrics(hdc,&tm);

//Text Metrics 정보 출력

wsprintf(temp,"tmHeight=%d tmAscent=%d",tm.tmHeight,tm.tmAscent);

TextOut(hdc,0,80,temp,strlen(temp));

wsprintf(temp,"tmInternalLeading=%d tmExternalLeading=%d",

tm.tmInternalLeading,tm.tmExternalLeading);

TextOut(hdc,0,100,temp,strlen(temp));

wsprintf(temp,"tmAveCharWidth=%d tmMaxCharWidth=%d",

tm.tmAveCharWidth,tm.tmMaxCharWidth);

TextOut(hdc,0,120,temp,strlen(temp));

wsprintf(temp,"tmWeight=%d ",tm.tmWeight);

TextOut(hdc,0,140,temp,strlen(temp));

wsprintf(temp,"tmItalic=%d tmUnderlined=%d",

tm.tmItalic,tm.tmUnderlined);

TextOut(hdc,0,160,temp,strlen(temp));

wsprintf(temp,"tmDigitizedAspectX=%d tmDigitizedAspectY=%d",

tm.tmDigitizedAspectX,tm.tmDigitizedAspectY);

TextOut(hdc,0,180,temp,strlen(temp));

텍스트 스타일 설정  예제 TextStyle

TextStyle는 글자 출력 및 정렬방법을 이용한 예제입니다. 화면에 글자를 좌측 또는 우측, 상단 또는 하단으로 정렬하며 폰트를 만들고 폰트를 설정하고 설정된 폰트대로 문자를 출력하며 현재 설정된 폰트의 크기 정보 즉 TextMetric를 출력하는 예제입니다.

(프로그램 소스)

//텍스트 출력 예제

//ExamText.c

#include <windows.h>

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

PSTR szCmdLine, int iCmdShow)

{

static char szAppName[] = "문자 스타일 예제:TextStyle" ;

HWND        hwnd ;

MSG         msg ;

WNDCLASSEX  wndclass ;

wndclass.cbSize        = sizeof (wndclass) ;

wndclass.style         = CS_HREDRAW | CS_VREDRAW ;

wndclass.lpfnWndProc   = WndProc ;

wndclass.cbClsExtra    = 0 ;

wndclass.cbWndExtra    = 0 ;

wndclass.hInstance     = hInstance ;

wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;

wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;

wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;

wndclass.lpszMenuName  = NULL ;

wndclass.lpszClassName = szAppName ;

wndclass.hIconSm       = LoadIcon (NULL, IDI_APPLICATION) ;

RegisterClassEx (&wndclass) ;

hwnd = CreateWindow (szAppName,

"문자 스타일 예제:TextStyle",

WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

NULL,

NULL,

hInstance,

NULL) ;

ShowWindow (hwnd, iCmdShow) ;

UpdateWindow (hwnd) ;

while (GetMessage (&msg, NULL, 0, 0))

{

TranslateMessage (&msg) ;

DispatchMessage (&msg) ;

}

return msg.wParam ;

}

LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)

{

HDC         hdc ;

PAINTSTRUCT ps ;

char temp[80];

HFONT hFont,holdFont;

TEXTMETRIC tm;

switch (iMsg)

{

case WM_CREATE :

return 0 ;

case WM_PAINT :

hdc = BeginPaint (hwnd, &ps) ;

//폰트만들기

hFont=CreateFont(10,10,//가로 세로 크기

0,0,//기울어짐 각도

FW_DONTCARE,//폰트의 굵기

FALSE,//이탤릭 플러그

FALSE,//언더라인 플러그

FALSE,//STRIKEOUT 플러그

DEFAULT_CHARSET,//CHAR SET

OUT_DEFAULT_PRECIS,

CLIP_DEFAULT_PRECIS,

DEFAULT_QUALITY,

DEFAULT_PITCH,

"바탕체");

//폰트를 dc에 설정

holdFont=SelectObject(hdc,hFont);

//텍스트를 우측 상단 기준으로 출력

SetTextAlign(hdc,TA_RIGHT|TA_TOP);

wsprintf(temp,"TA_RIGHT와TA_TOP 출력합니다.");

TextOut(hdc,100,20,temp,strlen(temp));

//텍스트를 좌측 하단 기준으로 출력

SetTextAlign(hdc,TA_LEFT|TA_BOTTOM);

wsprintf(temp,"TA_LEFT와TA_BOTTOM 출력합니다.");

TextOut(hdc,0,60,temp,strlen(temp));

//Text Metrics정보 얻음

GetTextMetrics(hdc,&tm);

//Text Metrics 정보 출력

wsprintf(temp,"tmHeight=%d tmAscent=%d",

tm.tmHeight,tm.tmAscent);

TextOut(hdc,0,80,temp,strlen(temp));

wsprintf(temp,"tmInternalLeading=%d tmExternalLeading=%d",

tm.tmInternalLeading,tm.tmExternalLeading);

TextOut(hdc,0,100,temp,strlen(temp));

wsprintf(temp,"tmAveCharWidth=%d tmMaxCharWidth=%d",

tm.tmAveCharWidth,tm.tmMaxCharWidth);

TextOut(hdc,0,120,temp,strlen(temp));

wsprintf(temp,"tmWeight=%d ",tm.tmWeight);

TextOut(hdc,0,140,temp,strlen(temp));

wsprintf(temp,"tmItalic=%d tmUnderlined=%d",

tm.tmItalic,tm.tmUnderlined);

TextOut(hdc,0,160,temp,strlen(temp));

wsprintf(temp,"tmDigitizedAspectX=%d tmDigitizedAspectY=%d",

tm.tmDigitizedAspectX,tm.tmDigitizedAspectY);

TextOut(hdc,0,180,temp,strlen(temp));

//이전폰트 설정

SelectObject(hdc,holdFont);

//폰트 지우기

DeleteObject(hFont);

EndPaint (hwnd, &ps) ;

return 0 ;

case WM_DESTROY :

PostQuitMessage (0) ;

return 0 ;

}

return DefWindowProc (hwnd, iMsg, wParam, lParam) ;

}

(프로그램 소스끝)

TextStyle 출력 결과는 그림8과 같습니다.

(그림 8)TextStyle 출력 결과

4.선박스 도형 출력하기

화면에 선과 박스 도형을 출력하는 방법은 윈도우 프로그램에서는 의외로 간단합니다. 몇 개의 함수를 이용하면 간단하게 도형을 출력할 수가 있습니다. 또한 Win32 SDK에서 지원하는 그래픽 함수에는 베지어 곡선 등을 지원하는 함수가 있으며 다각형을 그리는 방법도 옵션에 따라 그릴수 있도록 편리하게 지원되어 있습니다.

라인 그리기

라인을 그릴 때 사용하는 함수는 MoveToEx와 LineTo라는 두 개의 함수를 쌍으로 사용합니다. 우리가 라인을 그리고자 한다면 그리는 시작점이 필요할것이고 그리고 종료 점이 필요하게 됩니다. MoveToEx함수는 그리는 시작점으로 그리기 좌표를 이동키는 기능을 합니다.

BOOL MoveToEx(

HDC hdc, // 디바이스 컨텍스트 핸들

int X, // 새로운 위치의 x좌표

int Y, // 새로운 위치의 y자표

LPPOINT lpPoint // 이전 위치 좌표

);

LPPOINT 는 좌표 x와 y점을 가지고 있는 구조체입니다. 이구조체는 다음과 같습니다.

typedef struct tagPOINT { // pt

LONG x;

LONG y;

} POINT;

X와 Y는 그리기 시작점으로 이동할 위치의 값정보를 설정하고 lpPoint는 이전에 설정되어 있는 그리기 좌표값을 설정해줍니다. lpPoint가 필요한 이유은 어떤 라인을 그릴 때 기준점에서 라인을 계속그리고자 한다면 MoveToEx함수를 실행하고 라인을 그린다음 다시 이전점으로 복귀할 필요성이 있습니다. 이럴 경우 이전 좌표를 하나의 변수에 기록해도 문제는 없지만 MoveToEx를 실행한후 넘겨주는 이전봐표 포인터로 다시 이동하는것도 편리한 방법이기 때문입니다.

MoveEx 함수를 실행한후에 그 위치부터 라인을 그리고자 할 때 LineTo함수를 사용합니다.

BOOL LineTo(

HDC hdc, // 디바이스 핸들

int nXEnd, // 그리는 좌표 종점 x

int nYEnd // 그리는 좌표 종점 y

);

우리가 라인을 (0,0)에서 (100,100) 위치로 그리고자 한다면 먼저 MoveToEx함수를 이용하여 (0,0)으로 이동시키고 LineTo함수를 이용하여 (100,100)까지 그림을 그리면 됩니다.

POINT prevpos;

MoveToEx(hdc,0,0,&prevpos);

LineTo(100,100);

박스 그리기

박스를 그리는 함수중 중요한 함수는 2개입니다. Rectangle와 RoundRect 함수입니다. Rectangle함수는 일반적인 박스를 그리는 함수이며 RoundRect는 모서리가 둥근 박스를 그리는 함수입니다.

BOOL Rectangle(

HDC hdc, // 디바이스 컨텍스트 핸들

int nLeftRect, // 좌측 x좌표

int nTopRect, // 상단 y좌표

int nRightRect, // 우측 x좌표

int nBottomRect //하단 y좌표

);

BOOL RoundRect(

HDC hdc, // 디바이스 컨텍스트 핸들

int nLeftRect, // 좌측 x좌표

int nTopRect, // 상단  y좌표

int nRightRect, // 우측 x좌표

int nBottomRect, // 하단 y좌표

int nWidth, // 모서리의 가로측 지름

int nHeight // 모서리의 세로측 지름

);

RoundRect함수에 nWidth와 nHeitht는 모서리를 어느정도로 둥글게 할것인가의 가로 세로의 지름입니다. 이 지름이 커질수록 원에 가까운 둥근 사각형이 만들어지게 됩니다. nLeftRect, nTopRect,nRightRect와 nBottomRect란 박스를 그리는 4개의 좌표를 의미합니다. 위의 두 개의 함수를 이용하여 박스를 그리는 방법은 다음과 같습니다.

//박스 그리기

Rectangle(hdc,0,0,100,100);

//라운드 박스 그리기

RoundRect(hdc,0,0,100,100,20,20);

다양한 원 그리기

원을 그리는 함수는 Ellipse,Arc,Chord,Pie등이 있습니다.

BOOL Ellipse(

HDC hdc, // handle to device context

int nLeftRect, // 좌측 x좌표

int nTopRect, // 상단 y좌표

int nRightRect, // 우측 x좌표

int nBottomRect // 하단 y좌표

);

BOOL Arc(

HDC hdc, // 디바이스 컨텍스 핸들

int nLeftRect, // 좌측 x좌표

int nTopRect, // 상단 y좌표

int nRightRect, // 우측 x좌표

int nBottomRect, // 하단 y좌표

int nXRadial1, // 그리기를 시작하는 x지점

int nYRadial1, // 그리기를 시작하는 y지점

int nXRadial2, // 그리기를 종료하는 x지점

int nYRadial2 // 그리기를 종료하는 y지점

);

BOOL Chord(

HDC hdc, // 디비이스 컨텍스 핸들

int nLeftRect, // 좌측 x좌표

int nTopRect, // 상단 y좌표

int nRightRect, // 우측 x좌표

int nBottomRect, //하단 y좌표

int nXRadial1, // 그리기를 시작하는 x지점

int nYRadial1, // 그리기를 시작하는 y지점

int nXRadial2, // 그리기를 종료하는 x지점

int nYRadial2 // 그리기를 종료하는 y지점

);

BOOL Pie(

HDC hdc, // 디비이스 컨텍스 핸들

int nLeftRect, // 좌측 x좌표

int nTopRect, // 상단 y좌표

int nRightRect, // 우측 x좌표

int nBottomRect, //하단 y좌표

int nXRadial1, // 그리기를 시작하는 x지점

int nYRadial1, // 그리기를 시작하는 y지점

int nXRadial2, // 그리기를 종료하는 x지점

int nYRadial2 // 그리기를 종료하는 y지점

);

Ellipse 함수는 설정한 박스영역에 맞는 원을 그려주며 Arc는 설정된 영역에 그리기 시작좌표 (x,y)에서 종료지점 (x,y)까지 원호를 그려줍니다.. Chord함수는 현을 그리며 Pie는 부채꼴을 그려주게 됩니다. Arc와Chord와 Pie의 출력형태는  그림 9와 같습니다.

(그림 9) Arc,Chord,Pie의 출력 형태

이 3개의 함수는 nXRadial1과 nYRadial1으로부터 시작하여 시계반대방향으로 nXRadial2와 nYRadial2까지의 호를 그려주며 이 Chord는 호와 호사이를 라인으로 연결시킨 형태이고 Pie는 이 호와 중점을 연결 시킨 형태입니다. 각함수의 예는 다음과 같습니다.

Ellipse(hdc,0,0,100,100);

Arc(hdc,0,0,100,100,50,0,100,50);

Chord(hdc,0,0,100,100,50,0,0,50);

Pie(hdc,0,0,100,100,50,0,0,50);

다각형과 Poly Line

다각형은 여러점을 연결하는 도형을 의미하며 Poly Line는 여러점에 직선을 연결한 것을 의미합니다. 다각형을 그릴때는 Polygon함수를 이용하며 Poly Line을 그릴경우에는  Polyline를 이용합니다. 이 두함수 모두 x와 y의 좌표 배열을 가지고 있어야 합니다. 두함수 형태는 다음과 같습니다.

BOOL Polyline(

HDC hdc, // 디바이스 컨텍스트 핸들

CONST POINT *lppt, // 그릴 좌표

int cPoints // 좌표의 수

);

BOOL Polygon(

HDC hdc, // 디바이스 컨텍스트 핸들

CONST POINT *lppt, // 그릴 좌표

int cPoints // 좌표의 수

);

다음은 여러점의 다각형을 그리는 예입니다.

POINT pgon[4]={308,120,440,118,380,212,308,120};

Polygon(hdc,pgon,4);

pgon배열은 총4개이고 이 4개의 배열을 모두 그리기 위해서 cPoints의 인자값에 4를 설정해 주었습니다. 필요한 배열에서 전체를 사용하지 않고 일부분을 사용할때는 cPoints는 유용하게 사용됩니다. 예를 들어서 다각형이 어떤 시간지연이나 이벤트가 발생된 후에 증가되면서 그려질 경우 cPoints를 증가시키는 방법을 사용하여 도형을 출력할 수가 있습니다.

베지어 곡선 그리기

베지어 곡선이라느 설정해준 최소 3개의 점사이로 곡선이 그려지는 것을 의미합니다.

(그림 10)베지어 곡선

그림 10과 같이 어떤 좌표를 설정해 주고 이좌표위치에 선을 긋게 되면 모서리가 생깁니다. 그림 10의 외곽에 모서리가 있는 직선이 일반적으로 그어진 선입니다. 이선의 형태를 부드럽게 곡선화 한 것이 베지어 곡선입니다. 이베지어 곡선을 만들고자 할 경우 각 점에 따른 여러 계산식이 필요하게 됩니다. 그러나 Win32 SDK에서는 이런 베지어 곡선을 그릴수 있는 함수를 지원합니다. 이함수가 PolyBezier입니다.

BOOL PolyBezier(

HDC hdc, // 디바이스 컨텍스트 핸들

CONST POINT *lppt, // 그릴 좌표

int cPoints // 좌표의 수

);

이 베지어 곡선 함수는 cPoints가 최소 3개 이상이 있어야 합니다. 3점이 있어야 중앙점을 깃점으로 곡선을 그릴수 있기 때문입니다. 다음은 베지어 곡선을 그리는 예입니다.

POINT pline[4]={50,133,146,99,246,133,247,212};

PolyBezier(hdc,pline,4);

선,박스,도형 그리기 예제 DrawEx

DrawEx는 지금까지 설명한 선,박스,도형을 그려주는 예제입니다.

(프로그램 소스)

//기본 그래픽 그리기

//DrawEx.c

#include <windows.h>

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

PSTR szCmdLine, int iCmdShow)

{

static char szAppName[] = "DrawEx" ;

HWND        hwnd ;

MSG         msg ;

WNDCLASSEX  wndclass ;

wndclass.cbSize        = sizeof (wndclass) ;

wndclass.style         = CS_HREDRAW | CS_VREDRAW ;

wndclass.lpfnWndProc   = WndProc ;

wndclass.cbClsExtra    = 0 ;

wndclass.cbWndExtra    = 0 ;

wndclass.hInstance     = hInstance ;

wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;

wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;

wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;

wndclass.lpszMenuName  = NULL ;

wndclass.lpszClassName = szAppName ;

wndclass.hIconSm       = LoadIcon (NULL, IDI_APPLICATION) ;

RegisterClassEx (&wndclass) ;

hwnd = CreateWindow (szAppName,

"기본그리기예제:DrawEx",

WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

NULL,

NULL,

hInstance,

NULL) ;

ShowWindow (hwnd, iCmdShow) ;

UpdateWindow (hwnd) ;

while (GetMessage (&msg, NULL, 0, 0))

{

TranslateMessage (&msg) ;

DispatchMessage (&msg) ;

}

return msg.wParam ;

}

LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)

{

HDC         hdc ;

PAINTSTRUCT ps ;

RECT rect;

POINT prevpos;

POINT pline[6]={50,133,146,99,246,133,247,212,58,216,50,133};

POINT pgon[4]={308,120,440,118,380,212,308,120};

switch (iMsg)

{

case WM_CREATE :

return 0 ;

case WM_PAINT :

hdc = BeginPaint (hwnd, &ps) ;

GetClientRect(hwnd,&rect);

//박스 그리기

Rectangle(hdc,rect.left+10,rect.top+10,rect.

right-10,rect.bottom-10);

//라운드 박스 그리기

RoundRect(hdc,rect.left+20,rect.top+20,rect.right-20,rect.bottom-20,

20,20);

//원 그리기

Ellipse(hdc,rect.left+30,rect.top+30,rect.right-30,rect.bottom-30);

Arc(hdc,0,0,100,100,50,0,100,50);

//직선 그리기

MoveToEx(hdc,rect.left+10,rect.top+10,&prevpos);

LineTo(hdc,rect.right-10,rect.bottom-10);

//베지어 곡선

PolyBezier(hdc,pline,4);

//폴리 라인

Polyline(hdc,pline,6);

//다각형

Polygon(hdc,pgon,4);

EndPaint (hwnd, &ps) ;

return 0 ;

case WM_DESTROY :

PostQuitMessage (0) ;

return 0 ;

}

return DefWindowProc (hwnd, iMsg, wParam, lParam) ;

}

(프로그램 소스 끝)

그림 11은 DrawEx의 출력 결과입니다.

(그림 11)DrawEx출력 결과

DrawEx.c에서 GetClientRect(hwnd,&rect); 의 실행후에는 윈도우의 클라이언트 영역 즉 외곽 경계선과 타이틀바가 감싸이 있는 실제 그래픽 출력 판의 크기를 rect에 저장시켜 줍니다.따라서 DrawEx 실행후에 화면의 크기를 바꾼다 하더라도 외곽에 그린 박스와 라운드 박스는 윈도우 크기에 맞게 변화되면서 출력이 되게 됩니다.

펜과 브러쉬

DC안에는 기본적인 그리기 객체들이 있습니다. 보통 펜과 브러쉬 폰트 비트맵 등이 이것들입니다. DC를 만들거나 또는  WM_PAINT에 의해서 얻어지는 DC들은 생성과함께 기본적인 펜과 브러쉬와 폰트를 가지고 있습니다. 기본적인 펜과 브러쉬는 검정색 펜과 흰색 브러쉬로 설정되어 있습니다. 그러나 그림을 그릴 때 우리는 언제나 검정색으로 라인을 그리고 흰색으로 칠하지는 않습니다. 다양한 색과 다양한 형태로 색을 칠하거나 라인을 그리게 됩니다. 이럴 경우 DC에 설정되어 있는 펜과 브러쉬를 바꾸어 줍니다. 예를 들어서 현재 흰색 브러쉬와 검정색 펜이 DC에 설정되어 있을 때 이것을 적색 펜과 청색 브러쉬로 만들고자 한다면 새로운 펜을 만들어 이펜의 색을 적색으로 하고 새로운 브러쉬를 만들어 이색을 청색으로 한후 DC에 있는 펜과 브러쉬를 바꾸어 주면 됩니다. 폰트를 만들 때 와 같은 루틴입니다. DC에 그리기 객체를 설정하고자 할 때 SelectObject를 사용합니다. 이함수를 사용하면 이전 그리기 객체가 리턴되고 우리가 현재 펜이나 브러쉬로 그리기작업을 종료했다면 이전 펜이나 브러쉬로 설정해 주면 됩니다. 그리고 만든 펜과 브러쉬는 삭제하는 형식이 됩니다.

펜을 설정할 때 사용하는 형이 HPEN입니다. Pen의 Handle라는 의미입니다.

새로운 펜을 만들 때 사용하는 함수는 CreatePen입니다.

HPEN CreatePen(

int fnPenStyle, // 펜의 스타일

int nWidth, // 펜의 굵기

COLORREF crColor // 펜의 색

);

fnPenStyle는 펜의 스타일을 설정합니다. windows.h에는 이스타일이 다음과 같이 정의되어 있습니다.

PS_SOLID

PS_DASH 점선

PS_DOT

PS_DASHDOT

PS_DASHDOTDOT

만일 적색의 PS_SOLID 의 펜을 만들고자 한다면 다음과 같이 하면 됩니다.

HPEN pen;

pen=CreatePen(PS_SOLID,1,RGB(255,0,0));

펜을 만들고 난후에는 이 펜을 DC에 설정합니다. DC에 설정하는 방법은 폰트를 설정하는 방법과 같은 형식입니다.

HPEN oldpen;

oldpen=SelectObject(hdc,pen);

SelectObject 함수를 이용하여 pen을 등록하게 되면 이전에 등록되었던 펜이 리턴되게 됩니다. 여기에서 현재 만들었던 pen은 언젠가 우리가 삭제해야할 펜입니다.

SelectObject를 이용하여 설정된 펜을 삭제한다면 얼마후에는 화면이 깨지는 현상이 나타날것입니다. 이런 것을 방지하기 위해 이전에 설정되어 있는 펜을 삭제전에 다시 설정해주기 위해 SelectObject 함수 실행과 함께 리턴한 이전펜을 oldpen이라는 변수를 설정해 주고 받아 둡니다. pen으로 라인그리기가 종료하면 이제 oldpen을 다시 설정해 주고 그리고 pen을 삭제합니다.

//pen으로 라인그리기를 한다.

SelectObject(oldpen); //이전 펜을 설정하고

DeleteObject(pen); //현재 설정한 펜을 삭제한다.

결국 펜이나 브러쉬나 폰트나 모든 방식이 다음과 같이 표현됩니다.

그리기 객체 p,old;

p.Creat...()//그리기 객체를 만든다.

old=SelectObject(hdc,p);

//그리기를 시행

SelectObject(old); //이전 객체로 대치

DeleteObject(p); //만든 그리기 객체를 지운다.

브러쉬는 HBRUSH라는 형으로 선언된 변수에 할당됩니다. 즉 브러쉬 핸들러 형이 HBRUSH라는 것입니다. CreateSolidBrush 는 브러쉬에 어떤 무늬패턴 없이 단색으로 칠하는 브러쉬를 설정할 때 사용합니다. 이외에 패턴을 이용하여 브러쉬를 만들경우에는 CreatePatternBrush를 사용하시면 됩니다. CreateSolidBrush는 다음과 같습니다.

HBRUSH CreateSolidBrush(

COLORREF crColor // 브러쉬의 색

);

브러쉬를 만들고 브러쉬를 이용한다음 삭제하는 방법은 Pen과 같습니다. 다음은 브러쉬를 만들고 이용하는 예입니다.

HBRUSH brush,oldbrush;

brush=CreateSolidBrush(RGB(255,0,0));

oldbrush=SelectObject(brush);

//브러쉬사용

SelectObject(&oldbrush);//이전 브러쉬로 전환

DeleteObject(&oldbrush);

펜과 브러쉬 사용 예제 DrawEx2

DrawEx2는 DrawEx에 펜과 브러쉬를 사용한 예제입니다.

(프로그램 소스)

//펜과 브러쉬 사용

//DrawEx2.c

#include <windows.h>

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

PSTR szCmdLine, int iCmdShow)

{

static char szAppName[] = "DrawEx2" ;

HWND        hwnd ;

MSG         msg ;

WNDCLASSEX  wndclass ;

wndclass.cbSize        = sizeof (wndclass) ;

wndclass.style         = CS_HREDRAW | CS_VREDRAW ;

wndclass.lpfnWndProc   = WndProc ;

wndclass.cbClsExtra    = 0 ;

wndclass.cbWndExtra    = 0 ;

wndclass.hInstance     = hInstance ;

wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;

wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;

wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;

wndclass.lpszMenuName  = NULL ;

wndclass.lpszClassName = szAppName ;

wndclass.hIconSm       = LoadIcon (NULL, IDI_APPLICATION) ;

RegisterClassEx (&wndclass) ;

hwnd = CreateWindow (szAppName,

"색칠하기예제:DrawEx2",

WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

NULL,

NULL,

hInstance,

NULL) ;

ShowWindow (hwnd, iCmdShow) ;

UpdateWindow (hwnd) ;

while (GetMessage (&msg, NULL, 0, 0))

{

TranslateMessage (&msg) ;

DispatchMessage (&msg) ;

}

return msg.wParam ;

}

LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)

{

HDC         hdc ;

PAINTSTRUCT ps ;

RECT rect;

POINT prevpos;

POINT pline[6]={50,133,146,99,246,133,247,212,58,216,50,133};

POINT pgon[4]={308,120,440,118,380,212,308,120};

HPEN hPen,oldPen;

HBRUSH hBrushR,hBrushG,hBrushB,oldBrush;

char *temp="This is DrawTest";

static int flag=0;

switch (iMsg)

{

case WM_CREATE :

return 0 ;

case WM_PAINT :

hdc = BeginPaint (hwnd, &ps) ;

GetClientRect(hwnd,&rect);

//청책 펜을 만든다.

hPen=CreatePen(PS_SOLID,2,RGB(0,0,255));

//적,녹,청 색의 브러쉬를 만든다.

hBrushR=CreateSolidBrush(RGB(255,0,0));

hBrushG=CreateSolidBrush(RGB(0,255,0));

hBrushB=CreateSolidBrush(RGB(0,0,255));

//DC에 설정

oldPen=SelectObject(hdc,hPen);

oldBrush=SelectObject(hdc,hBrushR);

//박스 그리기

Rectangle(hdc,rect.left+10,rect.top+10,rect.right-10,rect.bottom-10);

//라운드 박스 그리기

RoundRect(hdc,rect.left+20,rect.top+20,rect.right-20,

rect.bottom-20,20,20);

SelectObject(hdc,hBrushG);

//원 그리기

Ellipse(hdc,rect.left+30,rect.top+30,rect.right-30,rect.bottom-30);

//직선 그리기

MoveToEx(hdc,rect.left+10,rect.top+10,&prevpos);

LineTo(hdc,rect.right-10,rect.bottom-10);

SelectObject(hdc,hBrushB);

//베지어 곡선

PolyBezier(hdc,pline,4);

//폴리 라인

Polyline(hdc,pline,6);

//다각형

Polyline(hdc,pgon,4);

SelectObject(hdc,oldPen);

SelectObject(hdc,oldBrush);

DeleteObject(hPen);

DeleteObject(hBrushR);

DeleteObject(hBrushG);

DeleteObject(hBrushB);

//글자를 배경에 투명하게 출력

if(flag)

SetBkMode(hdc,TRANSPARENT);

TextOut(hdc,20,100,temp,strlen(temp));

EndPaint (hwnd, &ps) ;

return 0 ;

case WM_LBUTTONDOWN:

flag=1-flag;

InvalidateRgn(hwnd,NULL,TRUE);

break;

case WM_DESTROY :

PostQuitMessage (0) ;

return 0 ;

}

return DefWindowProc (hwnd, iMsg, wParam, lParam) ;

}

(프로그램 소스끝)

DrawEx2프로그램 출력 결과는 그림 12와 같습니다.

(그림 12)DrawEx2출력 결과

배경과 투명한 글자 출력

그림 12에서 This is DrawTest라는 글자가 출력될 때 글자 출력 부분은 배경이 흰색으로 출력되고 있습니다. 우리는 때로는 배경에 글자만 출력되고 배경색은 없는 형태의 글자를 출력하고자 할경우가 있습니다. 이럴 경우 SetBkMode 함수를 사용합니다. DrawEx2에서 좌측 마우스 버튼을 클릭하면 "This is DrawTest"라는 문자열이 현재 그림에 배경색이 출력됨이 없이 출력되는 것을 보았을것입니다.

현재 바탕에 글자만 출력할 때 사용하는 옵션을 설정하기 위한 SetBkMode함수 실행방법은 다음과 같습니다.

SetBkMode(hdc,TRANSPARENT);

5.맵핑모드

윈도우 시스템에서 화면 출력이라는 의미는 단순하게 모니터만을 의미하지 않습니다. 출력에 관련된 모든 디바이스에 화면을 출력할수 있습니다. 화면을 출력하고자 할 경우 일반적으로 출력 디바이스는 모니터와 프린터로 볼수가 있을것입니다. 뒤에 프린터에 대한 내용에서 프린터 출력에 대해서 배우겠지만 프린터에 문자나 그래픽을 출력하고자 할경우에도 DC를 이용합니다. 보통 쉽게 말해서 프린터 DC를 받아서 그곳에 출력한다라고 합니다. 여기에서 출력 디바이스에 따라서 출력하는 내용의 맵핑에 문제가 됩니다. 모니터로 출력할 경우 모니터는 가로가 길고 세로가 가로보다 작습니다. 또한 모니터는 출력하는 형태에서 기본 단위가 점입니다. 그러나 프린터에서는 가로가 작고 세로가 깁니다. 또한 기본단위를 점으로 하기에는 문제가 있습니다.(실지로 프린터에 출력할경우에도 하부에서 보면 점이 기본입니다)

윈도우 프로그래밍에서 프린터에 그래픽을 출력할때는 cm단위로 출력하는 것이 매우 편리합니다. 모니터는 모니터의 크기에 따라서 보는 형태를 자유롭게 변화 시킬수 있으나 프린터로 출력을 하게 되면 용지로 나오기 변화가 불가능합니다. 또한 글자크기를 정할경우에도 cm나 mm또는 인치로 사용하는 것이 편리합니다.

윈도우 시스템은 출력을 할 때 어떤 형태로 출력할것인가의 맵핑모드를 설정할수 있는 함수를 제공합니다. 즉 점단위나 또는 mm나 인치형태 또는 사용자정의의 맵핑모드를 할수 있는 다양한 방법을 제공합니다. 이제 이 맵핑모드에 대하여 공부해 보겠습니다.

지원되는 맵핑 모드

윈도우에서 지원하는 맵핑 모드는 표5와 같습니다.

(표5) 맵핑모드

맵핑 모드	기본 단위	X축 증가값	Y축 증가값
MM_TEXT	픽셀	우측으로	아래로
MM_LOMETRIC	0.1mm	우측으로	위로
MM_HIMETRIC	0.01 mm	우측으로	위로
MM_LOENGLISH	0.01 in	우측으로	위로
MM_HIENGLISH	0.001 in	우측으로	위로
MM_TWIPS	1/1440 in	우측으로	위로
MM_ISOTROPIC	(X,Y)값 설정	선택적	선택적
MM_ANISOTROPIC	(X,Y)값 설정	선택적	선택적

MM_TEXT는 모니터에 출력하는 기본 모드를 의미합니다. 실제로는 디바이스 기본 모드라고 생각하시면 됩니다. 우리가 지금까지 사용한 모드가 MM_TEXT입니다. MM_LOMETRIC는 기본단위가 0.1 mm이며  이좌표는 위로 올라갈수록 증가됩니다.

MM_HIMETRIC,MM_LOENGLIST,MM_HIENGLISH,MM_TWIPS 등은 일반적인 미터법 맵핑모드로 사용됩니다.  이런 맵핑모드를 사용할 때 수행되는 함수가 SetMapMode입니다.

int SetMapMode(

HDC hdc, // 디바이스 핸들

int fnMapMode // 맵핑모드

);

맵핑모드를 이해하기 위핸 하나의 예제를 작성해 보겠습니다. MetricMap 예제가 매핑모드를 이해하기 위한 예제입니다.

(프로그램 소스)

//Metric 맵핑 모드

//MetricMap.c

#include <windows.h>

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

PSTR szCmdLine, int iCmdShow)

{

static char szAppName[] = "MetricMap" ;

HWND        hwnd ;

MSG         msg ;

WNDCLASSEX  wndclass ;

wndclass.cbSize        = sizeof (wndclass) ;

wndclass.style         = CS_HREDRAW | CS_VREDRAW ;

wndclass.lpfnWndProc   = WndProc ;

wndclass.cbClsExtra    = 0 ;

wndclass.cbWndExtra    = 0 ;

wndclass.hInstance     = hInstance ;

wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;

wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;

wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;

wndclass.lpszMenuName  = NULL ;

wndclass.lpszClassName = szAppName ;

wndclass.hIconSm       = LoadIcon (NULL, IDI_APPLICATION) ;

RegisterClassEx (&wndclass) ;

hwnd = CreateWindow (szAppName,

"MM_LOMETRIC맵 예제:TextMap",

WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

NULL,

NULL,

hInstance,

NULL) ;

ShowWindow (hwnd, iCmdShow) ;

UpdateWindow (hwnd) ;

while (GetMessage (&msg, NULL, 0, 0))

{

TranslateMessage (&msg) ;

DispatchMessage (&msg) ;

}

return msg.wParam ;

}

LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)

{

HDC         hdc ;

PAINTSTRUCT ps ;

RECT rect={10,-10,100,-100};

char temp[80];

switch (iMsg)

{

case WM_CREATE :

return 0 ;

case WM_PAINT :

hdc = BeginPaint (hwnd, &ps) ;

//MM_LOMETRIC 모드로 전환한다.

SetMapMode(hdc,MM_LOMETRIC);

//rect크기의 박스 출력

Rectangle(hdc,rect.left,rect.top,rect.right,rect.bottom);

//디바이스 좌표로 전환

LPtoDP(hdc,(PPOINT)&rect,2);

//MM_TEXT좌표로 변환

SetMapMode(hdc,MM_TEXT);

//정보 출력

wsprintf(temp,"현재모드 MM_LOMETRIC");

TextOut(hdc,10,80,temp,strlen(temp));

wsprintf(temp,"출력 좌표: 10,-10,100,-100");

TextOut(hdc,10,100,temp,strlen(temp));

wsprintf(temp,"디바이스 좌표: %d %d %d %d",

rect.left,rect.top,rect.right,rect.bottom);

TextOut(hdc,10,120,temp,strlen(temp));

EndPaint (hwnd, &ps) ;

return 0 ;

case WM_DESTROY :

PostQuitMessage (0) ;

return 0 ;

}

return DefWindowProc (hwnd, iMsg, wParam, lParam) ;

}

(프로그램 소스끝)

본예를 출력하면 그림13과 같은 내용이 화면에 출력됩니다.

(그림 13)MetricMap 출력결과

위의 예는 먼저 RECT형 구조체인 변수 rect에 10,-10,100,-100의 값을 설정합니다.

다음 SetMapMode를 이용하여 MM_LOMETRIC 로 맵핑모드를 바꾸어 줍니다.

10,-10,100,-100으로 값을 정한 이유가 MM_LOMETRIC에서는 기준 좌표가 (0,0)이되고 여기서부터 하단부로 내려갈 때 마다 감소하기 때문에 음수를 사용한것입니다. 이렇게 한후에 Rectangle함수를 사용하여 박스를 그리면 그림 13과 같이 화면에 박스가 나타납니다. 이박스는  MM_LOMETRIC 형태에서의 10,-10,100,-100의 크기의 박스입니다. 즉 이좌표는 로직좌표라는 의미입니다. 이것은 실제 모니터에 출력되는 좌표와는 다른 좌표입니다. 실제 모니터에 출력되는 좌표를 디바이스 좌표라고 합니다. 로직좌표를 디바이스 좌표로 전환하는 함수가 LPtoDP함수입니다.

LPtoDP함수는 POINT구조체의 값을 전환시켜 줍니다. rect값을 디바이스 좌표로 전환할 경우 다음과 같이 하였습니다.

LPtoDP(hdc,(PPOINT)&rect,2);

위와 같이 하고 나면 rect는 (4,4,38,38)이라는 값이 설정됩니다. 즉 모니터에 실제 출력하는 값은 4,4,38,38 이라는 의미입니다.

로직좌표와 디바이스 좌표

MetricMap에서 보면 실제출력하는 박스는 픽셀단위의 좌표로는 (4,4,38,38)의 영역에 그려지나 이것을 MM_LOMETRIC로 설정되었을때는 (10,-10,100,-100)이라는 형태로 생각됩니다. 즉 실제 디바이스에 출력되는 좌표를 디바이스 좌표라 하며 SetMapMode함수 등을 이용하여 설정된 좌표를 로직 좌표라고 합니다.

모니터나 프린터로 출력할 경우 시스템에서는 언제나 디바이스 좌표를 사용하여 출력합니다. 만일 SetMapMode함수를 이용하여 맵핑모드를 바꾸었다고 한다면 시스템에서는 자신이 출력하는 좌표와 사용자가 생각하는 좌표 두 개를 만들어야 합니다. 그리고 사용자가 설정된 그래픽 좌표를(로직좌표) 디바이스 좌표로 전환을 시키는 것입니다. 이런 로직좌표와 디바이스 좌표를 사용하는 이유는 출력 디바이스가 모니터가 아니라는데 매우 중요한 의미가 있습니다. 어떤 내용을 프린터로 출력한다고 가정을 합시다. 이때 프린터의 점단위가 프린터의 성능에 따라 매우다릅니다. 600dpi 프린터와 1200dpi 프린터에서 한점의 크기가 같을수가 없습니다.

만약 일반적인 MM_TEXT모드로 라인을 출력한다고 가정을 했을 경우 50 도트의 라인을 프린터에 출력을 하면 600dpi에서 출력되는 형태가 1200dpi에서 출력되는 형태보다 크게 나타납니다. 그이유는 600dpi프린터가 출력하는 점의 크기가 1200dpi프린터의 점보다 2배 크기 때문입니다. 어떤 내용을 프린터로 출력했을 때 1200dpi에서 출력된 것이 600dpi에서 출력된것보다 작게 나온다면 즉 프린터에 따라 출력물이 변한다면 이것은 문제가 있습니다. 따라서 어떤 프린터 든지 같은 크기의 내용을 출력할수 있게 하기 위해서는 디바이스 좌표형태로 하지않고 로직좌표를 하나 만들어 이좌표형태로 그래픽을 그려 놓으면 시스템에서는 로직좌표 내용을 디바이스 좌표로 전환하여 출력하기 때문에 균일하게 출력할수 있는것입니다.

프린터로 가로 1cm 세로 1cm 위치에 대각선 라인을 그리고자 한다면 맵핑모드를 MM_LOMETRIC 로 설정하고

SetMapMode(hdc,MM_LOMETRIC);

MoveToEx(hdc,0,0,NULL);

LineTo(hdc,0,-1000,NULL);

하면 될것입니다.  600dpi프린터에서 1cm안에 점이 50개가 들어가고 1200dpi에서는 1cm 안에 점이 100개가 들어간다고 가정을 합시다.

600dpi프린터에서 위의 좌표를 디바이스 좌표로 전환하게 되면 (0,0)에서 (0,50)으로 점을 찍게되는것이고 1200dpi에서는 (0,0)에서 (0,100)으로 점을 찍게되는 것입니다.

프로그래밍을 할 때 로직좌표를 MM_TEXT로 사용하지 않을 경우 때로는 디바이스 좌표값을 알고자 할 때가 있고 또는 디바이스 좌표를 로직좌표로 변환시키고자 할경우가 있습니다. 이때 사용하는 함수가 DPtoLP LPtoDP입니다.

BOOL LPtoDP(

HDC hdc, // 디바이스 컨텍스트 핸들

LPPOINT lpPoints, // 전환될 좌표 배열

int nCount // 좌표수

);

BOOL DPtoLP(

HDC hdc, // 디바이스 컨텍스트 핸들

LPPOINT lpPoints, // 전환될 좌표 배열

int nCount // 좌표수

);

Viewport와 Window

디바이스 좌표를 가지고 있는 어떤 좌표 평면의 영역이을 Viewport라고 하고 로직좌표를 가지고 있는 어떤 좌표 평면을 Window라고 합니다. 이런 개념을 쓰는 이유는 좌표 평면의 정보를 수정하고자 할경우가 있기 때문입니다.

(그림 14) (0,0)좌표 이동

그림 14의 (a)처럼  MM_TEXT에서는 좌측 상단 이 (0,0)의 초기점입니다. 이때 때로는 화면 영역의 중앙이 (0,0)으로 하고자 할경우가 있습니다 그림 14 의 (b)처럼 말입니다. 이럴 경우 SetWindowOrgEx라는 함수를 사용하여 이동시킬수 있습니다.

BOOL SetWindowOrgEx(

HDC hdc, // 디바이스 컨텍스 핸들

int X, // 이동 X좌표

int Y, // 이동 Y좌표

LPPOINT lpPoint // 이전 좌표

);

예를 들어 화면의 현재 전체 영역이 (400,400)이라고 가정을 하고 (b)같은 좌표로 전환하고자 할 경우 다음과 같이 할수 있습니다.

SetWindowOrgEx(hdc,200,200,NULL);

SetWindowOrg라는 함수는 로직 좌표계에서 초기 좌표를 이동하는 함수입니다. 전항목에서 좌표계를 디바이스 좌표계와 로직좌표계 두 개를 사용한다고 하였습니다. SetWindowOrg함수에 의해서 로직 좌표계의 좌표원점이 이동하였다하여도 디바이스 좌표계의 원점이 이동되는 것은 아닙니다. 다바이스 좌표계의 원점을 이동하고자 한다면 SetViewportOrgEx라는 함수를 사용합니다.

BOOL SetViewportOrgEx(

HDC hdc, // 디바이스 컨텍스 핸들

int X, // 이동 X좌표

int Y, // 이동 Y좌표

LPPOINT lpPoint // 이전 좌표

);

MM_LOMETRIC 로 맵핑모드를 설정하고 로직좌표와 디바이스좌표 중앙에 원점을 설정하고자 한다면 다음과 같은 형식이 될것입니다.

1)클라이언트 전체 영역을 얻습니다.

GetClientRect(hwnd,&rect)

2)rect좌표는 dc와 상관되는 좌표가 아닙니다. 이좌표는 디바이스 좌표입니다.

디바이스좌표를 이용하여 원점을 이동하는 함수가 SetViewportOrg이므로

SetViewportOrgEx(hdc,rect.right/2,rect.bottom/2,NULL);

3)이제 rect를 로직좌표로 전환을 하고 로직좌표계의 원점을 이동합니다.

DPtoLP(hdc,(PPOINT)&rect,2);

SetWindowOrgEx(hdc,rec,right/2,rect.bottom/2,NULL);

좌표를 리턴하는 함수들중에 디바이스 좌표만 리턴하는 경우의 함수들이 있습니다.

위의 예에서 보듯이 GetClientRect함수는 dc에서 얻는 함수가 아니기 때문에 디바이스 좌표를 리턴합니다. 이외에 GetDeviceCaps라는 함수는 DC를 이용하지만 로직좌표계가 아닌 디바이스좌표를 리턴합니다. 프로그래밍 할 경우 특정 맵핑모드를 선언하였을 경우 함수가 디바이스좌표를 리턴하는가? 또는 로직좌표를 리턴하는가에 대해서 주의를 해주어야 합니다.

좌표평면 정보를 수정할 경우 어떤 좌표평면을 수정할것인가를 알려주어야 하기 때문에 디바이스 좌표평면을 보통 Viewport라고하며 로직좌표 평면을 Window라고 합니다.

6.다양한 그래픽기법

지금까지는 일반적인 그래픽을 출력하는 방법에 대해서 설명하였습니다. 이밖에도 화면에 그래픽을 출력하는데 사용하는 다양한 함수가 있습니다. 그모든 함수들의 기본은 DC를 얻고 그래픽을 출력하기 위한 핸들을 얻고 설정하는 것으로 이루어졌습니다. 이번 항목에서는 화면에 그래픽을 출력하는데 필요한 필수적인 다양한 그래픽 기법을 설명합니다.

다각형 칠하기

다각형을 그리고 그안에 칠을 할경우가 있습니다. 이럴 경우 칠하는 모드를 교차되는 부분만칠하거나 또는 다각형 내부를 모두 칠하고자 할 경우가 있습니다.이럴 때  SetPolyFillMode 란 함수를 사용하여 다각형을 칠하는 방법을 설정할수 잇습니다. 이함수의 형은 다음과 같습니다.

int SetPolyFillMode(

HDC hdc, // 디바이스 컨텍스 핸들

int iPolyFillMode // 다각형 칠하기 모드

);

hdc는 디바이스 컨텍스 핸들이며 iPolyFilMode는 칠하고자 할 때 사용하는 플러그입니다. 이모드는 WINDING 모드와 ALTERNATE 모드입니다. WINDING모드란 다각형의 외곽선내부를 모두칠하는것이고 ALTERNATE 모드는 교차되는 부분을 칠하는 것입니다. 그림 15는 ALTERNATE모드로 출력한 별모양의 다각형그립니다.

(그림 15)ALTERNATE모드로 출력한 다각형 칠하기

그림 15에서 보듯이 교차되는 부분은 색이 칠해지나 교차되지 않은 안에의 8각형은 색이 칠해지지 않습니다. 다각형 내부에 모든 색을 칠하고자 할경우에는 WINDING 모드로 설정하시면 됩니다. 그림 16은 WINDING모드로 다각형을 출력한 형태입니다. SetPolyFillMode 모드는 다각형을 칠할 경우 모드를 설정할뿐이지 다각형을 그려주지는 않습니다. 이모드를 설정한후에 이모드에 의해서 다각형을 그리고자 할 경우 사용하는 함수가 Polygon입니다.

BOOL Polygon(

HDC hdc, // 디바이스 컨텍스트 핸들

CONST POINT *lpPoints, // 다각형 좌표

int nCount // 다각형 좌표수

);

(그림 16)WINDING 모드로 출력한 다각형 칠하기

lpPoints 란 다각형의 좌표배열 포인터를 의미하며 nCount 는 다각형의 꼭지점수를 의미합니다. 예를 들어서  그림 16과 같은 형태의 다각형을 그리고자 한다면 먼저 10개의 포인터 배열을 만든다음

POINT pgon[10]={96,68,275,75,268,242,88,233,96,68,68,148,

166,28,299,142,172,290,68,148};

SetPolyFillMode 함수를 이용하여 칠하기 모드를 설정한다음

SetPolyFillMode(hdc,ALTERNATE);

Polygon 함수를 이용하여 다각형을 출력합니다.

Polygon(hdc,pgon,10);

예제 PolyDraw는 좌측 마우스 버튼을 클릭할때마다 별모양의 다각형이 WINDING모드와 ALTERNATE 교차되는 예입니다.

(프로그램 소스)

//PolyDraw.c

//다각형 칠하기 예제

#include <windows.h>

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

PSTR szCmdLine, int iCmdShow)

{

static char szAppName[] = "PolyDraw" ;

HWND        hwnd ;

MSG         msg ;

WNDCLASSEX  wndclass ;

wndclass.cbSize        = sizeof (wndclass) ;

wndclass.style         = CS_HREDRAW | CS_VREDRAW ;

wndclass.lpfnWndProc   = WndProc ;

wndclass.cbClsExtra    = 0 ;

wndclass.cbWndExtra    = 0 ;

wndclass.hInstance     = hInstance ;

wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;

wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;

wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;

wndclass.lpszMenuName  = NULL ;

wndclass.lpszClassName = szAppName ;

wndclass.hIconSm       = LoadIcon (NULL, IDI_APPLICATION) ;

RegisterClassEx (&wndclass) ;

hwnd = CreateWindow (szAppName,

"다각형칠하기예제:PolyDraw",

WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

NULL,

NULL,

hInstance,

NULL) ;

ShowWindow (hwnd, iCmdShow) ;

UpdateWindow (hwnd) ;

while (GetMessage (&msg, NULL, 0, 0))

{

TranslateMessage (&msg) ;

DispatchMessage (&msg) ;

}

return msg.wParam ;

}

LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)

{

HDC         hdc ;

PAINTSTRUCT ps ;

HBRUSH hBrush;

//다각형을 칠할좌표

POINT pgon[10]={96,68,275,75,268,242,88,233,96,68,68,148,166,28,299,142,172,290,68,148};

static int style=0;

switch (iMsg)

{

case WM_CREATE :

return 0 ;

case WM_PAINT :

hdc = BeginPaint (hwnd, &ps) ;

hBrush=GetStockObject(BLACK_BRUSH);

SelectObject(hdc,hBrush);

//style가 1이면 WINDING모드로 설정

if(style)

SetPolyFillMode(hdc,WINDING);

else//그렇지 않으면 AlTERNAMTE 모드로 설정

SetPolyFillMode(hdc,ALTERNATE);

//다각형을 그린다.

Polygon(hdc,pgon,10);

EndPaint (hwnd, &ps) ;

return 0 ;

case WM_LBUTTONDOWN:

style=1-style;

InvalidateRgn(hwnd,NULL,TRUE);

break;

case WM_DESTROY :

PostQuitMessage (0) ;

return 0 ;

}

return DefWindowProc (hwnd, iMsg, wParam, lParam) ;

}

(프로그램 소스)

확장펜

확장펜이란 그림 17처럼 펜의 끝이 둥그렇고 교차점이 부드럽게 이어지는등 다양한 만들어진 펜을 말합니다. 일반 펜으로는 딱딱한 느낌을 주지만 확장펜을 사용하게 되면 매우 부드러운 느낌을 낼수 있습니다.

(그림 17) 확장펜을 이용한 출력결과

확장펜을 사용하기 위해서는 펜을 만들 때 ExtCreatePen 함수를 이용하여야 합니다.

HPEN ExtCreatePen(

DWORD dwPenStyle, // 펜스타일

DWORD dwWidth, // 펜 가로 굵기

CONST LOGBRUSH *lplb, //브러쉬의 속성이 들어있는 포인터

DWORD dwStyleCount, // 사용자 스타일 비트가 설정되어 있는 비트수

CONST DWORD *lpStyle // 사용자 스타일 비트 포인터

);

dwPenStyle는 펜의 스타일로써  다음과 같은 값을 가질수 있습니다.

PS_GEOMETRIC: 기하학 적인 형태

PS_COSMETIC: 장식하는 스타일

PS_ENDCAP_ROUND: 선끝이 둥근형태

PS_ENDCAP_SQUARE : 선끝이 네모각진 형태

PS_ENDCAP_FLAT : 일반적인 각진 형태

PS_JOIN_ROUND: 연결부위과 둥근 형태

위에서 설정한 PS계열외에 CreatePen 함수를 사용할 때 설정하는 플러그를 같이 사용할수 있습니다. PS_GEOMETRIC는 기하학 형태이며 이 옵션을 설정하면 dwWidth에 설정한 굵기대로 선이 그어지며 PS_COSMETIC일경우에는 사용사 스타일로 설정한 lpStyle에 의거하여 출력합니다. CreatePen함수와 다른 것은 펜을 설정할 때 브러시를 설정해주는 것입니다. 이때 사용하는 브러쉬는 LOGBRUSH입니다. LOGBRUSH는 구조체 형태는 다음과 같습니다.

typedef struct tagLOGBRUSH { // lb

UINT     lbStyle; //브러쉬 스타일

COLORREF lbColor; //브러쉬 색상

LONG     lbHatch; 빗금긋기 스타일

} LOGBRUSH;

로그 브러쉬는 일반 브러쉬와 달리 다양한 기능을 가지고 있습니다. 브러쉬에 패턴을 삽입할수 있으며 브러쉬에 빗금을 그는 다양한 스타일을 가지고 있습니다.

lbStyle는 일반 스타일과 함께 다음과 같은 확장스타일이 있습니다.

BS_HATCHED : 빗금 긋기형태의 브러쉬

BS_HOLLOW : 안에가 비워있는 스타일

BS_PATTERN : 패턴 스타일

BS_DIBPATTERN : DIB 패턴 스타일

lbColor은 브러쉬의 색상이며 lbHatch는 브러쉬에 빗금을 긋는 스타일입니다. 만일 BS_SOLID로 설정하였을경우에는 lbHatch가 무시됩니다. 이 lbHatch는 lbStyle에 BS_HATCHED가 설정되었을경우에만 유효한것입니다. 다음은 lbHatch의 값들입니다.

HS_BDIAGONAL : 좌측에서 우측으로 45도 빗금

HS_CROSS  : 십자가 형태

HS_DIAGCROSS : 45도로 비스듬한 십자가 형태

HS_FDIAGONAL : 우측에서 좌측으로 45도 빗금

HS_HORIZONTAL  : 수평 빗금

HS_VERTICAL : 수직 빗금

ExtCreatePen 함수를 사용하여 확장펜을 만드는 방법에 대해서 설명을 하겠습니다. 먼저 확장펜에 설정할 브러쉬를 만듭니다.

LOGBRUSH lbrush;

lbrush.lbStyle=BS_SOLID;

lbrush.lbColor=RGB(0,0,255);

설정된 브러쉬는 BS_SOLID즉 직선 형태의 브러쉬입니다. 이 브러쉬가 등록되었을 때 ExtCreatePen의 style도 이 브러쉬와 맞게 해주어야 합니다. 직선형태의 브러쉬를 가지고 끝에게 둥글고 그리고 연결부위가 둥근 확장 펜을 만든다면 다음과 같습니다.

pen=ExtCreatePen(PS_SOLID|PS_ENDCAP_ROUND|PS_GEOMETRIC|PS_JOIN_R	OUND,20,&lbrush,0,NULL);

펜을 설정하는 것은 일반펜을 설정하는 방법과 같이 SelectObject를 이용하며 펜을 다쓴경우에 해제할경우에는 DeleteObject함수를 이용합니다.

ExtendPen 예제는 확장펜을 사용하여 Z자를 그리는 예입니다. 이 프로그램의 출력결과가 그림 17입니다.

(프로그램 소스)

//확장펜 예제

//ExtendPen.c

#include <windows.h>

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

PSTR szCmdLine, int iCmdShow)

{

static char szAppName[] = "ExtendPen" ;

HWND        hwnd ;

MSG         msg ;

WNDCLASSEX  wndclass ;

wndclass.cbSize        = sizeof (wndclass) ;

wndclass.style         = CS_HREDRAW | CS_VREDRAW ;

wndclass.lpfnWndProc   = WndProc ;

wndclass.cbClsExtra    = 0 ;

wndclass.cbWndExtra    = 0 ;

wndclass.hInstance     = hInstance ;

wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;

wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;

wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;

wndclass.lpszMenuName  = NULL ;

wndclass.lpszClassName = szAppName ;

wndclass.hIconSm       = LoadIcon (NULL, IDI_APPLICATION) ;

RegisterClassEx (&wndclass) ;

hwnd = CreateWindow (szAppName,

"확장펜 예제:ExtendPen",

WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

NULL,

NULL,

hInstance,

NULL) ;

ShowWindow (hwnd, iCmdShow) ;

UpdateWindow (hwnd) ;

while (GetMessage (&msg, NULL, 0, 0))

{

TranslateMessage (&msg) ;

DispatchMessage (&msg) ;

}

return msg.wParam ;

}

LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)

{

HDC         hdc ;

PAINTSTRUCT ps ;

int i;

int point[8]={97,41,324,47,94,323,315,326};

HPEN pen,oldpen;

LOGBRUSH lbrush;

switch (iMsg)

{

case WM_CREATE :

return 0 ;

case WM_PAINT :

hdc = BeginPaint (hwnd, &ps) ;

//로그 브러쉬 설정

lbrush.lbStyle=BS_SOLID;

lbrush.lbColor=RGB(0,0,255);

//확장 펜을 만든다.

pen=ExtCreatePen(PS_SOLID|PS_ENDCAP_ROUND|PS_GEOMETRIC|PS_JOIN_ROUND,

20,&lbrush,0,NULL);

oldpen=SelectObject(hdc,pen);

//Z자를 출력한다.

for(i=0;i<6;i+=2)

{

MoveToEx(hdc,point[i],point[i+1],NULL);

LineTo(hdc,point[i+2],point[i+3]);

}

SelectObject(hdc,oldpen);

DeleteObject(pen);

EndPaint (hwnd, &ps) ;

return 0 ;

case WM_DESTROY :

PostQuitMessage (0) ;

return 0 ;

}

return DefWindowProc (hwnd, iMsg, wParam, lParam) ;

}

(프로그램 소스끝)

패스(Path)

패스란 미리 선을 그려놓고 출력을 결정하여 후에 출력하는 기법을 이야기 합니다. 복잡한 라인들을 그릴 경우 화면에 하나하나 계속적으로 그려가면 속도 또한 떨어지면 답답한 느낌을 줄수 있습니다. 예를 들어서 포인터가 3000개인 라인을 그리거나 또는 이 다각형을 출력한다고 가정할 경우 한번 하면을 출력할 때 마다 그리는 시간 때문에 사용자는 지루하게 느낍니다. 패스를 사용하면 다소 출력시간을 줄일수 있으며 또한 그린 형태의 출력 형태를 결정할수 있습니다. 패스를 사용하여 그래픽을 출력하는 방법은 다음과 같습니다.

BeginPath(hdc); //패스 시작

: 선을 그리거나 다각형을 그린다.

EndPath(hdc)// 패스 종료

: //그려놓은 형태의 출력을 결정한다.

BeginPath와 EndPath 만 사용하면 이사이에 출력한 선들은 실제 화면에 출력되지 않습니다. 내부에 좌표로만 설정되기 때문입니다. 이후에 설정한 패스를 출려하는 것을 결정할 때 이때 도형들이 그려집니다. 설정한 패스를 결정하는 함수는 StrokePath,FillPath,StrokeAndFillPath입니다. StrockePath는 외곽선만 그리는 패스함수이며 FillPath는 설정한 점사이에 색을 칠하는 함수입니다. 외곽선과 색을 함께 사용할경우에는 StrockeAndFillPath입니다.

위의 예에서 외곽선만 출력하고자 한다면 EndPath다음에 StrockePath함수를 다음과 같이 해줍니다.

StrokePath(hdc);//외곽선만 출력

PathEx는 패스를 상용하여 점들을 외곽선, 칠하기, 외곽선을 귿고 칠하기 형태로 출력하는 예제입니다. 본예제는 좌측 마우스 버튼을 클릭할 때 마다 3가지 방법이 교차됩니다.

(프로그램 소스)

//패스 예제

//PathEx.c

#include <windows.h>

void main();

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

PSTR szCmdLine, int iCmdShow)

{

static char szAppName[] = "PathEx" ;

HWND        hwnd ;

MSG         msg ;

WNDCLASSEX  wndclass ;

wndclass.cbSize        = sizeof (wndclass) ;

wndclass.style         = CS_HREDRAW | CS_VREDRAW ;

wndclass.lpfnWndProc   = WndProc ;

wndclass.cbClsExtra    = 0 ;

wndclass.cbWndExtra    = 0 ;

wndclass.hInstance     = hInstance ;

wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;

wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;

wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;

wndclass.lpszMenuName  = NULL ;

wndclass.lpszClassName = szAppName ;

wndclass.hIconSm       = LoadIcon (NULL, IDI_APPLICATION) ;

RegisterClassEx (&wndclass) ;

hwnd = CreateWindow (szAppName,

"Path 예제 :PathEx",

WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

NULL,

NULL,

hInstance,

NULL) ;

ShowWindow (hwnd, iCmdShow) ;

UpdateWindow (hwnd) ;

while (GetMessage (&msg, NULL, 0, 0))

{

TranslateMessage (&msg) ;

DispatchMessage (&msg) ;

}

return msg.wParam ;

}

LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM

wParam, LPARAM lParam)

{

HDC         hdc ;

PAINTSTRUCT ps ;

int pointx[10]={96,275,268,88,96,68,166,299,172,68};

int pointy[10]={68,75,242,233,68,148,28,142,290,148};

int i;

static int flag=0;

HBRUSH hBrush,oldBrush;

HPEN hPen,oldPen;

switch (iMsg)

{

case WM_CREATE :

return 0 ;

case WM_PAINT :

hdc = BeginPaint (hwnd, &ps) ;

BeginPath(hdc);//패스 시작

MoveToEx(hdc,pointx[0],pointy[0],NULL);

for(i=1;i<5;i++)

{

LineTo(hdc,pointx[i],pointy[i]);

}

MoveToEx(hdc,pointx[5],pointy[5],NULL);

for(i=6;i<10;i++)

{

LineTo(hdc,pointx[i],pointy[i]);

}

EndPath(hdc);//패스 끝

hBrush=GetStockObject(BLACK_BRUSH);

oldBrush=SelectObject(hdc,hBrush);

hPen=CreatePen(PS_SOLID,2,RGB(0,0,255));

oldPen=SelectObject(hdc,hPen);

switch(flag)

{

case 0:

//외곽선 형태로 출력

StrokePath(hdc);

break;

case 1://칠하기 형태로 출력

FillPath(hdc);

break;

case 2://외곽선과 칠하기로 출력

StrokeAndFillPath(hdc);

break;

}

SelectObject(hdc,oldBrush);

SelectObject(hdc,oldPen);

DeleteObject(hPen);

EndPaint (hwnd, &ps) ;

return 0 ;

case WM_LBUTTONDOWN:

flag++;

if(flag==3)

flag=0;

InvalidateRgn(hwnd,NULL,TRUE);

break;

case WM_DESTROY :

PostQuitMessage (0) ;

return 0 ;

}

return DefWindowProc (hwnd, iMsg, wParam, lParam) ;

}

(프로그램 소스끝)

(그림 18)PathEx출력 결과

래스터 연산 (ROP)

래스터 연산이란 현재 설정된 화면의 그림 형태와 새로 출력할 그림이 연산에 의해서 출력되는 것을 의미합니다. 예를 (0,0)과 (0,100) 사이에 100값의 색으로 라인이 그려졌을 때 다시 이위에 200의 값의 라인을 그리게 될 때 이전의 색을 무시하고 라인을 출력하는 것이 아니라 특정 연산에 의해서 출력하는 것을 말합니다.

이 래스터 연산에 사용되는 함수는 SetRop2이며 이함수에 설정한 연산코드는 표5와 같습니다.

연산 코드	부울함수	내용
R2_BLACK	0	모든 값을 0으로 처리
R2_NOTMERGEPEN	~(Src | Des)	배경과 설정할 값을 OR연산을 하고 난후에 그값을 Inverse한다.
R2_MASKNOPEN	~Src & Des	설정값을 Inverse 한다음 배경과 AND 연산을 한다.0
R2_NOTCOPYPEN	~Src	설정값을 Inverse하여 출력한다.
R2_MASKPENNOT	Src & ~Des	배경값을 Inverse 한다음 설정값과 AND연산을 한다.
R2_NOT	~Des	배경을 Inverse한다
R2_XORPEN	Src ^ Des	배경과 설정값을 XOR연산을 하여 출력한다.
R2_NOTMASKPEN	~(Src & Des)	배경과 설정값을 AND연산을 한다음 이것을 Inverse한다.
R2_NOP	Des	배경값을 그대로 출력
R2_MERGENOPTPEN	~Src | Des	설정값을 Inverse한다음 배경값과 OR연산을 한다.
R2_COPYPEN	Src	소스를 그래로 출력한다
R2_MERGEPENNOT	Src | ~Des	배경값을 Inverse한다음 설정값과 OR연산을 한다.
R2_MERGEPEN	Src | Des	설정값과 배경값을 OR연산을 한다.
R2_WHITE	1	배경에 흰색을 출력한다.

예를 들어서 100의 값이 설정되어 있는 배경에 200의 값을 R2_MERGENOPTPEN으로 한다면 다음과 같습니다.

11001000 //200의 값

00110111 //200의 값의 Inverse값

01100100 //100의값

------------------ OR연산

01110111 //119값

200의 2진수인 11001000이 Src이므로 이값을 Inverse합니다.  Inverse란 0은 1로 1은 0으로 전환을 하는 것을 의미합니다. 이값에 100의 2진수 01100100을  OR연산을하면 1110111즉 119의 값이 나오므로 119의 색이 화면에 출력이 됩니다.

이런 래스터 연산이 필요한 이유는 여러 가지가 있습니다. 배경에 글자가 출력될때 배경과 교차되어 배경위에 글자가 다양하게 출력하고자 할경우에 이 연산을 이용합니다. 또하나 예를 든다면 그래픽 에디터에서 박스상태의 영역을 설정할 때 박스가 커지고 작아질 경우 화면이 깜빡임이 없이 박스가 출력이 되는 것을 보았을것입니다. 이것은 R2_XORPEN 으로 설정하였기 때문입니다. XOR연산은 한번 출력을 하면 배경과 교차된 색으로 출력되었다가 다시 출력하면 출력한 것은 사라지고 이전의 배경이 나오는 형태입니다.

SetRop2함수를 이용하여 R2_XORPEN을 사용한 연산을 하고 라인을 그리고자 한다면 다음과 같이 할수 있습니다.

SetRop2(hdc,R2_XORPEN);

: 그래픽을 출력한다.

SetRop2는 펜을 이용하여 라인을 그릴때만 활성화 됩니다. 내부를 칠하는 박스같은 즉 브러쉬를 사용하는 경우에는 SetRop2가 무시됩니다.

RopEx는 가로 라인과 세로라인을 다양한 래스터 연산을 이용하여 출력하는 예제입니다. 이예제 또한 마우스 버튼을 클릭할 때 마다 출력이 바뀌도록 하였습니다.

(프로그램 소스)

//Rop예제

//RopEx.c

#include <windows.h>

void main();

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

PSTR szCmdLine, int iCmdShow)

{

static char szAppName[] = "RopEx" ;

HWND        hwnd ;

MSG         msg ;

WNDCLASSEX  wndclass ;

wndclass.cbSize        = sizeof (wndclass) ;

wndclass.style         = CS_HREDRAW | CS_VREDRAW ;

wndclass.lpfnWndProc   = WndProc ;

wndclass.cbClsExtra    = 0 ;

wndclass.cbWndExtra    = 0 ;

wndclass.hInstance     = hInstance ;

wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;

wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;

wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;

wndclass.lpszMenuName  = NULL ;

wndclass.lpszClassName = szAppName ;

wndclass.hIconSm       = LoadIcon (NULL, IDI_APPLICATION) ;

RegisterClassEx (&wndclass) ;

hwnd = CreateWindow (szAppName,

"ROP예제:RopEx",

WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

NULL,

NULL,

hInstance,

NULL) ;

ShowWindow (hwnd, iCmdShow) ;

UpdateWindow (hwnd) ;

while (GetMessage (&msg, NULL, 0, 0))

{

TranslateMessage (&msg) ;

DispatchMessage (&msg) ;

}

return msg.wParam ;

}

LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)

{

HDC         hdc ;

PAINTSTRUCT ps ;

int i;

HPEN hPen,oldPen;

static int flag=0;

char temp[80];

switch (iMsg)

{

case WM_CREATE :

return 0 ;

case WM_PAINT :

hdc = BeginPaint (hwnd, &ps) ;

hPen=CreatePen(PS_SOLID,2,RGB(0,0,255));

oldPen=SelectObject(hdc,hPen);

for(i=0;i<100;i+=10)

{

MoveToEx(hdc,i,10,NULL);

LineTo(hdc,i,300);

}

for(i=0;i<100;i+=10)

{

MoveToEx(hdc,10,i,NULL);

LineTo(hdc,300,i);

}

switch(flag)

{

case 1:

TextOut(hdc,100,100,temp,wsprintf(temp,"R2_NOT"));

SetROP2(hdc,R2_NOT);

break;

case 2:

TextOut(hdc,100,100,temp,wsprintf(temp,"R2_XORPEN"));

SetROP2(hdc,R2_XORPEN);

break;

case 3:

TextOut(hdc,100,100,temp,wsprintf(temp,"R2_MERGENOTPEN"));

SetROP2(hdc,R2_MERGENOTPEN);

break;

case 4:

TextOut(hdc,100,100,temp,wsprintf(temp,"R2_MASKPENNOT"));

SetROP2(hdc,R2_MASKPENNOT);

break;

case 5:

TextOut(hdc,100,100,temp,wsprintf(temp,"R2_MASKNOTPEN"));

SetROP2(hdc,R2_MASKNOTPEN);

break;

}

for(i=0;i<100;i+=10)

{

MoveToEx(hdc,10,i,NULL);

LineTo(hdc,300,i);

}

SelectObject(hdc,oldPen);

DeleteObject(hPen);

EndPaint (hwnd, &ps) ;

return 0 ;

case WM_LBUTTONDOWN:

flag++;

if(flag==6)

flag=0;

InvalidateRgn(hwnd,NULL,TRUE);

break;

case WM_DESTROY :

PostQuitMessage (0) ;

return 0 ;

}

return DefWindowProc (hwnd, iMsg, wParam, lParam) ;

}

(프로그램 소스끝)

(그림 19)RopEx출력 결과

(프로그램 팁)

박스좌표 만들기

지금까지 그래픽을 할 때 자주 사용하였던 것이 RECT입니다. 이 RECT는 구조체로 그안에 reft,right,bottom,top의 맴버를 가지고 있습니다. 이 맴버들을 변화시킬 때 사용하는 박스좌표 전환 함수들이 있습니다. 다음은 이함수들의 리스트입니다.

RECT구조체에 좌표값을 설정한다.

BOOL SetRect(

LPRECT lprc, // 박스 좌표 포인터

int xLeft, // left값

int yTop, // top 값

int xRight, // right 값

int yBottom // bottom 값

);

ex)

RECT rect;

SetRect(&rect,0,0,100,100);//rect에 0,0,100,100의 값을 대입한다.

박스좌표를 복사한다.

BOOL CopyRect(

LPRECT lprcDst, // 복사할 좌표 포인터

CONST RECT *lprcSrc // 복사할 좌표

);

ex)

RECT rect1,rect2;

SetRect(&rect1,0,0,100,100);//rect에 0,0,100,100의 값을 대입한다.

CopyRect(&rect2,&rect1);

두개의 박스좌표에 교차된 좌표를 얻는다.

BOOL IntersectRect(

LPRECT lprcDst, // 교차된 영역좌표

CONST RECT *lprcSrc1, // 박스좌표 1

CONST RECT *lprcSrc2 // 박스좌표 2

);

두 개의 박스좌표를 합친 영역을 구한다.

BOOL UnionRect(

LPRECT lprcDst, //합친 영역좌표

CONST RECT *lprcSrc1, // 박스좌표 1

CONST RECT *lprcSrc2 // 박스 좌표 2

);

설정된 좌표를 dx와 dy만큼 이동시킨좌표로 설정한다.

BOOL OffsetRect(

LPRECT lprc, // poin

int dx, // horizontal offset

int dy // vertical offset

);

설정된 좌표를 dx와 dy만큼 줄이거나 늘린다.

BOOL InflateRect(

LPRECT lprc, // address of rectangle

int dx, // amount to increase or decrease width

int dy // amount to increase or decrease height

);