8 장 윈도우에서의그리기 김성영교수 금오공과대학교 컴퓨터공학부
그래픽장치인터페이스객체 GDI 객체 객체의핸들타입 MFC 클래스속성 HPEN CPen 선속성 HBRUSH CBrush 채우기속성 HFONT CFont 글꼴속성 HBITMAP CBitmap 비트맵영상속성 HPALETTE CPalette 팔레트속성 HRGN CRgn 영역속성 GDI 클래스는 CGdiObject 클래스로부터상속 GDI 객체의핸들을할당할수있는멤버변수를가짐 class CGdiObject : public CObject { HANDLE m_hobject; // GDI 객체에대한핸들 } 2
윈도우가 GDI 객체를만들어각객체의속성에기본값설정 선을그리는경우 시작점과끝점만제공하면선을그릴수있음 MoveTo( int x, int y ) // 지정한위치로이동 LineTo( int x, int y ) // 현재위치에서지정된위치까지선을그음 사각형을그리는경우 Rectangle( int x1, int y1, int x2, int y2 ) // 각진사각형 RoundRect( int x1, int y1, int x2, int y2, int x3, int y3 ) // 모서리가둥근사각형 3
CDC 클래스 - 디바이스컨텍스트 (Device Context, DC) 구조체 - HDC 는 DC 를가리키는핸들 4
class CDC : public CObject { HDC m_hdc; // DC에대한핸들 BOOL LineTo( int x, int y ); BOOL Rectangle( int x1, int y1, int x2, int y2 ); } 멤버변수 : HDC 멤버함수 : Rectangle() 등의그리기함수 CDC 클래스를이용한그리기 GDI 객체를가리키고있는 DC 를준비 CDC* pdc; pdc = GetDC(); // DC 요청 pdc->rectangle(0, 0, 100, 100); // 그리기관련함수 (GetDC와 ReleaseDC 사이 ) ReleaseDC(pDC); // DC 반환 CDC: http://msdn.microsoft.com/ko-kr/library/4acfw2ha(vs.80).aspx 5
그리기의전형적인형태 기본으로설정된속성값을바꾸고자할때기존에생성된객체의속성을바꾸는대신새로운객체생성 검은색선을회색으로바꾸려면.. 1 회색속성을가진펜객체생성 2 DC 는검은색펜객체를가리키던연결선을끊음 3 DC 는새로생성한회색펜객체를연결 6
SelectObject() 함수 - 2, 3 과정처리 - GDI 객체별로존재 - 기존 GDI 객체의주소값반환 CPen* SelectObject( CPen* ppen ); CBrush* SelectObject( CBrush* pbrush ); CFont* SelectObject( CFont* pfont ); CBitmap* SelectObject( CBitmap* pbitmap ); int SelectObject( CRgn* prgn ); 윈도우가생성해놓은 DC 값이바뀔수도있으므로 GetDC() 함수에의해할당된 DC 는독점적으로사용 사용이끝나면 ReleaseDC() 함수로사용하지않음을알림 7
출력작업이끝나면 DC 를이전상태로되돌림 - SelectObject() 함수를다시호출하여이전 GDI 객체복원 새로생성하여사용한객체는메모리에서제거 CDC* pdc; pdc = GetDC(); CPen* oldpen, pen; pen.createpen( PS_SOLID, 1, RGB(128,128,128) ); oldpen = pdc->selectobject( &pen ); pdc->rectangle( 0, 0, 100, 100 ); pdc->selectobject( oldpen ); pen.deleteobject( ); ReleaseDC( pdc ); // 회색펜생성 // 새로생성한회색펜선택 // 회색테두리를가진사각형그림 // 이전검은펜복구 // 생성한펜을제거 8
CClientDC 클래스를사용하여위코드를아래와같이대체가능 - 생성자에서 GetDC(), 소멸자에서 ReleaseDC() 호출 - 단, CClientDC 클래스는클라이언트영역만담당 CClientDC dc( this ); pen.createpen( PS_SOLID, 1, RGB(128,128,128) ); oldpen = dc. SelectObject( &pen ); dc.rectangle( 0, 0, 100, 100 ); dc.selectobject( oldpen ); pen.deleteobject( ); 9
실습 8.1 마우스클릭위치를중심으로스타일을갖는사각형을그려보자. 마우스클릭처리는 CMyView 클래스의 WM_LBUTTONDWN 메시지핸들러에서처리 10
화면출력준비 / 사각형그리기는 Rectangle() 함수이용 void CMyView::OnLButtonDown( UINT nflags, CPoint point ) { CDC* pdc; pdc = GetDC( ); // DC 준비 MyView.cpp CRect rt; rt.left = point.x - 50; rt.top = point.y - 50; rt.right = point.x + 50; rt.bottom = point.y + 50; pdc->rectangle( rt ); // 사각형그림 ReleaseDC(pDC); // DC 반환 } CView::OnLButtonDown( nflags, point ); 11
브러쉬객체생성은 CreateSolidBrush() 함수사용 MyView.cpp void CMyView::OnLButtonDown( UINT nflags, CPoint point ) { CPen pen, *oldpen; // 펜객체선언 pen.createpen( PS_SOLID, 5, RGB(0,0,255) ); // 파란펜생성 oldpen = pdc->selectobject( &pen ); // 파란펜선택 & 이전펜저장 CBrush br, *oldbr; // 브러시객체선언 br.createsolidbrush( RGB(255,0,0) ); // 빨간브러쉬생성 oldbr = pdc->selectobject( &br ); // 빨간브러쉬선택 & 이전브러쉬저장 pdc->rectangle(rt); } pdc->selectobject(oldpen); // 이전펜으로복귀 pdc->selectobject(oldbr); // 이전브러쉬로복귀 pen.deleteobject(); // 펜객체제거 br.deleteobject(); // 브러쉬객체제거 12
참고 자주사용하는 GDI 객체는미리생성하고상수형태로제공 BLACK_BRUSH, DKGRAY_BRUSH, GRAY_BRUSH LTGRAY_BRUSH, NULL_BRUSH, WHITE_BRUSH BLACK_PEN, NULL_PEN, WHITE_PEN virtual CGdiObject* SelectStockObject( int nindex ); 13
WM_PAINT 메시지 실습 8.2 [ 실습 8.1] 에이어서윈도우가다른윈도우에가려졌다가다시나타났을때사각형이지워지지않도록해보자. WM_LBUTTONDOWN 메시지핸들러코드를 OnDraw() 함수로이동 DC 는 OnDraw() 함수의인자에포함된것을사용 출력위치는 WM_LBUTTONDOWN 메시지핸들러의인자로넘어온좌표를 OnDraw() 에서사용 멤버변수로선언 14
class CMyView : public CView { private: CPoint m_point; // 마우스클릭위치저장 } CMyView::CMyView( ) { m_point = CPoint( -100, -100 ); // 화면에서보이지않도록초기화 } void CMyView::OnDraw( CDC* pdc ) { CMyDoc* pdoc = GetDocument( ); ASSERT_VALID( pdoc ); MyView.h MyView.cpp CRect rt; rt.left = m_point.x - 50; rt.top = m_point.y - 50; rt.right = m_point.x + 50; rt.bottom = m_point.y + 50; 15
CPen pen, *oldpen; pen.createpen( PS_SOLID, 5, RGB(0,0,255) ); oldpen = pdc->selectobject( &pen ); CBrush br, *oldbr; br.createsolidbrush( RGB(255,0,0) ); oldbr = pdc->selectobject( &br ); pdc->rectangle( rt ); pdc->selectobject( oldpen ); pdc->selectobject(o ldbr ); pen.deleteobject(); br.deleteobject(); } void CMyView::OnLButtonDown( UINT nflags, CPoint point ) { m_point = point; // 현재마우스위치저장 CView::OnLButtonDown( nflags, point ); } 16
프로그램실행후왼쪽버튼을눌러도반응없음 명시적으로 WM_PAINT 메시지를발생시키는 UpdateWindow() 함수호출 void CMyView::OnLButtonDown( UINT nflags, CPoint point ) { m_point = point; // 현재마우스위치저장 UpdateWindow( ); // 명시적으로 WM_PAINT 메시지발생 CView::OnLButtonDown( nflags, point ); } MyView.cpp 17
이전에그려졌던것과비교해서무효한영역이있을때만새로그림 무효화영역을임의로만들기위해서는 CWnd 클래스의 Invalidate() 또는 InvalidateRect() 함수를사용 void Invalidate( BOOL berase = TRUE ); void InvalidateRect( LPCRECT lprect, BOOL berase = TRUE ); MyView.cpp void CMyView::OnLButtonDown( UINT nflags, CPoint point ) { m_point = point; // 현재마우스위치저장 Invalidate(); // 윈도우영역을무효화시킴 UpdateWindow( ); // 명시적으로 WM_PAINT 메시지발생 ( 생략가능 ) CView::OnLButtonDown( nflags, point ); } 18
참고 클릭한모든마우스좌표에대한사각형을다시그리고자한다면? 집합클래스 (Collection Class) 사용 : 배열 (array), 리스트 (list), 맵 (map) CArray CByteArray CArray<CPoint, CPoint&> array; CByteArray array; CArray<CPoint, CPoint&> array; array.setsize( 5 ); for( int i=0; i<5; i++ ) { CPoint pt( i, i*5 ); array[i] = pt; } 19
비트맵 비트맵객체는픽셀단위로표현된영상을다룸 리소스편집기를이용해비트맵을생성및출력 실습 8.3 마우스클릭위치에문자열을출력하는프로그램을작성하자. 단, 출력할문자열은리소스로등록한것을사용하고도큐먼트 - 뷰구조에충실하게작성한다. 20
도큐먼트 - 뷰구조에따른코딩 리소스편집기에서문자열리소스추가 - 리소스뷰탭에서 String Table 을선택 - 문자열테이블의마지막빈칸을클릭 21
속성창 - 문자열의아이디 IDS_MYSTRING 입력 - Caption 리소스편집기문자열입니다 입력 22
도큐먼트 - 뷰구조 - 도큐먼트 데이터처리 - 뷰 출력담당 도큐먼트클래스에리소스에있는문자열을저장하기위한변수선언 class CMyDoc : public CDocument { public: CString m_strmsg; } MyView.h 리소스의문자열을읽어오는함수 CString::LoadString( UINT nid ); 23
도큐먼트 데이터처리 사용할초기데이터의로딩은도큐먼트가초기화될때하는것이적절 CMyDoc::OnNewDocument( ) 함수에서처리 BOOL CMyDoc::OnNewDocument( ) { if (!CDocument::OnNewDocument() ) return FALSE; MyDoc.cpp // TODO: add reinitialization code here // (SDI documents will reuse this document) m_strmsg.loadstring( IDS_MYSTRING ); // 리소스에서문자열을읽어옴 } return TRUE; 24
뷰 - 출력 마우스왼쪽버튼을누르면출력되도록 WM_LBUTTONDOWN 메시지핸들러에코드추가 GetDocument() 도큐먼트객체주소획득 void CMyView::OnLButtonDown( UINT nflags, CPoint point ) { CString strmsg; CMyDoc* pdoc; pdoc = GetDocument( ); strmsg = pdoc->m_strmsg; MyView.cpp CClientDC dc( this ); dc.textout( point.x, point.y, strmsg, strmsg.getlength() ); } CView::OnLButtonDown( nflags, point ); 25
실습 8.4 마우스클릭위치에다음그림과같은영상을출력해보자. 26
비트맵리소스편집 비트맵도문자열과같이하나의리소스 리소스편집기사용 리소스추가대화상자 - 리소스뷰탭의 My.rc 에서마우스오른쪽버튼을눌러컨텍스트메뉴를활성화 - 리소스추가 (Insert..) 항목클릭 27
비트맵을선택하고 New 버튼클릭 비트맵편집창 편집내용은 IDB_BITMAP1 로설정 28
비트맵클래스를통해비트맵리소스를읽어옴 BOOL LoadBitmap( UINT nidresource ) 비트맵영상은화면에보이지않게일반메모리에먼저옮겨진후, 화면메모리로한꺼번에복사하여빠르게출력 메모리로비트맵을선택하는방법은메모리 DC 이용 CBitmap bmp; bmp.loadbitmap( IDB_BITMAP1 ); CDC* pdc; // 화면 DC pdc = GetDC( ); CDC memdc; // 메모리 DC memdc.createcompatibledc( pdc ); // 화면 DC 와호환이되도록생성 memdc.selectobject( &bmp ); 29
메모리 DC 의형식은화면 DC 와호환되도록 CreateCompatibleDC() 함수를이용해생성 비트맵관련정보 BITMAP 구조체에저장 typedef struct tagbitmap { LONG bmtype; //0으로설정 LONG bmwidth; // 비트맵의픽셀단위가로크기 LONG bmheight // 비트맵의픽셀단위세로크기 LONG bmwidthbytes; // 비트맵의바이트단위가로크기 ( 짝수 ) WORD bmplanes; // 컬러판의수 WORD bmbitspixel; // 필셀당할당된비트수 LPVOID bmbits; // 영상이저장된시작주소 } BITMAP; 30
BITMAP 정보는비트맵클래스의 GetObject() 함수를통해얻어올수있다 CBitmap bmp; bmp.loadbitmap( IDB_BITMAP1 ); BITMAP bm; bmp.getobject( sizeof(bm), &bm ); // 비트맵영상정보얻기 int width, height; width = bm.bmwidth; height = bm.bmheight; 31
메모리 DC 의내용을화면 DC 로복사 BitBlt() void CMyView::OnLButtonDown(UINT nflags, CPoint point) { CBitmap bmp; bmp.loadbitmap( IDB_BITMAP1 ); MyView.cpp CDC* pdc; CDC memdc; pdc = GetDC( ); memdc.createcompatibledc( pdc ); // 복사를위해화면 DC 와호환되도록생성 memdc.selectobject( &bmp );// 메모리에그림 BITMAP bm; bmp.getobject( sizeof(bm), &bm ); // 비트맵관련정보획득 32
pdc->bitblt( point.x, // 출력가로위치 point.y, // 출력세로위치 bm.bmwidth, // 비트맵가로크기 bm.bmheight, // 비트맵세로크기 &memdc, // 원본 ( 메모리 ) DC의주소 0, // 복사시작할비트맵의가로좌표 0, // 복사시작할비트맵의세로좌표 SRCCOPY // 원본을그대로복사 ); memdc.deletedc( ); // 메모리 DC 해제 ReleaseDC( pdc ); // 화면 DC 해제 } CView::OnLButtonDown(nFlags, point); 33
실습 8.5 비트맵을다시작성하지않고비트맵을확대해보자. 34
비트맵크기변경출력 비트맵크기변화는메모리 DC 의내용을화면 DC 로복사하는과정에서 CDC 클래스의 StretchBlt() 함수를통해조정가능 BOOL StretchBlt( int x, // 출력가로위치 int y, // 출력세로위치 int nwidth, // 출력비트맵가로크기 int nheight, // 출력비트맵세로크기 CDC* psrcdc, // 원본 ( 메모리 ) DC의주소 int xsrc, // 복사시작할비트맵의가로좌표 int ysrc, // 복사시작할비트맵의세로좌표 int nsrcwidth, // 원본비트맵가로크기 int nsrcheight, // 원본비트맵세로크기 DWORD dwrop // 그리기모드 ); 35
메모리 DC 의내용을확대하여화면 DC 로복사 StretchBlt() void CMyView::OnLButtonDown(UINT nflags, CPoint point) { CBitmap bmp; bmp.loadbitmap( IDB_BITMAP1 ); MyView.cpp CDC* pdc; CDC memdc; pdc = GetDC( ); memdc.createcompatibledc( pdc ); // 복사를위해화면 DC 와호환되도록생성 memdc.selectobject( &bmp );// 메모리에그림 BITMAP bm; bmp.getobject( sizeof(bm), &bm ); // 비트맵관련정보획득 36
pdc->stretchblt( point.x, // 출력가로위치 point.y, // 출력세로위치 bm.bmwidth*3, // 출력비트맵가로크기 bm.bmheight*3, // 출력비트맵세로크기 &memdc, // 원본 ( 메모리 ) DC의주소 0, // 복사시작할비트맵의가로좌표 0, // 복사시작할비트맵의세로좌표 bm.bmwidth, // 원본비트맵가로크기 bm.bmheight, // 원본비트맵세로크기 SRCCOPY // 원본을그대로복사 ); memdc.deletedc( ); // 메모리 DC 해제 ReleaseDC( pdc ); // 화면 DC 해제 } CView::OnLButtonDown(nFlags, point); 37
화면맵핑모드 맵핑모드 논리단위 X출증가방향 Y축증가방향 MM_TEXT 픽셀 오른쪽 아래 MM_LOMETRIC 0.1 mm 오른쪽위 MM_HIMETRIC 0.01 mm 오른쪽위 MM_LOENGLISH 0.1 inch 오른쪽위 MM_HIENGLISH 0.01 inch 오른쪽위 MM_TWIPS 1 / 1440 inch 오른쪽위 WW_ISOTROPIC 지정가능선택가능선택가능 MM_ANISOTROPIC 지정가능선택가능선택가능 MM_TEXT: 기본설정모드 화면좌측상단이 (0,0) 이고오른쪽으로갈수록 x 축값이증가하고아래로갈수록 y 축값이증가 MM_LOMETIC ~ MM_TWIPS 물리장치의종류에관계없이물리적길이가항상일정하도록설계 MM_ISOTROPIC, MM_ANISOTROPIC 프로그래밍가능한매핑모드 38
MM_ANISOTROPIC 을사용한방법 1 CRect rect; GetClientRect( &rect ); CDC* pdc; pdc = GetDC( ); pdc->setmapmode( MM_ANISOTROPIC ); // 맵핑모드설정 // 논리단위설정 pdc->setwindowext( 300, 300 ); // 물리단위설정 pdc->setviewportext( rect.width(), rect.height() ); pdc->ellipse( 0, 0, 300, 300 ); pdc->rectangle( 50, 50, 250, 250 ); ReleaseDC( pdc ); 39
MM_ANISOTROPIC 을사용한방법 2 CDC* pdc; pdc = GetDC( ); pdc->setmapmode( MM_ANISOTROPIC ); // 맵핑모드설정 // 디스플레이크기의 1/3 크기로논리단위설정 pdc->setwindowext( xdisplay/3, ydisplay/3 ); // 디스플레이크기그대로물리단위설정 pdc->setviewportext( xdisplay, ydisplay ); SetMapMode() 맵핑모드설정 xdisplay 및 ydisplay 디스플레이해상도 SetWindowExt() 화면출력단위가되는논리단위설정 SetViewPortExt() 물리단위화면크기설정 40
디스플레이해상도를얻는방법 xdisplay = pdc->getdevicecaps( HORZRES ); ydisplay = pdc->getdevicecaps( VERTRES ); // 픽셀단위의가로크기 // 픽셀단위의세로크기 마우스를클릭했을때인자로넘어오는마우스좌표는물리단위인픽셀단위 물리단위를논리단위로변환하는 DPtoLP( ) 함수를사용해해결 (CDC 클래스의멤버함수 ) void DPtoLP(LPPOINT lppoints, int ncount = 1) const; // 변환할점이여러개인경우 void DPtoLP(LPRECT lprect) const; // RECT 좌표변환 void DPtoLP(LPSIZE lpsize) const; // SIZE 좌표변환 void LPtoDP(LPPOINT lppoints, int ncount = 1) const; void LPtoDP(LPRECT lprect) const; void LPtoDP(LPSIZE lpsize) const; 41
SetViewPortOrg(), SetWindowOrg() 함수를이용하면좌표계의원점과축증가방향변경가능 MM_TEXT 모드에서다음코드는아래와같이화면의중심이원점이되도록변경 pdc->setviewportorg( xdisplay/2, ydisplay/2 ); 물리적인원점및논리적인원점 : 화면중심 42
논리적인원점설정을추가하여중앙으로변경된물리적인원점이화면의 -1/2 : 논리적인원점이오른쪽하단 pdc->setviewportorg( xdisplay/2, ydisplay/2 ); pdc->setwindoworg( -xdisplay/2, -ydisplay/2 ); -y -x 43