QT实现任意阶贝塞尔曲线绘制

    bezier曲线在编程中的难点在于求取曲线的系数,如果系数确定了那么就可以用微小的直线段画出曲线。bezier曲线的系数也就是bernstein系数,此系数的性质可以自行百度,我们在这里是利用bernstein系数的递推性质求取:

简单举例

两个点p0,p1 为一阶曲线,系数为 (1-u)p0+u*p1; 将系数存在数组中b[0] =1-u,b[1]=u。

三个点 p0 p1 p2 为二阶曲线,系数(1-u)(1-u)p0+2u(1-u)p1+u*u*p2 可以看出二阶的系数是一届的系数的关系 ((1-u)+u)(b[0]+b[1])。

注意:通过这个公式有没有发现,当u==0的时候这个点就是p0,当u==1的时候这个点就是p2,其他时候点被p1所吸引,也就是p1点的存在会导致(u!=0&&u!=1)的时候生成的点靠近p1

四个点 三阶曲线为:

((1-u)+u)((1-u)+u)(b[0]+b[1])

是不是有种似曾相识的感觉,对了,这就是高中牛顿二项式展开的过程:

二阶贝塞尔曲线实现代码:

QPointF p0(0,0);
QPointF p1(1000,0);
QPointF p2(1000,1000);
QPainterPath path;
path.moveTo(p0);
QPointF pTemp;
for(double t=0; t<1; t+=0.01)  //2次Bezier曲线 
{pTemp  =pow((1-t),2)*p0+2*t*(1-t)*p1+pow(t,2)*p2;path.lineTo(pTemp);
}

没有使用贝塞尔曲线(三个点直接相连)画出来三角形是这样:

使用贝塞尔曲线之后,(1000,0)这个位置的角会圆化:

上图中你会发现曲线不太圆滑,这个你可以调参数precision,主要的问题是它用了贝塞尔曲线之后都不像一个三角形了,我们只想对三角形的角进行圆化。我们可以选择构成三角形角的两边上接近交点位置的两个点,用这个两个点和这两边的交点(三角形的角)生成贝塞尔曲线,效果如下:

我们发现他就是有很多短小的曲线构成的,所以这就是多边形的角圆化的原理。

上面是实现的二阶贝塞尔曲线,但是有时候我们可能会使用其他阶数曲线,所以我们需要改一下代码使得代码更大众化:

/*** @brief createNBezierCurve 生成N阶贝塞尔曲线点* @param src 源贝塞尔控制点,里面有两个点就是一阶,有三个点就是二阶,依次类推* @param dest 目的贝塞尔曲线点* @param precision 生成精度,控制着细小直线的长度,细小直线长度越小模拟出现的圆角越圆滑(此值越小细小直线长度越小)*/
static void createNBezierCurve(const QList<QPointF> &src, QList<QPointF> &dest, qreal precision=0.5)
{if (src.size() <= 0) return;//清空QList<QPointF>().swap(dest);//外侧循环控制(1-u)p0+u*p1中u的值,用来生成多个点for (qreal t = 0; t < 1.0000; t += precision) {int size = src.size();QVector<qreal> coefficient(size, 0);coefficient[0] = 1.000;qreal u1 = 1.0 - t;//里面循环用来生成每一次u改变之后的参数值,参数就是二项展开式,然后把参数和各顶点乘起来就得到贝塞尔曲线的一个顶点for (int j = 1; j <= size - 1; j++) {qreal saved = 0.0;for (int k = 0; k < j; k++){qreal temp = coefficient[k];coefficient[k] = saved + u1 * temp;saved = t * temp;}coefficient[j] = saved;}//最后的贝塞尔顶点QPointF resultPoint;for (int i = 0; i < size; i++) {QPointF point = src.at(i);resultPoint = resultPoint + point * coefficient[i];}dest.append(resultPoint);}
}

然后我来讲讲代码如何实现把三角形的角圆化的:

/*
src就是保存多边形所有顶点的集合,要有序(有序的意思就是按照点的顺序可以形成一个多边形)
dest就是一个空的集合,最后生成的所有点都放在里面,然后按照这些点依次连接最后就是一个角圆化之后的多边形*/
void GeometryViewer::centralHandler(vector<CVector2d>&src, vector<CVector2d>&dest)
{vector<CVector2d>tmp;for (int i = 0; i < src.size(); ++i){   //对于每一个多边形顶点(角),我们需要找到构成这个顶点的两条直线上接近顶点的两个点,用这三个点生成贝塞尔曲线CVector2d pt1 = getLineStart(src[i],src[(src.size() + i - 1) % src.size()]);tmp.push_back(pt1);tmp.push_back(src[i]);CVector2d pt3 = getLineStart(src[i], src[(i + 1) % src.size()]);tmp.push_back(pt3);createNBezierCurve(tmp, dest);tmp.clear();}
}

CVector2d类的功能大致如下:

class CVector2d
{
public:double X,Y;CVector2d(double x,double y):X(x),Y(y){X=x;Y=y;printf("%lf 00**** %lf\n",x,y);}CVector2d operator+(CVector2d y)const{return CVector2d(X+y.X,Y+y.Y);}
};

getLineStart它将返回一个点, 该点是pt1顶点朝着pt2顶点离开m_uiRadius像素。变量fRat保持半径与第i个线段长度之间的比率。还有一项检查可以防止fRat的值超过0.5。如果fRat的值超过0.5, 则两个连续的圆角将重叠, 这将导致较差的视觉效果。

当从点P1到点P2直线行驶并完成距离的30%时, 我们可以使用公式0.7•P1 + 0.3•P2确定位置。通常, 如果我们获得完整距离的一小部分, 并且α= 1表示完整距离, 则当前位置为(1-α)•P1 +α•P2。

这就是GetLineStart方法确定在第(i + 1)方向上距离第i个顶点m_uiRadius像素的点的位置的方式。

 

CVector2d GeometryViewer::getLineStart(CVector2d pt1,CVector2d pt2,double radius=0.0)
{CVector2d pt;double fRat;if(radius==0)fRat = 0.02;else fRat = radius / getDistance(pt1, pt2);if (fRat > 0.5f)fRat = 0.5f;pt.X = (1.0f - fRat)*pt1.X + fRat*pt2.X;pt.Y = (1.0f - fRat)*pt1.Y + fRat*pt2.Y;return pt;
}
//欧几里得距离
double getDistance(CVector2d pt1, CVector2d pt2)
{double fD = (pt1.X - pt2.X)*(pt1.X - pt2.X) +(pt1.Y - pt2.Y) * (pt1.Y - pt2.Y);return sqrt(fD);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/70561.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

运维Shell脚本小试牛刀(七):在函数文脚本件中调用另外一个脚本文件中函数|函数递归调用|函数后台执行

运维Shell脚本小试牛刀(一) 运维Shell脚本小试牛刀(二) 运维Shell脚本小试牛刀(三)::$(cd $(dirname $0)&#xff1b; pwd)命令详解 运维Shell脚本小试牛刀(四): 多层嵌套if...elif...elif....else fi_蜗牛杨哥的博客-CSDN博客 Cenos7安装小火车程序动画 运维Shell脚本小试…

华纳云:Linux的底层体系结构是怎样的

Linux操作系统的底层体系结构是一个开源的Unix-like操作系统内核&#xff0c;通常称为Linux内核(Linux Kernel)。下面是Linux底层体系结构的主要组成部分和工作原理&#xff1a; 内核&#xff08;Kernel&#xff09;&#xff1a; Linux的核心部分是内核&#xff0c;它是操作系统…

【扩散模型 李宏毅B站教学以及基础代码运用】

李宏毅教学视频&#xff1a; Link1 B站DDPM公式推导以及代码实现&#xff1a; Link2 这个视频里面有论文里面的公式推导&#xff0c;并且1小时10分开始讲解实例代码。 文章目录 扩散模型概念&#xff1a;Diffusion Model工作原理&#xff1a;影像生成模型本质上的共同目标B站…

算法——组合程序算法解析

组合就是从m个元素的数组中求n个元素的所有组合&#xff0c;代码如下&#xff1a; #include <iostream> #include <vector> using namespace std; // 递归求解组合 void combinations(vector<int>& nums, vector<int>& combination, int star…

Linux 安装 JDK

要在Linux上安装JDK 1&#xff0c;按照以下步骤进行操作&#xff1a; 1. 下载JDK安装文件&#xff1a;首先&#xff0c;你需要找到适用于你操作系统的JDK安装文件&#xff08;tar.gz或tar.bz2格式&#xff09;。你可以从Oracle官方网站或其他可信的来源下载该文件。 2. 解压…

Ansible自动化运维

目录 前言 一、概述 常见的开源自动化运维工具比较 二、ansible环境搭建 三、ansible模块 &#xff08;一&#xff09;、hostname模块 &#xff08;二&#xff09;、file模块 &#xff08;三&#xff09;、copy模块 &#xff08;四&#xff09;、fetch模块 &#xff…

C++ 新旧版本两种读写锁

一、简介 读写锁&#xff08;Read-Write Lock&#xff09;是一种并发控制机制&#xff0c;用于多线程环境中实现对共享资源的高效读写操作。读写锁允许多个线程同时读取共享资源&#xff0c;但在有写操作时&#xff0c;需要互斥地独占对共享资源的访问&#xff0c;以确保数据的…

ITSS运维资质认证:提升企业竞争力

ITSS运维资质认证是近年来备受企业关注的热门话题。通过认证&#xff0c;企业能够提高运维团队的技术实力&#xff0c;加强服务质量&#xff0c;提升竞争力。下面将为大家分析其对企业的影响和意义。 随着信息技术的迅猛发展&#xff0c;企业对IT系统的可靠性和稳定性要求越来越…

ElementUI浅尝辄止21:Tree 树形控件

树形组件&#xff1a;用清晰的层级结构展示信息&#xff0c;可展开或折叠。 树组件使用挺频繁的&#xff0c;常见于侧边栏树形目录、树形下拉选项按钮或搜索查询树形信息选项 1.如何使用&#xff1f; 基础的树形结构展示 <el-tree :data"data" :props"defa…

TypeScript内置类型有哪些?

Partial Partial用于将给定类型的所有属性设置为可选。换句话说&#xff0c;Partial 可以创建一个新的类型&#xff0c;该类型具有与原始类型相同的属性&#xff0c;但是这些属性都是可选的。使用 Partial 可以很方便地定义一个对象&#xff0c;其中的属性可以选择性地进行赋值…

借助各大模型的优点生成原创视频(真人人声)Plus

【技术背景】 众所周知&#xff0c;组成视频的3大元素&#xff0c;即文本语音图片。接着小编逐一介绍生成原创视频的过程。 【文本生成】 天工AI搜索&#xff08;thttp://iangong.cn&#xff09; 直接手机短信验证就可以使用&#xff0c;该大模型已经接入互联网&#xff0c…

git_合并分支

1、环境 (1)将测试分支dev合并到master分支。 (2)使用merge命令。 2、合并步骤 (1)切换到master分支 git checkout master (2)如果是多人开发的话&#xff0c;需要把远程master上的代码pull下来。 //如果是自己一个开发就没有必要了&#xff0c;不过为了保险起见还是pul…

什么是IIFE(Immediately Invoked Function Expression)?它有什么作用?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐IIFE 的基本语法⭐IIFE 的主要作用⭐如何使用 IIFE 来创建私有变量和模块封装⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅…

GOOGLE SRE 运维模式解读

一、SRE核心是什么 我总结下来是&#xff1a;通过软件工程的方式开发&#xff08;GOOGLE规定SRE团队必须将50%的精力花在真实的开发工作上&#xff09;一些自动化的工具系统来解放传统运维工程师大量重复和手工操作&#xff0c;从而让新生代的SRE工程师有更多的时间&#xff1…

五种定时任务方案(Timer+ScheduleExecutorService+spring task+多线程执行+quartz)

方案一&#xff1a;Timer (1)Timer.schedule(TimerTask task,Date time)安排在制定的时间执行指定的任务。 (2)Timer.schedule(TimerTask task,Date firstTime ,long period)安排指定的任务在指定的时间开始进行重复的固定延迟执行&#xff0e; (3)Timer.schedule(TimerTask…

YashanDB:潜心实干,数据库核心技术突破没有捷径可走

都说数据库是三大基础软件中的一块硬骨头&#xff0c;技术门槛高、研发周期长、工程要求高&#xff0c;市场长期被几大巨头所把持。 因此&#xff0c;实现突破一直是中国数据库产业的夙愿。自上个世纪80年代起&#xff0c;中国数据库产业走过艰辛坎坷的四十余载&#xff0c;终…

【数据结构】二叉搜索树——二叉搜索树的概念和介绍、二叉搜索树的简单实现、二叉搜索树的增删查改

文章目录 二叉搜索树1. 二叉搜索树的概念和介绍2. 二叉搜索树的简单实现2.1二叉搜索树的插入2.2二叉搜索树的查找2.3二叉搜索树的遍历2.4二叉搜索树的删除2.5完整代码和测试 二叉搜索树 1. 二叉搜索树的概念和介绍 二叉搜索树又称二叉排序树&#xff0c;它或者是一棵空树&…

【Spring 事务和事务传播机制】

目录 1 事务概述 1.1 为什么需要事务 1.2 事务的特性 1.3 Spring 中事务的实现 2 Spring 声明式事务 2.1 Transactional 2.2 Transactional 的作用范围 2.3 Transactional 的各种参数 2.3.1 ioslation 2.4 事务发生了异常&#xff0c;也不回滚的情况 异常被捕获时 3 事务的传…

oracle数据库给用户授权DBA权限Oracle查看哪些用户具有DBA权限

oracle数据库给用户授权DBA权限 步骤一&#xff1a;以sysdba身份登录到Oracle数据库 在授予DBA权限之前&#xff0c;我们首先要以sysdba身份登录到Oracle数据库。使用以下命令登录&#xff1a; sqlplus / as sysdba步骤二&#xff1a;创建用户&#xff08;如有用户跳过&#…

通过 Blob 对二进制流文件下载实现文件保存下载

原理&#xff1a;前端将二进制文件做转换实现下载: 请求后端接口->接收后端返回的二进制流(通过二进制流&#xff08;Blob&#xff09;下载,把后端返回的二进制文件放在 Blob 里面)->再通过file-saver插件保存 页面上使用&#xff1a; <span click"downloadFil…