Canvas图像处理和滤镜特效

简介

这段时间做了一个手机WebRTC拍照加特效的应用,主要用到canvas标签获取图像矩阵(这个在上一篇博客中已经详细介绍过:),然后做图像处理,例如滤镜特效(马赛克,浮雕,衬底,反色等),这里我们详细介绍相关的知识。其实从原理上讲,大部分的图像处理都是对图像像素矩阵和某个特效矩阵做卷积,得到新的像素矩阵,就是处理后的效果。关于这个大学课程《数字信息处理》里面有详细介绍。

转载请注明出处:http://www.haomou.net/2014/08/05/2014_html_canvas/

canas介绍

上一个介绍WebRTC中使用video标签和canvas标签的文章中,并没有详细的介绍canvas标签。这里补上。
canvas是一个新的HTML元素,这个元素可以被Script语言(通常是JavaScript)用来绘制图形。例如可以用它来画图、合成图象、或做动画。canvas最先在苹果公司(Apple)的Mac OS X Dashboard上被引入,而后被应用于Safari。后面被采用成为HTML5标准规范的一部分。
像其他的html标签一样,canvas拥有大部分通用的属性,比如width和height。canvas标签在使用中一般是通过js来操作的,可以吧canvas看成一个画布,在上面如何作图,js是就是进行创作的工具。操作画布之前,首先要获取改画布的上文context,这个可以理解为环境状态,比如画画之前先固定好画布,看一下画布的大小,决定怎么作画。这个获取上下文也就是这个意思。代码:

1
2
3
4
5
6
7
var canvas = document.getElementById('canvas_id');
if (canvas.getContext){
var ctx = canvas.getContext('2d');
// drawing code here
} else {
// canvas-unsupported code here
}

目前这个canvas只支持2d绘画,而3d绘画是通过webgl实现的。这个后续课程会讲解到,有兴趣的可以做个预研,了解一下。目前这个使用的比较多的是开源的Three.js框架。

canas绘制文本

绘制文本需要用到下列的属性和方法:
属性:
1.context.font属性,获取或设置文本的字体和大小。设置该属性的字符串语法与CSS语法中的font是一样的,不能解析成CSS的值会被忽略掉。
2.context.textAlign属性,获取或设置字体的对其方式。只允许下列值:start, end, left, right, 和center。其他值会被忽略,默认值是start。
3.context.textBaseline属性,获取或设置字体的基准线。有效的值如下:top, hanging, middle, alphabetic, ideographic, bottom。其他值会被忽略,默认值是alphabetic。
用法:
1.context.fillText(text, x, y [, maxWidth ] ),该方法用于在指定的位置绘制文本。如果设置了maxWidth,则会调整字符串使之符合这个条件。
2.context. strokeText(text, x, y [, maxWidth ] ),该方法用于在指定的位置绘制镂空的文本。
例子代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE HTML>  
<html>
<body>
<canvas id="lesson" width="300"height="200">
<p>Your browser does not support the canvas element!</p>
</canvas>
<script type="text/javascript">
window.onload = function() {
var canvas =document.getElementById("lesson");
var context =canvas.getContext("2d");

context.font ="30px Times New Roman";
context.fillText("HelloCanvas!", 10, 35);
}
</script>
</body>
</html>

canas绘制图片

绘制原图:context.drawImage(image, dx, dy)
缩放绘图:context.drawImage(image, dx, dy, dw, dh)
切片绘图:context.drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh)
合理的绘制多幅图像可以做出漂亮的组合效果,比如常见的画廊就是框图片与照片的叠加,只不过要注意绘制的先后顺序。
第一个参数image代表图片的来源,可以是下列几种:
  (1)页面内的图片:我们可以通过 document.images 集合、document.getElementsByTagName 方法又或者 document.getElementById 方法来获取页面内的图片。
  (2)已经完备的canvas:可以使用document.getElementsByTagName或document.getElementById方法来获取已经准备好的canvas元素。一个常用的场景就是为另一个大的canvas做缩略图。
  (3)通过data:url方式(http://en.wikipedia.org/wiki/Data:_URL )嵌入图像:Data urls允许用一串Base64编码的字符串的方式来定义一个图片。其优点就是图片内容即时可用,无须再到服务器兜一圈。(还有一个优点是,可以将CSS,JavaScript,HTML和图片全部封装在一起,迁移起来十分方便。)缺点就是图像没法缓存,图片大的话内嵌的url数据会相当的长。例如:

1
var img_src = 'data:image/gif;base64,R0lGODlhCwALAIAAAAAA3pn/ZiH5BAEAAAEALAAAAAALAAsAAAIUhA+hkcuO4lmNVindo7qyrIXiGBYAOw==';

  (4)动态创建的图片:我们可以用脚本创建一个新的Image对象,但这种方法的主要缺点是如果不希望脚本因为等待图片装置而暂停,还得需要突破预装载。
var img = new Image();
img.src = ‘myImage.png’;
  当脚本执行后,图片开始装载。若调用drawImage时,图片没装载完,脚本会等待直至装载完毕。如果不希望这样的效果,则需要使用图片的onload事件。(见下面的例子)。其他几个参数的含义:sx,sy是Image在源中的起始坐标,sw/sWidth,sh/sHeight是源中图片的宽和高,dx,dy是在目标中的坐标,dw/dWidth,dh/dHeight是目标的宽和高。具体可以参看下图:
示例
代码:

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
<?doctype html>
<html>
<head>
<title>cavas tests</title>
<script type="text/javascript">
function onStart() {
var canvas = document.getElementById("lesson01");
if(canvas.getContext)
{
var context = canvas.getContext("2d");
var pic = new Image();
pic.onload=function(){
context.drawImage(pic,0, 0);
}
pic.src = "http://imgsrc.baidu.com/forum/pic/item/e6b14bc2a4561b1fe4dd3b24.jpg";
}
}
</script>
</head>
<body onload="onStart();">
<canvas id="lesson01" width="500" height="500">
Your browser does not support Canvas.
</canvas>
</body>
</html>

图像处理

在泛函分析中,卷积、旋积或摺积(英语:Convolution)是通过两个函数f 和g 生成第三个函数的一种数学算子,表征函数f 与经过翻转和平移的g 的重叠部分的面积。所谓的对图像的卷积操作,就是指对图像上的每一点的像素值,用这个矩阵进行运算,得到一个新的值。关于卷积的详细理论介绍,参考另一篇博文:
图像处理的数学理论
在HTML5中可以使用canvas标签的getImageData方法获得像素矩阵,然后采用卷积做特效处理。

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
var canvas = document.getElementById('canvas1');
var context = canvas.getContext('2d');
var defaultImage = context.getImageData(0, 0, this.canvas.width, this.canvas.height);

ImageFilters.ConvolutionFilter = function (srcImageData, matrixX, matrixY, matrix, divisor, bias, preserveAlpha, clamp, color, alpha) {
var srcPixels = srcImageData.data,
srcWidth = srcImageData.width,
srcHeight = srcImageData.height,
srcLength = srcPixels.length,
dstImageData = this.utils.createImageData(srcWidth, srcHeight),
dstPixels = dstImageData.data;

divisor = divisor || 1;
bias = bias || 0;
// default true

(preserveAlpha !== false) && (preserveAlpha = true);
(clamp !== false) && (clamp = true);

color = color || 0;
alpha = alpha || 0;

var index = 0,
rows = matrixX >> 1,
cols = matrixY >> 1,
clampR = color >> 16 & 0xFF,
clampG = color >> 8 & 0xFF,
clampB = color & 0xFF,
clampA = alpha * 0xFF;

for (var y = 0; y < srcHeight; y += 1) {
for (var x = 0; x < srcWidth; x += 1, index += 4) {
var r = 0,
g = 0,
b = 0,
a = 0,
replace = false,
mIndex = 0,
v;

for (var row = -rows; row <= rows; row += 1) {
var rowIndex = y + row,
offset;

if (0 <= rowIndex && rowIndex < srcHeight) {
offset = rowIndex * srcWidth;
}
else if (clamp) {
offset = y * srcWidth;
}
else {
replace = true;
}

for (var col = -cols; col <= cols; col += 1) {
var m = matrix[mIndex++];

if (m !== 0) {
var colIndex = x + col;

if (!(0 <= colIndex && colIndex < srcWidth)) {
if (clamp) {
colIndex = x;
}
else {
replace = true;
}
}

if (replace) {
r += m * clampR;
g += m * clampG;
b += m * clampB;
a += m * clampA;
}
else {
var p = (offset + colIndex) << 2;
r += m * srcPixels[p];
g += m * srcPixels[p + 1];
b += m * srcPixels[p + 2];
a += m * srcPixels[p + 3];
}
}
}
}

dstPixels[index] = (v = r / divisor + bias) > 255 ? 255 : v < 0 ? 0 : v | 0;
dstPixels[index + 1] = (v = g / divisor + bias) > 255 ? 255 : v < 0 ? 0 : v | 0;
dstPixels[index + 2] = (v = b / divisor + bias) > 255 ? 255 : v < 0 ? 0 : v | 0;
dstPixels[index + 3] = preserveAlpha ? srcPixels[index + 3] : (v = a / divisor + bias) > 255 ? 255 : v < 0 ? 0 : v | 0;
}
}

return dstImageData;
};
ImageFilters.GaussianBlur = function (srcImageData, strength) {
var size, matrix, divisor;

switch (strength) {
case 2:
size = 5;
matrix = [
1, 1, 2, 1, 1,
1, 2, 4, 2, 1,
2, 4, 8, 4, 2,
1, 2, 4, 2, 1,
1, 1, 2, 1, 1
];
divisor = 52;
break;
case 3:
size = 7;
matrix = [
1, 1, 2, 2, 2, 1, 1,
1, 2, 2, 4, 2, 2, 1,
2, 2, 4, 8, 4, 2, 2,
2, 4, 8, 16, 8, 4, 2,
2, 2, 4, 8, 4, 2, 2,
1, 2, 2, 4, 2, 2, 1,
1, 1, 2, 2, 2, 1, 1
];
divisor = 140;
break;
case 4:
size = 15;
matrix = [
2 ,2 , 3 , 4 , 5 , 5 , 6 , 6 , 6 , 5 , 5 , 4 , 3 ,2 ,2,
2 ,3 , 4 , 5 , 7 , 7 , 8 , 8 , 8 , 7 , 7 , 5 , 4 ,3 ,2,
3 ,4 , 6 , 7 , 9 ,10 ,10 ,11 ,10 ,10 , 9 , 7 , 6 ,4 ,3,
4 ,5 , 7 , 9 ,10 ,12 ,13 ,13 ,13 ,12 ,10 , 9 , 7 ,5 ,4,
5 ,7 , 9 ,11 ,13 ,14 ,15 ,16 ,15 ,14 ,13 ,11 , 9 ,7 ,5,
5 ,7 ,10 ,12 ,14 ,16 ,17 ,18 ,17 ,16 ,14 ,12 ,10 ,7 ,5,
6 ,8 ,10 ,13 ,15 ,17 ,19 ,19 ,19 ,17 ,15 ,13 ,10 ,8 ,6,
6 ,8 ,11 ,13 ,16 ,18 ,19 ,20 ,19 ,18 ,16 ,13 ,11 ,8 ,6,
6 ,8 ,10 ,13 ,15 ,17 ,19 ,19 ,19 ,17 ,15 ,13 ,10 ,8 ,6,
5 ,7 ,10 ,12 ,14 ,16 ,17 ,18 ,17 ,16 ,14 ,12 ,10 ,7 ,5,
5 ,7 , 9 ,11 ,13 ,14 ,15 ,16 ,15 ,14 ,13 ,11 , 9 ,7 ,5,
4 ,5 , 7 , 9 ,10 ,12 ,13 ,13 ,13 ,12 ,10 , 9 , 7 ,5 ,4,
3 ,4 , 6 , 7 , 9 ,10 ,10 ,11 ,10 ,10 , 9 , 7 , 6 ,4 ,3,
2 ,3 , 4 , 5 , 7 , 7 , 8 , 8 , 8 , 7 , 7 , 5 , 4 ,3 ,2,
2 ,2 , 3 , 4 , 5 , 5 , 6 , 6 , 6 , 5 , 5 , 4 , 3 ,2 ,2
];
divisor = 2044;
break;
default:
size = 3;
matrix = [
1, 2, 1,
2, 4, 2,
1, 2, 1
];
divisor = 16;
break;
}
return this.ConvolutionFilter(srcImageData, size, size, matrix, divisor, 0, false);
};

var result = ImageFilters.GaussianBlur(this.defaultImage);
context.clearRect(0, 0, this.canvas.width, this.canvas.height);
try {
context.putImageData(result, 0, 0);
}
catch (e) {
trace(e);
}

上面是做高斯模糊处理的例子。使用的对图像数据卷积的操作,类似的可以做出很多效果。最后使用clearRect函数清除画布,使用putImageData函数将处理结果像素矩阵绘制到画布上。

谢谢!

转载请注明出处:http://www.haomou.net/2014/08/05/2014_html_canvas/

有问题请留言。T_T 皓眸大前端开发学习 T_T