제 13 장그래픽프로그래밍 13.1 베지어곡선 실습 13-1 알고리즘을통한베지어곡선그리기 실습 13-2 컨트롤포인트이동및베지어곡선 해상도설정하기
그래픽프로그래밍 베지어곡선 베지어곡선알고리즘은곡선을생성하는대표적이고기본이되는알고리즘이다. MFC 에서의베지어곡선함수 BOOL PolyBezier(const POINT* lppoints, int ncount); lppoints : 곡선을구성하는컨트롤포인트들의좌표값 ncount : 컨트롤포인트의개수 이함수의특징은컨트롤포인트의개수가 ( 곡선수 * 3 +1) 개가되어야만곡선을그릴수있다. (Cubic Bezier Curve)
그래픽프로그래밍 베지어곡선상의한좌표를계산하는알고리즘 3 3 입력값 : b0, b1,, bn E 그리고 t R r r 1 r 1 출력값 : bi ( t) (1 t) bi ( t) tbi 1 ( t) ( r 0,, n, i 0, n r) 0 위의식에서 bi ( t) b i 이고 bn ( t 0 ) 가곡선상의파라미터 t 의값을가진한점의좌표값이다.
그래픽프로그래밍 베지어커브상의 t 에해당하는점의좌표를계산하는함수 CPoint ComputeCurvePoint(int ndegree, CPoint AryCtrlPoint[], float fdelta) { CPoint faryctrlpoint[30]; float fdelta_t; fdelta_t = 1.0f - fdelta; for( int i = 0; i <= ndegree; i++ ) faryctrlpoint[i] = AryCtrlPoint[i]; } for( r = 1; r <= ndegree; r++) for( i= 0; i <=ndegree - r; i++ ) { faryctrlpoint[i].x = (int)(fdelta_t*(float)faryctrlpoint[i].x+ fdelta*(float)faryctrlpoint[i+1].x); faryctrlpoint[i].y = (int)(fdelta_t*(float)faryctrlpoint[i].y+ fdelta*(float)faryctrlpoint[i+1].y); } return faryctrlpoint[0];
그래픽프로그래밍 커브를구성하는모든점의좌표를생성하는알고리즘 이알고리즘은커브를구성하는점의개수를지정하고, t 의값을변화시켜앞에서만든함수를반복적으로호출함으로써커브상의점의좌표를계산한다.
그래픽프로그래밍 모든점의좌표를계산하는함수 void CreateCurvePoint(int ndegree, int npointnum, CPoint pointcontrol[], CPoint pointcurve[]) { float fdelta, t; fdelta = 1.0f/(float)(nPointNum); t = 0.0f; for( int i = 0; i <= npointnum; i++ ) { pointcurve[i] = ComputeCurvePoint(nDegree, pointcontrol, t); t = t+fdelta; } } 함수의인자 첫번째 : 베지어곡선의차수 ( 컨트롤포인트 1) 두번째 : 커브를구성하는점의개수 세번째 : 컨트롤포인트의좌표배열 네번째 : 커브를구성하는점의좌표가저장된배열
실습 13-1 알고리즘을통한베지어곡선그리기 위에서설명한베지어곡선알고리즘을사용하여, 곡선을그려볼것이다. 왼쪽마우스버튼을이용해컨트롤폴리곤을그리고, 오른쪽마우스버튼을통해컨트롤폴리곤을완성하는동시에커브를그리게할것이다. 메뉴와툴바에컨트롤폴리곤과곡선보기를만들어, 선을보였다가감추는기능을만들것이다.
실습 13-1 실행화면 컨트롤폴리곤감추기 베지어곡선감추기
실습 13-1 프로그램작성순서 1) SDI 기반의프로젝트를생성한다. 2) 메뉴와툴바를추가한다. 선보이기 / 감추기메뉴와툴바를추가한다. 3) 프로그램에서사용될멤버변수를추가한다. 프로그램의상태, 선의색상, 기타플래그변수들을추가한다. 변수의초기화및프로그램의초기상태를설정 4) 컨트롤폴리곤과베지어곡선을그리는함수를작성한다.
실습 13-1 5) 마우스이벤트에대한메시지핸들러함수를작성한다. 왼쪽마우스메시지핸들러함수작성. 마우스이동에대한메시지핸들러함수작성. 오른쪽마우스메시지핸들러함수작성. 6) 사용자인터페이스메시지를처리한다. 그리기모드로전환하는메뉴를처리한다. 컨트롤폴리곤을보이기 / 감추기하는메뉴를처리한다. 베지어곡선을보이기 / 감추기하는메뉴를처리한다. 7) 프로그램을실행시킨다.
실습 13-2 컨트롤포인트의이동및해상도설정 이번실습에서는 Practice13_1 프로그램을수정하여, 곡선의컨트롤포인트를마우스의드래깅으로이동하여곡선을업데이트하도록할것이고, 곡선을구성하는선들의개수를사용자인터페이스를통해지정하여곡선의해상도를조정해볼것이다.
실습 13-2 실행화면 해상도 8 로변경 해상도 32 로변경 컨트롤포인트이동
실습 13-2 프로그램작성순서 1) Project13_1 을연다. 2) 사용자인터페이스를추가한다. 컨트롤포인트이동메뉴와툴바를추가한다. 곡선의해상도조정메뉴를추가한다. 3) 곡선의해상도설정을위한함수를생성한다. 곡성의해상도를저장할멤버변수를추가한다. 해상도 8, 16, 32 로설정하는함수를만든다.
실습 13-2 4) 컨트롤포인트이동을위한마우스이벤트함수를수정한다. 컨트롤포인트선택및이동에필요한멤버변수를추가한다. 프로그램을이동모드를전환하는메뉴의메시지핸들러함수를생성한다. OnLButtonDown() 함수를수정한다. OnMouseMove() 함수를수정한다. OnLButtonUp() 함수를추가한다. 5) 프로그램을실행시킨다.
연습문제 13-1 실습프로젝트에다음의기능을추가한다. 컨트롤폴리곤의색상을변경하는기능 베지어곡선의색상을변경하는기능 요구사항 메뉴와툴바를생성한다. 색상공용상자를이용한다. 대화상자의 [ 확인 ] 버튼을누르면색상이바로전환된다.
연습문제 13-1 실행결과 색상변경하기전화면 색상변경하기후화면
CPractice13_1View::CPractice13_1View() m_nctrlpointnum = 0; m_colorpolygon = RGB(200, 200, 200); // 컨트롤폴리곤의색상 - 밝은회색 m_colorcurve = RGB(255, 0, 0);// 베이지곡선의색상설정 - 빨강색 m_colorpoint = RGB(0, 0, 255);// 컨트롤포인트의색상 - 파란색 m_bstart = FALSE; m_nmode = ID_MODE_NONE;// 프로그램모드를초기상태로설정 m_bvisiblepolygon = TRUE;// 컨트롤폴리곤이보이도록설정 m_bvisiblecurve = TRUE;// 베지어곡선이보이도록설정 for( int i = 0; i < 50; i++ )// 컨트롤포인트의좌표배열초기화 m_ptcontrol[i] = 0; m_nresolution = 16; m_bselectedpoint = FALSE;
void CPractice13_1View::OnDraw(CDC* pdc) CPractice13_1Doc* pdoc = GetDocument(); ASSERT_VALID(pDoc); if (!pdoc) return; // TODO: 여기에원시데이터에대한그리기코드를추가합니다. DrawControlPolygon(pDC, m_bvisiblepolygon); DrawBezierCurve(pDC, m_bvisiblecurve);
void CPractice13_1View::OnRButtonUp(UINT /* nflags */, CPoint point) void CPractice13_1View::OnRButtonUp(UINT /* nflags */, CPoint point) ClientToScreen(&point); OnContextMenu(this, point);
void CPractice13_1View::DrawControlPolygon(CDC* pdc, bool bdrawline) CRect rectpoint; CPen CtrlPointPen, PolygonPen, *OldPen; CtrlPointPen.CreatePen(PS_SOLID, 3, m_colorpoint); PolygonPen.CreatePen(PS_SOLID, 1, m_colorpolygon); OldPen = pdc->selectobject(&ctrlpointpen); if( bdrawline ) for( int i = 0; i < m_nctrlpointnum; i++ ) rectpoint = CRect(m_ptControl[i].x-4, m_ptcontrol[i].y-4, m_ptcontrol[i].x+4, m_ptcontrol[i].y+4); pdc->selectobject(&ctrlpointpen); pdc->ellipse(&rectpoint); pdc->selectobject(&polygonpen); pdc->polyline(m_ptcontrol, m_nctrlpointnum); pdc->selectobject(oldpen); DeleteObject(CtrlPointPen); DeleteObject(PolygonPen);
CPoint CPractice13_1View::ComputeCurvePoint(int ndegree, CPoint AryCtrlPoint[], float fdelta) CPoint faryctrlpoint[30]; float fdelta_t; fdelta_t = 1.0f - fdelta; for( int i = 0; i <= ndegree; i++ ) faryctrlpoint[i] = AryCtrlPoint[i]; for( int r = 1; r <= ndegree; r++) for(int i = 0; i <= ndegree-r; i++ ) faryctrlpoint[i].x = (int)(fdelta_t * (float)faryctrlpoint[i].x + fdelta * (float)faryctrlpoint[i+1].x); faryctrlpoint[i].y = (int)(fdelta_t * (float)faryctrlpoint[i].y + fdelta * (float)faryctrlpoint[i+1].y); return faryctrlpoint[0];
void CPractice13_1View::CreateCurvePoint(int ndegree, int npointnum, CPoint ptcontrol[], CPoint ptcurve[]) float fdelta, t; fdelta = 1.0f/(float)(nPointNum); t = 0.0f; for( int i = 0; i <= npointnum; i++ ) ptcurve[i] = ComputeCurvePoint(nDegree, ptcontrol, t); t = t + fdelta;
void CPractice13_1View::DrawBezierCurve(CDC* pdc, bool bline) CPen CurvePen, *OldPen; CurvePen.CreatePen(PS_SOLID, 1, m_colorcurve); OldPen = pdc->selectobject(&curvepen); if( m_nctrlpointnum > 1 ) // 매개변수의 20 을 m_nresolution 으로변경 CreateCurvePoint(m_nCtrlPointNum-1, m_nresolution, m_ptcontrol, m_ptcurve); if( bline ) pdc->polyline(m_ptcurve, m_nresolution + 1); pdc->selectobject(oldpen); DeleteObject(CurvePen);
void CPractice13_1View::OnLButtonDown(UINT nflags, CPoint point) if(m_nmode == ID_MODE_DRAW) // 다시그리기또는처음그리는경우이면 --- 초기화. if(m_bstart == FALSE) m_nctrlpointnum = 0; for( int i = 0; i < 50; i++ ) m_ptcontrol[i] = 0; Invalidate(); // 뷰를갱신한다. CRect rectpoint(point.x - 4, point.y - 4, point.x + 4, point.y + 4); // 선택한지점에컨트롤포인트를그린다. CClientDC dc(this); CPen NewPen, *OldPen; NewPen.CreatePen(PS_SOLID, 3, m_colorpoint); OldPen = dc.selectobject(&newpen); dc.ellipse(&rectpoint); dc.selectobject(oldpen); DeleteObject(NewPen);
void CPractice13_1View::OnLButtonDown(UINT nflags, CPoint point) // 러버밴드구현을위한임시저장 m_ptorigin = m_ptprev = point; // 현재좌표값을배열에저장 m_ptcontrol[m_nctrlpointnum++] = point; // 마우스캡쳐 SetCapture(); RECT rectclient; GetClientRect(&rectClient); ClientToScreen(&rectClient); // 마우스이동범위를클라이언트영역으로제한 ::ClipCursor(&rectClient); // 선을그리기시작했음을저장 m_bstart = TRUE; // 현재점의선택및이동모드이면 if ( m_nmode == ID_MODE_MOVE ) // 반복문을통해배열안에점의좌표를검색한다. for(int i = 0; i < m_nctrlpointnum; i++ )
void CPractice13_1View::OnLButtonDown(UINT nflags, CPoint point) // 각좌표로부터선택영역을지정한다.( 사각형구조체 ) CRect rectpoint(m_ptcontrol[i].x - 4, m_ptcontrol[i].y - 4, m_ptcontrol[i].x + 4, m_ptcontrol[i].y + 4); // 위에서정의한사각형영역에들어와있다면... if ( rectpoint.ptinrect( point ) ) // 점이선택되었음을저장 m_bselectedpoint = TRUE; // 선택된점의인덱스를저장 m_nselectedpointindex = i; // 마우스를캡쳐하고마우스이동범위를클라이언트영역으로제한한다. SetCapture(); RECT rectclient; GetClientRect(&rectClient); ClientToScreen(&rectClient); ::ClipCursor(&rectClient); Invalidate();// 뷰를갱신한다.
void CPractice13_1View::OnMouseMove(UINT nflags, CPoint point) CClientDC dc(this); // 현재그리기모드이면 if( m_nmode == ID_MODE_DRAW ) // 러버밴드동작이시작되었으면 if( m_bstart ) dc.setrop2(r2_not); dc.moveto(m_ptorigin); dc.lineto(m_ptprev); dc.moveto(m_ptorigin); dc.lineto(point); m_ptprev = point;
void CPractice13_1View::OnMouseMove(UINT nflags, CPoint point) // 현재점의선택및이동모드이면 if ( m_nmode == ID_MODE_MOVE ) CRect rectclient; GetClientRect(&rectClient); // 왼쪽마우스버튼으로점이선택되었다면 if ( m_bselectedpoint ) // 선택된점의좌표를현재좌표값으로치환 m_ptcontrol[m_nselectedpointindex] = point; // 뷰의갱신 InvalidateRect(&rectClient, TRUE);
void CPractice13_1View::OnRButtonDown(UINT nflags, CPoint point) if( m_nmode == ID_MODE_DRAW ) // 현재저장된점들의수가 PolyBezier() 함수가요구하는 // 점의개수와동일하면 if( m_nctrlpointnum > 1 ) // 선그리기가시작되었다면... if( m_bstart ) // 오른쪽을눌러서마지막점을저장 m_ptcontrol[m_nctrlpointnum++] = point; // 마지막점의컨트롤포인트를그림다. CRect rectpoint(point.x - 4, point.y - 4, point.x + 4, point.y + 4); CClientDC dc(this); CPen NewPen, *OldPen; NewPen.CreatePen(PS_SOLID, 3, m_colorpoint); OldPen = dc.selectobject(&newpen); dc.ellipse(&rectpoint); dc.selectobject(oldpen); DeleteObject(NewPen);
void CPractice13_1View::OnRButtonDown(UINT nflags, CPoint point) DrawControlPolygon(&dc, m_bvisiblepolygon); // 베지어곡선을완성한다. DrawBezierCurve(&dc, m_bvisiblecurve); //dc.polybezier(m_pointarray, m_npointnum); // 그리기작업의끝을저장한다. m_bstart = FALSE; // 마우스캡쳐와제한을해제한다. ::ReleaseCapture(); ::ClipCursor(NULL); else AfxMessageBox(_T(" 적어도 2 개이상의컨트롤포인트가있어야합니다 "));
void CPractice13_1View::OnDrawBezier() // TODO: 여기에명령처리기코드를추가합니다. m_nctrlpointnum = 0; m_bstart = FALSE; for( int i = 0; i < 50; i++ ) m_ptcontrol[i] = 0; m_nmode = ID_MODE_DRAW; Invalidate();
void CPractice13_1View::OnViewPolygon() // TODO: 여기에명령처리기코드를추가합니다. m_bvisiblepolygon =!m_bvisiblepolygon; Invalidate();
void CPractice13_1View::OnViewCurve() // TODO: 여기에명령처리기코드를추가합니다. m_bvisiblecurve =!m_bvisiblecurve; Invalidate();
void CPractice13_1View::OnUpdateViewPolygon(CCmdUI *pcmdui) // TODO: 여기에명령업데이트 UI 처리기코드를추가합니다. pcmdui->setcheck(m_bvisiblepolygon);
void CPractice13_1View::OnUpdateViewCurve(CCmdUI *pcmdui) // TODO: 여기에명령업데이트 UI 처리기코드를추가합니다. pcmdui->setcheck(m_bvisiblecurve);
void CPractice13_1View::OnCurveRes8() // TODO: 여기에명령처리기코드를추가합니다. m_nresolution = 8; Invalidate();
void CPractice13_1View::OnCurveRes16() // TODO: 여기에명령처리기코드를추가합니다. m_nresolution = 16; Invalidate();
void CPractice13_1View::OnCurveRes32() // TODO: 여기에명령처리기코드를추가합니다. m_nresolution = 32; Invalidate(); void CPractice13_1View::OnSelectPoint() // TODO: 여기에명령처리기코드를추가합니다. m_nmode = ID_MODE_MOVE; m_bvisiblepolygon = TRUE; Invalidate();
void CPractice13_1View::OnLButtonUp(UINT nflags, CPoint point) // TODO: 여기에메시지처리기코드를추가및 / 또는기본값을호출합니다. CClientDC dc(this); // 현재점의선택및이동모드이면 if( m_nmode == ID_MODE_MOVE ) // 현재점이선택되어져있다면... if( m_bselectedpoint ) // 선택된점의좌표를현재좌표값으로치환 m_ptcontrol[m_nselectedpointindex] = point; dc.setrop2(r2_copypen); DrawControlPolygon(&dc, m_bvisiblepolygon);// 컨트롤폴리곤을그림 DrawBezierCurve(&dc, m_bvisiblecurve); // 베지어곡선을그림 m_bselectedpoint = FALSE; ::ReleaseCapture();// 마우스캡쳐를해제 ::ClipCursor(NULL);// 마우스클립을해제 Invalidate();// 뷰를갱신
void CPractice13_1View::OnColorPolygon() // TODO: 여기에명령처리기코드를추가합니다. CColorDialog dlgcolorcontrol; if( dlgcolorcontrol.domodal() == IDOK ) m_colorpolygon = dlgcolorcontrol.getcolor(); Invalidate(); void CPractice13_1View::OnColorCurve() // TODO: 여기에명령처리기코드를추가합니다. CColorDialog dlgcolorcurve; if( dlgcolorcurve.domodal() == IDOK ) m_colorcurve = dlgcolorcurve.getcolor(); Invalidate();