GDI+ 线/曲线绘制与命中测试






4.56/5 (13投票s)
2005年3月6日
2分钟阅读

100023

4830
使用 GDI+ 开发绘图应用程序的演示程序。包含线条/曲线绘制、命中测试以及 CObject/CObArray 类的实现。
引言
这个命中测试器演示应用程序只是为了说明使用 GDI/GDI+ 的基本绘图应用程序框架。它还包含有用的曲线/线条命中测试技术,以及 CObject
类和 CObArray
集合类的实现,这些类可以在未来扩展用于序列化。我还添加了额外的功能,用于将绘图存储为 *.png 文件格式。
背景
命中测试对于开发良好的绘图类应用程序以确定对象是否当前被选中非常重要。可以使用各种方法来执行曲线或线条命中测试。对于对角线(直线),可以通过比较对象线条和测试线条的两个斜率来完成命中测试。测试线是从线条形状的第一点到测试点虚拟构造的一条线。如果两个斜率匹配,则命中对象线条。
对于曲线,执行命中测试会比较困难。通常,会使用平坦化技术将曲线转换为一系列连接的直线,然后再为每个线段执行斜率比较。呼!感谢 GDI+ 提供的 GraphicsPath
类及其 IsOutlineVisible()
成员函数,这项命中测试任务可以非常容易地完成。
您可以参考 MSDN 中的这篇文章,了解有关 使用 Win32 进行命中测试。以及这篇文章,了解有关 创建电缆和连接器…
使用代码
以下是实现对象类和 GDI+ 初始化步骤。
步骤 1
将以下行添加到 StdAfx.h 文件
#include <gdiplus.h> #include <Gdiplusinit.h> using namespace Gdiplus; #pragma comment(lib, "gdiplus.lib")
第二步
将以下行添加到 HitTester.h 文件
GdiplusStartupInput m_gdiplusStartupInput; ULONG_PTR m_gdiplusToken;
步骤 3
将以下行添加到 HitTester.cpp 文件,在 CHitTesterApp::InitInstance()
函数下,在 return
语句之前
GdiplusStartup(&m_gdiplusToken, &m_gdiplusStartupInput, NULL);
步骤 4
将以下行添加到 HitTester.cpp 文件,在 CHitTesterApp::Exitnstance()
函数下
GdiplusShutdown(m_gdiplusToken);
步骤 5
将以下行添加到 HitTesterView.h 文件
#include "HCNObject.h"
步骤 6
将以下行添加到 HitTesterView.cpp
void CHitTesterView::OnLButtonDown(UINT nFlags, CPoint point) { // TODO: Add your message handler code here and/or call default CRect rect; GetClientRect(rect); if(rect.PtInRect(point)) { if(m_bSelectMode) { // Create DC CDC* pDC = this->GetDC(); VERIFY(pDC != NULL); if(pDC != NULL) { // Set ROP2 mode int nR2 = pDC->SetROP2(R2_NOTXORPEN); // Create pen CPen* newPen = new CPen(PS_SOLID|PS_GEOMETRIC, 1, RGB(255,0,255)); CPen* pOldPen = pDC->SelectObject(newPen); // Run Hit Test for(int i=0; i<m_obArray->GetSize(); i++) { // Get base object CHCN_Object* pBase = m_obArray->GetObjectBaseClass(i); if(pBase->GetObjectType() == pBase->HCN_Line) { CHCN_ObjectLine* pLine = (CHCN_ObjectLine*)pBase; // Segment Selection mode if(m_bSelectSegment) { if(pLine->IsSegmentHit(point)) { if(pLine->m_bSelected) { pLine->m_bSelected = FALSE; pLine->m_bShowNode = FALSE; } else { pLine->m_bSelected = TRUE; pLine->m_bShowNode = TRUE; } } } // Node Selection mode if(m_bSelectNode) { if(pLine->IsNodeHit(point)) { if(pLine->m_bSelected) { pLine->m_bSelected = FALSE; pLine->m_bShowNode = FALSE; } else { pLine->m_bSelected = TRUE; pLine->m_bShowNode = TRUE; } } } } // Redraw Invalidate(); } // Restore ROP2 mode VERIFY(nR2 >= 0); pDC->SetROP2(nR2); pDC->SelectObject(pOldPen); // Clean-up pDC->Detach(); pDC = NULL; } } if(m_bDrawMode) { ::SetCursor(AfxGetApp()->LoadStandardCursor(IDC_CROSS)); if(m_bDrawLine || m_bDrawCurve) { if(!m_bDrawBegin) { // Begin drawing m_bDrawBegin = TRUE; // Create new point array m_ptArray = new CHCN_ObjectArray(); } // Add this point AddPointToArray(point); } } } CView::OnLButtonDown(nFlags, point); }
void CHitTesterView::OnMouseMove(UINT nFlags, CPoint point) { // TODO: Add your message handler code here and/or call default CRect rect; GetClientRect(rect); if( rect.PtInRect(point)) { ::SetCursor(AfxGetApp()->LoadStandardCursor(IDC_ARROW)); if(m_bDrawMode) { ::SetCursor(AfxGetApp()->LoadStandardCursor(IDC_CROSS)); if(m_bDrawBegin && (nFlags != MK_LBUTTON)) { // Obtain DC CDC* pDC = this->GetDC(); // Verify it VERIFY(pDC != NULL); if(pDC != NULL) { // Set ROP2 mode int nR2 = pDC->SetROP2(R2_NOTXORPEN); if(m_bDrawLine || m_bDrawCurve) { // Convert to logical point pDC->DPtoLP(&m_pt1); pDC->DPtoLP(&m_pt2); // Erase previous line pDC->MoveTo(m_pt1); pDC->LineTo(m_pt2); // Set point #2 to current mouse point m_pt2 = point; pDC->DPtoLP(&m_pt2); // Draw new line pDC->MoveTo(m_pt1); pDC->LineTo(m_pt2); } // Restore ROP2 mode VERIFY(nR2 >= 0); pDC->SetROP2(nR2); // Clean-up pDC->Detach(); pDC = NULL; } } } } else { ::SetCursor(AfxGetApp()->LoadStandardCursor(IDC_ARROW)); } CView::OnMouseMove(nFlags, point); }
void CHitTesterView::OnDraw(CDC* pDC) { CHitTesterDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return; // TODO: add draw code for native data here if(m_obArray->GetSize() > 0) { CRect rect; GetClientRect(rect); Bitmap bmp(rect.Width(),rect.Height()); Graphics* graph = Graphics::FromImage(&bmp); for(int i=0; i<m_obArray->GetSize(); i++) { // Get base object CHCN_Object* pBase = reinterpret_cast<CHCN_Object*>(m_obArray->GetAt(i)); if(pBase->GetObjectType() == pBase->HCN_Line) { CHCN_ObjectLine* pLine = (CHCN_ObjectLine*)m_obArray->GetAt(i); // Draw object to bitmap graphics pLine->DrawObject(*graph); } } Rect rc(rect.left,rect.top,rect.Width(),rect.Height()); // Clone the bitmap m_Bitmap = bmp.Clone(rc, PixelFormatDontCare); // Draw the bitmap graphics Graphics graphics(pDC->m_hDC); graphics.DrawImage(m_Bitmap, rc); } }
void CHitTesterView::OnFileSave() { // TODO: Add your command handler code here // Save the drawing. const char szFilter[] = "Image Files (*.png)|*.png|All Files (*.*)|*.*||"; const char szExt[] = "png"; CFileDialog* dlg = new CFileDialog(FALSE, szExt, NULL, OFN_CREATEPROMPT|OFN_OVERWRITEPROMPT, szFilter, this); if(dlg->DoModal() == IDOK) { CLSID pngClsid; GetEncoderClsid(L"image/png", &pngClsid); m_Bitmap->Save(dlg->GetPathName().AllocSysString(), &pngClsid, NULL); } }
int CHitTesterView::GetEncoderClsid(const WCHAR* format, CLSID* pClsid) { UINT num = 0; // number of image encoders UINT size = 0; // size of the image encoder array in bytes ImageCodecInfo* pImageCodecInfo = NULL; GetImageEncodersSize(&num, &size); if(size == 0) return -1; // Failure pImageCodecInfo = (ImageCodecInfo*)(malloc(size)); if(pImageCodecInfo == NULL) return -1; // Failure GetImageEncoders(num, size, pImageCodecInfo); for(UINT j = 0; j < num; ++j) { if( wcscmp(pImageCodecInfo[j].MimeType, format) == 0 ) { *pClsid = pImageCodecInfo[j].Clsid; free(pImageCodecInfo); return j; // Success } } free(pImageCodecInfo); return -1; // Failure }