Цветосмешение
Современный дизайн просто помешан на спецэффектах, он, можно сказать, только ими и живёт. И это ничего, лишь бы со вкусом. Приходится иногда делать такую штуку как размытие
или fade-out
. Видимо из-за некоторых проблем с реализацией text-overflow:ellipsis
в браузерах, этот спецэффект получил некоторую популярность. Суть его сводится к тому, чтобы положить на объекты, которые не помещаются в рамки родителя, некую картинку, плавно переходящую от прозрачности к сплошному цвету. Мол, не поместилось — прокрутите или сделайте браузер пошире. Простая штука, на самом деле.
И всё бы ничего, но в какой-то момент я стал замечать, что выведенный из цвета в ноль градиент как-то не слишком точно ложится на тот же самый цвет. Покрутил-повертел: вроде не цветовые профили, да и цвета не напутал. Отложил, забылось.
И тут сегодня мне приходит письмо: мол, такие дела — как-то всё странно и плохо накладывается, вот вам тестовый пример. Спасибо, — говорю, — посмотрим. Посмотрел: и правда, какая-то чертовщина. Скажу сразу, внятного объяснения я так и не нашёл. Так вот…
Эксперимент
Сначала рисуем в Фотошопе пять квадратов:
- Квадратная шейпа, заливка (fill) уведена в ноль;
- внутри эффектом наложен горизонтальный линейный градиент: от выбранного цвета к нему же;
- левая часть градиента уведена в нулевую прозрачность.
Квадратные шейпы положены рядом, под каждой из них помещён растровый квадрат со сплошной заливкой, соответствующей основному цвету градиента. Режим смешения (blending mode) каждого из слоёв выставлен в «Normal». Тем самым, мы избавляемся от любых визуальных проявлений градиента. Экспортированная в PNG-8 картинка получается такой:
Любые попытки потрогать её пипеткой, приводят ровно к пяти цветам, как и ожидалось. Дальше верстаем такие же пять квадратов размером 200 × 200 пикселов. Каждому из них присваивается класс, накладывающий градиентную картинку (полупрозрачный PNG-24, почищенный при помощи ImageOptim) на соответствующий фоновый цвет:
.color-000 {
background:#000 url(000.png) no-repeat;
}
.color-CCC {
background:#CCC url(CCC.png) no-repeat;
}
.color-C00 {
background:#C00 url(C00.png) no-repeat;
}
.color-090 {
background:#090 url(090.png) no-repeat;
}
.color-069 {
background:#069 url(069.png) no-repeat;
}
Такое наложение имитирует слои в Фотошопе. В браузерах вышла следующая картина:
Свёрстанная страница выглядит так же, как экспортированная картинка. Даже если очень сильно приблизить. Но это если на первый взгляд, а если повозить по этому делу пипеткой (попробуйте сами), то получается что-то странное: цвет начинает дрожать. Например, мой любимый оттенок красного #CC0000
прыгает красной составляющей в #CB0000
(или 204–203, если в системе RGB). Исключение составил чёрный квадрат и, как выяснилось, другие простые цвета (#00F
, #FF0
и так далее) — они состоят из одного сплошного цвета.
Проблемы, как известно, передвигаются стаями. Поэтому давайте усложним задачу и протестируем это цветосмешение в разных браузерах. Сделаем скриншоты в семи браузерах, составим их вместе и удалим из картинок основной цвет каждого из квадратов, чтобы понять по какому принципу происходит смешение. Результат обескураживает:
Смешение разнится не только в разных браузерах и разных системах, но ещё и для разных цветов. На этом этапе любые эксперименты хочется прекратить и громко спросить кого-то: «Что за дела, шеф?!» Ответа, как обычно, не следует. Поэтому попробуем предположить что-нибудь самостоятельно.
Никаких проблем с гаммой, цветовыми профилями и другими чанками
в PNG быть не может — файлы почищены всеми возможными утилитами. Единственное, что приходит мне в голову — это нехватка цветов в некоторых цветовых диапазонах, которая заставляет браузеры делать смешение (dithering) пограничных цветов для имитации недостающего. Но почему в некоторых диапазонах всё проходит гладко, да и сам Фотошоп справляется на «отлично»…
Кажется у меня ещё осталась «Помощь зала». Идеи?
UPD: В комментариях были предложены весьма достойные версии происходящего: первая и вторая — обе, в общем-то, об одном и том же: сложности округления.
Комментарии
26Вот здесь тестировали картинку с фоном #ccc - и проблема наблюдается только в windows...
У меня есть отличная идея — спросить у программеров компании «Опера» ;)
«fade-out» — это скорее «затухание», а не «размытие». :)
mozzy, вот объект и затухает потихоньку. Или вот словари говорят:
Ну, если это не фотошоп так рисует градиенты, то возможно, последствия округления, особенности реализаций таковы, что дают подобный эффект.
Вот примерно те же вычисления, что происходят в браузере, в качестве выражения python:
И вот результат:
Так что скорее всего это особенности округления, причем видно, что разные браузеры делают его по-разному (оптимизируют?).
Сорри, что порвал верстку, не ожидал.
Цветовые профили, гамма и прочее? Хотя профили, я смотрю, ты почикал.
Возможно, фотошоп немного коряво сохраняет. Может быть что-то со сжатием перемудривает.
А дрожание видно даже без пипетки.
У меня нет под рукой optipng и прочих pngkrush - интересно, после ихней оптимизации фича исчезнет или нет.
Возможно это происходит из-за применения в алгоритмах смешивания MMX команд. В таком случае у разработчиков есть выбор, пожертвовать точностью на один бит, чтобы сумма 2-х старших байтов от произведений 2-х 8-и битных чисел поместилась в 8 бит, либо пожертвовать скоростью наложения картинок почти в 2 раза. Как видно, скоростью никто жертвовать не хочет :)
m1ron, это не фотошоп.
Переход от прозрачности к цвету равномерный - проблема именно в отображении браузерами. Если наложить эту же картинку на другой фон - никакого дрожания нет.
после обработки всех имиджей PNGOUT (pngout image.png /c6 /f5 /s0) проблема иcчезает и картинки теряют в весе
это была гамма
vladfrandom, а можно поподробнее? Что значит "была гамма"?
vladfrandom, попробовал также обработать своё изображение с помощью PNGOUT - картинка потеряла в весе, но проблема осталась.
Никита, спасибо, что порвали вёрстку — был повод её починить ;) Для больших фрагментов кода лучше использовать
<source>
, а не<code>
http://www.w3.org/TR/PNG-GammaAppendix (гамма-коррекция должна быть by default)
Ну если вкратце, по-умолчанию в PNG включается гамма-коррекция с значением 2.5. Каждый броузер (и система) почему-то понимает это значение по-своему. PNGOUT корректирует изображение в соответствии с информацией о значении гамма-коррекции, а само значение гамма-коррекции выкашивает. Если я все более-менее правильно понимаю... :) Подобную операцию делает любой png-оптимизатор. Попутно они еще и сам png перепаковывают "как-то хитрее" - файл "легчает", хотя и не всегда.
vladfrandom, спасибо за объяснение.
Выложите, пожалуйста, файл, в котором у вас глюк не наблюдается.
vladfrandom, в тексте упомянуто, что все файлы почищены программой ImageOptim, которая, в частности, включает и PNGOUT.
Я считаю, что проблема действительно в алгоритме браузера, который отвечает за смешивание цветов при обработке полупрозрачных изображений. Думаю, многим известна проблема с округлением значения ширины, указанной в процентах в ИЕ. Здесь что-то похожее.
Цвет в изображении описывается в rgba. Если разбить градиент на уровни, то каждый уровень будет отличаться только значением a (уровнем прозрачности) т. к. значение rgb у нас везде одинаковое. Это значение прозрачности участвует в формуле, по которой рассчитывается выводимый на экран цвет, а разница и итоговом цвете объясняется алгоритмом округления браузера.
Пристально посмотрел на картинку. Никаких гамм там нет, цвета указаны чётко, меняется только прозрачность. Так что дело исключительно в отображении.
Хм, погорячился, был не прав: пути на файлы слетели - не заметил. Посыпаю голову пеплом.
Баг повторяется. Действительно, похоже на ошибку при просчете RGBA.
Кстати, Вадим, в тексте таки нет упоминания ImageOptim'a, иначе бы я в сторону pngout и не взглянул.
Извиняюсь за внесенную в обсуждение дезу.
vladfrandom, ок — упомянул только саму чистку, но не инструмент. Исправлюсь.
Очевидно, что проблема исключительно в погрешности округления.
Например: возьмем код
Получилось:
А ведь складывались одни и те же числа в разном порядке!
Теперь прикинем, как мы могли бы считать смешение для цветов. Каждый канал содержит число в диапазоне [0..255], включая канал прозрачности - т.е. имеем целые числа, над которыми придется осуществлять операции деления, умножения, сложения и вычитания.
Алгоритм без округления такой:
А теперь подумаем, в скольких местах может происходить "естественное" округление:
1. После каждого деления (2 раза)
2. После каждого умножения (2 раза)
А теперь добавим к этому "искусственное", явное округление (при приведении типов и т.д.).
Очевидно, что при использовании чисел разной точности, а также при разном подходе к самому округлению ("отбрасывание остатка", "до ближайшего целого" и др.), результат будет разным.
Вот мы и имеем три разных результата. Никита Прокопов выше привел показательный пример погрешностей округления.
Очень удобно такую задачу решать с помощью canvas. Там градиент сделать от прозрачности до нужного цвета, нужной площади не проблема. Цвет в котором должно заканчиваться, тоже яваскриптом узнать можно. Работает кросбраузерно, даже в ie6(с применение excanvas.js).
Как уже написали другие - это косяки с округлением. Я не знаю как их исправляет фотошоп, но я тут делал сдвиг цвета (Hue) на JS и циклический сдвиг на одном изображении сделать невозможно - через несколько шагов погрешности убивают всю картинку. Через сотню - там просто мясо из случайных пикселей. А в фотошопе в 8-битном цвете всё ок.
Как это правильно разрулить и можно ли вообще (в JS математика такая убогая, что хоть пиши с нуля) - я не разбирался. Мне нужен был быстрый и эффективный код, а картинку я мог изначальную спокойно хранить сколько угодно.