Перевод статьи 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
.
Красный цвет использует слегка разные значения в sRGB и разных экранных профилях.
Аналогично, голубой цвет (заданный как rgb(0 255 255)
) отобразился как rgb(45 255 254)
.
Голубой цвет использует слегка разные значения в 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
).
Наложение красного и голубого цветов в режиме умножения в canvas в Chrome верно отображает сплошной черный цвет. Смотреть пример в CodePen.
Но для меня важнейшая причина в том, что если у нас есть один-единственный реальный способ задавать цвета в фиксированном пространстве чисел… то расчет должен учитывать те значения, что мы указали.
Более глобальная дискуссия
Люди, глубоко разбирающиеся в управлении цветами (вдобавок к цветам в вебе), предлагают разные подходы, как быть с этим в будущем. Крис Лилли и другие в W3C давно проталкивают управление цветами в вебе и обсуждают возможные варианты, как задавать цветовое пространство для документа и т.д. (спасибо Амелии Беллами-Ройдз за наводку на несколько соответствующих ишью на Гитхабе). Даже там, кажется, большинство согласно, что sRGB — дефолтный вариант ради совместимости, тем более когда вы именно в нем и работаете. В будущем, когда мы сможем писать lab()
или color(display-p3 100% 0% 0%)
, что даст нам другой диапазон в определенных цветовых пространствах, можно будет подумать о другом дефолтном варианте для интерполяции и композитинга. Занятно будет посмотреть, до чего дойдут новые уровни спецификации цветов с такими редакторами, как Крис, Лия Веру, Юна Кравец и Адам Аргайл.
Большое спасибо Эрику Портису за введение в некоторые инструменты Мака (типа Digital Color Meter) и основы экранных профилей мониторов. Это оказалось той основой, без которой я так и не смог бы понять, почему мои визуальные результаты не совпадают с результатами расчетов по спецификации. Он также начал небольшое обсуждение в Твиттере, которое подтвердило некоторые наши теории о том, что происходило с режимами наложения.
P.S. Это тоже может быть интересно: