Day 10 – Drawing rotated images into canvas

The room may still be spinning after the New Year celebrations but at least after this, you’ll be able to rotate your images to counter the effects!

You probably know we have this handy canvas context function drawImage :

context.drawImage(image, x, y);

where image is a DOM image (or another canvas), and x and y is the top left of the image. There are several other optional parameters, but I’m not using them today. (You can take a look at the canvas cheat sheet we posted yesterday if you’re curious.)

What’s not that obvious is how to draw a rotated image. You’d think there’d just be a nice rotation angle built in to drawImage, right? But no, instead, you have to rotate the entire co-ordinate system before you draw stuff!

Check this example out, we already have a canvas context set up, and we have logoImage loaded and ready to use :

context.drawImage(logoImage, 50, 35);

We’re drawing our logo from the top left at 50 pixels across and 35 pixels down which gives you this :

rotated image 1

All straightforward so far, but what if we rotate our co-ordinate system first?

context.rotate(0.5); 
context.drawImage(logoImage, 50, 35);

Whoa, what happened? Well, the entire co-ordinate system rotated by 0.5 radians (roughly 30º) around the top-left corner of the canvas before we drew the image. So if you think about it, 50 across and 35 down isn’t the same as place as it used to be.

So how do we rotate our image and keep it in the same place? Well before we call rotate we need to move the origin of our canvas to where we want to draw our image, and we can do this using context.translate(x,y).

context.translate(50, 35); 
context.drawImage(logoImage, 0, 0);

The translate function moves the entire co-ordinate system; context.translate(50, 35) moves the origin across by 50 and down 35. Now if we draw our image at 0,0 it’s actually at the new position of the origin :

Remember that context.rotate rotates around the origin. So if we move the origin to the 50,35, the image will be rotated around that point :

context.translate(50, 35); 
context.rotate(0.5); 
context.drawImage(logoImage, 0, 0);

All good so far, but what if we want to rotate around the centre of the image? Well we have to move the origin into the middle of where we want to draw the image, and then draw the image up and left half its width and height.

Confused? This example will make it clearer :

// move the origin to 50, 35   
context.translate(50, 35); 
 
// now move across and down half the 
// width and height of the image (which is 128 x 128)
context.translate(64, 64); 
 
// rotate around this point
context.rotate(0.5); 
 
// then draw the image back and up
context.drawImage(logoImage, -64, -64);

So, to re-iterate, we’re moving the origin to the middle of where we want the image to be drawn, then rotating around that point, and then drawing the image left and up in our newly rotated co-ordinate system.

The only thing left to understand is radians, but we explained all about that on our day 4 post. You did see that, right? :)

One final thing: at the end of all our translating and rotating, our co-ordinate system is all screwed up and there’s no way to get it back (don’t even think about resizing the canvas to reset it!). But we can save it first :

// save the context's co-ordinate system before 
// we screw with it
context.save(); 
 
// move the origin to 50, 35   
context.translate(50, 35); 
 
// now move across and down half the 
// width and height of the image (which is 128 x 128)
context.translate(64, 64); 
 
// rotate around this point
context.rotate(0.5); 
 
// then draw the image back and up
context.drawImage(logoImage, -64, -64); 
 
// and restore the co-ordinate system to its default
// top left origin with no rotation
context.restore();

Now we know how to draw centred and rotated images, so why not package this up into a nice function?

var TO_RADIANS = Math.PI/180; 
function drawRotatedImage(image, x, y, angle) { 
 
	// save the current co-ordinate system 
	// before we screw with it
	context.save(); 
 
	// move to the middle of where we want to draw our image
	context.translate(x, y);
 
	// rotate around that point, converting our 
	// angle from degrees to radians 
	context.rotate(angle * TO_RADIANS);
 
	// draw it up and to the left by half the width
	// and height of the image 
	context.drawImage(image, -(image.width/2), -(image.height/2));
 
	// and restore the co-ords to how they were when we began
	context.restore(); 
}

Check out this example to see it in action.

It’s worth taking the time to properly understand translation, rotation and saving and restoring the draw state stack – it’s incredibly powerful. And who knows, maybe the room will stop spinning in time for tomorrow’s post. :)