前言

前文带大家看过3D LUT滤镜如何实现影片级别的滤镜效果,这种滤镜最为强大,可以实现任何滤镜效果。

通过photoshop导出处理文件,即可通过程序实现对应的滤镜效果,而且由于是颜色查找,即颜色A,通过LUT文件,直接映射成颜色B,所以处理过程只是像素点的颜色映射,不存在计算,速度非常快。

然后也带大家看过如何操纵图片的像素,通过canvas获取页面上图片的像素,然后对像素点直接进行计算,再输出新的颜色,就能得到处理后的图片。

现在,带大家看看第二种方式的滤镜,到底可以实现哪些效果。由于这种方式是对像素点进行运算,而这些算法有限,所以能实现的效果也是有限的。

filter

初始化页面

首先,我们初始化一个简单的页面,提供一个上传图片按钮,一个canvas显示图片,并用css控制下图片显示的大小,还有一个点击使用滤镜的按钮。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html>
<head>
<title>基础滤镜</title>
<style type="text/css">
canvas {
width: 300px;
}
</style>
</head>

<body>
<input type="file" id="fileInput" name="选择图片" />
<div class="wrap-image">
<canvas id="imageUpload"></canvas>
</div>
<div onclick="useFilter()">点击使用滤镜</div>
<script type="text/javascript"></script>
</body>
</html>

封装图片处理

应用滤镜,我们会重复使用getImageData、putImageData和遍历像素点的逻辑,这些我们都可以封装起来。

下面我们创建一个CanvasImage类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class CanvasImage {
constructor(img, context) {
this.image = img;
this.context = context;
}

getData() {
return this.context.getImageData(0, 0, this.image.width, this.image.height);
}
setData(data) {
this.context.putImageData(data, 0, 0);
}
transform() {
// TODO 后文再完善
}
}

然后我们在上传图片时创建一个CanvasImage的实例,接下来我们看看上传图片。

上传图片

用户点击选择图片按钮后,选择了一张图片。
我们通过new Image()创建一个img,并将src设置为用户选择的本地图片的临时地址,在图片加载完成后,给页面中的canvas设置宽高,然后drawImage把图片显示到canvas上,最后new CanvasImage(img, context)来创建一个CanvasImage的实例filter。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let filter
const fileInput = document.getElementById('fileInput');

fileInput.addEventListener('change', (e) => {
const imgElement = document.getElementById('imageUpload');
const img = new Image();
img.src = URL.createObjectURL(e.target.files[0]);
img.onload = function () {
imgElement.width = img.width
imgElement.height = img.height
const context = imgElement.getContext("2d");
context.drawImage(img, 0, 0);
filter = new CanvasImage(img, context);
}
}, false);

封装滤镜处理函数

现在我们来完善上面空着的transform:

1
2
3
4
5
6
7
8
9
10
11
12
transform(fn, factor) {
const imageData = this.getData()
const { data } = imageData
for (let i = 0; i < data.length; i += 4) {
const [r, g, b, a] = fn.call(this, data[i], data[i + 1], data[i + 2], data[i + 3], factor, i);
data[i] = r;
data[i + 1] = g;
data[i + 2] = b;
data[i + 3] = a;
}
this.setData(imageData)
}

transform接受2个参数,fn是滤镜算法函数,这个函数输入像素相关数据,输出r,g,b,a。factor代表滤镜算法需要的控制因子。

这样我们就只需要关注滤镜算法函数fn怎么写,然后执行filter.transform(fn, factor)就可以对图片做处理,并把处理后的图片数据更新到页面。

接下来我们看看几个简单的滤镜算法。

滤镜算法

灰度

比如我们先实现一个灰度算法,取r、g、b的加权平均值:

1
2
3
4
function Greyscale(r, g, b) {
const avg = 0.3 * r + 0.59 * g + 0.11 * b;
return [avg, avg, avg, 255];
}

filter_Greyscale

然后在点击使用滤镜绑定的函数useFilter中可以这样写:

1
2
3
function useFilter() {
filter.transform(Greyscale)
}

怀旧

1
2
3
4
function Sepia(r, g, b) {
const avg = 0.3 * r + 0.59 * g + 0.11 * b;
return [avg + 100, avg + 50, avg, 255];
}

filter_Sepia

负片

1
2
3
function invert(r, g, b) {
return [255 - r, 255 - g, 255 - b, 255];
}

filter_invert

噪点

1
2
3
4
function noise(r, g, b, a, factor) {
var rand = (0.5 - Math.random()) * factor;
return [r + rand, g + rand, b + rand, 255];
}

调用时需传入噪点控制因子,比如传45:

1
2
3
function useFilter() {
filter.transform(noise, 45)
}

下图是原图、噪点传45和100的对比:

filter_noise

亮度

1
2
3
function brightness(r, g, b, a, factor) {
return [r + factor, g + factor, b + factor, 255];
}

调用时,控制因子factor是亮度。
下图是原图、亮度传45和100的对比:

filter_brightness

饱和度

1
2
3
4
5
6
7
8
function saturation(r, g, b, a, factor) {
// factor取值[-1, 1]
const max = Math.max(r, g, b);
r += max !== r ? (max - r) * (-factor) : 0;
g += max !== g ? (max - g) * (-factor) : 0;
b += max !== b ? (max - b) * (-factor) : 0;
return [r, g, b, 255];
}

下图是饱和度传-0.5,原图,饱和度传0.5的对比:

filter_saturation

对比度

1
2
3
4
5
6
7
8
9
function contrast(r, g, b, a, factor) {
// factor取值[-1, 1]
const contrast = Math.floor(factor * 255);
const contrastF = 259 * (contrast + 255) / (255 * (259 - contrast));
r = contrastF * (r - 128) + 128;
g = contrastF * (g - 128) + 128;
b = contrastF * (b - 128) + 128;
return [r, g, b, 255];
}

下图是对比度传-0.2,原图,对比度传0.2的对比:
filter_contrast

色温

1
2
3
4
5
6
7
function temperature(r, g, b, a, factor) {
// factor取值[-1, 1]
const temperature = Math.round(factor * 255)
r = Math.min(Math.max(r + temperature, 0), 255)
b = Math.min(Math.max(b - temperature, 0), 255)
return [r, g, b, 255];
}

下图是色温传-0.2,原图,色温传0.2的对比:
filter_temperature

总结

上面写了这些示例,现在轮到你发挥想象力了,只需要在滤镜函数中编写你的颜色处理程序:

1
2
3
4
function noise(r, g, b, a, factor) {
// 在这里发挥你的想象力
return [r, g, b, a];
}

比如,调换r、g、b的顺序,或者把b都变成255……然后实现别的滤镜效果,比如色调、模糊等等,而且这些基础滤镜效果还可以叠加,从而创建出更多的效果哦。