对比色对于字体着色很有用,但是,我发现很难确保调色板中的颜色始终保持令人满意的对比度。一些看上去很酷的调色板在某些情况下会出现相邻token对比度消失的情况,这是我非常讨厌的。
完美对比色序列可以通过使用单位根来构建。考虑HSL色轮上的点 r\theta,其中 r \in \mathbb {C} \land 0 < |r| < 1 是极径(决定饱和度和亮度),\theta 是极角(决定色调)。设 r = e ^{ i \phi_1 },\theta = \omega t + \phi_2。然后,如果 \omega = \frac{q}{p},其中 p 为素数,q \in \mathbb{N} \land 0 < q < p,我们可以确保对于任意连续的 \frac{p}{q} 种颜色,对比度不小于 \frac{q}{p}。这里 \phi_1 和 \phi_2 是种子参数,用于保存调色板的配置。
(require 'color)
(defun hsl-color-wheel (theta r i)
"Pick color from a color wheel, TEHTA ∈ [0, 2π], R, I ∈ [0, 1]."
(let* ((2pi (* 2 float-pi))
(hue (/ (- theta (* 2pi (floor (/ theta 2pi)))) 2pi)))
(apply #'color-rgb-to-hex
(nconc (color-hsl-to-rgb hue i r) '(2)))))
(defun perfect-palette (p q &optional phi-1 phi-2)
"Calculate perfect pallete with P, Q, PHI-1 and PHI-2.
P, Q are integers, PHI-1, PHI-2 ∈ [0, 2π].
Return a list of hex strings,"
(let ((phi-1 (or phi-1 0))
(phi-2 (or phi-2 0))
(2pi (* 2 float-pi))
(omega (/ (* 2 float-pi q) p)))
(mapcar
(lambda (x)
(let ((normal-x (* (/ (float x) p) 2pi)))
(hsl-color-wheel (+ (* omega x) phi-2)
(/ (1+ (sin phi-1)) 2)
(/ (1+ (cos phi-1)) 2))))
(number-sequence 0 (1- p) 1))))
;; example:
(perfect-palette 7 3 (/ float-pi 4) (/ float-pi 6))
在最初的设计里我考虑给决定饱和度和亮度的参数 \phi_1 再加一个微扰,但是欠缺稳定性。冯诺依曼说过,四个参数可以画大象了,我们还是只保留三个参数(注意到 p/q 实际上是一个参数)好了。
假设有一个语法,它有两个规则A B C
和A C B
,那么,如果我们分配颜色像(A . "#00f") (B. "#f00") (C . "#00f")
,当出现满足第二条规则A C B
的推导时,A C
就出现了对比度消失的问题。然而,如果这个语法中只有第一条规则A B C
,这个方案就没有那么糟糕了。因此,在许多情况下,p 确实可以被缩小。那么,在进行词法着色的时候,给定一个词法规则,是否存在一个高效的算法能算出最小的 p 呢?