Html canvas rotate larger image with aspect ratio - javascript

I have a code sample where I am adjusting a large image to a smaller size with the canvas matching the size of the image. There is a rotate right functionality that will rotate the image inside of the canvas which will adjust the canvas size. I almost have it perfectly working but the image is perfect size only when original or upside down. I can't get the height to be right ratio when the image is rotated on left or right. Here is my jsfiddle and code which is modified code from a different fiddle. Image is just used as test.
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var canvasWidth = 400;
var canvasHeight;
var degrees=0;
var image=document.createElement("img");
image.onload=function(){
canvas.width = canvasWidth;
canvasHeight = 400 / (image.width / image.height);
canvas.height = canvasHeight;
ctx.drawImage(image,0,0,canvas.width,canvas.height);
}
image.src="https://www.magezinepublishing.com/equipment/images/equipment/Lumix-DMCFZ330-5824/highres/Panasonic-Lumix-FZ330-Wide-P1010001_1438873612.jpg";
$("#clockwise").click(function(){
degrees+=90
if (degrees >= 360) degrees = 0;
if (degrees === 0 || degrees === 180 ) {
canvas.width = canvasWidth;
canvas.height = canvasHeight;
}
else {
// swap
canvas.width = canvasHeight;
canvas.height = canvasWidth;
}
ctx.save();
// you want to rotate around center of canvas
ctx.translate(canvas.width/2,canvas.height/2);
ctx.rotate(degrees*Math.PI/180);
ctx.drawImage(image, -canvas.width*0.5, -canvas.height*0.5, canvas.width, canvas.height);
ctx.restore();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id="canvas" ></canvas><br>
<button id="clockwise">Rotate right</button>
jsfiddle
Any help is appreciated.

Remember you rotate your image. You are changing the aspect ratio of the image when using the swapped canvas width/height in the image draw call. By your calculations, the original image, rotated, should already fit the canvas. The image you produce is also stretched because of this.
Use ctx.drawImage(image, -canvasWidth*0.5, -canvasHeight*0.5, canvasWidth, canvasHeight);.
Else said, you want to draw the same image, then rotate/translate it with canvas calls. But what you are doing is draw a different sized, stretched image when being rotated 90° or 270° and then rotate/translate that.

To rotate an image in 90deg steps.
Rather the use the translate and scale you can rotate the image by directly setting the tranformation matrix.
ctx.setTransform(a,b,c,d,e,f);
The arguments are as follows
a,b the vector describing the direction and size of a pixels x axis
c,d the vector describing the direction and size of a pixels y axis
e,f the coordinate of the origin (where 0,0 will be)
All these are in canvas pixel coordinates. The default is a = 1, b =0 x axis of a pixel is 1 pixel across, and 0 down, c = 0, d = 1 y axis 0 pixels across and one pixel down, e = 0, f = 0 origin at the top left.
To rotate the image 90 deg clockwise you want the xAxis to go down the canvas and the y axis to go from right to left. The origin is shifted to the top right of the canvas.
ctx.setTransform(
0,1, // x axis down
-1,0 // y axis from left to right
ctx.canvas.height, // origin x and y to top right
0,
)
ctx.drawimage(image,0,0);
As you are scaling by 0.5 this means the pixels will be half the size, and as you are drawing an image you want the origin so that the image fits the image.
// rotate image 90 deg and scale 0.5
ctx.setTransform(
0,0.5, // x axis down
-0.5,0 // y axis from left to right
image.height * 0.5, // origin x and y to top right
0,
)
ctx.drawimage(image,0,0);
You can do the same for each additional rotation
// rotate 180 scale 0.5
ctx.setTransform(-0.5,0,0,-0.5, image.width * 0.5,image.height * 0.5);
// rotate -90 scale 0.5
ctx.setTransform(0,-0.5,0.5,0, 0,image.width* 0.5);
The image dimensions for each rotation is as follows
// for 0deg and 180 deg rotation
canvas.width = image.width * 0.5;
canvas.width = image.height * 0.5;
// for 90deg and -90 deg rotation
canvas.width = image.height * 0.5;
canvas.width = image.width * 0.5;
To restore the transform to the default just set the transform to the identity matrix
ctx.setTransform(1,0,0,1,0,0);

Related

Image transform property not rotating image [duplicate]

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).
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.
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.
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 );

How do I rotate a html canvas shape by only using a transform?

I am wondering how you can rotate an image by only using the transform function. From my understanding this is not possible, since the only things you can do with transform are the following:
Horizontal scaling
Horizontal skewing
Vertical skewing
Vertical scaling
Horizontal moving
Vertical moving
Source: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Transformations
And I don't see how any of these would be able to rotate the shape, is this even possible. I assume it must be possible, since rotate is in-fact a type transformation.
2D transform basics
6 values as 3 vectors
The transform is a set of 6 numbers. The 6 numbers as 3 pairs represent the direction and scale of the x axis, the direction and scale of the y axis, and the position of the origin.
Default transform AKA Identity matrix
The default transform (called the identity matrix) has the values ctx.setTransform(1, 0, 0, 1, 0, 0) meaning that
the x axis is 1 transformed pixel per CSS pixel in the direction {x: 1, y: 0} left to right
the y axis is 1 transformed pixel per CSS pixel in the direction {x: 0, y: 1} top to bottom
the origin is at pixel location {x: 0, y: 0} top left corner
Scaling
If we scale the transform we increase the length of the first two vectors. To scale by 2 the transform is ctx.setTransform(2, 0, 0, 2, 0, 0);
the x axis is 1 transformed pixel for every 2 CSS pixel in the x direction {x: 2, y: 0} left to right
the y axis is 1 transformed pixel for every 2 CSS pixel in the y direction {x: 0, y: 2} top to bottom
the origin is still top left {x: 0, y: 0}
Rotate and translation
If we want to rotate by 90deg a square 256 by 256 image then the transform is ctx.setTransform(0, 1, -1, 0, 256, 0)
the x axis is 1 transformed pixel per CSS pixel down in the y direction {x: 0, y: 1}
the y axis is 1 transformed pixel per CSS pixel across in the negative x direction {x: -1, y: 0} right to left
the origin (where the image 0, 0 will be on the canvas) is {x: 256, y: 0}
Thus if we run
ctx.setTransform(0, 1, -1, 0, 256, 0);
ctx.drawImage(myImage, 0, 0, 256, 256); // will draw image rotated 90deg CW
We get a rotated image.
A vector
A vector is two values that have a x and y value. The vector defines a direction and length.
Create a rotated unit vector
To convert a direction to a vector we use sin and cos
const myDirection = angle;
const myDirectionAsRadians = angle * (Math.PI / 180); // convert angle to radians
const x = Math.cos(myDirectionAsRadians)
const y = Math.sin(myDirectionAsRadians)
If we set myDirection to 90 (deg) then x = 0 and y = 1 pointing down the canvas
Using sin and cos creates a vector in any direction. It has a special property in that its length is always 1. We call such a vector a Unit vector. You may sometimes see a vector being normalized. This converts a vector of any length to a unit vector. It is done by dividing the vector x and y by its length.
function normalize(vector) {
const length = Math.hypot(vector.x, vector.y);
vector.x /= length;
vector.y /= length;
}
NOTE a vector with zero length eg x: 0, y:0 can not be normalized. Not because it has no length (the length is 0) but because it has no direction.
Scale a rotated vector
We can define an angle and a scale
const myDirection = -90;
const myDirectionAsRadians = -90 * (Math.PI / 180); // -90 as radians
const myScale = 2;
const x = Math.cos(myDirectionAsRadians) * myScale
const y = Math.sin(myDirectionAsRadians) * myScale
Now for -90 deg the vector is x = 0 and y = -2 pointing up and two CSS pixels long.
Quick rotate vector 90deg CW
For a uniform scale and rotation (the image is always square) all we need is a single vector. For example from the above. x = 0 and y = -2 (pointing up) can be rotated 90 CW by swapping the two components and negating the new x. eg xx = -y and y = x to get xx = 2 and y = 0 2 CSS pixels from left two right. Thus we have the direction and scale of both the x and y axis. With the y axis always 90 CW from the x.
Using the transform to draw
Create rotated and scaled transform
To create a transform that rotates any angle and scales by any amount
function scaleAndRotate(scale, rotate) { // rotate is in radians
// get direction and length of x axis
const xAX = Math.cos(rotate) * scale;
const xAY = Math.sin(rotate) * scale;
// get direction and length of y axis that is 90 deg CW of x axis and same length
const [yAX, yAY] = [-xAY, xAX]; // swap and negate new x
// set the transform
ctx.setTransform(xAX, xAY, yAX, yAY, 0, 0);
}
Drawing an image
Lets create a function that will draw an image anywhere on the canvas that is rotated and scaled uniformly. We will use the center of the image as the reference point
function drawImageScaleRotate(img, x, y, scale, rotate) {
// define the direction and scale of x axis
const xAX = Math.cos(rotate) * scale;
const xAY = Math.sin(rotate) * scale;
// create the transform with yaxis at 90 CW of x axis and origin at x, y
ctx.setTransform(xAX, xAY, -xAY, xAX, x, y);
// Draw the image so that its center is at the new origin x, y
ctx.drawImage(img, -img.width / 2, -img.height / 2);
}
There is much more
When we set the transform with ctx.setTranform we replace the existing transform. This transform remains current. If we use ctx.transform, ctx.rotate, ctx.scale, ctx.translate the transforms are applied to the current transform, you build a transform in stages.
The transform functions are relatively expensive in terms of CPU cycles. That is way using sin and cos to build the matrix is much faster than using ctx.scale, ctx.rotate, ctx.translate to do the same thing starting from default.
Building transforms can become tricky as we need to keep track of what stage we are at.
We generally only use these function not to transform a single image (text, path, or what ever) but to create linked transforms.
For example a game object like a tank. The body of the tank is transformed (rotated and positioned) then the turret which is rotated with the body but has an additional independent rotation by using ctx.rotate. Full explanation is beyond the scope of this question.
The final function
From all this we can create a simplified function that will draw an image with its center at any location, that is uniformly scaled and rotated
function drawImageScaleRotate(img, x, y, scale, rotate) {
const xAX = Math.cos(rotate) * scale;
const xAY = Math.sin(rotate) * scale;
ctx.setTransform(xAX, xAY, -xAY, xAX, x, y);
ctx.drawImage(img, -img.width / 2, -img.height / 2);
}
To reset the transform to the default use ctx.resetTransform NOTE not fully supported yet or use ctx.setTransform(1,0,0,1,0,0);
Animated transforms faster than CSS + HTML or SVG
Using the above function is the 2nd fastest way to draw animated rotated scaled images, faster than CSS + HTML or SVG. You can literally fill the screen with animated images.
Demo
var w,h;
var image = new Image;
image.src = "https://i.stack.imgur.com/C7qq2.png?s=328&g=1";
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
canvas.style.position = "absolute";
canvas.style.top = "0px";
canvas.style.left = "0px";
document.body.appendChild(canvas);
const resize = () => { w = canvas.width = innerWidth; h = canvas.height = innerHeight;}
const rand = (min,max) => Math.random() * (max ?(max-min) : min) + (max ? min : 0);
const DO = (count,callback) => { while (count--) { callback(count) } }
resize();
addEventListener("resize",resize);
const sprites = [];
DO(500,()=>{
sprites.push({
xr : rand(w), yr : rand(h),
x : 0, y : 0, // actual position of sprite
r : rand(Math.PI * 2),
scale : rand(0.1,0.25),
dx : rand(-2,2), dy : rand(-2,2),
dr : rand(-0.2,0.2),
});
});
function drawImage(image, spr){
const xAX = Math.cos(spr.r) * spr.scale;
const xAY = Math.sin(spr.r) * spr.scale;
ctx.setTransform(xAX, xAY, -xAY, xAX, spr.x, spr.y);
ctx.drawImage(image, -image.width / 2, -image.height / 2);
}
function update(){
var ihM,iwM;
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(0,0,w,h);
if(image.complete){
var iw = image.width;
var ih = image.height;
for(var i = 0; i < sprites.length; i ++){
var spr = sprites[i];
spr.xr += spr.dx;
spr.yr += spr.dy;
spr.r += spr.dr;
// keeps images in canvas adds space to all sides so that image
// can move completely of the canvas befor warping to other side
// I do this to prevent images visualy popping in and out at edges
iwM = iw * spr.scale * 2 + w;
ihM = ih * spr.scale * 2 + h;
spr.x = ((spr.xr % iwM) + iwM) % iwM - iw * spr.scale;
spr.y = ((spr.yr % ihM) + ihM) % ihM - ih * spr.scale;
drawImage(image,spr);
}
}
requestAnimationFrame(update);
}
requestAnimationFrame(update);
If you are wondering which is the fastest way to draw animated content. That is via webGL. The above can draw 1000 scaled rotated images on most devices at a good frame rate. WebGL can easily draw 10000 (with extra features eg colored) in the same time.
You can rotate your image easily. You can specify angles by which you wants to apply rotation.
Source : https://www.w3schools.com/cssref/css3_pr_transform.asp
<style>
img.a {
transform: rotate(180deg);
}
</style>
<img class="a" src="https://picsum.photos/id/237/200/300"/>
use transform "rotate".and use it as below
p{
color:red;
font-size:12px;
text-align:center;
}
.rotate1{
transform:rotate(45deg);
margin-top:40px;
}
.rotate2{
transform:rotate(90deg);
margin-top:40px;
}
.rotate3{
transform:rotate(180deg);
margin-top:40px;
}
<p class="rotate1">ROTATE1</p>
<p class="rotate2">ROTATE2</p>
<p class="rotate3">ROTATE3</p>
You have ctx.rotate(radians) function.
Read below:
https://www.w3schools.com/tags/canvas_rotate.asp

Javascript Canvas rotate image in circle axis

I need help, I have been trying to rotate my image which is a pointer. with my code below it rotates but it rotates not as I want.
Code:
var c = document.getElementById("ctx");
var ctx = c.getContext("2d");
for (var d = 0; d < 360; d++) {
setTimeout(function (d) {
c.globalAlpha = 0.5;
ctx.clearRect(0, 0, c.width, c.height);
ctx.save();
ctx.translate(c.width / 2, c.height / 2);
ctx.rotate(d * Math.PI / 180);
ctx.drawImage(imageLoader.pointer, -imageLoader.pointer.width / 2, -imageLoader.pointer.height / 2);
ctx.restore();
}, 100 * d, d);
}
This code makes my image rotate weirdly I think it rotates on its own axis but I am not sure.
However I need a rotation something like this image.
I think this rotation is around a circle, i need something like this , can someone give me a hint or help me out?
I was trying to do it with a shape but its more difficult because i need to find the tangent and more geometric formulas to make it rotate like this.
I appreciate your time in advance, thanks.
The function to draw a rotated image rotating around a point on the canvas and offset to its own center of rotation.
// x,y is the location on the canvas that the image will rotate around
// cx,cy is the coordinates on the image that is rotated around
// angle is the amount of rotation in radians
function drawImage(image,x,y,cx,cy,angle){
ctx.setTransform(1,0,0,1,x,y); // set the rotation origin
ctx.rotate(angle); // rotate
ctx.drawImage(image,-cx,-cy); // draw image offset to put cx,cy at the point of rotation
ctx.setTransform(1,0,0,1,0,0); // restore the transform
}
So if your image is 50 by 100 pixels and you want the image to rotate about the point on it at 25, 80 (center near bottom) and that rotation point to be on the canvas at 200,200 then
drawImage(image,200,200,25,80,3); //rotate 3 radians
To do so in an animation.
// assumes image has loaded and ctx is the context of the canvas.
requestAnimationFrame(mainLoop); // starts the animation
function mainLoop(time){ // time is passed by requestAnimationFrame
ctx.clearRect(0,0,ctx.canvas.width,ctx.canvas.height); // clear
drawImage(image,200,200,25,80,(time / 5000) * Math.PI * 2); // rotate one every 5 seconds
requestAnimationFrame(mainLoop);
}
const image = new Image
image.src = "https://i.stack.imgur.com/C7qq2.png?s=328&g=1";
const ctx = myCanvas.getContext("2d");
function drawImage(image,x,y,cx,cy,angle){
ctx.setTransform(1,0,0,1,x,y); // set the rotation origin
ctx.rotate(angle); // rotate
ctx.drawImage(image,-cx,-cy); // draw image offset to put cx,cy at the point of rotation
ctx.setTransform(1,0,0,1,0,0); // restore the transform
}
// assumes image has loaded and ctx is the context of the canvas.
requestAnimationFrame(mainLoop); // starts the animation
function mainLoop(time){ // time is passed by requestAnimationFrame
ctx.clearRect(0,0,ctx.canvas.width,ctx.canvas.height); // clear
if(image.complete){
drawImage(image,250,250,image.width / 2,image.height * 0.8,(time / 5000) * Math.PI * 2); // rotate one every 5 seconds
}
requestAnimationFrame(mainLoop);
}
canvas {
border : 2px black solid;
}
<canvas id="myCanvas" width = 500 height = 500></canvas>

Issues in canvas image rotation

I've created a basic HTML5 image slider where images move from top to bottom in a canvas.
I want all the images rotated at angle of 5 degrees. When I tried it out there seems to be some
distortion to the canvas and the image is not properly rotated.
I've tried the method for rotation mentioned in the below post
How do I rotate a single object on an html 5 canvas?
Fiddle - http://jsfiddle.net/DS2Sb/
Code
this.createImage = function (image, width, height) {
var fbWallImageCanvas = document.createElement('canvas');
var fbWallImageCanvasContext = fbWallImageCanvas.getContext('2d');
fbWallImageCanvas.width = width;
fbWallImageCanvas.height = height;
fbWallImageCanvasContext.save();
fbWallImageCanvasContext.globalAlpha = 0.7;
this.rotateImage(image, 0, 0, width, height, 5, fbWallImageCanvasContext);
fbWallImageCanvasContext.drawImage(image, width, height);
fbWallImageCanvasContext.restore();
return fbWallImageCanvas;
};
this.rotateImage = function (image, x, y, width, height, angle, context) {
var radian = angle * Math.PI / 180;
context.translate(x + width / 2, y + height / 2);
context.rotate(radian);
context.drawImage(image, width / 2 * (-1), height / 2 * (-1), width, height);
context.rotate(radian * (-1));
context.translate((x + width / 2) * (-1), (y + height / 2) * (-1));
};
The distortion you see is due to the fact that a rotated image will only fit in a larger canvas. So what we see is a rectangle view on a rotated image.
The computations are not that easy to get things done properly, but instead of pre-computing the rotated image, you might rotate them just when you draw them, which lets you also change the angle whenever you want (and opacity also btw).
So i simplified createImage, so that it just stores the image in a canvas (drawing a canvas is faster than drawing an image) :
this.createImage = function(image , width, height) {
var fbWallImageCanvas = document.createElement('canvas');
fbWallImageCanvas.width = width;
fbWallImageCanvas.height = height;
var fbWallImageCanvasContext = fbWallImageCanvas.getContext('2d');
fbWallImageCanvasContext.drawImage(image,0,0);
return fbWallImageCanvas;
};
And i changed drawItem so it draws the image rotated :
this.drawItem = function(ct) {
var angle = 5;
var radian = angle * Math.PI/180;
ct.save();
ct.translate(this.x + this.width/2 , this.y + this.height/2);
ct.rotate(radian);
ct.globalAlpha = 0.7;
ct.drawImage(fbc, - this.width/2, -this.height/2 , this.width, this.height);
ct.restore();
this.animate();
};
You'll probably want to refactor this, but you see the idea.
fiddle is here :
http://jsfiddle.net/DS2Sb/1/
Here is a link to a small html5 tutorial I created a while ago:
https://bitbucket.org/Garlov/html5-sidescroller-game-source
And here is the rotate code:
// save old coordinate system
ctx.save();
// move to the middle of where we want to draw our image
ctx.translate(canvas.width/2, canvas.height-64);
// rotate around that point
ctx.rotate(0.02 * (playerPosition.x));
//draw playerImage
ctx.drawImage(playerImage, -playerImage.width/2, -playerImage.height/2);
//and restore coordniate system to default
ctx.restore();

HTML5 canvas drawImage with at an angle

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).
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.
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.
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 );

Categories

Resources