Математика цвета: Внутри Photoshop Gradients
Прежде чем начать
На тему «Как работает цвет» написано гигантское количество статей, и куда более компетентными людьми. В этой статье мы ограничимся только теми знаниями, которые нужны для понимания этой серии статей.
Если вы хотите проникнуться глубиной этих знаний — я крайне рекомендую следующие ресурсы:
- Charles Poynton — учёный-исследователь в области изображений и цвета
- Björn Ottosson — Software engineer, который изобрёл математику цветового пространства Oklab
По ходу статьи я буду давать новые и новые ссылки, чаще всего на Wikipedia, которые помогут углубиться в тему.
Цвет сложнее, чем кажется
Возьмём два цвета и построим переход между ними. Простейший способ — линейная интерполяция: разложить на R, G, B и для каждого канала вычислить промежуточное значение.
Где t ∈ [0, 1] — позиция в переходе: t = 0 — начальный цвет, t = 1 — конечный.
Казалось бы, готово? Но вот вам четыре метода интерполяции, которые предлагает Photoshop для одного и того же градиента:
Все четыре градиента заметно отличаются. Можно решить, что дело в разных формулах интерполяции — но главная причина в другом: интерполяция происходит в разных цветовых пространствах.
Gamma, sRGB и оптический обман зрения
sRGB — цветовое пространство «для глаз»
Когда мы выбираем цвет в Photoshop, задаём RGB в CSS, или сохраняем PNG — мы работаем в sRGB.
Это стандарт кодирования цвета, определяющий как числа (R, G, B ∈ [0, 255]) соответствуют видимым цветам.
Но у меня есть для вас загадка: Почему значение 128 — это не половина яркости 255?
Отгадка на этот вопрос кроется в чувствительности восприятия человеческого глаза. Зрение устроено таким образом, что оно значительно более восприимчиво к тёмным тонам. То есть вы можете отличить больше тёмных тонов друг от друга, чем светлых того же объёма.
Это приводит нас к ответу на вопрос про яркость — 128 не половина яркости, потому что зависимость намеренно нелинейна, и отражает чувствительность человеческого зрения, чтобы избежать ступенчатости (цвета с разной яркостью сливаются в один и образуют «плоскую ступеньку»).
sRGB кодирует значения нелинейно, распределяя больше уровней яркости в тёмных тонах.
Но возникает ещё больше вопросов: если sRGB кодирует и сжимает, то что конкретно он кодирует и что сжимает?
Linear-light — физическое пространство света
Linear-light RGB — это несжатое пространство физического света, где каждый канал выражен значением от 0 до 1.
Здесь значение 0.5 — это ровно половина физической яркости.
Именно его сжимает sRGB при помощи gamma-коррекции — трансферной функции.
Преобразование Linear RGB в sRGB
Гамма-функция применяется поканально, и она полностью обратима, позволяя перемещаться из одного цветового пространства в другое.
Преобразование sRGB в Linear RGB
Почему это важно для градиентов
Метод «Classic» в Photoshop интерполирует в sRGB. А метод Linear — в linear-light RGB. Одна и та же формула интерполяции, по RGB каналам, но визуально разный результат:
- В sRGB середина (128, 128, 128) → яркость ≈ 21%.
- В linear-light середина (0.5, 0.5, 0.5) → sRGB ≈ (184, 184, 184) → яркость = 50%.
Можно заметить, что sRGB интерполяция выглядит «равномерной», это происходит именно по той причине, что мы описали выше — ваш глаз гораздо чувствительнее к тёмным тонам, нежели к светлым.
Oklab — пространство для восприятия
Ни sRGB, ни linear RGB не являются перцептуально-равномерными. Одинаковое числовое расстояние между двумя цветами может восприниматься как большое или наоборот как маленькое, в зависимости от оттенка и яркости.
В 2020 году Björn Ottosson разработал цветовое пространство Oklab, в котором выделил три компонента:
- L — lightness,
- a — ось от зелёного к красному,
- b — ось от синего к жёлтому.
Основное достоинство Oklab в том, что интерполяция в этом цветовом пространстве приближена к перцептуально-равномерной, и интуитивно воспринимается как более точная и верная.
На примерах отчётливо видно, что sRGB интерполяция выглядит «грязной» на стыках переходов, там где Oklab даёт яркий насыщенный цвет.
Математически преобразование между sRGB и Oklab несколько сложнее, так как требует двух промежуточных цветовых пространств, а именно linear RGB (который мы разобрали выше) и LMS.
LMS описывает отклик трёх типов цветовых рецепторов глаза (колбочки в сетчатке глаза):
- L — Long wavelength (красный, пик ~564 нм)
- M — Medium wavelength (зелёный, пик ~534 нм)
- S — Short wavelength (синий, пик ~420 нм)
Oklab строит цветовое пространство начиная с того, как глаз физически воспринимает цвет. Кубический корень в формулах преобразования (см. ниже) моделирует нелинейный отклик рецепторов — это аналог gamma, но для биологической системы.
Преобразование sRGB в Oklab
-
sRGB → linear RGB (формула находится выше)
-
linear RGB → LMS
- Кубический корень
- LMS’ → Oklab:
Преобразование Oklab в sRGB
- Oklab → LMS
- Возведение в куб
- LMS → linear RGB
- linear RGB → sRGB (формулу можно найти выше)
Кубические кривые и плавные переходы
Мы разобрались, в каком пространстве интерполировать. Теперь разберёмся, как — какую форму может принимать кривая перехода.
До сих пор мы рассматривали линейную интерполяцию — прямую линию от V0 к V1. На полпути (t = 0.5) получаем ровно среднее двух значений. Никаких изгибов, никаких сюрпризов.
На практике же при построении градиентов используют смешение линейной интерполяции и смягчающих функций. И в качестве смягчающих функций используются кубические кривые. В основном нам будут интересны кубический сплайн Эрмита и кубическая кривая Безье.
И Эрмит, и Безье описывают одну и ту же математику — построение S-образных кривых. Отличаются же они только параметризацией и способом вычисления.
Кубический сплайн Эрмита определяется через значения на концах V0, V1 и касательные на концах T0 и T1. Касательная определяет скорость и направление, с которыми кривая входит в точку или выходит из неё.
- Большая касательная -> крутой вход/выход
- Нулевая -> горизонтальный
- Отрицательная (при положительной ΔV) -> кривая сначала идёт «не туда».
Влияние каждого компонента можно записать в виде строительного блока (базисной формулы):
Финальная формула интерполяции — сумма базисных функций, умноженных на значения кривой:
Откуда берутся касательные
Формула Эрмита принимает четыре параметра: значения на концах (V0, V1) и касательные (T0, T1). Значения — это цвета наших стопов (color stops), тут всё понятно. А вот касательные нужно откуда-то взять.
У градиента с двумя стопами — один сегмент, оба стопа крайние. У крайнего стопа сосед только с одной стороны, поэтому касательная определяется единственным сегментом — кривая плавно поднимается (или опускается) от V0 до V1:
У градиента с тремя и более стопами появляются внутренние стопы (interior stops) — те, что между первым и последним. У каждого внутреннего стопа есть два соседних сегмента: левый (от предыдущего стопа) и правый (к следующему). Касательная в этой точке должна учитывать оба.
Некоторые из методов интерполяции используют формулу касательной Catmull-Rom — среднее направление между левым и правым сегментами:
Если красный канал рос на левом сегменте (100 → 200) и продолжает расти на правом (200 → 250) — касательная положительная, кривая плавно проходит через стоп. Если рос слева (100 → 200), но падает справа (200 → 50) — касательная близка к нулю, кривая замедляется и разворачивается.
Concordant, discordant и overshoot
Для каждого канала на каждом внутреннем стопе возникает вопрос: куда направлена касательная относительно следующего сегмента?
Concordant (согласованный): касательная направлена туда же, куда идёт сегмент. Канал рос и продолжает расти, или падал и продолжает падать. Кривая плавно проходит через стоп, не выходя за пределы значений на его концах.
Discordant (рассогласованный): касательная направлена в противоположную сторону от сегмента. Канал рос, а теперь падает — но касательная по инерции продолжает тянуть вверх. Кривая вынуждена сначала пойти «не туда», потом развернуться. Этот заход за пределы называется overshoot — кривая выходит за границы значений на концах сегмента.
Flat (плоский): канал не меняется на сегменте (ΔV ≈ 0), но касательная ненулевая, потому что соседний сегмент имеет наклон. Кривая «вздувается» выше или ниже плоского значения.
На графике ниже — три кривых Эрмита на одном сегменте. Серые линии показывают границы значений на концах:
Overshoot — это не баг, а математическое свойство кубических кривых с Catmull-Rom касательными. Разные методы интерполяции в Photoshop решают проблему overshoot по-разному — и в этом кроется одно из главных отличий между ними.
Кубические кривые Безье
Помимо Hermite, кубические кривые можно задать и через контрольные точки — это кривые Безье. Вместо значений и касательных на концах — четыре контрольные точки (P0, P1, P2, P3). Кривая проходит через первую и последнюю точки, а две средние «притягивают» её к себе, формируя изгиб.
Любую кривую Безье можно пересчитать в эквивалентный сплайн Эрмита и наоборот — это одна и та же математика, записанная по-разному. Подробно мы разберём Безье в статье про метод Smooth — единственный метод Photoshop, который использует именно эту форму.
Midpoint как центр тяжести
Между каждыми двумя соседними стопами в Photoshop есть маленький ромбик — это midpoint. По умолчанию он стоит посередине (50%), но его можно перетащить ближе к одному из стопов.
Midpoint смещает центр тяжести градиента, заставляя его быстрее (при значениях < 50%) или медленнее (при значениях > 50%)
проходить через центральный цвет сегмента. Начальный и конечный цвет сегмента не изменяются, смещается только его центр — место, где проходит
основной переход.
- Midpoint 50% — всё плавно, середина цвета в середине сегмента.
- Midpoint 5% — уже на 5% пути цвет почти на полпути от V0 к V1. Переход происходит стремительно в самом начале, а оставшиеся 95% плавно «дотягивают» до V1.
- Midpoint 95% — наоборот. Почти весь сегмент плавно тянется к переходу, а потом резко переходит к V1.
Технически это реализовано через пересчёт позиции пикселя внутри сегмента:
u∈ [0, 1]— нормализованная позиция пикселя в сегментеm∈ [0, 1]— позиция midpointφ∈ [0, 1]— пересчитанная позиция для интерполяции.
Переназначение позиции в сегменте через midpoint всегда применяется до интерполяции — и кубической, и линейной. Эрмитова интерполяция и Безье работают с уже пересчитанной позицией φ(u), не с исходной u. Поэтому midpoint одинаково влияет на все методы интерполяции.
Приблизительный пайплайн градиентов
Подведём итог. Каждый метод интерполяции в Photoshop проходит через одни и те же этапы:
- Преобразование стопов в цветовое пространство интерполяции
- Вычисление касательных
- Кубическая интерполяция с учётом midpoint
- Пост-процессинг (обработка overshoot)
- Преобразование обратно в sRGB
| Шаг | Classic | Linear | Perceptual | Smooth |
|---|---|---|---|---|
| Цветовое пространство | ??? | ??? | ??? | ??? |
| Кривая | ??? | ??? | ??? | ??? |
| Касательная | ??? | ??? | ??? | ??? |
| Пост-процессинг | ??? | ??? | ??? | ??? |
В следующих статьях мы заполним каждую ячейку этой таблицы.
Что дальше
В следующей статье мы разберём самый простой из известных методов интерполяции — Classic, и узнаем, какие ухищрения были придуманы инженерами Adobe для борьбы с вечно убегающими за края кривыми.