canvas“美图”照片

最近遇到需要对图片进行处理的情况,搜索到了这篇文章Image filters with canvas,实质就是利用canvas对图片的像素进行处理。

Step1: get pixels 获取图片的像素

在canvas中,可以用getImageData来获取画布上的像素数据,

1
var imageData = ctx.getImageData(left, top, width, height);`

得到的是一个imageData对象,包含三个内容

  • imageData.width [只读]

  • imageData.height [只读]

  • imageData.data [只读] 首先这是一个一维数组,其次它是Uint8ClampedArray类型的一维数组。 这种类型的数组,就是把rgba这四个通道的数值(0-255)依次排开保存在这个一维的数组中,按照图片从上到下,从左到右的顺序。

比如data 的值是[59,61,60,255,65,65,62,255…..] data[0],59就表示的是这个图像左上角第一个像素的r值(红色通道的值) ; data[1],61表示左上角第一个像素的g值(绿色通道的值); data[2],60表示 左上角第一个像素的b值(蓝色通道的值);data[3],255表示左上角第一个像素的a值(透明度的值)。

Step3: draw pixels 把像素画成图片

得到像素的rgba数组之后,对它们进行相应的处理,然后再使用putImageData把像素“绘制”到画布上,就可以看到处理之后的图片了。

1
ctx.putImageData(imagedata, dx, dy);

imagedata同上面的imagedata对象;dx 在目标画布上距离左上角的水平位移;dy在目标画布上距离左上角的垂直位移。

有了getImageData和putImageData这两个API,已经解决了“输入”与“输出”的问题。剩下的就是中间的处理过程了。

Step2: how to handle pixels 处理像素

最关键的就是中间的处理过程啦。

(1)灰度图片(把一个彩色的图片变成灰色的),公式如下,这是所谓的心理学公式,来源于人眼对红/蓝不敏感。

1
0.2126*r + 0.7152*g + 0.0722*b;

把imageData中所有的rgb值都设置为这个值,显示出来的图片“看起来”就是灰色的。

1
2
3
4
5
6
7
8
9
10
11
12
function(imagedata) {
var d = imagedata.data;
for (var i=0; i<d.length; i+=4) {
var r = d[i];
var g = d[i+1];
var b = d[i+2];

var v = 0.2126*r + 0.7152*g + 0.0722*b;
d[i] = d[i+1] = d[i+2] = v
}
return imagedata;
}

(2)图片变亮变暗, 把rgb的颜色值加大,图片就变亮;颜色值减小,图片就变暗。255,255,255白色;0,0,0黑色

1
2
3
4
5
6
7
8
9
function(imagedata, adjustment) {
var d = imagedata.data;
for (var i=0; i<d.length; i+=4) {
d[i] += adjustment;
d[i+1] += adjustment;
d[i+2] += adjustment;
}
return imagedata;
};

(3)卷积convolution,一个看到名字就觉得很难的东西。同时它又是一个对图像处理很有用的东西。可以用它来做“模糊”、“锐化”、“边缘检测”等工作。它的概念就是源像素周围的像素进行加权相加,然后作为目标元素。

比如我们要“锐化”一个图像,那么就用

[ 0, -1, 0,

-1, 5, -1,

​ 0, -1, 0 ]

这个变化矩阵去加权像素。

如下图所示:

目标像素的r = 1 0 + 10 -1 + 1 0 + 5 -1 + 2 5 + 20 -1 + 10 0 + 1 -1 + 2 * 0 = -26 最终r就是0(小于0的会用0代替,大于255的会用255代替), 同理依次计算g ,b 就能得到目标像素的r, g ,b 。

注意,因为imageData是按照rgba的顺序依次存的,变化矩阵也是用一维数组存的,所以在计算的时候,要注意正确的取值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
function convolution(imageData, matrix) {
//获取变化矩阵的合
var divisor = matrix.reduce(function(a, b) {return a + b;}) || 1;

var oldpx = imageData.data;
var w = imageData.width;
var h = imageData.height

//如果直接在imageData上修改的话,会导致后面的数据计算的时候拿到的不是原始的数据。所以需要创建一个新的 imageData数据
var ctx = document.createElement('canvas').getContext('2d');
var newdata = ctx.createImageData(w,h);

var newpx = newdata.data
var len = newpx.length;


var res = 0;

for (var i = 0; i < len; i++) {
if ((i + 1) % 4 === 0) { //alpha 通道的数据不变
newpx[i] = oldpx[i];
continue;
}
res = 0;
var these = [
oldpx[i - w * 4 - 4] || oldpx[i], //没有的元素用本身去填
oldpx[i - w * 4] || oldpx[i],
oldpx[i - w * 4 + 4] || oldpx[i],
oldpx[i - 4] || oldpx[i],
oldpx[i],
oldpx[i + 4] || oldpx[i],
oldpx[i + w * 4 - 4] || oldpx[i],
oldpx[i + w * 4] || oldpx[i],
oldpx[i + w * 4 + 4] || oldpx[i]
];
for (var j = 0; j < 9; j++) {
res += these[j] * m[j];
}//求和
res /= divisor;

newpx[i] = res;
}

return newdata;

}

卷积的变化矩阵有很多,运用不同的矩阵可以得到很多“美图”的效果。查了一下,有很多常见的算法,并且对于卷积计算的优化工作也是图像处理研究领域中的一个小课题。这也是一个可以深入研究的分支~