티스토리 뷰

gdi 엔진



가장 중요한 점은, 인쇄 기능이 있다는 것이지요.첨부의 파일은 SumatraPDF의 인쇄 기능을 MuPDF에 붙여서 MuPDF에 인쇄 기능을 추가한 소스입니다.

  컴파일 ※ mupdf 1.3 정식 릴리즈 버전에 맞춰져 있으므로 다른 버전 이용자는 적용이 불가능합니다.

 1) 첨부 파일을 다운받은 후 압축을 풉니다.

 2) 'colorspace.h, device.h, path.h, pixmap.h, system.h' 파일을 MuPDF의 'include/mupdf/fitz' 디렉토리에 덮어쓰기로 넣습니다.

 3) 'draw-path.c, draw-unpack.c, gdiplus-device.cpp, image.c, load-png.c, path.c, pixmap.c, svg-device.c, trace-device.c' 파일을 MuPDF의 'source/fitz' 디렉토리에 덮어쓰기로 넣습니다.

 4) 'pdf-device.c' 파일을 MuPDF의 'source/pdf' 디렉토리에 덮어쓰기로 넣습니다.

 5) VS를 실행한 후 'libmupdf 속성 페이지 > C/C++ > 전처리기' 메뉴로 이동한 후 '전처리기 정의' 항목에 '_GDIPLUS_'를 합니다.

(홀따옴표는 하는거 아닙니다;;) 6) 컴파일 하면 인쇄 기능이 추가됩니다.

  인쇄 기능 사용 방법 요 부분은 다음 글에서 예제와 함께 설명해 드리도록 하겠습니다.

  기타 일부 한글 PDF 파일 인쇄 시 프로그램이 죽어 버리는 현상이 발견되었습니다.

SumatraPDF에서도 동일 현상이 발견되어 GDI+ 인쇄 소스에 약간의 패치가 가해진 상태이므로 SumatraPDF의 인쇄 소스와 내용이 다릅니다.

  GDI+ 소개GDI(Graphic User Interface)는 윈도우즈의 핵심 모듈 중의 하나로 출력과 관련된 기능을 담당한다.

화면, 프린터 등의 출력 하드웨어와 응용 프로그램의 중간에 위치하여 장치 독립성을 확보하며 복수 개의 프로그램이 서로의 영역내에서 방해하지 않고 자유롭게 출력하도록 조율하는 것이 주된 기능이다.

응용 프로그램은 하드웨어를 직접 액세스할 수 없으며 반드시 GDI를 통해야만 한다.

이 강좌를 읽을 정도면 Win32의 기본적인 구조에 대해서는 잘 알고 있을 것이므로 GDI에 대해서는 이미 익숙할 것이다.

만약 그렇지 않다면 별도의 강좌를 통해 Win32의 기본에 대해 먼저 공부할 필요가 있다.

GDI를 모른 상태에서 그 상위 버전인 GDI+를 공부할 수는 없다.

GDI는 그래픽 환경의 출력 엔진으로써 오랫동안 충실히 제 역할을 해 왔지만 현대적인 프로그램의 복잡한 요구를 충족시키기에는 다소 부족한 점이 많다.

 ① 기본적인 그래픽 출력은 가능하지만 섬세한 그래픽 표현에는 다소 역부족이다.

예를 들어 점선 펜을 만들 수는 있지만 굵기가 2이상이면 무조건 실선이 되어 버리므로 굵은 점선을 그을 수 없고 NT 이하에서는 비트맵 브러시도 8*8 이상의 크기를 지원하지 않아 큰 비트맵 브러시를 쓸 수 없다.

② 그래픽 속성을 바꿀 때마다 GDI 오브젝트를 일일이 생성, 선택한 후 사용해야 하므로 무척 번거롭다.

빨간색 테두리에 파란색 면을 가지는 타원을 하나 그리려면 Ellipse를 호출하기 전에 펜과 브러시를 미리 생성하여 DC에 선택해야 하며 그린 후에도 선택 해제, 파괴의 뒷처리를 반드시 해야 한다.

정작 중요한 출력문보다 준비하고 정리하는 코드가 더 많아 무척 불편하다.

③ 실수로 GDI 오브젝트를 해제하지 않을 경우 리소스 누출에 의해 시스템의 안정성을 위협하기도 한다.

비트맵같은 큰 개체를 해제하지 않으면 많은 리소스를 소모하여 더 이상 그리기를 할 수 없는 상태가 되기도 하는데 이는 시스템 다운에 버금갈 정도로 치명적이다.

 GDI의 단점을 요약하자면 기능은 부족하고 쓰기도 불편하며 안전하지도 않다는 것이다.

GDI를 최초 디자인했던 20년 전에는 하드웨어 성능이 좋지 못해 화려한 기능보다는 단순하고 빠른 동작이 더 중요했었다.

그러나 지금은 하드웨어 환경이 개선되고 사용자의 요구가 늘어남에 따라 좀 더 강력한 출력 엔진이 필요해졌다.

GDI+는 전통적인 GDI 모듈의 업그레이드 버전으로서 복잡하고 섬세한 그래픽을 출력할 수 있고 기존 기능을 최적화한 새로운 출력 모듈이다.

GDI의 계승자이므로 장치 독립성을 제공한다는 기본 목적은 동일하며 GDI로 할 수 있는 대부분의 작업을 GDI+로도 할 수 있다.

윈도우즈 XP와 비스타에 기본적으로 탑재되어 있으며 닷넷 플랫폼에도 포함되어 있고 64비트 윈도우즈에서도 계속 지원되므로 향후 GDI를 완전히 대체하게 될 것이다.

2000을 포함하여 2000 이하의 버전에서는 별도의 모듈을 배포해야만 사용할 수 있지만 GdiPlus.dll 파일 하나만 복사하면 되므로 하위 호환성의 문제도 거의 없는 셈이다.

95/98 이후의 NT/2000에서 GDI도 투명 비트맵 출력, 좌표 변환, 반투명 출력 등 많은 기능 개선이 이루어졌지만 이런 기능은 사실 있어도 마음대로 사용할 수 없었다.

왜냐하면 추가된 함수를 하나라도 사용하게 되면 95/98에서 이 프로그램은 제대로 실행되지 않기 때문이다.

이렇게 만든 프로그램을 95/98 사용자가 쓸 수 있는 유일한 방법은 운영체제를 업그레이드하는 것 뿐이므로 시장을 포기하지 않는 한 이런 함수를 쓸 수 없었다.

그러나 GDI+는 DLL을 같이 복사하면 하위 버전의 운영체제에서도 문제없이 잘 실행되므로 호환성을 걱정할 필요가 없다.

95/98 환경이라도 DLL 파일을 같이 배포하기만 하면 되므로 용량이 약간 늘어난다는 것 외에는 별다른 번거로움이 없는 것이다.

DLL의 버전 충돌을 피하기 위해서 DLL을 재배포할 때는 가급적이면 시스템 디렉토리보다 응용 프로그램이 설치되는 디렉토리에 같이 복사하는 것이 안전하며 마이크로소프트는 이런 배포 방식을 권장하고 있다.

GDI+를 사용하기 위해 특별한 준비를 할 필요는 없다.

비주얼 스튜디오 2005 이상의 버전에는 GDI+ 개발에 필요한 SDK와 상세한 도움말이 제공되므로 컴파일러만 설치하면 GDI+를 바로 사용할 수 있다.

XP 이후에 GDI+가 기본 출력 엔진 역할을 하므로 컴파일러가 SDK를 제공하는 것이 당연하다.

이 강좌는 비스타 환경에서 비주얼 스튜디오 2008을 기준으로 하므로 이 환경을 갖추었다면 아무 것도 준비할 필요가 없다.

그러나 이전 버전에서는 약간의 준비를 해야 한다.

현재 2001이나 2003은 사용자가 거의 없고 VC 6.0 사용자가 일부 남아 있는데 VC 6.0 사용자는 별도로 SDK를 설치해야 한다.

VC 60용으로 발표된 가장 최신 SDK인 2003년 2월 플랫폼 SDK를 다운로드 받아 설치하고 컴파일러의 디렉토리 옵션창에서 Include 경로와 Library 경로를 추가하면 된다.

최신 SDK 정보를 가장 먼저 참조하도록 목록의 제일 위쪽으로 이동시켜야 한다.

최신 MSDN에는 GDI+에 대한 문서가 포함되어 있다.

친절한 자습서는 물론이고 컴파일해 볼만한 예제와 각 클래스의 멤버에 대한 정보, 함수에 대한 도움말, 고급 기법에 대한 문서 등이 수록되어 있으므로 이 도움말만 순서대로 읽어 봐도 GDI+는 쉽게 정복할 수 있다.

물론 영어로 되어 있다.

플랫폼 SDK와 함께 설치되는 도움말에도 GDI+에 대한 도움말이 제공된다.

GDI+는 최신 라이브러리인만큼 객체 지향적인 C++언어로 작성되어 있다.

그래서 C 컴파일러에서는 이 라이브러리를 사용할 수 없다는 문제점이 있기는 하지만 요즘의 컴파일러들은 대부분 C++언어를 잘 지원하므로 개발툴의 제약도 거의 없는 셈이다.

GDI+는 GDI의 기능을 개선한 클래스 라이브러리이므로 일단 GDI에 대해서 잘 알고 있어야 하며 C++ 언어의 기본적인 사용 방법에 대해서도 충분히 숙지하고 있어야 한다.

GDI+는 기초적인 C++ 문법만을 요구할 뿐이며 해박한 객체 지향 이론을 요구하는 것은 아니다.

상속이나 가상 함수, 템플리트 같은 고급 기법까지는 모르더라도 객체안에 속성과 함수가 캡슐화되어 있다는 것과 객체가 생성 파괴될 때 자동으로 호출되는 함수가 있다는 것 정도만 알아도 GDI+를 배우고 사용할 수 있다.

GDI+를 공부해 보면 왜 C++이 좋은가를 실감할 수 있을 것이다.

나.초기화

어떤 라이브러리든지 사용하려면 약간의 초기화 과정을 거쳐야 한다.

몇 가지 함수만 시기 적절하게 호출하면 되므로 아주 간단하다.

예제 수준에서는 복잡한 UI나 특별한 기능이 필요치 않으므로 Win32 프로젝트로 첫 예제를 만들어 보자. 비주얼 스튜디오 2008을 실행하고 새 프로젝트 명령으로 프로젝트를 생성한다.

예제 작성용으로 GpExam2008이라는 이름의 디렉토리를 미리 생성해 놓고 이 디렉토리안에 GdiPlusStart라는 이름으로 새 프로젝트를 만들었다.

빈 프로젝트 옵션을 선택하고 GdiPlusStart.cpp 파일을 추가한 후 다음 소스를 작성한다.

  예 제 : GdiPlusStart#include <windows.h> LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);HINSTANCE g_hInst;HWND hWndMain;LPCTSTR lpszClass=TEXT("GdiPlusStart"); #include <gdiplus.h>using namespace Gdiplus;#pragma comment(lib, "gdiplus") int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance       ,LPSTR lpszCmdParam,int nCmdShow){     HWND hWnd;     MSG Message;     WNDCLASS WndClass;     g_hInst=hInstance;      ULONG_PTR gpToken;     GdiplusStartupInput gpsi;     if (GdiplusStartup(&gpToken,&gpsi,NULL) != Ok) {          MessageBox(NULL,TEXT("GDI+ 라이브러리를 초기화할 수 없습니다.

"),              TEXT("알림"),MB_OK);          return 0;     }      WndClass.cbClsExtra=0;     WndClass.cbWndExtra=0;     WndClass.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);     WndClass.hCursor=LoadCursor(NULL,IDC_ARROW);     WndClass.hIcon=LoadIcon(NULL,IDI_APPLICATION);     WndClass.hInstance=hInstance;     WndClass.lpfnWndProc=WndProc;     WndClass.lpszClassName=lpszClass;     WndClass.lpszMenuName=NULL;     WndClass.style=CS_HREDRAW | CS_VREDRAW;     RegisterClass(&WndClass);      hWnd=CreateWindow(lpszClass,lpszClass,WS_OVERLAPPEDWINDOW,          CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,          NULL,(HMENU)NULL,hInstance,NULL);     ShowWindow(hWnd,nCmdShow);      while (GetMessage(&Message,NULL,0,0)) {          TranslateMessage(&Message);          DispatchMessage(&Message);     }     GdiplusShutdown(gpToken);     return (int)Message.wParam;} void OnPaint(HDC hdc){     Graphics G(hdc);     Pen P(Color(255,0,0,255),5);      G.DrawEllipse(&P,10,10,300,200);} LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam){     HDC hdc;     PAINTSTRUCT ps;      switch (iMessage) {     case WM_CREATE:          hWndMain=hWnd;          return 0;     case WM_PAINT:          hdc=BeginPaint(hWnd, &ps);          OnPaint(hdc);          EndPaint(hWnd, &ps);          return 0;     case WM_DESTROY:          PostQuitMessage(0);          return 0;     }     return(DefWindowProc(hWnd,iMessage,wParam,lParam));} 첫 번째 예제들이 늘상 그렇듯이 특별한 동작은 없고 굵기 5의 파란색 펜으로 타원만 하나 그려 보았다.

