CSS-live.ru

Когда 255 × 0 не равно нулю

Перевод статьи When 255 × 0 does not Equal Zero с сайта danielcwilson.com для css-live.ru, автор — Дэниел Уилсон

Прежде всего, важная оговорка: я не эксперт в работе с цветами и в цветовых профилях мониторов. И я понимаю лишь самые очевидные различия между дефолтным цветовым пространством для веба — sRGB — и другими более новыми моделями. Мы обсудим оба случая, я объясню то, что знаю сам, и оставлю другим возможность (и ссылки) подключиться к разговору и объяснить лучше.

Ладно… с этим разобрались, теперь к делу (и, пожалуй… простите, что заранее проспойлерил, о чем будет статья): поговорим о математике режимов наложения!

Как режимы наложения работают в вебе сегодня

Если вы пользовались Photoshop-ом (а тем, кто тоже вспомнил Сorel-Photo-Paint-из-90-х, от меня особый привет) либо имели дело с mix-blend-mode или background-blend-mode в CSS, режимы наложения могут вам быть в какой-то мере знакомы. Когда два слоя или элемента накладываются друг на друга при указанном режиме наложения, для каждого пикселя происходит вычисление с RGB-цветами обоих элементов на входе, результатом которого становится новый цвет для отображения. Есть много разных режимов вроде multiply или hard-light, по разным формулам рассчитывающих отображаемые цвета в местах наложения элементов.

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

Веб использует sRGB в качестве цветового пространства по умолчанию. Это происходит при интерполяции цветов в анимациях и градиентах, и благодаря этому математика очень простая. Даже если указать значение в HSL (как оттенок, насыщенность и яркость), итоговый цвет будет рассчитываться как RGB-цвет (красный, зеленый и синий компоненты). В модулях CSS Color 4-го и 5-го уровней будет больше вариантов, и на гитхабе Рабочей группы идет множество обсуждений, как в будущем можно будет интерполировать значения по разным моделям.

Но на сегодня математика режимов наложения применяется один раз для красного канала, один раз для зеленого, и один раз для синего.

Мы рассмотрим эту математику на примере multiply, а затем поговорим о некоторых ключевых моментах, где с математикой что-то не складывается.

Режим наложения multiply («умножение»)

Возьмем красный цвет (red), определенный как #ff0000. Каждый канал — это число от 0 до 255 (включительно). У этого цвета 255 в красном канале и по 0 в зеленом и синем.

Значение #ff0000 эквивалентно функции rgb(255,0,0). В этой функции можно использовать и проценты, так что до дальше в статье мы будем говорить о значениях от 0% до 100%, а не от 0 до 255. В такой записи red — это rgb(100%,0%,0%).

Допустим, мы хотим смешать его с другим цветом, возьмем желтый в виде rgb(100%, 100%, 0%).

Для наложения в режиме multiply мы берем значения из каждого элемента по каждому каналу и перемножаем их. Мы делаем это на шкале от 0 до 1 (так что 100% — это 1, 50% — это .5, и т.д.)

  • Красный в RGB: rgb(100%, 0%, 0%)
  • Желтый в RGB: rgb(100%, 100%, 0%)
  • Умножение в R-канале: 1 × 1 = 1
  • Умножение в G-канале: 0 × 1 = 0
  • Умножение в B-канале: 0 × 0 = 0

Таким образом наш результат, переведенный обратно в проценты — это rgb(100%, 0%, 0%)… или же наше исходное значение красного цвета!

Наложение красного и желтого цветов
Перемножение красного и желтого дает красный. Посмотреть пример на CodePen

Что, если вместо этого мы возьмем голубой (rgb(0%,100%,100%))?

  • Красный в RGB: rgb(100%, 0%, 0%)
  • Голубой в RGB: rgb(0%, 100%, 100%)
  • Умножение в R-канале: 1 × 0 = 0
  • Умножение в G-канале: 0 × 1 = 0
  • Умножение в B-канале: 0 × 1 = 0

Переведя обратно в проценты, получим rgb(0%, 0%, 0%): черный цвет

Наложение красного и голубого цветов
Перемножение красного и голубого дает черный. Посмотреть пример на CodePen

Поскольку мы перемножаем значения, которые меньше либо равны единице, результат наложения в режиме multiply никогда не даст цвета светлее, чем исходные. Каждый канал либо останется без изменений от одного из исходных цветов, либо станет темнее (приближаясь к нулю).

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

До тех пор, пока не окажется иначе.

Когда расчет происходит за пределами цветового пространства

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

Как отмечено выше, когда вы указываете в вебе цвет типа rgb(100% 0% 0%), вы имеете дело с цветовым пространством sRGB, и это основная спецификация цветов, которую веб знает. Есть другие, более новые способы указывать цвет, скажем, функция color(), они уже начинают понемногу поддерживаться, но в простейшем случае, если вам нужен яркий красный цвет, вы напишете rgb(100% 0% 0%). Когда вам, например, нужен градиент от этого красного цвета до синего (rgb(0% 0% 100%)), браузер определяет промежуточные значения в этом же пространстве sRGB и рассчитывает их, постепенно уменьшая красный канал от 100 до 0, одновременно увеличивая синий канал от 0 до 100.

Но разные экраны и мониторы используют разные цветовые пространства, когда отображают цвет для пользователя. Например, на Маках можно открыть настройку System Preferences > Displays > Color и посмотреть, какой экранный профиль использует ваш компьютер. Есть вероятность, что на iMac он использует нечто под названием Color LCD, и в конечном итоге это значит, что когда вы указываете rgb(100% 0% 0%) в коде страницы в браузере, который использует схему управления цветом текущего устройства (скажем, Safari или Edge/Chrome), браузер формально использует другие значения R, G и B, чтобы отобразить подобный красный цвет.

На iMac, за которым я недавно работал, цвет на экране (с помощью инструмента Digital Color Meter) оказался не 255 0 0, a 252 13 27.

Отобржение красного цвета на iMac
Красный цвет использует слегка разные значения в sRGB и разных экранных профилях.

Аналогично, голубой цвет (заданный как rgb(0 255 255)) отобразился как rgb(45 255 254).

Отображение голубого цвета на iMac
Голубой цвет использует слегка разные значения в sRGB и разных экранных профилях.

Большинству дизайнеров и разработчиков не приходится сталкиваться с этой информацией, потому что, опять же, интерполяция в вебе происходит в пространстве sRGB, так что расчеты для градиентов, о которых мы говорили выше, по-прежнему происходят между исходными значениями. А экранная система по-своему пересчитывает эти sRGB-цвета, определяемые браузером, в нужные цвета на экране.

Ладно.

А теперь неожиданный поворот.

Расчеты не всегда происходят в sRGB.

В частности… и вы уже могли догадаться, судя по тому, сколько времени я потратил на обсуждение режимов наложения…

Математика режимов наложения в Safari, Edge и Chrome работает в цветовом пространстве экрана, а не в sRGB.

:эмодзи со взрывающимся мозгом:

Так… что же это значит?

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

  • Красный: rgb(98.824% 5.098% 10.588%)
  • Голубой: rgb(17.647% 100% 99.608%)

И когда Chrome или Safari выполняет наложение в режиме умножения с этим значениями (которое изначально давало нам rgb(0 0 0) в sRGB):

  • Умножение в R-канале: .98824 × .17647 = 17.439%
  • Умножение в G-канале: .05098 × 1 = 5.098%
  • Умножение в B-канале: .10588 × .99608 = 10.546%

Это, конечно, тёмный цвет… но не черный. Два исходных цвета, определенные в sRGB, дают в результате цвет, тоже определенный в sRGB, используя расчет в другом цветовом пространстве.

Похожие комбинации цветов, которые должны давать черный, например умножение желтого (rgb(100% 100% 0%)) на синий (rgb(0% 0% 100%)), тоже дают значения, близкие к черному… но не черный.


Умножение в других цветовых пространствах дают значения, отличающиеся от черного. Посмотреть пример в CodePen

Это ожидаемо?

Я бы сказал, что это — не ожидаемое поведение. В спецификациях цветов/значений/анимаций везде рассказывается, как интерполяция и расчеты со смешиванием цветов проводятся в том же цветовом пространстве по умолчанию (sRGB), в котором цвета определяются. В спецификации композитинга единственное упоминание цветовых пространств есть в разделе «Неделимые режимы наложения», где речь идет о режимах наложения, связанных с оттенком и насыщенностью.

Как отмечено выше, в Firefox такой проблемы нет, хотя есть мнение, что это отчасти оттого, что Firefox не поддерживает продвинутого управления цветом.

Еще одна причина, почему это кажется неправильным способом расчета — то, что в Canvas цвета смешиваются по-другому. С теми же самыми цветами в canvas и тем же самым режимом наложения даже в Safari и Chrome получается сплошной черный цвет (black).

Наложение цветов в CSS и в Canvas
Наложение красного и голубого цветов в режиме умножения в canvas в Chrome верно отображает сплошной черный цвет. Смотреть пример в CodePen.

Но для меня важнейшая причина в том, что если у нас есть один-единственный реальный способ задавать цвета в фиксированном пространстве чисел… то расчет должен учитывать те значения, что мы указали.

Более глобальная дискуссия

Люди, глубоко разбирающиеся в управлении цветами (вдобавок к цветам в вебе), предлагают разные подходы, как быть с этим в будущем. Крис Лилли и другие в W3C давно проталкивают управление цветами в вебе и обсуждают возможные варианты, как задавать цветовое пространство для документа и т.д. (спасибо Амелии Беллами-Ройдз за наводку на несколько соответствующих ишью на Гитхабе). Даже там, кажется, большинство согласно, что sRGB — дефолтный вариант ради совместимости, тем более когда вы именно в нем и работаете. В будущем, когда мы сможем писать lab() или color(display-p3 100% 0% 0%), что даст нам другой диапазон в определенных цветовых пространствах, можно будет подумать о другом дефолтном варианте для интерполяции и композитинга. Занятно будет посмотреть, до чего дойдут новые уровни спецификации цветов с такими редакторами, как Крис, Лия Веру, Юна Кравец и Адам Аргайл.

Большое спасибо Эрику Портису за введение в некоторые инструменты Мака (типа Digital Color Meter) и основы экранных профилей мониторов. Это оказалось той основой, без которой я так и не смог бы понять, почему мои визуальные результаты не совпадают с результатами расчетов по спецификации. Он также начал небольшое обсуждение в Твиттере, которое подтвердило некоторые наши теории о том, что происходило с режимами наложения.

P.S. Это тоже может быть интересно:

1 комментарий

Оставить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Получать новые комментарии по электронной почте. Вы можете подписаться без комментирования.