canvas講解進階篇四

貝塞爾曲線、座標變換以及圖像合成

我們之前說過:canvas的元素大小與繪圖環境大小不一定相同,例如我們通過css定義canvas的寬高後,改變的只是canvas元素的寬高,而繪圖環境的寬高還是不變的,當我們在canvas內部作圖時候,繪圖環境會默認的縮放到canvas元素大小,

這時候如果我們繪製1像素那麼該1像素就會被縮放同樣倍數。很多時候我們需要根據用戶鼠標位置去操作canvas環境。這時候獲得鼠標位置就至關重要了,然後我們獲得的鼠標位置如果都是相對於整個頁面而言的,

那麼canvas的存在就給計算鼠標位置帶來不少複雜度,而且我們操作的鼠標座標僅僅是在canvas內部,跟外部頁面無關,所以利用整個頁面的座標來計算canvas內部的位置顯然是不可取的。於是我們封裝一個函數,

用來獲得當前鼠標針對於canvas繪圖環境的座標位置。(注意這裏的canvas.width是繪圖環境的寬度)

function htmlToCanvas(canvas,x,y){

  var box=canvas.getBoundingClientRect();

  return {x:x-box.left*(canvas.width/box.width),y:y-box.top*(canvas.height/box.height)}

}

在這個函數中 我們傳入三個參數:分別是canvas繪圖環境以及當前鼠標位置一般就是e.clientX,e.clientY.然後我們在函數內部獲得元素的寬高左右數據,返回一個對象包含xy座標,其中xy座標不僅僅是鼠標位置減去元素的左邊距,而是要去判斷有沒有縮放。

乍一看 這個跟我們目前學的東西不沾邊,不過後面牽扯到用戶鼠標拖拽剪切等操作就離不開它了。

貝塞爾曲線以及多邊形

貝塞爾曲線

有些時候我們畫曲線需要把已有的點連接起來,而貝塞爾曲線則不需要,它只需要控制點以及錨點來控制整個曲線的構造,這樣繪出來的曲線不僅平滑而且可以畫出很多意想不到的效果。如圖實例:


ps中的繪製貝塞爾曲線工具就是鋼筆,在canvas中就是貝塞爾曲線,通常canvas中貝塞爾曲線分爲兩種,平方貝塞爾以及立方貝塞爾曲線,其中平方貝塞爾曲線由三個點來定義,兩個錨點以及一個控制點,而立方貝塞爾曲線在平方基礎上添加了一個控制點。

平方貝塞爾曲線又叫二次貝塞爾曲線。是一種只向一個方向彎曲的簡單曲線。我們繪製二次貝塞爾曲線用到的canvas方法爲quadraticCurveTo()接受四個參數:兩對xy座標分別表示曲線的控制點以及曲線錨點,

連續的使用會將錨點與當前路徑中最後一個點連接起來:無論控制點還是錨點都不會參與構成曲線,只是決定曲線走向。這裏大家可以看看貝塞爾動畫效果:查看動畫貝塞爾貝塞爾曲線動畫

三次貝塞爾曲線也就是立方貝塞爾曲線。其canvas實現方法爲:bezierCurveTo()接受六個參數三對座標,前兩個是曲線控制點,最後一個是錨點。三次貝塞爾曲線是三維的也就是說可以向兩個方向彎曲,其中兩個控制點可以控制曲線的方向,

數學公式表示如下:

P0、P1、P2、P3四個點在平面或在三維空間中定義了三次方貝茲曲線。曲線起始於P0走向P1,並從P2的方向來到P3。P0和P1之間的間距,決定了曲線走向P3的時機。

B(t)=P0(1-t)^3+3P1t(1-t)^2+3P2t_2t^2(1-t)+P3t^3

真實開發中 如果我們需要用到貝塞爾曲線繪畫股票走勢等圖形,建議大家先在ps裏面畫好然後取得控制點再回到我們程序中進行繪製。

其中的控制點我們要想得到跟屏幕一樣效果應該把ps中畫布大小設置成屏幕大小,然後在畫布中一塊區域繪製canvas。這樣得到的控制點纔會準確。

貝塞爾應用很多,我們可以自定義圖形或者曲線去描述股票走勢等。有興趣大家可以做一做試試。

後期我會跟大家一步步做一個拖動端點來編輯貝塞爾曲線的小插件,利用它你可以獲得每個控制點以及錨點座標以便日後開發各種圖形。

多邊形

我們大致看一下多邊形實現方式。多邊形沒有像貝塞爾曲線那樣固定的canvas方法,這樣我們就必須借用數學思想以及canvas中其他對象來實現。

其實畫多邊形也不過是將一個個的點用線段連接起來,這個過程中尋找點就成了最重要的事情了。例如我們畫一個正八邊形。

思路:八邊形當然有八個點了,而且是對稱的,正多邊形都是內接於圓的,我們可以利用圓來定義每個點的座標。八個點將一個圓的360度平分,也就是說每個點與點之間都差了45度。

畫八邊形肯定知道圓心半徑,利用三角函數遞歸求出每個點的座標連接起來 就over了。


總之:畫多邊形就是一個尋點的過程。

座標變換:

之前我們大致說過跟座標變化相關的話題:例如平移、縮放、旋轉等。。這些對應的canvas方法分別爲translate、scale、以及rotate。這些方法的實現都是特定效果,

例如scale其實是利用座標變換,將當前座標擴大或者縮小固定的倍數從而使得變換的像素也縮放從而達到縮放效果。

