当前位置: 编程技术>c/c++/嵌入式
解析在Direct2D中画Bezier曲线的实现方法
来源: 互联网 发布时间:2014-10-14
本文导语: Direct2D通过ID2D1RenderTarget接口支持基本图元(直线,矩形,圆角矩形,椭圆等)的绘制,然而,此接口并未提供对曲线绘制的直接支持。因此,想要使用Direct2D绘制一段通过指定点的曲线,比如Bezier曲线,必须借助于DrawGeometry()...
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++函数:
///
/// Refer to : http://www.codeproject.com/KB/graphics/BezierSpline.aspx
/// Solves a tridiagonal system for one of coordinates (x or y) of first Bezier control points.
///
/// Right hand side vector.
/// Solution vector.
void GetFirstControlPoints(
__in const std::vector& rhs,
__out std::vector& x )
{
ATLASSERT(rhs.size()==x.size());
int n = rhs.size();
std::vector tmp(n); // Temp workspace.
FLOAT b = 2.0f;
x[0] = rhs[0] / b;
for (int i = 1; i < n; i++) // Decomposition and forward substitution.
{
tmp[i] = 1 / b;
b = (i < n-1 ? 4.0f : 3.5f) - tmp[i];
x[i] = (rhs[i] - x[i-1]) / b;
}
for (int i = 1; i < n; i++)
{
x[n-i-1] -= tmp[n-i] * x[n-i]; // Back substitution.
}
}
///
/// Refer to : http://www.codeproject.com/KB/graphics/BezierSpline.aspx
/// Get open-ended Bezier Spline Control Points.
///
/// Input Knot Bezier spline points.
/// Output First Control points array of knots.size()-1 length.
/// Output Second Control points array of knots.size()-1 length.
void GetCurveControlPoints(
__in const std::vector& knots,
__out std::vector& firstCtrlPt,
__out std::vector& secondCtrlPt )
{
ATLASSERT( (firstCtrlPt.size()==secondCtrlPt.size())
&& (knots.size()==firstCtrlPt.size()+1) );
int n = knots.size()-1;
ATLASSERT(n>=1);
if (n == 1)
{
// Special case: Bezier curve should be a straight line.
// 3P1 = 2P0 + P3
firstCtrlPt[0].x = (2 * knots[0].x + knots[1].x) / 3.0f;
firstCtrlPt[0].y = (2 * knots[0].y + knots[1].y) / 3.0f;
// P2 = 2P1 – P0
secondCtrlPt[0].x = 2 * firstCtrlPt[0].x - knots[0].x;
secondCtrlPt[0].y = 2 * firstCtrlPt[0].y - knots[0].y;
return;
}
// Calculate first Bezier control points
// Right hand side vector
std::vector rhs(n);
// Set right hand side X values
for (int i = 1; i < (n-1); ++i)
{
rhs[i] = 4 * knots[i].x + 2 * knots[i+1].x;
}
rhs[0] = knots[0].x + 2 * knots[1].x;
rhs[n-1] = (8 * knots[n-1].x + knots[n].x) / 2.0f;
// Get first control points X-values
std::vector x(n);
GetFirstControlPoints(rhs,x);
// Set right hand side Y values
for (int i = 1; i < (n-1); ++i)
{
rhs[i] = 4 * knots[i].y + 2 * knots[i+1].y;
}
rhs[0] = knots[0].y + 2 * knots[1].y;
rhs[n-1] = (8 * knots[n-1].y + knots[n].y) / 2.0f;
// Get first control points Y-values
std::vector y(n);
GetFirstControlPoints(rhs,y);
// Fill output arrays.
for (int i = 0; i < n; ++i)
{
// First control point
firstCtrlPt[i] = D2D1::Point2F(x[i],y[i]);
// Second control point
if (i < (n-1))
{
secondCtrlPt[i] = D2D1::Point2F(2 * knots[i+1].x - x[i+1], 2*knots[i+1].y-y[i+1]);
}
else
{
secondCtrlPt[i] = D2D1::Point2F((knots[n].x + x[n-1])/2, (knots[n].y+y[n-1])/2);
}
}
}
HRESULT CreateBezierSpline(
__in ID2D1Factory* pD2dFactory,
__in const std::vector& points,
__out ID2D1PathGeometry** ppPathGeometry )
{
CHECK_PTR(pD2dFactory);
CHECK_OUTPUT_PTR(ppPathGeometry);
ATLASSERT(points.size()>1);
int n = points.size();
std::vector firstCtrlPt(n-1);
std::vector secondCtrlPt(n-1);
GetCurveControlPoints(points,firstCtrlPt,secondCtrlPt);
HRESULT hr = pD2dFactory->CreatePathGeometry(ppPathGeometry);
CHECKHR(hr);
if (FAILED(hr))
return hr;
CComPtr spSink;
hr = (*ppPathGeometry)->Open(&spSink);
CHECKHR(hr);
if (SUCCEEDED(hr))
{
spSink->SetFillMode(D2D1_FILL_MODE_WINDING);
spSink->BeginFigure(points[0],D2D1_FIGURE_BEGIN_FILLED);
for (int i=1;iAddBezier(D2D1::BezierSegment(firstCtrlPt[i-1],secondCtrlPt[i-1],points[i]));
spSink->EndFigure(D2D1_FIGURE_END_OPEN);
spSink->Close();
}
return hr;
}
下面是一个使用此函数绘制正弦函数的Sample,曲线的红点是曲线的控制点:
#pragma once
#include "stdafx.h"
#include
using D2D1::Point2F;
using D2D1::SizeU;
using D2D1::ColorF;
using D2D1::Matrix3x2F;
using D2D1::BezierSegment;
using D2D1::RectF;
#include
using std::vector;
#include
#include
class CMainWindow :
public CWindowImpl
{
public:
BEGIN_MSG_MAP(CMainWindow)
MSG_WM_PAINT(OnPaint)
MSG_WM_ERASEBKGND(OnEraseBkgnd)
MSG_WM_SIZE(OnSize)
MSG_WM_CREATE(OnCreate)
MSG_WM_DESTROY(OnDestroy)
END_MSG_MAP()
int OnCreate(LPCREATESTRUCT /*lpCreateStruct*/)
{
CreateDeviceIndependentResource();
CreateDeviceResource();
CreateCurve();
return 0;
}
void OnDestroy()
{
PostQuitMessage(0);
}
void OnPaint(CDCHandle)
{
CPaintDC dc(m_hWnd);
Render();
}
BOOL OnEraseBkgnd(CDCHandle dc)
{
return TRUE; // we have erased the background
}
void OnSize(UINT /*nType*/, CSize size)
{
if (m_spHwndRT)
{
m_spHwndRT->Resize(SizeU(size.cx,size.cy));
CreateCurve();
}
}
private:
void Render()
{
if (!m_spHwndRT)
CreateDeviceResource();
m_spHwndRT->BeginDraw();
m_spHwndRT->Clear(ColorF(ColorF::CornflowerBlue));
m_spHwndRT->SetTransform(Matrix3x2F::Identity());
D2D1_SIZE_F size = m_spHwndRT->GetSize();
FLOAT width = size.width-50, height = size.height-50;
D2D1_MATRIX_3X2_F reflectY = Direct2DHelper::ReflectYMatrix();
D2D1_MATRIX_3X2_F translate = Matrix3x2F::Translation(size.width/2.0f,size.height/2.0f);
m_spHwndRT->SetTransform(reflectY*translate);
// draw coordinate axis
m_spSolidBrush->SetColor(ColorF(ColorF::Red));
m_spHwndRT->DrawLine(Point2F(-width*0.5f,0),Point2F(width*0.5f,0),m_spSolidBrush,2.0f);
m_spSolidBrush->SetColor(ColorF(ColorF::DarkGreen));
m_spHwndRT->DrawLine(Point2F(0,-height*0.5f),Point2F(0,height*0.5f),m_spSolidBrush,2.0f);
// draw curve
m_spSolidBrush->SetColor(ColorF(ColorF::Blue));
m_spHwndRT->DrawGeometry(m_spPathGeometry,m_spSolidBrush,1.0f);
// draw point marks
m_spSolidBrush->SetColor(ColorF(ColorF::Red));
for (auto p=m_Points.cbegin();p!=m_Points.cend();p++)
{
Direct2DHelper::DrawRectPoint(m_spHwndRT,m_spSolidBrush,(*p),5.0f);
}
HRESULT hr = m_spHwndRT->EndDraw();
if (hr == D2DERR_RECREATE_TARGET)
DiscardDeviceResource();
}
void CreateDeviceIndependentResource()
{
Direct2DHelper::CreateD2D1Factory(&m_spD2dFactory);
}
void CreateDeviceResource()
{
CRect rc;
GetClientRect(&rc);
CHECK_PTR(m_spD2dFactory);
IFR(m_spD2dFactory->CreateHwndRenderTarget(
D2D1::RenderTargetProperties(),
D2D1::HwndRenderTargetProperties(m_hWnd,SizeU(rc.Width(),rc.Height())),
&m_spHwndRT));
IFR(m_spHwndRT->CreateSolidColorBrush(ColorF(ColorF::Red),&m_spSolidBrush));
}
void DiscardDeviceResource()
{
m_spSolidBrush.Release();
m_spHwndRT.Release();
}
void CreateCurve()
{
if (!m_spHwndRT)
return;
if (m_spPathGeometry)
{
m_spPathGeometry.Release();
m_Points.clear();
}
const int ptCount = 100;
D2D1_SIZE_F size = m_spHwndRT->GetSize();
FLOAT width = size.width-50.0f, height = size.height*0.4f;
#define SIN_CURVE
#ifdef SIN_CURVE // create sin curve
FLOAT factor = static_cast(4.0f*M_PI/width);
FLOAT x = -width*0.5f, y = 0, dx = width/ptCount;
for (int i=0;i
Draw a Smooth Curve through a Set of 2D Points with Bezier Primitives
C#的代码可以很容易的转换成C++版本的,下面是我转换的一个用于Direct2D的绘制Bezier曲线的C++函数:
代码如下:
///
/// Refer to : http://www.codeproject.com/KB/graphics/BezierSpline.aspx
/// Solves a tridiagonal system for one of coordinates (x or y) of first Bezier control points.
///
/// Right hand side vector.
/// Solution vector.
void GetFirstControlPoints(
__in const std::vector& rhs,
__out std::vector& x )
{
ATLASSERT(rhs.size()==x.size());
int n = rhs.size();
std::vector tmp(n); // Temp workspace.
FLOAT b = 2.0f;
x[0] = rhs[0] / b;
for (int i = 1; i < n; i++) // Decomposition and forward substitution.
{
tmp[i] = 1 / b;
b = (i < n-1 ? 4.0f : 3.5f) - tmp[i];
x[i] = (rhs[i] - x[i-1]) / b;
}
for (int i = 1; i < n; i++)
{
x[n-i-1] -= tmp[n-i] * x[n-i]; // Back substitution.
}
}
///
/// Refer to : http://www.codeproject.com/KB/graphics/BezierSpline.aspx
/// Get open-ended Bezier Spline Control Points.
///
/// Input Knot Bezier spline points.
/// Output First Control points array of knots.size()-1 length.
/// Output Second Control points array of knots.size()-1 length.
void GetCurveControlPoints(
__in const std::vector& knots,
__out std::vector& firstCtrlPt,
__out std::vector& secondCtrlPt )
{
ATLASSERT( (firstCtrlPt.size()==secondCtrlPt.size())
&& (knots.size()==firstCtrlPt.size()+1) );
int n = knots.size()-1;
ATLASSERT(n>=1);
if (n == 1)
{
// Special case: Bezier curve should be a straight line.
// 3P1 = 2P0 + P3
firstCtrlPt[0].x = (2 * knots[0].x + knots[1].x) / 3.0f;
firstCtrlPt[0].y = (2 * knots[0].y + knots[1].y) / 3.0f;
// P2 = 2P1 – P0
secondCtrlPt[0].x = 2 * firstCtrlPt[0].x - knots[0].x;
secondCtrlPt[0].y = 2 * firstCtrlPt[0].y - knots[0].y;
return;
}
// Calculate first Bezier control points
// Right hand side vector
std::vector rhs(n);
// Set right hand side X values
for (int i = 1; i < (n-1); ++i)
{
rhs[i] = 4 * knots[i].x + 2 * knots[i+1].x;
}
rhs[0] = knots[0].x + 2 * knots[1].x;
rhs[n-1] = (8 * knots[n-1].x + knots[n].x) / 2.0f;
// Get first control points X-values
std::vector x(n);
GetFirstControlPoints(rhs,x);
// Set right hand side Y values
for (int i = 1; i < (n-1); ++i)
{
rhs[i] = 4 * knots[i].y + 2 * knots[i+1].y;
}
rhs[0] = knots[0].y + 2 * knots[1].y;
rhs[n-1] = (8 * knots[n-1].y + knots[n].y) / 2.0f;
// Get first control points Y-values
std::vector y(n);
GetFirstControlPoints(rhs,y);
// Fill output arrays.
for (int i = 0; i < n; ++i)
{
// First control point
firstCtrlPt[i] = D2D1::Point2F(x[i],y[i]);
// Second control point
if (i < (n-1))
{
secondCtrlPt[i] = D2D1::Point2F(2 * knots[i+1].x - x[i+1], 2*knots[i+1].y-y[i+1]);
}
else
{
secondCtrlPt[i] = D2D1::Point2F((knots[n].x + x[n-1])/2, (knots[n].y+y[n-1])/2);
}
}
}
HRESULT CreateBezierSpline(
__in ID2D1Factory* pD2dFactory,
__in const std::vector& points,
__out ID2D1PathGeometry** ppPathGeometry )
{
CHECK_PTR(pD2dFactory);
CHECK_OUTPUT_PTR(ppPathGeometry);
ATLASSERT(points.size()>1);
int n = points.size();
std::vector firstCtrlPt(n-1);
std::vector secondCtrlPt(n-1);
GetCurveControlPoints(points,firstCtrlPt,secondCtrlPt);
HRESULT hr = pD2dFactory->CreatePathGeometry(ppPathGeometry);
CHECKHR(hr);
if (FAILED(hr))
return hr;
CComPtr spSink;
hr = (*ppPathGeometry)->Open(&spSink);
CHECKHR(hr);
if (SUCCEEDED(hr))
{
spSink->SetFillMode(D2D1_FILL_MODE_WINDING);
spSink->BeginFigure(points[0],D2D1_FIGURE_BEGIN_FILLED);
for (int i=1;iAddBezier(D2D1::BezierSegment(firstCtrlPt[i-1],secondCtrlPt[i-1],points[i]));
spSink->EndFigure(D2D1_FIGURE_END_OPEN);
spSink->Close();
}
return hr;
}
下面是一个使用此函数绘制正弦函数的Sample,曲线的红点是曲线的控制点:
代码如下:
#pragma once
#include "stdafx.h"
#include
using D2D1::Point2F;
using D2D1::SizeU;
using D2D1::ColorF;
using D2D1::Matrix3x2F;
using D2D1::BezierSegment;
using D2D1::RectF;
#include
using std::vector;
#include
#include
class CMainWindow :
public CWindowImpl
{
public:
BEGIN_MSG_MAP(CMainWindow)
MSG_WM_PAINT(OnPaint)
MSG_WM_ERASEBKGND(OnEraseBkgnd)
MSG_WM_SIZE(OnSize)
MSG_WM_CREATE(OnCreate)
MSG_WM_DESTROY(OnDestroy)
END_MSG_MAP()
int OnCreate(LPCREATESTRUCT /*lpCreateStruct*/)
{
CreateDeviceIndependentResource();
CreateDeviceResource();
CreateCurve();
return 0;
}
void OnDestroy()
{
PostQuitMessage(0);
}
void OnPaint(CDCHandle)
{
CPaintDC dc(m_hWnd);
Render();
}
BOOL OnEraseBkgnd(CDCHandle dc)
{
return TRUE; // we have erased the background
}
void OnSize(UINT /*nType*/, CSize size)
{
if (m_spHwndRT)
{
m_spHwndRT->Resize(SizeU(size.cx,size.cy));
CreateCurve();
}
}
private:
void Render()
{
if (!m_spHwndRT)
CreateDeviceResource();
m_spHwndRT->BeginDraw();
m_spHwndRT->Clear(ColorF(ColorF::CornflowerBlue));
m_spHwndRT->SetTransform(Matrix3x2F::Identity());
D2D1_SIZE_F size = m_spHwndRT->GetSize();
FLOAT width = size.width-50, height = size.height-50;
D2D1_MATRIX_3X2_F reflectY = Direct2DHelper::ReflectYMatrix();
D2D1_MATRIX_3X2_F translate = Matrix3x2F::Translation(size.width/2.0f,size.height/2.0f);
m_spHwndRT->SetTransform(reflectY*translate);
// draw coordinate axis
m_spSolidBrush->SetColor(ColorF(ColorF::Red));
m_spHwndRT->DrawLine(Point2F(-width*0.5f,0),Point2F(width*0.5f,0),m_spSolidBrush,2.0f);
m_spSolidBrush->SetColor(ColorF(ColorF::DarkGreen));
m_spHwndRT->DrawLine(Point2F(0,-height*0.5f),Point2F(0,height*0.5f),m_spSolidBrush,2.0f);
// draw curve
m_spSolidBrush->SetColor(ColorF(ColorF::Blue));
m_spHwndRT->DrawGeometry(m_spPathGeometry,m_spSolidBrush,1.0f);
// draw point marks
m_spSolidBrush->SetColor(ColorF(ColorF::Red));
for (auto p=m_Points.cbegin();p!=m_Points.cend();p++)
{
Direct2DHelper::DrawRectPoint(m_spHwndRT,m_spSolidBrush,(*p),5.0f);
}
HRESULT hr = m_spHwndRT->EndDraw();
if (hr == D2DERR_RECREATE_TARGET)
DiscardDeviceResource();
}
void CreateDeviceIndependentResource()
{
Direct2DHelper::CreateD2D1Factory(&m_spD2dFactory);
}
void CreateDeviceResource()
{
CRect rc;
GetClientRect(&rc);
CHECK_PTR(m_spD2dFactory);
IFR(m_spD2dFactory->CreateHwndRenderTarget(
D2D1::RenderTargetProperties(),
D2D1::HwndRenderTargetProperties(m_hWnd,SizeU(rc.Width(),rc.Height())),
&m_spHwndRT));
IFR(m_spHwndRT->CreateSolidColorBrush(ColorF(ColorF::Red),&m_spSolidBrush));
}
void DiscardDeviceResource()
{
m_spSolidBrush.Release();
m_spHwndRT.Release();
}
void CreateCurve()
{
if (!m_spHwndRT)
return;
if (m_spPathGeometry)
{
m_spPathGeometry.Release();
m_Points.clear();
}
const int ptCount = 100;
D2D1_SIZE_F size = m_spHwndRT->GetSize();
FLOAT width = size.width-50.0f, height = size.height*0.4f;
#define SIN_CURVE
#ifdef SIN_CURVE // create sin curve
FLOAT factor = static_cast(4.0f*M_PI/width);
FLOAT x = -width*0.5f, y = 0, dx = width/ptCount;
for (int i=0;i
您可能感兴趣的文章:
本站(WWW.)旨在分享和传播互联网科技相关的资讯和技术,将尽最大努力为读者提供更好的信息聚合和浏览方式。
本站(WWW.)站内文章除注明原创外,均为转载、整理或搜集自网络。欢迎任何形式的转载,转载请注明出处。
本站(WWW.)站内文章除注明原创外,均为转载、整理或搜集自网络。欢迎任何形式的转载,转载请注明出处。