畫布狀態儲存與復原
在使用變形效果等複雜繪圖方法之前,有兩個不可或缺的方法(method)必須要先了解一下:
-
save()
- 儲存現階段畫布完整狀態。
-
restore()
- 復原最近一次儲存的畫布狀態。
每一次呼叫save(),畫布狀態便會存進一個堆疊(stack)之中。畫布狀態包含了:
- 曾經套用過的變形效果,如translate, rotate和scale(稍後說明)。
strokeStyle
,fillStyle
,globalAlpha
,lineWidth
,lineCap
,lineJoin
,miterLimit
,shadowOffsetX
,shadowOffsetY
,shadowBlur
,shadowColor
,globalCompositeOperation
屬性的屬性值- 目前截圖路徑(稍後說明)。
我們可以呼叫save()的次數不限,而每一次呼叫restore(),最近一次儲存的畫布狀態便會從堆疊中被取出,然後還原畫布到此畫布狀態。
畫布狀態儲存與復原範例
本例會畫一連串矩形圖案來說明畫布狀態堆疊是如何運作。
function draw() { var ctx = document.getElementById('canvas').getContext('2d'); ctx.fillRect(0,0,150,150); // Draw a rectangle with default settings ctx.save(); // Save the default state ctx.fillStyle = '#09F' // Make changes to the settings ctx.fillRect(15,15,120,120); // Draw a rectangle with new settings ctx.save(); // Save the current state ctx.fillStyle = '#FFF' // Make changes to the settings ctx.globalAlpha = 0.5; ctx.fillRect(30,30,90,90); // Draw a rectangle with new settings ctx.restore(); // Restore previous state ctx.fillRect(45,45,60,60); // Draw a rectangle with restored settings ctx.restore(); // Restore original state ctx.fillRect(60,60,30,30); // Draw a rectangle with restored settings }
<canvas id="canvas" width="150" height="150"></canvas>
draw();
第一步先畫一個預設樣式的矩形,接下來儲存狀態完再變更填滿顏色,第二步畫一個較小的藍色矩形,然後再儲存目前狀態,然後再一次變更樣式,以新樣式畫第三個半透明白色矩形。
之後當我們呼叫restore()方法,第一次呼叫會最上層的畫布狀態會從堆疊中取出然後移除,畫布會復原到這個取出的狀態,如果我們事先沒有呼叫save()來儲存畫布狀態,那我們便需要自己手動回復樣式到先前的設定,或許手動撰寫程式碼回復一、兩個屬性不是問題,不過若是有好多個屬性需要回復,手動回復便是一個問題了。
第二次呼叫restore (),先前取出的狀態的下一個畫布狀態會被取出,所以最後一個矩形的顏色又會跟最初始的顏色設定一樣是黑色。
Screenshot | Live sample |
---|---|
移動畫布
第一個變形效果方法是translate()。translate()是用來移動畫布,如右圖,原先畫布的原點在網格(0, 0)位置,我們可以移動畫布,使其原點移到(x, y)位置。
-
translate(x, y)
- 移動網格上的畫布,其中x代表水平距離、y代表垂直距離。
最好在做任何變形效果前先儲存一下畫布狀態,如此當我們需要復原先前的狀態時,只需要呼叫一下restore()即可,而且有一種情況是當我們在迴圈中移動畫布,如果不記得儲存和回復畫布狀態,繪圖區域很容易最後就超出邊界,然後出現圖案不見的狀況。
移動畫布範例
下面程式碼示範了利用translate()畫圖的好處,裡面,我們用了drawSpirograph()函數畫萬花筒類的圖案,如果沒有移動畫布原點,那麼每個圖案只會有四分之一會落在可視範圍,藉由移動畫布原點我們便可以自由變換每個圖案的位置,使圖案完整出現,而且省去手動計算調整每個圖案的座標位置。
另外一個draw()函數透過兩個for迴圈移動畫布原點、呼叫drawSpirograph()函數、復歸畫布圓點位置共九次。
function draw() { var ctx = document.getElementById('canvas').getContext('2d'); ctx.fillRect(0,0,300,300); for (var i=0;i<3;i++) { for (var j=0;j<3;j++) { ctx.save(); ctx.strokeStyle = "#9CFF00"; ctx.translate(50+j*100,50+i*100); drawSpirograph(ctx,20*(j+2)/(j+1),-8*(i+3)/(i+1),10); ctx.restore(); } } } function drawSpirograph(ctx,R,r,O){ var x1 = R-O; var y1 = 0; var i = 1; ctx.beginPath(); ctx.moveTo(x1,y1); do { if (i>20000) break; var x2 = (R+r)*Math.cos(i*Math.PI/72) - (r+O)*Math.cos(((R+r)/r)*(i*Math.PI/72)) var y2 = (R+r)*Math.sin(i*Math.PI/72) - (r+O)*Math.sin(((R+r)/r)*(i*Math.PI/72)) ctx.lineTo(x2,y2); x1 = x2; y1 = y2; i++; } while (x2 != R-O && y2 != 0 ); ctx.stroke(); }
<canvas id="canvas" width="300" height="300"></canvas>
draw();
Screenshot | Live sample |
---|---|
旋轉
rotate()函數可以畫布原點作中心,旋轉畫布。
-
rotate(x)
- 以畫布原點為中心,順時針旋轉畫布x弧度(弧度 = Math.PI * 角度 / 180)。
我們可以呼叫translate()方法來移動旋轉中心(亦即畫布原點)。
旋轉範例
本範例我們呼叫rotate()方法來畫一系列環狀圖案。如果不用rotate(),同樣的效果也可以藉由個別計算x, y座標點(x = r*Math.cos(a); y = r*Math.sin(a))達成;呼叫rotate()和個別計算x, y座標點不同之處在於,個別計算x, y座標點只有旋轉圓形圓心,而圓形並沒有旋轉,呼叫rotate()則會旋轉圓形和圓心,不過因為我們的圖案是圓形,所以兩種作法產生的效果不會有差異。
我們執行了兩個迴圈來作圖,第一個迴圈決定的圓環個數和該圓環上圓環上圓點的個數的顏色,第二個迴圈決定了圓環上圓點的個數,每一次作圖前我們都儲存了原始畫布狀態,以便結束時可以復原狀態。畫布旋轉的弧度則以圓環上圓點的個數決定,像是最內圈的圓環共有六個圓點,所以每畫一個原點,畫布就旋轉60度(360度/6),第二的圓環有12個原點,所以畫布一次旋轉度數為30度(360度/12),以此類推。
function draw() { var ctx = document.getElementById('canvas').getContext('2d'); ctx.translate(75,75); for (var i=1;i<6;i++){ // Loop through rings (from inside to out) ctx.save(); ctx.fillStyle = 'rgb('+(51*i)+','+(255-51*i)+',255)'; for (var j=0;j<i*6;j++){ // draw individual dots ctx.rotate(Math.PI*2/(i*6)); ctx.beginPath(); ctx.arc(0,i*12.5,5,0,Math.PI*2,true); ctx.fill(); } ctx.restore(); } }
<canvas id="canvas" width="150" height="150"></canvas>
draw();
Screenshot | Live sample |
---|---|
縮放
接下來這一個變形效果為縮放圖形。
- scale(x, y)
- x代表縮放畫布水平網格單位x倍,y代表縮放畫布垂直網格單位y倍,輸入1.0不會造成縮放。如果輸入負值會造成座標軸鏡射,假設輸入x為-1,那麼原本畫布網格X軸上的正座標點都會變成負座標點、負座標點則變成正座標點。
只要利用scale(),我們可以建立著名的笛卡兒座標系;執行translate(0,canvas.height)先移動畫布原點到左下角,再執行scale(1,-1)顛倒Y軸正負值,一個笛卡兒座標系便完成了。
預設上畫布網格前進一單位等於前進一像素大小,所以縮小0.5倍,就會變成前進0.5的像素大小,亦即縮小圖像一半大小,反之,放大2倍將放大圖像2倍。
縮放範例
本程式碼範例會畫出一系列不同縮放比例的萬花筒樣式圖案。
function draw() { var ctx = document.getElementById('canvas').getContext('2d'); ctx.strokeStyle = "#fc0"; ctx.lineWidth = 1.5; ctx.fillRect(0,0,300,300); // Uniform scaling ctx.save() ctx.translate(50,50); drawSpirograph(ctx,22,6,5); ctx.translate(100,0); ctx.scale(0.75,0.75); drawSpirograph(ctx,22,6,5); ctx.translate(133.333,0); ctx.scale(0.75,0.75); drawSpirograph(ctx,22,6,5); ctx.restore(); // Non uniform scaling (y direction) ctx.strokeStyle = "#0cf"; ctx.save() ctx.translate(50,150); ctx.scale(1,0.75); drawSpirograph(ctx,22,6,5); ctx.translate(100,0); ctx.scale(1,0.75); drawSpirograph(ctx,22,6,5); ctx.translate(100,0); ctx.scale(1,0.75); drawSpirograph(ctx,22,6,5); ctx.restore(); // Non uniform scaling (x direction) ctx.strokeStyle = "#cf0"; ctx.save() ctx.translate(50,250); ctx.scale(0.75,1); drawSpirograph(ctx,22,6,5); ctx.translate(133.333,0); ctx.scale(0.75,1); drawSpirograph(ctx,22,6,5); ctx.translate(177.777,0); ctx.scale(0.75,1); drawSpirograph(ctx,22,6,5); ctx.restore(); } function drawSpirograph(ctx,R,r,O){ var x1 = R-O; var y1 = 0; var i = 1; ctx.beginPath(); ctx.moveTo(x1,y1); do { if (i>20000) break; var x2 = (R+r)*Math.cos(i*Math.PI/72) - (r+O)*Math.cos(((R+r)/r)*(i*Math.PI/72)) var y2 = (R+r)*Math.sin(i*Math.PI/72) - (r+O)*Math.sin(((R+r)/r)*(i*Math.PI/72)) ctx.lineTo(x2,y2); x1 = x2; y1 = y2; i++; } while (x2 != R-O && y2 != 0 ); ctx.stroke(); }
<canvas id="canvas" width="300" height="300"></canvas>
draw();
第一排第一個黃色圖案是沒有縮放的圖案,然後往右到了第二個圖案,我們程式碼中輸入了 (0.75, 0.75)的縮放倍率,到了第三個圖案,我們還是輸入了 (0.75, 0.75)的縮放倍率,而基於之前有縮小過一次,所以第三個圖案相對於第一個沒有縮放的圖案是0.75 × 0.75 = 0.5625倍。
第二排藍色圖案我們只改變Y軸的縮放倍率,X軸維持不變,因而產生一個比一個更扁的橢圓圖形。同理,第三排綠色圖案改變X軸的縮放倍率,Y軸維持不變。
Screenshot | Live sample |
---|---|
變形
最後一個方法是設定變形矩陣,藉由改變變形矩陣,我們因此可以營造各種變形效果;其實先前所提到的rotate, translate, scale都是在設定變形矩陣,而這邊的這個方法就是直接去改變變形矩陣。
-
transform(m11, m12, m21, m22, dx, dy)
- 呼叫Transform會拿目前的變形矩陣乘以下列矩陣:
-
m11 m21 dx m12 m22 dy 0 0 1
- 運算後的新矩陣將取代目前的變形矩陣。
- 其中m11代表水平縮放圖像,m12代表水平偏移圖像,m21代表垂直偏移圖像,m22代表垂直縮放圖像,dx代表水平移動圖像,dy代表垂直移動圖像。
-
如果輸入
Infinity
值,不會引起例外錯誤,矩陣值會依照輸入設成無限。 -
setTransform(m11, m12, m21, m22, dx, dy)
- 復原目前矩陣為恆等矩陣(Identiy matrix,也就是預設矩陣),然後再以輸入參數呼叫transform()。
transform
/ setTransform
範例
function draw() { var ctx = document.getElementById('canvas').getContext('2d'); var sin = Math.sin(Math.PI/6); var cos = Math.cos(Math.PI/6); ctx.translate(100, 100); var c = 0; for (var i=0; i <= 12; i++) { c = Math.floor(255 / 12 * i); ctx.fillStyle = "rgb(" + c + "," + c + "," + c + ")"; ctx.fillRect(0, 0, 100, 10); ctx.transform(cos, sin, -sin, cos, 0, 0); } ctx.setTransform(-1, 0, 0, 1, 100, 100); ctx.fillStyle = "rgba(255, 128, 255, 0.5)"; ctx.fillRect(0, 50, 100, 100); }
<canvas id="canvas" width="200" height="250"></canvas>
draw();
Screenshot | Live sample |
---|---|