Canvas Rotating and Scaling Images Around a Particular Point

Transforming objects in your HTML5 Game or Experiment requires a bit of understanding regarding how they work. The behaviour and end result is not as intuitive as CSS3 transform functions like rotate() or scale(). I could quickly show you how to rotate or scale an image with its center as the origin, but it’s going to be super useful to take an indepth look at how the entire system works.

Canvas Coordinate System

You probably already know about the Cartesian coordinate system used by the 2D Rendering Context of HTML5 Canvas. Top left is the origin point with the coordinates (0,0). As you move to the right, the values increases on the X axis. Although, moving downwards increases values on the Y axis, unlike the standard cartesian plane where the values would decrease on the negative side of the number system.

What's the one thing every developer wants? More screens! Enhance your coding experience with an external monitor to increase screen real estate.

Translating Objects

Before getting into rotating and scaling, let’s first understand the concept of translating. Translating basically specifies the (x,y) position at which you want to render something.
The translate method on the 2D context allows you to move the origin point of the context. Not making sense ? Basically with this method you can specify a new (x,y) coordinates on the context that’ll act as (0,0) after calling the translate method.

Let me show you some code to make things uber clear.

var canvas = document.querySelector('canvas');
var ctx = canvas.getContext('2d');

ctx.fillStyle = 'red';
ctx.fillRect(0, 0, 100, 100);

We make a call to fillRect. Where is the rectangle going to render ? At (0,0) with a width of 100px and height of 100px. Yeh, you got that right!

Now, let’s add a bit more code that uses the translate method.

var canvas = document.querySelector('canvas');
var ctx = canvas.getContext('2d');

ctx.fillStyle = 'red';
ctx.fillRect(0, 0, 100, 100);
// We "move" the origin of our coordinate system
// to (100,100) of the current 2D Rendering Context
ctx.translate(100, 100);
ctx.fillStyle = 'blue';
ctx.fillRect(0, 0, 100, 100);

Our second fillRect call is exactly same as the first one, so it should render a blue square atop the red square, right ? NO! It will not. Using the translate method we shifted (0,0) – origin point – of the entire 2D rendering context to (100,100).

Demo time!


Gotcha!

Rotating Objects

Finally, after hours of reading, we’re onto the key part. Rotating Objects in Canvas. I am not going to deal with simple rectangles now. Instead we’ll rotate images drawn using the drawImage method on the canvas context. Why ? Because that’s what you’re going to use in 99% cases (games, etc.). You can always replace the drawImage called with fillRect or something else anytime you want.


Basically this is the code we use (refresh if you don’t see the image).

var img = new Image();
img.src = 'http://cssdeck.com/uploads/media/4/4M29duK.png';

ctx.drawImage(img, 100, 100);

First argument to drawImage is an image object whose src is set to the Image’s source. Second and Third arguments are the start X and Y positions to draw the image on the canvas. You can pass in some extra optional arguments, but life is just fine with what we have at the moment.

So, I want to rotate the image by 30 degrees. Let’s do it then.

var TO_RADIANS = Math.PI/180;

var img = new Image();
img.src = 'http://cssdeck.com/uploads/media/4/4M29duK.png';

ctx.rotate(30 * TO_RADIANS);
ctx.drawImage(img, 100, 100);

Take a look at the demo now:


So, we had our rotation done. The image rotated but a part of it is cut and its positions have changed. This might not exactly be what you or me wanted. If we had a div with that background image and rotated that with CSS3 transforms, then we might have gotten something according to our desire. Basically, the rotation did not occur around the center of the original position of the image (like a clock’s hand).

But Why?

That’s a smart question. The reason is, the rotate method rotates the entire 2D Rendering Context, and NOT just the following object/figure that is drawn. See the following figure:

Clear now ? Great!

Solving the Problem

Let’s work towards the solution now. We just learnt about translate sometime ago. Using translation with rotation will give us something like this:

Let’s use this knowledge by translating our rendering context (before rotating).

ctx.translate(100,100);
ctx.rotate(30 * TO_RADIANS);
ctx.drawImage(img, 0, 0);

Notice carefully. The second and third argument values to drawImage have been used as the first and second arguments to translate and they’ve been set to 0 themselves. This little trick can save time!


So first, we translated our rendering context to (100,100) – the old drawImage's start x/y positions. Next we rotate the context and finally we draw our image. But, this is still not what we want. We want to rotate the image exactly at it’s center. Well, that’s quite easy.

We just need to translate a bit more by about half the width (X axis) and height (Y axis) and then only rotate. Then we need to draw by that same amount (half of width and height) as negative start (x,y) offsets (to drawImage). Negating is very important since we are doing that extra translation.

ctx.save();
ctx.translate(100,100);
ctx.translate(img.width/2, img.height/2);
ctx.rotate(30 * TO_RADIANS);
ctx.drawImage(img, -img.width/2, -img.height/2);
ctx.restore();

Following is what we wanted, right ?


We made it! Notice those 2 extra method calls ctx.save() and ctx.restore() ? Using methods like translate and rotate, we changed our coordinate system. This means any further drawing after drawImage is going to occur on the altered coordinate system. This is not what we want. We need to reset back to our good old healthy system instead of this screwed version.

We do that by first saving the system using ctx.save() -> apply all transformations -> restore to our good old system by calling ctx.restore(). So cool!

Scaling Objects

The scale method scales (or resizes) the 2D Rendering Context (and not just the following drawings). See this code.

ctx.save();
ctx.scale(2,3);
ctx.drawImage(img, 100, 100);
ctx.restore();

We just multiplied the size of X axis by 2 and also made the Y dimension 3 times larger – ctx.scale(2,3). Due to this, the 100 that we pass as start_x to drawImage is now 2x the size, i.e, equal to 200. Similarly, 100 that we pass as start_y is 3x the size, i.e., equal to 300! So, not only did all our drawing’s scale up, but also the coordinates scaled. This is an important thing to remember.

Now the thing that we’ll want to do often is, scale our object (or image) in such a way, that it’s center (origin) is same as the one of the non-scaled (original) version. The trick to use here, is same as the one we used for rotation.

ctx.save();
ctx.translate(100,100);

var half_width = img.width/2;
var half_height = img.height/2;

ctx.translate(half_width, half_height);
ctx.scale(2,3);
ctx.drawImage(img, -half_width, -half_height);
ctx.restore();

And here’s the last demo:


Conclusion

Basically, now we know how to “control” the origin point for our Canvas transformations. Hope this is gonna help you out in several cases. Let’s have further discussions in the comments.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download

Author: Rishabh

Rishabh is a full stack web and mobile developer from India. Follow me on Twitter.

7 thoughts on “Canvas Rotating and Scaling Images Around a Particular Point”

  1. Great article, It helped me fix my collision detection issue on rotating shapes.

    Thanks for taking the time to write this!

  2. Very instructive article. But I start finding Canvas too complicated! There should be a method for rotating things more easily. The fact that one needs to read more than one article to understand a single rotation method is too much!

  3. Can you please tell me how to implement responsive canvas? I am able to do resizing when we refresh the page, but as we already has draw images on canvas and if we are changing portrait to landsape mode it does not redraw images using new hight and width. What should we need to do?

Leave a Reply to madhawa Cancel reply

Your email address will not be published. Required fields are marked *