Pull to refresh
VK
Building the Internet

Алгоритм генерирования цветовых палитр

Reading time 4 min
Views 12K
Original author: Christian Muehlhaeuser


Ищете красивую цветовую палитру для сайта? Недавно установили дома 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, позволяющий генерировать и управлять цветовыми палитрами и темами. Вы уже можете его попробовать, но он пока в работе.
Tags:
Hubs:
+19
Comments 11
Comments Comments 11

Articles

Information

Website
vk.com
Registered
Founded
Employees
5,001–10,000 employees
Location
Россия
Representative
Миша Берггрен