本文将阐述如何使用canvas做一些最简单的图像处理,如水印添加、灰度滤镜等。
先用HTML代码创建一个canvas标签。并用js对这个canvas进行最简单的初始化。
<body>
<canvas id="canvas" width="650" height="328"></canvas>
</body>
<script>
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
</script>
以上代码创建了一个2D画布。
接下来我们往canvas中引入一张图片,需要使用drawImage
这个api,代码如下。
var image = new Image();
image.src = "img/img.png";
image.onload = function() {
context.drawImage(image, 0, 0);
}
注意要在image加载完成后再调用drawImage
函数。接下来我们可以看到图片成功显示在了界面上
我们还可以通过调整drawImage
的参数来让图片以不同的尺寸展示。接下来我们详细看看drawImage
的参数类型。
根据MDN的定义,drawImage
一共支持如下9个参数。
drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
看到这里不要懵逼…
接下来我来慢慢解析一下这些参数的意义。
第一个参数image应该不需要额外解释,就是传入一个图片对象。
第2、3、4、5个参数其实可以当作一个整体来看。
代表了从这张图片的(sx, sy)坐标开始,取长为sWidth,sHeight的一部分图片。
第6、7、8、9个参数其实也可以当作一个整体来看。
代表了将图片渲染到画布的(dx, dy)坐标,在画布上渲染的实际长度为dWidth, 实际高度为dHeight。
比如我可以这样修改一下之前程序中的drawImage函数。
context.drawImage(image, 300, 100, 300, 200, 50, 80, 150, 100)
得到了如下的效果。
当传递参数不为9个时,有如下情况:
- 传3个参数时。(image, dx, dy),图片为原尺寸
- 传5个参数时。(image, dx, dy, dWidth, dHeight)
接下来我们来尝试给图片添加一个水印
在drawImage
这个方法中,我们不光能给它传一个图像作为参数,其实我们还可以把一个canvas作为参数传递给它。
接下来让我们来思考一下,我们是不是可以尝试把水印也制作成一个canvas,之后将这个水印canvas也渲染到呈现图片的主canvas当中去呢?
让我们来尝试一下:
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创建一个canvas元素,并给它设置好宽高。在这个canvas里,我输出了一段文字,并设置文字的颜色和大小等属性。
接下来在原先的代码中增加一些
image.onload = function() {
context.drawImage(image, 0, 0);
context.drawImage(markCanvas, 500, 250, 150, 40); //渲染水印canvas
}
在渲染完图片后,我们紧接着又将水印作为canvas渲染了进去。
其实这种做法有一个专业术语,叫做离屏canvas
getImageData和putImageData实现滤镜
如果你需要对图片进行像素级的操作,那么你很有可能会用到这两个API。
我们通过getImageData
可以将图像转换为一个数组。具体用法如下
var canvas2 = document.getElementById("canvas2");
var context2 = canvas2.getContext("2d");
//...
context2.drawImage(image, 0, 0);
//从图片的(dx, dy)坐标开始,获取长为dWidth,高为dHeight区域的图像
var imageData = context2.getImageData(image, dx, dy, dWidth, dHeight);
我们可以尝试将imageData里打印出来
里面的data是一个一维数组,记录了图像中所有坐标的信息。
但是要注意,在这个每一个像素点,在这个一维数组里都有四位数来代表它,这四位数分别代表了r、g、b、a
也就是说,我们要获取第x个像素点的信息,应该采用这样的方式:
var imageData = context2.getImageData(image, dx, dy, dWidth, dHeight);
var pxData = imageData.data;
//第x个像素的rgba信息
var obj = {
r: pxData[4 * x],
g: pxData[4 * x + 1],
b: pxData[4 * x + 2],
a: pxData[4 * x + 3]
}
接下来我们可以尝试用以上api来制作一个灰度滤镜。
//context先绘执正常图片
context.drawImage(image2, 0, 0);
//先从第一个context中获取图片信息
var imageData = context.getImageData(0, 0, 1000, 667);
var pxData = imageData.data;
//canvas区域的长为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];
//运用图像学公式,设置灰度值
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;
}
//将改变后的数据重新展现在canvas上
context.putImageData(imageData, 0, 0, 0, 0, 1000, 667);
效果如下:
canvas在retina屏上显示模糊的bug
要解决这个问题,你需要将canvas本身的宽高设置成实际的两倍(假定devicePixelRatio为2),然后再将其CSS设置为原始尺寸即可。而且经过这样的处理,对性能还会更好。
比如要渲染一个500px * 300px的图片,就可以采用如下的方式
canvas.width=1000;
canvas.height=600;
canvas.style.width="500px";
canvas.style.height="300px";
关于详细原因,可以查看这篇文章High DPI Canvas