貝塞爾曲線開發的藝術

eclipse_xu發表於2016-10-23

貝塞爾曲線開發的藝術

一句話概括貝塞爾曲線:將任意一條曲線轉化為精確的數學公式。

很多繪圖工具中的鋼筆工具,就是典型的貝塞爾曲線的應用,這裡的一個網站可以線上模擬鋼筆工具的使用:

http://bezier.method.ac/

20160719162422192

貝塞爾曲線中有一些比較關鍵的名詞,解釋如下:

  • 資料點:通常指一條路徑的起始點和終止點
  • 控制點:控制點決定了一條路徑的彎曲軌跡,根據控制點的個數,貝塞爾曲線被分為一階貝塞爾曲線(0個控制點)、二階貝塞爾曲線(1個控制點)、三階貝塞爾曲線(2個控制點)等等。

要想對貝塞爾曲線有一個比較好的認識,可以參考WIKI上的連結:

https://en.wikipedia.org/wiki/B%C3%A9zier_curve

20160719162452111

貝塞爾曲線模擬

在Android中,一般來說,開發者只考慮二階貝塞爾曲線和三階貝塞爾曲線,SDK也只提供了二階和三階的API呼叫。對於再高階的貝塞爾曲線,通常可以將曲線拆分成多個低階的貝塞爾曲線,也就是所謂的降階操作。下面將通過程式碼來模擬二階和三階的貝塞爾曲線是如何繪製和控制的。

貝塞爾曲線的一個比較好的動態演示如下所示:

http://myst729.github.io/bezier-curve/

20160719162741693

二階模擬

二階貝塞爾曲線在Android中的API為:quadTo()和rQuadTo(),這兩個API在原理上是可以互相轉換的——quadTo是基於絕對座標,而rQuadTo是基於相對座標,所以後面我都只以其中一個來進行講解。

先來看下最終的效果:

20160719163120476

從前面的介紹可以知道,二階貝塞爾曲線有兩個資料點和一個控制點,只需要在程式碼中繪製出這些輔助點和輔助線即可,同時,控制點可以通過onTouchEvent來進行傳遞。

三階模擬

三階貝塞爾曲線在Android中的API為:cubicTo()和rCubicTo(),這兩個API在原理上是可以互相轉換的——quadTo是基於絕對座標,而rCubicTo是基於相對座標,所以後面我都只以其中一個來進行講解。

有了二階的基礎,再來模擬三階就非常簡單了,無非是增加了一個控制點而已,先看下效果圖:

20160719173906245

程式碼只需要在二階的基礎上新增一些輔助點即可,下面只給出一些關鍵程式碼,詳細程式碼請參考Github:

模擬網頁

如下所示的網頁,模擬了三階貝塞爾曲線的繪製,可以通過拖動曲線來獲取兩個控制點的座標,而起始點分別是(0,0)和(1,1)。

http://cubic-bezier.com/

20160719173939902

通過這個網頁,也可以比較方便的獲取三階貝塞爾曲線的控制點座標。

貝塞爾曲線應用

圓滑繪圖

當在螢幕上繪製路徑時,例如手寫板,最基本的方法是通過Path.lineTo將各個觸點連線起來,而這種方式在很多時候會發現,兩個點的連線是非常生硬的,因為它畢竟是通過直線來連線的,如果通過二階貝塞爾曲線來將各個觸點連線,就會圓滑的多,不會出現太多的生硬連線。

先來看下程式碼,非常簡單的繪製路徑程式碼:

先來看下通過mPath.lineTo來實現的繪圖,效果如下所示:

20160719174009506

圖片中的拐點有明顯的鋸齒效果,即通過直線的連線,再來看下通過貝塞爾曲線來連線的效果,通常情況下,貝塞爾曲線的控制點取兩個連續點的中點:

通過二階貝塞爾曲線的連線效果如圖所示:

20160719174039075

可以明顯的發現,曲線變得更加圓滑了。

曲線變形

通過控制貝塞爾曲線的控制點,就可以實現對一條路徑的修改。所以,利用貝塞爾曲線,可以實現很多的路徑動畫,例如:

20160719174305469

這裡就是簡單的改變二階貝塞爾曲線的控制點來實現曲線的變形。

網上一些比較複雜的變形動畫效果,也是基於這種實現方式,其原理都是通過改變控制點的位置,從而達到對圖形的變換,例如圓形到心形的變化、圓形到五角星的變換,等等。

波浪效果

波浪的繪製是貝塞爾曲線一個非常簡單的應用,而讓波浪進行波動,其實並不需要對控制點進行改變,而是可以通過位移來實現,這裡我們是藉助貝塞爾曲線來實現波浪的繪製效果,效果如圖所示:

20160719174547117

波浪動畫實際上並不複雜,但三角函式確實對一些開發者比較困難,開發者可以通過下面的這個網站來模擬三角函式影象的繪製:

https://www.desmos.com/calculator

20160719175058994

路徑動畫

貝塞爾曲線的另一個非常常用的功能,就是作為動畫的運動軌跡,讓動畫目標能夠沿曲線平滑的實現移動動畫,也就是讓物體沿著貝塞爾曲線運動,而不是機械的直線,本例實現效果如下所示:

20160719175157622

其中,用於改變運動點座標的關鍵evaluator如下所示:

這裡的TypeEvaluator計算用到了計算貝塞爾曲線上點的計算演算法,這個會在後面繼續講解。

貝塞爾曲線進階

求貝塞爾曲線上任意一點的座標

求貝塞爾曲線上任意一點的座標,這一過程,就是利用了De Casteljau演算法。

http://www.cs.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/Bezier/de-casteljau.html

20160719175248779

利用這一演算法,有開發者開發了一個演示多階貝塞爾曲線的效果的App,其原理就是通過繪製貝塞爾曲線上的點來進行繪製的,地址如下所示:

https://github.com/venshine/BezierMaker

下面這篇文章就詳細的講解了該演算法的應用,我的程式碼也從這裡提取而來:

http://devmag.org.za/2011/04/05/bzier-curves-a-tutorial/

計算

有了公式,只需要程式碼實現就OK了,我們先寫兩個公式:

我們來將路徑繪製到View中,看是否正確:

這次我們並沒有通過API提供的貝塞爾曲線繪製方法來繪製二階、三階貝塞爾曲線,而是通過時間t和起始點來計算一條貝塞爾曲線上的所有點,可以發現,通過演算法計算出來的點,與通過API所繪製出來的點,是完全吻合的。

貝塞爾曲線擬合計算

貝塞爾曲線有一個非常常用的動畫效果——MetaBall演算法。相信很多開發者都見過類似的動畫,例如QQ的小紅點消除,UC瀏覽器的下拉重新整理loading等等。要做好這個動畫,實際上最重要的就是通過貝塞爾曲線來擬合兩個圖形。

效果如圖所示:

20160719175318436

矩形擬合

我們來看一下擬合的原理,實際上就是通過貝塞爾曲線來連線兩個圓上的四個點,當我們調整下畫筆的填充方式,並繪製一些輔助線,我們來看具體是如何進行擬合的,如圖所示:

20160719175333312

可以發現,控制點為兩圓圓心連線的中點,連線線為圖中的這樣一個矩形,當圓比較小時,這種通過矩形來擬合的方式幾乎是沒有問題的,但我們把圓放大,再來看下這種擬合,如圖所示:

20160719175348172

當圓的半徑擴大之後,就可以非常明顯的發現擬合的連線點與圓有一定相交的區域,這樣的擬合效果就不好了,我們將畫筆模式調整回來,如圖所示:

20160719175501611

所以,簡單的矩形擬合,在圓半徑小的時候,是可以的,但當圓半徑變大之後,就需要更加嚴格的擬合了。

這裡我們先來講解下,如何計算矩形擬合的幾個關鍵點。

從前面那張線圖可以看出,標紅的兩個角是相等的,而這個角可以通過兩個圓心的座標來算出,有了這樣一個角度,通過R x cos和 R x sin來計算矩形的一個頂點的座標,類似的,其它座標可求,關鍵程式碼如下所示:

切線擬合

如前面所說,矩形擬合在半徑較小的情況下,是可以實現完美擬合的,而當半徑變大後,就會出現貝塞爾曲線與圓相交的情況,導致擬合失敗。

那麼如何來實現完美的擬合呢?實際上,也就是說貝塞爾曲線與圓的連線點到貝塞爾曲線的控制點的連線,一定是圓的切線,這樣的話,無論圓的半徑如何變化,貝塞爾曲線一定是與圓擬合的,具體效果如圖所示:

20160719175539971

這時候我們把畫筆模式調整回來看下填充效果,如圖所示:

20160719175609777

這樣擬合是非常完美的。那麼要如何來計算這些擬合的關鍵點呢?在前面的線圖中,我標記出了兩個角,這兩個角分別可以求出,相減,就可以獲取切點與圓心的夾角了,這樣,通過R x cos和R x sin就可以求出切點的座標了。

其中,小的角可以通過兩個圓心的座標來求出,而大的角,可以通過直角三角形(圓心、切點、控制點)來求出,即控制點到圓心的距離/半徑。

關鍵程式碼如下所示:

圓的擬合

貝塞爾曲線做動畫,很多時候都需要使用到圓的特效,而通過二階、三階貝塞爾曲線來擬合圓,也不是一個非常簡單的事情,所以,我直接把結論拿出來了,具體的演算法地址如下所示:

http://spencermortensen.com/articles/bezier-circle/

http://stackoverflow.com/questions/1734745/how-to-create-circle-with-b%C3%A9zier-curves

20160719175640612

20160719175710856

有了貝塞爾曲線的控制點,再對其實現動畫,就非常簡單了,與之前的動畫沒有太大的區別。

原始碼

本次的講解程式碼已經全部上傳到Github :

https://github.com/xuyisheng/BezierArt

歡迎大家提issue。

打賞支援我寫出更多好文章,謝謝!

打賞作者

打賞支援我寫出更多好文章,謝謝!

貝塞爾曲線開發的藝術

相關文章