RGB какой-то отстой.

Модель RGB, мало чем отличающаяся от ASCII, адресов памяти и наличия 86 400 секунд в сутках, является одним из тех инструментов, которые немного упрощают программирование, до поры до времени.

Теоретически RGB — это группа цветовых пространств, которая позволяет указать дисплею, какое напряжение требуется каждому субпикселю. Однако на практике теперь у нас есть телефоны с дисплеями, которые позволяют отображать более 100% красного цвета, что является новым типом красного цвета, называемым суперкрасным. У нас есть другие дисплеи, в которых синего в два раза больше, чем красного или зелёного. И, вероятно, уже некоторое время ваши значения RGB не соответствуют напряжениям дисплея.

RGB сложно осмысливать рационально. Красный, зелёный и синий источники света ведут себя не так, как мы привыкли — вы можете видеть отдельные цвета вблизи, но по мере удаления они смешиваются вместе, и вы начинаете видеть только один цвет. С достаточно большого расстояния вы не сможете убедить свой разум в том, что существует три источника света. В настоящее время вы смотрите на миллионы крошечных массивов из 3 источников света, и всё же эффект настолько убедителен, что вы почти никогда не задумываетесь об этом.

Наконец, RGB трудно настраивать. Если вы начнёте с чёрного, вы можете увеличить количество “red” (красного) в палитре цветов RGB, что сделает всё более красным. Всё идет нормально. Затем вы начинаете увеличивать “green” (зелёный), и вы получаете… жёлтый? Это не очень интуитивно понятное цветовое пространство для навигации. Есть и другие представления цветов, которые легче поддаются изменению.

Цвета на годы

У меня есть личное приложение, где мне необходимо показать график за несколько лет. Каждому году на графике требуется другой цвет, поэтому каждый новый год я захожу в код, нахожу новый красивый цвет для нового года и развертываю приложение. Сколько лет я буду этим заниматься, пока не найду алгоритм, с помощью которого можно это автоматизировать?

Мне необходимы некоторые цвета, которые:

а) произвольны при генерации кода;

б) красиво выглядят;

в) определяются исключительно целым числом года.

Нам требуется реализовать такую функцию:

func color(for year: Int) -> Color

RGB действительно может удовлетворить только первому из моих критериев — эта модель может создавать случайные цвета со случайными числами:

Color(red: .random(in: 0..<1), blue: .random(in: 0..<1), green: .random(in: 0..<1))

К сожалению, цвета, сгенерированные таким образом, выглядят очень плохо. Они часто получаются грязными и румяными, а создание более чем одного цвета не сопровождается каким-либо узором или структурой. Цвета повсюду.

Это структурная проблема с RGB. RGB фокусируется на том, как создается цвет, а не на том, как он воспринимается.

К счастью, решение этой проблемы хорошо задокументировано. Есть несколько сообщений в блогах и постах (предупреждение: JavaScript), в которых излагается подход. Идея такова: используя цветовое пространство, основанное на оттенках, такое как HSL, вы можете сохранять два параметра постоянными saturation и lightness (насыщенность и яркость) и изменять только оттенок, давая вам несколько цветов, которые живут в одном и том же “family” (семействе).

(Существуют тонкие различия между HSL, HSB, HSV и HWB, но чередование оттенков в основном одинаково во всех цветовых моделях, и любая из них будет хорошо работать с этой техникой.)

Например, использование значения 0.8 для saturation и lightness дает хорошие пастельные тона:

Color(hue: .random(in: 0..<360), saturation: 0.8, lightness: 0.8)

Вы можете играть с этой палитрой цветов; переместите ползунок “hue” (оттенок), чтобы увидеть множество цветов в этом семействе.

С другой стороны, значения 0.6 для saturation и 0.5 для lightness дают более насыщенные цвета:

Color(hue: .random(in: 0..<360), saturation: 0.6, lightness: 0.5)

Эта палитра цветов показывает примеры этих цветов.

