Canvas学习:贝塞尔曲线
在绘制圆和圆弧一节中,了解到在Canvas中可以使用arc()
和arcTo()
绘制制圆或弧线,但很多时候,仅这两个方法还不能满足我们实际的需求,特别是绘制复杂的曲线。不过值得庆幸的是,在Canvas中还提供了其他的方法可以帮助我们绘制复杂的曲线。那就是我们今天要说的贝塞尔曲线,在Canvas中提供了两个独立的方法:quadraticCurveTo()
和bezierCurveTo()
方法。这两个方法就是贝塞尔曲线。
贝塞尔曲线
相信很多同学都知道“贝塞尔曲线”这个词,我们在很多地方都能经常看到。但是,可能并不是每位同学都清楚地知道,到底什么是“贝塞尔曲线”,又是什么特点让它有这么高的知名度。
贝塞尔曲线的数学基础是早在 1912 年就广为人知的伯恩斯坦多项式。但直到 1959 年,当时就职于雪铁龙的法国数学家 Paul de Casteljau 才开始对它进行图形化应用的尝试,并提出了一种数值稳定的 de Casteljau 算法。然而贝塞尔曲线的得名,却是由于 1962 年另一位就职于雷诺的法国工程师 Pierre Bézier 的广泛宣传。他使用这种只需要很少的控制点就能够生成复杂平滑曲线的方法,来辅助汽车车体的工业设计。
正是因为控制简便却具有极强的描述能力,贝塞尔曲线在工业设计领域迅速得到了广泛的应用。不仅如此,在计算机图形学领域,尤其是矢量图形学,贝塞尔曲线也占有重要的地位。今天我们最常见的一些矢量绘图软件,如 Flash、Illustrator、CorelDraw 等,无一例外都提供了绘制贝塞尔曲线的功能。甚至像 Photoshop 这样的位图编辑软件,也把贝塞尔曲线作为仅有的矢量绘制工具(钢笔工具)包含其中。
贝塞尔曲线在 web 开发领域同样占有一席之地。CSS3 新增了 transition-timing-function
属性,它的取值就可以设置为一个三次贝塞尔曲线方程。在此之前,也有不少 JavaScript 动画库使用贝塞尔曲线来实现美观逼真的缓动效果。
有关于贝塞尔曲线的详细介绍,可以查看维基百科的相关介绍。
贝塞尔曲线实例化
对于贝塞尔曲线,我们常看到的有:
- 线性贝塞尔曲线
- 二次方贝塞尔曲线
- 三次方贝塞尔曲线
n
阶贝塞尔曲线
线性贝塞尔曲线
给定点P0
、P1
,线性贝塞尔曲线只是一条两点之间的直线,这条线由下面的公式可以计算出来:
线性贝塞尔曲线函数中的t
会经过由P0
至P1
的B(t)
所描述的曲线。例如当t=0.25
时,B(t)
即一条由点P0
至P1
路径的四分之一处。就像由0
至1
的连续t
,B(t)
描述一条由P0
至P1
的直线。
上图演示了:线性贝塞尔曲线演示动画,t
在[0,1]
区间,其实就是绘制了一条直线。
二次方贝塞尔曲线
二次方贝塞尔曲线的路径由给定点P0
、P1
、P2
的函数B(t)
追踪:
为建构二次贝塞尔曲线,可以中介点Q0
和Q1
作为由0
至1
的t
:
- 由
P0
至P1
的连续点Q0
,描述一条线性贝塞尔曲线。 - 由
P1
至P2
的连续点Q1
,描述一条线性贝塞尔曲线。 - 由
Q0
至Q1
的连续点B(t)
,描述一条二次贝塞尔曲线。
三次方贝塞尔曲线
P0
、P1
、P2
、P3
四个点在平面或在三维空间中定义了三次方贝塞尔曲线。曲线起始于P0
走向P1
,并从P2
的方向来到P3
。一般不会经过P1
或P2
;这两个点只是在那里提供方向资讯。P0
和P1
之间的间距,决定了曲线在转而趋进P2
之前,走向P1
方向的“长度有多长”。
对于三次曲线,可由线性贝塞尔曲线描述的中介点Q0
、Q1
、Q2
,和由二次曲线描述的点R0
、R1
所建构:
n阶贝塞尔曲线
n
阶贝塞尔曲线也称为高阶贝塞尔曲线。n
阶贝塞尔曲线可如下推断。给定点P0
、P1
、…、Pn
,其贝塞尔曲线即:
例如 n=5
:
还可参阅五阶贝塞尔曲线的构成:
针对这些,我们可以写一个函数实现Bézier Curve效果,比如下面这个DEMO,修改控制点的数量,就能实现不同的阶级的贝塞尔曲线:
来看个录制的动图效果:
贝塞尔典线绘制原理
上面通过维基百科,我们对贝塞尔曲线有了一定的了解,但还是只是理论性的,下面我们通过实例来了解,有助于大家更好的理解贝塞尔曲线。
下面的内容取自于@芋头大大的网站。
下面我们就通过例子来了解一下如何用de Casteljau 算法绘制一条贝塞尔曲线。
在平面内任选3
个不共线的点,依次用线段连接:
在第一条线段上任选一个点 D
。计算该点到线段起点的距离 AD
,与该线段总长 AB
的比例:
根据上一步得到的比例,从第二条线段上找出对应的点 E
,使得 AD:AB = BE:BC
:
连接这两点 DE
:
从新的线段 DE
上再次找出相同比例的点 F
,使得 DF:DE = AD:AB = BE:BC
:
到这里,我们就确定了贝塞尔曲线上的一个点 F
。接下来,请稍微回想一下中学所学的极限知识,让选取的点 D
在第一条线段上从起点 A
移动到终点 B
,找出所有的贝塞尔曲线上的点 F
。所有的点找出来之后,我们也得到了这条贝塞尔曲线。
如果你实在想象不出这个过程,没关系,看动画!
回过头来看这条贝塞尔曲线,为了确定曲线上的一个点,需要进行两轮取点的操作,因此我们称得到的贝塞尔曲线为二次曲线(这样记忆很直观,但曲线的次数其实是由前面提到的伯恩斯坦多项式决定的)。
当控制点个数为 4
时,情况是怎样的?
步骤都是相同的,只不过我们每确定一个贝塞尔曲线上的点,要进行三轮取点操作。如图,AE:AB = BF:BC = CG:CD = EH:EF = FI:FG = HJ:HI
,其中点 J
就是最终得到的贝塞尔曲线上的一个点。
这样我们得到的是一条三次贝塞尔曲线:
看过了二次和三次曲线,更高次的贝塞尔曲线大家应该也知道要怎么画了吧。要绘制更复杂的曲线,控制点的增加也仅仅是线性的。这一特点使其不光在工业设计领域大展拳脚,就连数学基础不好的人也可以比较容易地掌握,比如大多数平面美术设计师们。
Cavnas中使用贝塞尔曲线
通过前面的内容,我们对贝塞尔曲线有了一定的了解,那我们回到Canvas中来,那么在Canvas中怎么使用贝塞尔曲线的方法。
二次贝塞尔曲线
在Canvas中,二次贝塞尔曲线的方法如下:
quadraticCurveTo(cp1x, cp1y, x, y)
这个和arcTo()
有异曲同工之妙。P0
是起始点,所以通常搭配moveTo()
或lineTo()
使用。P1
是第一个控制点(cpx, cpy)
,P2
是终止点,也就是第二个控制点(x, y)
,它们之间不是相切的关系。
function drawScreen () {
ctx.lineWidth = 1;
ctx.strokeStyle = '#f36';
ctx.fillStyle = 'red';
// 一个起始点 ( 100, 50 ), 那么绘制其点. 颜色设置为红色
ctx.fillRect( 100 - 4, 50 - 4, 8, 8 );
// 两个参考点分别为 ( 100, 200 ) 和 ( 300, 200 ), 绘制出该点
ctx.fillRect( 100 - 4, 200 - 4, 8, 8 );
ctx.fillRect( 300 - 4, 200 - 4, 8, 8 );
// 连接两个参考点
ctx.beginPath();
ctx.strokeStyle = 'red';
ctx.moveTo(100, 50);
ctx.lineTo( 100, 200 );
ctx.lineTo( 300, 200 );
ctx.stroke();
// 调用 quadraticCurveTo()
ctx.beginPath();
ctx.strokeStyle = 'blue';
ctx.moveTo( 100, 50 );
ctx.quadraticCurveTo( 100, 200, 300, 200);
ctx.stroke();
}
首先通过moveTo()
确定第一个点,然后quadraticCurveTo()
绘制出二次贝塞尔曲线。根据这个原理,配合一些鼠标事件,我们可以动态实现二次贝塞尔曲线:
我们平常在项目中,特别是在聊天工具中气泡效果,在Canvas中使用二次贝塞尔曲线,很容易实现一个气泡效果:
function drawScreen () {
ctx.lineWidth = 1;
ctx.strokeStyle = '#f36';
ctx.fillStyle = 'red';
ctx.beginPath();
ctx.moveTo(75, 25);
ctx.quadraticCurveTo(25, 25, 25, 62.5);
ctx.quadraticCurveTo(25, 100, 50, 100);
ctx.quadraticCurveTo(50, 120, 30, 125);
ctx.quadraticCurveTo(60, 120, 65, 100);
ctx.quadraticCurveTo(125, 100, 125, 62.5);
ctx.quadraticCurveTo(125, 25, 75, 25);
ctx.stroke();
}
三次贝塞尔曲线
在Canvas中,除了提供了二次贝塞尔曲线方法之外,还提供了绘制三次贝塞尔曲线的方法bezierCurveTo()
。绘制三次贝塞尔曲线代码如下:
bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
这个方法可谓是绘制波浪线的神器。根据之前的结论,n
阶贝塞尔曲线就有n-1
个控制点,所以三次贝塞尔曲线有1
个起始点(这个起点也可以通过moveTo()
或lineTo()
)、1
个终止点、2
个控制点。因此传入的6
个参数分别为控制点cp1
(坐标(cp1x, cp1y)
),控制点cp2
(坐标(cp2x, cp2y)
),与终止点 (x, y)
。
ctx.beginPath();
ctx.strokeStyle = 'blue';
ctx.moveTo( 100, 50 );
ctx.bezierCurveTo( 100, 200, 300, 200, 400, 150);
ctx.stroke();
和quadraticCurveTo()
方法一样,可以通过JavaScript定制一个在线的工具,实现三次贝塞尔曲线:
使用bezierCurveTo()
我们可以很轻易的实现一个心形效果:
function drawScreen () {
ctx.lineWidth = 1;
ctx.fillStyle = 'red';
ctx.beginPath();
ctx.moveTo(75, 40);
ctx.bezierCurveTo(75, 37, 70, 25, 50, 25);
ctx.bezierCurveTo(20, 25, 20, 62.5, 20, 62.5);
ctx.bezierCurveTo(20, 80, 40, 102, 75, 120);
ctx.bezierCurveTo(110, 102, 130, 80, 130, 62.5);
ctx.bezierCurveTo(130, 62.5, 130, 25, 100, 25);
ctx.bezierCurveTo(85, 25, 75, 37, 75, 40);
ctx.fill();
}
在Canvas中提供的quadraticCurveTo()
和bezierCurveTo()
方法只是二次贝塞尔曲线和三次贝塞尔曲线方法。但实际上还有n
阶贝塞尔曲线,实现这功能是需要JavaScript方法做相关处理。比如下面的这个示例:
案例:水波效果
前面也提到过了,贝塞尔曲线是实现波纹效果的一个神器。既然前面我们对贝塞尔曲线有了一定的了解,我们来做一个实例,实现一个水波效果。这个效果来自于alloyteam团队,具体代码就不在这里展示了,效果如下:
总结
这篇文章主要介绍了贝塞尔曲线的相关知识,简单的介绍了在Canvas中二次贝塞尔曲线quadraticCurveTo()
和三次贝塞尔曲线bezierCurveTo()
方法绘制复杂曲线。同样使用这两个方法可以绘制不同的图形,比如气泡和心形效果。当然除此之外,还可以通过JavaScript实现n
阶贝塞尔曲线,实现一些更有意思的效果。大家可以开动脑筋,自己动手,实现自己的创意。
如需转载,烦请注明出处:https://www.fedev.cn/canvas/drawing-curve.htmlNike React Element 87 Dusty Peach