android N阶贝瑟尔曲线绘制
本文主要讲解N阶贝瑟尔曲线绘制的原理,主要实现是通过描点的方式绘制在Canvas对象上。
贝瑟尔曲线又称为贝兹曲线或贝济埃曲线,是应用于二维图形应用程序的数学曲线。
推导
二阶推导
给定点A,B,C,a=[0,1];则二阶贝瑟尔曲线的点集可以表示为 :
P
=
(
1
−
a
)
2
A
+
2
a
(
1
−
a
)
B
+
a
2
C
P=(1-a)^2A+2a(1-a)B+a^2C
P=(1−a)2A+2a(1−a)B+a2C
计算过程:
AB线段上点E,BC线段上点F,EF线段上点P,P点的集合就是二阶贝瑟尔曲线的集合
E
=
A
+
a
(
B
−
A
)
=
(
1
−
a
)
A
+
a
B
同理可得:
E
=
(
1
−
a
)
A
+
a
B
F
=
(
1
−
a
)
B
+
a
C
P
=
(
1
−
a
)
2
A
+
2
a
(
1
−
a
)
B
+
a
2
C
E=A+a(B-A)\\\quad=(1-a)A+aB\\ 同理可得:\\ E=(1-a)A+aB\\ F=(1-a)B+aC\\ P=(1-a)^2A+2a(1-a)B+a^2C
E=A+a(B−A)=(1−a)A+aB同理可得:E=(1−a)A+aBF=(1−a)B+aCP=(1−a)2A+2a(1−a)B+a2C
三阶推导
给定点A,B,C,D,a=[0,1]; 则三阶贝瑟尔曲线的点集可以表示为:
P
=
(
1
−
a
)
3
A
+
3
a
(
1
−
a
)
2
B
+
3
a
2
(
1
−
a
)
C
+
a
3
D
P=(1-a)^3A+3a(1-a)^2B+3a^2(1-a)C+a^3D
P=(1−a)3A+3a(1−a)2B+3a2(1−a)C+a3D
计算过程:
AB线段上点E, BC 线段上点F,CD上线段点G,点E、F、G就类似二阶贝瑟尔曲线上的三个点。
E
=
(
1
−
a
)
A
+
a
B
F
=
(
1
−
a
)
B
+
a
C
G
=
(
1
−
a
)
C
+
a
D
则
E
F
上点
M
、
F
G
上点
N
,
M
N
上点
P
的点分别表示为:
M
=
(
1
−
a
)
E
+
a
F
N
=
(
1
−
a
)
F
+
a
G
P
=
(
1
−
a
)
M
+
a
N
=
(
1
−
a
)
2
E
+
2
a
(
1
−
a
)
F
+
a
2
G
=
(
1
−
a
)
2
[
(
1
−
a
)
A
+
a
B
]
+
2
a
(
1
−
a
)
[
(
1
−
a
)
B
+
a
C
]
+
a
2
[
(
1
−
a
)
C
+
a
D
]
=
(
1
−
a
)
3
A
+
a
(
1
−
a
)
2
B
+
2
a
(
1
−
a
)
2
B
+
2
a
2
(
1
−
a
)
C
+
a
2
(
1
−
a
)
C
+
a
3
D
=
(
1
−
a
)
3
A
+
3
a
(
1
−
a
)
2
B
+
3
a
2
(
1
−
a
)
C
+
a
3
D
)
E=(1-a)A+aB\\ F=(1-a)B+aC\\ G=(1-a)C+aD\\ 则EF上点M、FG上点N,MN上点P的点分别表示为:\\ M=(1-a)E+aF\\ N=(1-a)F+aG\\ P=(1-a)M+aN\\ =(1-a)^2E+2a(1-a)F+a^2G\\ =(1-a)^2[(1-a)A+aB]+2a(1-a)[(1-a)B+aC]+a^2[(1-a)C+aD]\\ =(1-a)^3A+a(1-a)^2B+2a(1-a)^2B+2a^2(1-a)C+a^2(1-a)C+a^3D\\ =(1-a)^3A+3a(1-a)^2B+3a^2(1-a)C+a^3D)
E=(1−a)A+aBF=(1−a)B+aCG=(1−a)C+aD则EF上点M、FG上点N,MN上点P的点分别表示为:M=(1−a)E+aFN=(1−a)F+aGP=(1−a)M+aN=(1−a)2E+2a(1−a)F+a2G=(1−a)2[(1−a)A+aB]+2a(1−a)[(1−a)B+aC]+a2[(1−a)C+aD]=(1−a)3A+a(1−a)2B+2a(1−a)2B+2a2(1−a)C+a2(1−a)C+a3D=(1−a)3A+3a(1−a)2B+3a2(1−a)C+a3D)
N阶推导
给定点x1,x2,x3,…xn,则N阶贝瑟尔曲线上的点集可以表示为:
P
=
∑
i
=
0
n
C
n
i
a
i
(
1
−
a
)
n
−
i
x
i
P=\sum_{i=0}^nC_n^ia^i(1-a)^{n-i}x_i
P=i=0∑nCniai(1−a)n−ixi
推理过程:
由二阶和三阶的点集表示形式可以看出:按照顺序每个点前面的乘数恰好是 ((1-a) + a) 的 2次、3次方展开式。由此可得,N阶贝瑟尔曲线每个点的系数恰好是N次方的展开式。
核心代码
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.i("xxx", String.format("onDraw: pointSize=%d", pointSize));
bezierTest(canvas, paint, new float[]{50,50,250,50, 250,250,50,250,50,150});
}
/**
* 测试
* @param canvas
* @param paint
* @param points
*/
public void bezierTest(Canvas canvas, Paint paint, float[] points){
paint.setColor(Color.BLACK);
paint.setStrokeWidth(5);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setColor(Color.YELLOW);
float[] _points = nBezier(2000, points);
canvas.drawPoints(_points, paint);
paint.setColor(Color.RED);
paint.setStrokeWidth(6);
canvas.drawPoints(points, paint);
}
/**
* 获取 N 阶贝瑟尔曲线的点
* @param pointNum
* @param points :[x1,y1,x2,y2,x3,y3....]
* @return
*/
public float[] nBezier(int pointNum, float ...points){
if (points == null || points.length <6 || pointNum<=0){
return new float[]{0,0};
}
float[] res = new float[pointNum*2];
float ratio = (float) (1.0/((float)pointNum));
float stepRatio = ratio;
int n = points.length / 2;
// 多项式系数
int[] nIndex = getNIndex(n);
// nn:(1-stepRatio) 的N次方; n1:(1-stepRatio)的k次*stepRatio的(1-k)次方
float nn, n1=0;
float dx, dy;
for (int i = 1; i < pointNum; i++) {
nn = (float) Math.pow((1-stepRatio), n-1);
dx = 0;
dy = 0;
n1 = 1;
for (int i1 = 0; i1 < nIndex.length; i1++) {
dx += points[i1*2] * n1 * nn * nIndex[i1];
dy += points[i1*2+1] * n1 * nn * nIndex[i1];
n1 *= stepRatio;
nn /=(1-stepRatio);
}
res[i*2] = dx;
res[i*2+1] = dy;
stepRatio += ratio;
}
res[0] = points[0];
res[1] = points[1];
res[n-2] = points[2*(n-1)];
res[n-1] = points[2*(n-1)+1];
return res;
}
/**
* 获取N次方系数
* @param n
* @return
*/
public int[] getNIndex(int n){
if (n <=1){
return new int[]{1};
}
int[] res = new int[n];
long nn = getN(n);
long k1=1, k2=nn;
for (int i = 1; i < n; i++) {
k1 *=i;
k2 /=(n-i);
res[i] = (int) (nn / k1 / k2);
}
res[0] = 1;
res[n-1] = 1;
return res;
}
/**
* 获取N的阶乘
* @param n
* @return
*/
private long getN(long n){
if (n <= 1){
return 1;
}
return getN(n-1)*n;
}
这里的onDraw()方法是自定义View 中需要重写的方法。
nBezier()方法是获取贝瑟尔曲线点的方法。
二阶三阶可以不使用这个方法来进行绘制,Path中有quadTo() 方法来绘制二阶贝瑟尔曲线,cubicTo()方法来绘制三阶贝瑟尔曲线。