![](https://habrastorage.org/files/bc8/06e/680/bc806e6800ab42a0b55e8f114f325002.jpg)
В процессе разработки игры в текстовом режиме, мне пришлось нарисовать больше сотни анимационных ASCII спрайтов. После релиза игра получила неожиданно хорошие отзывы и было принято решение делать продолжение. Рисуя спрайты для первой части и перепробовав с десяток вариантов выбора цвета и несколько десятков различных палитр, я понял, что нужен свой, «идеальный» набор цветов на все времена. За сотни и сотни часов рисования, сложились следующие критерии идеальности палитры:
- Краткость: небольшое количество цветов в палитре. Весь набор цветов можно охватить одним взглядом.
- Полнота: цвета палитры должны равномерно и достаточно плотно заполнять цветовое пространство.
- Дискретность: цвета палитры должны отличаться друг от друга на глаз.
- Группировка: цвета должны быть удобно сгруппированы для быстрого нахождения нужного.
Оказалось, что можно подобрать набор из ровно 233 цветов, который удовлетворит всем этим критериям.
Начиная отбирать цвета, взглянем на все цветовое пространство. Его можно представить в виде RGB куба, HSB цилиндра/конуса — и это только самые популярные аддитивные модели.
![](https://habrastorage.org/files/9c5/50b/2bf/9c550b2bf4aa41d9b84054e6b731c94c.jpg)
Выбирать цвет из «внутренностей» этих трехмерных фигур не удобно. Поэтому графические редакторы придумывают различные удобные и красивые способы выбрать произвольный цвет. Но обратите внимание, что в каждой цветовой модели все равно как минимум три измерения.
![](https://habrastorage.org/files/2a5/e57/980/2a5e579807a045899ac22f48a57708c7.jpg)
Для ASCII графики весь этот «континуум» цветов не нужен. Выбор произвольного цвета только затрудняет редактирование. Дело в том, что каждый символ занимает достаточно большую площадь на экране и символы как правило не соприкасаются. Чтобы заметить разницу, цвета символов должны отличаться на глаз.
![](https://habrastorage.org/files/c97/2eb/06c/c972eb06c3a642cc819f2046c10dd62e.gif)
Итак, нужна дискретная палитра. Различных палитр — великое множество. Это как современные палитры, так и ретро палитры, пришедшие из прошлого компьютерной техники.
![](https://habrastorage.org/files/58c/878/a41/58c878a41f8b4a5da0921ad425028094.jpg)
Не смотря на все многообразие, я не нашел палитру, которая бы удовлетворяла моим требованиям. Это и не удивительно. Ретро палитры определялись возможностями железа. Современные же палитры составляются, как правило, для целей реализации определенного стиля в дизайне.
Большинство графических редакторов позволяют формировать пользовательские палитры. Но встроенных возможностей группировки цветов палитры оказалось не достаточно для моих целей. Итак, я принял решение собрать палитру сам.
Процедурная палитра?
Что, если равномерно распределить точки-цвета палитры внутри цветового пространства? Например, каждую компоненту цвета в модели RGB разделить на 6 частей. Получится 216 цветов.
Проблема в том, что человек воспринимает цвета не линейно. Зеленые оттенки палитры «сольются», а синие будут слишком дискретны. Вот интересный материал, посвященный цветам:
Poynton's Color FAQ
Только ручной отбор!
Разработанная в 70-ых годах модель HSB, являющаяся нелинейным преобразованием модели RGB, позволяет намного более интуитивно выбирать цвета. Первым компонентом модели является Hue – Цветовой тон. Какие бывают цветовые тона? Например, как в мнемонической фразе: «Каждый охотник желает знать, где сидит фазан».
7 цветовых тонов: Красный, Оранжевый, Желтый, Зеленый, Голубой, Синий, Фиолетовый
Этого явно мало! А где, например, розовый или бирюзовый? Более полный набор из 12-ти тонов дает палитра «циферблата», где каждому часу на циферблате соответствует свой цвет.
12 цветовых тонов: Красный, Оранжевый, Желтый, Лайм, Зеленый, Аквамарин, Бирюзовый, Голубой, Синий, Фиолетовый, Розовый, Малиновый
Полученные значения Hue все еще слишком дискретны. Я добавил несколько тонов, чтобы разница на глаз между соседними тонами была одинакова. Теперь результат меня устроил.
15 цветовых тонов: Красный, Оранжевый, Золотой, Желтый, Лайм, Зеленый, Аквамарин, Бирюзовый, Голубой, Синий, Ультрамарин, Фиолетовый, Розовый, Малиновый, Алый
![](https://habrastorage.org/files/090/336/568/09033656811f420a9b0d619ae3c44943.jpg)
Для каждого из 15-ти цветовых тонов можно задавать компоненты Saturation – Насыщенность и Brightness – Яркость. Вот, например, множества значений насыщенности и яркости для оранжевого, фиолетового и лайма.
![](https://habrastorage.org/files/e3c/984/7b9/e3c9847b96f54329b59a5e6528ccef1c.jpg)
Существует известная палитра, в которой компоненты насыщенности и яркости вытянуты в одну линию.
![](https://habrastorage.org/files/1b4/0e1/174/1b40e1174a2c4e1eb5825e440a765c2b.jpg)
Может показаться, что в этой палитре представлено вообще все цветовое пространство. Вот она — идеальная палитра с хорошо сгруппированными цветами. Но нет, здесь полностью отсутствуют цвета с не единичной яркостью и насыщенностью. Например, цвет сирени, выделенный на картинке выше — не возможно уменьшить его яркость в рамках данной палитры.
Интересно, что цвета этой палитры образуют поверхность цилиндра/конуса модели HSB. Цвета из внутренних частей цилиндра/конуса — отсутствуют.
Вернемся к нашим 15-ти цветовым тонам. Построим для каждого дискретную раскладку по компонентам яркости и насыщенности. Значения компонент выберем равномерно следующим образом: 100%, 80%, 60%, 40%, 20%.
![](https://habrastorage.org/files/1f2/1a2/c8f/1f21a2c8fc914b50a387d31875568795.jpg)
Часто, пространство значений яркости и насыщенности представляют не в виде квадрата, а в виде треугольника. Дело в том, что глаз человека хуже различает изменение насыщенности при более низкой яркости.
В нашем случае, треугольник получается так: для ряда с более низкой яркостью предоставляется меньше вариантов насыщенности.
![](https://habrastorage.org/files/341/e13/088/341e13088d4e48c585aa6d11eebf08a3.jpg)
Неплохо! Теперь приступим к более тонкой настройке. Для каждого тона, компоненты яркости и насыщенности были распределены равномерно. Но глаз человека воспринимает яркость нелинейно. Более того, для разных цветовых тонов восприятие яркости — разное.
Одной, наиболее часто применяемой формулой для оценки яркости цвета, является следующая:
![](https://habrastorage.org/files/ab0/2dc/4a1/ab02dc4a1aa64b449f23cec7a80d2609.jpg)
Надо понимать, что яркость цвета зависит как от типа монитора, так и от индивидуальных особенностей зрения человека, а эта формула была выведена еще для CRT мониторов. Тем не менее, она отражает тот факт, что вклад в общую яркость синего компонента меньше чем вклад красного, который, в свою очередь, меньше вклада зеленого. Это видно невооруженным взглядом, достаточно, например, сравнить синий цвет с яркостью 20% и зеленый с яркостью 20%. Синий гораздо темнее!
Для того, чтобы выровнять темные цвета и сделать визуальное уменьшение яркости более линейным, я применил следующее преобразование:
![](https://habrastorage.org/files/1f0/0ab/9ff/1f00ab9ff6a948c78f567897f0149aa0.jpg)
![](https://habrastorage.org/files/4d0/140/f72/4d0140f7237e4213b905364139cfa648.jpg)
Формула выглядит слегка громоздко, но на самом деле она простая. На вход подается уровень яркости Y, цветовой тон (r, g, b), а так же количество градаций яркости n. Внутри больших скобок происходит выравнивание темных цветов в зависимости от тона. Возведение в степень 0.7 делает изменение яркости более равномерным визуально. Степени 0.15 и 0.7 я подбирал вручную, пытаясь добиться лучшего результата.
Для выравнивания насыщенности мне было достаточно следующего простого преобразования:
![](https://habrastorage.org/files/a9c/905/551/a9c90555177d4726b19f9ad2c672fb15.jpg)
Степень 0.65 так же подбиралась вручную. На представленной ниже GIF анимации приводится вариант до преобразования и после. Фон я сделал черным, чтобы лучше были видны темные цвета. Судите сами, стали ли цвета по яркости и насыщенности более равномерно распределены. На мой взгляд — да, стало лучше.
![](https://habrastorage.org/files/839/d7e/626/839d7e62684049dbb05a76f1c99af3cd.gif)
Осталось добавить оттенки серого и сгруппировать полученные цвета. В итоге получилась такая палитра.
![](https://habrastorage.org/files/a4c/69f/494/a4c69f494c914fc08f370724682d7653.jpg)
Я получил 233 орешка для золушки — 233 цвета для редактора ASCII графики, которая — прекрасна!
RGB представление цветов палитры в виде JSON
{
"Алый": [
[
{"r": 255, "g": 0, "b": 85},
{"r": 179, "g": 0, "b": 59},
{"r": 131, "g": 0, "b": 43},
{"r": 91, "g": 0, "b": 30},
{"r": 54, "g": 0, "b": 18}
],
[
{"r": 255, "g": 79, "b": 138},
{"r": 179, "g": 55, "b": 96},
{"r": 131, "g": 41, "b": 71},
{"r": 91, "g": 28, "b": 49}
],
[
{"r": 255, "g": 124, "b": 168},
{"r": 179, "g": 87, "b": 118},
{"r": 131, "g": 64, "b": 86}
],
[
{"r": 255, "g": 162, "b": 193},
{"r": 179, "g": 114, "b": 135}
],
[
{"r": 255, "g": 195, "b": 215}
]
],
"Малиновый": [
[
{"r": 255, "g": 0, "b": 162},
{"r": 178, "g": 0, "b": 113},
{"r": 130, "g": 0, "b": 82},
{"r": 89, "g": 0, "b": 56},
{"r": 52, "g": 0, "b": 33}
],
[
{"r": 255, "g": 79, "b": 191},
{"r": 178, "g": 55, "b": 133},
{"r": 130, "g": 40, "b": 97},
{"r": 89, "g": 27, "b": 67}
],
[
{"r": 255, "g": 124, "b": 207},
{"r": 178, "g": 87, "b": 145},
{"r": 130, "g": 63, "b": 106}
],
[
{"r": 255, "g": 162, "b": 221},
{"r": 178, "g": 113, "b": 154}
],
[
{"r": 255, "g": 195, "b": 233}
]
],
"Розовый": [
[
{"r": 255, "g": 0, "b": 255},
{"r": 177, "g": 0, "b": 177},
{"r": 129, "g": 0, "b": 129},
{"r": 87, "g": 0, "b": 87},
{"r": 50, "g": 0, "b": 50}
],
[
{"r": 255, "g": 79, "b": 255},
{"r": 177, "g": 55, "b": 177},
{"r": 129, "g": 40, "b": 129},
{"r": 87, "g": 27, "b": 87}
],
[
{"r": 255, "g": 124, "b": 255},
{"r": 177, "g": 86, "b": 177},
{"r": 129, "g": 63, "b": 129}
],
[
{"r": 255, "g": 162, "b": 255},
{"r": 177, "g": 113, "b": 177}
],
[
{"r": 255, "g": 195, "b": 255}
]
],
"Фиолетовый": [
[
{"r": 170, "g": 0, "b": 255},
{"r": 119, "g": 0, "b": 179},
{"r": 88, "g": 0, "b": 132},
{"r": 61, "g": 0, "b": 92},
{"r": 37, "g": 0, "b": 56}
],
[
{"r": 196, "g": 79, "b": 255},
{"r": 138, "g": 56, "b": 179},
{"r": 102, "g": 41, "b": 132},
{"r": 71, "g": 28, "b": 92}
],
[
{"r": 211, "g": 124, "b": 255},
{"r": 149, "g": 88, "b": 179},
{"r": 110, "g": 65, "b": 132}
],
[
{"r": 224, "g": 162, "b": 255},
{"r": 158, "g": 114, "b": 179}
],
[
{"r": 235, "g": 195, "b": 255}
]
],
"Ультрамарин": [
[
{"r": 98, "g": 0, "b": 255},
{"r": 70, "g": 0, "b": 182},
{"r": 52, "g": 0, "b": 136},
{"r": 37, "g": 0, "b": 98},
{"r": 24, "g": 0, "b": 63}
],
[
{"r": 146, "g": 79, "b": 255},
{"r": 105, "g": 56, "b": 182},
{"r": 78, "g": 42, "b": 136},
{"r": 56, "g": 30, "b": 98}
],
[
{"r": 174, "g": 124, "b": 255},
{"r": 124, "g": 89, "b": 182},
{"r": 93, "g": 67, "b": 136}
],
[
{"r": 198, "g": 162, "b": 255},
{"r": 141, "g": 116, "b": 182}
],
[
{"r": 218, "g": 195, "b": 255}
]
],
"Синий": [
[
{"r": 0, "g": 0, "b": 255},
{"r": 0, "g": 0, "b": 187},
{"r": 0, "g": 0, "b": 145},
{"r": 0, "g": 0, "b": 109},
{"r": 0, "g": 0, "b": 76}
],
[
{"r": 79, "g": 79, "b": 255},
{"r": 58, "g": 58, "b": 187},
{"r": 45, "g": 45, "b": 145},
{"r": 34, "g": 34, "b": 109}
],
[
{"r": 124, "g": 124, "b": 255},
{"r": 91, "g": 91, "b": 187},
{"r": 71, "g": 71, "b": 145}
],
[
{"r": 162, "g": 162, "b": 255},
{"r": 119, "g": 119, "b": 187}
],
[
{"r": 195, "g": 195, "b": 255}
]
],
"Голубой": [
[
{"r": 0, "g": 145, "b": 255},
{"r": 0, "g": 100, "b": 176},
{"r": 0, "g": 72, "b": 128},
{"r": 0, "g": 49, "b": 86},
{"r": 0, "g": 27, "b": 48}
],
[
{"r": 79, "g": 179, "b": 255},
{"r": 55, "g": 124, "b": 176},
{"r": 39, "g": 90, "b": 128},
{"r": 26, "g": 60, "b": 86}
],
[
{"r": 124, "g": 198, "b": 255},
{"r": 86, "g": 137, "b": 176},
{"r": 62, "g": 99, "b": 128}
],
[
{"r": 162, "g": 215, "b": 255},
{"r": 112, "g": 149, "b": 176}
],
[
{"r": 195, "g": 229, "b": 255}
]
],
"Бирюзовый": [
[
{"r": 0, "g": 255, "b": 255},
{"r": 79, "g": 255, "b": 255},
{"r": 124, "g": 255, "b": 255},
{"r": 162, "g": 255, "b": 255},
{"r": 195, "g": 255, "b": 255}
],
[
{"r": 0, "g": 173, "b": 173},
{"r": 62, "g": 173, "b": 173},
{"r": 97, "g": 173, "b": 173},
{"r": 127, "g": 173, "b": 173}
],
[
{"r": 0, "g": 121, "b": 121},
{"r": 53, "g": 121, "b": 121},
{"r": 83, "g": 121, "b": 121}
],
[
{"r": 0, "g": 78, "b": 78},
{"r": 44, "g": 78, "b": 78}
],
[
{"r": 0, "g": 38, "b": 38}
]
],
"Аквамарин": [
[
{"r": 0, "g": 255, "b": 170},
{"r": 79, "g": 255, "b": 196},
{"r": 124, "g": 255, "b": 211},
{"r": 162, "g": 255, "b": 224},
{"r": 195, "g": 255, "b": 235}
],
[
{"r": 0, "g": 173, "b": 115},
{"r": 62, "g": 173, "b": 136},
{"r": 98, "g": 173, "b": 148},
{"r": 127, "g": 173, "b": 158}
],
[
{"r": 0, "g": 122, "b": 81},
{"r": 53, "g": 122, "b": 99},
{"r": 83, "g": 122, "b": 109}
],
[
{"r": 0, "g": 79, "b": 52},
{"r": 44, "g": 79, "b": 67}
],
[
{"r": 0, "g": 40, "b": 26}
]
],
"Зеленый": [
[
{"r": 0, "g": 255, "b": 0},
{"r": 79, "g": 255, "b": 79},
{"r": 124, "g": 255, "b": 124},
{"r": 162, "g": 255, "b": 162},
{"r": 195, "g": 255, "b": 195}
],
[
{"r": 0, "g": 174, "b": 0},
{"r": 62, "g": 174, "b": 62},
{"r": 98, "g": 174, "b": 98},
{"r": 128, "g": 174, "b": 128}
],
[
{"r": 0, "g": 124, "b": 0},
{"r": 54, "g": 124, "b": 54},
{"r": 84, "g": 124, "b": 84}
],
[
{"r": 0, "g": 81, "b": 0},
{"r": 46, "g": 81, "b": 46}
],
[
{"r": 0, "g": 42, "b": 0}
]
],
"Лайм": [
[
{"r": 196, "g": 255, "b": 0},
{"r": 214, "g": 255, "b": 79},
{"r": 224, "g": 255, "b": 124},
{"r": 233, "g": 255, "b": 162},
{"r": 241, "g": 255, "b": 195}
],
[
{"r": 131, "g": 171, "b": 0},
{"r": 146, "g": 171, "b": 61},
{"r": 154, "g": 171, "b": 97},
{"r": 161, "g": 171, "b": 126}
],
[
{"r": 91, "g": 119, "b": 0},
{"r": 104, "g": 119, "b": 52},
{"r": 110, "g": 119, "b": 81}
],
[
{"r": 57, "g": 75, "b": 0},
{"r": 67, "g": 75, "b": 42}
],
[
{"r": 27, "g": 35, "b": 0}
]
],
"Желтый": [
[
{"r": 255, "g": 255, "b": 0},
{"r": 255, "g": 255, "b": 79},
{"r": 255, "g": 255, "b": 124},
{"r": 255, "g": 255, "b": 162},
{"r": 255, "g": 255, "b": 195}
],
[
{"r": 170, "g": 170, "b": 0},
{"r": 170, "g": 170, "b": 61},
{"r": 170, "g": 170, "b": 96},
{"r": 170, "g": 170, "b": 125}
],
[
{"r": 118, "g": 118, "b": 0},
{"r": 118, "g": 118, "b": 51},
{"r": 118, "g": 118, "b": 80}
],
[
{"r": 73, "g": 73, "b": 0},
{"r": 73, "g": 73, "b": 41}
],
[
{"r": 33, "g": 33, "b": 0}
]
],
"Золотой": [
[
{"r": 255, "g": 170, "b": 0},
{"r": 255, "g": 196, "b": 79},
{"r": 255, "g": 211, "b": 124},
{"r": 255, "g": 224, "b": 162},
{"r": 255, "g": 235, "b": 195}
],
[
{"r": 173, "g": 115, "b": 0},
{"r": 173, "g": 136, "b": 62},
{"r": 173, "g": 148, "b": 98},
{"r": 173, "g": 157, "b": 127}
],
[
{"r": 122, "g": 81, "b": 0},
{"r": 122, "g": 99, "b": 53},
{"r": 122, "g": 109, "b": 83}
],
[
{"r": 78, "g": 52, "b": 0},
{"r": 78, "g": 67, "b": 44}
],
[
{"r": 39, "g": 26, "b": 0}
]
],
"Оранжевый": [
[
{"r": 255, "g": 94, "b": 0},
{"r": 255, "g": 144, "b": 79},
{"r": 255, "g": 172, "b": 124},
{"r": 255, "g": 196, "b": 162},
{"r": 255, "g": 217, "b": 195}
],
[
{"r": 175, "g": 64, "b": 0},
{"r": 175, "g": 104, "b": 63},
{"r": 175, "g": 127, "b": 99},
{"r": 175, "g": 146, "b": 129}
],
[
{"r": 126, "g": 46, "b": 0},
{"r": 126, "g": 81, "b": 54},
{"r": 126, "g": 100, "b": 86}
],
[
{"r": 83, "g": 30, "b": 0},
{"r": 83, "g": 60, "b": 47}
],
[
{"r": 45, "g": 16, "b": 0}
]
],
"Красный": [
[
{"r": 255, "g": 0, "b": 0},
{"r": 255, "g": 79, "b": 79},
{"r": 255, "g": 124, "b": 124},
{"r": 255, "g": 162, "b": 162},
{"r": 255, "g": 195, "b": 195}
],
[
{"r": 180, "g": 0, "b": 0},
{"r": 180, "g": 64, "b": 64},
{"r": 180, "g": 101, "b": 101},
{"r": 180, "g": 132, "b": 132}
],
[
{"r": 133, "g": 0, "b": 0},
{"r": 133, "g": 57, "b": 57},
{"r": 133, "g": 90, "b": 90}
],
[
{"r": 93, "g": 0, "b": 0},
{"r": 93, "g": 52, "b": 52}
],
[
{"r": 57, "g": 0, "b": 0}
]
]
}
Буду рад видеть вас в своей группе textpuzzlelab на Фейсбуке, где можно посмотреть как я применяю цвета этой палитры в изображениях в стиле ASCII.
![](https://habrastorage.org/files/4f9/c56/12c/4f9c5612c1e34d959f28f2a575f24f4d.gif)
Работа над палитрой еще не закончена. Текущая версия 0.8. Одной из проблем является то, что при изменении насыщенности, критерий дискретности не совсем выполняется для желтого, лайма, зеленого, аквамарина и бирюзового. Пока не придумал как быть.
С радостью приму замечания и предложения. Одна пара глаз хорошо, а две и больше — лучше!