Click here to Skip to main content
6,630,586 members and growing! (17,943 online)
Email Password   helpLost your password?
Multimedia » GDI+ » General     Intermediate

GDI+ Line/Curve Drawing and Hit Test

By Husni Che Ngah

Demo application for developing a drawing application using GDI+. Featuring line/curve draw, hit test, and implementation of CObject/CObArray classes.
VC7WinXP, Visual Studio, MFC, GDI+, Dev
Posted:6 Mar 2005
Views:51,332
Bookmarked:52 times
Announcements
Loading...
 
Search    
Advanced Search
Add to IE Search
printPrint   add Share
      Discuss Discuss   Broken Article?Report  
10 votes for this article.
Popularity: 3.74 Rating: 3.74 out of 5
1 vote, 10.0%
1

2
1 vote, 10.0%
3
5 votes, 50.0%
4
3 votes, 30.0%
5

Introduction

This HitTester demo application was written just to illustrate the basic drawing application framework using GDI/GDI+. It also features the useful curve/line hit test technique together with the implementation of CObject class and CObArray collection class which can be extended for serialization in future. I also added extra features for storing the drawing into *.png file format.

Background

Hit test is very important for developing good drawing kind of applications to determine whether the object is currently selected or not. There are various methods that can be used to perform curve or line hit test. For diagonal (straight) line, performing hit test can be done by comparing the two slopes of the object line and the test line. Test line is a line that is virtually constructed starting from the first point of the line shape to the test point. If both slopes match, the object line is hit.

For the case of curved-line, it is a bit difficult to perform hit test. Normally, flatten technique will be used to convert the curved line to a series of connected straight lines before performing slope comparison for each line segment. Phew! Thanks to GDI+ for the GraphicsPath class and its IsOutlineVisible() member function, this hit test task can be done very easily.

You can refer to this article from MSDN regarding hit test using Win32. And this one as well for creating cables and connectors...

Using the code

Below are the steps for implementing the object class and GDI+ initialization.

Step 1:

Add the lines below to StdAfx.h file:

#include <gdiplus.h>

#include <Gdiplusinit.h>

using namespace Gdiplus;
#pragma comment(lib, "gdiplus.lib")

Step 2:

Add the lines below to HitTester.h file:

GdiplusStartupInput  m_gdiplusStartupInput;
ULONG_PTR  m_gdiplusToken;

Step 3:

Add the line below to HitTester.cpp file, under the CHitTesterApp::InitInstance() function, before the return statement:

GdiplusStartup(&m_gdiplusToken, &m_gdiplusStartupInput, NULL);

Step 4:

Add the line below to HitTester.cpp file, under the CHitTesterApp::Exitnstance() function:

GdiplusShutdown(m_gdiplusToken);

Step 5:

Add the line below to HitTesterView.h file:

#include "HCNObject.h"

Step 6:

Add the lines below to 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

}

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Husni Che Ngah


Member
Born somewhere in East Coast of Peninsular Malaysia on January 7th, 1971.

Owned a Bac. Degree in Electrical/Electronics Engineering and a Diploma Electromechanical Engineering. - Major in Industrial Automation and Robotics.

Experienced in wide range working environments, hardisk manufacturing company (6 years), petroleum industry (1 year), university/government (1 year), and freelance developement (5 years).

Fall in love with VC++/MFC since year 1996... and start getting serius since 2000 till now!
Occupation: Web Developer
Location: Malaysia Malaysia

Other popular GDI+ articles:

Article Top
You must Sign In to use this message board.
FAQ FAQ 
 
Noise Tolerance  Layout  Per page   
 Msgs 1 to 2 of 2 (Total in Forum: 2) (Refresh)FirstPrevNext
GeneralC# version Pinmemberdeepklone10:39 23 Mar '05  
GeneralRe: C# version PinmemberHusni Che Ngah19:52 25 Mar '05  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

PermaLink | Privacy | Terms of Use
Last Updated: 6 Mar 2005
Editor: Smitha Vijayan
Copyright 2005 by Husni Che Ngah
Everything else Copyright © CodeProject, 1999-2009
Web21 | Advertise on the Code Project