Меню

Компьютер как смешивать цвета



Руководство по работе с цветом в веб-разработке. Часть первая. Смешивание цветов, их значения и свойства

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

Смешивание цветов

Очень важно понять, что использовать цвета на компьютере так же, как в детстве, не выйдет из-за смешивания цветов. Когда вы были детьми, у вас была краска. Краска и чернила принтера состоят из пигментов — маленьких частиц, которые смешиваются и образуют видимый цвет. Это субтрактивная схема формирования цвета. Чем больше цвета вы добавите, тем темнее будет цвет. Базовые цвета — это голубой, пурпурный и жёлтый, но при их смешивании по субтрактивной схеме вы получите чёрный.

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

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

У мониторов есть несколько режимов отображения, которые определяют то, как мы воспринимаем изображение. Обычно это называют глубиной цвета. Именно глубина цвета определяет количество отображаемых цветов. Если глубина равна 1 биту, нам доступны лишь два цвета — чёрный и белый. Глубина, равная двум битам, даёт 4 цвета, и так далее до 32, хотя обычные мониторы поддерживают глубину цвета до 24, что даёт нам 16 777 216 цветов (True Color и альфа-канал).

True Color (истинный цвет) называется так, потому что человеческий глаз способен различать около 10 миллионов цветов, и 24-битной глубины для этого достаточно. Красный, зелёный и синий цвета занимают по 8 битов, а оставшиеся используются для прозрачности (альфа-канал).

Давайте воспользуемся этой информацией и рассмотрим доступные нам свойства цвета.

Цветовые значения

RGB-значения

В конце предыдущего раздела между строк было сказано, что означает rbga(x, x, x, y); , однако, стоит разобраться в этом получше. Если мы работаем с RGB-моделью, то разделяем цвет на три канала со значениями от 0 до 255.

Hex-значения

Шестнадцатеричные цвета — это немного другой формат отображения цвета, и он наиболее часто используется в вебе.

1–2 апреля, Нижний Новгород и онлайн, От 1000 до 3500 ₽

Как вы знаете, байт — это 8 бит, то есть каждый шестнадцатеричный цвет задаётся 1 байтом. Каждый байт задаётся числом от 00 до FF в шестнадцатеричной системе счисления, или числом от 0 до 255 в десятичной. Таким образом, чёрный цвет задаётся числом #000000 , а белый — #FFFFFF .

Если разряды попарно повторяются, то их можно сократить, например, #00FFFF станет #0FF . Такая система записи весьма удобна для понимания и записи, а также для использования в программировании. Если же вы будете работать с цветом на более глубоком уровне, стоит рассмотреть систему HSL.

HSL-значения

HSL работает примерно так же, как и RGB, но вместо цветов она задаёт тон, насыщенность и светлоту.

Тон вращается на 360 градусов, а насыщенность и светлота принимают значения от 0 до 1.

Переход между этими двумя системами очень прост, но для человеческого восприятия гораздо лучше подходит HSL. Разобраться получше поможет эта демка и этот инструмент.

HSL также будет рассмотрена в следующей статье.

Именованные цвета

Разработчикам также доступны и именованные цвета. Тем не менее, с ними непросто работать из-за разницы в восприятии и их неточности. Вопиющим примером может служить то, что «dark grey» (тёмно-серый) светлее, чем «grey» (серый). Есть даже игра, в которой нужно угадывать цвета. Раньше, например, кроваво-красный цвет назывался chucknorris (сейчас это поддерживается только в HTML, насколько я знаю). Так или иначе, гораздо удобнее и профессиональнее будет использовать препроцессоры наподобие Sass для хранения цветов в переменных.

Читайте также:  Как сделать цвет закладок

Цветовые переменные

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

Но это совсем новая фича, и браузеры от Microsoft на момент написания статьи её не поддерживают.

Препроцессоры CSS также поддерживают переменные, так что вы можете создать переменные наподобие $brandPrimary и использовать их. Или использовать map:

Помните, что имена важны. Абстрактные имена порой пригождаются, ведь если вы измените переменную, которая раньше задавала синий цвет, а теперь — оранжевый, вам не придётся переименовывать все другие переменные, или, что хуже, вешать комментарий типа «$blue теперь оранжевый.»

currentColor

currentColor — это очень важное значение. Оно учитывает вложенность, и используется для переноса цвета в тени, границы и даже фоны.

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

Это очень полезно при работе с системами иконок. Вы можете задать currentColor как значение по умолчанию, а затем использовать необходимые CSS-классы для стилизации.

Препроцессоры

Препроцессоры CSS отлично подходят для работы с цветом. Вот несколько ссылок на документацию к различным функциям:

Вот несколько прикольных вещей, которые можно сделать при помощи Sass:

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

Свойства цвета

Цвет (color) как свойство CSS относится к цвету шрифта. Для заливки цветом большой области вы используете background-color , а в случае SVG-элемента — fill . Граница (border) — это граница вокруг HTML-элемента, а stroke — её SVG-аналог.

