如何在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: };