• 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])

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

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

    1. QPointF p0(0,0);
    2. QPointF p1(1000,0);
    3. QPointF p2(1000,1000);
    4. QPainterPath path;
    5. path.moveTo(p0);
    6. QPointF pTemp;
    7. for(double t=0; t<1; t+=0.01) //2次Bezier曲线
    8. {
    9. pTemp =pow((1-t),2)*p0+2*t*(1-t)*p1+pow(t,2)*p2;
    10. path.lineTo(pTemp);
    11. }

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

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

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

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

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

    1. /**
    2. * @brief createNBezierCurve 生成N阶贝塞尔曲线点
    3. * @param src 源贝塞尔控制点,里面有两个点就是一阶,有三个点就是二阶,依次类推
    4. * @param dest 目的贝塞尔曲线点
    5. * @param precision 生成精度,控制着细小直线的长度,细小直线长度越小模拟出现的圆角越圆滑(此值越小细小直线长度越小)
    6. */
    7. static void createNBezierCurve(const QList<QPointF> &src, QList<QPointF> &dest, qreal precision=0.5)
    8. {
    9. if (src.size() <= 0) return;
    10. //清空
    11. QList<QPointF>().swap(dest);
    12.   //外侧循环控制(1-u)p0+u*p1中u的值,用来生成多个点
    13. for (qreal t = 0; t < 1.0000; t += precision) {
    14. int size = src.size();
    15. QVector<qreal> coefficient(size, 0);
    16. coefficient[0] = 1.000;
    17. qreal u1 = 1.0 - t;
    18.     //里面循环用来生成每一次u改变之后的参数值,参数就是二项展开式,然后把参数和各顶点乘起来就得到贝塞尔曲线的一个顶点
    19. for (int j = 1; j <= size - 1; j++) {
    20. qreal saved = 0.0;
    21. for (int k = 0; k < j; k++){
    22. qreal temp = coefficient[k];
    23. coefficient[k] = saved + u1 * temp;
    24. saved = t * temp;
    25. }
    26. coefficient[j] = saved;
    27. }
    28.     //最后的贝塞尔顶点
    29. QPointF resultPoint;
    30. for (int i = 0; i < size; i++) {
    31. QPointF point = src.at(i);
    32. resultPoint = resultPoint + point * coefficient[i];
    33. }
    34. dest.append(resultPoint);
    35. }
    36. }

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

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

    CVector2d类的功能大致如下:

    1. class CVector2d
    2. {
    3. public:
    4. double X,Y;
    5. CVector2d(double x,double y):X(x),Y(y)
    6. {
    7. X=x;
    8. Y=y;
    9. printf("%lf 00**** %lf\n",x,y);
    10. }
    11. CVector2d operator+(CVector2d y)const
    12. {
    13. return CVector2d(X+y.X,Y+y.Y);
    14. }
    15. };

    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像素的点的位置的方式。

     

    1. CVector2d GeometryViewer::getLineStart(CVector2d pt1,CVector2d pt2,double radius=0.0)
    2. {
    3. CVector2d pt;
    4. double fRat;
    5. if(radius==0)
    6. fRat = 0.02;
    7. else fRat = radius / getDistance(pt1, pt2);
    8. if (fRat > 0.5f)
    9. fRat = 0.5f;
    10. pt.X = (1.0f - fRat)*pt1.X + fRat*pt2.X;
    11. pt.Y = (1.0f - fRat)*pt1.Y + fRat*pt2.Y;
    12. return pt;
    13. }
    1. //欧几里得距离
    2. double getDistance(CVector2d pt1, CVector2d pt2)
    3. {
    4. double fD = (pt1.X - pt2.X)*(pt1.X - pt2.X) +
    5. (pt1.Y - pt2.Y) * (pt1.Y - pt2.Y);
    6. return sqrt(fD);
    7. }

  • 相关阅读:
    中国乙腈市场预测与战略咨询研究报告(2022版)
    LeetCode78.子集
    Java堆外缓存(一个很有意思的应用)
    训练深度神经网络,使用反向传播算法,产生梯度消失和梯度爆炸问题的原因?
    IDEA怎么将CRLF转化为LF
    O-羟丙基壳聚糖/聚乙二醇水凝胶/N-乙烯基吡咯烷酮(NVP)接枝壳聚糖(CHI)水凝胶的制备
    RPA教程01:EXCEL自动化从入门到实操
    短信测压APP/网卡
    子比主题v7.4绕授权接口源码
    黑客零基础入门教程及方法,从零开始学习黑客技术,看这一篇就够了
  • 原文地址:https://blog.csdn.net/hulinhulin/article/details/132642754