前言

开始之前先看网上对这三者的解释。

亮度、对比度和饱和度是图像处理中的三个基本概念。亮度是指图像的明亮程度,对比度是指图像中最亮和最暗部分之间的差异,饱和度是指图像中颜色的鲜艳程度。一般来说,对比度越大,图像越清晰醒目,色彩也越鲜明艳丽;而对比度小,则会让整个画面都灰蒙蒙的。

看这些文字并不能完全理解这三者,反而容易混淆,看起来这三者都能调整画面的明暗,但又有所不同。

作为一个程序员,代码语言是比人言更加精确的。既然理解不了文字,就看代码如何实现。

亮度

1
2
3
4
5
6
7
8
9
10
11
function adjustBrightness(imageData, brightness) {
const data = imageData.data;

for (let i = 0; i < data.length; i += 4) {
data[i] = data[i] * brightness;
data[i + 1] = data[i + 1] * brightness;
data[i + 2] = data[i + 2] * brightness;
}

return imageData;
}

上面的代码给rgb每个值乘以一个大于0的系数brightness,brightness大于1就是增加亮度,brightness小于1(大于0)就是降低亮度。所以用一句话总结:调整亮度就是给图片上每个点像素的rgb值分别乘以一个相同的系数。

对比度

1
2
3
4
5
6
7
8
9
10
11
12
function adjustContrast(imageData, contrast) {
const data = imageData.data;
const factor = (259 * (contrast + 255)) / (255 * (259 - contrast));

for (let i = 0; i < data.length; i += 4) {
data[i] = factor * (data[i] - 128) + 128;
data[i + 1] = factor * (data[i + 1] - 128) + 128;
data[i + 2] = factor * (data[i + 2] - 128) + 128;
}

return imageData;
}

整体思路和调整亮度差不多,都是给rgb分别乘以某个系数,但没那么简单,主要是这两行让人很费解。

1
2
3
4
5
//...
const factor = (259 * (contrast + 255)) / (255 * (259 - contrast)); // 代码1
//...
data[i] = factor * (data[i] - 128) + 128; // 代码2
//...

代码1

公式中的255表示rgb颜色空间的最大取值,259是一个经过反复试验得出的数字,contrast取值-255到255,把contrast带入这个公式,259可以让这个公式的取值范围最接近0到128,至于整个公式的推导过程,我也搞不清楚。

我们写一些代码来看一下factor具体都有哪些值

1
2
3
4
5
6
7
function getFactor(contrast){
return (259 * (contrast + 255)) / (255 * (259 - contrast));
}

for(let i=-255;i<=255;i++){
console.log(getFactor(i))
}

这段代码运行结果factor取值从0到129.5,129.5趋近128,你可以试一试,把259改成258或者260,结果都不趋近128。

其实用坐标系表示更为合适:y=(259*(x+255))/(255*(259-x))

817d9b1e-a8ef-4b65-916b-031c3e94169e-image.png

把这条曲线分成两部分,如果-255<x<0,0<y<1;如果0<x<255,1<y<128。

代码2

假设0<x<255,y从1到128,把y带入代码2。带入之前先解释一下代码2,rgb颜色空间的取值范围是0-255,128是中点,之所以先用色值减去128,是为了判断色值的强弱,位于128左边的色值弱,反之则强,由于factor取值1到128,正数乘以factor,结果是比原值还大的正数,负数乘以factor,结果是比原值还小的负数。

所以如果x(对比度)大于0,代码2就能保证强者更强,弱者更弱的效果。

反之如果x(对比度)小于0,代码2也能保证强者更弱,弱者更强的效果,因为正数乘以大于0小于1的数,会变小;负数乘以大于0小于1的数,会变大。

饱和度

这个和前两个不一样,前两个都是先计算出一个合适的factor,然后每个像素rgb分别乘以这个factor,而饱和度却没有合适的factor,通常都是先把rgb转换成hls调节饱和度,完了再转成rgb。就像下面这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function adjustSaturation(imageData, saturation) {
const data = imageData.data;

for (let i = 0; i < data.length; i += 4) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];

// 转换为 HSL 颜色空间
const hsl = rgbToHsl(r, g, b);

// 改变饱和度
hsl.s += saturation;

// 转换为 RGB 颜色空间
const rgb = hslToRgb(hsl.h, hsl.s, hsl.l);

data[i] = rgb.r;
data[i + 1] = rgb.g;
data[i + 2] = rgb.b;
}
return imageData;
}

饱和度又叫纯度,颜色的纯度由某种颜色的主色占比决定的,所以调节饱和度就是调整某种颜色主色的占比,那如何调节这个比例,这和一些生活经验很相似,比如我们有一些浓墨,如何稀释,对没错,加水就能稀释。

那如何稀释主色,这就不能加水了,是加灰,在rgb颜色空间中,所有rgb三种一样的颜色都是灰色。

所以综上,调整某种颜色的饱和度首先要找到它的主色,然后往里面加/减灰色。有的颜色主色很容易找到,比如rgb(255,10,10),要增加这个颜色的饱和度,只需要减少g、b的值,比如饱和度最大的rgb(255,0,0)。有的颜色则没有这么简单,比如rgb(230,233,232),可能就是大多数颜色都难以简单的计算出主色,所以调整饱和度不能简单的给rgb乘以一个factor。

HLS

最后说一下hls颜色空间,通过上面对饱和度的解释你会发现使用rgb颜色空间可能有利于计算机存储颜色,但是不方便人类理解,hls便是解决这个问题的,hls由色相、饱和度、亮度组成,色相就是我上面说的主色,取值从0到360,所以可以理解有360种色相;饱和度指的是某种颜色色相的占比(色相与灰度的比例)。

亮度与饱和度也是容易混淆的,其实借用生活中的经验很容易区分。我们知道大多数物体本身是不发光的,光线照到物体上,物体反射光线到人眼,我们才会看到东西,这里的反射强度就是亮度的概念,而饱和度指的是某种颜色主色的占比,比如rgb(255,0,0)这种颜色,虽然红色的纯度很高,但是没有光源照射,也是看不到的,这种现象用hls描述就是hsl(0, 100%, 0%)。

明白了光源的存在,亮度与饱和度很容易区分,大多数颜色(除了灰色)都有饱和度,但是不一定有亮度,因为可能没有光源。有了以上理解,那色温就很好理解了,亮度反应的是光源与被照射物体反光的强度,而色温顾名思义则表示光的温度,关于色温的详细介绍可以参考这篇。色温用生活中的经验也很好理解,同样的物体在日光的照耀下与在灯光的照耀下,效果肯定不一样,尤其是像自如配套的那种大黄灯。