Pull to refresh

Делаем превью сайтов в стиле Yandex Браузера

Reading time 5 min
Views 24K
yandex browserПрошло уже почти два месяца как Yandex порадовал некоторых пользователей новым продуктом — Yandex Браузером. Несмотря на невероятную динамику развития продуктов в этой области (Chrome и Firefox), Яндексу удалось привнести в свой браузер ряд новых идей.

Из всех особенностей этого браузера больше всего меня зацепило их дизайнерское решение относительно изображений сайтов в «быстрых закладках» (Speed dial). Люди любят глазами и поэтому приятно видеть у себя в новом табе не пустую белую страницу, а красочные картинки. Беда только в том, что лично я, чаще всего, смотрю на подпись под этой картинкой или же на favicon, так как по скриншоту сайта бывает очень сложно его узнать. Эту проблему дизайнеры яндекса, на мой взгляд, решили очень элегантно. В данном посте мы посмотрим, как реализовать эту идею на клиентской стороне.



Суть идеи заключается в следующем:

  1. Получаем favicon сайта
  2. Определяем доминирующий в favicon цвет
  3. Рисуем прямоугольник с доминирующим цветом и вставляем в него favicon
  4. Для пущей привлекательности сверху накладывается градиент.

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

  1. Получить favicon.
  2. Получить доступ к значению каждого пикселя изображения.
  3. Определиться с алгоритмом определения доминирующего цвета.


Получение favicon


Для того чтобы получить favicon, можно либо написать на сервере некий обработчик, который по домену будет искать и возвращать favicon, либо можно подглядеть как это делает Яндекс браузер… А делает он это при помощи запроса на одноименный сервис яндекса. Например такой запрос:

GET favicon.yandex.net/favicon/habrahabr.ru

Вернет вот такую вот картинку:

image


Доступ к пикселям изображения


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

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

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

В связи с этим реализация такого превью сайтов чисто на клиентской стороне, увы, не получится. На самом деле раз уж нам все равно нужен сервер в качестве прокси для изображений, то мы могли бы перенести на сервер и вычисление доминирующего цвета, получая назад на страницу уже не иконку, а цвет. Однако то, как это реализовать на серверной стороне, не так интересно и сильно разнородно, так как в зависимости от используемого на сервере языка (Python, PHP, Java) реализация будет разная. Поэтому мы рассмотрим как это сделать на клиенте с помощью элемента canvas.

Алгоритм определения доминирующего цвета


Существует множество алгоритмов определения доминирующего цвета изображения. Общая идея этих алгоритмов следующая. Каждый пиксель изображения представляет собой четверку чисел R, G, B, A. Мы идем по всем пикселям и особым образом анализируем их составляющие:

// создаем элемент canvas
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
// отрисовываем в canvas наш favicon
ctx.drawImage( faviconImage, 0, 0, 18, 18);
// получаем данные о пикселях
var data = ctx.getImageData(0, 0, 18, 18);
// Обходим каждый пиксель
for ( i = 0; i<data.data.length; i=i+4 ) {
       rgb = {};
       rgb.r = data.data[i];
       rgb.g = data.data[i + 1];
       rgb.b = data.data[i + 2];       
}


В нашем случае при реализации следует учитывать две особенности:

I. У многих favicon-ов существуют прозрачные пиксели, которые при отрисовке в элемент canvas представляются в виде rgba( 0, 0, 0, 0 ). Так как мы игнорируем альфа-канал, то для нас эти пиксели будут выглядеть как черные (#000000), что не соответствует действительности. Чтобы исправить это, просто закрасим canvas в белый перед тем как отрисовывать на ней favicon.

// Заливаем canvas белым прямоугольником
ctx.fillStyle = 'rgb(255, 255, 255)';
ctx.fillRect(0, 0, 18, 18);
// Отрисовываем favicon
ctx.drawImage( faviconImage, 0, 0, 18, 18);


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

Я рассмотрю три различных алгоритма определения доминирующего цвета по возрастанию их сложности.

Алгоритм 1. Среднее значение цвета

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

image

Алгоритм 2. Евклидово расстояние

Данный алгоритм чуть более сложен и заключается в следующем. Каждый цвет представляет собой вектор в трехмерном пространстве (r, g, b). Мы проходим по каждому пикселю и считаем его расстояние до всех остальных пикселей (Евклидово расстояние между двумя векторами). Затем ищем тот пиксель, который находится ближе всех ко всем остальным. Цвет этого пикселя и есть наш искомый цвет. Здесь следует отметить, что данный алгоритм, в отличие от предыдущего, не создает новый цвет, а лишь выбирает из уже существующих в данном изображении.

image

Алгоритм 3. Метод кластеризации k-средних

Суть данного алгоритма заключается в следующем. Произвольно выбирается k пикселей (центров) изображения различного цвета. Проходим по всем остальным пикселям и относим каждый из них к одному из центров на основании их близости друг к другу (считаем Евклидово расстояние как и в алгоритме 2). Затем пересчитываем центры — устанавливаем для них значение равное среднему среди всех пикселей, отнесенных к нему (как в алгоритме 1). Вновь проходим по всем пикселям и распределяем их по новым центрам. Проделываем все это до тех пор, пока значение центров не перестанет изменятся. Искомым цветом будет значение центра с наибольшим количеством пикселей. Следует отметить, что данный метод используется для тех же целей в браузере Chrome. Результат его работы для k = 3 следующий:

image


Для k = 5:

image


(для остальных значений параметра k результаты не столь показательны)

Заключение


Вопрос о том какой из данных алгоритмов лучше — спорный.

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


P. S. Github-юзер static-lab предложил и реализовал еще один алгоритм «Усреднённое по YUV с контрастом», который тоже дает весьма неплохой результат:

image

Пример можно запустить у себя в браузере (favicons закодированы в Base64): DEMO
Исходники тут: github
Tags:
Hubs:
+36
Comments 28
Comments Comments 28

Articles