导语:可视化智能色彩是Tvsion中的一个子项目,主要功能是根据一个颜色智能生成和其搭配的色板。

一、需求分析
        输入一个颜色,找出n个和这个颜色最搭配的颜色,再通过分裂互补方法将其排序,然后每个颜色按照规则生成m个色阶。

寻找颜色流程图:

请输入图片描述

分裂互补排序流程图:

请输入图片描述

色阶生成流程图:

请输入图片描述

二、分步实现
1 寻找搭配颜色
        在《可视化色彩空间解析指南》中详细介绍了最佳人眼视觉空间CIELab颜色空间,根据设计师的要求,所以寻找颜色实际上是,在CIELab颜色空间中固定一个颜色,然后均匀的找出n个颜色,这样得到的颜色视觉效果最佳。

        CIELab颜色空间是一个不规则的三维立体空间,而且颜色差异计算不是常规的三维空间距离计算,所以无法用等差的方式均匀的找到n个点,开发时,借鉴了iwanthue中使用的k-means聚类算法,对其进行了固定主色、分级生成等的一些优化。

k-means聚类算法是最著名的划分聚类算法,运用到智能色彩中的操作步骤如下:

1.1 建立坐标系
        将CIELab颜色空间按照一定的数值等差划分为一个三维坐标系。

1.2 色差计算
        在这个坐标系中随机找n个点,计算坐标系的每个点和这n个点的色彩相识度。

iwanthue中计算方式是CIE(国际照明委员会)在1976年提出的公式:

        

但是这种方式计算结果不是很准确,所以CIE(国际照明委员会)在2000年又提出更加科学的计算方式:

        

deltaE2000的js实现如下

type ColorType = [number, number, number];
const deltaE2000 = (lab1: ColorType, lab2: ColorType) => {
const deg2rad = (a: number) => a * .017453292519943295;
const rad2deg = (a: number) => a * 57.29577951308232;

const { sqrt, pow, atan2, abs, cos, sin, exp } = Math;

const [l1, a1, b1] = lab1,

[l2, a2, b2] = lab2,
avg_lp = (l1 + l2) / 2,
c1 = sqrt(pow(a1, 2) + pow(b1, 2)),
c2 = sqrt(pow(a2, 2) + pow(b2, 2)),
avg_c = (c1 + c2) / 2,
g = (1 - sqrt(pow(avg_c, 7) / (pow(avg_c, 7) + pow(25, 7)))) / 2,
a1p = a1 * (1 + g),
a2p = a2 * (1 + g),
c1p = sqrt(pow(a1p, 2) + pow(b1, 2)),
c2p = sqrt(pow(a2p, 2) + pow(b2, 2)),
avg_cp = (c1p + c2p) / 2;

let h1p = rad2deg(atan2(b1, a1p));
if (h1p < 0) {

h1p += 360;

}

let h2p = rad2deg(atan2(b2, a2p));
if (h2p < 0) {

h2p += 360;

}

const avg_hp = abs(h1p - h2p) > 180 ? (h1p + h2p + 360) / 2 : (h1p + h2p) / 2,

t = 1 - 0.17 * cos(deg2rad(avg_hp - 30)) + 0.24 * cos(deg2rad(2 * avg_hp)) + 0.32 * cos(deg2rad(3 * avg_hp + 6)) - 0.2 * cos(deg2rad(4 * avg_hp - 63));

let delta_hp = h2p - h1p;

if (abs(delta_hp) > 180) {

if (h2p <= h1p) {
  delta_hp += 360;
}
else {
  delta_hp -= 360;
}

}

const delta_lp = l2 - l1,

delta_cp = c2p - c1p;

delta_hp = 2 sqrt(c1p c2p) * sin(deg2rad(delta_hp) / 2);

const s_l = 1 + ((0.015 * pow(avg_lp - 50, 2)) / sqrt(20 + pow(avg_lp - 50, 2))),

s_c = 1 + 0.045 * avg_cp,
s_h = 1 + 0.015 * avg_cp * t,

delta_ro = 30 * exp(-(pow((avg_hp - 275) / 25, 2))),
r_c = 2 * sqrt(pow(avg_cp, 7) / (pow(avg_cp, 7) + pow(25, 7))),
r_t = -r_c * sin(2 * deg2rad(delta_ro));

const kl = 1, kc = 1, kh = 1;

return sqrt(pow(delta_lp / (s_l * kl), 2)

+ pow(delta_cp / (s_c * kc), 2)
+ pow(delta_hp / (s_h * kh), 2)
+ r_t * (delta_cp / (s_c * kc)) * (delta_hp / (s_h * kh)));

}
 

1.3 聚类
        将更相似的颜色划分到一起,得到n个聚类块,把n个点换成n个聚类块的中心,如此聚类计算多次,n个点会比较均匀的分布在CIELab颜色空间中。

1.4 初始点选取优化
        如果初始的n个点用随机的方式选取,有时计算结果会不太理想,所以可引入k-means++算法。

1.5 色域空间分级优化
        最开始是从色域空间中之间寻找n个点,然后对其中的某些色相进行单独重新聚类,但是这样的结果有时会分布的不太均匀,导致某些色相的颜色出现比较多,后面设计师提出在第一次聚类时就先把色域空间进行划分,然后对这些色域空间分别进行聚类计算,得到的结果相比之前均匀性更加稳定。

2 分裂互补排序
        最开始使用的排序方式是按色相从小到大排序,后面对其进行改进,使用了开源项目珊瑚中的颜色排列方式—分裂互补排序,首先计算出主色的标准邻近色,将n个颜色和标准邻近色按deltaE从小到大进行排序,然后计算出主色的标准互补色,将n个颜色中最接近标准互补色的颜色放到最后。

js代码实现如下:

import chroma from 'chroma-js';
type ColorType = [number, number, number];
// 分裂互补排序
const sortColorsByComplementary = (colors: string[], color: string) => {
const colorHsv = chroma(color).hsv();

// 标准互补色
const complementary: ColorType = [...colorHsv];
complementary[0] = (colorHsv[0] + 180) % 360;

// 标准邻近色
const nearby: ColorType = [...colorHsv];
nearby[0] = (colorHsv[0] + 330) % 360;

const hex2Lab = (str: string) => chroma(str).lab();
const hsv2Lab = (hcl: ColorType) => chroma(hcl, 'hsv').lab();

// 计算颜色差异为上文提到的deltaE2000
const { distance } = utils;

// 寻找最接近互补色的颜色
const complementaryLab = hsv2Lab(complementary);
let complementaryColor = colors[0];
let d1 = distance(complementaryLab, hex2Lab(colors[0]));
colors.forEach(c => {

const dc = distance(complementaryLab, hex2Lab(c));
if (dc < d1) {
  d1 = dc;
  complementaryColor = c;
}

});

// 按与标准邻近色deltaE小->大排序,去除最接近互补色的颜色
const nearbyLab = hsv2Lab(nearby);
const res = colors.sort((a, b) => {

const da = distance(nearbyLab, hex2Lab(a));
const db = distance(nearbyLab, hex2Lab(b));
return da - db;

}).filter(c => c !== complementaryColor);

// 插入主色
res.unshift(chroma(color).hex());
// 插入最接近互补色的颜色
res.push(complementaryColor);

return res;
};
 

3 生成色阶
        目前生成色阶使用的是用主色lch的l亮度变低得到一个暗的颜色,用主色lch中的l亮度变亮得到一个亮的颜色,加上主色三个颜色,通过人眼最佳分辨颜色变化中均分为n个色阶,使用的是chroma辅助计算,后期会进行自研优化。

代码如下:

import chroma, { deltaE } from 'chroma-js';

const genColorByDarken = (color: string, step: number) => {
const colorIsWhite = chroma(color).hex() !== '#ffffff';
const number = colorIsWhite ? step + 1 : step;
const darkNum = 17, brightenNum = 9999;

const darkenColor = chroma(color).set('lch.l', darkNum).hex();
const brightenColor = chroma(color).set('lch.l', brightenNum).hex();
const steps = Array(number).fill(1).map((_, i) => i / number);
const scaleFn = chroma.scale(sortColors([brightenColor, color, darkenColor])).mode('hsl').correctLightness();

const res = steps.map(number => scaleFn(number).hex());

// 当主色不为白色时去掉顶部的白色
if (colorIsWhite) {

res.shift();

}
return res;
};

标签: none

评论已关闭