EN

Classic: что может быть проще?

Также доступно на английском

Проще некуда

Из четырёх методов интерполяции Photoshop самым безобидным выглядит Classic.

Казалось бы, что тут сложного? Берёшь один RGB, берёшь другой, раскладываешь на каналы и интерполируешь каждый. Никаких преобразований: ни в linear RGB, ни в Oklab, которые мы разбирали в прошлой статье. Один вечер работы, максимум два.

Думаете, готово?

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

Каждый канал сам по себе

Classic не работает с цветом целиком. Он разбирает его на три числа и ведёт каждое отдельно, ничего не зная про остальные два.

Для перехода от красного (255, 0, 0) к синему (0, 0, 255) получается три независимые кривые: 255 → 0 для красного канала, постоянное 0 для зелёного и 0 → 255 для синего. Потом три результата снова собираются в цвет.

В прошлой статье мы для краткости называли это интерполяцией в sRGB. Точнее будет сказать так: Classic интерполирует закодированные RGB-значения документа напрямую, без преобразования в другое цветовое пространство. В обычном sRGB-документе это действительно числа sRGB, но принцип не привязан именно к нему: числа входят в алгоритм такими же, какими вышли из палитры.

У этого решения есть цена, и мы её уже видели: в прошлой статье чёрно-белый переход в Classic оказался темнее, чем в Linear. Причина в той самой загадке про 128, которое стоит посередине числовой шкалы, но несёт лишь около 21% световой интенсивности. Classic наступает на эти грабли ровно потому, что не выходит из чисел документа.

Стопы и сегменты

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

У каждого стопа есть:

  • позиция внутри градиента,
  • RGB-цвет.

А между каждыми двумя соседними стопами есть midpoint, то есть центр перехода, который мы разобрали в разделе «Midpoint как центр тяжести» прошлой статьи. Позиция и цвет описывают сам стоп, а midpoint — переход между двумя стопами. Поэтому на n стопов приходится n−1 midpoint. Промежутки между соседними стопами мы будем называть сегментами. Их тоже n−1:

стоп 0сегмент 0стоп 1сегмент 1стоп 2

Каждый сегмент считается отдельно от остальных, и первым делом позиция пикселя переводится из координат всего градиента в координаты сегмента:

u=xpipi+1piu=\frac{x-p_i}{p_{i+1}-p_i}

Где:

  • x — позиция пикселя во всём градиенте
  • pᵢ — позиция левого стопа сегмента
  • pᵢ₊₁ — позиция правого стопа
  • u ∈ [0,1] — нормализованная позиция пикселя внутри сегмента

Если x лежит за крайними стопами, искать сегмент не нужно: левее первого стопа сохраняется цвет первого, правее последнего — цвет последнего.

Разберём на числах. Пусть мы рисуем горизонтальный градиент в прямоугольнике 200 × 50 пикселей и ставим три стопа: чёрный в самом начале, белый на 60% длины и серый в конце. В пикселях это 0, 120 и 200.

0 px120 px200 px150 px

Возьмём отметку 150 пикселей. Во всём градиенте это три четверти пути: x = 150/200 = 0.75. Но интерполяции важно не это, а то, где он находится внутри своего сегмента. Попадает он во второй, тот, что идёт от белого стопа к серому. Границы этого сегмента: p₁ = 0.6 и p₂ = 1.

u=0.750.610.6=0.375u=\frac{0.75-0.6}{1-0.6}=0.375

Итого: во всём градиенте пиксель прошёл 75% пути, а в своём сегменте только 37.5%. Дальше алгоритм будет работать именно со вторым числом.

Так что в рамках сегмента это всегда переход от 0 до 1. Сколько пикселей он занимает на самом деле и в каком месте градиента начинается, для интерполяции уже неважно.

Переназначение позиции через midpoint

Midpoint мы подробно разобрали в предыдущей статье, так что здесь только напомню формулу:

