Посты с тэгом «html5»

  • 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

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

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

    UPD: как показал эксперимент, далеко не у всех нормально работает HD-видео (изначально размер ролика был 1280×544), снижение разрешения до 592×256 решило проблему.

    Метки: , , ,