写在前面
-
提到色彩,即使是刚有辨知能力的小朋友,也能说上几句~
-
那我们前端工程师,无论工作还是生活,每天都在与各种色彩打交道,那便更有必要系统地了解关于色彩方面的知识
-
本文将从前端角度,聊聊色彩空间
-
根据 CSS Color Module Level 4 标准
<color> = <hex-color> | <named-color> | currentcolor | transparent <rgb()> | <rgba()> | <hsl()> | <hsla()> | <hwb()> | <lab()> | <lch()> | <gray()> | <color()> | <device-cmyk()> | <system-color>
named-color
transparent
currentcolor
system-color
减色法混色模型 CMYK 与 加色法混色模型 RGB
减色法混色模型 CMYK
- C - Cyan - 青色
- M - Magenta - 红色
- Y - Yellow - 黄色
- K - blacK - 黑色(防止与 RGB 的 B 冲突)
- 上过美术课,应该听过“红黄蓝”三原色的说法,但这和 CMYK 不太一样,实际上,颜料显示颜色的原理是它吸收了所有别的颜色的光,只反射一种颜色,所以颜料三原色其实是红、绿、蓝的补色,也就是:品红、黄、青。因为它们跟红、黄、蓝相近,所以有了这样的说法。
- 为什么会比三原色多了一色呢?
- 三种颜色调成黑色,贵
- 且不是纯粹的黑,三色等量相加之后只能形成一种深灰或深褐(我要五彩斑斓的黑
加色法混色模型 RGB
- 我们在 CSS 样式中看到的形式如 #RRGGBB 的颜色代码,就是 RGB 颜色的十六进制表示法,其中 RR、GG、BB 分别是两位十六进制数字,表示红、绿、蓝三色通道的色阶。色阶可以表示某个通道的强弱。
- 但 RGB 并不能表示人眼所见的所有颜色,它只能表示下面的这个区域
- RGB 对色彩的描述,对计算机友好,但不够人性化
- RGB 我们只能大致判断出它偏向于红色、绿色还是蓝色,或者在颜色立方体的大致位置。
- 以及对比两个 RGB 颜色的时候,我们只能通过对比它们在 RGB 立方体中的相对距离,来判断它们的颜色差异。
- 但我们对色彩的认识往往先是:颜色是什么,鲜艳不鲜艳,亮还是暗
- and It’s not easy to to alter an RGB color to produce a lighter variant of the same hue.
- 这在我们开发中也十分常见,有些时候设计师定下视觉基调和主色,我们需要根据数据情况由前端动态生成颜色值
HSL HSB&HSV
- CSS 和 Canvas2D 都可以直接支持 HSL 颜色,只有 WebGL 需要做 RGB2HSV 转换。
HSL
H - Hue - 色相 - 0-360°的圆心角
- H(hue)分量,代表的是人眼所能感知的颜色范围,这些颜色分布在一个平面的色相环上,取值范围是0°到360°的圆心角,每个角度可以代表一种颜色。
- 色相值的意义在于,我们可以在不改变光感的情况下,通过旋转色相环来改变颜色。
- 在实际应用中,我们需要记住色相环上的六大主色,用作基本参照:360°/0°红、60°黄、120°绿、180°青、240°蓝、300°洋红,它们在色相环上按照60°圆心角的间隔排列。
S - Saturation - 饱和度 - 0~1 或者 0%~100% 间的值
- 和黑白没有关系,饱和度不控制颜色中混入黑白的多寡
- 数值越大,颜色中的灰色越少,颜色越鲜艳,呈现一种从灰度到纯色的变化。
L - Lightness - 亮度 - 0~1 或者 0%~100% 间的值
- 控制纯色中的混入的黑白两种颜色
- 数值越小,色彩越暗,越接近于黑色;数值越大,色彩越亮,越接近于白色。
HSB & HSV
- HSB 又称 HSV
- HSV 色轮- WebGL 实现
- 像素坐标转换为极坐标,再除以 2π,就能得到 HSV 的 H 值
- 鼠标位置的 x、y 坐标来决定 S 和 V 的值
S - Saturation - 饱和度 - 0~1 或者 0%~100%间的值
- 控制纯色中混入白色的量,值越大,白色越少,颜色越纯;
B - Brightness - 亮度 - 0~1 或者 0%~100%间的值
- 控制纯色中混入黑色的量,值越大,黑色越少,明度越高
RGB 立方体转换成 HSL / HSV 过程
-
我们可以把这个过程简单理解成,将 RGB 颜色的立方体从直角坐标系投影到极坐标系的圆柱上
-
HSV(色相、饱和度、明度)在概念上可以被认为是颜色的倒圆锥体(黑点在下顶点,白色在上底面圆心)
-
HSL 在概念上表示了一个双圆锥体和圆球体(白色在上顶点,黑色在下顶点,最大横切面的圆心是半程灰色)
-
我们可以从下图来对比 HSL & HSV
-
但即使我们可以均匀地修改每组的亮度和饱和度,由于人眼对不同频率的光的敏感度不同,我们仍然会感觉有的颜色看起来和其他的颜色差距明显,有的颜色还是没那么明显
-
看下面的例子
for(let i = 0; i < 20; i++) { ctx.fillStyle = `hsl(${Math.floor(i * 15)}, 50%, 50%)`; ctx.beginPath(); ctx.arc((i - 10) * 20, 60, 10, 0, Math.PI * 2); ctx.fill(); } for(let i = 0; i < 20; i++) { ctx.fillStyle = `hsl(${Math.floor((i % 2 ? 60 : 210) + 3 * i)}, 50%, 50%)`; ctx.beginPath(); ctx.arc((i - 10) * 20, -60, 10, 0, Math.PI * 2); ctx.fill(); }
- 我们绘制两排不同的圆,让第一排每个圆的色相间隔都是 15,再让第二排圆的颜色在色相 60 和 210 附近两两交错。
- 最终生成效果如下:
- 可以看出:
- 第一排圆你会发现,虽然它们的色相相差都是 15,但是相互之间颜色变化并不是均匀的,尤其是中间几个绿色圆的颜色比较接近。
- 第二排圆,虽然这些圆的亮度都是 50%,但是蓝色和紫色的圆看起来就是不如偏绿偏黄的圆亮。
-
因此,HSL 依然不是最完美的色彩表示方法,我们还需要建立一套符合人类认知的标准。这个标准尽可能地满足一下 2 个原则
- 人眼看到的色差 = 颜色向量间的欧氏距离
- 相同的亮度,能让人感觉亮度相同
CIE Lab / CIE Lch
-
Lab色彩空间(英语:Lab color space)是颜色-对立空间,带有维度L表示亮度,a和b表示颜色对立维度,基于了非线性压缩的CIE XYZ色彩空间坐标。
-
CIE Lab 比较特殊的一点是,目前还没有能支持 CIE Lab 的图形系统,但规范中已经给出了 Lab 颜色值的定义
lab() = lab( <percentage> <number> <number> [ / <alpha-value> ]? ) lch() = lch( <percentage> <number> <hue> [ / <alpha-value> ]? )
-
且一些 JavaScript 库也已经可以直接处理 Lab 颜色空间了,如 d3-color
- 我们可以通过 Lab color picker 来了解三个颜色之前的相互作用
for(let i = 0; i < 20; i++) { const c = d3.lab(30, i * 15 - 150, i * 15 - 150).rgb(); ctx.fillStyle = `rgb(${c.r}, ${c.g}, ${c.b})`; ctx.beginPath(); ctx.arc((i - 10) * 20, 60, 10, 0, Math.PI * 2); ctx.fill(); } for(let i = 0; i < 20; i++) { const c = d3.lab(i * 5, 80, 80).rgb(); ctx.fillStyle = `rgb(${c.r}, ${c.g}, ${c.b})`; ctx.beginPath(); ctx.arc((i - 10) * 20, -60, 10, 0, Math.PI * 2); ctx.fill(); }
- 这个例子与 HSL 的例子一样,也是显示两排圆形。
- 第一排相邻圆形之间的 lab 色值的欧氏空间距离相同
- 第二排相邻圆形之间的亮度按 5 阶的方式递增
-
可以看出,在以 CIELab 方式呈现的色彩变化中,我们设置的数值和人眼感知的一致性比较强。
-
至此,规范中出现的色彩空间表示方法已经介绍完了。
Cubehelix 色盘
- 它的原理就是在 RGB 的立方中构建一段螺旋线,让色相随着亮度增加螺旋变换。
- 我们可以用 cubehelix 写一个颜色随着长度变化的长度
- 首先,我们直接使用 cubehelix 函数创建一个 color 映射。
- cubehelix 函数是一个高阶函数,它的返回值是一个色盘映射函数。这个返回函数的参数范围是 0 到 1,当它从小到大依次改变的时候,不仅颜色会依次改变,亮度也会依次增强。
- 然后,我们用正弦函数来模拟数据的周期性变化,通过 color 获取当前的颜色值,再把颜色值赋给 ctx.fillStyle,颜色就能显示出来了。
- 最后,我们用 rect 将柱状图画出来,用 requestAnimationFrame 实现动画就可以了 。
- 首先,我们直接使用 cubehelix 函数创建一个 color 映射。
import {cubehelix} from 'cubehelix';
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
ctx.translate(0, 256);
ctx.scale(1, -1);
const color = cubehelix(); // 构造cubehelix色盘颜色映射函数
const T = 2000;
function update(t) {
const p = 0.5 + 0.5 * Math.sin(t / T);
ctx.clearRect(0, -256, 512, 512);
const {r, g, b} = color(p);
ctx.fillStyle = `rgb(${255 * r},${255 * g},${255 * b})`;
ctx.beginPath();
ctx.rect(20, -20, 480 * p, 40);
ctx.fill();
window.ctx = ctx;
requestAnimationFrame(update);
}
update(0);
Coverting
参考资料
- CSS Color Module Level 4
- wikipedia HSL&HSV
- 知乎-色彩空间中的 HSL、HSV、HSB 有什么区别?
- d3-color
- Cubehelix
- Cubehelix颜色表算法
- wikipedia Lab色彩空间
- JS实现RGB-HSL-HSB相互转换
写在后面
- 祝大家多多发财