실행 결과는 다음과 같다.

전형적인 Win32 시작 소스에 GDI+와 관련된 추가 코드가 조금 더 작성되어 있다.

 ① gdiplus.h 헤더 파일을 인클루드하고 있는데 이 헤더 파일에 GDI+의 모든 타입, 열거형, 클래스 등이 선언되어 있다.

이 헤더 파일을 열어 보면 GDI+의 기본적인 선언문들을 직접 구경할 수 있는데 gdiplus.h는 GDI+의 모든 헤더를 포함하는 헤더 파일 컨테이너라고 할 수 있다.

② GDI+의 모든 명칭은 GdiPlus 네임 스페이스에 포함되어 있다.

따라서 클래스 이름과 열거형 이름앞에 GdiPlus::을 일일이 붙여야 하는데 너무 번거로우므로 using 지시자를 사용하여 GdiPlus 네임 스페이스에 선언된 모든 명칭을 전역 영역으로 가져 오도록 한다.

이 지시자에 의해 컴파일러는 전역 네임 영역에 없는 명칭에 대해 GdiPlus 네임 스페이스를 검색하므로 이름만으로 GDI+의 명칭들을 사용할 수 있다.

③ GDI+의 실제 코드는 gdiplus.dll에 정의되어 있으므로 이 라이브러리를 링크해야 한다.

원칙적으로는 프로젝트 설정 대화상자의 링크/ 페이지의 추가 종속성란에 gdiplus.lib 임포트 라이브러리를 지정해야 하나 매 실습마다 대화상자를 열기 귀찮으므로 소스에서 링크하는 것이 편리하다.

 #pragma comment(lib, "gdiplus") 라이브러리 모듈에게 gdiplus.lib 임포트 라이브러리를 참조하라고 알려 준다.

이렇게 선언문을 작성해 놓으면 새 소스를 만들 때도 붙여 넣기만 하면 되므로 실습하기 편리하다.

④ 프로그램 시작시 GDI+ 라이브러리를 초기화한다.

이때는 다음 전역 함수를 사용한다.

GDI+는 모든 것이 클래스로 되어 있지만 초기화와 관련된 함수는 클래스에 소속되지 않으며 일반 함수로 제공된다.

객체를 아직 만들기 전이므로 초기화 함수는 일반 함수 또는 정적 함수일 수밖에 없다.

 Status GdiplusStartup(ULONG_PTR *token, const GdiplusStartupInput *input, GdiplusStartupOutput *output); token은 초기화시 받는 토큰이며 종료할 때 이 토큰을 GdiplusShutdown으로 전달해야 한다.

input은 옵션 구조체이며 이 안에 GDI+의 버전, 디버그 콜백 함수, 백그라운드 스레드 허용 여부 등을 지정할 수 있는데 별 다른 옵션을 지정하지 않을 경우는 변수만 선언해서 그 포인터를 넘겨 주면 된다.

예제에서는 gpsi 구조체를 선언한 후 넘기기만 했다.

도움말에서 확인해 보면 알겠지만 이 구조체의 생성자가 무난한 디폴트로 초기화하도록 되어 있으므로 지역 변수라 하더라도 쓰레기값이 들어가지는 않는다.

생략할 수 없으므로 반드시 변수를 선언한 후 그 포인터를 전달해야 한다.

output은 초기화 결과를 돌려 주기 위한 구조체인데 필요없을 경우 NULL로 줄 수 있다.

백그라운드 스레드를 허용하지 않을 때만 output 인수가 필요하다.

이 함수는 초기화 결과를 Status형의 열거형으로 리턴하는데 성공했을 경우 Ok가 리턴된다.

Ok가 아닌 경우는 GDI+를 사용할 수 없으므로 적절한 에러 처리를 할 필요가 있다.

예제에서는 메시지 박스를 출력하고 프로그램을 종료했다.

⑤ 프로그램을 끝낼 때 다음 함수를 호출하여 GDI+ 라이브러리를 셧다운한다.

GDI+는 실행중에 많은 리소스를 할당하는데 이 리소스를 반드시 해제해야 한다.

 void GdiplusShutdown(ULONG_PTR token); 스타트업할 때 발급받았던 토큰을 그대로 전달하되 그 전에 사용중인 모든 GDI+ 객체들을 닫아야 한다.

이 두 함수는 보통 쌍으로 사용되며 프로그램 시작시, 종료시에 각각 호출한다.

메인 윈도우의 WM_CREATE, WM_DESTROY에서 처리할 수도 있지만 응용 프로그램 전역적인 초기화이므로 WinMain에서 처리하는 것이 더 적당하다.

WndProc에는 별다른 코드가 없으며 WM_PAINT 메시지를 받았을 때 OnPaint 함수만 호출한다.

OnPaint에서는 파란색 펜으로 굵기 5의 타원을 그렸다.

이후 실습에서 OnPaint에 GDI+ 그리기 코드를 작성하면서 실습을 진행하기 바란다.

아직 GDI+ 사용법을 배울 단계는 아니므로 OnPaint의 코드는 일단 무시하기로 하자.

다.


MFC에서의 초기화다음은 MFC 프로젝트에서 GDI+를 사용하는 방법에 대해 알아보자. Win32 API 프로젝트와 실질적인 내용상의 차이는 없지만 MFC의 독특한 구조로 인해 코드를 작성하는 위치상의 차이가 있다.

API 프로젝트는 WinMain에 초기화, 정리 코드가 작성되지만 MFC는 WinMain이 라이브러리안에 숨겨져 있으므로 적당한 다른 시점을 찾아야 한다.

간단하게 예제를 만들어 보자.  예 제 : MfcGdiPlusMfcGdiPlus라는 이름으로 SDI 프로젝트를 만든다.

MDI나 대화상자 기반의 프로젝트로 만들어도 초기화 과정은 거의 비슷하다.

MFC 프로젝트는 기본적으로 미리 컴파일된 헤더(PCH) 기능을 사용하므로 StdAfx.h에 gdiplus.h 헤더 파일을 포함시켜야 한다.

