最近遇到個題,給定一系列坐标點,如何把它們繪制成一條平滑的曲線。
1 Catmull-Rom算法繪制曲線
首先來了解一下,樣條曲線(Spline Curves),
是指給定一組控制點而得到一條曲線,曲線的大緻形狀由這些點予以控制,一般可分為插值樣條和逼近樣條兩種,插值樣條通常用于數字化繪圖或動畫的設計,逼近樣條一般用來構造物體的表面。
而Catmull-Rom就是其中一種常用于繪制曲線的樣條曲線算法,我們可以把它當成是一種特殊的貝塞爾曲線,一種能夠經過所有控制點的曲線。
給定四個坐标點,P0,P1,P2,P3,同時再給定一個float值t(從P1移動到P2的同時,t從0變化到1),可以繪制P1到P2這段曲線的坐标點。

我們可以看到相關的計算公式:

根據公式我們可以轉化成代碼:
private PointF interpolatedPosition(PointF point0, PointF point1,
PointF point2, PointF point3, float i) {
float u3 = i * i * i;
float u2 = i * i;
float f1 = -0.5f * u3 + u2 - 0.5f * i;
float f2 = 1.5f * u3 - 2.5f * u2 + 1.0f;
float f3 = -1.5f * u3 + 2.0f * u2 + 0.5f * i;
float f4 = 0.5f * u3 - 0.5f * u2;
float x = point0.x * f1 + point1.x * f2 + point2.x * f3 + point3.x * f4;
float y = point0.y * f1 + point1.y * f2 + point2.y * f3 + point3.y * f4;
return new PointF(x, y);
}
給定一系列坐标點:
mPointFList = new ArrayList<>();
mPointFList.add(new PointF(0, 500));
mPointFList.add(new PointF(100, 330));
mPointFList.add(new PointF(200, 280));
mPointFList.add(new PointF(300, 460));
mPointFList.add(new PointF(400, 560));
mPointFList.add(new PointF(500, 200));
mPointFList.add(new PointF(600, 300));
mPointFList.add(new PointF(700, 340));
效果圖:

2 三階貝塞爾曲線繪制
在Android開發中多用二階或者三階貝塞爾曲線來繪制曲線,但是大多數情況我們是基于兩個點來繪制,當坐标點數量多起來的時候,我們遇到一個難題,如果使用多段貝塞爾曲線來繪制,如何處理兩端曲線之間的連接配接,使得它們過度自然。
感謝鄭航,看了他的文章之後,大概了解了如何實作。要使得連續兩段的貝塞爾曲線的連接配接自然,我們需要使得接頭處,前後兩段曲線的曲線率相同。
如果我們要繪制P(i)到P(i+1)之間的曲線,那麼我們就需要知道兩個控制點A和B,那麼現在根據上面的原理,我們已經可以得到這兩個點。
A.x = P(i).x + (P(i + 1).x - P(i - 1).x) * a;
A.y = P(i).y + (P(i + 1).y - P(i - 1).y) * a;
B.x = P(i + 1).x + (P(i + 2).x - P(i).x) * b;
B.y = P(i + 1).y + (P(i + 2).y - P(i).y) * b;
這裡的值a和值b可以是任意正數。
我們用java代碼來實作:
private void getCtrlPoint(List pointFList, int currentIndex,
PointF ctrlPointA, PointF ctrlPointB) {
ctrlPointA.x = pointFList.get(currentIndex).x +
(pointFList.get(currentIndex + 1).x - pointFList.get(currentIndex - 1).x) * CTRL_VALUE_A;
ctrlPointA.y = pointFList.get(currentIndex).y +
(pointFList.get(currentIndex + 1).y - pointFList.get(currentIndex - 1).y) * CTRL_VALUE_A;
ctrlPointB.x = pointFList.get(currentIndex + 1).x -
(pointFList.get(currentIndex + 2).x - pointFList.get(currentIndex).x) * CTRL_VALUE_B;
ctrlPointB.y = pointFList.get(currentIndex + 1).y -
(pointFList.get(currentIndex + 2).y - pointFList.get(currentIndex).y) * CTRL_VALUE_B;
}
上面我們講到a和b的值可以是任意正數,但是它們是會影響到最後繪制出來的曲線的效果的。
我們可以對a和b的值取不同值,坐标點依然采用上面例子的數值,然後觀察最後繪制出來的曲線圖
1)a = b = 0.05f

2)a = b = 0.2f

3) a = b = 0.5f

可以看到,當a和b的值取0.2f的時候,我們通過貝塞爾曲線繪制出來的曲線,是比較平滑的,而且很接近通過樣條曲線算法繪制的曲線。
兩種方式繪制出來的曲線基本上是一緻的,相信其實有更多更有效率的繪制方法,希望能繼續補充~
代碼demo: https://github.com/82367825/Polyline