彩色图片灰度转换

彩色图片灰度转换

色彩空间

色彩空间是通过一组数值来表示颜色的抽象数学模型,如 RGB、HSL。

三原色模型

三原色模型(RGB color model)指的是将红、绿、蓝三原色的色光按不同的比例叠加,产生其他颜色。

三原色模型之所以选择红、绿、蓝为基本色,是因为这三者不能用其他单色光合成,且人眼的锥形感光细胞对红绿蓝的光线比较敏感。

在计算机中采用 8 bit 分别表示三种基本色的亮度,即从 0~255,0 表示强度最低,255 表示强度最高。

RGB 通过简单的亮度加减来表示所有的颜色,如白色(255, 255, 255)、黑色(0, 0, 0)、黄色(255, 255, 0)等。

HSL和HSV色彩空间

HSL 是 RGB 的一种变形,主要通过色相、饱和度、亮度三个数值来表示颜色。

  • 色相(Hue

    色彩的基本属性,表示颜色的范围,分布在一个环(也叫色相环)中,每个角度代表不同的颜色;色相环中有 6 大主色,分别为红(360°/0°)、黄(60°)、绿(120°)、青(180°)、蓝(240°)、洋红(300°)。

  • 饱和度(Saturation

    色彩的纯度,范围为百分比(0~1);数值越大,颜色中的灰色越少,颜色越鲜艳,呈现一种从理性(灰度)到感性(纯色)的变化。

  • 亮度(LightnessBrightnessvalue

    颜色的明亮程度,范围为百分比(0~1);数值越小,色彩越暗,越接近于黑色;数值越大,色彩越亮,越接近于白色。

HSL 和 HSV(HSB)两者的区别在于对饱和度的定义不一样,HSL 中 L 为 1 时,在 HSV 中 L 的值仅有 0.5。通过上面的柱状图也可以看出,HSL 在 L 为 1 时表现为白色,而 HSV 需要 S 为 0 且 L 为 1 时才表现为白色。

灰度转换

  • 算法一

    1
    shader = (Red, Green, Blue) => (Red + Green + Blue) / 3
  • 算法二

    1
    shader = (Red, Green, Blue) => Red * 0.299 + Green * 0.587 + Blue * 0.114;
  • 算法三

    1
    shader = (Red, Green, Blue) => (Math.max(Red, Green, Blue) + Math.min(Red, Green, Blue)) / 2;
  • 算法四

    1
    shader = (Red, Green, Blue) => Math.max(Red, Green, Blue);
  • 算法五

    1
    shader = (Red, Green, Blue) => Math.min(Red, Green, Blue);
  • 算法六

    1
    2
    3
    4
    5
    shader = (Red, Green, Blue) => Red;

    shader = (Red, Green, Blue) => Green;

    shader = (Red, Green, Blue) => Blue;

代码实现

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
// html

<img src="./demo.jpg" alt="">
<h2>算法1:</h2>
<p>(Red + Green + Blue) / 3</p>
<canvas id="demo1"></canvas>

<h2>算法2:</h2>
<p>Red * 0.299 + Green * 0.587 + Blue * 0.114</p>
<canvas id="demo2"></canvas>

<h2>算法3:</h2>
<p>(Math.max(Red, Green, Blue) + Math.min(Red, Green, Blue)) / 2</p>
<canvas id="demo3"></canvas>

<h2>算法4:</h2>
<p>Math.max(Red, Green, Blue)</p>
<canvas id="demo4"></canvas>

<h2>算法5:</h2>
<p>Math.min(Red, Green, Blue)</p>
<canvas id="demo5"></canvas>

<h2>算法6:</h2>
<p>Red</p>
<canvas id="demo6"></canvas>

<h2>算法7:</h2>
<p>Green</p>
<canvas id="demo7"></canvas>

<h2>算法8:</h2>
<p>Blue</p>
<canvas id="demo8"></canvas>
<script src="./index.js"></script>
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
// index.js
const init = ({ src, imgSize }) => {
const img = new Image();

img.src = src;
img.onload = () => {
const imgData = getImgData(img, imgSize.width, imgSize.height);

shader('demo1', 1, imgData, imgSize);
shader('demo2', 2, imgData, imgSize);
shader('demo3', 3, imgData, imgSize);
shader('demo4', 4, imgData, imgSize);
shader('demo5', 5, imgData, imgSize);
shader('demo6', 6, imgData, imgSize);
shader('demo7', 7, imgData, imgSize);
shader('demo8', 8, imgData, imgSize);
};
};

const initWorker = (src, id, imgSize) => {
if (Object.is(src, '')) return;

const worker = new Worker(src);
worker.onmessage = (message) => {
const context = getContextByID(id, imgSize);

context && context.putImageData(message.data, 0, 0);
};

return worker;
}

const getContextByID = (id, imgSize) => {
const element = document.getElementById(id) || {},
{ tagName } = element;

element.width = imgSize.width;
element.height = imgSize.height;
return Object.is(tagName, 'CANVAS') ? element.getContext('2d') : null;
}

const getImgData = (img, width, height) => {
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;

const context = canvas.getContext('2d');
context.drawImage(img, 0, 0, width, height);

return context.getImageData(0, 0, width, height);
}

const shader = (id, type, imgData, imgSize) => {
const worker = initWorker('./shader.js', id, imgSize);
worker.postMessage({ type, imgData });
};

init({ src: './demo.jpg', imgSize: { width: 1920, height: 1080} });
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
// shader.js

onmessage = (message) => {
const { type, imgData } = message.data;

let shader = null;
switch (type) {
case 1:
shader = (r, g, b) => (r + g + b) / 3;
break;
case 2:
shader = (r, g, b) => (r * 0.299 + g * 0.587 + b * 0.114);
break;
case 3:
shader = (r, g, b) => (Math.max(r, g, b) + Math.min(r, g, b)) / 2;
break;
case 4:
shader = (r, g, b) => Math.max(r, g, b);
break;
case 5:
shader = (r, g, b) => Math.min(r, g, b);
break;
case 6:
shader = (r, g, b) => r;
break;
case 7:
shader = (r, g, b) => g;
break;
case 8:
shader = (r, g, b) => b;
break;
default:
}

let data = imgData.data;
for (let i = 0; i < (data.length / 4); i++) {
const [red, green, blue] = [data[i * 4], data[i * 4 + 1], data[i * 4 + 2]],
color = shader(red, green, blue);

data[i * 4] = color;
data[i * 4 + 1] = color;
data[i * 4 + 2] = color;
data[i * 4 + 3] = 255;
}

postMessage(imgData);
};

参考