如何在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

你可能感兴趣的:(IE)