穿过已知点画平滑曲线(3次贝塞尔曲线)

摘要:
Style=“margin:20px0px0px;font-size:14px;line-height:26px;font-family:Arial;color:rgb”˃为了将一系列点连接成一条平滑的曲线,首先研究贝塞尔曲线,然后进行B样条插值……稍后在“[平移]AGG的贝塞尔插值”中http://liyiwen.javaeye.com/blog/705489 。 查看更好的方法:使用贝塞尔曲线的平滑度通过这些点。其基本思想是首先计算相邻原始点的中点,然后将由相邻中点连接的线段平移到相应的原始点,使用平移的中点作为控制点,并使用相邻原始点作为起点绘制贝塞尔曲线,从而确保连接的平滑性。贝塞尔曲线本身是平滑的,因此这些原始点与平滑曲线相连。

tyle="margin:20px 0px 0px; font-size:14px; line-height:26px; font-family:Arial; color:rgb(51,51,51)">

为了把一串点连成光滑的曲线,先研究贝塞尔曲线,又搞B样条插值。。。。都没有成功(数学没那么强)。

后来在

[翻译] AGG 之贝塞尔插值http://liyiwen.javaeye.com/blog/705489。看到一种比较好的方法:

运用贝塞尔曲线的光滑性来穿过这些点。

大致思路就是 先算出相邻原始点的中点,在把相邻中点连成的线段平移到对应的原始点,以平移后的中点作为控制点,相邻原始点为起始点画贝塞尔曲线,这样就保证了连接处的光滑。而贝塞尔曲线本身是光滑的,所以就把这些原始点用光滑曲线连起来了。

穿过已知点画平滑曲线(3次贝塞尔曲线)第1张

我封装了一个函数,留着以后用。

(c++版,其它语言只要把数组和可变数组稍微变一下就能用)

  1. voidcreateCurve(CvPoint*originPoint,intoriginCount,vector<CvPoint>&curvePoint){
  2. //控制点收缩系数,经调试0.6较好,CvPoint是opencv的,可自行定义结构体(x,y)
  3. floatscale=0.6;
  4. CvPointmidpoints[originCount];
  5. //生成中点
  6. for(inti=0;i<originCount;i++){
  7. intnexti=(i+1)%originCount;
  8. midpoints[i].x=(originPoint[i].x+originPoint[nexti].x)/2.0;
  9. midpoints[i].y=(originPoint[i].y+originPoint[nexti].y)/2.0;
  10. }
  11. //平移中点
  12. CvPointextrapoints[2*originCount];
  13. for(inti=0;i<originCount;i++){
  14. intnexti=(i+1)%originCount;
  15. intbacki=(i+originCount-1)%originCount;
  16. CvPointmidinmid;
  17. midinmid.x=(midpoints[i].x+midpoints[backi].x)/2.0;
  18. midinmid.y=(midpoints[i].y+midpoints[backi].y)/2.0;
  19. intoffsetx=originPoint[i].x-midinmid.x;
  20. intoffsety=originPoint[i].y-midinmid.y;
  21. intextraindex=2*i;
  22. extrapoints[extraindex].x=midpoints[backi].x+offsetx;
  23. extrapoints[extraindex].y=midpoints[backi].y+offsety;
  24. //朝originPoint[i]方向收缩
  25. intaddx=(extrapoints[extraindex].x-originPoint[i].x)*scale;
  26. intaddy=(extrapoints[extraindex].y-originPoint[i].y)*scale;
  27. extrapoints[extraindex].x=originPoint[i].x+addx;
  28. extrapoints[extraindex].y=originPoint[i].y+addy;
  29. intextranexti=(extraindex+1)%(2*originCount);
  30. extrapoints[extranexti].x=midpoints[i].x+offsetx;
  31. extrapoints[extranexti].y=midpoints[i].y+offsety;
  32. //朝originPoint[i]方向收缩
  33. addx=(extrapoints[extranexti].x-originPoint[i].x)*scale;
  34. addy=(extrapoints[extranexti].y-originPoint[i].y)*scale;
  35. extrapoints[extranexti].x=originPoint[i].x+addx;
  36. extrapoints[extranexti].y=originPoint[i].y+addy;
  37. }
  38. CvPointcontrolPoint[4];
  39. //生成4控制点,产生贝塞尔曲线
  40. for(inti=0;i<originCount;i++){
  41. controlPoint[0]=originPoint[i];
  42. intextraindex=2*i;
  43. controlPoint[1]=extrapoints[extraindex+1];
  44. intextranexti=(extraindex+2)%(2*originCount);
  45. controlPoint[2]=extrapoints[extranexti];
  46. intnexti=(i+1)%originCount;
  47. controlPoint[3]=originPoint[nexti];
  48. floatu=1;
  49. while(u>=0){
  50. intpx=bezier3funcX(u,controlPoint);
  51. intpy=bezier3funcY(u,controlPoint);
  52. //u的步长决定曲线的疏密
  53. u-=0.005;
  54. CvPointtempP=cvPoint(px,py);
  55. //存入曲线点
  56. curvePoint.push_back(tempP);
  57. }
  58. }
  59. }
  60. //三次贝塞尔曲线
  61. floatbezier3funcX(floatuu,CvPoint*controlP){
  62. floatpart0=controlP[0].x*uu*uu*uu;
  63. floatpart1=3*controlP[1].x*uu*uu*(1-uu);
  64. floatpart2=3*controlP[2].x*uu*(1-uu)*(1-uu);
  65. floatpart3=controlP[3].x*(1-uu)*(1-uu)*(1-uu);
  66. returnpart0+part1+part2+part3;
  67. }
  68. floatbezier3funcY(floatuu,CvPoint*controlP){
  69. floatpart0=controlP[0].y*uu*uu*uu;
  70. floatpart1=3*controlP[1].y*uu*uu*(1-uu);
  71. floatpart2=3*controlP[2].y*uu*(1-uu)*(1-uu);
  72. floatpart3=controlP[3].y*(1-uu)*(1-uu)*(1-uu);
  73. returnpart0+part1+part2+part3;
  74. }

