本文將闡述如何使用canvas做一些最簡單的圖像處理,如水印添加、灰度濾鏡等。
先用HTML代碼創(chuàng)建一個(gè)canvas標(biāo)簽。并用js對這個(gè)canvas進(jìn)行最簡單的初始化。
<body>
<canvas id="canvas" width="650" height="328"></canvas>
</body>
<script>
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
</script>
以上代碼創(chuàng)建了一個(gè)2D畫布。
接下來我們往canvas中引入一張圖片,需要使用drawImage這個(gè)api,代碼如下。
var image = new Image();
image.src = "img/img.png";
image.onload = function() {
context.drawImage(image, 0, 0);
}
注意要在image加載完成后再調(diào)用drawImage函數(shù)。接下來我們可以看到圖片成功顯示在了界面上

我們還可以通過調(diào)整drawImage的參數(shù)來讓圖片以不同的尺寸展示。接下來我們詳細(xì)看看drawImage的參數(shù)類型。
根據(jù)MDN的定義,drawImage一共支持如下9個(gè)參數(shù)。
drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
看到這里不要懵逼…
接下來我來慢慢解析一下這些參數(shù)的意義。
第一個(gè)參數(shù)image應(yīng)該不需要額外解釋,就是傳入一個(gè)圖片對象。
第2、3、4、5個(gè)參數(shù)其實(shí)可以當(dāng)作一個(gè)整體來看。
代表了從這張圖片的(sx, sy)坐標(biāo)開始,取長為sWidth,sHeight的一部分圖片。
第6、7、8、9個(gè)參數(shù)其實(shí)也可以當(dāng)作一個(gè)整體來看。
代表了將圖片渲染到畫布的(dx, dy)坐標(biāo),在畫布上渲染的實(shí)際長度為dWidth, 實(shí)際高度為dHeight。
比如我可以這樣修改一下之前程序中的drawImage函數(shù)。
context.drawImage(image, 300, 100, 300, 200, 50, 80, 150, 100)

得到了如下的效果。
當(dāng)傳遞參數(shù)不為9個(gè)時(shí),有如下情況:
- 傳3個(gè)參數(shù)時(shí)。(image, dx, dy),圖片為原尺寸
- 傳5個(gè)參數(shù)時(shí)。(image, dx, dy, dWidth, dHeight)
接下來我們來嘗試給圖片添加一個(gè)水印
在drawImage這個(gè)方法中,我們不光能給它傳一個(gè)圖像作為參數(shù),其實(shí)我們還可以把一個(gè)canvas作為參數(shù)傳遞給它。
接下來讓我們來思考一下,我們是不是可以嘗試把水印也制作成一個(gè)canvas,之后將這個(gè)水印canvas也渲染到呈現(xiàn)圖片的主canvas當(dāng)中去呢?
讓我們來嘗試一下:
var markCanvas = document.createElement("canvas");
var markContext = markCanvas.getContext('2d');
markCanvas.width = 150;
markCanvas.height = 40;
markContext.font = "20px serif";
markContext.fillStyle = "rgba(255, 255, 255, 0.5)";
markContext.fillText("這是水印", 0, 20);
首先用js創(chuàng)建一個(gè)canvas元素,并給它設(shè)置好寬高。在這個(gè)canvas里,我輸出了一段文字,并設(shè)置文字的顏色和大小等屬性。
接下來在原先的代碼中增加一些
image.onload = function() {
context.drawImage(image, 0, 0);
context.drawImage(markCanvas, 500, 250, 150, 40); //渲染水印canvas
}
在渲染完圖片后,我們緊接著又將水印作為canvas渲染了進(jìn)去。
其實(shí)這種做法有一個(gè)專業(yè)術(shù)語,叫做離屏canvas
getImageData和putImageData實(shí)現(xiàn)濾鏡
如果你需要對圖片進(jìn)行像素級(jí)的操作,那么你很有可能會(huì)用到這兩個(gè)API。
我們通過getImageData可以將圖像轉(zhuǎn)換為一個(gè)數(shù)組。具體用法如下
var canvas2 = document.getElementById("canvas2");
var context2 = canvas2.getContext("2d");
//...
context2.drawImage(image, 0, 0);
//從圖片的(dx, dy)坐標(biāo)開始,獲取長為dWidth,高為dHeight區(qū)域的圖像
var imageData = context2.getImageData(image, dx, dy, dWidth, dHeight);
我們可以嘗試將imageData里打印出來

里面的data是一個(gè)一維數(shù)組,記錄了圖像中所有坐標(biāo)的信息。
但是要注意,在這個(gè)每一個(gè)像素點(diǎn),在這個(gè)一維數(shù)組里都有四位數(shù)來代表它,這四位數(shù)分別代表了r、g、b、a
也就是說,我們要獲取第x個(gè)像素點(diǎn)的信息,應(yīng)該采用這樣的方式:
var imageData = context2.getImageData(image, dx, dy, dWidth, dHeight);
var pxData = imageData.data;
//第x個(gè)像素的rgba信息
var obj = {
r: pxData[4 * x],
g: pxData[4 * x + 1],
b: pxData[4 * x + 2],
a: pxData[4 * x + 3]
}
接下來我們可以嘗試用以上api來制作一個(gè)灰度濾鏡。
//context先繪執(zhí)正常圖片
context.drawImage(image2, 0, 0);
//先從第一個(gè)context中獲取圖片信息
var imageData = context.getImageData(0, 0, 1000, 667);
var pxData = imageData.data;
//canvas區(qū)域的長為1000,寬為667
for(var i = 0; i < 1000 * 667; i++) {
//分別獲取rgb的值(a代表透明度,在此處用不上)
var r = pxData[4 * i];
var g = pxData[4 * i + 1];
var b = pxData[4 * i + 2];
//運(yùn)用圖像學(xué)公式,設(shè)置灰度值
var grey = r * 0.3 + g * 0.59 + b * 0.11;
//將rgb的值替換為灰度值
pxData[4 * i] = grey;
pxData[4 * i + 1] = grey;
pxData[4 * i + 2] = grey;
}
//將改變后的數(shù)據(jù)重新展現(xiàn)在canvas上
context.putImageData(imageData, 0, 0, 0, 0, 1000, 667);
效果如下:

canvas在retina屏上顯示模糊的bug
要解決這個(gè)問題,你需要將canvas本身的寬高設(shè)置成實(shí)際的兩倍(假定devicePixelRatio為2),然后再將其CSS設(shè)置為原始尺寸即可。而且經(jīng)過這樣的處理,對性能還會(huì)更好。
比如要渲染一個(gè)500px * 300px的圖片,就可以采用如下的方式
canvas.width=1000;
canvas.height=600;
canvas.style.width="500px";
canvas.style.height="300px";
關(guān)于詳細(xì)原因,可以查看這篇文章High DPI Canvas