Свойства box-shadow и text-shadow принимают цветовое значение. Текстовые тени принимают 2 или 3 значения (вертикальную и горизонтальную тени и опциональный радиус блюра). Блочные тени же могут принимать ещё и опциональный радиус распространения. Также можено создать и внутренние тени. Вот отличный сайт с демонстрацией.

Градиенты

Линейные градиенты принимают направление, точки смены цвета и значение прозрачности. Вот пример:

Синтаксис совсем прост, но удобнее работать с онлайн-генераторами кода, хотя бы потому, что они генерируют код для устравших браузеров. Вот ещё один весьма красивый генератор градиентов. Кстати, его исходники открыты.

Примерно также градиенты создаются и в SVG. Мы задаём блок
, обращаться к которому мы будем по идентификатору. Также можно задать и область.

Такие градиенты также поддерживают непрозрачность, и это позволяет создавать красивые эффекты:

Также можно создавать и градиентный текст, подробнее об этом можно прочитать здесь.

Следующая часть про генерацию цвета и различные эффекты.

Источник

Смешиваем цвета правильно или оптимизируем AlphaBlend

Теория альфа смешивания

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

Итак, у нас есть 2 пикселя — исходный и пиксель назначения. Их нужно смешать и получить новый пиксель назначения. Каждый пиксель представлен 4-мя байтами A,R,G,B, где A — значение (не)прозрачности пикселя (0 — полностью прозрачный, 255 — полностью непрозрачный), RGB — цветовые компоненты. Классическая формула смешивания такова:

Важный момент! Единица — это в формуле. В жизни у нас за единицу выступает значение 255. Т.е. чтобы применять формулу, мы должны предварительно разделить значение каждого байта на 255. Как не трудно заметить, 255 и 256 — довольно близкие значения, а деление на 256 — это всего лишь сдвиг вправо на 8 бит. Поэтому часто встречается такое упрощение: вместо операции

делают следующее:
Это работает неплохо (а главное, значительно быстрее честного деления), но, в случае альфа смешивания результат не вполне корректный, а именно, результирующий пиксель получается немного темнее. Далее, я покажу, как можно выполнить вычисления точно и без потери в скорости работы.

Читайте также:  С чем носить сапоги цвета графит

Другой важный момент! Посмотрите на формулу. Во второй части есть SRC_COLOR * SRC_ALPHA. Такое умножение 3D акселераторы выполняют миллионами и даже миллиардами, не моргнув глазом. Но мы то пытаемся решить задачу, используя центральный процессор, и лишнее умножение (точнее 4 лишних умножения) на каждый пиксель — это не очень хорошо. Почему лишнее? Да потому что это умножение можно сделать заранее, преобразовав исходную картинку. У таких изображений даже название есть: premultiplied. Я не знаю термина в русском языке, но переведя дословно получим «предумноженные». И точно, GDI функция AlphaBlend требует в качестве исходного изображения строго premultiplied. Это разумно.

Что ж, с теорией закончили. На практике будем работать с 32-битным цветом. Один пиксель представлен 32-битным числом, в котором 4 байта, начиная с младшего, означают: B(lue), G(reen), R(ed), A(lpha). Поехали.

Первая реализация

Моя первая реализация была такой:

Согласен, выглядит не очень. 4 вещественных (точнее 5) умножения и 4 округления на каждый пиксель — это чересчур. Неудивительно, что по скорости этот монстр проигрывал AlphaBlend’у примерно в 7 раз.

Попробуем улучшить. Избавимся от вещественных умножений.

Здесь функции BLUEx256, GREENx256, и т.п. возвращают соответствующую компоненту, сдвинутую влево на 8 бит, т.е. умноженную на 256.

Эта функция примечательна тем, что в ней имеется компенсация замены деления на 255 сдвигом на 8 бит вправо. Заметили? Если нет, потерпите, ниже я опишу этот момент подробнее.

По скорости эта реализация уступает AlphaBlend’у примерно в 3 раза. Уже лучше, но все еще очень далеко от идеала.

Неожиданный результат

Как можно улучшить предыдущую функцию? Кажется, мы сделали все что можно. Однако, у меня получилось улучшить эту функцию способом, который стал для меня сюрпризом. Я и попробовал его просто чтобы убедиться, что ничего не получится. Однако, получилось.
Что если вынести операцию умножения байта на байт в таблицу. Получится не очень много — всего 65536 байт. Копейки.

Заводим такую табличку:

Удивительно, но эта функция работала в полтора раза быстрее предыдущей реализации. Правда есть одна тонкость — компилятор (в моем случае это был msvc 2013) очень грамотно сработал в операциях работы с памятью. Когда я попытался написать эту функцию на голом ассемблере, сделав, как мне казалось, все гораздо лучше оптимизатора, я получил функцию, которая работала в два раза медленнее этой. Это был провал. Я не стал разбираться, в чем конкретно я ошибся — очевидно мне не удалось грамотно распараллелить все операции — я просто оставил эту функцию на откуп оптимизатору.

Итак. Тут больше нечего оптимизировать. Мне не приходит в голову больше ничего. Но AlphaBlend все еще быстрее, раза в два. Как они этого добились? Кажется, пора на пенсию?

