如何在Direct2D中画Bezier曲线

Direct2D通过ID2D1RenderTarget接口支持基本图元(直线,矩形,圆角矩形,椭圆等)的绘制,然而,此接口并未提供对曲线绘制的直接支持。因此,想要使用Direct2D绘制一段通过指定点的曲线,比如Bezier曲线,必须借助于DrawGeometry()方法间接实现。需要通过一定的算法,将指定点转换为定义Path的控制点。幸运的是,codproject上已经有人做了这项工作,给出了相应的转换算法,并给出了C#版的实现:

Draw a Smooth Curve through a Set of 2D Points with Bezier Primitives

 

C#的代码可以很容易的转换成C++版本的,下面是我转换的一个用于Direct2D的绘制Bezier曲线的C++函数:

  1: /// <summary>
  2: /// Refer to : http://www.codeproject.com/KB/graphics/BezierSpline.aspx
  3: /// Solves a tridiagonal system for one of coordinates (x or y) of first Bezier control points.
  4: /// </summary>
  5: /// <param name="rhs">Right hand side vector.</param>
  6: /// <param name="x">Solution vector.</param>
  7: void GetFirstControlPoints(
  8:     __in const std::vector<FLOAT>& rhs, 
  9:     __out std::vector<FLOAT>& x )
 10: {
 11:     ATLASSERT(rhs.size()==x.size());
 12:     int n = rhs.size();
 13:     std::vector<FLOAT> tmp(n);    // Temp workspace.
 14: 
 15:     FLOAT b = 2.0f;
 16:     x[0] = rhs[0] / b;
 17:     for (int i = 1; i < n; i++) // Decomposition and forward substitution.
 18:     {
 19:         tmp[i] = 1 / b;
 20:         b = (i < n-1 ? 4.0f : 3.5f) - tmp[i];
 21:         x[i] = (rhs[i] - x[i-1]) / b;
 22:     }
 23:     for (int i = 1; i < n; i++)
 24:     {
 25:         x[n-i-1] -= tmp[n-i] * x[n-i]; // Back substitution.
 26:     }
 27: }
 28: 
 29: /// <summary>
 30: /// Refer to : http://www.codeproject.com/KB/graphics/BezierSpline.aspx
 31: /// Get open-ended Bezier Spline Control Points.
 32: /// </summary>
 33: /// <param name="knots">Input Knot Bezier spline points.</param>
 34: /// <param name="firstCtrlPt">Output First Control points array of knots.size()-1 length.</param>
 35: /// <param name="secondCtrlPt">Output Second Control points array of knots.size()-1 length.</param>
 36: void GetCurveControlPoints(
 37:     __in const std::vector<D2D1_POINT_2F>& knots,
 38:     __out std::vector<D2D1_POINT_2F>& firstCtrlPt, 
 39:     __out std::vector<D2D1_POINT_2F>& secondCtrlPt )
 40: {
 41:     ATLASSERT( (firstCtrlPt.size()==secondCtrlPt.size())
 42:         && (knots.size()==firstCtrlPt.size()+1) );
 43: 
 44:     int n = knots.size()-1;
 45:     ATLASSERT(n>=1);
 46: 
 47:     if (n == 1)
 48:     { 
 49:         // Special case: Bezier curve should be a straight line.
 50:         // 3P1 = 2P0 + P3
 51:         firstCtrlPt[0].x = (2 * knots[0].x + knots[1].x) / 3.0f;
 52:         firstCtrlPt[0].y = (2 * knots[0].y + knots[1].y) / 3.0f;
 53: 
 54:         // P2 = 2P1 – P0
 55:         secondCtrlPt[0].x = 2 * firstCtrlPt[0].x - knots[0].x;
 56:         secondCtrlPt[0].y = 2 * firstCtrlPt[0].y - knots[0].y;
 57:         return;
 58:     }
 59: 
 60:     // Calculate first Bezier control points
 61:     // Right hand side vector
 62:     std::vector<FLOAT> rhs(n);
 63: 
 64:     // Set right hand side X values
 65:     for (int i = 1; i < (n-1); ++i)
 66:     {
 67:         rhs[i] = 4 * knots[i].x + 2 * knots[i+1].x;
 68:     }
 69:     rhs[0] = knots[0].x + 2 * knots[1].x;
 70:     rhs[n-1] = (8 * knots[n-1].x + knots[n].x) / 2.0f;
 71:     // Get first control points X-values
 72:     std::vector<FLOAT> x(n);
 73:     GetFirstControlPoints(rhs,x);
 74: 
 75:     // Set right hand side Y values
 76:     for (int i = 1; i < (n-1); ++i)
 77:     {
 78:         rhs[i] = 4 * knots[i].y + 2 * knots[i+1].y;
 79:     }
 80:     rhs[0] = knots[0].y + 2 * knots[1].y;
 81:     rhs[n-1] = (8 * knots[n-1].y + knots[n].y) / 2.0f;
 82:     // Get first control points Y-values
 83:     std::vector<FLOAT> y(n);
 84:     GetFirstControlPoints(rhs,y);
 85: 
 86:     // Fill output arrays.
 87:     for (int i = 0; i < n; ++i)
 88:     {
 89:         // First control point
 90:         firstCtrlPt[i] = D2D1::Point2F(x[i],y[i]);
 91:         // Second control point
 92:         if (i < (n-1))
 93:         {
 94:             secondCtrlPt[i] = D2D1::Point2F(2 * knots[i+1].x - x[i+1], 2*knots[i+1].y-y[i+1]);
 95:         }
 96:         else
 97:         {
 98:             secondCtrlPt[i] = D2D1::Point2F((knots[n].x + x[n-1])/2, (knots[n].y+y[n-1])/2);
 99:         }
100:     }
101: }
102: 
103: HRESULT CreateBezierSpline(
104:     __in ID2D1Factory* pD2dFactory, 
105:     __in const std::vector<D2D1_POINT_2F>& points,
106:     __out ID2D1PathGeometry** ppPathGeometry )
107: {
108:     CHECK_PTR(pD2dFactory);
109:     CHECK_OUTPUT_PTR(ppPathGeometry);
110:     ATLASSERT(points.size()>1);
111: 
112:     int n = points.size();
113:     std::vector<D2D1_POINT_2F> firstCtrlPt(n-1);
114:     std::vector<D2D1_POINT_2F> secondCtrlPt(n-1);
115:     GetCurveControlPoints(points,firstCtrlPt,secondCtrlPt);
116: 
117:     HRESULT hr = pD2dFactory->CreatePathGeometry(ppPathGeometry);
118:     CHECKHR(hr);
119:     if (FAILED(hr))
120:         return hr;
121: 
122:     CComPtr<ID2D1GeometrySink> spSink;
123:     hr = (*ppPathGeometry)->Open(&spSink);
124:     CHECKHR(hr);
125:     if (SUCCEEDED(hr))
126:     {
127:         spSink->SetFillMode(D2D1_FILL_MODE_WINDING);
128:         spSink->BeginFigure(points[0],D2D1_FIGURE_BEGIN_FILLED);
129:         for (int i=1;i<n;i++)
130:             spSink->AddBezier(D2D1::BezierSegment(firstCtrlPt[i-1],secondCtrlPt[i-1],points[i]));
131:         spSink->EndFigure(D2D1_FIGURE_END_OPEN);
132:         spSink->Close();
133:     }
134:     return hr;
135: }

