使用canvas的過程中,使用 drawImage 可以繪製圖片。但有時候,需要將一張本來方正的圖片,繪製到一個平行四邊形上面去。如下圖所示。
大體的思路是,先對canvas坐標系使用一定的坐標變換,然後再使用 drawImage 繪製圖片,即可獲得以上圖片。
常規的變換有3種,平移,縮放,旋轉,原生的canvas已經包含了相應的一些方法。
為了方便觀察,先繪製上一個網格(其中, WIDTH 和 HEIGHT 分別為canvas的寬度與高度)
function drawGrids() { drawGrid(x); drawGrid(y); }
function drawGrid(direction) { var i, maxLength, step = 100; ctx.strokeStyle = rgba(0,0,0,0.3); if (direction == x) { maxLength = HEIGHT; } else if (direction == y) { maxLength = WIDTH; } ctx.beginPath(); for (i = 0; i < maxLength; i += step) { if (direction == x) { ctx.moveTo(0, i); ctx.lineTo(WIDTH, i); } else if (direction == y) { ctx.moveTo(i, 0); ctx.lineTo(i, HEIGHT); } } ctx.stroke(); ctx.closePath(); }
然後繪製一個圖片,
ctx.save(); ctx.lineWidth = 2; ctx.strokeStyle = blue; ctx.drawImage(image, 200, 200, width, height); ctx.strokeRect(200, 200, width, height); ctx.restore(); 原圖
ctx.save(); ctx.lineWidth = 2; ctx.strokeStyle = blue; ctx.drawImage(image, 200, 200, width, height); ctx.strokeRect(200, 200, width, height); ctx.restore();
平移
context.translate(x, y);
各個參數含義和作用如下:
平移坐標系,繪製一個圖片(為了便於對比觀察,將原圖也畫上,原圖沒有邊框)
ctx.save(); ctx.drawImage(image, 200, 200, width, height); ctx.translate(100, 100); ctx.lineWidth = 2; ctx.strokeStyle = blue; ctx.drawImage(image, 200, 200, width, height); ctx.strokeRect(200, 200, width, height); ctx.restore();
縮放
context.scale(x, y);
-1
ctx.save(); ctx.drawImage(image, 200, 200, width, height); ctx.scale(2, 2); ctx.lineWidth = 2; ctx.strokeStyle = blue; ctx.drawImage(image, 200, 200, width, height); ctx.strokeRect(200, 200, width, height); ctx.restore(); 縮放
ctx.save(); ctx.drawImage(image, 200, 200, width, height); ctx.scale(2, 2); ctx.lineWidth = 2; ctx.strokeStyle = blue; ctx.drawImage(image, 200, 200, width, height); ctx.strokeRect(200, 200, width, height); ctx.restore();
值得注意的是,原圖中,圖片的左上角在(200,200)這個位置,但是放大之後,圖片的左上角在(400,400)處。這是因為,縮放操作是都是相對於原點來計算的。
旋轉
context.rotate(angle);
ctx.save(); ctx.drawImage(image, 200, 200, width, height); ctx.translate(100, 100); ctx.lineWidth = 2; ctx.strokeStyle = blue; ctx.drawImage(image, 200, 200, width, height); ctx.strokeRect(200, 200, width, height); ctx.restore(); 旋轉
與上方的縮放類似,旋轉操作後,不以原點為起點的圖片在繪製的過程中,起點位置都發生了改變。
鏡像
前面在將縮放的時候有提到,如果值是-1,則可以起到翻轉的效果。(為了便於觀察,在繪製前先進行了平移操作,藍框為水平翻轉,紅框為垂直翻轉)
ctx.save(); ctx.translate(400, 300); ctx.drawImage(image, 100, 100, width, height); ctx.lineWidth = 2; ctx.strokeStyle = blue;
ctx.save(); ctx.scale(-1, 1); ctx.drawImage(image, 100, 100, width, height); ctx.strokeRect(100, 100, width, height); ctx.restore();
ctx.save(); ctx.scale(1, -1); ctx.strokeStyle = red; ctx.drawImage(image, 100, 100, width, height); ctx.strokeRect(100, 100, width, height); ctx.restore();
ctx.restore(); 鏡像
自定義坐標變換包括兩個方法:transform 和 setTransform。這兩者大體相同, transform 方法和 setTransform 方法的區別在於,後者一旦執行會完全重置已有的變換, transform 方法則是累加。
context.transform(a, b, c, d, e, f);
a ~ f 這些參數對應的變換矩陣描述為:
transform 和 setTransform 的優點是能實現可能的任何效果,缺點則是不夠直觀。上述的translate(),scale(),rotate(),這三個方法,都可以使用的 transform 方法來實現。
ctx.translate(x, y); ctx.transform(1, 0, 0, 1, x, y);
ctx.scale(x, y); ctx.transform(x, 0, 0, y, 0, 0);
ctx.rotate(angle); sin = Math.sin(angle); cos = Math.cos(angle); ctx.transform(cos, sin, -sin, cos, 0, 0);
接下來,利用 transform 來畫出最上面的效果。
先找出需要變形成的平行四邊形的幾個頂點,然後畫出邊框
var points = [{ x: 200, y: 400, }, { x: 500, y: 300, }, { x: 600, y: 400, }, { x: 300, y: 500, }, ];
function drawPath(points) { var p0 = points[0], p1 = points[1], p2 = points[2], p3 = points[3]; ctx.save(); ctx.beginPath(); ctx.strokeStyle = #e00; ctx.moveTo(p0.x, p0.y); ctx.lineTo(p1.x, p1.y); ctx.lineTo(p2.x, p2.y); ctx.lineTo(p3.x, p3.y); ctx.lineTo(p0.x, p0.y); ctx.stroke(); ctx.closePath(); ctx.restore(); } 邊框
以左上角為起點,先畫一個寬高均為100的圖
然後水平放大3倍
然後再水平斜切
最後再垂直斜切
即可完成需求。
代碼實現如下(注釋中包含了分析)
function drawImageInPoints(ctx, image, points) { var xSize, ySize, xTan, yTan, width = 100, height = 100, transform = {}, p0 = points[0], p1 = points[1], p3 = points[3];
// 根據圖片左上角的坐標 設置e和f transform.e = p0.x; transform.f = p0.y;
// 根據進行斜切之前的矩形和圖片尺寸的大小關係 設置a和d xSize = p1.x - p0.x; transform.a = xSize / width; ySize = p3.y - p0.y; transform.d = ySize / height;
// 根據圖片是否需要斜切,來設置 設置b和d // 水平斜切,水平線旋轉到目標線的角度的正切值,角度以順時針旋轉為正 // 垂直斜切,垂直線旋轉到目標線的角度的正切值,角度以逆時針旋轉為正 if (p1.x == p0.x) { transform.b = 0; console.log(不應該相等); } else { xTan = (p1.y - p0.y) / (p1.x - p0.x); transform.b = xTan * transform.a; }
if (p3.y == p0.y) { transform.c = 0; console.log(不應該相等); } else { yTan = (p3.x - p0.x) / (p3.y - p0.y); transform.c = yTan * transform.d; }
ctx.save(); ctx.transform(transform.a, transform.b, transform.c, transform.d, transform.e, transform.f); ctx.strokeStyle = #6cf; ctx.drawImage(image, 0, 0, width, height); ctx.restore();
}
這裡需要注意的一點為:points為一個數組,長度為4,每個數組元素表示一個(x,y)坐標,數組的順序為特定的順序,第一個點為左上角的點,剩下的點以順時針方向添加。若4個點不能構成一個平行四邊形,則會認作是第一個點,第三個點,第四個點組成的平行四邊形,第三個傳入的點坐標可以為null。