翻译] AGG 之贝塞尔插值

文章分类:综合技术

原文地址:http://www.antigrain.com/research/
bezier_interpolation/index.html#PAGE_BEZIER_INTERPOLATION

Interpolation with Bezier Curves 贝塞尔插值

A very simple method of smoothing polygons 一种非常简单的多边形平滑方法

翻译:唐风

之前 comp.graphic.algorithms 上有一个讨论,是关于怎么样使用曲线对多边形进行插值处理,使得最终产生的曲线是光滑的而且能通过所有的顶点。Gernot Hoffmann 建议说使用著名的 B-Spline 来进行插值。这里有他当时的文章。B-Spline 在这里效果很好,它看起来就像是一个固定在多边形顶点上的橡皮尺(elastic ruler)。

spline_polygon1spline_polygon2


bezier_interpolation但我有个大胆的推测,我觉得肯定还存在更简单的方法。比如,使用三次贝塞曲线(cubic Bezier)进行近似。贝塞尔曲线有两个固定点(起点和终点),另加两个决定曲线形状的控制点(CP)。关于贝塞尔曲线的更多知识可以在搜索引擎中找到,比如,你可以参考Paul Bourke 的站点。 现在给贝塞尔曲线的锚点(固定点),也就是多边形的某一对顶点,那么问题是,我们怎么计算控制点的位置?我运行 Xara X 然后画出了右边这个图形,这很简单,所以我决定尝试下计算出它们的坐标。很显然,多边形两条相邻边的两个控制点与这两个控制点之间的顶点应该在一条直线 上,只有这样,两条相邻的插值曲线才能平滑地连接在一起。所以,这两个控制点应该是相对于顶点是对称的,不过,也不全是……,因为真正的对称就要求它们与 中心点的距离应该是相等的,但对于我们的情况中并不完全是这样的。一开始,我试着先算出多边形两条边的角平分线,然后把控制点放在这条角平分线的垂直线 上。但从图上可以看到,控制点的连线并不会总是垂直于角平分线的。


最终,我找到一个非常简单的办法,不需要任何复杂的数学计算。首先,我们计算出多边形所有边线的中点,Ai。

bezier_interpolation_s1


然后连接起相邻边中点,得到很多线段,记为 Ci 。并用图记的方法计算出 Bi 点。

bezier_interpolation_s2


最后一步,只需要简单地将 Ci 进行平移,平移的路径就是每条线段上 Bi 到对应顶点的路径。就这样,我们计算出了贝塞尔曲线的控制点,平滑的结果看起来也很棒。

bezier_interpolation_s3


这里还可以做一点小小的改进,因为我们已经得到了一条决定控制点的直线,所以,我们可以根据需要,使控制点在这条直线上移动,这样可以改变插值曲线 的状态。我使用了一个与控制点和顶点初始距离相关的系数 K ,用来沿直线移动控制点。控制点离顶点越远,图形看起来就越锐利。

bezier_interpolation_s4


