Ищете красивую цветовую палитру для сайта? Недавно установили дома 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,2) и слишком яркие (яркость >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, позволяющий генерировать и управлять цветовыми палитрами и темами. Вы уже можете его попробовать, но он пока в работе.

Комментарии (10)


  1. staticlab
    19.06.2018 12:39

    Хорошо, вы получили своим методом палитру из каких-то 8 цветов. Насколько они сочетаются друг с другом?


  1. NickyX3
    19.06.2018 12:42

    Палитры проще генерить от какого-то базового цвета, вычислив его светлоту в HSL, и равномерно распределив на HSL круге по H?


    1. NickyX3
      19.06.2018 13:01

      image
      Вот как-то так


      1. inoyakaigor
        19.06.2018 13:07

        В этом методе надо всё-таки делать поправку на воспринимаемую яркость как в HCL


        1. NickyX3
          19.06.2018 13:35

          Этот метод я использую не для цветовых тем, а для генерации палитр уникальных цветов для графиков данных на одном поле, чтоб не было одинаковых, но при этом все цвета имели одинаковую светлоту (чтоб не было ярких светлых и темных в одном графике)


  1. Zverik
    19.06.2018 12:53

    Обязательный комментарий про ColorBrewer и более наглядный Adobe Color CC.


    1. NickyX3
      19.06.2018 13:01

      В статье речь о программной генерации палитр


      1. Zverik
        19.06.2018 13:48

        Я знаю, прочитал её. Немного разочаровало отсутствие рандомизации: что если мне не нравится набор цветов, который выдал скрипт? Мои ссылки для тех, кому лениво писать программу или кому не нравится этот набор. Они отвечают на неявный вопрос из картинки для привлечения внимания.


        1. NickyX3
          19.06.2018 13:50
          +1

          В моем случае начальный цвет задается, из него вычисляется светлота палитры.
          Лень на гитхаб сливать, да и там PHP :-)


        1. NickyX3
          19.06.2018 14:55
          +1

          На PHP, статикой, для экспериментов
          github.com/NickyX3/ColorTools