如何使用javascript HTML5 canvas通过N点绘制平滑曲线?

how to draw smooth curve through N points using javascript HTML5 canvas?

对于绘图应用程序,我将鼠标移动坐标保存到数组中,然后使用lineTo进行绘制。 结果线不平滑。 如何在所有收集的点之间生成一条曲线?

我已经用谷歌搜索过,但是我只发现了3个绘制线条的函数:对于2个采样点,只需使用lineTo。 对于3个采样点quadraticCurveTo,对于4个采样点bezierCurveTo

(我尝试为数组中的每4个点绘制一个bezierCurveTo,但这会导致每4个采样点扭结,而不是连续的平滑曲线。)

我该如何编写一个函数绘制5个采样点及更多的平滑曲线?


将随后的采样点与不相交的" curveTo"类型的函数连接在一起的问题在于,曲线相交的位置不平滑。这是因为两条曲线共享一个端点,但受到完全不相交的控制点的影响。一种解决方案是"弯曲"到下两个后续采样点之间的中点。使用这些新的内插点将曲线连接起来,可以在端点处实现平滑过渡(一次迭代的终点变成下一次迭代的控制点。)换句话说,两条不相交的曲线现在有很多共同点。

该解决方案摘自《 Foundation ActionScript 3.0动画:使事物移动》一书。第95页-渲染技术:创建多条曲线。