그리고 네임 스페이스 선언과 라이브러리 임포트 선언문도 StdAfx.h에 같이 작성한다.

 #include <gdiplus.h>using namespace Gdiplus;#pragma comment(lib, "gdiplus") 다음은 초기화 토큰을 저장하기 위한 멤버 변수를 MfcGdiPlusApp 클래스에 선언한다.

 class CMfcGdiPlusApp : public CWinApp{public:     ....     ULONG_PTR m_gpToken; API 프로젝트에서는 WinMain 안에 초기화, 해제 코드가 모두 작성되므로 토큰 변수가 지역 변수여도 상관없지만 MFC 프로젝트는 두 시점을 처리하는 함수가 나누어져 있으므로 멤버로 선언해야 한다.

응용 프로그램을 초기화하는 InitInstance의 선두에서 GDI+ 라이브러리 초기화 함수를 호출한다.

 BOOL CMfcGdiPlusApp::InitInstance(){     AfxEnableControlContainer();      GdiplusStartupInput gpsi;     if (GdiplusStartup(&m_gpToken,&gpsi,NULL) != Ok) {          AfxMessageBox(TEXT("GDI+ 라이브러리를 초기화할 수 없습니다.

"));          return FALSE;     }     .... 초기화에 실패하면 FALSE를 리턴하여 응용 프로그램을 아예 시작하지 않도록 했다.

해제할 시점은 응용 프로그램이 종료되는 ExitInstance 함수이다.

AppWizard로 만든 프로젝트에는 이 함수가 정의되어 있지 않으므로 클래스 뷰의 속성창에서 ExitInstance 가상 함수를 재정의한 후 셧다운 코드를 작성한다.

 int CMfcGdiPlusApp::ExitInstance() {     GdiplusShutdown(m_gpToken);          return CWinApp::ExitInstance();} MFC의 프레임워크가 요구하는 바대로 초기화 및 해제를 했다.

라이브러리가 초기화되면 이제 GDI+를 사용할 수 있다.

뷰의 OnDraw에 간단한 테스트 코드를 작성해 보자. void CMfcGdiPlusView::OnDraw(CDC* pDC){     CMfcGdiPlusDoc* pDoc = GetDocument();     ASSERT_VALID(pDoc);     if (!pDoc)          return;      Graphics G(pDC->m_hDC);     Pen P(Color(255,0,0,255),5);      G.DrawEllipse(&P,10,10,300,200);} 앞 예제와 똑같은 모양의 타원을 그려 보았는데 사용하는 방법도 동일하고 실행 결과도 물론 동일하다.

MFC가 특별히 GDI+를 위한 지원 클래스를 제공하는 것도 아니고 GDI+도 마찬가지로 MFC를 위한 별도의 배려를 하지 않는다.

따라서 MFC라고 해서 GDI+를 쓰는 방법이 특별히 편리하지도 않으며 불이익이 있는 것도 아니다.

하지만 클래스 단위로 응용 프로그램을 구성하다 보니 코드가 여기 저기 흩어져 Win32 프로젝트에 비해 잔손이 많이 가는 것은 솔직히 사실이다.

Win32 프로젝트는 복사해서 붙여 넣으면 그만이지만 MFC 프로젝트는 멤버 선언하고 함수 만들고 신경쓸 게 많다.

이럴 때는 좀 더 쉬운 방법이 있는데 바로 클래스의 생성자와 파괴자를 활용하는 것이다.

이런 기법은 C++ 문법서에도 일반적으로 많이 소개된 것이라 친숙할 것이다.

  예 제 : GdiPlusStarter#include <windows.h> LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);HINSTANCE g_hInst;HWND hWndMain;LPCTSTR lpszClass=TEXT("GdiPlusStarter"); #include <gdiplus.h>using namespace Gdiplus;#pragma comment(lib, "gdiplus")class CGdiPlusStarter{private:     ULONG_PTR m_gpToken; public:     bool m_bSuccess;     CGdiPlusStarter() {          GdiplusStartupInput gpsi;          m_bSuccess=(GdiplusStartup(&m_gpToken,&gpsi,NULL) == Ok);     }    

CGdiPlusStarter() {          GdiplusShutdown(m_gpToken);     }};CGdiPlusStarter g_gps; int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance       ,LPSTR lpszCmdParam,int nCmdShow){     HWND hWnd;     MSG Message;     WNDCLASS WndClass;     g_hInst=hInstance;      if (g_gps.m_bSuccess == FALSE) {          MessageBox(NULL,TEXT("GDI+ 라이브러리를 초기화할 수 없습니다.

"),              TEXT("알림"),MB_OK);          return 0;     }     ....} void OnPaint(HDC hdc){     Graphics G(hdc);     Pen P(Color(255,0,0,255),5);      G.DrawEllipse(&P,10,10,300,200);} LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam){     ....}  WinMain 이전에 선언되어 있는 CGdiPlusStarter 클래스가 초기화 및 해제를 자동으로 수행한다.

토큰값은 클래스의 멤버 변수로 선언해 두었고 생성자에서 초기화 함수를 호출하고 파괴자에서 정리 함수를 호출했다.

이 클래스 타입의 전역 객체 g_gps를 선언해 놓기만 하면 모든 처리는 자동으로 수행된다.

전역 객체의 이름 따위는 별 의미가 없으며 단지 이 객체가 존재하기만 하면 된다.

전역 객체의 생성자는 WinMain보다 먼저 호출되므로 프로그램 시작 직후에 생성자에 의해 GDI+가 자동으로 초기화될 것이다.

초기화 토큰은 멤버 변수에 잘 보관해 놓는다.

프로그램이 종료되기 직전에 파괴자가 호출되며 저장된 토큰을 꺼내 해제 코드를 수행한다.

자동으로 호출되는 생성자와 파괴자의 특성을 잘 활용한 예라고 할 수 있다.

초기화 성공 여부는 m_bSuccess 멤버 변수에 저장한다.

생성자는 리턴값을 가질 수 없으므로 외부에서 초기화 결과를 알고 싶다면 m_bSuccess 멤버 변수를 참조하면 된다.

위 예제에서는 WinMain 선두에 성공 여부를 점검하는 코드를 작성해 놓았는데 예제 수준에서는 꼭 필요한 처리가 아니므로 생략해도 큰 문제는 없다.

이렇게 초기화 클래스를 잘 만들어 놓으면 Win32에서나 MFC에서나 클래스 선언문과 전역 객체 선언문을 붙여 넣기만 하면 된다.

단, MFC처럼 미리 컴파일된 헤더 기능을 쓰는 경우는 헤더 파일 인클루드문만 StdAfx.h로 옮기면 된다.

이후의 예제들은 이 클래스를 활용하여 GDI+를 초기화할 것이다.

초기화, 해제 등의 처리는 어떤 프로젝트에서나 동일하므로 앞으로 WinMain의 코드는 생략하기로 한다.

라.닷넷

GDI+를 활용할 수 있는 또 다른 환경은 닷넷이다.

사실 GDI+를 만든 주된 이유가 바로 닷넷 때문이라고 할 수 있다.

사운을 걸고 만들었다는 닷넷의 출력 엔진으로 20년 전의 구형 GDI를 쓴다는 것은 어울리지 않아 최신 운영 환경에 맞게 새로 만든 고품질의 출력 엔진이 바로 GDI+인 것이다.

C#은 물론이고 닷넷을 지원하는 모든 언어에서 GDI+를 사용할 수 있다.

닷넷은 플랫폼 독립성을 확보하기 위해 IL이라는 중간 언어 방식으로 동작한다.

그러나 기본 출력 엔진인 GDI+ 자체는 네이티브 C++로 만들었는데 왜냐하면 IL로는 제대로된 출력 속도를 낼 수 없기 때문이다.

그래픽 엔진이 가상 코드로 동작하면 너무 느리기 때문에 출력 엔진만큼은 가상 코드를 쓰지 않는다.

닷넷은 C++ DLL 형태의 GdiPlus.dll을 인테롭이라는 기법으로 호출하는 구조이다.

덕분에 C++ 프로젝트에서도 GdiPlus를 사용할 수 있는 것이다.

닷넷에서 GDI+는 기본 출력 엔진이기 때문에 별도의 초기화 과정이 필요치 않다.

닷넷 프로젝트를 만들기만 하면 GDI+를 언제든지 사용할 수 있다.

지금 사용하고 있는 VS 2008로 닷넷 예제도 만들 수 있으므로 간단하게 실습을 해 보자. 새 프로젝트를 시작하고 프로젝트 형식에서 Visual C#의 Windows를 선택한다.

오른쪽 템플릿에서 "Windows Forms 응용 프로그램" 항목을 선택하고 프로젝트 이름은 dotNet으로 준다.

확인 버튼을 누르면 별다른 질문없이 프로젝트를 생성하고 빈 폼 하나를 열어 줄 것이다.

이 폼에 컨트롤을 올려 폼을 디자인하고 이벤트 핸들러를 작성하여 동작을 정의한다.

여기서는 단순히 출력만 해 볼 것이므로 컨트롤은 배치할 필요가 없고 폼의 Paint 이벤트만 처리해 보자. Paint는 폼이 무효화될 때마다 전달되며 Win32의 WM_PAINT 메시지에 대응되는 이벤트이다.

속성창의 이벤트 페이지에서 Paint 항목을 더블 클릭하면 이벤트 핸들러가 생성되고 코드창이 열린다.

폼이 이벤트를 받는데 필요한 모든 코드가 같이 작성되므로 개발자는 핸들러 내부의 본체에 원하는 코드만 작성하면 된다.

MFC와 비슷한 방식이며 잡스러운 처리들은 개발툴이 알아서 해 주므로 생산성이 훨씬 더 높다.

빈 핸들러에 다음 코드를 작성해 보자. private void Form1_Paint(object sender, PaintEventArgs e){     Pen P = new Pen(Color.Blue,5);     e.Graphics.DrawEllipse(P, 10, 10, 300, 200); } Paint 이벤트의 인수 e 안에 Graphics의 객체가 전달되므로 Graphics 객체를 따로 만들 필요가 없다.

닷넷 프레임워크가 Graphics의 객체를 미리 만들어 주므로 이벤트 핸들러에서는 전달된 객체의 메소드를 호출하기만 하면 된다.

앞 예제와 마찬가지로 굵기 5의 파란색 펜으로 타원을 그렸다.

실행 결과는 물론 동일하다.

네이티브 C++에서나 닷넷에서나 GDI+를 프로그래밍하는 방법은 거의 동일하다.

어차피 같은 DLL을 사용하는 것이므로 클래스의 메소드 이름이나 호출하는 방법 등에서는 차이가 없다.

다만 언어적인 문법 차이에 의해 호출문이나 인수의 형태가 조금 다르기는 한데 이런 차이점은 두 언어의 문법 구조를 알게 되면 자연스럽게 이해될 것이다.

또 닷넷은 GDI+를 래핑하여 사용하므로 원래 GdiPlus.dll에는 없는 유틸리티 클래스들이 추가로 제공되는 이점이 있다.

위 코드에서 파란색을 의미하는 Color.Blue라는 표현이 대표적인 예인데 프로피터라는 문법으로 자주 사용하는 표준 색상을 미리 정의해 놓아 색상을 사용하기가 훨씬 더 용이하다.

이 점에 대해서는 다음에 조금 더 깊이 있게 연구해 보도록 하자.이 강좌는 C++ 언어를 중심으로 쓰여졌으므로 이후부터 C++ 컴파일러로 예제를 만들 것이다.

두 언어로 예제를 모두 만들어 보면 좋겠지만 강좌가 난잡해지는 경향이 있어 C++에만 집중하기로 한다.

닷넷용의 예제가 필요하다면 C# 언어 입문서를 참조하기 바란다.

시중에 닷넷 관련 서적은 그야 말로 널려 있으므로 어렵지 않게 원하는 자료를 구할 수 있을 것이다.

  참조 : http://www.winapi.co.kr/project/library/gdiplus/gdiplus.htm가장 중요한 점은, 인쇄 기능이 있다는 것이지요.첨부의 파일은 SumatraPDF의 인쇄 기능을 MuPDF에 붙여서 MuPDF에 인쇄 기능을 추가한 소스입니다.

  컴파일 ※ mupdf 1.3 정식 릴리즈 버전에 맞춰져 있으므로 다른 버전 이용자는 적용이 불가능합니다.

 1) 첨부 파일을 다운받은 후 압축을 풉니다.

 2) 'colorspace.h, device.h, path.h, pixmap.h, system.h' 파일을 MuPDF의 'include/mupdf/fitz' 디렉토리에 덮어쓰기로 넣습니다.

 3) 'draw-path.c, draw-unpack.c, gdiplus-device.cpp, image.c, load-png.c, path.c, pixmap.c, svg-device.c, trace-device.c' 파일을 MuPDF의 'source/fitz' 디렉토리에 덮어쓰기로 넣습니다.

 4) 'pdf-device.c' 파일을 MuPDF의 'source/pdf' 디렉토리에 덮어쓰기로 넣습니다.

 5) VS를 실행한 후 'libmupdf 속성 페이지 > C/C++ > 전처리기' 메뉴로 이동한 후 '전처리기 정의' 항목에 '_GDIPLUS_'를 합니다.

(홀따옴표는 하는거 아닙니다;;) 6) 컴파일 하면 인쇄 기능이 추가됩니다.

  인쇄 기능 사용 방법 요 부분은 다음 글에서 예제와 함께 설명해 드리도록 하겠습니다.

  기타 일부 한글 PDF 파일 인쇄 시 프로그램이 죽어 버리는 현상이 발견되었습니다.

SumatraPDF에서도 동일 현상이 발견되어 GDI+ 인쇄 소스에 약간의 패치가 가해진 상태이므로 SumatraPDF의 인쇄 소스와 내용이 다릅니다.

  가장 중요한 점은, 인쇄 기능이 있다는 것이지요.첨부의 파일은 SumatraPDF의 인쇄 기능을 MuPDF에 붙여서 MuPDF에 인쇄 기능을 추가한 소스입니다.

  컴파일 ※ mupdf 1.3 정식 릴리즈 버전에 맞춰져 있으므로 다른 버전 이용자는 적용이 불가능합니다.

 1) 첨부 파일을 다운받은 후 압축을 풉니다.

 2) 'colorspace.h, device.h, path.h, pixmap.h, system.h' 파일을 MuPDF의 'include/mupdf/fitz' 디렉토리에 덮어쓰기로 넣습니다.

 3) 'draw-path.c, draw-unpack.c, gdiplus-device.cpp, image.c, load-png.c, path.c, pixmap.c, svg-device.c, trace-device.c' 파일을 MuPDF의 'source/fitz' 디렉토리에 덮어쓰기로 넣습니다.

 4) 'pdf-device.c' 파일을 MuPDF의 'source/pdf' 디렉토리에 덮어쓰기로 넣습니다.

 5) VS를 실행한 후 'libmupdf 속성 페이지 > C/C++ > 전처리기' 메뉴로 이동한 후 '전처리기 정의' 항목에 '_GDIPLUS_'를 합니다.

(홀따옴표는 하는거 아닙니다;;) 6) 컴파일 하면 인쇄 기능이 추가됩니다.

  인쇄 기능 사용 방법 요 부분은 다음 글에서 예제와 함께 설명해 드리도록 하겠습니다.

  기타 일부 한글 PDF 파일 인쇄 시 프로그램이 죽어 버리는 현상이 발견되었습니다.

SumatraPDF에서도 동일 현상이 발견되어 GDI+ 인쇄 소스에 약간의 패치가 가해진 상태이므로 SumatraPDF의 인쇄 소스와 내용이 다릅니다.

[gdi 엔진] 결국 이렇게


   먼저 기본적으로 화면을 하얗게 만들어 깜빡이는 현상을 유발하는  afx_msg BOOL OnEraseBkgnd(CDC* pDC);을 수정하여 준다.

