簡介
Bézier curve(貝塞爾曲線)是應用於二維圖形應用程序的數學曲線。 曲線定義:起始點、終止點、控制點。通過調整控制點,貝塞爾曲線的形狀會發生變化
一階貝賽爾:
一階貝塞爾曲線是一條直線,計算公式爲:
二階貝賽爾:
1.步驟一:在平面內選3個不同線的點並且依次用線段連接。如下所示..
2.步驟二:在AB和BC線段上找出點D和點E,使得 AD/AB = BE/BC
3.步驟三:連接DE,在DE上尋找點F,F點需要滿足:DF/DE = AD/AB = BE/BC
4.步驟四:當D點從A到B,並且E點從B到C這條線路中所有的F點形成的線路,就是這條二階貝塞爾曲線
計算公式如下
三階貝塞爾:
1.步驟一:在平面內選4個不同線的點並且依次用線段連接。如下所示..
步驟二:分別找到E F G三個點,滿足要求:AE/AB = BF/BC = CG/CD;在EH FG上找到H J點,滿足EH/EF = FI/FG = HJ/HI;
最終達到AE/AB = BF/BC = CG/CD=EH/EF = FI/FG = HJ/HI
步驟三:這個J點所形成的線,就是我們的三階貝塞爾曲線:
計算公式如下:
高階貝塞爾曲線:
四階貝塞爾曲線:
五階貝塞爾曲線
n階貝賽爾曲線計算公式如下:
貝塞爾曲線的代碼實現
-
使用path自帶的方法畫
1:使用quadTo畫二階貝塞爾曲線
quadTo方法傳入兩個參數,第一個是二階貝塞爾曲線的控制點,第二個是二階貝塞爾曲線的結束點
//二階貝塞爾曲線
mPath1.moveTo(100, 100);//起始點
mPath1.quadTo(400, 200, 10, 500);//二階貝塞爾曲線
//繪製三個點
canvas.drawCircle(100, 100, 10, mPaint);//起始點
canvas.drawCircle(400, 200, 10, mPaint);//控制點
canvas.drawCircle(10, 500, 10, mPaint);//結束點
canvas.drawPath(mPath1, mPaint);
繪製結果如下:
2:使用cubicTo繪製三階貝塞爾曲線
cubicTo方法傳入參數分別爲第一個控制點,第二個控制點,結束點
//三階貝塞爾曲線
mPath1.moveTo(100, 100);
mPath1.cubicTo(400, 200, 10, 500, 300, 700);
canvas.drawCircle(100, 100, 10, mPaint);//起始點
canvas.drawCircle(400, 200, 10, mPaint);//控制點
canvas.drawCircle(120, 500, 10, mPaint);//控制點
canvas.drawCircle(300, 700, 10, mPaint);//結束點
canvas.drawPath(mPath1, mPaint);
繪製結果如下
-
使用遞歸方式畫
因爲path只提供了二階,三階的繪製方法,所以,高階貝塞爾曲線,就需要自己繪製了,我們這裏使用遞歸來繪製
遞歸方法主要如下遞歸計算每個點的座標值
/**
* p(i,j) = (1-t) * p(i-1,j) + t * p(i-1,j+1);
*
* 遞歸計算各個點
* @param i 階數
* @param j 控制點
* @param t 時間
* @param calculateX true代表計算的是x的值,false代表的是y的值
* @return
*/
private float deCastelJau(int i, int j, float t, boolean calculateX) {
if (i == 1) {
return calculateX ? (1 - t) * mControlPoints.get(j).x + t * mControlPoints.get(j + 1).x : (1 - t) * mControlPoints.get(j).y + t * mControlPoints.get(j + 1).y;
} else {
return (1 - t) * deCastelJau(i - 1, j, t, calculateX) + t * deCastelJau(i - 1, j + 1, t, calculateX);
}
}
完整方法如下
public class BezierView extends View {
private Paint mPaint;
private Paint mLinePointPaint;
private Path mPath;
private List<PointF> mControlPoints;//控制點集合(包含數據點)
public BezierView(Context context) {
super(context);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(4);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(Color.RED);
mLinePointPaint = new Paint();
mLinePointPaint.setAntiAlias(true);
mLinePointPaint.setStrokeWidth(4);
mLinePointPaint.setStyle(Paint.Style.STROKE);
mLinePointPaint.setColor(Color.GRAY);
mPath = new Path();
mControlPoints = new ArrayList<>();
init();
}
private void init() {
mControlPoints.clear();
//添加起始點
PointF pointF = new PointF(200,200);
//添加控制點
PointF pointF1 = new PointF(1000,550);
PointF pointF2 = new PointF(600,1150);
//結束點
PointF pointF3 = new PointF(1200,1350);
mControlPoints.add(pointF);
mControlPoints.add(pointF1);
mControlPoints.add(pointF2);
mControlPoints.add(pointF3);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//控制點及控制點的連線
int size = mControlPoints.size();
PointF pointF;
for (int i = 0; i < size; i++) {
pointF = mControlPoints.get(i);
canvas.drawCircle(pointF.x, pointF.y, 10, mLinePointPaint);
}
//畫曲線
buildBezierPoints();
canvas.drawPath(mPath, mPaint);
}
/**
* 劃線
*/
private void buildBezierPoints() {
mPath.reset();
int order = mControlPoints.size() - 1;//階數
//份數
float delta = 1.0f / 1000;
//將一條線分爲了1000個點來顯示,這裏的數量可以自己控制,越多越精確
for (float t = 0; t <= 1; t += delta) {
//bezier點集
PointF pointF = new PointF(deCastelJau(order, 0, t, true), deCastelJau(order, 0, t, false));//計算在曲線上點位置
if (t ==0) {
mPath.moveTo(pointF.x, pointF.y);
} else {
mPath.lineTo(pointF.x, pointF.y);
}
}
}
/**
* p(i,j) = (1-t) * p(i-1,j) + t * p(i-1,j+1);
*
* 遞歸計算各個點
* @param i 階數
* @param j 控制點
* @param t 時間
* @param calculateX true代表計算的是x的值,false代表的是y的值
* @return
*/
private float deCastelJau(int i, int j, float t, boolean calculateX) {
if (i == 1) {
return calculateX ? (1 - t) * mControlPoints.get(j).x + t * mControlPoints.get(j + 1).x : (1 - t) * mControlPoints.get(j).y + t * mControlPoints.get(j + 1).y;
} else {
return (1 - t) * deCastelJau(i - 1, j, t, calculateX) + t * deCastelJau(i - 1, j + 1, t, calculateX);
}
}
}
我們這裏只添加了四個點 所以上面的代碼運行結果如下: