I'm trying to do a few thing with canvas. First I have a user upload an image, if the image is larger than I want I need to scale it down. That part is working just fine. Recently we ran into an issue with iPhone users uploading images. These have orientation issues. I've figured out how to get the orientation extracted, my issue is what happens when I manipulate the image in the canvas.
This is what I need to do: Get the image, translate(), scale(), rotate(), translate() <- get it back to its original position, drawImage().
When I do that part of the image is off in the abyss.
if (dimensions[0] > 480 || dimensions[1] > 853) {
// Scale the image.
var horizontal = width > height;
if (horizontal) {
scaledHeight = 480;
scaleRatio = scaledHeight / height;
scaledWidth = width * scaleRatio;
} else {
scaledWidth = 640;
scaleRatio = scaledWidth / width;
scaledHeight = height * scaleRatio;
}
canvas['width'] = scaledWidth;
canvas['height'] = scaledHeight;
ctx['drawImage'](image, 0, 0, width, height, 0, 0, scaledWidth, scaledHeight);
/* Rotate Image */
orientation = 8; //manual orientation -> on the site we use loadImage to get the orientation
if(orientation != 1){
switch(orientation){
case 8:
case 6:
canvas.width = scaledHeight;
canvas.height = scaledWidth;
break;
}
var halfScaledWidth = scaledWidth/2;
var halfScaledheight = scaledHeight/2;
ctx.save(); //<- SAVE
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.translate(halfScaledWidth,halfScaledheight);
switch(orientation){
case 8: //rotate left
ctx.scale(scaleRatio,scaleRatio);
ctx.rotate(-90*Math.PI/180);
ctx.translate(-1380,-1055); // <-Manuial numbers
break;
case 3: //Flip upside down
ctx.scale(scaleRatio,scaleRatio);
ctx.rotate(180*Math.PI/180);
ctx.translate(-925,-595); //<-Manuial numbers
break;
case 6: //rotate right
ctx.scale(scaleRatio,scaleRatio);
ctx.rotate(90*Math.PI/180);
ctx.translate(-462,-130); //<-Manuial numbers
break;
}
//re-translate and draw image
//ctx.translate(-halfScaledWidth,-halfScaledheight);
ctx.drawImage(image,-halfScaledWidth, -halfScaledheight);
ctx.restore(); //<- RESTORE
}
/* Rotate Image */
}
I have orientation set manually so I can see how it looks in each position Im worried about. If its a portrait orientation I flip the canvas.
I've tried save() and restore(). I've tried translate(x,y) then translate(-x,-y).. My guess is that because of the scale the grid is off and x and y need to be multiplied. I tried doing that against the scaleRatio and still didn't work.
As you can see I manually set the translate back but that only works with the image size I am working with, so not a good solution at all!
Here is the code: JSFiddle If I do a normal rotate right it all works.
Thanks!
Transformations
For the easy answer if you are not interested in the how skip to the bottom where you will find an alternative approch to your problem. It is all commented. I have made a bit of a guess as to what you wanted.
If you are interested in what I consider a simpler way to use the 2D transformation functions read the rest.
Matrix Math
When you use translate, scale, and rotate via the canvas 2D API what you are doing is multiplying the existing matrix with one created with each function.
Basically when you do
the API creates a new rotation matrix and multiplies the existing matrix with it.
Unlike normal math multiplication matrix multiplication will change the result depending on what order you multiply. In normal math multiplication
A * B = B * A
but this does not hold true for matricesmA * mB != mB * mA
(Note the not equals)This becomes more problematic when you need to apply several different transformations.
Does not give the same result as
The order that you need to apply the tranforms depends on what you are trying to achieve. Using the API this way is very handy for complex linked animations. Unfortunately it is also a source of endless frustration if you are not aware of matrix math. It also forces many to use the
save
andrestore
functions to restore the default transformation, that in some situations can be very costly in GPU performance.setTransform()
We are in luck though as the 2D API also has the function
ctx.setTransform(a, b, c, d, e, f)
which is all you really should ever need. This function replaces the existing transform with the one supplied. Most of the documentation is rather vague as to the meaning of thea,b,c,d,e,f
but contained in them is the rotation, scale, and translation.One handy use of the function is to set the default transform rather than use save and restore.
I see this type of thing a lot. (The Example 1, referenced further down)
An easier way is to just drop the save and restores and reset the transform manually by setting it to the Identity matrix
Now then I am sure you are still wondering what are these numbers being passed to setTransform and what do they mean?
The easiest way to remember them is as 2 vectors and 1 coordinate. The two vectors describe the direction and scale of a single pixel, the coordinate is simply the x,y pixel location of the origin (the location that drawing at 0,0 will be on the canvas).
A Pixel and its axis
Imagine a single pixel, this is the abstract transformed pixel that can be scaled and rotated by the current transformation. It has two axis, X and Y. To describe each axis we need two numbers ( a vector) these describes the screen (untransformed) direction and scale of the top and left side of the pixel. So for a normal pixel that matches the screen pixels the X axis is across the top from left to right and is one pixel long. The vector is (
1,0
) one pixel across, no pixels down. For the Y axis that goes down the screen the vector is (0,1
) no pixels across, one pixel down. The origin is the top right screen pixel which is at coordinate (0,0
).Thus we get the Identity Matrix, the default matrix for the 2D API (and many other APIs) The X axis (
1,0
), Y axis (0,1
) and the origin (0,0
) which match the six arguments forsetTransform(1,0,0,1,0,0)
.Now say we want to scale the pixel up. All we do is increase the size of the X and Y Axis
setTransform(2,0,0,2,0,0)
is the same asscale(2,2)
(from the default transform) Our pixel's top is now two pixels long across the top and two pixels long down the left side. To scale downsetTransform(0.5,0,0,0.5,0,0)
our pixel is now half a pixel across and down.These two axis vectors (a,b) & (c,d) can point in any direction, are completely independent of each other , they don't have to be at 90 deg to each other so can skew the pixel, nor do they require that they be the same length so you can change the pixel aspect. The origin is also independent and is just the canvas absolute coordinates in pixels of the origin and can be set to anywhere on or off the canvas.
Now say we want to rotate the transform 90Deg clockwise, scale up both axes by 2 and position the origin at the center of the canvas. We want the X axis (top) of our pixel to be 2 pixels long and pointing down the screen. The vector is (
0,2
) 0 across and two down. We want the left side of our pixel to 2 long and point to the left of the screen (-2,0
) Negative two across and none down. And the origin at the center is (canvas.width / 2, canvas.height / 2
) to get the final matrix that issetTransform(0,2,-2,0,canvas.width / 2, canvas.height / 2)
Rotate the other way is
setTransform(0,-2,2,0,canvas.width / 2, canvas.height / 2)
Easy Rotate 90deg
You may notice that rotating 90 degrees is just swapping the vectors and changing a sign.
x,y
) rotated 90 degrees clockwise is (-y,x
).x,y
) rotated 90 degrees anti-clockwise is (y,-x
).Swap the x, and y and negate the y for clockwise or negate the x for the anticlockwise rotation.
For 180 it is starting at 0 deg vector (
1,0
)Or all in terms of just x and y
x,y
)-y,x
)-x,-y
)y,-x
)x,y
).This is a very handy attribute of a vector that we can exploit to simplify the creation of our transformation matrix. In most situations we do not want to skew our image thus we know that the Y axis is always 90Deg clockwise from the x axis. Now we only need to describe the x axis and by applying the 90deg rotation to that vector we have the y axis.
So the vars
x
andy
are the scale and direction of the top of our pixel (x axis),ox
,oy
are the location of the origin on the canvas (translation) .Now to create the transform is
Note that the y axis is at 90 degs to the x axis.
Trig and the Unit vector
All well and easy when the axis are aligned to the top and sides, how do you get the vector for a axis at an arbitrary angle such as is supplied by the argument for
ctx.rotate(angle)
For that we need a tiny bit of trig. The Math functionMath.cos(angle)
returns the x component of the angle, angle andMath.sin(angle)
gives us the Y component. For zero degcos(0) = 1
andsin(0) = 0
for 90 deg (Math.PI/2
radians)cos(PI/2) = 0
andsin(PI/2) = 1
.The beauty of using sin and cos is that the two numbers that we get for our vector always give us a vector that is 1 unit (pixel) long (this is called a normalised vector or a unit vector) thus cos(a)2 + sin(a)2 = 1
Why does this matter? because it makes scaling very easy. Assuming that we always keep the aspect square we only need one number for the scale. To scale a vector you simply multiply it by the scale
the vector x,y is now two units long.
Better than using save, restore, rotate, scale, translate... :(
Now put it all together to create a matrix with an arbitrary rotation, scale and translation (origin)
Now to apply that to the example (1) given above
And that is how you use setTransform to simplify transforming the canvas, rather than guessing, trial and error, scale, rotates, and translates back and forth within a sea of save and restores.
Using that to simplify your code
The answer
And now to your question
I am not entirely sure what you are after, I presume you a dont mind scaling the canvas to accommodate the image, that the image is always in the center and that the aspect remains the same. As the rotations are aligned to the screen I will set the transforms manualy
Try this one https://jsfiddle.net/uLdf4paL/2/. Your code is correct, but you have to change
orientation
variable when you try to rotate and scale image (if it is what you wanna get).