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

Продолжим обсуждение того, как развивался алгоритм.

Роллинг Наши собственные

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

Хотя может показаться необычным ожидать, что пользовательское изображение будет демонстрировать те же качества, ниже мы увидим, насколько дешево вычислить стоимость поиска размытия белого, черного (и даже серого).

Ограничения

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

Изображение, которое мы показываем на карте, в настоящее время имеет размер 50x50. Это также является хорошим размером для выборки, а также означает, что мы можем повторно использовать кешированные данные, когда изображение уже ранее было получено из Facebook Graph API (зависит от браузера — размеры передаются как параметры строки запроса).

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

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

Кроме того, у нас есть всего несколько секунд для обработки всех карточек на приборной панели, поэтому мы допускаем достаточно близкий ответ в интересах своевременности (эвристика).

По общему признанию, в настоящее время мы находимся около 3-секундной отметки. Это прямо на верхнем пределе того, где я бы хотел, чтобы страница была в идеале, поэтому улучшение времени загрузки будет задачей в будущем.

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

Обнаружение доминирующих цветов

Обновим нашу стратегию автоматизации брендинга сервисных карточек:

Исходным цветом для определенного бренда является наиболее доминирующий яркий цвет, который был выведен из изображения логотипа для этого бренда.

Итак, первый шаг — перебрать пиксельный буфер для изображения логотипа:

  • Мы извлекаем значение RGB для каждого пикселя
  • Если все три компонента цвета ≥ 250, мы считаем его белым.
  • Если все три компонента цвета равны == 0, мы считаем его черным.
  • Если интервал между R и G ≤ 10, а интервал между R и B ≤ 10, мы считаем его серым. Мы записываем оттенки серого в хэш-карту.
  • Если цвет не белый, черный или серый, мы считаем его ярким цветом. Мы записываем яркие цвета в отдельную хеш-карту.
  • Всякий раз, когда мы записываем цвет в хэш-карту, мы увеличиваем счетчик того, сколько раз мы видели этот цвет. Это позволяет нам определять наиболее доминирующий цвет в хэш-карте на лету, вместо того, чтобы позже перебирать заполненную хэш-карту. И хотя дальнейшая итерация не повлияет на сложность алгоритма — а объекты JavaScript хранятся в виде массивов во многих движках (поэтому их можно просмотреть за линейное время) — мы благодарны за любое время, которое мы можем сэкономить.

Определение исходного цвета бренда

Итак, теперь, когда мы собрали всю необходимую информацию об изображении, мы можем приступить к выводу:

  • Если у нас есть доминирующий яркий цвет, используйте его, если он составляет не менее 10 % всех ярких пикселей изображения логотипа (см. Ошибку Instagram ниже).
  • Если у нас нет доминирующего яркого цвета, но мы нашли ярко окрашенные пиксели, аппроксимируйте доминирующий яркий цвет для исходного цвета (поясняется ниже).
  • В противном случае используйте доминирующий серый цвет для цвета семян, если он доступен.
  • В крайнем случае мы просто используем черный. Раньше этот шаг был черным, если счет черных был выше, чем счет белых, и наоборот… но на практике черный выглядел лучше. Как вы увидите — и этого следует ожидать, имея дело с внешним видом и поведением, — существует достаточное количество прерогативы и субъективного влияния на формирование кода. Существует возможность сплит-тестирования для проверки этих предположений в будущем.

Приближение доминирующих цветов

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

Однако проблема с цветовым пространством из миллионов цветов заключается в том, что ваши данные содержат разные триплеты RGB, которые отображаются незаметно для невооруженного глаза.

Обнаружение подобных сходств в RGB — это хорошо заданный вопрос (Я даже сам задавался им в славные дни 2012 года). Вывод часто состоит в том, чтобы использовать другой формат, более подходящий для работы.

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

Хотя стоимость преобразования из RGB в HSL не бесплатна, она имеет решающее значение для текущей реализации. Это лишь одна из многих возможностей использования мемоизации для повышения производительности, когда обрабатываемые наборы данных пикселей отличаются друг от друга, но пересекаются с точки зрения составляющих цветов.

Ошибка Instagram

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

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

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

Поощрение столкновений

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

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

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

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

В конце этого процесса мы выбираем победителя (наиболее часто встречающийся приблизительный оттенок) и вычисляем доминирующий цвет как:

hue := winner.hueSum / winner.bucketSize
saturation := winner.saturationSum / winner.bucketSize
luminance := winner.luminanceSum / winner.bucketSize

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

Генерация темы из исходного цвета

Тема состоит из трех частей:

  • цвет фона (также известный как исходный цвет)
  • граница цвет
  • цвет текста

Обратите внимание, что тема нето же самое, что палитра! Последний представляет собой набор цветов, полученных из цвета фона темы в данном конкретном случае. Да, сначала немного запутанно! ¯\_(ツ)_/¯

У нас уже есть цвет фона, так что сложного в двух других частях темы? Одним словом, контраст.

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

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

Контраст как функция яркости

Чтобы определить лучшие цвета текста и границ, код делает следующее:

  • Примените цвет фона к адаптированной версии этого уравнения, чтобы получить значение яркости. Это значение яркости находится в области RGB и находится в диапазоне 0–255 (2⁸-1).
  • Если это значение яркости больше 127,5 (половина), мы ближе к белому, чем к черному.
  • Если ближе к белому, установите цвет текста на черный. Установите цвет границы следующим значением HSL:
hue := bgc.hue < ΔH ? -ΔH : ΔH
saturation := bgc.saturation < ΔS ? -ΔS : ΔS
luminance := bgc.luminance — ΔL
  • Если ближе к черному, установите цвет текста на белый. Установите цвет границы аналогичным образом, но поменяв местами знаки:
hue := bgc.hue < ΔH ? ΔH : -ΔH
saturation := bgc.saturation < ΔS ? ΔS : -ΔS
luminance := bgc.luminance < ΔL ? ΔL : -ΔL

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

Дельты — это просто скучные старые константы, представляющие корректировки, применяемые к каждому цветовому компоненту соответственно:

Обратите внимание: любые дизайнеры становятся белыми из-за того, что макеты оригинального дизайна мутируют из-за «вольности инженера» (!) — вы абсолютно правы, но все заинтересованные стороны были довольны таким подходом!

Создание палитры

Это становится самой простой и произвольной частью всего алгоритма:

  • Размер палитры, которую мы выводим, в настоящее время жестко запрограммирован на 5 цветов HSL, по одному для каждого сегмента в дизайне карты:

  • Средний цвет (по индексу floor(n / 2) массива) всегда является начальным цветом.
  • Другие цвета получаются путем применения настраиваемых констант к исходному цвету (в допустимом диапазоне), так что результирующая палитра остается комплементарной (опять же, на основе вкуса).

Вот где HSL действительно сияет. Суммируя достаточно малые константы с каждым цветовым компонентом, мы гарантируем ощутимо похожий результирующий цвет HSL. Что-то, что мы бы (и сделали) изо всех сил пытались сделать в таких форматах, как шестнадцатеричный или RGB.

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

На этом мы рассмотрели, как реализован алгоритм. В третьей части этой статьи я расскажу о его практическом применении в контексте, а также о некоторых препятствиях, которые нужно решать вместе с ним!