Adjust colors of your page based on the lighting of the room with HTML5, webrtc and a webcam

Most modern laptops automatically dim the screen when the light conditions in the room change. If there is a lot of light, the screen normally increases in brightness to make sure you can still see everything clearly. If the light conditions are turned down, the screen will also descrease in brightness. This isn't only pleasant on the eyes, but will also probably extend your battery life. But why not take this a step further and not only adjust the brightness of the screen, but also increase the readability of the web site in the user's browser?

In this article I'll show you how you can use input from the webcam, in a fully HTML5 compatible manner, to detect the luminance of the room. If you're in a brightly lit location, the browser will dynamically increase the contrast between the background and the fonts. If the lighting conditions change, you'll see this also automatically reflected in the background. If you want to directly look at the result look here.

With a bright room we see this:
bright
With a dark room we see this:
bright

For those of you that don't have a webcam to test with, I recorded a small movie that shows the effect we're aiming at:

What do you need to do for this:

  1. Allow access to the webcam
  2. At an interval get a screenshot from the webcam
  3. Calculate the luminance of the room
  4. Adjust the CSS for the background

The first couple of steps are easy, and you can find a more detailled explanation in one of my previous posts.

Allow access to the webcam

I tested the code in this article with the latest development build of chromium. To enable webcam access (through the webrtc APIs) you need to open chrome to the following page:
chrome webrtc
And there enable getUserMedia API. Restart the browser to be sure, and you have access to the getUserMedia operation that you can use to access the video stream (and in a later version also an audio stream).

At an interval get a screenshot from the webcam

To calculate the luminance of the room you need to take a snapshot from the video stream so that you can determine how bright it is in the room. This is also something I discussed in the same article, so I won't go into too much detail. You need to do the following:

  1. Output the videostream to a video element.
  2. Copy a snapshot from the video element to a canvas element.
  3. Get the content of the canvas as a set of bytes for further processing

It isn´t useful to show the video, so create a div with a video element, and use css to hide the div.

<!-- use an hidden video element -->
<div style="visibility: hidden; height: 0; width: 0">
    <video id="live" width="320" height="240" autoplay></video>
</div>

To connect to the webcam and stream the data to the webcam all you need to do is this:

    video = document.getElementById("live")
 
    navigator.webkitGetUserMedia("video",
            function (stream) {
                console.log(stream);
                video.src = webkitURL.createObjectURL(stream);
            },
            function (err) {
                console.log("Unable to get video stream!")
            }
    )

Now you need to capture a screenshot every couple of seconds. For this you just use the setInterval javascript function, and from there you can use the following to capture the current screen from the video:

  // create a dummy context
   var ctx = $('<canvas />', {width:'320', height:'240'})[0].getContext('2d');
   ctx.drawImage(video, 0, 0, 320, 240);
   var imgd = ctx.getImageData(0, 0, 320, 240);
    var pix = imgd.data;

The pix variable will now contain the bytes that make up the image. With this bytearray you can calculate the luminance of the picture.

Calculate the luminance of the room

Calculating the luminance of the picture isn't that hard. You check the brightness of each pixel and add them. The result is the total luminance of our picture. This function, combined with the capture part now looks like this:

 
     function calculateLuminance(w, h) {
 
        // draw the current image
        ctx.drawImage(video, 0, 0, w, h);
        var imgd = ctx.getImageData(0, 0, w, h);
        var pix = imgd.data;
 
        var totalL = 0;
        for (var i = 0, n = pix.length; i < n; i += 4) {
            // Red, Green and Blue have different influence on the total luminance
            totalL += pix[i  ] * .3 + pix[i + 1] * .59 + pix[i + 2] * .11;
        }
 
        return totalL;
    }

That leaves us just with the last part. Change the background based on the current luminance.

Adjust the CSS for the background

To increase or decrease the luminance I use the following function (courtesy of stackoverflow):

    // http://stackoverflow.com/questions/5560248/programmatically-lighten-or-darken-a-hex-color
            function LightenDarkenColor(col, amt) {
                var usePound = false;
                if (col[0] == "#") {
                    col = col.slice(1);
                    usePound = true;
                }
 
                var num = parseInt(col, 16);
 
                var r = (num >> 16) + amt;
 
                if (r > 255) r = 255;
                else if (r < 0) r = 0;
 
                var b = ((num >> 8) & 0x00FF) + amt;
 
                if (b > 255) b = 255;
                else if (b < 0) b = 0;
 
                var g = (num & 0x0000FF) + amt;
 
                if (g > 255) g = 255;
                else if (g < 0) g = 0;
 
                return (usePound ? "#" : "") + (g | (b << 8) | (r << 16)).toString(16);
            }

This function should be called from the setInterval javascript function which is executed every couple of seconds:

    // luminance for dark is around 300.000
    var lower = 300000;
    // luminance for bright is around 6.000.000, could be different per webcam
    var higher = 6400000;
 
    // base color that we change
    var baseColor = "666666";
 
    timer = setInterval(
            function () {
                var luminance = calculateLuminance(320, 240);
                // based on the luminance we need to set the background
                // color to a specific value. We do this by calculating
                // the required target offset
                var offsetFromCenter = ((luminance - lower) / (higher - lower)) * 100;
 
                // now we can increase the luminance of the background
                var targetColor = LightenDarkenColor(baseColor, offsetFromCenter);
 
                console.log(luminance);
 
                $("body").animate({
                    backgroundColor:"#" + targetColor
                }, 2000);
 
            }, 2000);

And that's it. Now every two seconds a screenshot is taken, the luminance of that screenshot is calculated, and using jquery animations the background color is adjusted.