44

I am experimenting with animation in <canvas> and can't work out how to draw an image at an angle. The desired effect is a few images drawn as usual, with one image rotating slowly. (This image is not at the centre of the screen, if that makes any difference).

Matthew Crumley
  • 101,441
  • 24
  • 103
  • 129
Greg
  • 21,235
  • 17
  • 84
  • 107

3 Answers3

90

You need to modify the transformation matrix before drawing the image that you want rotated.

Assume image points to an HTMLImageElement object.

var x = canvas.width / 2;
var y = canvas.height / 2;
var width = image.width;
var height = image.height;

context.translate(x, y);
context.rotate(angleInRadians);
context.drawImage(image, -width / 2, -height / 2, width, height);
context.rotate(-angleInRadians);
context.translate(-x, -y);

The x, y coordinates is the center of the image on the canvas.

Jakub Wieczorek
  • 1,849
  • 13
  • 10
  • 9
    instead of rotating/translating at the end, you could do a `context.save()` and `context.restore()` before and after you do the transformations and drawing. – Matthew Crumley Sep 25 '10 at 15:53
  • 41
    While using save()/restore() may seem cleaner, it would be much more inefficient. The engine will have to store/restore all the context properties, not just the ones we're manipulating here. That matters in a sensitive code path. – Jakub Wieczorek Sep 25 '10 at 17:39
  • 1
    For another example, see this (duplicate) question and answer: [Dial of a Gauge using HTML5 Canvas - Rotate About Arbitrary Point](http://stackoverflow.com/questions/4649836/dial-of-a-gauge-using-html5-canvas-rotate-about-arbitrary-point/4650102#4650102). – Phrogz Jan 10 '11 at 20:52
  • 10
    @JakubWieczorek No, the engine could easily keep "dirty" flags on each context property and lazily save (and then restore) only the ones that get touched between the `save()` and the `restore()`. Hand-tuning this is premature optimization. – s4y Nov 29 '15 at 01:36
  • 1
    what are the performance implication for doing this for a game, ie every game object is animated and is an image? – Nikos Oct 27 '16 at 23:04
  • 2
    -1 Using `translate()` and `rotate()` to restore the transformation matrix is very bad advice. This will _not_ reliably restore the matrix to its original value due to rounding of floating-point operations. – Feuermurmel Aug 25 '21 at 16:04
  • 3
    @JakubWieczorek Do you have measured performance of these two approaches? I'd be surprised if two method calls (where `rotate()` needs to call two trigonometric functions) is faster than copying the whole transformation matrix (48 bytes, if implemented as 6 64-bit-floating point numbers). – Feuermurmel Aug 25 '21 at 16:07
19

It is interesting that the first solution worked for so many people, it didn't give the result I needed. In the end I had to do this:

ctx.save();
ctx.translate(positionX, positionY);
ctx.rotate(angle);
ctx.translate(-x,-y);
ctx.drawImage(image,0,0);
ctx.restore();

where (positionX, positionY) is the coordinates on the canvas that I want the image to be located at and (x, y) is the point on the image where I want the image to rotate.

unrealsoul007
  • 3,809
  • 1
  • 17
  • 33
PetrolHead
  • 390
  • 4
  • 7
15

I have written a function (based on Jakub's answer) that allows user to paint an image in a X,Y position based on a custom rotation in a custom rotation point:

function rotateAndPaintImage ( context, image, angleInRad , positionX, positionY, axisX, axisY ) {
  context.translate( positionX, positionY );
  context.rotate( angleInRad );
  context.drawImage( image, -axisX, -axisY );
  context.rotate( -angleInRad );
  context.translate( -positionX, -positionY );
}

Then you can call it like this:

var TO_RADIANS = Math.PI/180; 
ctx = document.getElementById("canvasDiv").getContext("2d");
var imgSprite = new Image();
imgSprite.src = "img/sprite.png";

// rotate 45º image "imgSprite", based on its rotation axis located at x=20,y=30 and draw it on context "ctx" of the canvas on coordinates x=200,y=100
rotateAndPaintImage ( ctx, imgSprite, 45*TO_RADIANS, 200, 100, 20, 30 );
Yaume
  • 198
  • 1
  • 6
  • 2
    -1 Using `translate()` and `rotate()` to restore the transformation matrix is very bad advice. This will _not_ reliably restore the matrix to its original value due to rounding of floating-point operations. – Feuermurmel Aug 25 '21 at 16:08