О компенсации замены деления на 255 сдвигом

Есть много способов быстрого деления на 255. Мне встречался такой:

Это неплохо. Это быстрее честного деления на 255. Но это все еще слишком громоздко. Я долго думал, как быстро разделить на 255 и не потерять ни в качестве ни в скорости. Как компенсировать деградацию цвета при использовании сдвига?

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

0xff * 0xff = 0xfe01. Сдвинув на 8 бит вправо, получим 0xfe — яркость компоненты уменьшена. Плохо.
А что, если мы одну из компонент увеличим на 1 перед умножением?
0xff * 0x100 = 0xff00. Хм, кажется это оно. Проверим случай, когда одна из компонент равна 0:
0xff * 1 = 0x00ff, сдвигаем вправо на 8 бит, получаем 0. Вуаля! При других значених компонент результат также будет верным.
Теперь легко найти место компенсации во второй функции: uint not_a = 256 — ALPHA(src);
Не 255 — A, а 256 — A, т.е. +1 к компоненте перед умножением. Для табличного метода умножения компенсация не требуется, т.к. в таблице все значения и так посчитаны как нужно.

Читайте также:  Как сделать цветочницу для домашних цветов

Тяжелая артилерия — инструкции SSSE3

Пора задуматься об оптимизации с использованием simd. Говорят, компилятор Intel умеет это делать и без участия человека. Возможно. Но гложат меня сомнения, что Intel совладает с AlphaBlend’ом. Ну максимум — сравняется с ней. Но мне то нужно сделать быстрее. Открываем справочник и вперед.

Первый вопрос, которым следует задаться — под какие инструкции делать оптимизации? У меня есть подозрение, что AlphaBlend оптимизирована под MMX, иначе я не могу объяснить ее превосходства над чистой x86 реализацией. MMX — это хорошо, но это прошлый век. Сейчас трудно найти компьютер, где бы не было поддержки SSE4. А под SSE вообще можно оптимизировать, даже не утруждая себя проверкой на наличие поддержки этих инструкций — вероятность, что твоя программа будет запущена на чем-то ниже Pentium 3 близка к нулю. Я, конечно же, веду речь о desktop приложениях. Экзотика вне рамок этой статьи.

Я остановил свой выбор на SSSE3. Этот набор инструкций достаточно распространен, чтобы заморочиться оптимизацией именно под него, учитывая наличие в нем очень очень удобных инструкций.

Самая же полезная инструкция, которая и ляжет в основу всех оптимизаций — это pshufb (интринсик _mm_shuffle_epi8). Именно ради нее и выбран SSSE3. В чем же ее сила? В том, что эта инструкция позволяет раскидать байты исходного 16-байтового регистра в любом произвольном порядке или вообще выкинуть эти байты за ненадобностью. Т.е. я могу с помощью этой инструкции в одно движение подготавливать всё необходимое для нужных расчетов. Другая важная инструкция — pmulhuw (интринсик _mm_mulhi_epu16) — это 8 умножений и 8 сдвигов вправо на 16 бит. Как будто специально для операции альфа смешивания. Т.е. одной этой командой я фактически вычисляю сразу 2 пикселя.

Ну чтож, поехали:

Как видно, simd реализация смешивает сразу 4 исходных пикселя с 4-мя пикселями назначения. Ну на то она и simd. За кадром рамками этой статьи оставлю описание решения проблемы, когда требуется смешать не кратное 4-м количество пикселей. Лично я применяю для этого «однопиксельные» вызовы c++ реализации.

Итоги

В итоге данная ssse3 реализация работает почти в 4 раза быстрее (в 3.78 на моем железе), чем функция AlphaBlend. Это весьма неплохой результат. Многие программисты (и я в том числе) скептически относятся к подобным «велосипедам». Как правило, результат получается заведомо хуже, чем труд коллектива высококлассных специалистов. Я взялся за написание своей реализации AlphaBlend функции не веря в то, что смогу победить ребят из майкрософта. Это был просто спортивный интерес, который, тем не менее, дал результат.

Но и это не всё. Дело в том, что в этой статье я привел код простого случая — когда исходная картинка смешивается с результирующей как есть. Но если вы читали документацию к функции AlphaBlend, то могли заметить, что эта функция умеет делать дополнительное умножение на константную альфу (передается через параметры). Я написал ssse3 реализацию и для этого случая. Интересен результат: AlphaBlend работает почти в 2 раза медленнее, если константная альфа не равна 255, т.е. требуется дополнительное умножение цвета. Моя же реализация деградирует в скорости всего на 4%, что тоже выгодно отличает ее от творения майкрософта.

Ссылки

Код в статье приведен только для ознакомления с самим принципом ssse3 оптимизации. Я не стал тут приводить значение используемых констант. Если вы захотите использовать оптимизированный AlphaBlend в своем проекте, вам придется добыть рабочий код непосредственно из исходных текстов Isotoxin’а (так называется моя разработка).

Репозиторий Isotoxin’а на гитхабе.
Непосредственно файл, в котором находится нужная функция тут.

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

Источник