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阶贝塞尔曲线

线性贝塞尔曲线

给定点P0P1,线性贝塞尔曲线只是一条两点之间的直线,这条线由下面的公式可以计算出来:

线性贝塞尔曲线函数中的t会经过由P0P1B(t)所描述的曲线。例如当t=0.25时,B(t)即一条由点P0P1路径的四分之一处。就像由01的连续tB(t)描述一条由P0P1的直线。

上图演示了:线性贝塞尔曲线演示动画,t[0,1]区间,其实就是绘制了一条直线。

二次方贝塞尔曲线

二次方贝塞尔曲线的路径由给定点P0P1P2的函数B(t)追踪:

为建构二次贝塞尔曲线,可以中介点Q0Q1作为由01t

  • P0P1的连续点Q0,描述一条线性贝塞尔曲线。
  • P1P2的连续点Q1,描述一条线性贝塞尔曲线。
  • Q0Q1的连续点B(t),描述一条二次贝塞尔曲线。

三次方贝塞尔曲线

P0P1P2P3四个点在平面或在三维空间中定义了三次方贝塞尔曲线。曲线起始于P0走向P1,并从P2的方向来到P3。一般不会经过P1P2;这两个点只是在那里提供方向资讯。P0P1之间的间距,决定了曲线在转而趋进P2之前,走向P1方向的“长度有多长”。

对于三次曲线,可由线性贝塞尔曲线描述的中介点Q0Q1Q2,和由二次曲线描述的点R0R1所建构:

n阶贝塞尔曲线

n阶贝塞尔曲线也称为高阶贝塞尔曲线。n阶贝塞尔曲线可如下推断。给定点P0P1、…、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阶贝塞尔曲线,实现一些更有意思的效果。大家可以开动脑筋,自己动手,实现自己的创意。

大漠

常用昵称“大漠”,W3CPlus创始人,目前就职于手淘。对HTML5、CSS3和Sass等前端脚本语言有非常深入的认识和丰富的实践经验,尤其专注对CSS3的研究,是国内最早研究和使用CSS3技术的一批人。CSS3、Sass和Drupal中国布道者。2014年出版《图解CSS3:核心技术与案例实战》。

如需转载,烦请注明出处:https://www.fedev.cn/canvas/drawing-curve.htmlNike React Element 87 Dusty Peach