Mozilla

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.

17 comments

Comments are now closed.

  1. Pingback from 颠覆网络35天 ─ 使用Canvas操作像素 < MJiA on June 9th, 2009 at 01:51:

    […] 原文地址:http://hacks.mozilla.org/2009/06/pushing-pixels-with-canvas/ […]

  2. Pingback from 柏強的城市探險記: 顛覆網路 35 天 (1): Canvas 與像素操作 on June 10th, 2009 at 11:16:

    […] pushing pixels with canvas […]

  3. Pingback from Ajaxian » Animating SVG with Canvas and Burst on June 11th, 2009 at 05:57:

    […] Pushing pixels with Canvas […]

  4. Pingback from 谋智社区 » Blog Archives » 颠覆网络35天 ─ 使用Canvas操作像素 on July 3rd, 2009 at 03:20:

    […] 原文地址:http://hacks.mozilla.org/2009/06/pushing-pixels-with-canvas/ […]

  5. Franz wrote on November 11th, 2010 at 00:59:

    hi, just wanted to rant, that the CanvasPixelArray sucks, it’s not a real array (at least in webkit) it’s an array like object. but it misses all the cool js internal array functions. also it’s slow. nearly unuseable slow in firefox. don’t know why.

    1. David Bradbury wrote on July 18th, 2011 at 11:59:

      It works exactly how graphics buffers have worked for a long time. You point to the pixel you want based on the width, height, and bpp of the image, then grab the color values of that pixel. Manipulate as needed, then put it to the screen buffer. As far as it being slow goes, well, that will just take time.

      1. interior designer wrote on May 19th, 2012 at 20:56:

        i try, and i have no worked :(

        i think there are something wrongs, on my act

  6. Dwight Vietzke wrote on December 24th, 2010 at 13:10:

    Nice post. Thanks. A year later and it is still the best reference so far.

  7. Geil Ficken wrote on August 14th, 2012 at 08:44:

    Thanks so much, it´s a mess with the canvas stuff but now I made it thanks to this article.

  8. Antonio wrote on August 18th, 2012 at 09:43:

    Interesting article, but for me, this hack does not work unfortunately…

  9. Absensi Sidik Jari wrote on September 16th, 2012 at 22:41:

    I was interest this trick,
    but why do try so hard :(

  10. kulturystyka sklep wrote on September 26th, 2012 at 10:20:

    Nice post. Thanks. A year later and it is still the best reference so far.

  11. Peter wrote on October 26th, 2012 at 10:05:

    @Dwight Vietzke
    3 years later is still the best, thanks!. :-)

  12. Pick A Part wrote on November 13th, 2012 at 02:23:

    By now this should be 3D video on a 4K screen, I started doing some pixel manipulation on Firefox and Chrome, both work great, IE still stock on 1999, what a waist!

  13. Cris wrote on December 19th, 2012 at 03:13:

    I would like to know why in my code,I have applied a translate(x,y) firstly,and now my 0,0 point is in another place on the canvas.But when I execute:
    var imageData = context.putImageData(imageData, 0,0 );
    the pixels are painted in the original 0,0 coordinate,instead of in the new one.

  14. youjizz wrote on January 30th, 2013 at 03:54:

    i couldnt make it work eh,can the new version be the problem?i see this is olda article

  15. SEO Translator wrote on March 19th, 2013 at 13:09:

    I’ve tried it on Firefox 19.0.2 but can´t make it work… Is this no longer compatible?

Comments are closed for this article.