我們可以用代數方程來解釋各個操作效果:

平移:將座標進行移動x0=(1+d)x,y0=(1+d)y其中d爲移動參數,x0,y0爲新座標,xy爲舊座標。

縮放:x0=dx,y0=dy,將原先座標縮放y倍數得到新座標x0,y0;

旋轉:進行旋轉要用到一些三角函數:x0=x*cos(angle)-y*sin(angle),y0=y*cos(angle)+x*sin(angle);將原來座標經過函數運算旋轉一定角度得到新座標。

可以看出我們的這些操作實際都是在代數方程基礎上進行單位座標值的改變。那麼我們如果要綜合起來做出這些效果應該怎麼做呢?肯定大家會說每個方法都調用一遍,其實canvas中有一個方法專門爲座標轉換而生。

確切的說應該是一對兒(但不定同時出現):transfrom()與setTranform();前者是將現有環境進行座標轉換,後者是在操作之前將座標轉換。

transfrom(a,b,c,d,e,f)接受六個參數,這六個參數可以在一組等式中出現:x0=ax+cy+e;y0=bx+dy+f;

分析一下:如果a=1,b=0,c=0且d=1,那麼通過參數ef就可以單純的進行座標平移操作也就是x0=x+e;y0=y+f;

同樣,如果我們將參數cebf都設置爲0的話就得到x0=ax,y0=dy;這樣一組等式,這不就是我們剛纔的等式縮放嗎。

而我們要是想讓座標進行旋轉(假設是單純旋轉)angle角度,那麼最終結果就是x0=x*cos(angle)-y*sin(angle),y0=y*cos(angle)+x*sin(angle);也就是說將a=cons(angle),b=sin(angle),c=-sin(angle),d=cos(angle),e=f=0;即可。

通過我們的分析我們可以看出 無論是哪種操作我們都可以通過改變transfrom中參數值來控制新座標的位置,從而達到我們預想效果。

而且不僅僅是上述的平移縮放旋轉,那三種只是利用了6個參數中的九牛一毛而已,其他大量的效果以及更加精確轉換座標同樣都可以用該方法去實現。

我們加一個例子 給大家看看水平方向進行錯切的效果

context.transform(1,0,0.5,1,0,0);

context.rect(100,100,100,100);

context.rect(100,200,100,100);

context.stroke();

這段代碼中transform的值帶入我們的等式會得到:x0=x+0.5y;y0=y;從等式可以看出我們是將x座標平移了原來y座標的0.5倍,y座標值不變,於是就得到了以下效果:


圖像合成:

通常情況下我們將一張紙蓋在另一張紙上面肯定會覆蓋另一張紙,瀏覽器也是這樣默認實現圖像合成的,canvas提供一種屬性可以改變默認圖像合成:globalCompositeOperation,其值可以是下表列出的任意一個值:

source-atop source-in source-out source-over destination-atop destination-in destination-out destination-over lighter copy xor

爲了更好的給大家演示每個屬性值究竟是如何合成的。我們看一段代碼程序 大家可以ctrl+c ctrl+v跑一下試試:

html代碼:

<!DOCTYPE html>

<html>

<head>

<style>

#canvas{

  border:1px solid #000;

  position:absolute;

  left:150px;

  top:10px;

  background:#eee;

  cursor:pointer;

}

</style>

</head>

<select id="mySel"size='11'>

  <option value='source-atop'>source-atop</option>

  <option value='source-in'>source-in</option>

  <option value='source-out'>source-out</option>

  <option value='source-over'>source-over</option>

  <option value='destination-atop'>destination-atop</option>

  <option value='destination-in'>destination-in</option>

  <option value='destination-out'>destination-out</option>

  <option value='destination-over'>destination-over</option>

  <option value='lighter'>lighter</option>

  <option value='copy'>copy</option>

  <option value='xor'>xor</option>

</select>

<canvas id='canvas' width='600'height='420'></canvas

</html>

js代碼:

var canvas=document.getElementById('canvas'),

context=canvas.getContext('2d'),

sel=document.getElementById('mySel');

sel.selectedIndex=10;

context.font='128pt 微軟雅黑';

drawText();

function drawText(){

  context.save();

  context.fillStyle='red';

  context.fillText('yuchao',20,250);

  context.restore();

}

canvas.οnmοusemοve=function(e){

  var loc=htmlToCanvas(canvas,e.clientX,e.clientY);

  context.clearRect(0,0,canvas.width,canvas.height);

  drawText();

  context.save();

  context.globalCompositeOperation=sel.value;

  context.beginPath();

  context.arc(loc.x,loc.y,100,0,Math.PI*2,false);

  context.fillStyle='orange';

  context.fill();

  context.restore();

}

上面的mousemove中我們用到了開始說到的座標轉換方法。下面來看兩個截圖效果:


另外需要注意的是目前瀏覽器對於幾個圖像合成有兩種不同支持方式:例如source-in,chrome以及safari就是我們看到的這種效果“局部合成”,而firefox與opera則是選擇了全局合成方式,這種方式會將源圖像範圍之外的那部分擦除。

然而canvas規範目前支持的合成方式竟然是全局合成方式,不過該規範很有可能做出修改。畢竟局部合成才更符合我們預期效果。

下一篇我們着重來學習一下canvas的圖像操作。像是ps中濾鏡通道之類的效果等都在下一篇,敬請期待哦。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章