BOOL CScrollViewEx::OnEraseBkgnd(CDC* pDC){    return TRUE; // 강제 리턴 처리하여 하얗게 그려주는 루틴을 들어가지 않게 한다.

    //return CScrollView::OnEraseBkgnd(pDC);  // 이부분에서 배경을 하얗게 그려주므로 주석 처리함.} 2. afx_msg void OnPaint();를 GDI+ 더블 버퍼링 환경에 맞게 코딩해 준다.

void CScrollViewEx::OnPaint(){     CPaintDC dc(this); // device context for painting      // Double buffering start     CRect rectDraw;     GetClientRect(rectDraw);          // 한번에 그리기 위한 buffer역할을 하는 bitmap을 생성한다.

(깜빡임은 한번에 그리지 않고 여러번 그리기 때문에 발생함)     Gdiplus::Bitmap bitmap(rectDraw.Width(), rectDraw.Height());      Gdiplus::Graphics graphics(dc);     Gdiplus::Graphics memDC(&bitmap);          // 그리기 전 bitmap 바탕을 하얗게 그려준다.

(이 부분이 없으면 잔상이 남게 된다)     Gdiplus::SolidBrush whiteBrush( Gdiplus::Color(255, 255, 255, 255));     memDC.FillRectangle(&whiteBrush, 0, 0, rectDraw.Width(), rectDraw.Height());          // 원하는 이미지를 bitmap에 그린다.

          Gdiplus::Rect DestRect(0, 0, rect.right, rect.bottom);     Gdiplus::Image* imageDisplay = NULL;     imageDisplay = m_pImage;     memDC.DrawImage(imageDisplay, DestRect, 0, 0, rectDraw.right, rectDraw.bottom, Gdiplus::UnitPixel,  NULL, NULL, NULL);      // 원하는 선을 bitmap에 그린다.

     Gdiplus::Pen GreenPen(Gdiplus::Color(255, 0, 255, 0), 1.0f);     memDC.DrawLine(&GreenPen, Gdiplus::Point(0,  0), Gdiplus::Point(100,  100));            // 완성된 bitmap은 한번에 화면에 그린다.

     graphics.DrawImage(&bitmap, 0, 0);  } 이상입니다.

GDI plus(GDI+)에서의 더블 버퍼링 방법을 소개한다.

  1. 먼저 기본적으로 화면을 하얗게 만들어 깜빡이는 현상을 유발하는  afx_msg BOOL OnEraseBkgnd(CDC* pDC);을 수정하여 준다.

BOOL CScrollViewEx::OnEraseBkgnd(CDC* pDC){    return TRUE; // 강제 리턴 처리하여 하얗게 그려주는 루틴을 들어가지 않게 한다.

    //return CScrollView::OnEraseBkgnd(pDC);  // 이부분에서 배경을 하얗게 그려주므로 주석 처리함.} 2. afx_msg void OnPaint();를 GDI+ 더블 버퍼링 환경에 맞게 코딩해 준다.

void CScrollViewEx::OnPaint(){     CPaintDC dc(this); // device context for painting      // Double buffering start     CRect rectDraw;     GetClientRect(rectDraw);          // 한번에 그리기 위한 buffer역할을 하는 bitmap을 생성한다.

(깜빡임은 한번에 그리지 않고 여러번 그리기 때문에 발생함)     Gdiplus::Bitmap bitmap(rectDraw.Width(), rectDraw.Height());      Gdiplus::Graphics graphics(dc);     Gdiplus::Graphics memDC(&bitmap);          // 그리기 전 bitmap 바탕을 하얗게 그려준다.

(이 부분이 없으면 잔상이 남게 된다)     Gdiplus::SolidBrush whiteBrush( Gdiplus::Color(255, 255, 255, 255));     memDC.FillRectangle(&whiteBrush, 0, 0, rectDraw.Width(), rectDraw.Height());          // 원하는 이미지를 bitmap에 그린다.

          Gdiplus::Rect DestRect(0, 0, rect.right, rect.bottom);     Gdiplus::Image* imageDisplay = NULL;     imageDisplay = m_pImage;     memDC.DrawImage(imageDisplay, DestRect, 0, 0, rectDraw.right, rectDraw.bottom, Gdiplus::UnitPixel,  NULL, NULL, NULL);      // 원하는 선을 bitmap에 그린다.

     Gdiplus::Pen GreenPen(Gdiplus::Color(255, 0, 255, 0), 1.0f);     memDC.DrawLine(&GreenPen, Gdiplus::Point(0,  0), Gdiplus::Point(100,  100));            // 완성된 bitmap은 한번에 화면에 그린다.

     graphics.DrawImage(&bitmap, 0, 0);  } 이상입니다.

 #컬러RGB형식과 같은 컬러를 만들기위해서는 일단 Color 객체를 생성해야한다.

Color 객체는 구조체형식으로 되어있고 다음과 같은 형태를 가진다.

 public static Color FromArgb(int alpha, int red, int greenm int blue) 알파값이 추가된 ARGB로 만들어낼수 있으며 예를 들면 다음과 같이 선언할수있다.

 Color myColor = Color.FromArgb(255,255,255); 이런식으로 myColor 를 만들어내면, myColor.R 과 같이 해당 색의 RGB값을 byte 형식으로 가져올수있다.

    또한 GetHue(), GetSaturation(), GetBrightness() 메소드로 해당 Color의 HSB값을 float 형식으로 받아오는것도 가능하다.

  #이미지 영상처리를 하기위해서는 일단 작업할 이미지를 불러와야한다.

Image 클래스를 프로그램에서 이미지가 필요할때 사용할수있는 클래스이며 다양한 기능이 들어있다.

 이미지를 불러오는 방법은 여러가지인데, 디스크의 이미지파일로부터 읽어오는 방법Win32 Bitmap으로부터 불러오는방법, 파일스트림으로부터 불러오는 방법 등이 있다.

 가장 간단한 디스크내의 이미지를 불러오는 방법은 다음과 같다.

 Image img = Image.FromFile("C:\test.jpg"); 이렇게하면 img객체에 이미지가 불러와진다.

이제 이 img를 picturebox나 form으로 볼수있는것이다.

(일반적으로 Load 시에 이미지를 읽어서 객체화시키는것이 동작속도면에서 어느정도 유리한면이 있다.

)    Image 클래스의 속성으로는 다음과 같은 항목들이 있다.

  여기서 PixelFormat 속성은 이미지의 픽셀형식을 가져올수있는데, 형식은 PixelFormat 형식이며 열거형으로 되어있다.

16비트/32비트/흑백컬러 등을 확인할때 사용할수있다.

  #비트맵 Bitmap 클래스는 Image 클래스에서 파생된 클래스이며 주로 이미지를 불러와서 생성,수정하는데에 사용된다.

Bitmap은 픽셀단위로 메모리에서 작업하는 것이 가능한데, 이것을 사용해서 영상처리를 하게된다.

다양한 필터들을 적용하는 과정에서 메모리를 잠그고 다시 해제하는 과정을 거치게되는것이다.

    GDI+ 소개GDI(Graphic User Interface)는 윈도우즈의 핵심 모듈 중의 하나로 출력과 관련된 기능을 담당한다.

화면, 프린터 등의 출력 하드웨어와 응용 프로그램의 중간에 위치하여 장치 독립성을 확보하며 복수 개의 프로그램이 서로의 영역내에서 방해하지 않고 자유롭게 출력하도록 조율하는 것이 주된 기능이다.

응용 프로그램은 하드웨어를 직접 액세스할 수 없으며 반드시 GDI를 통해야만 한다.

이 강좌를 읽을 정도면 Win32의 기본적인 구조에 대해서는 잘 알고 있을 것이므로 GDI에 대해서는 이미 익숙할 것이다.

만약 그렇지 않다면 별도의 강좌를 통해 Win32의 기본에 대해 먼저 공부할 필요가 있다.

GDI를 모른 상태에서 그 상위 버전인 GDI+를 공부할 수는 없다.

GDI는 그래픽 환경의 출력 엔진으로써 오랫동안 충실히 제 역할을 해 왔지만 현대적인 프로그램의 복잡한 요구를 충족시키기에는 다소 부족한 점이 많다.

 ① 기본적인 그래픽 출력은 가능하지만 섬세한 그래픽 표현에는 다소 역부족이다.

예를 들어 점선 펜을 만들 수는 있지만 굵기가 2이상이면 무조건 실선이 되어 버리므로 굵은 점선을 그을 수 없고 NT 이하에서는 비트맵 브러시도 8*8 이상의 크기를 지원하지 않아 큰 비트맵 브러시를 쓸 수 없다.

② 그래픽 속성을 바꿀 때마다 GDI 오브젝트를 일일이 생성, 선택한 후 사용해야 하므로 무척 번거롭다.

빨간색 테두리에 파란색 면을 가지는 타원을 하나 그리려면 Ellipse를 호출하기 전에 펜과 브러시를 미리 생성하여 DC에 선택해야 하며 그린 후에도 선택 해제, 파괴의 뒷처리를 반드시 해야 한다.

정작 중요한 출력문보다 준비하고 정리하는 코드가 더 많아 무척 불편하다.

③ 실수로 GDI 오브젝트를 해제하지 않을 경우 리소스 누출에 의해 시스템의 안정성을 위협하기도 한다.

비트맵같은 큰 개체를 해제하지 않으면 많은 리소스를 소모하여 더 이상 그리기를 할 수 없는 상태가 되기도 하는데 이는 시스템 다운에 버금갈 정도로 치명적이다.

 GDI의 단점을 요약하자면 기능은 부족하고 쓰기도 불편하며 안전하지도 않다는 것이다.

GDI를 최초 디자인했던 20년 전에는 하드웨어 성능이 좋지 못해 화려한 기능보다는 단순하고 빠른 동작이 더 중요했었다.

그러나 지금은 하드웨어 환경이 개선되고 사용자의 요구가 늘어남에 따라 좀 더 강력한 출력 엔진이 필요해졌다.

GDI+는 전통적인 GDI 모듈의 업그레이드 버전으로서 복잡하고 섬세한 그래픽을 출력할 수 있고 기존 기능을 최적화한 새로운 출력 모듈이다.

GDI의 계승자이므로 장치 독립성을 제공한다는 기본 목적은 동일하며 GDI로 할 수 있는 대부분의 작업을 GDI+로도 할 수 있다.

윈도우즈 XP와 비스타에 기본적으로 탑재되어 있으며 닷넷 플랫폼에도 포함되어 있고 64비트 윈도우즈에서도 계속 지원되므로 향후 GDI를 완전히 대체하게 될 것이다.

2000을 포함하여 2000 이하의 버전에서는 별도의 모듈을 배포해야만 사용할 수 있지만 GdiPlus.dll 파일 하나만 복사하면 되므로 하위 호환성의 문제도 거의 없는 셈이다.

95/98 이후의 NT/2000에서 GDI도 투명 비트맵 출력, 좌표 변환, 반투명 출력 등 많은 기능 개선이 이루어졌지만 이런 기능은 사실 있어도 마음대로 사용할 수 없었다.

왜냐하면 추가된 함수를 하나라도 사용하게 되면 95/98에서 이 프로그램은 제대로 실행되지 않기 때문이다.

이렇게 만든 프로그램을 95/98 사용자가 쓸 수 있는 유일한 방법은 운영체제를 업그레이드하는 것 뿐이므로 시장을 포기하지 않는 한 이런 함수를 쓸 수 없었다.

그러나 GDI+는 DLL을 같이 복사하면 하위 버전의 운영체제에서도 문제없이 잘 실행되므로 호환성을 걱정할 필요가 없다.

95/98 환경이라도 DLL 파일을 같이 배포하기만 하면 되므로 용량이 약간 늘어난다는 것 외에는 별다른 번거로움이 없는 것이다.

DLL의 버전 충돌을 피하기 위해서 DLL을 재배포할 때는 가급적이면 시스템 디렉토리보다 응용 프로그램이 설치되는 디렉토리에 같이 복사하는 것이 안전하며 마이크로소프트는 이런 배포 방식을 권장하고 있다.

GDI+를 사용하기 위해 특별한 준비를 할 필요는 없다.

비주얼 스튜디오 2005 이상의 버전에는 GDI+ 개발에 필요한 SDK와 상세한 도움말이 제공되므로 컴파일러만 설치하면 GDI+를 바로 사용할 수 있다.

XP 이후에 GDI+가 기본 출력 엔진 역할을 하므로 컴파일러가 SDK를 제공하는 것이 당연하다.

이 강좌는 비스타 환경에서 비주얼 스튜디오 2008을 기준으로 하므로 이 환경을 갖추었다면 아무 것도 준비할 필요가 없다.

그러나 이전 버전에서는 약간의 준비를 해야 한다.

현재 2001이나 2003은 사용자가 거의 없고 VC 6.0 사용자가 일부 남아 있는데 VC 6.0 사용자는 별도로 SDK를 설치해야 한다.

VC 60용으로 발표된 가장 최신 SDK인 2003년 2월 플랫폼 SDK를 다운로드 받아 설치하고 컴파일러의 디렉토리 옵션창에서 Include 경로와 Library 경로를 추가하면 된다.

최신 SDK 정보를 가장 먼저 참조하도록 목록의 제일 위쪽으로 이동시켜야 한다.

최신 MSDN에는 GDI+에 대한 문서가 포함되어 있다.

친절한 자습서는 물론이고 컴파일해 볼만한 예제와 각 클래스의 멤버에 대한 정보, 함수에 대한 도움말, 고급 기법에 대한 문서 등이 수록되어 있으므로 이 도움말만 순서대로 읽어 봐도 GDI+는 쉽게 정복할 수 있다.

물론 영어로 되어 있다.

플랫폼 SDK와 함께 설치되는 도움말에도 GDI+에 대한 도움말이 제공된다.

GDI+는 최신 라이브러리인만큼 객체 지향적인 C++언어로 작성되어 있다.

그래서 C 컴파일러에서는 이 라이브러리를 사용할 수 없다는 문제점이 있기는 하지만 요즘의 컴파일러들은 대부분 C++언어를 잘 지원하므로 개발툴의 제약도 거의 없는 셈이다.

GDI+는 GDI의 기능을 개선한 클래스 라이브러리이므로 일단 GDI에 대해서 잘 알고 있어야 하며 C++ 언어의 기본적인 사용 방법에 대해서도 충분히 숙지하고 있어야 한다.

GDI+는 기초적인 C++ 문법만을 요구할 뿐이며 해박한 객체 지향 이론을 요구하는 것은 아니다.

상속이나 가상 함수, 템플리트 같은 고급 기법까지는 모르더라도 객체안에 속성과 함수가 캡슐화되어 있다는 것과 객체가 생성 파괴될 때 자동으로 호출되는 함수가 있다는 것 정도만 알아도 GDI+를 배우고 사용할 수 있다.

GDI+를 공부해 보면 왜 C++이 좋은가를 실감할 수 있을 것이다.

나.초기화

어떤 라이브러리든지 사용하려면 약간의 초기화 과정을 거쳐야 한다.

몇 가지 함수만 시기 적절하게 호출하면 되므로 아주 간단하다.

예제 수준에서는 복잡한 UI나 특별한 기능이 필요치 않으므로 Win32 프로젝트로 첫 예제를 만들어 보자. 비주얼 스튜디오 2008을 실행하고 새 프로젝트 명령으로 프로젝트를 생성한다.

예제 작성용으로 GpExam2008이라는 이름의 디렉토리를 미리 생성해 놓고 이 디렉토리안에 GdiPlusStart라는 이름으로 새 프로젝트를 만들었다.

빈 프로젝트 옵션을 선택하고 GdiPlusStart.cpp 파일을 추가한 후 다음 소스를 작성한다.

  예 제 : GdiPlusStart#include <windows.h> LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);HINSTANCE g_hInst;HWND hWndMain;LPCTSTR lpszClass=TEXT("GdiPlusStart"); #include <gdiplus.h>using namespace Gdiplus;#pragma comment(lib, "gdiplus") int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance       ,LPSTR lpszCmdParam,int nCmdShow){     HWND hWnd;     MSG Message;     WNDCLASS WndClass;     g_hInst=hInstance;      ULONG_PTR gpToken;     GdiplusStartupInput gpsi;     if (GdiplusStartup(&gpToken,&gpsi,NULL) != Ok) {          MessageBox(NULL,TEXT("GDI+ 라이브러리를 초기화할 수 없습니다.

"),              TEXT("알림"),MB_OK);          return 0;     }      WndClass.cbClsExtra=0;     WndClass.cbWndExtra=0;     WndClass.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);     WndClass.hCursor=LoadCursor(NULL,IDC_ARROW);     WndClass.hIcon=LoadIcon(NULL,IDI_APPLICATION);     WndClass.hInstance=hInstance;     WndClass.lpfnWndProc=WndProc;     WndClass.lpszClassName=lpszClass;     WndClass.lpszMenuName=NULL;     WndClass.style=CS_HREDRAW | CS_VREDRAW;     RegisterClass(&WndClass);      hWnd=CreateWindow(lpszClass,lpszClass,WS_OVERLAPPEDWINDOW,          CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,          NULL,(HMENU)NULL,hInstance,NULL);     ShowWindow(hWnd,nCmdShow);      while (GetMessage(&Message,NULL,0,0)) {          TranslateMessage(&Message);          DispatchMessage(&Message);     }     GdiplusShutdown(gpToken);     return (int)Message.wParam;} void OnPaint(HDC hdc){     Graphics G(hdc);     Pen P(Color(255,0,0,255),5);      G.DrawEllipse(&P,10,10,300,200);} LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam){     HDC hdc;     PAINTSTRUCT ps;      switch (iMessage) {     case WM_CREATE:          hWndMain=hWnd;          return 0;     case WM_PAINT:          hdc=BeginPaint(hWnd, &ps);          OnPaint(hdc);          EndPaint(hWnd, &ps);          return 0;     case WM_DESTROY:          PostQuitMessage(0);          return 0;     }     return(DefWindowProc(hWnd,iMessage,wParam,lParam));} 첫 번째 예제들이 늘상 그렇듯이 특별한 동작은 없고 굵기 5의 파란색 펜으로 타원만 하나 그려 보았다.

실행 결과는 다음과 같다.

전형적인 Win32 시작 소스에 GDI+와 관련된 추가 코드가 조금 더 작성되어 있다.

 ① gdiplus.h 헤더 파일을 인클루드하고 있는데 이 헤더 파일에 GDI+의 모든 타입, 열거형, 클래스 등이 선언되어 있다.

이 헤더 파일을 열어 보면 GDI+의 기본적인 선언문들을 직접 구경할 수 있는데 gdiplus.h는 GDI+의 모든 헤더를 포함하는 헤더 파일 컨테이너라고 할 수 있다.

② GDI+의 모든 명칭은 GdiPlus 네임 스페이스에 포함되어 있다.

따라서 클래스 이름과 열거형 이름앞에 GdiPlus::을 일일이 붙여야 하는데 너무 번거로우므로 using 지시자를 사용하여 GdiPlus 네임 스페이스에 선언된 모든 명칭을 전역 영역으로 가져 오도록 한다.

[gdi 엔진] 해결책이 있는지



이 지시자에 의해 컴파일러는 전역 네임 영역에 없는 명칭에 대해 GdiPlus 네임 스페이스를 검색하므로 이름만으로 GDI+의 명칭들을 사용할 수 있다.

③ GDI+의 실제 코드는 gdiplus.dll에 정의되어 있으므로 이 라이브러리를 링크해야 한다.

원칙적으로는 프로젝트 설정 대화상자의 링크/ 페이지의 추가 종속성란에 gdiplus.lib 임포트 라이브러리를 지정해야 하나 매 실습마다 대화상자를 열기 귀찮으므로 소스에서 링크하는 것이 편리하다.

 #pragma comment(lib, "gdiplus") 라이브러리 모듈에게 gdiplus.lib 임포트 라이브러리를 참조하라고 알려 준다.

이렇게 선언문을 작성해 놓으면 새 소스를 만들 때도 붙여 넣기만 하면 되므로 실습하기 편리하다.

④ 프로그램 시작시 GDI+ 라이브러리를 초기화한다.

이때는 다음 전역 함수를 사용한다.

GDI+는 모든 것이 클래스로 되어 있지만 초기화와 관련된 함수는 클래스에 소속되지 않으며 일반 함수로 제공된다.

객체를 아직 만들기 전이므로 초기화 함수는 일반 함수 또는 정적 함수일 수밖에 없다.

 Status GdiplusStartup(ULONG_PTR *token, const GdiplusStartupInput *input, GdiplusStartupOutput *output); token은 초기화시 받는 토큰이며 종료할 때 이 토큰을 GdiplusShutdown으로 전달해야 한다.

input은 옵션 구조체이며 이 안에 GDI+의 버전, 디버그 콜백 함수, 백그라운드 스레드 허용 여부 등을 지정할 수 있는데 별 다른 옵션을 지정하지 않을 경우는 변수만 선언해서 그 포인터를 넘겨 주면 된다.

예제에서는 gpsi 구조체를 선언한 후 넘기기만 했다.

도움말에서 확인해 보면 알겠지만 이 구조체의 생성자가 무난한 디폴트로 초기화하도록 되어 있으므로 지역 변수라 하더라도 쓰레기값이 들어가지는 않는다.

생략할 수 없으므로 반드시 변수를 선언한 후 그 포인터를 전달해야 한다.

output은 초기화 결과를 돌려 주기 위한 구조체인데 필요없을 경우 NULL로 줄 수 있다.

백그라운드 스레드를 허용하지 않을 때만 output 인수가 필요하다.

이 함수는 초기화 결과를 Status형의 열거형으로 리턴하는데 성공했을 경우 Ok가 리턴된다.

Ok가 아닌 경우는 GDI+를 사용할 수 없으므로 적절한 에러 처리를 할 필요가 있다.

예제에서는 메시지 박스를 출력하고 프로그램을 종료했다.

⑤ 프로그램을 끝낼 때 다음 함수를 호출하여 GDI+ 라이브러리를 셧다운한다.

GDI+는 실행중에 많은 리소스를 할당하는데 이 리소스를 반드시 해제해야 한다.

 void GdiplusShutdown(ULONG_PTR token); 스타트업할 때 발급받았던 토큰을 그대로 전달하되 그 전에 사용중인 모든 GDI+ 객체들을 닫아야 한다.

이 두 함수는 보통 쌍으로 사용되며 프로그램 시작시, 종료시에 각각 호출한다.

메인 윈도우의 WM_CREATE, WM_DESTROY에서 처리할 수도 있지만 응용 프로그램 전역적인 초기화이므로 WinMain에서 처리하는 것이 더 적당하다.

WndProc에는 별다른 코드가 없으며 WM_PAINT 메시지를 받았을 때 OnPaint 함수만 호출한다.

OnPaint에서는 파란색 펜으로 굵기 5의 타원을 그렸다.

이후 실습에서 OnPaint에 GDI+ 그리기 코드를 작성하면서 실습을 진행하기 바란다.

아직 GDI+ 사용법을 배울 단계는 아니므로 OnPaint의 코드는 일단 무시하기로 하자.

다.


MFC에서의 초기화다음은 MFC 프로젝트에서 GDI+를 사용하는 방법에 대해 알아보자. Win32 API 프로젝트와 실질적인 내용상의 차이는 없지만 MFC의 독특한 구조로 인해 코드를 작성하는 위치상의 차이가 있다.

API 프로젝트는 WinMain에 초기화, 정리 코드가 작성되지만 MFC는 WinMain이 라이브러리안에 숨겨져 있으므로 적당한 다른 시점을 찾아야 한다.

간단하게 예제를 만들어 보자.  예 제 : MfcGdiPlusMfcGdiPlus라는 이름으로 SDI 프로젝트를 만든다.

MDI나 대화상자 기반의 프로젝트로 만들어도 초기화 과정은 거의 비슷하다.

MFC 프로젝트는 기본적으로 미리 컴파일된 헤더(PCH) 기능을 사용하므로 StdAfx.h에 gdiplus.h 헤더 파일을 포함시켜야 한다.

그리고 네임 스페이스 선언과 라이브러리 임포트 선언문도 StdAfx.h에 같이 작성한다.

 #include <gdiplus.h>using namespace Gdiplus;#pragma comment(lib, "gdiplus") 다음은 초기화 토큰을 저장하기 위한 멤버 변수를 MfcGdiPlusApp 클래스에 선언한다.

 class CMfcGdiPlusApp : public CWinApp{public:     ....     ULONG_PTR m_gpToken; API 프로젝트에서는 WinMain 안에 초기화, 해제 코드가 모두 작성되므로 토큰 변수가 지역 변수여도 상관없지만 MFC 프로젝트는 두 시점을 처리하는 함수가 나누어져 있으므로 멤버로 선언해야 한다.

응용 프로그램을 초기화하는 InitInstance의 선두에서 GDI+ 라이브러리 초기화 함수를 호출한다.

 BOOL CMfcGdiPlusApp::InitInstance(){     AfxEnableControlContainer();      GdiplusStartupInput gpsi;     if (GdiplusStartup(&m_gpToken,&gpsi,NULL) != Ok) {          AfxMessageBox(TEXT("GDI+ 라이브러리를 초기화할 수 없습니다.

"));          return FALSE;     }     .... 초기화에 실패하면 FALSE를 리턴하여 응용 프로그램을 아예 시작하지 않도록 했다.

해제할 시점은 응용 프로그램이 종료되는 ExitInstance 함수이다.

AppWizard로 만든 프로젝트에는 이 함수가 정의되어 있지 않으므로 클래스 뷰의 속성창에서 ExitInstance 가상 함수를 재정의한 후 셧다운 코드를 작성한다.

 int CMfcGdiPlusApp::ExitInstance() {     GdiplusShutdown(m_gpToken);          return CWinApp::ExitInstance();} MFC의 프레임워크가 요구하는 바대로 초기화 및 해제를 했다.

라이브러리가 초기화되면 이제 GDI+를 사용할 수 있다.

뷰의 OnDraw에 간단한 테스트 코드를 작성해 보자. void CMfcGdiPlusView::OnDraw(CDC* pDC){     CMfcGdiPlusDoc* pDoc = GetDocument();     ASSERT_VALID(pDoc);     if (!pDoc)          return;      Graphics G(pDC->m_hDC);     Pen P(Color(255,0,0,255),5);      G.DrawEllipse(&P,10,10,300,200);} 앞 예제와 똑같은 모양의 타원을 그려 보았는데 사용하는 방법도 동일하고 실행 결과도 물론 동일하다.

MFC가 특별히 GDI+를 위한 지원 클래스를 제공하는 것도 아니고 GDI+도 마찬가지로 MFC를 위한 별도의 배려를 하지 않는다.

따라서 MFC라고 해서 GDI+를 쓰는 방법이 특별히 편리하지도 않으며 불이익이 있는 것도 아니다.

하지만 클래스 단위로 응용 프로그램을 구성하다 보니 코드가 여기 저기 흩어져 Win32 프로젝트에 비해 잔손이 많이 가는 것은 솔직히 사실이다.

Win32 프로젝트는 복사해서 붙여 넣으면 그만이지만 MFC 프로젝트는 멤버 선언하고 함수 만들고 신경쓸 게 많다.

이럴 때는 좀 더 쉬운 방법이 있는데 바로 클래스의 생성자와 파괴자를 활용하는 것이다.

이런 기법은 C++ 문법서에도 일반적으로 많이 소개된 것이라 친숙할 것이다.

  예 제 : GdiPlusStarter#include <windows.h> LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);HINSTANCE g_hInst;HWND hWndMain;LPCTSTR lpszClass=TEXT("GdiPlusStarter"); #include <gdiplus.h>using namespace Gdiplus;#pragma comment(lib, "gdiplus")class CGdiPlusStarter{private:     ULONG_PTR m_gpToken; public:     bool m_bSuccess;     CGdiPlusStarter() {          GdiplusStartupInput gpsi;          m_bSuccess=(GdiplusStartup(&m_gpToken,&gpsi,NULL) == Ok);     }    

CGdiPlusStarter() {          GdiplusShutdown(m_gpToken);     }};CGdiPlusStarter g_gps; int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance       ,LPSTR lpszCmdParam,int nCmdShow){     HWND hWnd;     MSG Message;     WNDCLASS WndClass;     g_hInst=hInstance;      if (g_gps.m_bSuccess == FALSE) {          MessageBox(NULL,TEXT("GDI+ 라이브러리를 초기화할 수 없습니다.

"),              TEXT("알림"),MB_OK);          return 0;     }     ....} void OnPaint(HDC hdc){     Graphics G(hdc);     Pen P(Color(255,0,0,255),5);      G.DrawEllipse(&P,10,10,300,200);} LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam){     ....}  WinMain 이전에 선언되어 있는 CGdiPlusStarter 클래스가 초기화 및 해제를 자동으로 수행한다.

토큰값은 클래스의 멤버 변수로 선언해 두었고 생성자에서 초기화 함수를 호출하고 파괴자에서 정리 함수를 호출했다.

이 클래스 타입의 전역 객체 g_gps를 선언해 놓기만 하면 모든 처리는 자동으로 수행된다.

전역 객체의 이름 따위는 별 의미가 없으며 단지 이 객체가 존재하기만 하면 된다.

전역 객체의 생성자는 WinMain보다 먼저 호출되므로 프로그램 시작 직후에 생성자에 의해 GDI+가 자동으로 초기화될 것이다.

초기화 토큰은 멤버 변수에 잘 보관해 놓는다.

프로그램이 종료되기 직전에 파괴자가 호출되며 저장된 토큰을 꺼내 해제 코드를 수행한다.

자동으로 호출되는 생성자와 파괴자의 특성을 잘 활용한 예라고 할 수 있다.

초기화 성공 여부는 m_bSuccess 멤버 변수에 저장한다.

생성자는 리턴값을 가질 수 없으므로 외부에서 초기화 결과를 알고 싶다면 m_bSuccess 멤버 변수를 참조하면 된다.

위 예제에서는 WinMain 선두에 성공 여부를 점검하는 코드를 작성해 놓았는데 예제 수준에서는 꼭 필요한 처리가 아니므로 생략해도 큰 문제는 없다.

이렇게 초기화 클래스를 잘 만들어 놓으면 Win32에서나 MFC에서나 클래스 선언문과 전역 객체 선언문을 붙여 넣기만 하면 된다.

단, MFC처럼 미리 컴파일된 헤더 기능을 쓰는 경우는 헤더 파일 인클루드문만 StdAfx.h로 옮기면 된다.

이후의 예제들은 이 클래스를 활용하여 GDI+를 초기화할 것이다.

초기화, 해제 등의 처리는 어떤 프로젝트에서나 동일하므로 앞으로 WinMain의 코드는 생략하기로 한다.

라.닷넷

GDI+를 활용할 수 있는 또 다른 환경은 닷넷이다.

사실 GDI+를 만든 주된 이유가 바로 닷넷 때문이라고 할 수 있다.

사운을 걸고 만들었다는 닷넷의 출력 엔진으로 20년 전의 구형 GDI를 쓴다는 것은 어울리지 않아 최신 운영 환경에 맞게 새로 만든 고품질의 출력 엔진이 바로 GDI+인 것이다.

C#은 물론이고 닷넷을 지원하는 모든 언어에서 GDI+를 사용할 수 있다.

닷넷은 플랫폼 독립성을 확보하기 위해 IL이라는 중간 언어 방식으로 동작한다.

그러나 기본 출력 엔진인 GDI+ 자체는 네이티브 C++로 만들었는데 왜냐하면 IL로는 제대로된 출력 속도를 낼 수 없기 때문이다.

그래픽 엔진이 가상 코드로 동작하면 너무 느리기 때문에 출력 엔진만큼은 가상 코드를 쓰지 않는다.

닷넷은 C++ DLL 형태의 GdiPlus.dll을 인테롭이라는 기법으로 호출하는 구조이다.

덕분에 C++ 프로젝트에서도 GdiPlus를 사용할 수 있는 것이다.

닷넷에서 GDI+는 기본 출력 엔진이기 때문에 별도의 초기화 과정이 필요치 않다.

닷넷 프로젝트를 만들기만 하면 GDI+를 언제든지 사용할 수 있다.

지금 사용하고 있는 VS 2008로 닷넷 예제도 만들 수 있으므로 간단하게 실습을 해 보자. 새 프로젝트를 시작하고 프로젝트 형식에서 Visual C#의 Windows를 선택한다.

오른쪽 템플릿에서 "Windows Forms 응용 프로그램" 항목을 선택하고 프로젝트 이름은 dotNet으로 준다.

확인 버튼을 누르면 별다른 질문없이 프로젝트를 생성하고 빈 폼 하나를 열어 줄 것이다.

이 폼에 컨트롤을 올려 폼을 디자인하고 이벤트 핸들러를 작성하여 동작을 정의한다.

여기서는 단순히 출력만 해 볼 것이므로 컨트롤은 배치할 필요가 없고 폼의 Paint 이벤트만 처리해 보자. Paint는 폼이 무효화될 때마다 전달되며 Win32의 WM_PAINT 메시지에 대응되는 이벤트이다.

속성창의 이벤트 페이지에서 Paint 항목을 더블 클릭하면 이벤트 핸들러가 생성되고 코드창이 열린다.

폼이 이벤트를 받는데 필요한 모든 코드가 같이 작성되므로 개발자는 핸들러 내부의 본체에 원하는 코드만 작성하면 된다.

MFC와 비슷한 방식이며 잡스러운 처리들은 개발툴이 알아서 해 주므로 생산성이 훨씬 더 높다.

빈 핸들러에 다음 코드를 작성해 보자. private void Form1_Paint(object sender, PaintEventArgs e){     Pen P = new Pen(Color.Blue,5);     e.Graphics.DrawEllipse(P, 10, 10, 300, 200); } Paint 이벤트의 인수 e 안에 Graphics의 객체가 전달되므로 Graphics 객체를 따로 만들 필요가 없다.

닷넷 프레임워크가 Graphics의 객체를 미리 만들어 주므로 이벤트 핸들러에서는 전달된 객체의 메소드를 호출하기만 하면 된다.

앞 예제와 마찬가지로 굵기 5의 파란색 펜으로 타원을 그렸다.

실행 결과는 물론 동일하다.

네이티브 C++에서나 닷넷에서나 GDI+를 프로그래밍하는 방법은 거의 동일하다.

어차피 같은 DLL을 사용하는 것이므로 클래스의 메소드 이름이나 호출하는 방법 등에서는 차이가 없다.

다만 언어적인 문법 차이에 의해 호출문이나 인수의 형태가 조금 다르기는 한데 이런 차이점은 두 언어의 문법 구조를 알게 되면 자연스럽게 이해될 것이다.

또 닷넷은 GDI+를 래핑하여 사용하므로 원래 GdiPlus.dll에는 없는 유틸리티 클래스들이 추가로 제공되는 이점이 있다.

위 코드에서 파란색을 의미하는 Color.Blue라는 표현이 대표적인 예인데 프로피터라는 문법으로 자주 사용하는 표준 색상을 미리 정의해 놓아 색상을 사용하기가 훨씬 더 용이하다.

이 점에 대해서는 다음에 조금 더 깊이 있게 연구해 보도록 하자.이 강좌는 C++ 언어를 중심으로 쓰여졌으므로 이후부터 C++ 컴파일러로 예제를 만들 것이다.

두 언어로 예제를 모두 만들어 보면 좋겠지만 강좌가 난잡해지는 경향이 있어 C++에만 집중하기로 한다.

닷넷용의 예제가 필요하다면 C# 언어 입문서를 참조하기 바란다.

시중에 닷넷 관련 서적은 그야 말로 널려 있으므로 어렵지 않게 원하는 자료를 구할 수 있을 것이다.

  참조 : http://www.winapi.co.kr/project/library/gdiplus/gdiplus.htm button1의click이벤트시의 코드와 form1의 paint이벤트의 코드가 있다.

Image는 form1 클래스의 처음 부분에서 이미 생성되었고, button1과 form1의 각 이벤트에 따라서 다르게 나타난다.

Graphics클래스는 직접 객체를 생성할수없는 클래스이기 때문에 Paint 가아닌 이벤트에서는정적메소드인 CreateGraphics()를 이용해서 생성을 해줘야한다.

     public partial class Form1 : Form    {        Image img = Image.FromFile("F:imagelena.png");        public Form1()        {            InitializeComponent();        }        private void button1_Click(object sender, EventArgs e)        {            Graphics g = this.CreateGraphics();            g.DrawImage(img, 10, 10, img.Width, img.Height);            g.Dispose();        }        private void Form1_Paint(object sender, PaintEventArgs e)        {            e.Graphics.DrawImage(img, 10, 10, img.Width, img.Height);        }    }직접 실행해보면 알겠지만 button1의 click 이벤트를 통해서 그려진 이미지는 폼이 화면에 표시되는 상태가 달라지면그려진 이미지가 없어진다.

이는 form에 직접 DrawImage를 이용해서 그린 것이기 때문에 from이 변경되면Paint 이벤트에서 처리해주어야한다.

이때 Form1_Paint 이벤트를 위와 같이 처리해주면 form이 변경되더라도 이미지는 그대로 표시된다.

Graphics 클래스에대한 자세한 정보는 msdn을 참조하면된다.

 또한 FromFile() 메소드로 사용된 Image 객체는 리소스해제를위해서 프로그램을 종료할때 Dispose 해주는것이 좋다.

 #DrawImage g.DrawImage(img, 10, 10, img.Width/2, img.Height/2);위의 코드를 해석해보면 img객체를 form의 x=10, y=10 위치에서 시작해서 img의 가로세로 크기의 절반으로 출력하라는 것이다.

여기서 img.Width/2 와 img.Height/2 로서 원본 이미지의 절반으로 축소되어 나타나게된다.

 DrawImage는 오버로딩이 상당히 많이 되어있는데 그 중 하나로 이미지의 일부분만을 clip하여 표시하는 방법은 아래와같다.

아래 코드는 form 의 (10,10)위치에서 img객체의 (70,70)부터 시작하여 가로세로 240크기만큼만 draw하게된다.

g.DrawImage(img, 10, 10, new Rectangle(70, 70, 240, 240), GraphicsUnit.Pixel); <이미지파일의 일부만을 draw한 상태>  #ImageAttributes이 클래스는 픽셀을 조정해서 이미지의 색을 변경시키는데 사용된다.

상당히 유용한 클래스이며 대부분 ColorMatrix 와 함께 사용한다.

ColorMatrix 는 5x5 행렬을 생성하여 ImageAttributes를 통해 이미지의 픽셀에 연산을 수행할수있도록한다.

이 두 클래스를 사용하면 직접 code 상에서 픽셀을 처리해야하는 부분을 간편하고 빠르게 수행할수있다.

본격적인 영상처리를 배우기전에 일단 아래와 같이 사용한다는것 정도만알면 충분하다.

ColorMatrix colorMatrix = new ColorMatrix( new float[][]       {         new float[] {.3f, .3f, .3f, 0, 0},         new float[] {.59f, .59f, .59f, 0, 0},         new float[] {.11f, .11f, .11f, 0, 0},         new float[] {0, 0, 0, 1, 0},         new float[] {0, 0, 0, 0, 1}      });   ImageAttributes attributes = new ImageAttributes();   attributes.SetColorMatrix(colorMatrix);#C#에서의 영상처리방법c#으로 영상처리를 하려면 크게 3가지 방법이 있다.

 첫번째로 닷넷프레임워크가 제공하는 GDI+ 엔진을 이용, SetPixel, GetPixel 등의 메소드를 이용하는것이다.

이미지의 가로 세로만큼을 반복문을 사용해서 두 메소드를 사용해서 처리하는 방식으로 이해하기도 쉽고어떻게보면 가장 간단한 방법이다.

 두번째로 위에서 말한 ImageAttributes를 이용하여 ColorMatrix 로 DrawImage 메소드에 인수로 넘겨주는 방법이다.

이것 역시 GDI+ 를 이용하는것으로 볼수있으나 첫번째 방법은 Bitmap 을 사용해서 메모리에 올려진 이미지의 픽셀들을메소드를 사용해서 접근하는 것이지만 ImageAttributes는 Graphics 를 사용하여 이미지를 그릴때 처리해준다는 점에서 다르다.

 세번째로는 '안전하지않은 코드' 즉 unsafe 구문을 사용해서 직접 메모리로 이미지를 불러온다음 LockBits와 같은 방법을 사용하는것이다.

이 방법은 직접 바이트단위로 이미지를 접근하는것 빼고는 첫번째 방법과 거의 비슷한 방식으로 처리되지만속도면에서 첫번째 방법보다는 좋다고 알려져있다.

실제로도 테스트를 해보면 어느정도의 차이가 나게된다.

 일반적으로 순서대로 첫번째, 두번째, 세번째 순으로 자주 사용된다.

   GDI+ 소개GDI(Graphic User Interface)는 윈도우즈의 핵심 모듈 중의 하나로 출력과 관련된 기능을 담당한다.

화면, 프린터 등의 출력 하드웨어와 응용 프로그램의 중간에 위치하여 장치 독립성을 확보하며 복수 개의 프로그램이 서로의 영역내에서 방해하지 않고 자유롭게 출력하도록 조율하는 것이 주된 기능이다.

응용 프로그램은 하드웨어를 직접 액세스할 수 없으며 반드시 GDI를 통해야만 한다.

이 강좌를 읽을 정도면 Win32의 기본적인 구조에 대해서는 잘 알고 있을 것이므로 GDI에 대해서는 이미 익숙할 것이다.

만약 그렇지 않다면 별도의 강좌를 통해 Win32의 기본에 대해 먼저 공부할 필요가 있다.

GDI를 모른 상태에서 그 상위 버전인 GDI+를 공부할 수는 없다.

GDI는 그래픽 환경의 출력 엔진으로써 오랫동안 충실히 제 역할을 해 왔지만 현대적인 프로그램의 복잡한 요구를 충족시키기에는 다소 부족한 점이 많다.

 ① 기본적인 그래픽 출력은 가능하지만 섬세한 그래픽 표현에는 다소 역부족이다.

예를 들어 점선 펜을 만들 수는 있지만 굵기가 2이상이면 무조건 실선이 되어 버리므로 굵은 점선을 그을 수 없고 NT 이하에서는 비트맵 브러시도 8*8 이상의 크기를 지원하지 않아 큰 비트맵 브러시를 쓸 수 없다.

② 그래픽 속성을 바꿀 때마다 GDI 오브젝트를 일일이 생성, 선택한 후 사용해야 하므로 무척 번거롭다.

빨간색 테두리에 파란색 면을 가지는 타원을 하나 그리려면 Ellipse를 호출하기 전에 펜과 브러시를 미리 생성하여 DC에 선택해야 하며 그린 후에도 선택 해제, 파괴의 뒷처리를 반드시 해야 한다.

정작 중요한 출력문보다 준비하고 정리하는 코드가 더 많아 무척 불편하다.

③ 실수로 GDI 오브젝트를 해제하지 않을 경우 리소스 누출에 의해 시스템의 안정성을 위협하기도 한다.

비트맵같은 큰 개체를 해제하지 않으면 많은 리소스를 소모하여 더 이상 그리기를 할 수 없는 상태가 되기도 하는데 이는 시스템 다운에 버금갈 정도로 치명적이다.

 GDI의 단점을 요약하자면 기능은 부족하고 쓰기도 불편하며 안전하지도 않다는 것이다.

GDI를 최초 디자인했던 20년 전에는 하드웨어 성능이 좋지 못해 화려한 기능보다는 단순하고 빠른 동작이 더 중요했었다.

그러나 지금은 하드웨어 환경이 개선되고 사용자의 요구가 늘어남에 따라 좀 더 강력한 출력 엔진이 필요해졌다.

GDI+는 전통적인 GDI 모듈의 업그레이드 버전으로서 복잡하고 섬세한 그래픽을 출력할 수 있고 기존 기능을 최적화한 새로운 출력 모듈이다.

GDI의 계승자이므로 장치 독립성을 제공한다는 기본 목적은 동일하며 GDI로 할 수 있는 대부분의 작업을 GDI+로도 할 수 있다.

윈도우즈 XP와 비스타에 기본적으로 탑재되어 있으며 닷넷 플랫폼에도 포함되어 있고 64비트 윈도우즈에서도 계속 지원되므로 향후 GDI를 완전히 대체하게 될 것이다.

2000을 포함하여 2000 이하의 버전에서는 별도의 모듈을 배포해야만 사용할 수 있지만 GdiPlus.dll 파일 하나만 복사하면 되므로 하위 호환성의 문제도 거의 없는 셈이다.

95/98 이후의 NT/2000에서 GDI도 투명 비트맵 출력, 좌표 변환, 반투명 출력 등 많은 기능 개선이 이루어졌지만 이런 기능은 사실 있어도 마음대로 사용할 수 없었다.

왜냐하면 추가된 함수를 하나라도 사용하게 되면 95/98에서 이 프로그램은 제대로 실행되지 않기 때문이다.

이렇게 만든 프로그램을 95/98 사용자가 쓸 수 있는 유일한 방법은 운영체제를 업그레이드하는 것 뿐이므로 시장을 포기하지 않는 한 이런 함수를 쓸 수 없었다.

그러나 GDI+는 DLL을 같이 복사하면 하위 버전의 운영체제에서도 문제없이 잘 실행되므로 호환성을 걱정할 필요가 없다.

95/98 환경이라도 DLL 파일을 같이 배포하기만 하면 되므로 용량이 약간 늘어난다는 것 외에는 별다른 번거로움이 없는 것이다.

DLL의 버전 충돌을 피하기 위해서 DLL을 재배포할 때는 가급적이면 시스템 디렉토리보다 응용 프로그램이 설치되는 디렉토리에 같이 복사하는 것이 안전하며 마이크로소프트는 이런 배포 방식을 권장하고 있다.

GDI+를 사용하기 위해 특별한 준비를 할 필요는 없다.

비주얼 스튜디오 2005 이상의 버전에는 GDI+ 개발에 필요한 SDK와 상세한 도움말이 제공되므로 컴파일러만 설치하면 GDI+를 바로 사용할 수 있다.

XP 이후에 GDI+가 기본 출력 엔진 역할을 하므로 컴파일러가 SDK를 제공하는 것이 당연하다.

이 강좌는 비스타 환경에서 비주얼 스튜디오 2008을 기준으로 하므로 이 환경을 갖추었다면 아무 것도 준비할 필요가 없다.

그러나 이전 버전에서는 약간의 준비를 해야 한다.

현재 2001이나 2003은 사용자가 거의 없고 VC 6.0 사용자가 일부 남아 있는데 VC 6.0 사용자는 별도로 SDK를 설치해야 한다.

VC 60용으로 발표된 가장 최신 SDK인 2003년 2월 플랫폼 SDK를 다운로드 받아 설치하고 컴파일러의 디렉토리 옵션창에서 Include 경로와 Library 경로를 추가하면 된다.

최신 SDK 정보를 가장 먼저 참조하도록 목록의 제일 위쪽으로 이동시켜야 한다.

최신 MSDN에는 GDI+에 대한 문서가 포함되어 있다.

친절한 자습서는 물론이고 컴파일해 볼만한 예제와 각 클래스의 멤버에 대한 정보, 함수에 대한 도움말, 고급 기법에 대한 문서 등이 수록되어 있으므로 이 도움말만 순서대로 읽어 봐도 GDI+는 쉽게 정복할 수 있다.

물론 영어로 되어 있다.

플랫폼 SDK와 함께 설치되는 도움말에도 GDI+에 대한 도움말이 제공된다.

GDI+는 최신 라이브러리인만큼 객체 지향적인 C++언어로 작성되어 있다.

그래서 C 컴파일러에서는 이 라이브러리를 사용할 수 없다는 문제점이 있기는 하지만 요즘의 컴파일러들은 대부분 C++언어를 잘 지원하므로 개발툴의 제약도 거의 없는 셈이다.

GDI+는 GDI의 기능을 개선한 클래스 라이브러리이므로 일단 GDI에 대해서 잘 알고 있어야 하며 C++ 언어의 기본적인 사용 방법에 대해서도 충분히 숙지하고 있어야 한다.

GDI+는 기초적인 C++ 문법만을 요구할 뿐이며 해박한 객체 지향 이론을 요구하는 것은 아니다.

상속이나 가상 함수, 템플리트 같은 고급 기법까지는 모르더라도 객체안에 속성과 함수가 캡슐화되어 있다는 것과 객체가 생성 파괴될 때 자동으로 호출되는 함수가 있다는 것 정도만 알아도 GDI+를 배우고 사용할 수 있다.

GDI+를 공부해 보면 왜 C++이 좋은가를 실감할 수 있을 것이다.

나.초기화

어떤 라이브러리든지 사용하려면 약간의 초기화 과정을 거쳐야 한다.

몇 가지 함수만 시기 적절하게 호출하면 되므로 아주 간단하다.

예제 수준에서는 복잡한 UI나 특별한 기능이 필요치 않으므로 Win32 프로젝트로 첫 예제를 만들어 보자. 비주얼 스튜디오 2008을 실행하고 새 프로젝트 명령으로 프로젝트를 생성한다.

예제 작성용으로 GpExam2008이라는 이름의 디렉토리를 미리 생성해 놓고 이 디렉토리안에 GdiPlusStart라는 이름으로 새 프로젝트를 만들었다.

빈 프로젝트 옵션을 선택하고 GdiPlusStart.cpp 파일을 추가한 후 다음 소스를 작성한다.

  예 제 : GdiPlusStart#include <windows.h> LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);HINSTANCE g_hInst;HWND hWndMain;LPCTSTR lpszClass=TEXT("GdiPlusStart"); #include <gdiplus.h>using namespace Gdiplus;#pragma comment(lib, "gdiplus") int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance       ,LPSTR lpszCmdParam,int nCmdShow){     HWND hWnd;     MSG Message;     WNDCLASS WndClass;     g_hInst=hInstance;      ULONG_PTR gpToken;     GdiplusStartupInput gpsi;     if (GdiplusStartup(&gpToken,&gpsi,NULL) != Ok) {          MessageBox(NULL,TEXT("GDI+ 라이브러리를 초기화할 수 없습니다.

"),              TEXT("알림"),MB_OK);          return 0;     }      WndClass.cbClsExtra=0;     WndClass.cbWndExtra=0;     WndClass.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);     WndClass.hCursor=LoadCursor(NULL,IDC_ARROW);     WndClass.hIcon=LoadIcon(NULL,IDI_APPLICATION);     WndClass.hInstance=hInstance;     WndClass.lpfnWndProc=WndProc;     WndClass.lpszClassName=lpszClass;     WndClass.lpszMenuName=NULL;     WndClass.style=CS_HREDRAW | CS_VREDRAW;     RegisterClass(&WndClass);      hWnd=CreateWindow(lpszClass,lpszClass,WS_OVERLAPPEDWINDOW,          CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,          NULL,(HMENU)NULL,hInstance,NULL);     ShowWindow(hWnd,nCmdShow);      while (GetMessage(&Message,NULL,0,0)) {          TranslateMessage(&Message);          DispatchMessage(&Message);     }     GdiplusShutdown(gpToken);     return (int)Message.wParam;} void OnPaint(HDC hdc){     Graphics G(hdc);     Pen P(Color(255,0,0,255),5);      G.DrawEllipse(&P,10,10,300,200);} LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam){     HDC hdc;     PAINTSTRUCT ps;      switch (iMessage) {     case WM_CREATE:          hWndMain=hWnd;          return 0;     case WM_PAINT:          hdc=BeginPaint(hWnd, &ps);          OnPaint(hdc);          EndPaint(hWnd, &ps);          return 0;     case WM_DESTROY:          PostQuitMessage(0);          return 0;     }     return(DefWindowProc(hWnd,iMessage,wParam,lParam));} 첫 번째 예제들이 늘상 그렇듯이 특별한 동작은 없고 굵기 5의 파란색 펜으로 타원만 하나 그려 보았다.

실행 결과는 다음과 같다.

전형적인 Win32 시작 소스에 GDI+와 관련된 추가 코드가 조금 더 작성되어 있다.

 ① gdiplus.h 헤더 파일을 인클루드하고 있는데 이 헤더 파일에 GDI+의 모든 타입, 열거형, 클래스 등이 선언되어 있다.

이 헤더 파일을 열어 보면 GDI+의 기본적인 선언문들을 직접 구경할 수 있는데 gdiplus.h는 GDI+의 모든 헤더를 포함하는 헤더 파일 컨테이너라고 할 수 있다.

② GDI+의 모든 명칭은 GdiPlus 네임 스페이스에 포함되어 있다.

따라서 클래스 이름과 열거형 이름앞에 GdiPlus::을 일일이 붙여야 하는데 너무 번거로우므로 using 지시자를 사용하여 GdiPlus 네임 스페이스에 선언된 모든 명칭을 전역 영역으로 가져 오도록 한다.

이 지시자에 의해 컴파일러는 전역 네임 영역에 없는 명칭에 대해 GdiPlus 네임 스페이스를 검색하므로 이름만으로 GDI+의 명칭들을 사용할 수 있다.

③ GDI+의 실제 코드는 gdiplus.dll에 정의되어 있으므로 이 라이브러리를 링크해야 한다.

원칙적으로는 프로젝트 설정 대화상자의 링크/ 페이지의 추가 종속성란에 gdiplus.lib 임포트 라이브러리를 지정해야 하나 매 실습마다 대화상자를 열기 귀찮으므로 소스에서 링크하는 것이 편리하다.

 #pragma comment(lib, "gdiplus") 라이브러리 모듈에게 gdiplus.lib 임포트 라이브러리를 참조하라고 알려 준다.

이렇게 선언문을 작성해 놓으면 새 소스를 만들 때도 붙여 넣기만 하면 되므로 실습하기 편리하다.

④ 프로그램 시작시 GDI+ 라이브러리를 초기화한다.

이때는 다음 전역 함수를 사용한다.

GDI+는 모든 것이 클래스로 되어 있지만 초기화와 관련된 함수는 클래스에 소속되지 않으며 일반 함수로 제공된다.

객체를 아직 만들기 전이므로 초기화 함수는 일반 함수 또는 정적 함수일 수밖에 없다.

 Status GdiplusStartup(ULONG_PTR *token, const GdiplusStartupInput *input, GdiplusStartupOutput *output); token은 초기화시 받는 토큰이며 종료할 때 이 토큰을 GdiplusShutdown으로 전달해야 한다.

input은 옵션 구조체이며 이 안에 GDI+의 버전, 디버그 콜백 함수, 백그라운드 스레드 허용 여부 등을 지정할 수 있는데 별 다른 옵션을 지정하지 않을 경우는 변수만 선언해서 그 포인터를 넘겨 주면 된다.

예제에서는 gpsi 구조체를 선언한 후 넘기기만 했다.

도움말에서 확인해 보면 알겠지만 이 구조체의 생성자가 무난한 디폴트로 초기화하도록 되어 있으므로 지역 변수라 하더라도 쓰레기값이 들어가지는 않는다.

생략할 수 없으므로 반드시 변수를 선언한 후 그 포인터를 전달해야 한다.

output은 초기화 결과를 돌려 주기 위한 구조체인데 필요없을 경우 NULL로 줄 수 있다.

백그라운드 스레드를 허용하지 않을 때만 output 인수가 필요하다.

이 함수는 초기화 결과를 Status형의 열거형으로 리턴하는데 성공했을 경우 Ok가 리턴된다.

Ok가 아닌 경우는 GDI+를 사용할 수 없으므로 적절한 에러 처리를 할 필요가 있다.

예제에서는 메시지 박스를 출력하고 프로그램을 종료했다.

⑤ 프로그램을 끝낼 때 다음 함수를 호출하여 GDI+ 라이브러리를 셧다운한다.

GDI+는 실행중에 많은 리소스를 할당하는데 이 리소스를 반드시 해제해야 한다.

 void GdiplusShutdown(ULONG_PTR token); 스타트업할 때 발급받았던 토큰을 그대로 전달하되 그 전에 사용중인 모든 GDI+ 객체들을 닫아야 한다.

이 두 함수는 보통 쌍으로 사용되며 프로그램 시작시, 종료시에 각각 호출한다.

메인 윈도우의 WM_CREATE, WM_DESTROY에서 처리할 수도 있지만 응용 프로그램 전역적인 초기화이므로 WinMain에서 처리하는 것이 더 적당하다.

WndProc에는 별다른 코드가 없으며 WM_PAINT 메시지를 받았을 때 OnPaint 함수만 호출한다.

OnPaint에서는 파란색 펜으로 굵기 5의 타원을 그렸다.

이후 실습에서 OnPaint에 GDI+ 그리기 코드를 작성하면서 실습을 진행하기 바란다.

아직 GDI+ 사용법을 배울 단계는 아니므로 OnPaint의 코드는 일단 무시하기로 하자.

다.


MFC에서의 초기화다음은 MFC 프로젝트에서 GDI+를 사용하는 방법에 대해 알아보자. Win32 API 프로젝트와 실질적인 내용상의 차이는 없지만 MFC의 독특한 구조로 인해 코드를 작성하는 위치상의 차이가 있다.

API 프로젝트는 WinMain에 초기화, 정리 코드가 작성되지만 MFC는 WinMain이 라이브러리안에 숨겨져 있으므로 적당한 다른 시점을 찾아야 한다.

간단하게 예제를 만들어 보자.  예 제 : MfcGdiPlusMfcGdiPlus라는 이름으로 SDI 프로젝트를 만든다.

MDI나 대화상자 기반의 프로젝트로 만들어도 초기화 과정은 거의 비슷하다.

MFC 프로젝트는 기본적으로 미리 컴파일된 헤더(PCH) 기능을 사용하므로 StdAfx.h에 gdiplus.h 헤더 파일을 포함시켜야 한다.

그리고 네임 스페이스 선언과 라이브러리 임포트 선언문도 StdAfx.h에 같이 작성한다.

 #include <gdiplus.h>using namespace Gdiplus;#pragma comment(lib, "gdiplus") 다음은 초기화 토큰을 저장하기 위한 멤버 변수를 MfcGdiPlusApp 클래스에 선언한다.

 class CMfcGdiPlusApp : public CWinApp{public:     ....     ULONG_PTR m_gpToken; API 프로젝트에서는 WinMain 안에 초기화, 해제 코드가 모두 작성되므로 토큰 변수가 지역 변수여도 상관없지만 MFC 프로젝트는 두 시점을 처리하는 함수가 나누어져 있으므로 멤버로 선언해야 한다.

응용 프로그램을 초기화하는 InitInstance의 선두에서 GDI+ 라이브러리 초기화 함수를 호출한다.

 BOOL CMfcGdiPlusApp::InitInstance(){     AfxEnableControlContainer();      GdiplusStartupInput gpsi;     if (GdiplusStartup(&m_gpToken,&gpsi,NULL) != Ok) {          AfxMessageBox(TEXT("GDI+ 라이브러리를 초기화할 수 없습니다.

"));          return FALSE;     }     .... 초기화에 실패하면 FALSE를 리턴하여 응용 프로그램을 아예 시작하지 않도록 했다.

해제할 시점은 응용 프로그램이 종료되는 ExitInstance 함수이다.

AppWizard로 만든 프로젝트에는 이 함수가 정의되어 있지 않으므로 클래스 뷰의 속성창에서 ExitInstance 가상 함수를 재정의한 후 셧다운 코드를 작성한다.

 int CMfcGdiPlusApp::ExitInstance() {     GdiplusShutdown(m_gpToken);          return CWinApp::ExitInstance();} MFC의 프레임워크가 요구하는 바대로 초기화 및 해제를 했다.

라이브러리가 초기화되면 이제 GDI+를 사용할 수 있다.

뷰의 OnDraw에 간단한 테스트 코드를 작성해 보자. void CMfcGdiPlusView::OnDraw(CDC* pDC){     CMfcGdiPlusDoc* pDoc = GetDocument();     ASSERT_VALID(pDoc);     if (!pDoc)          return;      Graphics G(pDC->m_hDC);     Pen P(Color(255,0,0,255),5);      G.DrawEllipse(&P,10,10,300,200);} 앞 예제와 똑같은 모양의 타원을 그려 보았는데 사용하는 방법도 동일하고 실행 결과도 물론 동일하다.

MFC가 특별히 GDI+를 위한 지원 클래스를 제공하는 것도 아니고 GDI+도 마찬가지로 MFC를 위한 별도의 배려를 하지 않는다.

따라서 MFC라고 해서 GDI+를 쓰는 방법이 특별히 편리하지도 않으며 불이익이 있는 것도 아니다.

하지만 클래스 단위로 응용 프로그램을 구성하다 보니 코드가 여기 저기 흩어져 Win32 프로젝트에 비해 잔손이 많이 가는 것은 솔직히 사실이다.

Win32 프로젝트는 복사해서 붙여 넣으면 그만이지만 MFC 프로젝트는 멤버 선언하고 함수 만들고 신경쓸 게 많다.

이럴 때는 좀 더 쉬운 방법이 있는데 바로 클래스의 생성자와 파괴자를 활용하는 것이다.

이런 기법은 C++ 문법서에도 일반적으로 많이 소개된 것이라 친숙할 것이다.

  예 제 : GdiPlusStarter#include <windows.h> LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);HINSTANCE g_hInst;HWND hWndMain;LPCTSTR lpszClass=TEXT("GdiPlusStarter"); #include <gdiplus.h>using namespace Gdiplus;#pragma comment(lib, "gdiplus")class CGdiPlusStarter{private:     ULONG_PTR m_gpToken; public:     bool m_bSuccess;     CGdiPlusStarter() {          GdiplusStartupInput gpsi;          m_bSuccess=(GdiplusStartup(&m_gpToken,&gpsi,NULL) == Ok);     }    

CGdiPlusStarter() {          GdiplusShutdown(m_gpToken);     }};CGdiPlusStarter g_gps; int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance       ,LPSTR lpszCmdParam,int nCmdShow){     HWND hWnd;     MSG Message;     WNDCLASS WndClass;     g_hInst=hInstance;      if (g_gps.m_bSuccess == FALSE) {          MessageBox(NULL,TEXT("GDI+ 라이브러리를 초기화할 수 없습니다.

"),              TEXT("알림"),MB_OK);          return 0;     }     ....} void OnPaint(HDC hdc){     Graphics G(hdc);     Pen P(Color(255,0,0,255),5);      G.DrawEllipse(&P,10,10,300,200);} LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam){     ....}  WinMain 이전에 선언되어 있는 CGdiPlusStarter 클래스가 초기화 및 해제를 자동으로 수행한다.

토큰값은 클래스의 멤버 변수로 선언해 두었고 생성자에서 초기화 함수를 호출하고 파괴자에서 정리 함수를 호출했다.

이 클래스 타입의 전역 객체 g_gps를 선언해 놓기만 하면 모든 처리는 자동으로 수행된다.

전역 객체의 이름 따위는 별 의미가 없으며 단지 이 객체가 존재하기만 하면 된다.

전역 객체의 생성자는 WinMain보다 먼저 호출되므로 프로그램 시작 직후에 생성자에 의해 GDI+가 자동으로 초기화될 것이다.

초기화 토큰은 멤버 변수에 잘 보관해 놓는다.

프로그램이 종료되기 직전에 파괴자가 호출되며 저장된 토큰을 꺼내 해제 코드를 수행한다.

자동으로 호출되는 생성자와 파괴자의 특성을 잘 활용한 예라고 할 수 있다.

초기화 성공 여부는 m_bSuccess 멤버 변수에 저장한다.

생성자는 리턴값을 가질 수 없으므로 외부에서 초기화 결과를 알고 싶다면 m_bSuccess 멤버 변수를 참조하면 된다.

위 예제에서는 WinMain 선두에 성공 여부를 점검하는 코드를 작성해 놓았는데 예제 수준에서는 꼭 필요한 처리가 아니므로 생략해도 큰 문제는 없다.

이렇게 초기화 클래스를 잘 만들어 놓으면 Win32에서나 MFC에서나 클래스 선언문과 전역 객체 선언문을 붙여 넣기만 하면 된다.

단, MFC처럼 미리 컴파일된 헤더 기능을 쓰는 경우는 헤더 파일 인클루드문만 StdAfx.h로 옮기면 된다.

이후의 예제들은 이 클래스를 활용하여 GDI+를 초기화할 것이다.

초기화, 해제 등의 처리는 어떤 프로젝트에서나 동일하므로 앞으로 WinMain의 코드는 생략하기로 한다.

라.닷넷

GDI+를 활용할 수 있는 또 다른 환경은 닷넷이다.

사실 GDI+를 만든 주된 이유가 바로 닷넷 때문이라고 할 수 있다.

사운을 걸고 만들었다는 닷넷의 출력 엔진으로 20년 전의 구형 GDI를 쓴다는 것은 어울리지 않아 최신 운영 환경에 맞게 새로 만든 고품질의 출력 엔진이 바로 GDI+인 것이다.

C#은 물론이고 닷넷을 지원하는 모든 언어에서 GDI+를 사용할 수 있다.

닷넷은 플랫폼 독립성을 확보하기 위해 IL이라는 중간 언어 방식으로 동작한다.

그러나 기본 출력 엔진인 GDI+ 자체는 네이티브 C++로 만들었는데 왜냐하면 IL로는 제대로된 출력 속도를 낼 수 없기 때문이다.

그래픽 엔진이 가상 코드로 동작하면 너무 느리기 때문에 출력 엔진만큼은 가상 코드를 쓰지 않는다.

닷넷은 C++ DLL 형태의 GdiPlus.dll을 인테롭이라는 기법으로 호출하는 구조이다.

덕분에 C++ 프로젝트에서도 GdiPlus를 사용할 수 있는 것이다.

닷넷에서 GDI+는 기본 출력 엔진이기 때문에 별도의 초기화 과정이 필요치 않다.

닷넷 프로젝트를 만들기만 하면 GDI+를 언제든지 사용할 수 있다.

지금 사용하고 있는 VS 2008로 닷넷 예제도 만들 수 있으므로 간단하게 실습을 해 보자. 새 프로젝트를 시작하고 프로젝트 형식에서 Visual C#의 Windows를 선택한다.

오른쪽 템플릿에서 "Windows Forms 응용 프로그램" 항목을 선택하고 프로젝트 이름은 dotNet으로 준다.

확인 버튼을 누르면 별다른 질문없이 프로젝트를 생성하고 빈 폼 하나를 열어 줄 것이다.

이 폼에 컨트롤을 올려 폼을 디자인하고 이벤트 핸들러를 작성하여 동작을 정의한다.

여기서는 단순히 출력만 해 볼 것이므로 컨트롤은 배치할 필요가 없고 폼의 Paint 이벤트만 처리해 보자. Paint는 폼이 무효화될 때마다 전달되며 Win32의 WM_PAINT 메시지에 대응되는 이벤트이다.

속성창의 이벤트 페이지에서 Paint 항목을 더블 클릭하면 이벤트 핸들러가 생성되고 코드창이 열린다.

폼이 이벤트를 받는데 필요한 모든 코드가 같이 작성되므로 개발자는 핸들러 내부의 본체에 원하는 코드만 작성하면 된다.

MFC와 비슷한 방식이며 잡스러운 처리들은 개발툴이 알아서 해 주므로 생산성이 훨씬 더 높다.

빈 핸들러에 다음 코드를 작성해 보자. private void Form1_Paint(object sender, PaintEventArgs e){     Pen P = new Pen(Color.Blue,5);     e.Graphics.DrawEllipse(P, 10, 10, 300, 200); } Paint 이벤트의 인수 e 안에 Graphics의 객체가 전달되므로 Graphics 객체를 따로 만들 필요가 없다.

닷넷 프레임워크가 Graphics의 객체를 미리 만들어 주므로 이벤트 핸들러에서는 전달된 객체의 메소드를 호출하기만 하면 된다.

앞 예제와 마찬가지로 굵기 5의 파란색 펜으로 타원을 그렸다.

실행 결과는 물론 동일하다.

네이티브 C++에서나 닷넷에서나 GDI+를 프로그래밍하는 방법은 거의 동일하다.

어차피 같은 DLL을 사용하는 것이므로 클래스의 메소드 이름이나 호출하는 방법 등에서는 차이가 없다.

다만 언어적인 문법 차이에 의해 호출문이나 인수의 형태가 조금 다르기는 한데 이런 차이점은 두 언어의 문법 구조를 알게 되면 자연스럽게 이해될 것이다.

또 닷넷은 GDI+를 래핑하여 사용하므로 원래 GdiPlus.dll에는 없는 유틸리티 클래스들이 추가로 제공되는 이점이 있다.

위 코드에서 파란색을 의미하는 Color.Blue라는 표현이 대표적인 예인데 프로피터라는 문법으로 자주 사용하는 표준 색상을 미리 정의해 놓아 색상을 사용하기가 훨씬 더 용이하다.

이 점에 대해서는 다음에 조금 더 깊이 있게 연구해 보도록 하자.이 강좌는 C++ 언어를 중심으로 쓰여졌으므로 이후부터 C++ 컴파일러로 예제를 만들 것이다.

두 언어로 예제를 모두 만들어 보면 좋겠지만 강좌가 난잡해지는 경향이 있어 C++에만 집중하기로 한다.

닷넷용의 예제가 필요하다면 C# 언어 입문서를 참조하기 바란다.

시중에 닷넷 관련 서적은 그야 말로 널려 있으므로 어렵지 않게 원하는 자료를 구할 수 있을 것이다.

  참조 : http://www.winapi.co.kr/project/library/gdiplus/gdiplus.htm
공유하기 링크
TAG
, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,
댓글
댓글쓰기 폼