Оффтоп Лена Очкова
6 998

Автоматическое определение цвета — опыт команды «Яндекса»

Продуктовый дизайнер «Яндекса» Михаил Аникин в своем блоге на Medium описал новый алгоритм подбора цветов для промо-карточек приложений в Yandex Launcher.

Редакция vc.ru публикует материал с разрешения автора.

Различные сервисы «Яндекса» работают с цветом для решения интерфейсных задач: выделение информационных блоков и объектных ответов, управление вниманием и создание визуальной иерархии.

Примеры использования алгоритмов подбора цветов в поиске и в «Яндекс.Музыке»

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

У нас в Yandex Launcher есть промо-карточки приложений: рейтинг, описание и кнопка «Установить». Это контекстные рекомендации — они открываются поверх списка приложений или в папке на рабочем столе.

Первоначальная реализация

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

  • неправильное определение цвета;
  • «грязные» цвета из-за усреднения;
  • тусклые кнопки, скучные карточки.

Примеры проблемных карточек

Чего на самом деле хотелось

Карточка должна была стать настоящим продолжением иконки, а цвета — сочными и яркими. Хотелось создать ощущение, что карточку бережно делали вручную, а не подсунули что-то небрежно сгенерированное автоматически.

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

В субботу я сдул пыль с редактора кода, расчехлил HTML5 и Canvas и стал придумывать. В понедельник пришел к команде с предложением.

Новый алгоритм определения цветов

Шаг 1. Берем иконку. Выкидываем белые, черные и прозрачные пиксели.

Исходная иконка → Квадрат из отфильтрованных пикселей

Шаг 2. Уменьшаем полученное изображение до размера 2 × 2 пикселя (с отключенным антиалиасингом). В результате получим четыре цвета иконки. Если исходная картинка однородная, они могут повторяться — ничего страшного.

Результат после второго шага. Исходная иконка → Цвета

У нас отключен антиалиасинг, чтобы цвета не смешивались, не становились «грязными».

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

Шаг 3. Почти все готово. Осталось совсем чуть-чуть: достаем полученные цвета, переводим в HSL, сортируем по светлоте (L). Красим карточку.

Светлая схема:

  • фон — самый светлый цвет;
  • кнопка — ближайший к светлому;
  • текст — самый темный.

Темная схема (если два и более цветов темные):

  • фон — самый темный цвет;
  • кнопка — ближайший к темному;
  • текст — самый светлый.

Применяя цвета, проверяем контрастность: разница Lightness между фоном и кнопкой ≥ 20; между фоном и текстом ≥ 60. Если не соответствует, корректируем.

Получившиеся карточки. Исходная иконка → Цвета → Карточка

И еще немного карточек для примера:

Результат

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

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

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

Доработанный алгоритм вошел в ближайший релиз. Отдельное спасибо руководителю группы разработки Yandex Launcher Диме Овчарову — за интерес, желание и терпение. Напоследок — больше примеров.

#Интерфейсы #яндекс #цвет #Yandex_Launcher

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

Написать
{ "author_name": "Лена Очкова", "author_type": "self", "tags": ["\u044f\u043d\u0434\u0435\u043a\u0441","\u0446\u0432\u0435\u0442","\u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u044b","\u0434\u0438\u0437\u0430\u0439\u043d","yandex_launcher"], "comments": 14, "likes": 15, "favorites": 1, "is_advertisement": false, "subsite_label": "flood", "id": 15639, "is_wide": true, "is_ugc": true, "date": "Fri, 20 May 2016 12:54:18 +0300" }
{ "id": 15639, "author_id": 32927, "diff_limit": 1000, "urls": {"diff":"\/comments\/15639\/get","add":"\/comments\/15639\/add","edit":"\/comments\/edit","remove":"\/admin\/comments\/remove","pin":"\/admin\/comments\/pin","get4edit":"\/comments\/get4edit","complain":"\/comments\/complain","load_more":"\/comments\/loading\/15639"}, "attach_limit": 2, "max_comment_text_length": 5000, "subsite_id": 199791 }

14 комментариев 14 комм.

Популярные

По порядку

Написать комментарий...
3

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

Ответить
11

Если результат хорош, всем плевать знаете вы 1001 алгоритм или нет

Ответить
2

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

Ответить
0

// действительно, почему бы и нет
- (float) random {
return 0.123456
}

Ответить
4

В Яндекс.Музыке используют другой подход: на фон ставится цвет, расположенный на противоположной стороне цветового круга.

Ответить
5

Мне нравится как реализовано в новом AppleTV. Они растягивают обложку фильма + размытие и немного ноиза. Очень ок.

