node-canvas的模糊实现

在较新的HTML版本中,图像的模糊可以用canvas的filter指定为blur的方式实现,但是node-canvas库目前还不支持filter。不过它开放了像素的访问和设置,所以业务需要自己实现了一个简单的图像模糊。

具体思路为,使用某种算法,对一个像素p,通过计算其周边的像素点(模糊半径)得到一个新的像素值,写入p,并且按顺序对于每一个像素操作一边。这里举例最简单的,均值模糊,即一个像素等于它周边像素的平均值。通常称为均值滤波器。常见的还有中值模糊、高斯模糊等。

代码实现如下,由于是自行操作像素点,效率比较低,实测大概需要500-800ms完成一张800*600的图片处理。而node-canvas自带的方法通常在100ms内可以完成。

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
const blur = 3; // 模糊半径
const url = 'https://sijie.wang/images/UeUkxVrtbEFwqCux.jpg'; // 图片

const canvas = Canvas.createCanvas(width, height);
const ctx = canvas.getContext('2d');
const image = await Canvas.loadImage(url);
ctx.drawImage(image, 0, 0);
const imageData = ctx.getImageData(0, 0, width, height);
ctx.clearRect(0, 0, width, height);
const { data } = imageData;
// 简单的均值模糊
for (let i = 0; i < data.length; i += 4) { // rgba,一个像素有4个值
// 从相邻格子中计算平均值
// 例如blur为1,即相邻1像素的格子,总共组成9个
// 9宫格
// 1|2|3
// 4|5|6
// 7|8|9
const rectWidth = 2 * blur + 1;
const points = Array.apply(null, { length: rectWidth * rectWidth }).map((item, index) => {
const row = Math.floor(index / rectWidth);
const column = Math.floor(index % rectWidth);
return i + (row - blur) * width * 4 + (column - blur) * 4;
});
// 边界上有些点不可用
const availablePoints = points.filter(p => p >= 0 && p < data.length);
const r = availablePoints.reduce((p, point) => p + data[point], 0);
const g = availablePoints.reduce((p, point) => p + data[point + 1], 0);
const b = availablePoints.reduce((p, point) => p + data[point + 2], 0);
data[i] = Math.floor(r / availablePoints.length);
data[i + 1] = Math.floor(g / availablePoints.length);
data[i + 2] = Math.floor(b / availablePoints.length);
}
ctx.putImageData(imageData, 0, 0);

处理前后效果:

处理前
处理后(距离为1)
处理后(距离为3)

文章作者: 王思捷
文章链接: https://sijie.wang/2019/03/29/canvas-blur/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 我爱平铺