下面是一个使用此函数绘制正弦函数的Sample,曲线的红点是曲线的控制点:

  1: #pragma once
  2: #include "stdafx.h"
  3: #include <Direct2DHelper.h>
  4: using D2D1::Point2F;
  5: using D2D1::SizeU;
  6: using D2D1::ColorF;
  7: using D2D1::Matrix3x2F;
  8: using D2D1::BezierSegment;
  9: using D2D1::RectF;
 10: 
 11: #include <vector>
 12: using std::vector;
 13: #include <algorithm>
 14: #include <boost/math/distributions/normal.hpp>
 15: 
 16: class CMainWindow :
 17:     public CWindowImpl<CMainWindow,CWindow,CSimpleWinTraits>
 18: {
 19: public:
 20:     BEGIN_MSG_MAP(CMainWindow)
 21:         MSG_WM_PAINT(OnPaint)
 22:         MSG_WM_ERASEBKGND(OnEraseBkgnd)
 23:         MSG_WM_SIZE(OnSize)
 24:         MSG_WM_CREATE(OnCreate)
 25:         MSG_WM_DESTROY(OnDestroy)
 26:     END_MSG_MAP()
 27:     
 28:     int OnCreate(LPCREATESTRUCT /*lpCreateStruct*/)
 29:     {
 30:         CreateDeviceIndependentResource();
 31:         CreateDeviceResource();
 32:         CreateCurve();
 33:         return 0;
 34:     }
 35:     
 36:     void OnDestroy()
 37:     {
 38:         PostQuitMessage(0);
 39:     }
 40:     
 41:     void OnPaint(CDCHandle)
 42:     {
 43:         CPaintDC dc(m_hWnd);
 44:         Render();
 45:     }
 46:     
 47:     BOOL OnEraseBkgnd(CDCHandle dc)
 48:     {
 49:         return TRUE;    // we have erased the background
 50:     }
 51:     
 52:     void OnSize(UINT /*nType*/, CSize size)
 53:     {
 54:         if (m_spHwndRT)
 55:         {
 56:             m_spHwndRT->Resize(SizeU(size.cx,size.cy));
 57:             CreateCurve();
 58:         }
 59:     }
 60: 
 61: private:
 62:     void Render()
 63:     {
 64:         if (!m_spHwndRT)
 65:             CreateDeviceResource();
 66: 
 67:         m_spHwndRT->BeginDraw();
 68:         m_spHwndRT->Clear(ColorF(ColorF::CornflowerBlue));
 69: 
 70:         m_spHwndRT->SetTransform(Matrix3x2F::Identity());
 71: 
 72:         D2D1_SIZE_F size = m_spHwndRT->GetSize();
 73:         FLOAT width = size.width-50, height = size.height-50;
 74:         D2D1_MATRIX_3X2_F reflectY = Direct2DHelper::ReflectYMatrix();
 75:         D2D1_MATRIX_3X2_F translate = Matrix3x2F::Translation(size.width/2.0f,size.height/2.0f);
 76:         m_spHwndRT->SetTransform(reflectY*translate);
 77: 
 78:         // draw coordinate axis
 79:         m_spSolidBrush->SetColor(ColorF(ColorF::Red));
 80:         m_spHwndRT->DrawLine(Point2F(-width*0.5f,0),Point2F(width*0.5f,0),m_spSolidBrush,2.0f);
 81:         m_spSolidBrush->SetColor(ColorF(ColorF::DarkGreen));
 82:         m_spHwndRT->DrawLine(Point2F(0,-height*0.5f),Point2F(0,height*0.5f),m_spSolidBrush,2.0f);
 83: 
 84:         // draw curve
 85:         m_spSolidBrush->SetColor(ColorF(ColorF::Blue));
 86:         m_spHwndRT->DrawGeometry(m_spPathGeometry,m_spSolidBrush,1.0f);
 87: 
 88:         // draw point marks
 89:         m_spSolidBrush->SetColor(ColorF(ColorF::Red));
 90:         for (auto p=m_Points.cbegin();p!=m_Points.cend();p++)
 91:         {
 92:             Direct2DHelper::DrawRectPoint(m_spHwndRT,m_spSolidBrush,(*p),5.0f);
 93:         }
 94: 
 95:         HRESULT hr = m_spHwndRT->EndDraw();
 96:         if (hr == D2DERR_RECREATE_TARGET)
 97:             DiscardDeviceResource();
 98:     }
 99: 
100:     void CreateDeviceIndependentResource()
101:     {
102:         Direct2DHelper::CreateD2D1Factory(&m_spD2dFactory);
103:     }
104: 
105:     void CreateDeviceResource()
106:     {
107:         CRect rc;
108:         GetClientRect(&rc);
109: 
110:         CHECK_PTR(m_spD2dFactory);
111:         IFR(m_spD2dFactory->CreateHwndRenderTarget(
112:             D2D1::RenderTargetProperties(),
113:             D2D1::HwndRenderTargetProperties(m_hWnd,SizeU(rc.Width(),rc.Height())),
114:             &m_spHwndRT));
115:         IFR(m_spHwndRT->CreateSolidColorBrush(ColorF(ColorF::Red),&m_spSolidBrush));
116:     }
117: 
118:     void DiscardDeviceResource()
119:     {
120:         m_spSolidBrush.Release();
121:         m_spHwndRT.Release();
122:     }
123: 
124:     void CreateCurve()
125:     {
126:         if (!m_spHwndRT) 
127:             return;
128:         if (m_spPathGeometry)
129:         {
130:             m_spPathGeometry.Release();
131:             m_Points.clear();
132:         }
133: 
134:         const int ptCount = 100;
135:         D2D1_SIZE_F size = m_spHwndRT->GetSize();
136:         FLOAT width = size.width-50.0f, height = size.height*0.4f;
137: 
138: #define SIN_CURVE    
139: #ifdef SIN_CURVE    // create sin curve
140:         FLOAT factor = static_cast<FLOAT>(4.0f*M_PI/width);
141:         FLOAT x = -width*0.5f, y = 0, dx = width/ptCount;
142:         for (int i=0;i<ptCount+1;i++)
143:         {
144:             y = height*sin(factor*x);
145:             m_Points.push_back(Point2F(x,y));
146:             x += dx;
147:         }
148: #else                // create normal distribute curve
149:         FLOAT factor = 10.0f/width;
150:         FLOAT x = -width*0.5f, y = 0, dx = width/ptCount;
151:         boost::math::normal nd;
152:         for (int i=0;i<ptCount+1;i++)
153:         {
154:             y = height*static_cast<FLOAT>(boost::math::pdf(nd,factor*x));
155:             m_Points.push_back(Point2F(x,y));
156:             x += dx;
157:         }
158: #endif // SIN_CURVE
159: 
160:         // create Bezier spline
161:         Direct2DHelper::CreateBezierSpline(m_spD2dFactory,m_Points,&m_spPathGeometry);
162:         CHECK_PTR(m_spPathGeometry);
163:     }
164: 
165: private:
166:     CComPtr<ID2D1Factory> m_spD2dFactory;
167:     CComPtr<ID2D1HwndRenderTarget> m_spHwndRT;
168:     CComPtr<ID2D1SolidColorBrush> m_spSolidBrush;
169:     CComPtr<ID2D1PathGeometry> m_spPathGeometry;
170: 
171:     vector<D2D1_POINT_2F> m_Points;
172: };
ScreenShot00170

posted on 2011-04-06 22:24  wudong  阅读(2646)  评论(0编辑  收藏  举报

导航