下面是用原始形状和系统K=1.0的贝塞尔插值两种方法来描画的 SVG 的狮子。

bezier_lion1bezier_lion2


下面是放大图

bezier_lion3bezier_lion4


这个方法对于自相关的多边形也适用,下面的例子可以看到,结果非常有意思:

bezier_self_intersecting1


bezier_self_intersecting2


bezier_self_intersecting3


这个方法只是探索和经验式的,如果从严格的数学模型的角度看它可能是错误的。但在实际使用中的效果已经足够好了,而 且这个方法只需要最小的计算量。下面的代码就是用来画出上面狮子图像的。这些代码并没有进行优化,只是用来演示的。里面有些变量计算了两次,在实际程序 中,如果连续的步骤中都用到同一个变量值,我们可以先缓存变量值进行复用(以避免重复的计算)。

This method is pure heuristic and empiric. It probably gives 

a wrong result from the point of view of strict mathematical 

modeling. But in practice the result is good enough and it 

requires absolute minimum of calculations. Below is the source code

that has been used to generate the lions shown above. It's

not optimal and just an illustration. It calculates some variables 

twice, while in real programs we can store and reuse them in the
  1. //Assumeweneedtocalculatethecontrol
  2. //pointsbetween(x1,y1)and(x2,y2).
  3. //Thenx0,y0-thepreviousvertex,
  4. //x3,y3-thenextone.
  5. doublexc1=(x0+x1)/2.0;
  6. doubleyc1=(y0+y1)/2.0;
  7. doublexc2=(x1+x2)/2.0;
  8. doubleyc2=(y1+y2)/2.0;
  9. doublexc3=(x2+x3)/2.0;
  10. doubleyc3=(y2+y3)/2.0;
  11. doublelen1=sqrt((x1-x0)*(x1-x0)+(y1-y0)*(y1-y0));
  12. doublelen2=sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1));
  13. doublelen3=sqrt((x3-x2)*(x3-x2)+(y3-y2)*(y3-y2));
  14. doublek1=len1/(len1+len2);
  15. doublek2=len2/(len2+len3);
  16. doublexm1=xc1+(xc2-xc1)*k1;
  17. doubleym1=yc1+(yc2-yc1)*k1;
  18. doublexm2=xc2+(xc3-xc2)*k2;
  19. doubleym2=yc2+(yc3-yc2)*k2;
  20. //Resultingcontrolpoints.Heresmooth_valueismentioned
  21. //abovecoefficientKwhosevalueshouldbeinrange[0...1].
  22. ctrl1_x=xm1+(xc2-xm1)*smooth_value+x1-xm1;
  23. ctrl1_y=ym1+(yc2-ym1)*smooth_value+y1-ym1;
  24. ctrl2_x=xm2+(xc2-xm2)*smooth_value+x2-xm2;
  25. ctrl2_y=ym2+(yc2-ym2)*smooth_value+y2-ym2;
 



consecutive steps.

使用三次贝塞尔近似的代码:

  1. //Numberofintermediatepointsbetweentwosourceones,
  2. //Actually,thisvalueshouldbecalculatedinsomeway,
  3. //Obviously,dependingonthereallengthofthecurve.
  4. //ButIdon'tknowanyelegantandfastsolutionforthis
  5. //problem.
  6. #defineNUM_STEPS20
  7. voidcurve4(Polygon*p,
  8. doublex1,doubley1,//Anchor1
  9. doublex2,doubley2,//Control1
  10. doublex3,doubley3,//Control2
  11. doublex4,doubley4)//Anchor2
  12. {
  13. doubledx1=x2-x1;
  14. doubledy1=y2-y1;
  15. doubledx2=x3-x2;
  16. doubledy2=y3-y2;
  17. doubledx3=x4-x3;
  18. doubledy3=y4-y3;
  19. doublesubdiv_step=1.0/(NUM_STEPS+1);
  20. doublesubdiv_step2=subdiv_step*subdiv_step;
  21. doublesubdiv_step3=subdiv_step*subdiv_step*subdiv_step;
  22. doublepre1=3.0*subdiv_step;
  23. doublepre2=3.0*subdiv_step2;
  24. doublepre4=6.0*subdiv_step2;
  25. doublepre5=6.0*subdiv_step3;
  26. doubletmp1x=x1-x2*2.0+x3;
  27. doubletmp1y=y1-y2*2.0+y3;
  28. doubletmp2x=(x2-x3)*3.0-x1+x4;
  29. doubletmp2y=(y2-y3)*3.0-y1+y4;
  30. doublefx=x1;
  31. doublefy=y1;
  32. doubledfx=(x2-x1)*pre1+tmp1x*pre2+tmp2x*subdiv_step3;
  33. doubledfy=(y2-y1)*pre1+tmp1y*pre2+tmp2y*subdiv_step3;
  34. doubleddfx=tmp1x*pre4+tmp2x*pre5;
  35. doubleddfy=tmp1y*pre4+tmp2y*pre5;
  36. doubledddfx=tmp2x*pre5;
  37. doubledddfy=tmp2y*pre5;
  38. intstep=NUM_STEPS;
  39. //Suppose,wehavesomeabstractobjectPolygonwhich
  40. //hasmethodAddVertex(x,y),similartoLineToin
  41. //manygraphicalAPIs.
  42. //Note,thattheloophasonlyoperationadd!
  43. while(step--)
  44. {
  45. fx+=dfx;
  46. fy+=dfy;
  47. dfx+=ddfx;
  48. dfy+=ddfy;
  49. ddfx+=dddfx;
  50. ddfy+=dddfy;
  51. p->AddVertex(fx,fy);
  52. }
  53. p->AddVertex(x4,y4);//Laststepmustgoexactlytox4,y4
  54. }

