Ambilight для тэга video

В некоторых топовых моделях телевизоров Philips есть такая прикольная штука, как Ambilight. По сути, это светодиодная подсветка телевизора, которая меняет цвет в зависимости от цвета картинки. Смотреть кино на таком телевизоре — одно удовольствие.

На флэше уже есть реализации такой подсветки, ну а чем мы — фронтовики — хуже? Дабы в очередной раз разобраться, на что способны современные браузеры, на свет появился очередной эксперимент:

Ambilight для тэга <video> (Firefox 3.5, Opera 10.5, Safari 4, Google Chrome 4)

Далее рассмотрим, как это было сделано.

Алгоритм

Прежде, чем начать что-то писать, нужно составить алгоритм, по которому будет работать наша подсветка.

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

Исходя из этого описания, нам нужно проделать следующее: определить цвет каждого диода для текущего кадра и отрисовать его свечение. Что ж, приступим.

Определяем цвет диода

Для удобства предположим, что в нашем «телевизоре» всего по 5 светодиодов с каждой стороны. Соответственно, нужно взять фрагмент кадра, разделить его на области по количеству диодов и найти усреднённый цвет в каждой области — это и будут цвета подсветки:

get-color

Чтобы получить изображение текущего видео-кадра, достаточно отрисовать его в <canvas> через методdrawImage():

var canvas = document.createElement('canvas'),
video = document.getElementsByTagName('video')[0],
ctx = canvas.getContext('2d');
 
// обязательно выставляем размер холста
canvas.width = video.width;
canvas.height = video.height;
 
// рисуем кадр
ctx.drawImage(video, 0, 0, video.width, video.height);

Текущий кадр получили, теперь нужно узнать, какого цвета пиксели сбоку изображения. Для этого воспользуемся методом getImageData():

/** Ширина области, которую будем анализировать */
var block_width = 50;
 
var pixels = ctx.getImageData(0, 0, block_width, canvas.height);

В объекте pixels есть свойство data, в котором содержатся цвета всех пикселей. Причём хранятся они в немного необычном формате: это массив RGBA-компонетнов всех пикселей. К примеру, чтобы узнать цвет и прозрачность первого пикселя, нужно взять первые 4 элемента массива data, второго пикселя — следующие 4 и так далее:

var pixel1 = {
r: pixels.data[0],
g: pixels.data[1],
b: pixels.data[2],
a: pixels.data[3]
};
 
var pixel2 = {
r: pixels.data[4],
g: pixels.data[5],
b: pixels.data[6],
a: pixels.data[7]
};

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

function getMidColors() {
var width = canvas.width,
height = canvas.height,
lamps = 5, //количество светодиодов
block_width = 50, // ширина анализируемой области
block_height = Math.ceil(height / lamps), // высота анализируемого блока
pxl = block_width * block_height * 4, // сколько всего RGBA-компонентов в одной области
result = [],
 
img_data = ctx.getImageData(0, 0, block_width, h),
total = img_data.data.length;
 
for (var i = 0; i < lamps; i++) {
var from = i * width * block_width;
result.push( calcMidColor(img_data.data, i * pxl, Math.min((i + 1) * pxl, total_pixels - 1)) );
}
 
return result;
}

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

function calcMidColor(data, from, to) {
var result = [0, 0, 0];
var total_pixels = (to - from) / 4;
 
for (var i = from; i <= to; i += 4) {
result[0] += data[i];
result[1] += data[i + 1];
result[2] += data[i + 2];
}
 
result[0] = Math.round(result[0] / total_pixels);
result[1] = Math.round(result[1] / total_pixels);
result[2] = Math.round(result[2] / total_pixels);
 
return result;
}

Итак, мы получили цвета для светодиодов, но они слишком тусклые: ведь диоды светят очень ярко чтобы добиться достаточного уровня свечения. Нужно увеличить яркость цветов, а также увеличить насыщенность, чтобы добавить глубины свечению. Для этих целей очень удобно пользоваться цветовой моделью HSV — hue, saturation, value, — достаточно домножить два последних компонента на некий коэффициент. Но цвета у нас хранятся в модели RGB, поэтому сначала конвертируем цвет в HSV, увеличиваем яркость и насыщенность, а затем обратно конвертируем в RGB (формулы конвертирования RGB→HSV и обратно легко находятся в интернетах):

function adjustColor(color) {
color = rgb2hsv(color);
color[1] = Math.min(100, color[1] * 1.4); // насыщенность
color[2] = Math.min(100, color[2] * 2.7); // яркость
return hsv2rgb(color);
}

Рисуем свечение

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

Градиент рисуется просто: сначала создаём его с помощью createLinearGradient(), а потом добавляем цвета через addColorStop() и отрисовываем его:

// для свечения создаём новый холст
var light_canvas = document.createElement('canvas'),
light_ctx = light_canvas.getContext('2d');
 
light_canvas.width = 200;
light_canvas.height = 200;
 
var midcolors = getMidColors(), // полчаем усреднённые цвета
 
grd = ctx.createLinearGradient(0, 0, 0, canvas.height); // градиент
 
for (var i = 0, il = midcolors.length; i < il; i++) {
grd.addColorStop(i / il, 'rgb(' + adjustColor(midcolors[i]).join(',') + ')');
}
 
// рисуем градиент
light_ctx.fillStyle = grd;
light_ctx.fillRect(0, 0, light_canvas.width, light_canvas.height);

Получим что-то вроде этого:

gradient

Маска

Маску мы нарисуем в фотошопе. Есть замечательный фильтр Lightning Effects (Filters→Render→ Lightning Effects…), который позволяет создавать источники света. Заливаем слой белым цветом и вызываем этот фильтр примерно с такими настройками:

lightning

Получим вот такое световое пятно:

spot

Меняем режим наложения на Lighten, дублируем, крутим, меняем масштаб, играемся с прозрачностью, правим уровни и получаем вот такой результат:
spot-grid

Так как изображение чёрно-белое, из него очень легко получить маску, где белый цвет будет прозрачным. И если эту маску наложить поверх градиента, то получим вполне себе симпатичное свечение:

result

Но самое главное — мы легко сможем менять внешний вид и интенсивность свечения, не прибегая к программированию.

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值