65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.56/5 (13投票s)

2005年3月6日

2分钟阅读

viewsIcon

100023

downloadIcon

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
}
© . All rights reserved.