[Перевод] Алгоритм генерирования цветовых палитр
Ищете красивую цветовую палитру для сайта? Недавно установили дома RGB-подсветку, или хотите покрасить комнату в новые цвета? Или купили клавиатуру с цветной подсветкой и хотите использовать её по полной? В какой бы ситуации вы ни оказались, наверняка постоянно настраиваете цветовые схемы.
Будучи программистом, я быстро написал несколько строк кода для генерирования случайных цветовых палитр. Сразу почуяв, что такой подход может дать не лучшие результаты, я за пару минут реализовал кнопку «перезагрузки» палитры. Мне представлялось, что для получения прекрасной схемы просто нужно немного удачи и терпения.
Я ошибался. Генерирование палитр из случайных цветов — отстой. Время от времени красивый цвет соседствует с уродливым, грязным оттенком коричневого или жёлтого. Подборки цветов получаются всегда либо слишком тёмные, либо слишком светлые и малоконтрастные, либо наборы состоят из очень похожих цветов. Нужно было придумать другое решение.
Цветовые пространства
Начнём с теории. Сегодня для классификации цветов широко используются такие цветовые пространства:
sRGB
RGB означает Red Green Blue
. Так работают дисплеи: они излучают свет в трёх цветовых каналах, которые смешиваются в разной пропорции для получения всевозможных цветов. Значение в каждом канале варьируется от 0 до 255. R:0, G:0, B:0
(или #000000 в шестнадцатеричном выражении) — это чёрный цвет, а R:255, G:255, B:255
(или #ffffff) — белый.
CIE Lab
Цветовое пространство CIE Lab шире sRGB и включает в себя все цвета, воспринимаемые человеком. Оно создавалось с расчётом на универсальность восприятия. Иными словами, расстояние между цветами соответствует субъективной разнице: если значения двух цветов близки друг к другу, то и выглядят они похоже. С другой стороны, два далеко расположенных друг от друга цвета также воспринимаются как совсем непохожие. В CIE Lab для насыщенных цветов выделено больше места, чем для тёмных и светлых. К слову, для человеческого глаза очень тёмный зелёный почти неотличим от чёрного. Кроме того, это цветовое пространство трёхмерное: L
означает светлоту (от 0.0 до 1.0), a
и b
(примерно от -1.0 до 1.0) — цветовые каналы.
HCL
Если RGB описывает, как дисплей отображает цвета, а CIE Lab — как мы их воспринимаем, то HCL — это цветовое пространство, которое ближе всего описывает то, как мы думаем о цветах. Оно тоже трёхмерное, H
означает цветовой тон (hue) (от 0 до 360 градусов), С
— насыщенность (chroma) и L
— яркость (luminance) (оба параметра измеряются от 0,0 до 1,0).
Для вычислений рекомендую использовать CIE Lab, а для представления палитр пользователю — HCL. При желании можно преобразовать значения из этих пространств в RGB.
Разложение цветового пространства
Поскольку мне нужно было получить набор уникальных, индивидуальных цветов, сначала отбросим те, что выглядят очень похожими. Цветовое пространство будет трёхмерным, и для разделения таких низкоразмерных наборов данных прекрасно подходит алгоритм кластеризации методом k-средних. Он пытается разложить данные (в нашем случае — цветовое пространство) на k отдельных областей. И затем палитра собирается из центральных точек кластеров в этих областях. На гифке показано двумерное отображение работы алгоритма в трёхмерном пространстве CIE Lab.
Пишем код
С помощью реализованного на Go алгоритма метода k-средних задача решается всего в несколько строк кода. Сначала подготовим значения цветов в пространстве CIE Lab:
var d clusters.Observations
for l := 0.2; l <= 0.8; l += 0.05 {
for a := -1.0; a < 1.0; a += 0.1 {
for b := -1.0; b < 1.0; b += 0.1 {
d = append(d, clusters.Coordinates{l, a, b})
}
}
}
Я уже подобрал пару параметров и ввёл определённые ограничения для генерируемых цветов. В этом примере мы выкинем цвета слишком тёмные (яркость 0,8).
Разложим только что созданное цветовое пространство:
km := kmeans.New()
clusters, _ := km.Partition(d, 8)
Функция Partition
возвратит кусочки восьми кластеров. У каждого кластера есть точка Center
, представляющая собой отдельный цвет в заданном пространстве. Её координаты легко можно преобразовать в шестнадцатеричное RGB-значение:
col := colorful.Lab(c.Center[0], c.Center[1], c.Center[2])
col.Clamped().Hex()
Помните, что CIE Lab шире RGB, и значит некоторые Lab-значения нельзя преобразовать в RGB. Такие значения можно с помощью Clamped
преобразовать в наиболее близкие цвета RGB-пространства.
Полный код
package main
import (
"github.com/muesli/kmeans"
"github.com/muesli/clusters"
colorful "github.com/lucasb-eyer/go-colorful"
)
func main() {
// Create data points in the CIE L*a*b* color space
// l for lightness, a & b for color channels
var d clusters.Observations
for l := 0.2; l <= 0.8; l += 0.05 {
for a := -1.0; a <= 1.0; a += 0.1 {
for b := -1.0; b <= 1.0; b += 0.1 {
d = append(d, clusters.Coordinates{l, a, b})
}
}
}
// Partition the color space into 8 clusters
km := kmeans.New()
clusters, _ := km.Partition(d, 8)
for _, c := range clusters {
col := colorful.Lab(c.Center[0], c.Center[1], c.Center[2])
fmt.Println("Color as Hex:", col.Clamped().Hex())
}
}
Набор из восьми (не очень) случайных цветов, сгенерированный этим кодом:
Определяем собственное цветовое пространство
Добавим больше контроля над генерированием цветов. Мы легко можем управлять данными, используемыми для дальнейших вычислений, тем самым подбирая цветовое пространство под свои нужды. Сгенерируем пастельную палитру:
func pastel(c colorful.Color) bool {
_, s, v := col.Hsv()
return 0.2 <= s && s <= 0.4 && 0.7 <= v && v <= 1.0
}
for l := 0.0; l <= 1.0; l += 0.05 {
for a := -1.0; a <= 1.0; a += 0.1 {
for b := -1.0; b <= 1.0; b += 0.1 {
col := colorful.Lab(l, a, b)
if col.IsValid() && pastel(col) {
d = append(d, clusters.Coordinates{l, a, b})
}
}
}
}
Ещё одно цветовое пространство — HSV, буквы в названии означают оттенок (hue), насыщенность (saturation) и яркость (value). В этом пространстве пастельные цвета обычно имеют высокие значения яркости и низкие значения насыщенности.
Вот что получилось:
Также вы можете фильтровать цвета по их насыщенности (chroma) и яркости, чтобы получить набор «тёплых» тонов:
func warm(col colorful.Color) bool {
_, c, l := col.Hcl()
return 0.1 <= c && c <= 0.4 && 0.2 <= l && l <= 0.5
}
Результат:
Пакет gamut
Я работаю над библиотекой под названием gamut, в которой соберу все описанные здесь кусочки в один удобный пакет на Go, позволяющий генерировать и управлять цветовыми палитрами и темами. Вы уже можете его попробовать, но он пока в работе.