跳到主要内容

色差与对比度

两族函数回答的是不同问题。色差是一个数字,回答「这两个颜色看起来差多远」,用哪种指标就决定了在 哪个 space 里度量。WCAG 对比度是文本与图形的规范比值,始终在 sRGB 里计算。

plt_difference(a, b, metric) 与 plt_difference(a, b, metric, space)​

签名:plt_difference(a plt_color, b plt_color, metric VARCHAR) -> DOUBLE 签名:plt_difference(a plt_color, b plt_color, metric VARCHAR, space VARCHAR) -> DOUBLE

三参数形式在 CIE L*a*b*(D65)里度量 —— ΔE、HyAb 与 CIEDE2000 就定义在那里。四参数形式把两个 颜色都转换到 space(不做夹紧)再在那儿度量;如果该指标在这个 space 里没有定义,语句会失败,错误 信息里会列出可用的 space。

SELECT plt_difference(plt_lab(50, 2.6772, -79.7751), plt_lab(50, 0, -82.7485), 'ciede2000') AS delta_e,
plt_difference(plt_lab(50, 2.6772, -79.7751), plt_lab(50, 0, -82.7485), 'ciede2000', 'lch') AS same_in_lch;
指标含义有定义的空间
euclidean在该 space 各分量上的直线距离cam16_ucs_jab、lab、lms、lms_von_kries、luma、luma_linear、luma_gamma、luv、oklab、每个 RGB space、xyz、xyz_d50、yxy
euclidean_squared同上,取平方(排序更省,次序不变)与 euclidean 相同
delta_eCIE76 ΔE*abcam16_ucs_jab、cam16_ucs_jmh、lab、lch
improved_delta_e加上 palette 修正的 ΔE*abcam16_ucs_jab、cam16_ucs_jmh、lab、lch
hyab混合距离:Lab 亮度差加上 a/b(或 u/v)距离cam16_ucs_jab、lab、luv、oklab
ciede2000CIEDE2000 ΔE*00,标准的感知指标lab、lch
improved_ciede2000修掉不连续处的 CIEDE2000lab、lch

别名:cie76、deltae 与 delta_e_76 都指 delta_e;cie2000 与 delta_e_2000 都指 ciede2000。名字大小写不敏感,- 与空格视作 _。

SELECT plt_difference_metrics() AS metrics;
SELECT plt_difference(plt_srgb(1, 0, 0), plt_srgb(0, 1, 0), 'euclidean', 'oklab') AS oklab_distance,
plt_difference(plt_srgb(1, 0, 0), plt_srgb(0, 1, 0), 'euclidean', 'srgb') AS srgb_distance,
plt_difference(plt_srgb(1, 0, 0), plt_srgb(1, 0.05, 0), 'delta_e') AS nearly_identical;

指标用在错误的 space 里会报错,而不是悄悄做一次转换:

SELECT plt_difference(plt_srgb(1, 0, 0), plt_srgb(0, 1, 0), 'ciede2000', 'srgb');
SELECT plt_difference(plt_srgb(1, 0, 0), plt_srgb(0, 1, 0), 'lab-distance');

plt_relative_luminance(color)​

签名:plt_relative_luminance(color plt_color) -> DOUBLE

WCAG 2.1 的相对亮度:黑色为 0,白色为 1。颜色会先转换到 sRGB,因为这个定义以 sRGB 为前提。对比度 正是由它算出来的;单独用它也能判断一个颜色算「亮」还是「暗」:

SELECT plt_relative_luminance(plt_srgb(0, 0, 0)) AS black,
plt_relative_luminance(plt_srgb(0.5, 0.5, 0.5)) AS middle_gray,
plt_relative_luminance(plt_srgb(1, 1, 1)) AS white,
plt_relative_luminance(plt_lab(50, 0, 0)) AS lab_gray;

plt_contrast(a, b)​

签名:plt_contrast(a plt_color, b plt_color) -> DOUBLE

WCAG 2.1 的对比度 (L_lighter + 0.05) / (L_darker + 0.05),从 1(完全相同)到 21(黑对白)。两个 颜色都会转换到 sRGB。结果是对称的:

SELECT plt_contrast(plt_srgb(1, 1, 1), plt_srgb(0, 0, 0)) AS max_contrast,
plt_contrast(plt_srgb(1, 0, 0), plt_srgb(1, 1, 1)) AS red_on_white,
plt_contrast(plt_srgb(1, 0, 0), plt_srgb(1, 0, 0)) AS identical;
SELECT plt_contrast(plt_srgb(1, 1, 1), plt_srgb(0, 0, 0)) =
plt_contrast(plt_srgb(0, 0, 0), plt_srgb(1, 1, 1)) AS symmetric;

plt_contrast_check(a, b, criterion)​

