定义
在数学的数值分析领域中,贝塞尔曲线(Bézier curve)是计算机图形学中相当重要的参数曲线。更高维度的广泛化贝塞尔曲线就称作贝塞尔曲面,其中贝塞尔三角是一种特殊的实例。
贝塞尔曲线于1962年,由法国工程师皮埃尔·贝兹(Pierre Bézier)所广泛发表,他运用贝塞尔曲线来为汽车的主体进行设计。贝塞尔曲线最初由保尔·德·卡斯特里奥于1959年运用德卡斯特里奥算法开发,以稳定数值的方法求出贝塞尔曲线。
贝塞尔曲线在平面绘图中有着广泛的应用,一般的矢量图形软件通过它来精确画出曲线。贝塞尔曲线由线段与节点组成,节点是可拖动的支点,线段像可伸缩的皮筋,我们在绘图工具上看到的钢笔工具就是来做这种矢量曲线的。贝塞尔曲线是计算机图形学中相当重要的参数曲线,在一些比较成熟的位图软件中也有贝塞尔曲线工具,如PhotoShop等。
如何绘制贝塞尔曲线
线性(一阶)贝塞尔曲线
一阶贝塞尔曲线函数中的t会经过由P~0~至P~1~的B(t)所描述的曲线。例如当t=0.25时,B(t)即一条由点P~0~至P~1~路径的四分之一处。就像由0至1的连续t,B(t)描述一条由P0至P1的直线。
对于一阶贝塞尔曲线,可以很容易根据t的值得出线段上那个点的坐标:
<math xmlns="http://www.w3.org/1998/Math/MathML"> B 1 ( t ) = P 0 + ( P 1 − P 0 ) t B_{1}(t) = P_0 + (P_1 - P_0)t </math>B1(t)=P0+(P1−P0)t
然后可以得出:
<math xmlns="http://www.w3.org/1998/Math/MathML"> B 1 ( t ) = ( 1 − t ) P 0 + t P 1 , t ∈ [ 0 , 1 ] B_{1}(t) = (1 - t)P_0 + tP_1,t\in[0,1] </math>B1(t)=(1−t)P0+tP1,t∈[0,1]
我们说的应该是曲线啊,这这这不是直线么?!!先别着急,我们一般说的贝塞尔曲线都不是这种,但是要把曲线绘制出来都离不开它,我们接着往下看。
二阶曲线
这便是最常见的一个标准的二阶贝塞尔曲线,它由三个点 P~0~、P~1~、P~2~组成,其中 P~0~、P~2~ 为起点或终点,P~1~ 为 控制点 ,Q~0~Q~1~为中介点。我们来看看应该怎么绘制这种曲线。
- 首先我们需要三个点:P~0~,P~1~,P~2~,并连接 P~0~P~1~ 和 P~1~P~2~
- 在P~0~P~1~和P~1~P~2~两条线上分别找到两个点Q~0~、Q~1~,并使等式 <math xmlns="http://www.w3.org/1998/Math/MathML"> P 0 Q 0 P 0 P 1 = P 1 Q 1 P 1 P 2 \frac{P_0Q_0} {P_0P_1}=\frac{P_1Q_1}{P_1P_2} </math>P0P1P0Q0=P1P2P1Q1成立,连接Q~0~Q~1~
- 在 Q0Q1 上找到一个点T,并使 <math xmlns="http://www.w3.org/1998/Math/MathML"> P 0 Q 0 Q 0 P 1 = P 1 Q 1 Q 1 P 2 = Q 0 T Q 0 Q 1 \frac{P_0Q_0} {Q_0P_1}=\frac{P_1Q_1}{Q_1P_2}=\frac{Q_0T}{Q_0Q_1} </math>Q0P1P0Q0=Q1P2P1Q1=Q0Q1Q0T
到这一步我们便找到了贝塞尔曲线上的一个点,剩下我们要做的工作就是找到所有上述符合条件的点,便可以绘制出贝塞尔曲线,如下图:
我们来看看是怎么收集这些点的
|-----------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------|
| |
|
结合这两张图我们可以看出来 B 点的变化是由 Q~0~ 和 Q~1~ 两个点共同影响的,这三个点在做同一件事,就是在同一时间内由起点移动到终点,有点区别的是 Q~0~Q~1~ 这个线段在不断变化。最终点 B 的运动过程就形成了贝塞尔曲线,到这里大家应该就能明白了吧。
总结一下,为建构二次贝塞尔曲线,可以中介点Q~0~和Q~1~作为由 0 至 1 的 t:
- 由P~0~至P~1~的连续点Q~0~,描述一条线性贝塞尔曲线。
- 由P~1~至P~2~的连续点Q~1~,描述一条线性贝塞尔曲线。
- 由Q~0~至Q~1~的连续点B(t),描述一条二次贝塞尔曲线。
无论多少阶的贝塞尔曲线,它的绘制过程都会进行降阶,四阶降三阶直至降到一阶
三阶及更高阶曲线
三阶贝塞尔曲线
这是一个三阶曲线,正如上文所说,高阶曲线绘制会有一个降阶的一个过程,我们来分析下,是怎样的过程。
- 三阶曲线有四个点P~0~、P~1~、P~2~、P~3~,连接相邻的点便有三条边P~0~P~1~、P~1~P~2~、P~2~P~3~
- 在三条边上取三个中介点 Q~0~、Q~1~、Q~2~,与二阶曲线一样满足 <math xmlns="http://www.w3.org/1998/Math/MathML"> P 0 Q 0 P 0 P 1 = P 1 Q 1 P 1 P 2 = P 2 Q 2 P 2 P 3 \frac{P_0Q_0} {P_0P_1}=\frac{P_1Q_1}{P_1P_2}=\frac{P_2Q_2}{P_2P_3} </math>P0P1P0Q0=P1P2P1Q1=P2P3P2Q2,设它们的比值为 <math xmlns="http://www.w3.org/1998/Math/MathML"> U U </math>U
- 连接Q~0~Q~1~、Q~1~Q~2~,并在两条边上取两点 R~0~、R~1~,连接R~0~R~1~取一点B,需要满足
<math xmlns="http://www.w3.org/1998/Math/MathML"> Q 0 R 0 Q 0 Q 1 = Q 1 R 1 Q 1 R 2 = R 0 B R 0 R 3 = U \frac{Q_0R_0} {Q_0Q_1}=\frac{Q_1R_1}{Q_1R_2}=\frac{R_0B}{R_0R_3}=U </math>Q0Q1Q0R0=Q1R2Q1R1=R0R3R0B=U,这个点 B 即为三阶曲线的其中一点 - 找到所有满足上述条件的点,便是三阶贝塞尔曲线
这时候细心的人变发现,第3步之后不就是二阶曲线的流程吗?确实是,不同的是二阶曲线的两条边是固定的,而此处 Q~0~、Q~1~、Q~2~是中介点,所以会不断变化。
来看看三阶曲线的绘制过程吧:
三阶贝塞尔曲线
于是乎,此处我们可以得出一个结论:n阶曲线可近似理解为由n个一阶曲线与(n-1)阶曲线相互作用而成。
四阶贝塞尔曲线
五阶贝塞尔曲线
在 Android Canvas 里绘制贝塞尔曲线
二阶贝塞尔曲线
在 Android 上绘制曲线一般会用路径工具 Path
来处理,Path
基本可以绘制出任何你想要的线条。所以贝塞尔曲线也不例外,可以通过 Paht#quadTo
方法来实现:
java
/**
* Add a quadratic bezier from the last point, approaching control point
* (x1,y1), and ending at (x2,y2). If no moveTo() call has been made for
* this contour, the first point is automatically set to (0,0).
*
* @param x1 The x-coordinate of the control point on a quadratic curve
* @param y1 The y-coordinate of the control point on a quadratic curve
* @param x2 The x-coordinate of the end point on a quadratic curve
* @param y2 The y-coordinate of the end point on a quadratic curve
*/
public void quadTo(float x1, float y1, float x2, float y2) {
nQuadTo(mNativePath, x1, y1, x2, y2);
}
参数是两个点:控制点和结束点。要绘制一个二阶曲线可以这样:
kotlin
val path = Path()
path.moveTo(pointA.x, pointA.y)
path.quadTo(pointB.x, pointB.y, pointC.x, pointC.y)
canvas.drawPath(path,paint)
这样便绘制了一个二阶的贝塞尔曲线
三阶贝塞尔曲线
java
/**
* Add a cubic bezier from the last point, approaching control points
* (x1,y1) and (x2,y2), and ending at (x3,y3). If no moveTo() call has been
* made for this contour, the first point is automatically set to (0,0).
*
* @param x1 The x-coordinate of the 1st control point on a cubic curve
* @param y1 The y-coordinate of the 1st control point on a cubic curve
* @param x2 The x-coordinate of the 2nd control point on a cubic curve
* @param y2 The y-coordinate of the 2nd control point on a cubic curve
* @param x3 The x-coordinate of the end point on a cubic curve
* @param y3 The y-coordinate of the end point on a cubic curve
*/
public void cubicTo(float x1, float y1, float x2, float y2,
float x3, float y3) {
nCubicTo(mNativePath, x1, y1, x2, y2, x3, y3);
}
三阶贝塞尔曲线可以用此方法绘制,三阶曲线有两个控制点,方法参数前两对为控制点位置,最后一对为中点位置,那我们绘制一下:
kotlin
val path = Path()
path.moveTo(pointA.x, pointA.y)
path.cubicTo(pointB.x, pointB.y, pointC.x, pointC.y, pointD.x, pointD.y)
canvas.drawPath(path,paint)
以上就是 Android Canvas 绘制二阶、三阶曲线的方法,那如果想绘制更多阶的曲线呢?很不幸,Path 只支持二阶、三阶的贝塞尔曲线绘制,因为多阶的曲线用得确实很少。如果确实有需求,那就需要自己去计算曲线轨迹了,社区里有开源支持,本文就不在赘述了。