Проницательные читатели заметят, что в то время как собственные API-интерфейсы Apple принимают число от 0 до 1, этот фальшивый инициализатор, который я сделал, ожидает оттенок от 0 до 360. Я нахожу это более наглядным, потому что это значение представляет собой некоторое количество градусов. Здесь есть физическая аналогия с кругом оттенков. Круги зацикливаются сами на себе, поэтому 3590 в основном того же цвета, что и 10. Это позволяет вам выйти за пределы круга оттенков и изменить его на 3600, чтобы вернуться к разумному цвету.

Это позволяет нам реализовать большую часть нашей функции color(for year: Int):

func color(for year: Int) -> Color {
	let spacing = ...
	return Color(hue: (year * spacing) % 360, saturation: 0.8, lightness: 0.5)
}

Интервал представляет собой количество градусов, которое необходимо пройти по кругу оттенков каждый раз, когда нам необходимо выбрать следующий цвет.

Какое оптимальное число выбрать здесь?

Вращение в пространстве оттенков

Если мы сделаем этот угол слишком близким к нулю, цвета будут располагаться слишком близко друг к другу на круге оттенков, что сделает их слишком похожими. Однако, если мы сделаем это слишком близко к значению 3600 (полный оборот), после изменения градусов на 360 они всё равно будут слишком похожи, за исключением того, что они будут идти назад по кругу оттенков. Может быть, мы хотим попробовать 1800 ? Это делает все остальные цвета абсолютно одинаковыми, так что это тоже не совсем правильно.

На самом деле, любое вращение, которое равномерно делится на 3600, через некоторое время приведет к повторению. А у 360 много факторов!

Одно из решений состоит в том, чтобы разложить компоненты на 360 значений, разделенных на количество лет, которые у нас есть, и тогда цвета будут меняться каждый раз, когда наступает новый год. Это создает радугу, которая, хотя и выглядит красиво, не имеет случайного представления, к которому я стремлюсь.

Однако есть лучший способ сделать это, и ответ содержится в видео на YouTube, которое я смотрел более 10 лет назад. Замечательный Ви Харт (Vi Hart) опубликовал серию видеороликов (раздватри) о том, как растениям необходимо отрастить свои новые листья таким образом, чтобы они не были заблокированы верхними листьями, что позволяет им получать максимум солнечного света. Во втором видео из этой серии есть соответствующий бит.

Число градусов вокруг стебля, из которого растение решает вырастить свой следующий лист — это именно то число, которое мы ищем. Это некоторый поворот, на который мы получим неперекрывающиеся листья — я имею в виду цвета.

Поскольку любое рациональное число приведет к повторяющимся цветам или перекрывающимся листьям, она ищет иррациональное число. В идеале “самое” иррациональное число. Она находит его в ϕ, это примерно 1.618. Нам необходимо проходить 1/1.618 часть круга оттенков каждый раз, когда нам требуется новый цвет, и это даст нам необходимые цвета.

Если цвета вам не нравятся, вы можете дополнительно повернуть, добавив фазовый сдвиг в уравнение:

func color(for year: Int) -> Color {
	let spacing = 360/1.618
	return Color(hue: 300 + (year * spacing) % 360, saturation: 0.8, lightness: 0.5)
}

Эта функция соответствует нашим критериям, цвета, которые получаются из неё:

а) произвольны;

б) выглядят неплохо;

в) определяются исключительно годом.

Следующий шаг

Если ваша единственная цель — несколько простых цветов для прототипа или побочного проекта, то того, что я рассказал до сих пор, будет достаточно. Но если вы хотите использовать это в более серьёзных и масштабных приложениях, вы можете сделать ещё один шаг.

Цветовая модель HSL имеет несколько серьезных недостатков. Она, как и RGB, была разработана для простоты вычислений, а не для точности базовых цветов. В частности, при повороте значения оттенка (что мы и делаем с помощью этой техники) вы обнаружите, что некоторые оттенки окрашены намного светлее, чем другие, даже если saturation и lightness остаются постоянными. Эти цвета выглядят светлее, хотя технически они являются одной и той же “lightness”.