Ответить
2
Ответить
2

В первом шаге есть проверка на достаточность отфильтрованных цветов. Если пикселей осталось слишком мало, белый и черный остаются в палитре (выкидываем только прозрачные).

Ответить
0

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

Ответить
2

А лежит где то в открытом доступе это все?
Вот у гугла либа отдельная — https://developer.android.com/reference/android/support/v7/graphics/Palette.html

Ответить
1

Уменьшаем полученное изображение до размера 2 × 2 пикселя

А каким образом? Можно поподробнее?

Ответить
1

Пусть лучше продуктовый дизайнер Яндекса расскажет нам что случилось с Яндекс-браузером )))

Ответить
0

Это же новый интерфейс Калипсо. Теперь с подсветкой активной вкладки.

Ответить
0

Это я понял, но подсветка это просто жесть, то она горит адски аж глаза режет, то её почти не заметно, да и сделана имхо этими квадратами как-то по-уродливому. Если фон тёмный + навести мышку слово пропадает (становится тёмным на тёмном)

Ответить
0
{ "page_type": "article" }

Прямой эфир

[ { "id": 1, "label": "100%×150_Branding_desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox_method": "createAdaptive", "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "ezfl" } } }, { "id": 2, "label": "1200х400", "provider": "adfox", "adaptive": [ "phone" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "ezfn" } } }, { "id": 3, "label": "240х200 _ТГБ_desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "fizc" } } }, { "id": 4, "label": "240х200_mobile", "provider": "adfox", "adaptive": [ "phone" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "flbq" } } }, { "id": 5, "label": "300x500_desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "ezfk" } } }, { "id": 6, "label": "1180х250_Interpool_баннер над комментариями_Desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "pp": "h", "ps": "bugf", "p2": "ffyh" } } }, { "id": 7, "label": "Article Footer 100%_desktop_mobile", "provider": "adfox", "adaptive": [ "desktop", "tablet", "phone" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "fjxb" } } }, { "id": 8, "label": "Fullscreen Desktop", "provider": "adfox", "adaptive": [ "desktop", "tablet" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "fjoh" } } }, { "id": 9, "label": "Fullscreen Mobile", "provider": "adfox", "adaptive": [ "phone" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "fjog" } } }, { "id": 10, "disable": true, "label": "Native Partner Desktop", "provider": "adfox", "adaptive": [ "desktop", "tablet" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fmyb" } } }, { "id": 11, "disable": true, "label": "Native Partner Mobile", "provider": "adfox", "adaptive": [ "phone" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fmyc" } } }, { "id": 12, "label": "Кнопка в шапке", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "p1": "bscsh", "p2": "fdhx" } } }, { "id": 13, "label": "DM InPage Video PartnerCode", "provider": "adfox", "adaptive": [ "desktop", "tablet", "phone" ], "adfox_method": "createAdaptive", "adfox": { "ownerId": 228129, "params": { "pp": "h", "ps": "bugf", "p2": "flvn" } } }, { "id": 14, "label": "Yandex context video banner", "provider": "yandex", "yandex": { "block_id": "VI-223676-0", "render_to": "inpage_VI-223676-0-1104503429", "adfox_url": "//ads.adfox.ru/228129/getCode?pp=h&ps=bugf&p2=fpjw&puid1=&puid2=&puid3=&puid4=&puid8=&puid9=&puid10=&puid21=&puid22=&puid31=&puid32=&puid33=&fmt=1&dl={REFERER}&pr=" } }, { "id": 15, "label": "Плашка на главной", "provider": "adfox", "adaptive": [ "desktop", "tablet", "phone" ], "adfox": { "ownerId": 228129, "params": { "p1": "byudx", "p2": "ftjf" } } }, { "id": 16, "label": "Кнопка в шапке мобайл", "provider": "adfox", "adaptive": [ "tablet", "phone" ], "adfox": { "ownerId": 228129, "params": { "p1": "byzqf", "p2": "ftwx" } } }, { "id": 17, "label": "Stratum Desktop", "provider": "adfox", "adaptive": [ "desktop" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "fzvb" } } }, { "id": 18, "label": "Stratum Mobile", "provider": "adfox", "adaptive": [ "tablet", "phone" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "fzvc" } } }, { "id": 19, "label": "Тизер на главной", "provider": "adfox", "adaptive": [ "desktop", "tablet", "phone" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "p1": "cbltd", "p2": "gazs" } } } ]
Хакеры смогли обойти двухфакторную
авторизацию с помощью уговоров
Подписаться на push-уведомления
{ "page_type": "default" }