平滑多条曲线
Canvas 画曲线(非圆弧)有两个方法:
1. quadraticCurveTo(cpx, cpy, x, y)
2. bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)
本文只讲quadraticCurveTo(),先来看如何画一条曲线。
我们需要曲线的 开始点(p0) 和 结束点(p1),外加一个控制点(pc),虽然说有3个点,但实际画图时,我们关心的只是开始点和结束点
可以看到曲线经过 p0 和 p1 两个点,控制点pc 只是用来调整曲线的形状的。
如果曲线要经过3个点呢?不好做了吧?
这里介绍一个公式,从上图可知,曲线不经过控制点,但曲线肯定会经过某个点,这个点怎么求呢?
xc = x1 * 2 - (x0 + x2) / 2; yc = y1 * 2 - (y0 + y2) / 2;
(x0, y0) 是开始点,(x2, y2) 是结束点,(xc, yc) 是控制点,而 (x1, y1) 则是曲线会经过的某点。
继续刚才的3点画线,我们可以把第二个点当作点(x1, y1),于是
如果有4个点呢?这里需要总结一个算法,先看一个图:
首先声明一下,这张图原本不是用来解释本文的,我从别处截来,但是可以讲解我想说的东西。
这条曲线很平滑,注意中间的两个黑点,他俩都是相邻控制点的中点,于是我得出了以下结论:
通过第一个控制点,循环算出之后的控制点,相邻的两个控制点的中点是曲线会经过的点。
仔细看3个点的例子,我们算出了控制点,它就是第一个控制点。
第二个控制点怎么算呢?
我们知道曲线会经过p2,所以 p2 是第一个控制点 和 第二个控制点的中点,于是
// (ctrlX, ctrlY) 是第一个控制点 ctrlX = 2 * x2 - ctrlX; ctrlY = 2 * y2 - ctrlY;
之后的控制点都可以这样计算,下面我给出一段算法:
/** * @param {object} c 即canvas.getContext('2d') * @param {array} points 数组元素格式为{x: 0, y: 0} */ function drawMultipleCurves(c, points) { var len = points.length; if (len < 3) { return; } // 计算第一个控制点 var ctrlX = 2 * points[1].x - 0.5 * (points[0].x + points[2].x); ctrlY = 2 * points[1].y - 0.5 * (points[0].y + points[2].y); // 画出前三个点的曲线 c.beginPath(); c.moveTo(points[0].x, points[0].y); c.quadraticCurveTo(ctrlX, ctrlY, points[2].x, points[2].y); if (len === 3) { c.stroke(); return; } for (var i = 2; i < len - 1; i++) { // 计算下个控制点 ctrlX = 2 * points[i].x - ctrlX; ctrlY = 2 * points[i].y - ctrlY; c.quadraticCurveTo(ctrlX, ctrlY, points[i + 1].x, points[i + 1].y); } c.stroke(); }
最后看一个经过6个点的曲线吧