N阶贝塞尔曲线画法
涉及知识:
- 贝塞尔曲线
- 牛顿二项式
- 杨辉三角
- 组合数
主要逻辑代码:
/**
* @brief createNBezierCurve 生成N阶贝塞尔曲线点
* @param src 源贝塞尔控制点
* @param dest 目的贝塞尔曲线点
* @param precision 生成精度
*/
static void createNBezierCurve(const QVector<QPointF> &src, QVector<QPointF> &dest, qreal precision)
{
int size = src.size();
//系数数组
QVector<qreal> coff(size,0);
//准备二维数组a[n][m]表示n阶杨辉三角,使用new创建动态二维数组
int** a = new int*[size];
for(int i=0;i<size;++i){
a[i] = new int[size];
}
{//求系数,个数对应控制点数。使用杨辉三角+组合数的方法
for(int i=0;i<size;++i)
{//首尾为 1
a[i][0]=1;
a[i][i]=1;
}
//利用组合数原理 创建杨辉三角 递推 动态规划法
for(int i=1;i<size;++i)
for(int j=1;j<i;++j)
a[i][j] = a[i-1][j-1] + a[i-1][j];
}
//总循环,时间变化 p=t*p1+(1-t)*p0
for(qreal t1=0; t1<1; t1+=precision ){
//求每个系数
qreal t2 = 1 - t1 ;
int n = size - 1;
//利用到牛顿二项式展开 (a2+2ab+b2)
coff[0] = pow(t2,n); coff[n] = pow(t1,n);
for(int i=1;i<size-1;++i){
coff[i] = pow(t2,n-i) * pow(t1,i) * a[n][i];
}
//求曲线上点
QPointF ret(0,0);
for(int i=0;i<size;++i){
ret += src[i] * coff[i];
}
//把所得的点放入目标数组,最后用QPainterPath连起来
dest.append(ret);
}
//释放二维数组
for(int i = 0; i < size; i ++)
delete [] a[i];
delete [] a;
}
绘制
头文件
#ifndef DRAWBEZIER_H
#define DRAWBEZIER_H
#include <QWidget>
#include <QGraphicsItem>
QT_BEGIN_NAMESPACE
namespace Ui { class DrawBezier; }
QT_END_NAMESPACE
class DrawBezier : public QWidget
{
Q_OBJECT
public:
DrawBezier(QWidget *parent = nullptr);
~DrawBezier();
void draw();
private slots:
void on_pushButton_clicked();
void on_pushButton_2_clicked();
void on_pushButton_3_clicked();
private:
Ui::DrawBezier *ui;
QList<QGraphicsItem *>_ctlPts;
QVector<QPointF> _src;
QVector<QPointF> _dest;
QGraphicsItem *_curve{nullptr};
QGraphicsItem *_Line{nullptr};
QGraphicsScene* scene;
bool isCreating{false};
bool isAltering{false};
};
#endif // DRAWBEZIER_H
源文件
#include "DrawBezier.h"
#include "ui_DrawBezier.h"
#include <QDebug>
/**
* @brief createNBezierCurve 生成N阶贝塞尔曲线点
* @param src 源贝塞尔控制点
* @param dest 目的贝塞尔曲线点
* @param precision 生成精度
*/
static void createNBezierCurve(const QVector<QPointF> &src, QVector<QPointF> &dest, qreal precision){}
DrawBezier::DrawBezier(QWidget *parent)
: QWidget(parent)
, ui(new Ui::DrawBezier)
{
ui->setupUi(this);
this->resize(600,600);
//创建场景,必须创建
scene = new QGraphicsScene(300,300,600,600);
ui->graphicsView->setScene(scene);
ui->graphicsView->setDragMode(QGraphicsView::RubberBandDrag);
//=========================================================
connect(ui->graphicsView,&QWGraphicsView::mouseClicked,this,[this](QPoint pos){
if(isCreating==false)return;
QPointF Scenepos = ui->graphicsView->mapToScene(pos);
QGraphicsItem* item = scene->addRect(QRectF(Scenepos,QSize(10,10)),QPen(QColor(Qt::blue)),QBrush(QColor(Qt::blue)));
item->setFlags(QGraphicsItem::ItemIsSelectable|
QGraphicsItem::ItemIsFocusable|
QGraphicsItem::ItemIsMovable);
_ctlPts.append(item);
ui->label->setText(u8"控制点个数:"+QString::number(_ctlPts.size()));
ui->graphicsView->viewport()->update();
draw();
});
connect(ui->graphicsView,&QWGraphicsView::mouseClickedFinished,this,[this](){
isCreating = false;
});
connect(ui->graphicsView,&QWGraphicsView::mouseReleased,this,[this](){
if(isCreating==true)return;
draw();
});
}
DrawBezier::~DrawBezier()
{
for(auto i : _ctlPts){delete i;}
if(_curve)delete _curve;
if(_Line){delete _Line;_Line=nullptr;}
delete scene;
delete ui;
}
void DrawBezier::draw()
{
if(_ctlPts.size()==0)return;
_src.clear();
_dest.clear();
//利用生成的控制点生成需要的控制点位置,因为在拖动控制点时位置会刷新
// item在场景中的位置需要使用 scenePos+boundingRect().center()来获取
for(auto i : _ctlPts){
_src.append(i->scenePos() + i->boundingRect().center());
}
createNBezierCurve(_src,_dest,0.01);
QPainterPath curvepath;
curvepath.moveTo(_dest[0]);
for(int i=1 ;i<_dest.size() ;++i){
curvepath.lineTo(_dest[i]);
}
if(_curve){delete _curve;_curve=nullptr;}
_curve = scene->addPath(curvepath,QPen(QBrush(Qt::green),3,Qt::SolidLine));
QPainterPath linepath;
linepath.moveTo(_src[0]);
for(int i=1 ;i<_src.size() ;++i){
linepath.lineTo(_src[i]);
}
if(_Line){delete _Line;_Line=nullptr;}
_Line = scene->addPath(linepath,QPen(QBrush(Qt::yellow),3,Qt::DotLine));
}
void DrawBezier::on_pushButton_clicked()
{
isCreating = true;
}
void DrawBezier::on_pushButton_2_clicked()
{
for(auto i : _ctlPts){delete i;}
if(_curve){delete _curve;_curve=nullptr;}
if(_Line){delete _Line;_Line=nullptr;}
_ctlPts.clear();
isCreating = false;
}
void DrawBezier::on_pushButton_3_clicked()
{
for(auto item : scene->selectedItems()){
_ctlPts.removeAll(item);
delete item;
}
draw();
}