你可以下载一个能运行的画狮子的例子,对它进行旋转和缩放,也可以生成一些随机的多边形。点左键并拖动它可以围绕中 心点旋转和缩放图像。点右键并从左向右拖动,可以改变系统数K。 K=1时大约是距窗口左边100像素处。每次双击会产生一个随机的多边形,对于这些多边形,也可以进行旋转、缩放以及改变K值的操作。

免责声明:文章转载自《穿过已知点画平滑曲线(3次贝塞尔曲线)》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇xcache的使用与配置【ABAP系列】SAP ABAP MIR7预制凭证BAPI下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

在AutoCAD中生成贝塞尔曲线

贝赛尔曲线的定义  贝塞尔曲线(Bézier curve)是由法国雷诺公司的设计师Pierre Bézier设计。它的具体定义如下:                    其中Pi(i=0,1,2,...n)称作曲线的控制向量,他们组成的连续多段线叫做曲线的控制多边形; t ∈[0,1],即 t 从0连续变化到1时所生成的曲线就叫做贝赛尔曲线。而函数:...

ios 动画系列之六------UIBezierPath贝塞尔弧线常用方法记

//根据一个矩形画曲线 + (UIBezierPath*)bezierPathWithRect:(CGRect)rect //根据矩形框的内切圆画曲线 + (UIBezierPath*)bezierPathWithOvalInRect:(CGRect)rect //根据矩形画带圆角的曲线 + (UIBezierPath*)bezierPathWith...

iOS开发 贝塞尔曲线UIBezierPath(后记)

使用CAShapeLayer与UIBezierPath可以实现不在view的drawRect方法中就画出一些想要的图形 。 1:UIBezierPath: UIBezierPath是在 UIKit 中的一个类,继承于NSObject,可以创建基于矢量的路径.此类是Core Graphics框架关于path的一个OC封装。使用此类可以定义常见的圆形、多边形等...

UIBezierPath精讲

前言 笔者在写本篇文章之前,也没有系统学习过贝塞尔曲线,只是曾经某一次的需求需要使用到,才临时百度看了一看而且使用最基本的功能。现在总算有时间停下来好好研究研究这个神奇而伟大的贝塞尔先生! 笔者在学习时,首先看了两遍UIBezierPath类头文件定义,熟悉了一下相关的属性和方法。 基础知识 使用UIBezierPath可以创建基于矢量的路径,此类是Cor...

Flutter 贝塞尔曲线切割

现在人们对于网站的美感要求是越来越高了,所以很多布局需要优美的曲线设计。当然最简单的办法是作一个PNG的透明图片,然后外边放一个Container.但其内容如果本身就不是图片,只是容器,这种放入图片的做法会让包体变大。其实我们完全可以使用贝塞尔曲线进行切割。 ClipPath 路径裁切控件 clipPath控件可以把其内部的子控件切割,它有两个主要属性(...

canvas基础2--绘制图形

栅格 绘制矩形 不同于SVG,HTML中的元素canvas只支持一种原生的图形绘制:矩形。所有其他的图形的绘制都至少需要生成一条路径。不过,我们拥有众多路径生成的方法让复杂图形的绘制成为了可能。 首先,我们回到矩形的绘制中。canvas提供了三种方法绘制矩形: fillRect(x, y, width, height) 绘制一个填充的矩形 strokeR...