根據多個點使用canvas貝賽爾曲線畫一條平滑的曲線


眾所周知想用canvas畫一條曲線我們可以使用這些函數:

二次曲線:quadraticCurveTo(cp1x, cp1y, x, y)

貝塞爾曲線:bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)

畫圓弧:arcTo(x1,y1,x2,y2,radius);

但是如果一組點給你,怎么通過這些已知點畫一條平滑的曲線呢?使用二次曲線,或是圓弧?恐怕這些都沒法滿足曲線多變的需求,唯一的方法就是一段貝塞爾曲線連着一段貝塞爾曲線。於是乎我在百度上大海撈針,發現居然沒有一個人把自己的算法放出來,太不人道了,被逼無奈的我只好從理論入手,先了解一下貝賽爾曲線的控制點計算方法,說起來容易,想要畫貝賽爾曲線誰都會,但是計算控制點你就會遇到一點小麻煩,這次我突擊惡補了一下終於弄懂了怎么計算貝賽爾曲線控制點的方法。

百度一下貝塞爾曲線控制點計算方法,在搜索結果第一條有一篇百度文庫的文章《怎樣確定 Bezier 曲線的控制點》,通過瀏覽全文找到了計算控制點的公式,以下是核心結論。

QQ截圖20150412022628.png

哎媽,是不是看了之后頭都大了,反正公式已經給出來了,就讓我們用行數去實現這個Ai和Bi點的計算吧,忘了說一句a、b可以為任意正數,等我們完成了我們的demo可以測試一下不同的a、bz值對我們的曲線會造成什么影響

 

JavaScript
/* *根據已知點獲取第i個控制點的坐標 *param ps 已知曲線將經過的坐標點 *param i 坐標點的個數 *param a,b 可以自定義的正數 */ function getCtrlPoint(ps, i, a, b){ if(!a||!b){ a=0.25; b=0.25; } var pAx = ps[i].x + (ps[i+1].x-ps[i-1].x)*a; var pAy = ps[i].y + (ps[i+1].y-ps[i-1].y)*a; var pBx = ps[i+1].x - (ps[i+2].x-ps[i].x)*b; var pBy = ps[i+1].y - (ps[i+2].y-ps[i].y)*b; return { pA:{x:pAx,y:pAy}, pB:{x:pBx,y:pBy} } }

寫完這些心中不免有些疑問,ps是我們的坐標點數組,我們的第0個點和第n-1、n個點怎么辦,我函數中的ps[n-1+2]和ps[n+2]是會超數組范圍的,於是我接着往文庫文章下面看,文中他給我們提供了兩種解決辦法。

sadfdgdf.jpg

方法2確實麻煩,我比較急迫的想看到效果,於是選擇了第一種方案,有興趣的同學可以在附件中找到這篇文章,自己回去研究研究,在我的算法上進行改進。

QQ截圖20150412024734.png

於是乎我改進了我的函數。

JavaScript
/* *根據已知點獲取第i個控制點的坐標 *param ps 已知曲線將經過的坐標點 *param i 第i個坐標點 *param a,b 可以自定義的正數 */ function getCtrlPoint(ps, i, a, b){ if(!a||!b){ a=0.25; b=0.25; } //處理兩種極端情形 if(ips.length-3){ var last=ps.length-1 var pBx = ps[last].x - (ps[last].x-ps[last-1].x)*b; var pBy = ps[last].y - (ps[last].y-ps[last-1].y)*b; }else{ var pBx = ps[i+1].x - (ps[i+2].x-ps[i].x)*b; var pBy = ps[i+1].y - (ps[i+2].y-ps[i].y)*b; } return { pA:{x:pAx,y:pAy}, pB:{x:pBx,y:pBy} } }

nice!接下來開始我們的canvas畫圖工作吧。

一、首先頂一個點數組,將我們即將描繪的坐標點模擬出來。

JavaScript
var point=[{x:0,y:380},{x:100,y:430},{x:200,y:280},{x:300,y:160}, {x:400,y:340},{x:500,y:100},{x:600,y:300},{x:700,y:240}]

二、開始遍歷我們的坐標數組這里為了方便描述point簡稱p,下標point[n]簡稱pn。

1)創建一個畫布,將畫筆的移動到p0點,這是我們的起始點。

2)循環點數組,在循環體中使用我們定義的getCtrlPoint函數計算出第i個點A、B兩個控制點的點坐標。

JavaScript
var ctrlP=getCtrlPoint(point,i-1); ctx.bezierCurveTo(ctrlP.pA.x, ctrlP.pA.y, ctrlP.pB.x,ctrlP.pB.y, point[i].x, point[i].y);

3)刷新頁面看效果吧

QQ截圖20150412024734.png

是不是很有感覺?為了再次確認我話的曲線是否有問題,我又畫了一些輔助線,標出了控制點,並且直線連接各個點與我們的貝塞爾曲線做個對比。於是得到了下圖。

QQ截圖20150412022628.png

紅色是通過點與控制點的連線,綠色是控制點與控制點之間的連線,藍色的是直線連接各點畫的線。

可以看到所有的點一個不落的被貝賽爾曲線貫穿,達到了我們希望的效果,通過這次探索,以后什么曲線都不怕了,明天再研究研究怎么把這條實線改成虛線,這些都是HighCharts、ECharts直流的基礎,有了這個demo曲線圖表什么的再也不怕了,想想心里還有些小激動呢。

最后奉上本次成果的demo:demo.rar

還有前面提到的百度文庫的文章:貝塞爾曲線控制點確定的方法.doc

來自:根據多個點使用canvas貝賽爾曲線畫一條平滑的曲線


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM