One thing we haven’t covered yet is pixel level access to canvas. The good news is that this is indeed possible. The bad news is that it isn’t quite as straightforward as it is in Flash. But it’s not horrible.
Getting pixel values is not done directly on a canvas, but through an object called ImageData. You can get an ImageData object from any rectangular area of a canvas with the function canvas.getImageData(x, y, w, h). ImageData has three properties: data, which is an array of ints, and width and height, which are just the width and height you passed in when you called getImageData. The values in the data array are arranged in groups of four. Each four ints represent the red, green, blue, and alpha value of a single pixel. They are arranged from the left to right and top to bottom of the image. Thus imageData.data[0] would be the red value (0-255) of the top left pixel. imageData.data[1] would be the green value of the same pixel. imageData.data[4] would be the red value of the second pixel in the first row, and so on. Since this is not the easiest thing to hold in your mind, it’s useful to make a utility function that lets you pass in an imageData object and x, y position and returns you a color. Like so:
[php lang=”JavaScript”]function getPixel(imageData, x, y) {
var r, g, b, a, offset = x * 4 + y * 4 * imageData.width;
r = imageData.data[offset];
g = imageData.data[offset + 1];
b = imageData.data[offset + 2];
a = imageData.data[offset + 3];
return “rgba(” + r + “,” + g + “,” + b + “,” + a + “)”;
}[/php]
The calculation, offset = x * 4 + y * 4 * imageData.width, gets the array offset value for the start of the pixel data of a given x, y pixel. This would be the red value. offset + 1, 2, and 3 would be the green, blue, and alpha values. We can assemble these into a color string and return that for direct use in a fillStyle or strokeStyle statement on a context. Or you could format the data differently depending on your needs.
With this, you can start to do some image processing or effects. Here’s a simple pixellate effect.
[php lang=”JavaScript”]$(function () {
var canvas, context, image, imageData;
canvas = $(“#canvas”)[0];
context = canvas.getContext(“2d”);
image = new Image();
image.src = “keith.jpg”;
image.onload = function() {
context.drawImage(image, 0, 0);
imageData = context.getImageData(0, 0, canvas.width, canvas.height);
}
$(“#canvas”).click(function(e) {
pixellate(5);
});
function pixellate(size) {
var x, y;
for(x = 0; x < canvas.width; x += size) {
for(y = 0; y < canvas.height; y += size) {
context.fillStyle = getPixel(imageData, x, y);
context.fillRect(x, y, size, size);
}
}
}
function getPixel(imageData, x, y) {
var r, g, b, a, offset = x * 4 + y * 4 * imageData.width;
r = imageData.data[offset];
g = imageData.data[offset + 1];
b = imageData.data[offset + 2];
a = imageData.data[offset + 3];
return "rgba(" + r + "," + g + "," + b + "," + a + ")";
}
});[/php]
First we create a new Image, load a bitmap into it, and when it's loaded, draw it to our canvas and get the imageData. An important thing to note here is a security restriction. If you draw an image to a context, and that image was not from the same domain as the script that is doing the drawing, getImageData will fail with a security error. In other words, if I was trying to load an image from, say, flickr, draw it in to the canvas and then get the image data, it would not work. This works because the image I'm using, keith.jpg, is on the same server as the rest of the html and js. If you want to do this with external images, you'll have to do some kind of proxy so that the image is served up from the same server. I also found that this fails for local files on the file system. So if you're testing this locally, you'll need to do it through a local web server.
Anyway, once we load the image, we listen for a click event on the canvas. At this, we call pixellate(5). This loops through the image getting every 5th pixel value and drawing a 5x5 rect with that color. Simple enough.
Try it here: http://www.bit-101.com/jscanvas/mar29.html
Tomorrow, we’ll cover the opposite function, putImageData.
Thank you again, Keith – this is a great series!
I’m doing all these exercises locally on my laptop, and this one works fine as well. Using FF 12.0 and your Wirelib 1.7.2.
I was able to make this work using an image from PlaceKittens with no problems.