Цветовое пространство LCH (luminance, chroma, hue) решает эту проблему. Насколько я могу судить, это золотой стандарт цветов на дисплее. Это даёт вам единообразие восприятия, что позволяет вам поворачивать оттенок и получать цвета, которые даже больше похожи друг на друга, чем вы могли бы получить с помощью HSL. Это также даёт некоторые преимущества, когда дело доходит до контраста при чтении текста.

На самом деле, если вы внимательно посмотрите на приведённые выше цвета (которые представляют собой цвета для 2015–2023 годов с использованием нашего алгоритма), этот зелёный лайм выглядит немного приглушенным по сравнению с его фиолетовым соседом.

Здесь вы можете поиграть с палитрой цветов LCH. Чтобы заставить LCH работать с UIColor, вы можете использовать эти четыре полезных принципа.

Использование LCH для создания моих цветов с помощью описанной выше техники вращения оттенков дало красивые цвета.

func color(for year: Int) -> Color {
	let spacing = 360/1.618
	return Color(luminance: 0.7, chroma: 120, hue: 300 + (year * spacing) % 360)
}

Этот браузер не поддерживает цвет LCH. Попробуйте Safari или мобильную версию Safari 15 или выше.

Все эти цвета имеют одинаковую lightness, и они отлично смотрятся для чего-то полностью процедурно сгенерированного. Они яркие, однородные и замечательные.

Модель, которую вы выбираете для жизни, создает ограничения, которые вы, возможно, не предполагали ограничивать. Любой цвет из любого из этих цветовых пространств может быть (более или менее) переведён в любое другое цветовое пространство с небольшой разницей. Поэтому цвета, которые мы получили в итоге, могут быть записаны в терминах красных, зеленых и синих значений (опять же, здесь махнув немного рукой). Но хотя RGB может представлять эти цвета, это не означает, что вы можете легко перемещаться по пространству таким образом, чтобы получить цвета, которые хорошо смотрятся вместе. Выбор правильного цветового пространства для начала делает проблему, по крайней мере, решаемой.

Поправимо, но до сих пор не решено. Эти произвольные красивые цвета могут быть сгенерированы с помощью процесса, стохастически открытого эволюцией, открытого учеными в 1830 году, и применённого на практике с использованием надёжного набора веб-стандартов, которые позволяют мне показать их вам в браузере.

