1. content aware image resizing

    Note: The author of the demo, Stéphane Roucheray, is a member of the PIMS team. The demo was first posted on the Pims World Labs weblog.

    View the demo in Firefox 3.5.

    Content Aware Image Resizing is a way to re-target an image size without modifying its content ratio, in other words : non-linear image resizing. The algorithm was first explained by Shai Avidan and Ariel Shamir and published in 2007 (“Seam Carving for Content-Aware Image Resizing.”)

    Since then several excellent Open Source implementations have been released. For example, there is a plugin for The Gimp and CAIR a standalone application in C++.

    But thanks to Canvas and JavaScript it’s now possible to do this in the browser without a plugin.

    Since version 1.5, Firefox offers bitmap manipulation through the Canvas API. Version 3.5 introduced not only the fastest Firefox JavaScript engine ever, but also a new Canvas method – createImageData – providing a much more powerful environment.

    For this demo, a sub part of the Content Aware Image Resizing algorithm has been implemented. The width of the image can be reduced interactively without modifying its height. This implementation uses seam carving to re-size the image, subtracting the less visible vertical lines. It is a four step iterative algorithm. One iteration is one pixel width re-size. First an image is loaded into the Canvas context and then the iteration starts :

    1. A grayscale version of the image has to be calculated
    2. The edges of the image (Sobel convolution is used in our case) and its energy matrix is computed
    3. The seam of least energy (1 pixel vertical line from the bottom to the top of the energy matrix) is detected
    4. Then the pixel of the detected seam is removed from the original image and the result is re-injected as a source image to step 1

    Each of the previous steps stores a whole matrix of data at the source image size. While these matrices are not all images but actually artifacts of the algorithm, storing them in an ImageData object is more convenient than using simple Arrays. This is why the createImageData method of the Canvas context is used. One of the benefits of this process is to allow showing the intermediate computations made under the hood.

    This demo shows that’s possible to do more intelligent image resizing than just flattening an image’s pixels with CSS. Having computational and image manipulation capabilities directly in the browser opens up a new range of possibilities of how image data can be displayed to users. This is only one small demonstration of that.

  2. pushing pixels with canvas

    This post was written by Paul Rouget, who is a member of the Mozilla Evangelism team. Paul lives in Paris, France and is well known for some of his amazing work with open video on the web among other things.

    Canvas, at its most simple level, is an easy way to draw bitmap data into an HTML page. It has methods that allow you to draw rectangles, arcs, curves and other simple primitives. However, for some types of effects and drawing you need direct access to the pixels.

    In Firefox 3.5 we’ve added a new method to the canvas element – createImageData. The createImageData call is a convenience method to create a blank set of pixels for manipulation that can eventually be copied back onto a canvas.

    Since we’re talking about a single call we thought that it might be worth it to go through all of the calls that let you read, manipulate and update the pixels directly in a canvas and put createImageData in its full context.

    Retrieving Pixel Data

    You can’t directly manipulate pixels in a canvas. In order to make changes to the data in a canvas you first need to copy the data out, make changes, and copy the changed data back to the target canvas.

    The getImageData call lets you copy a rectangle of pixels out of a canvas. A call to get all of the pixel data out of a canvas looks like this:

    var canvas = document.getElementById('myCanvasElt');
    var ctx = canvas.getContext('2d');
    var canvasData = ctx.getImageData(0, 0, canvas.width, canvas.height);

    The canvasData object contains the pixel data. It has the following members:

    canvasData {
        width: unsigned long, // the width of the canvas
        height: unsigned long, // the height of the canvas
        data: CanvasPixelArray // the values of the pixels
    }

    The data is a flat array of values that has one value for each component in a pixel, organized left to right, top to bottom, with each pixel represented as four values in RGBA order.

    For example, in a 2×2 canvas, there would be 4 pixels represented with 16 values that look like this:

    0,0  0,1  1,0  1,1
    RGBA RGBA RGBA RGBA
    

    So you can calculate the length of that array with the following formula: width * height * 4.

    In a larger canvas if you wanted to know the value of a blue in a pixel at x = 10, y = 20 you would use the following code:

    var x = 10;
    var y = 10;
    var blue = canvasData.data[(y * width + x) * 4 + 2];
    

    Note that each RGB pixel has a value of 0..255 with the alpha bit being 0..255 with 0 being completely transparent and 255 fully opaque.

    Create a new set of pixels

    If you want to create a new matrix from scratch, just use the createImageData call which needs two arguments: the height and the width of the matrix.

    Note that the createImageData call does not copy pixels out of the existing canvas, it produces a blank matrix of pixels with the values set to transparent black (255,255,255,0).

    Here’s an example you want to create a set of pixels that fits the canvas size:

    var canvas = document.getElementById('myCanvasElt');
    var ctx = canvas.getContext('2d');
    var canvasData = ctx.createImageData(canvas.width, canvas.height);

    Note that this is the method that you should use to create pixel data. Previous versions of Firefox allowed you to create a canvasData object out of a simple JavaScript object and use it in later calls to update the canvas data. This call was added to maintain compatibility with WebKit which under the hood uses a specialized object instead of a generic JavaScript object.

    Update the pixels

    Once you have the canvasData object you can update the pixel values through the array. Here’s one example of how to walk through the array reading and updating the values.

    for (var x = 0; x < canvasData.width; x++)  {
        for (var y = 0; y < canvasData.height; y++)  {
     
            // Index of the pixel in the array
            var idx = (x + y * width) * 4;
     
            // If you want to know the values of the pixel
            var r = canvasData.data[idx + 0];
            var g = canvasData.data[idx + 1];
            var b = canvasData.data[idx + 2];
            var a = canvasData.data[idx + 3];
     
            //[...] do what you want with these values
     
            // If you want to update the values of the pixel
            canvasData.data[idx + 0] = ...; // Red channel
            canvasData.data[idx + 1] = ...; // Green channel
            canvasData.data[idx + 2] = ...; // Blue channel
            canvasData.data[idx + 3] = ...; // Alpha channel
        }
    }

    Update the canvas

    Now that you’ve got a set of pixels updated you can use the simple putImageData call. This call takes the canvasData object and the x,y location where you would like to draw the rectangle of pixel data into the canvas:

    var canvas = document.getElementById('myCanvasElt');
    var ctx = canvas.getContext('2d');
    var canvasData = ctx.putImageData(canvasData, 0, 0);

    Full example for getImageData

    Here is code that transforms a color image to a grey scale version of the image. You can also see a live version of this demo on Paul’s site.

    var canvas = document.getElementById('myCanvasElt');
    var ctx = canvas.getContext('2d');
    var canvasData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    for (var x = 0; x < canvasData.width; x++) {
        for (var y = 0; y < canvasData.height; y++) {
            // Index of the pixel in the array
            var idx = (x + y * canvas.width) * 4;
     
            // The RGB values
            var r = canvasData.data[idx + 0];
            var g = canvasData.data[idx + 1];
            var b = canvasData.data[idx + 2];
     
            // Update the values of the pixel;
            var gray = (r + g + b) / 3;
            canvasData.data[idx + 0] = gray;
            canvasData.data[idx + 1] = gray;
            canvasData.data[idx + 2] = gray;
        }
    }
    ctx.putImageData(canvasData, 0, 0);

    Full example for createImageData

    This bit of code will draw a fractal into a canvas. Once again, you can see a live demo of this code on Paul’s site.

    var canvas = document.getElementById('myCanvasElt');
    var ctx = canvas.getContext('2d');
    var canvasData = ctx.createImageData(canvas.width, canvas.height);
     
    // Mandelbrot
    function computeColor(x, y) {
        x = 2.5 * (x/canvas.width - 0.5);
        y = 2 * (y/canvas.height - 0.5);
        var x0 = x;
        var y0 = y;
     
        var iteration = 0;
        var max_iteration = 100;
     
        while (x * x + y * y <= 4 && iteration < max_iteration ) {
            var xtemp = x*x - y*y + x0;
            y = 2*x*y + y0;
            x = xtemp;
            iteration++;
        }
     
        return Math.round(255 * iteration / max_iteration);
    }
     
    for (var x = 0; x < canvasData.width; x++) {
        for (var y = 0; y < canvasData.height; y++) {
            var color = computeColor(x, y);
     
            // Index of the pixel in the array
            var idx = (x + y * canvas.width) * 4;
     
            // Update the values of the pixel;
            canvasData.data[idx + 0] = color;
            canvasData.data[idx + 1] = color;
            canvasData.data[idx + 2] = color;
            canvasData.data[idx + 3] = 255;
        }
    }
     
    ctx.putImageData(canvasData, 0, 0);

    More Documentation

    If you want to know more about Canvas, we strongly encourage you to browse the MDC documentation that we have for canvas:

    We hope that this was useful for everyone and puts the single call in its full context.