φ(u,m)={u2m,um12+um2(1m),u>m\varphi(u,m)= \begin{cases} \dfrac{u}{2m}, & u \leq m \\[8pt] \dfrac{1}{2}+\dfrac{u-m}{2(1-m)}, & u > m \end{cases}

Она устроена так, что φ(m, m) = 0.5: куда бы ни уехал ромбик, для интерполяции он всегда оказывается ровно посередине. Переназначенную позицию обозначим t. Именно она, а не исходная u, идёт дальше.

Midpoint сжимает одну половину и растягивает другую

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

Почему прямых недостаточно

Позиция посчитана, midpoint учтён. Остался главный вопрос: по какой траектории цвет идёт от одного стопа к другому.

Самый очевидный ответ: по прямой. Но если каждый сегмент интерполировать прямой, во внутренних стопах градиент будет менять скорость скачком.

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

Смотрите на жёлтый стоп: на верхней ленте вокруг него видно маленькое плато, цвет там будто притормаживает. На нижней его нет. Переход идёт ровно.

Вот и вся разница. В самом заметном месте это около 20 значений из 255, а такие изломы глаз охотно прощает: цвет меняется плавно, подвоха не видно.

Зато он сразу виден, если перестать смотреть на ленту и посмотреть на кривую каждого канала:

Каждый сегмент — прямая

Каждый канал идёт к среднему стопу по прямой и от него по другой прямой. В точке стыка все три ломаются под углом.

Кривая учитывает соседей

Те же стопы, но углы сглажены: каждая кривая входит в средний стоп и выходит из него с одним и тем же наклоном.

Чтобы две соседние кривые встречались в стопе без излома, им нужно знать, что происходит по обе стороны от него.

Эту роль выполняет касательная. Мы встречали её в прошлой статье: она задаёт скорость и направление, с которыми кривая входит в точку и выходит из неё. Важная деталь: скорость эта измеряется по шкале сегмента, то есть по той самой t от 0 до 1, а не по пикселям всего градиента. Если слева и справа от стопа касательная одна и та же, по этой шкале излома нет. А что получится, когда мы вернёмся к пикселям, посмотрим чуть ниже.

Classic вычисляет такую касательную в каждом стопе отдельно для красного, зелёного и синего каналов.

Удобнее всего записать это через хорды. Хорда сегмента — это то, насколько канал изменился от его начала к концу (то есть разница между значениями канала в следующем стопе и в текущем):

di=Vi+1Vid_i=V_{i+1}-V_i

Где:

  • dᵢ — хорда сегмента i
  • Vᵢ — значение канала в стопе с индексом i

Тогда касательная в стопе — это среднее двух хорд, которые в нём встречаются:

Ti=di1+di2T_i=\frac{d_{i-1}+d_i}{2}

Отсутствующая хорда считается нулём: у первого стопа нет хорды слева, у последнего — справа. Поэтому одна и та же формула работает во всех трёх случаях:

  • первый стоп: T₀ = (0 + d₀)/2 = d₀/2 — половина хорды справа
  • внутренний стоп: Tᵢ = (dᵢ₋₁ + dᵢ)/2
  • последний стоп: Tₙ₋₁ = (dₙ₋₂ + 0)/2 = dₙ₋₂/2 — половина хорды слева

Стоп не участвует в собственной касательной

С внутренним стопом связана одна тонкость, которую стоит проговорить.

Обе хорды включают его значение: левая приходит в него, правая из него выходит. Казалось бы, его собственное значение и определяет наклон. Но стоит раскрыть хорды, как оно пропадает:

Ti=di1+di2=(ViVi1)+(Vi+1Vi)2=Vi+1Vi12T_i=\frac{d_{i-1}+d_i}{2} =\frac{(V_i-V_{i-1})+(V_{i+1}-V_i)}{2} =\frac{V_{i+1}-V_{i-1}}{2}

Vᵢ входит в левую хорду со знаком плюс, а в правую — со знаком минус, и в сумме сокращается. Остаётся разница между соседями, поделённая пополам. Это та самая касательная Catmull-Rom из прошлой статьи. Только теперь видно, откуда она берётся.

Практический смысл такой: наклон кривой в стопе задают его соседи, а не он сам. Если подвинуть средний стоп вверх или вниз, не трогая крайние, кривая пройдёт через новую точку, но сохранит прежнюю касательную.

Пример

Рассмотрим значения одного канала в трёх стопах:

V₀ = 0, V₁ = 1, V₂ = 0.9

Хорды: d₀ = 1, d₁ = −0.1. Канал сначала резко растёт, потом слегка падает.

Касательные:

T0=0+12=0.5T_0=\frac{0+1}{2}=0.5 T1=1+(0.1)2=0.45T_1=\frac{1+(-0.1)}{2}=0.45 T2=0.1+02=0.05T_2=\frac{-0.1+0}{2}=-0.05

Обратите внимание на T₁. Второй сегмент падает, а касательная в его начале осталась почти такой же, как на первом сегменте, и направлена вверх. Разгон с первого сегмента никуда не делся. Это и есть та самая рассогласованность, про которую мы говорили в разделе про concordant, discordant и overshoot предыдущей статьи.

Касательные на стопах: 0 → 1 → 0.9

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

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

стоп i−1левый сегментстоп iправый сегментстоп i+1

одна касательная Tᵢ обслуживает оба сегмента

От касательных к кривой

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

H(t)=(2t33t2+1)V0+(t32t2+t)T0+(2t3+3t2)V1+(t3t2)T1\begin{aligned} H(t)={}&(2t^3-3t^2+1)V_0 \\ &+(t^3-2t^2+t)T_0 \\ &+(-2t^3+3t^2)V_1 \\ &+(t^3-t^2)T_1 \end{aligned}

Где:

  • t ∈ [0,1] — позиция после переназначения через midpoint
  • V₀, V₁ — значения канала на концах сегмента
  • T₀, T₁ — касательные в этих стопах
  • H(t) — значение Эрмитовой кривой

Формула гарантирует, что кривая пройдёт точно через оба стопа:

H(0)=V0,H(1)=V1H(0)=V_0, \qquad H(1)=V_1

Всё остальное между стопами решают касательные.

Градиент с двумя стопами

Для простейшего перехода 0 → 1 хорда одна, и обе касательные равны её половине:

T0=T1=102=0.5T_0=T_1=\frac{1-0}{2}=0.5
Подстановка: как формула сворачивается
H(t)=(2t33t2+1)0+(t32t2+t)0.5+(2t3+3t2)1+(t3t2)0.5=0+(0.5t3t2+0.5t)+(2t3+3t2)+(0.5t30.5t2)=t3+1.5t2+0.5t\begin{aligned} H(t)={}&(2t^3-3t^2+1)\cdot 0 \\ &\quad +(t^3-2t^2+t)\cdot 0.5 \\ &\quad +(-2t^3+3t^2)\cdot 1 \\ &\quad +(t^3-t^2)\cdot 0.5 \\[8pt] ={}&0 \\ &\quad +(0.5t^3-t^2+0.5t) \\ &\quad +(-2t^3+3t^2) \\ &\quad +(0.5t^3-0.5t^2) \\[8pt] ={}&-t^3+1.5t^2+0.5t \end{aligned}
H(t)=t3+1.5t2+0.5tH(t)=-t^3+1.5t^2+0.5t

Сравним её с линейной интерполяцией L(t)=t:

Переход 0 → 1
Линейная интерполяцияЭрмитова интерполяция

Обе кривые проходят через (0, 0), (0.5, 0.5) и (1, 1), но между этими точками Эрмитова кривая сначала движется медленнее, затем быстрее, а перед концом снова замедляется. Именно это и делает переход «мягким» на глаз: цвет задерживается у обоих стопов и быстро проскакивает середину.

Когда кривая уезжает за край

Вернёмся к примеру 0 → 1 → 0.9.

Второй сегмент должен падать от 1 до 0.9, всего на одну десятую. Но касательная в его начале равна 0.45 и направлена вверх: кривая входит в него на полной скорости роста, унаследованной от первого сегмента.

Деться ей некуда. Она сначала поднимается выше 1 и только потом разворачивается к 0.9. Подставим V₀ = 1, V₁ = 0.9, T₀ = 0.45 и T₁ = −0.05:

Подстановка: откуда берётся эта кривая
H(t)=(2t33t2+1)1+(t32t2+t)0.45+(2t3+3t2)0.9+(t3t2)(0.05)=(2t33t2+1)+(0.45t30.9t2+0.45t)+(1.8t3+2.7t2)+(0.05t3+0.05t2)=0.6t31.15t2+0.45t+1\begin{aligned} H(t)={}&(2t^3-3t^2+1)\cdot 1 \\ &\quad +(t^3-2t^2+t)\cdot 0.45 \\ &\quad +(-2t^3+3t^2)\cdot 0.9 \\ &\quad +(t^3-t^2)\cdot(-0.05) \\[8pt] ={}&(2t^3-3t^2+1) \\ &\quad +(0.45t^3-0.9t^2+0.45t) \\ &\quad +(-1.8t^3+2.7t^2) \\ &\quad +(-0.05t^3+0.05t^2) \\[8pt] ={}&0.6t^3-1.15t^2+0.45t+1 \end{aligned}
H(t)=0.6t31.15t2+0.45t+1H(t)=0.6t^3-1.15t^2+0.45t+1

Максимум приходится на t ≈ 0.24 и равен 1.05. Канал, который не должен был подниматься выше 1, забирается на 5% дальше.

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

Classic не изменяет касательную и не заменяет её нулём. Он поступает проще: сначала вычисляет обычную Эрмитову кривую, а затем ограничивает результат значениями на концах текущего сегмента.

Определим нижнюю и верхнюю границы:

Vmin=min(V0,V1),Vmax=max(V0,V1)V_{min}=\min(V_0,V_1), \qquad V_{max}=\max(V_0,V_1)

После этого:

C(t)=min(Vmax,max(Vmin,H(t)))C(t)=\min\bigl(V_{max},\max(V_{min},H(t))\bigr)

Если H(t) лежит между Vmin и Vmax, ничего не меняется. Если кривая вышла выше Vmax, результат становится равен Vmax. Если опустилась ниже Vmin, результат становится равен Vmin.

Ограничение в работе

Подсвеченный коридор показывает диапазон значений сегмента. Красным залито то, что ограничение срезает. Потяните последнюю точку выше средней: рассогласованность исчезнет, и срезать станет нечего.

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

Почему диапазон задаётся сегментом, а не sRGB

На первый взгляд, достаточно ограничить результат диапазоном [0,1]. За пределы допустимого цвета кривая всё равно не выйдет. Но ограничение здесь строже.

Для перехода 0.4 → 0.6 значение 0.7 совершенно законно с точки зрения sRGB, но находится за пределами значений текущего сегмента. Classic ограничит его до 0.6.

Благодаря этому каждый канал движется только между значениями стопов своего сегмента, даже если его касательные пытаются увести Эрмитову кривую дальше. Приятное следствие: отдельная проверка на выход за [0,1] уже не нужна. Если все стопы лежат в допустимом диапазоне, результат из него не выйдет.

Можно подумать, что ограничение нужно только на рассогласованных сегментах, где касательная и хорда смотрят в разные стороны. Но совпадение направлений говорит только о знаке касательной, а не о её величине.

Представьте, что канал сначала вырос от 0 до 0.8, а на следующем сегменте должен подняться совсем немного: от 0.8 до 0.81. Касательная в общем стопе унаследует разгон первого сегмента и останется положительной, то есть будет согласована со вторым. Но по сравнению с его крошечной хордой 0.01 она окажется огромной. Кривая проскочит выше 0.81, развернётся и вернётся к конечному значению. Ограничение срежет этот overshoot точно так же, как на рассогласованном сегменте.

Значит, важен не только знак касательной, но и её величина относительно хорды. На спокойных согласованных сегментах ограничение ничего не делает. На слишком резких оно не выпускает кривую за значения стопов.

Smoothness

В панели градиента есть параметр Smoothness. Логично предположить, что он плавно смешивает линейный переход с кривым: на нуле получается прямая, на сотне полный Эрмит, а посередине что-то среднее.

Так и есть. Интересно другое: смешиваются не кривые.

Сразу оговорюсь: всё, что мы считали до сих пор, происходило при Smoothness = 100%, значении по умолчанию. Потому касательные и брались чистым средним двух хорд.

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

Обозначим силу сглаживания через s ∈ [0,1]. В интерфейсе это проценты от 0 до 100. Для сегмента с хордой d:

T0=(1s)d+sT0,T1=(1s)d+sT1T'_0=(1-s)\,d+s\,T_0, \qquad T'_1=(1-s)\,d+s\,T_1

Здесь T₀ и T₁ обозначают те самые касательные Catmull-Rom, что мы считали выше, d обозначает хорду текущего сегмента, а через T′ записан результат смешивания.

В кривую Эрмита уходит именно T′. При Smoothness = 100% штрих можно опустить: T′ там совпадает с T, поэтому до сих пор мы их и не различали.

Посмотрим на края диапазона.

При s = 1 касательные остаются нетронутыми. Это полный Эрмит.

При s = 0 обе касательные становятся равны хорде сегмента. И вот здесь происходит красивое: кубическая кривая Эрмита, у которой обе касательные равны собственной хорде, становится в точности прямой линией.

Почему Эрмит с T′₀ = T′₁ = d вырождается в прямую

Подставим T′₀ = T′₁ = d = V₁ − V₀:

H(t)=(2t33t2+1)V0+(t32t2+t)(V1V0)+(2t3+3t2)V1+(t3t2)(V1V0)=V0[(2t33t2+1)(t32t2+t)(t3t2)]+V1[(t32t2+t)+(2t3+3t2)+(t3t2)]=V0(1t)+V1t\begin{aligned} H(t)={}&(2t^3-3t^2+1)V_0 \\ &\quad +(t^3-2t^2+t)(V_1-V_0) \\ &\quad +(-2t^3+3t^2)V_1 \\ &\quad +(t^3-t^2)(V_1-V_0) \\[8pt] ={}&V_0\Bigl[ (2t^3-3t^2+1) -(t^3-2t^2+t) -(t^3-t^2) \Bigr] \\ &\quad +V_1\Bigl[ (t^3-2t^2+t) +(-2t^3+3t^2) +(t^3-t^2) \Bigr] \\[8pt] ={}&V_0(1-t)+V_1t \end{aligned}

Кубические и квадратичные члены сокращаются.

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

Заодно выясняется, что ломаная, с которой начинался разговор про касательные, не была выдумана для наглядности. Это вполне достижимый режим: достаточно увести ползунок в ноль.

А есть ли разница, что смешивать

Тут стоит остановиться, потому что напрашивается возражение. Кривая Эрмита линейно зависит от касательных: если подставить в неё сумму, получится сумма результатов. Значит смешивание касательных буквально равносильно смешиванию двух готовых кривых, линейной и полной Эрмитовой:

H((1s)d+sT)=(1s)H(d)+sH(T)H\bigl((1-s)\,d+s\,T\bigr)=(1-s)\,H(d)+s\,H(T)

Слева записано то, что делает Classic. Справа записано то, что напрашивалось в начале раздела. И H(d) мы только что посчитали: это прямая.

Так что различить два описания по картинке невозможно до того момента, пока в дело не вступает ограничение. Оно не линейно, и вот тут порядок операций начинает решать: срезание overshoot у уже смешанной кривой отличается от смешивания прямой с уже срезанной Эрмитовой.

Smoothness от 0 до 100%

Уведите ползунок в ноль: касательные лягут на хорды, и от кривой останется ломаная из прямых отрезков. Вместе с изгибами исчезнет и overshoot. Ограничивать станет нечего.

Classic делает это в таком порядке: сначала Smoothness изменяет касательные, затем по ним строится кривая, и только готовое значение ограничивается диапазоном сегмента.

Кривая целиком

Соберём всё, что получилось. Для одного канала цепочка выглядит так:

  1. значения канала в стопах
  2. 1поиск сегмента и нормализация позиции внутри него
  3. 2переназначение позиции через midpoint
  4. 3хорды: своя и две соседние
  5. 4касательные, подтянутые к хорде по Smoothness
  6. 5кубический сплайн Эрмита
  7. 6ограничение диапазоном значений на концах сегмента
  8. значение канала

А если развернуть цепочку в код, вот она целиком, без сокращений:

Псевдокод: одна процедура на канал
CLASSIC-CHANNEL(stops, midpoints, s, x)
# 0. за крайними стопами держится их цвет
если x ≤ позиция первого стопа: вернуть его значение
если x ≥ позиция последнего стопа: вернуть его значение
# 1. сегмент, которому принадлежит x
i ← индекс сегмента, где лежит x
p₀, p₁ ← позиции его стопов
V₀, V₁ ← значения его стопов
u ← (x − p₀) / (p₁ − p₀)
# 2. переназначение через midpoint
m ← midpoints[i]
t ← u/(2m) если u ≤ m
½ + (u−m)/(2(1−m)) иначе
# 3. хорды: своя и две соседние,
# отсутствующая равна нулю
d ← V₁ − V₀
d_пред ← V₀ − значение стопа i−1
d_след ← значение стопа i+2 − V₁
# 4. касательные Catmull-Rom
T₀ ← (d + d_пред)/2
T₁ ← (d + d_след)/2
# 5. и смешивание с хордой по Smoothness
T′₀ ← (1−s)·d + s·T₀
T′₁ ← (1−s)·d + s·T₁
# 6. кубический сплайн Эрмита
H ← (2t³ − 3t² + 1)·V₀
+ (t³ − 2t² + t)·T′₀
+ (−2t³ + 3t²)·V₁
+ (t³ − t²)·T′₁
# 7. ограничение диапазоном сегмента
вернуть min(max(H, min(V₀,V₁)), max(V₀,V₁))

Для цвета эта процедура вызывается трижды, по разу на красный, зелёный и синий. Три результата собираются обратно в RGB, и кривая Classic готова.

В самой кривой нет ни одного подобранного коэффициента: все описанные шаги полностью определяются стопами, midpoint и Smoothness. Графики выше визуализируют эти же шаги. Они не нарисованы от руки.

Можем закрывать Classic?

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

ШагClassicLinearPerceptualSmooth
Цветовое пространствоRGB документа, без преобразования?????????
Криваясплайн Эрмита?????????
КасательнаяCatmull-Rom, смешанная по Smoothness?????????
Пост-процессингограничение диапазоном сегмента?????????

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

Что дальше

Кривая готова. Мы знаем, откуда берутся касательные, какой формулой считается значение, что происходит с overshoot и что на самом деле делает Smoothness.

Осталось сравнить результат с тем, что рисует сам Photoshop. И вот тут начинается интересное.

Кривая совпадает. Пиксели расходятся.

Расхождение крошечное: даже в худшей точке оно не дотягивает до одного значения из 255, а обычно держится около десятой доли. Такое не увидеть глазом ни на каком градиенте.

Но оно есть. Оно устойчиво, повторяется от рендера к рендеру и не исчезает, сколько ни правь формулы.

Потому что оно не в кривой.

И даже не в Classic.

Причина лежит глубже.

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