签名:plt_contrast_check(a plt_color, b plt_color, criterion VARCHAR) -> BOOLEAN

用同一个比值直接回答某条 WCAG 准则:

准则最低比值
min_text4.5
min_large_text3
enhanced_text7
enhanced_large_text4.5
min_graphics3
SELECT plt_contrast_criteria() AS criteria;
SELECT plt_contrast_check(plt_srgb(1, 1, 1), plt_srgb(0, 0, 0), 'min_text') AS white_on_black,
plt_contrast_check(plt_srgb(1, 1, 1), plt_srgb(1, 0, 0), 'min_text') AS white_on_red,
plt_contrast_check(plt_srgb(1, 1, 1), plt_srgb(1, 0, 0), 'min_large_text') AS white_on_red_large;

一个常用做法是给一套色板逐色标注,再按它必须满足的准则过滤:

SELECT plt_to_hex(c) AS color,
plt_contrast(plt_srgb(1, 1, 1), c) AS on_white,
plt_contrast_check(plt_srgb(1, 1, 1), c, 'min_text') AS readable_on_white
FROM (VALUES (plt_named('rebeccapurple')),
(plt_named('gold')),
(plt_named('teal'))) t(c);
SELECT plt_contrast_check(plt_srgb(1, 1, 1), plt_srgb(0, 0, 0), 'aaa');

plt_most_readable(base, candidates) 及其重载​

签名:plt_most_readable(base plt_color, candidates plt_color[]) -> plt_color 签名:plt_most_readable(base plt_color, candidates plt_color[], criterion VARCHAR) -> plt_color 签名:plt_most_readable(base plt_color, candidates plt_color[], criterion VARCHAR, use_black_and_white BOOLEAN) -> plt_color

在候选颜色里挑出与 base 对比度最高的那个,用的就是 plt_contrast 返回的同一个比值。默认准则是 min_text,它只在 use_black_and_white 为真时起作用:此时若最优候选仍不满足该准则,函数返回黑白 两色中与 base 对比度更高的那个 —— 即 tinycolor 的 mostReadable 行为。候选列表为空会报错,未知 准则与 plt_contrast_check 一样让语句失败。

SELECT plt_to_hex(plt_most_readable(plt_named('navy'),
[plt_named('teal'), plt_named('gold'), plt_named('tomato')])) AS best,
plt_to_hex(plt_most_readable(plt_named('navy'),
[plt_named('teal')], 'min_text', true)) AS fallback;

这就是「给这个背景色找一个读得清的文字色」的逐行写法:

SELECT plt_to_hex(bg) AS background,
plt_to_hex(plt_most_readable(bg, [plt_named('white'), plt_named('black')])) AS label
FROM (VALUES (plt_named('seagreen')), (plt_named('gold')), (plt_named('rebeccapurple'))) t(bg);
SELECT plt_most_readable(plt_srgb(1, 1, 1), []);

亮度与明暗判断​

plt_relative_luminance 遵循 WCAG,对比度用的就是它。下面三个函数用的是更老的 YIQ 亮度 —— tinycolor 把它暴露为 getBrightness。它更粗糙,按通道加权(0.299 R + 0.587 G + 0.114 B),但 很多设计工具正是用它决定文字该用亮色还是暗色。

plt_brightness(color)​

签名:plt_brightness(color plt_color) -> DOUBLE

YIQ 亮度,0(黑)到 1(白)。颜色会先转换到 sRGB 并夹紧。

SELECT plt_brightness(plt_srgb(0, 0, 0)) AS black,
plt_brightness(plt_srgb(0.5, 0.5, 0.5)) AS middle_gray,
plt_brightness(plt_srgb(1, 1, 1)) AS white,
plt_brightness(plt_named('gold')) AS gold;

plt_is_light(color) 与 plt_is_dark(color)​

签名:plt_is_light(color plt_color) -> BOOLEAN 签名:plt_is_dark(color plt_color) -> BOOLEAN

在同一个 YIQ 值上取 128/255(约 0.502)这个阈值,也就是 tinycolor 的默认值。两者严格互补 —— 一个 颜色要么是亮的、要么是暗的,不会同时成立,也不会都不成立。

SELECT plt_is_light(plt_named('gold')) AS gold_light,
plt_is_dark(plt_named('gold')) AS gold_dark,
plt_is_light(plt_named('navy')) AS navy_light,
plt_is_dark(plt_named('navy')) AS navy_dark;

列出支持的名字​

plt_difference_metrics()​

签名:plt_difference_metrics() -> VARCHAR[]

plt_difference 接受的七个指标名。

plt_contrast_criteria()​

签名:plt_contrast_criteria() -> VARCHAR[]

plt_contrast_check 接受的五个准则名。