Some high-end Philips TV sets have a cool feature, . Basically, it is LED lighting that changes its color dynamically, depending on the television picture's color. It is such a pleasure to watch movies on an Ambilight-enabled TV!
There are some implementations of such lighting in Adobe Flash. Why can't we, web masters, do the same thing using scripts? It was another opportunity for me to check out what state-of-the-art web browsers can do, so I've made the following thing:
(Firefox 3.5, Opera 10.5, Safari 4, Google Chrome 4)
Let's take a look at how it was done
Algorithm
Before writing the script for Ambient-like lighting, we need to make an algorithm for that.
The real Ambient lighting works as follows: There are many super-bright LEDs on the TV set's back panel, and they emit light of different colors. The color emitted by each LED approximates the color of the television picture area in its neighborhood, so when the picture changes, the LEDs smoothly change their colors too.
Therefore, we should do the following: determine each LED's color for the current picture frame and paint the lighting. Let's start, shall we.
Determining the LED color
To make the task easier, we'll assume that there are only 5 LEDs on each side of our "TV set". So we take a part of the picture frame, divide it into several areas (each corresponding to one LED) and find each area's average color, that is, the lighting colors.
To get the current picture frame, we just need to draw it in <canvas> using the drawImage() method:
var canvas = document.createElement('canvas'),
video = document.getElementsByTagName('video')[0],
ctx = canvas.getContext('2d');
// setting the canvas size (remember to do it!)
canvas.width = video.width;
canvas.height = video.height;
// drawing the picture frame
ctx.drawImage(video, 0, 0, video.width, video.height);
We've got the current frame, and now we need to check the color of each pixel at the picture's side. To do that, we use the getImageData() method:
/** Width of the analyzed area */
var block_width = 50;
var pixels = ctx.getImageData(0, 0, block_width, canvas.height);
The pixels object has the data property, which contains the colors of all pixels, though in a somewhat odd format: an array of RGBA components of all pixels. For example, the first 4 elements of the data array give us the color and transparency of the first pixel; the next 4 elements, the same of the second pixel; and so on:
We should divide all the pixels into 5 groups (one group per LED) and analyze each group, one after another:
function getMidColors() {
var width = canvas.width,
height = canvas.height,
lamps = 5, // number of LEDs
block_width = 50, // width of the analyzed area
block_height = Math.ceil(height / lamps), // height of the analyzed block
pxl = block_width * block_height * 4, // number of RGBA components in one area
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;
}
In this function, we are simply going block by block and calculating the average color for each of them, using the calcMidColor() function. We don't have to use sophisticated formulas to calculate the average color since it's enough to get the arithmetic mean for each color component:
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;
}
We've got the LED colors, but they are too dim. Keep in mind that the LEDs must be very bright, otherwise we don't get sufficient light intensity. So we must crank up the brightness of those colors, and do the same for the saturation (to add depth to the lighting). The HSV (hue, saturation, value) color model is very handy for that purpose — just multiply the last two components by some factor, and you have what you need. However, our colors are stored as RGB, so first we must convert them to HSV, then increase brightness and saturation, and then convert everything back to RGB (you can easily google the formulas for RGB>HSV and HSV>RGB conversion):
LEDs are omnidirectional light sources. It is best to use radial gradients to draw them, one gradient per LED. But to get really good visuals, we'd have to do a lot of very complex calculations as we'd need to consider each LED's position and diameter, light attenuation, mixing of adjacent colors, and so on. So let's cheat a little bit: draw a linear gradient, and then overlay a special mask to make a realistic lighting.
It's easy to draw a gradient: first make it using createLinearGradient(), then add colors using addColorStop() and draw it:
// creating a new canvas for lighting
var light_canvas = document.createElement('canvas'),
light_ctx = light_canvas.getContext('2d');
light_canvas.width = 200;
light_canvas.height = 200;
var midcolors = getMidColors(), // getting average colors
grd = ctx.createLinearGradient(0, 0, 0, canvas.height); // gradient
for (var i = 0, il = midcolors.length; i < il; i++) {
grd.addColorStop(i / il, 'rgb(' + adjustColor(midcolors[i]).join(',') + ')');
}
// drawing gradient
light_ctx.fillStyle = grd;
light_ctx.fillRect(0, 0, light_canvas.width, light_canvas.height);
The result looks like that:
Mask
To make the mask, we'll use Adobe Photoshop. There is a wonderful filter, Lightning Effects (Filters→Render→ Lightning Effects...), which lets you to create light sources. We flood the layer with white color and then call the filter with settings like that:
We've made this light spot:
Now we change the blending mode to Lighten, duplicate, rotate, change the scale, play a little bit with transparence, adjust the levels, and here's what we've got:
The picture is black-and-white, so it's very easy to make a mask with transparent white color. By laying the mask over the gradient, we get a cute enough lighting:
But the most important thing is that we can easily change the lighting's appearance and intensity even without using more programming.
The left-side lighting is ready — now we must do the same for the right side, add smooth changing of lighting, and code a controller to update the lighting at some interval. Describing the whole process would be too long and tedious, so better take a look at the .
As some people reported that the Ambient-like lighting didn't work well the initial HD video (1280 by 544 pixels), I reduced the video's resolution to 592 by 256 pixels, and now everything should be fine.
To post a comment, you must be registered and logged in. Login or Register
Welcome to Splashnology
Splashnology is an exciting new platform for a IT community dedicated to Web Technology, Web 2.0 culture, reviewing new Internet products and services and of course source for news in social and digital media. Anyone — including you — can become an author at Splashnology and post things that might be of interest to our community in personal or general blogs. Read About Us
We are looking for writers. If you are interested in writing exclusive articles for Splashnology please contact us. Also you can submit any article into your personal blog.
Comments (1)
RSS Collapse / Expandflaim
To post a comment, you must be registered and logged in. Login or Register