注意:此解决方案实际上并没有绘制出每个点,这是我的问题的标题(而是通过采样点近似绘制曲线,但从不通过采样点),但是出于我的目的(绘图应用程序),对我来说已经足够了,从视觉上讲,您无法分辨出区别。有一种解决方案可以遍历所有样本点,但是要复杂得多(请参阅http://www.cartogrammar.com/blog/actionscript-curves-update/)

这是近似方法的图形代码:

1
2
3
4
5
6
7
8
9
10
11
12
// move to the first point
   ctx.moveTo(points[0].x, points[0].y);


   for (i = 1; i < points.length - 2; i ++)
   {
      var xc = (points[i].x + points[i + 1].x) / 2;
      var yc = (points[i].y + points[i + 1].y) / 2;
      ctx.quadraticCurveTo(points[i].x, points[i].y, xc, yc);
   }
 // curve through the last two points
 ctx.quadraticCurveTo(points[i].x, points[i].y, points[i+1].x,points[i+1].y);


有点晚了,但是为了记录。

您可以通过使用基本样条曲线(也称为规范样条曲线)绘制穿过点的平滑曲线来获得平滑线。

我为画布制作了此功能-它分为三个功能以增加多功能性。主要包装函数如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function drawCurve(ctx, ptsa, tension, isClosed, numOfSegments, showPoints) {

    showPoints  = showPoints ? showPoints : false;

    ctx.beginPath();

    drawLines(ctx, getCurvePoints(ptsa, tension, isClosed, numOfSegments));

    if (showPoints) {
        ctx.stroke();
        ctx.beginPath();
        for(var i=0;i<ptsa.length-1;i+=2)
                ctx.rect(ptsa[i] - 2, ptsa[i+1] - 2, 4, 4);
    }
}

要绘制曲线,请按以下顺序排列一个带有x,y点的数组:x1,y1, x2,y2, ...xn,yn

像这样使用它:

1
2
3
4
5
var myPoints = [10,10, 40,30, 100,10]; //minimum two points
var tension = 1;

drawCurve(ctx, myPoints); //default tension=0.5
drawCurve(ctx, myPoints, tension);

上面的函数调用两个子函数,一个子函数计算平滑点。这将返回一个包含新点的数组-这是计算平滑点的核心功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
function getCurvePoints(pts, tension, isClosed, numOfSegments) {

    // use input value if provided, or use a default value  
    tension = (typeof tension != 'undefined') ? tension : 0.5;
    isClosed = isClosed ? isClosed : false;
    numOfSegments = numOfSegments ? numOfSegments : 16;

    var _pts = [], res = [],    // clone array
        x, y,           // our x,y coords
        t1x, t2x, t1y, t2y, // tension vectors
        c1, c2, c3, c4,     // cardinal points
        st, t, i;       // steps based on num. of segments

    // clone array so we don't change the original
    //
    _pts = pts.slice(0);

    // The algorithm require a previous and next point to the actual point array.
    // Check if we will draw closed or open curve.
    // If closed, copy end points to beginning and first points to end
    // If open, duplicate first points to befinning, end points to end
    if (isClosed) {
        _pts.unshift(pts[pts.length - 1]);
        _pts.unshift(pts[pts.length - 2]);
        _pts.unshift(pts[pts.length - 1]);
        _pts.unshift(pts[pts.length - 2]);
        _pts.push(pts[0]);
        _pts.push(pts[1]);
    }
    else {
        _pts.unshift(pts[1]);   //copy 1. point and insert at beginning
        _pts.unshift(pts[0]);
        _pts.push(pts[pts.length - 2]); //copy last point and append
        _pts.push(pts[pts.length - 1]);
    }

    // ok, lets start..

    // 1. loop goes through point array
    // 2. loop goes through each segment between the 2 pts + 1e point before and after
    for (i=2; i < (_pts.length - 4); i+=2) {
        for (t=0; t <= numOfSegments; t++) {

            // calc tension vectors
            t1x = (_pts[i+2] - _pts[i-2]) * tension;
            t2x = (_pts[i+4] - _pts[i]) * tension;

            t1y = (_pts[i+3] - _pts[i-1]) * tension;
            t2y = (_pts[i+5] - _pts[i+1]) * tension;

            // calc step
            st = t / numOfSegments;

            // calc cardinals
            c1 =   2 * Math.pow(st, 3)  - 3 * Math.pow(st, 2) + 1;
            c2 = -(2 * Math.pow(st, 3)) + 3 * Math.pow(st, 2);
            c3 =       Math.pow(st, 3)  - 2 * Math.pow(st, 2) + st;
            c4 =       Math.pow(st, 3)  -     Math.pow(st, 2);

            // calc x and y cords with common control vectors
            x = c1 * _pts[i]    + c2 * _pts[i+2] + c3 * t1x + c4 * t2x;
            y = c1 * _pts[i+1]  + c2 * _pts[i+3] + c3 * t1y + c4 * t2y;

            //store points in array
            res.push(x);
            res.push(y);

        }
    }

    return res;
}

并将点实际绘制为平滑曲线(或任何其他分段线,只要您具有x,y数组):

1
2
3
4
function drawLines(ctx, pts) {
    ctx.moveTo(pts[0], pts[1]);
    for(i=2;i<pts.length-1;i+=2) ctx.lineTo(pts[i], pts[i+1]);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
var ctx = document.getElementById("c").getContext("2d");


function drawCurve(ctx, ptsa, tension, isClosed, numOfSegments, showPoints) {

  ctx.beginPath();

  drawLines(ctx, getCurvePoints(ptsa, tension, isClosed, numOfSegments));
 
  if (showPoints) {
    ctx.beginPath();
    for(var i=0;i<ptsa.length-1;i+=2)
      ctx.rect(ptsa[i] - 2, ptsa[i+1] - 2, 4, 4);
  }

  ctx.stroke();
}


var myPoints = [10,10, 40,30, 100,10, 200, 100, 200, 50, 250, 120]; //minimum two points
var tension = 1;

drawCurve(ctx, myPoints); //default tension=0.5
drawCurve(ctx, myPoints, tension);


function getCurvePoints(pts, tension, isClosed, numOfSegments) {

  // use input value if provided, or use a default value\t
  tension = (typeof tension != 'undefined') ? tension : 0.5;
  isClosed = isClosed ? isClosed : false;
  numOfSegments = numOfSegments ? numOfSegments : 16;

  var _pts = [], res = [],\t// clone array
      x, y,\t\t\t// our x,y coords
      t1x, t2x, t1y, t2y,\t// tension vectors
      c1, c2, c3, c4,\t\t// cardinal points
      st, t, i;\t\t// steps based on num. of segments

  // clone array so we don't change the original
  //
  _pts = pts.slice(0);

  // The algorithm require a previous and next point to the actual point array.
  // Check if we will draw closed or open curve.
  // If closed, copy end points to beginning and first points to end
  // If open, duplicate first points to befinning, end points to end
  if (isClosed) {
    _pts.unshift(pts[pts.length - 1]);
    _pts.unshift(pts[pts.length - 2]);
    _pts.unshift(pts[pts.length - 1]);
    _pts.unshift(pts[pts.length - 2]);
    _pts.push(pts[0]);
    _pts.push(pts[1]);
  }
  else {
    _pts.unshift(pts[1]);\t//copy 1. point and insert at beginning
    _pts.unshift(pts[0]);
    _pts.push(pts[pts.length - 2]);\t//copy last point and append
    _pts.push(pts[pts.length - 1]);
  }

  // ok, lets start..

  // 1. loop goes through point array
  // 2. loop goes through each segment between the 2 pts + 1e point before and after
  for (i=2; i < (_pts.length - 4); i+=2) {
    for (t=0; t <= numOfSegments; t++) {

      // calc tension vectors
      t1x = (_pts[i+2] - _pts[i-2]) * tension;
      t2x = (_pts[i+4] - _pts[i]) * tension;

      t1y = (_pts[i+3] - _pts[i-1]) * tension;
      t2y = (_pts[i+5] - _pts[i+1]) * tension;

      // calc step
      st = t / numOfSegments;

      // calc cardinals
      c1 =   2 * Math.pow(st, 3) \t- 3 * Math.pow(st, 2) + 1;
      c2 = -(2 * Math.pow(st, 3)) + 3 * Math.pow(st, 2);
      c3 = \t   Math.pow(st, 3)\t- 2 * Math.pow(st, 2) + st;
      c4 = \t   Math.pow(st, 3)\t- \t  Math.pow(st, 2);

      // calc x and y cords with common control vectors
      x = c1 * _pts[i]\t+ c2 * _pts[i+2] + c3 * t1x + c4 * t2x;
      y = c1 * _pts[i+1]\t+ c2 * _pts[i+3] + c3 * t1y + c4 * t2y;

      //store points in array
      res.push(x);
      res.push(y);

    }
  }

  return res;
}

function drawLines(ctx, pts) {
  ctx.moveTo(pts[0], pts[1]);
  for(i=2;i<pts.length-1;i+=2) ctx.lineTo(pts[i], pts[i+1]);
}
1
canvas { border: 1px solid red; }
1
<canvas id="c"><canvas>

结果是:

Example pix

您可以轻松扩展画布,因此可以这样调用它:

1
ctx.drawCurve(myPoints);

将以下内容添加到javascript中:

1
2
3
4
5
if (CanvasRenderingContext2D != 'undefined') {
    CanvasRenderingContext2D.prototype.drawCurve =
        function(pts, tension, isClosed, numOfSegments, showPoints) {
       drawCurve(this, pts, tension, isClosed, numOfSegments, showPoints)}
}

您可以在NPM(npm i cardinal-spline-js)或GitLab上找到更优化的版本。


第一个答案并不能说明所有问题。该图将精确地穿过所有点,并且将是一条完美曲线,其中点[[x:,y:}] n个这样的点。

1
2
3
4
5
6
7
8
9
10
11
12
13
var points = [{x:1,y:1},{x:2,y:3},{x:3,y:4},{x:4,y:2},{x:5,y:6}] //took 5 example points
ctx.moveTo((points[0].x), points[0].y);

for(var i = 0; i < points.length-1; i ++)
{

  var x_mid = (points[i].x + points[i+1].x) / 2;
  var y_mid = (points[i].y + points[i+1].y) / 2;
  var cp_x1 = (x_mid + points[i].x) / 2;
  var cp_x2 = (x_mid + points[i+1].x) / 2;
  ctx.quadraticCurveTo(cp_x1,points[i].y ,x_mid, y_mid);
  ctx.quadraticCurveTo(cp_x2,points[i+1].y ,points[i+1].x,points[i+1].y);
}


正如Daniel Howard所指出的,Rob Spencer在http://scaledinnovation.com/analytics/splines/aboutSplines.html上描述了您想要的东西。

这是一个交互式演示:http://jsbin.com/ApitIxo/2/

这是jsbin关闭时的摘要。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
<!DOCTYPE html>
    <html>
      <head>
        <meta charset=utf-8 />
        Demo smooth connection
      </head>
      <body>
       
          Click to build a smooth path.
          (See Rob Spencer's article)
          <label><input type="checkbox" id="showPoints" checked> Show points</label>
          <label><input type="checkbox" id="showControlLines" checked> Show control lines</label>
         
          <label>
            <input type="range" id="tension" min="-1" max="2" step=".1" value=".5"> Tension <span id="tensionvalue">(0.5)</span>
          </label>
       
       
        <canvas id="canvas"></canvas>
        <style>
          html { position: relative; height: 100%; width: 100%; }
          body { position: absolute; left: 0; right: 0; top: 0; bottom: 0; }
          canvas { outline: 1px solid red; }
          #display { position: fixed; margin: 8px; background: white; z-index: 1; }
        </style>
       
          function update() {
            $("tensionvalue").innerHTML="("+$("tension").value+")";
            drawSplines();
          }
          $("showPoints").onchange = $("showControlLines").onchange = $("tension").onchange = update;
     
          // utility function
          function $(id){ return document.getElementById(id); }
          var canvas=$("canvas"), ctx=canvas.getContext("2d");

          function setCanvasSize() {
            canvas.width = parseInt(window.getComputedStyle(document.body).width);
            canvas.height = parseInt(window.getComputedStyle(document.body).height);
          }
          window.onload = window.onresize = setCanvasSize();
     
          function mousePositionOnCanvas(e) {
            var el=e.target, c=el;
            var scaleX = c.width/c.offsetWidth || 1;
            var scaleY = c.height/c.offsetHeight || 1;
         
            if (!isNaN(e.offsetX))
              return { x:e.offsetX*scaleX, y:e.offsetY*scaleY };
         
            var x=e.pageX, y=e.pageY;
            do {
              x -= el.offsetLeft;
              y -= el.offsetTop;
              el = el.offsetParent;
            } while (el);
            return { x: x*scaleX, y: y*scaleY };
          }
     
          canvas.onclick = function(e){
            var p = mousePositionOnCanvas(e);
            addSplinePoint(p.x, p.y);
          };
     
          function drawPoint(x,y,color){
            ctx.save();
            ctx.fillStyle=color;
            ctx.beginPath();
            ctx.arc(x,y,3,0,2*Math.PI);
            ctx.fill()
            ctx.restore();
          }
          canvas.onmousemove = function(e) {
            var p = mousePositionOnCanvas(e);
            $("mouse").innerHTML = p.x+","+p.y;
          };
     
          var pts=[]; // a list of x and ys

          // given an array of x,y'
s, return distance between any two,
          // note that i and j are indexes to the points, not directly into the array.
          function dista(arr, i, j) {
            return Math.sqrt(Math.pow(arr[2*i]-arr[2*j], 2) + Math.pow(arr[2*i+1]-arr[2*j+1], 2));
          }

          // return vector from i to j where i and j are indexes pointing into an array of points.
          function va(arr, i, j){
            return [arr[2*j]-arr[2*i], arr[2*j+1]-arr[2*i+1]]
          }
     
          function ctlpts(x1,y1,x2,y2,x3,y3) {
            var t = $("tension").value;
            var v = va(arguments, 0, 2);
            var d01 = dista(arguments, 0, 1);
            var d12 = dista(arguments, 1, 2);
            var d012 = d01 + d12;
            return [x2 - v[0] * t * d01 / d012, y2 - v[1] * t * d01 / d012,
                    x2 + v[0] * t * d12 / d012, y2 + v[1] * t * d12 / d012 ];
          }

          function addSplinePoint(x, y){
            pts.push(x); pts.push(y);
            drawSplines();
          }
          function drawSplines() {
            clear();
            cps = []; // There will be two control points for each"middle" point, 1 ... len-2e
            for (var i = 0; i < pts.length - 2; i += 1) {
              cps = cps.concat(ctlpts(pts[2*i], pts[2*i+1],
                                      pts[2*i+2], pts[2*i+3],
                                      pts[2*i+4], pts[2*i+5]));
            }
            if ($("showControlLines").checked) drawControlPoints(cps);
            if ($("showPoints").checked) drawPoints(pts);
   
            drawCurvedPath(cps, pts);
 
          }
          function drawControlPoints(cps) {
            for (var i = 0; i < cps.length; i += 4) {
              showPt(cps[i], cps[i+1],"pink");
              showPt(cps[i+2], cps[i+3],"pink");
              drawLine(cps[i], cps[i+1], cps[i+2], cps[i+3],"pink");
            }
          }
     
          function drawPoints(pts) {
            for (var i = 0; i < pts.length; i += 2) {
              showPt(pts[i], pts[i+1],"black");
            }
          }
     
          function drawCurvedPath(cps, pts){
            var len = pts.length / 2; // number of points
            if (len < 2) return;
            if (len == 2) {
              ctx.beginPath();
              ctx.moveTo(pts[0], pts[1]);
              ctx.lineTo(pts[2], pts[3]);
              ctx.stroke();
            }
            else {
              ctx.beginPath();
              ctx.moveTo(pts[0], pts[1]);
              // from point 0 to point 1 is a quadratic
              ctx.quadraticCurveTo(cps[0], cps[1], pts[2], pts[3]);
              // for all middle points, connect with bezier
              for (var i = 2; i < len-1; i += 1) {
                // console.log("to", pts[2*i], pts[2*i+1]);
                ctx.bezierCurveTo(
                  cps[(2*(i-1)-1)*2], cps[(2*(i-1)-1)*2+1],
                  cps[(2*(i-1))*2], cps[(2*(i-1))*2+1],
                  pts[i*2], pts[i*2+1]);
              }
              ctx.quadraticCurveTo(
                cps[(2*(i-1)-1)*2], cps[(2*(i-1)-1)*2+1],
                pts[i*2], pts[i*2+1]);
              ctx.stroke();
            }
          }
          function clear() {
            ctx.save();
            // use alpha to fade out
            ctx.fillStyle ="rgba(255,255,255,.7)"; // clear screen
            ctx.fillRect(0,0,canvas.width,canvas.height);
            ctx.restore();
          }
     
          function showPt(x,y,fillStyle) {
            ctx.save();
            ctx.beginPath();
            if (fillStyle) {
              ctx.fillStyle = fillStyle;
            }
            ctx.arc(x, y, 5, 0, 2*Math.PI);
            ctx.fill();
            ctx.restore();
          }

          function drawLine(x1, y1, x2, y2, strokeStyle){
            ctx.beginPath();
            ctx.moveTo(x1, y1);
            ctx.lineTo(x2, y2);
            if (strokeStyle) {
              ctx.save();
              ctx.strokeStyle = strokeStyle;
              ctx.stroke();
              ctx.restore();
            }
            else {
              ctx.save();
              ctx.strokeStyle ="pink";
              ctx.stroke();
              ctx.restore();
            }
          }

       


      </body>
    </html>


我发现这很好用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function drawCurve(points, tension) {
    ctx.beginPath();
    ctx.moveTo(points[0].x, points[0].y);

    var t = (tension != null) ? tension : 1;
    for (var i = 0; i < points.length - 1; i++) {
        var p0 = (i > 0) ? points[i - 1] : points[0];
        var p1 = points[i];
        var p2 = points[i + 1];
        var p3 = (i != points.length - 2) ? points[i + 2] : p2;

        var cp1x = p1.x + (p2.x - p0.x) / 6 * t;
        var cp1y = p1.y + (p2.y - p0.y) / 6 * t;

        var cp2x = p2.x - (p3.x - p1.x) / 6 * t;
        var cp2y = p2.y - (p3.y - p1.y) / 6 * t;

        ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, p2.x, p2.y);
    }
    ctx.stroke();
}

我决定添加内容,而不是将解决方案发布到另一篇文章中。
以下是我构建的解决方案,可能并不完美,但到目前为止输出效果很好。

重要提示:它将贯穿所有要点!

如果您有任何想法,请使其改善。谢谢。

以下是之前和之后的比较:

enter image description here

将此代码保存为HTML进行测试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
    <!DOCTYPE html>
    <html>
    <body>
    \t<canvas id="myCanvas" width="1200" height="700" style="border:1px solid #d3d3d3;">Your browser does not support the HTML5 canvas tag.</canvas>
    \t
    \t\tvar cv = document.getElementById("myCanvas");
    \t\tvar ctx = cv.getContext("2d");
   
    \t\tfunction gradient(a, b) {
    \t\t\treturn (b.y-a.y)/(b.x-a.x);
    \t\t}
   
    \t\tfunction bzCurve(points, f, t) {
    \t\t\t//f = 0, will be straight line
    \t\t\t//t suppose to be 1, but changing the value can control the smoothness too
    \t\t\tif (typeof(f) == 'undefined') f = 0.3;
    \t\t\tif (typeof(t) == 'undefined') t = 0.6;
   
    \t\t\tctx.beginPath();
    \t\t\tctx.moveTo(points[0].x, points[0].y);
   
    \t\t\tvar m = 0;
    \t\t\tvar dx1 = 0;
    \t\t\tvar dy1 = 0;
   
    \t\t\tvar preP = points[0];
    \t\t\tfor (var i = 1; i < points.length; i++) {
    \t\t\t\tvar curP = points[i];
    \t\t\t\tnexP = points[i + 1];
    \t\t\t\tif (nexP) {
    \t\t\t\t\tm = gradient(preP, nexP);
    \t\t\t\t\tdx2 = (nexP.x - curP.x) * -f;
    \t\t\t\t\tdy2 = dx2 * m * t;
    \t\t\t\t} else {
    \t\t\t\t\tdx2 = 0;
    \t\t\t\t\tdy2 = 0;
    \t\t\t\t}
    \t\t\t\tctx.bezierCurveTo(preP.x - dx1, preP.y - dy1, curP.x + dx2, curP.y + dy2, curP.x, curP.y);
    \t\t\t\tdx1 = dx2;
    \t\t\t\tdy1 = dy2;
    \t\t\t\tpreP = curP;
    \t\t\t}
    \t\t\tctx.stroke();
    \t\t}
   
    \t\t// Generate random data
    \t\tvar lines = [];
    \t\tvar X = 10;
    \t\tvar t = 40; //to control width of X
    \t\tfor (var i = 0; i < 100; i++ ) {
    \t\t\tY = Math.floor((Math.random() * 300) + 50);
    \t\t\tp = { x: X, y: Y };
    \t\t\tlines.push(p);
    \t\t\tX = X + t;
    \t\t}
   
    \t\t//draw straight line
    \t\tctx.beginPath();
    \t\tctx.setLineDash([5]);
    \t\tctx.lineWidth = 1;
    \t\tbzCurve(lines, 0, 1);
   
    \t\t//draw smooth line
    \t\tctx.setLineDash([0]);
    \t\tctx.lineWidth = 2;
    \t\tctx.strokeStyle ="blue";
    \t\tbzCurve(lines, 0.3, 1);
    \t
    </body>
    </html>


尝试一下KineticJS-您可以使用点数组定义样条线。这是一个例子:

旧网址:http://www.html5canvastutorials.com/kineticjs/html5-canvas-kineticjs-spline-tutorial/

参见存档网址:https://web.archive.org/web/20141204030628/http://www.html5canvastutorials.com/kineticjs/html5-canvas-kineticjs-spline-tutorial/


太晚了,但受到Homan出色的简单答案的启发,让我发布了一个更通用的解决方案(一般意义上来说,Homan的解决方案在少于3个顶点的点阵列上发生崩溃):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function smooth(ctx, points)
{
    if(points == undefined || points.length == 0)
    {
        return true;
    }
    if(points.length == 1)
    {
        ctx.moveTo(points[0].x, points[0].y);
        ctx.lineTo(points[0].x, points[0].y);
        return true;
    }
    if(points.length == 2)
    {
        ctx.moveTo(points[0].x, points[0].y);
        ctx.lineTo(points[1].x, points[1].y);
        return true;
    }
    ctx.moveTo(points[0].x, points[0].y);
    for (var i = 1; i < points.length - 2; i ++)
    {
        var xc = (points[i].x + points[i + 1].x) / 2;
        var yc = (points[i].y + points[i + 1].y) / 2;
        ctx.quadraticCurveTo(points[i].x, points[i].y, xc, yc);
    }
    ctx.quadraticCurveTo(points[i].x, points[i].y, points[i+1].x, points[i+1].y);
}

如果要通过n个点确定曲线方程,则以下代码将为您提供n-1阶多项式的系数,并将这些系数保存到coefficients[]数组(从常数项开始)。 x坐标不必按顺序排列。这是拉格朗日多项式的一个示例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var xPoints=[2,4,3,6,7,10]; //example coordinates
var yPoints=[2,5,-2,0,2,8];
var coefficients=[];
for (var m=0; m<xPoints.length; m++) coefficients[m]=0;
    for (var m=0; m<xPoints.length; m++) {
        var newCoefficients=[];
        for (var nc=0; nc<xPoints.length; nc++) newCoefficients[nc]=0;
        if (m>0) {
            newCoefficients[0]=-xPoints[0]/(xPoints[m]-xPoints[0]);
            newCoefficients[1]=1/(xPoints[m]-xPoints[0]);
    } else {
        newCoefficients[0]=-xPoints[1]/(xPoints[m]-xPoints[1]);
        newCoefficients[1]=1/(xPoints[m]-xPoints[1]);
    }
    var startIndex=1;
    if (m==0) startIndex=2;
    for (var n=startIndex; n<xPoints.length; n++) {
        if (m==n) continue;
        for (var nc=xPoints.length-1; nc>=1; nc--) {
        newCoefficients[nc]=newCoefficients[nc]*(-xPoints[n]/(xPoints[m]-xPoints[n]))+newCoefficients[nc-1]/(xPoints[m]-xPoints[n]);
        }
        newCoefficients[0]=newCoefficients[0]*(-xPoints[n]/(xPoints[m]-xPoints[n]));
    }    
    for (var nc=0; nc<xPoints.length; nc++) coefficients[nc]+=yPoints[m]*newCoefficients[nc];
}

为了添加到K3N的基本样条方法中,并可能解决T. J. Crowder对在误导位置"浸入"曲线的担忧,我在getCurvePoints()函数中的res.push(x);之前插入了以下代码

1
2
3
4
5
6
if ((y < _pts[i+1] && y < _pts[i+3]) || (y > _pts[i+1] && y > _pts[i+3])) {
    y = (_pts[i+1] + _pts[i+3]) / 2;
}
if ((x < _pts[i] && x < _pts[i+2]) || (x > _pts[i] && x > _pts[i+2])) {
    x = (_pts[i] + _pts[i+2]) / 2;
}

这有效地在每对连续点之间创建一个(不可见的)边界框,并确保曲线停留在该边界框内-即。如果曲线上的一个点在两个点的上方/下方/左侧/右侧,则其位置将更改为位于框内。这里使用了中点,但是可以通过使用线性插值来改善。