В конце концов, стремление растений к солнечному свету стало ключом к созданию приятных цветов для моей маленькой диаграммы.

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


  1. LordDarklight
    00.00.0000 00:00
    +6

    Этот браузер не поддерживает цвет LCH. Попробуйте Safari или мобильную версию Safari 15 или выше.

    Но картинки то он поддерживает - можно было бы и картинку вставить


  1. ksbes
    00.00.0000 00:00
    +4

    Э-э-э
    Этот браузер не поддерживает цвет LCH. Попробуйте Safari или мобильную версию Safari 15 или выше.
    можно как-то в png артинку вставить?

    Ну и собственно это — критика LCH формата. RGB и HSL-то все поддерживают!


    1. LordDarklight
      00.00.0000 00:00
      +1

      Честно, не понимаю. Это всего лишь разные алгоритмы, но об одном и том же. Можно же самому написать алгоритм, который будет работать в LCH на входе и возвращать цвет в RGB. Да - согласен, тут уже проблема скорее мониторов - какие-то могут выводить более глубокий диапазон и HDR, какие-то менее - это тоже вопрос алгоритма - определить возможности вывода и вывести в максимально допустимом формате! Так, например делают в графических шейдерах. Да.... и для браузера тоже можно было бы задействовать вывод сэмплов через WEBGL - но это уже, конечно перебор, достаточно (и необходимо для сайтов) просто сделать алгоритм гибким и подстраиваемым под возможности системы вывода, оставляя саму модель в формате нужного поля точности вычислений


      1. mayorovp
        00.00.0000 00:00

        Можно же самому написать алгоритм, который будет работать в LCH на входе и возвращать цвет в RGB.

        Нельзя, этому помешают ограничения диапазона (больше 255 координату в RGB нельзя) и дискретизации (нельзя сделать один из цветов 0.5).


        1. LordDarklight
          00.00.0000 00:00

          я же написал - работать модели любого цветового пространства (условно входные аргументы), а выдавать цвет в поддерживаемой модели (условно в RGB)/

          Если это нельзя - прошу пояснить почему


          1. mayorovp
            00.00.0000 00:00

            А что толку работать в LCH, если вы просто не сможете выдать на выходе в RGB нужный цвет?


            1. nin-jin
              00.00.0000 00:00
              -2

              rgb( 320 0.5 128 )


              1. mayorovp
                00.00.0000 00:00
                +1

                Увы, это значение полностью эквивалентно rgb(255 0 128)


                1. nin-jin
                  00.00.0000 00:00
                  -3

                  Пишите багрепорт разработчику вашего браузера.


            1. Fodin
              00.00.0000 00:00

              Этот вопрос похож на: "А что толку работать с фото в RGB, если на выходе в CMYK мы не получим нужный цвет?".


        1. Fodin
          00.00.0000 00:00

          Вроде, не нужна такая точность. Например, в Lab яркость от 0 до 100%. И шаг в 1% - это едва различимое глазом отличие.


  1. fire64
    00.00.0000 00:00
    +1

    Прошу объяснить, мне делитанту.

    Возможно ли используя HSL, LCH задать цвет за пределами RGB палитры?

    Поддерживают ли мониторы возможности отображения цветов за пределами RGB палитры? Несколько я понимаю, тот же HDR в играх, это по факту своего рода аппроксимация до RGB.


    1. mayorovp
      00.00.0000 00:00
      +1

      Для HSL — нет, для LCH — да.
      Мониторы — поддерживают, потому LCH и обсуждают сейчас.


      HDR тут не совсем при чём, HDR просто увеличивает число бит в представлении цвета, но оно не меняет цветовое пространство. Ну, если ещё и снять ограничение сверху по яркости пикселя — это, конечно, поможет передать расширенные цвета на монитор, однако подобное решение всё равно половинчатое. HDR никак не решает чисто практическую задачу вроде "задать суперкрасный цвет в CSS".


      1. lorc
        00.00.0000 00:00

        А можно пример монитора который поддерживает LCH? Я вообще не представляю как это может работать. Это же надо чтобы у вас весь стек поддерживал LCH: графический композитор, графическая подсистема, видеокарта, драйвера видеокарты, физический интерфейс между видеокартой и монитором, монитор. Я для интереса открыл список цветовых форматов и цветовых пространств, которые поддерживаются HDMI. Даже в HDMI 2.1 нет LCH. Вы ни с чем не путаете?


        1. mayorovp
          00.00.0000 00:00

          На уровне монитора это, скорее всего, всё те же RGB, только с большими ограничениями.


    1. Fodin
      00.00.0000 00:00
      +2

      RGB-монитор по определению не может показывать что-то за пределами RGB.

      RGB, как модель, может охватывать любые диапазоны яркостей, вплоть до выжигающих сетчатку, вопрос только в том, на что ее натянуть. Без указания цветового пространства числа в координатах RGB - только числа, не имеющие никакого смысла. Просто по-умолчанию негласно (кое-где и гласно) принято считать, что это пространство sRGB.


  1. Iskin
    00.00.0000 00:00
    +1

    LCH к сожалению именно проблему в синей части спектра и не является сейчас самым хорошим решением.

    OKLCH лучше, так как решает эту проблему и удобнее для изменили охвата.

    https://web-standards.ru/articles/oklch-in-css-why-quit-rgb-hsl/