I'm trying to rotate an image and move it around the canvas using the arrow keys. The plan is to have the left and right keys control the rotation of the image, and the up-down key control the movement - a bit like a tank!
I can successfully rotate an image around a centre point and place it back where it should be in the canvas, but once I rotate it by, say 45 deg, I would like the up key to move it right, rotate 180 and the up-key moves it down the canvas etc. At the moment, I can rotate the image using left/right keys, but up/down keys are always up/down the canvas.
Do I somehow need to rotate the canvas coordinates by the same amount as the image?
This is what I have so far and is in my draw function…
ctx.save();
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.translate(T1.x + base_image.width/2, T1.y + base_image.height/2);
ctx.rotate(rotation * Math.PI/180);
ctx.translate(-(T1.x + base_image.width/2), -(T1.y + base_image.height/2));
ctx.drawImage(base_image, T1.x, T1.y);
ctx.stroke();
ctx.restore()
T1.x and T1.y are the x and y coordinates of the image.
Thanks!
Finally got it! The solution was to separate the rotation and the movement rather than trying to do it all using ctx.translate.
In my draw function called every 100 Hz:
ctx.save();
ctx.translate(T1.x + base_image.width/2, T1.y + base_image.height/2);
ctx.rotate(rotation*Math.PI/180);
ctx.drawImage(base_image, -base_image.width/2, -base_image.height/2);
ctx.restore();
ctx.stroke();
The left key is, for example, like:
rotation = rotation - 5;
Draw();
The up key is, for example, like:
var radians = (rotation*Math.PI/180)
T1.x += 4*Math.cos(radians);
T1.y += 4*Math.sin(radians);
Draw();
Note: For this to work, I had to change the default orientation of the image in paint by 45 deg.
The quickest way to draw a rotated, uniformly-scaled image on Edge, Chrome, and Firefox is
// When the image has loaded add the center offsets
image.onload = function(){
// … your code
this.centerX = -this.width/2;
this.centerY = -this.height/2;
}
// When rendering
// x,y position of image center
// scale the uniform scale
// rotate angle in radians starting at 0 and + clockwise
var xdx = Math.cos(rotate) * scale;
var xdy = Math.sin(rotate) * scale;
ctx.setTransform(xdx, xdy, -xdy, xdx, x, y);
ctx.drawImage(image,image.centerX,image.centerY)
The above method with the extra sin and cos and the two multiplications is significantly quicker on Chrome, slightly quicker on Firefox, and I forget the margin on Edge, but it was quicker than the next quickest method:
ctx.setTransform(scale, 0, 0, scale, x, y);
ctx.rotate(rotate);
ctx.drawImage(image, image.centerX, image.centerY);
Though I am leaving the canvas transform state as it is, you can continue using both methods without having to reset the canvas state. When you are done with all the rendering, to restore the current transform, use
ctx.setTransform(1, 0, 0, 1, 0, 0);
To save a tiny bit of time, each time you convert from degrees to radians, create a const DEG2RAD = Math.PI/180;. Then, you can convert with var radians = degrees*DEG2RAD; or save by typing const D2R = Math.PI/180; or call const oneDeg = Math.PI/180;.
Related
I was trying to get the green triangle to rotate about its center and orient itself towards the mouse position. I was able to accomplish this, and you can view the full code and result here:
https://codepen.io/Carpetfizz/project/editor/DQbEVe
Consider the following lines of code:
r = Math.atan2(mouseY - centerY, mouseX - centerX)
ctx.rotate(r + Math.PI/2)
I arbitrarily added Math.PI/2 to my angle calculation because without it, the rotations seemed to be 90 degrees off (by inspection). I want a better understanding of the coordinate system which atan2 is being calculated with respect to so I can justify the reason for offsetting the angle by 90 degrees (and hopefully simplify the code).
EDIT:
To my understanding, Math.atan2 is measuring the angle illustrated in blue. Shouldn't rotating both triangles that blue angle orient it towards the mouse mouse pointer (orange dot) ? Well - obviously not since it's the same angle and they are two different orientations, but I cannot seem to prove this to myself.
This is because of how the Math.atan2 works.
From MDN:
This is the counterclockwise angle, measured in radians, between the positive X axis, and the point (x, y).
In above figure, the positive X axis is the horizontal segment going from the junction to the right-most position.
To make it clearer, here is an interactive version of this diagram, where x, y values are converted to [-1 ~ 1] values.
const ctx = canvas.getContext('2d'),
w = canvas.width,
h = canvas.height,
radius = 0.3;
ctx.textAlign = 'center';
canvas.onmousemove = canvas.onclick = e => {
// offset mouse values so they are relative to the center of our canvas
draw(as(e.offsetX), as(e.offsetY));
}
draw(0, 0);
function draw(x, y) {
clear();
drawCross();
drawLineToPoint(x, y);
drawPoint(x, y);
const angle = Math.atan2(y, x);
drawAngle(angle);
writeAngle(angle);
}
function clear() {
ctx.clearRect(0, 0, w, h);
}
function drawCross() {
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(s(0), s(-1));
ctx.lineTo(s(0), s(1));
ctx.moveTo(s(-1), s(0));
ctx.lineTo(s(0), s(0));
ctx.strokeStyle = ctx.fillStyle = '#2e404f';
ctx.stroke();
// positive X axis
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(s(0), s(0));
ctx.lineTo(s(1), s(0));
ctx.stroke();
ctx.lineWidth = 1;
ctx.font = '20px/1 sans-serif';
ctx.fillText('+X', s(1) - 20, s(0) - 10);
}
function drawPoint(x, y) {
ctx.beginPath();
ctx.arc(s(x), s(y), 10, 0, Math.PI * 2);
ctx.fillStyle = 'red';
ctx.fill();
ctx.font = '12px/1 sans-serif';
ctx.fillText(`x: ${x.toFixed(2)} y: ${y.toFixed(2)}`, s(x), s(y) - 15);
}
function drawLineToPoint(x, y) {
ctx.beginPath();
ctx.moveTo(s(0), s(0));
ctx.lineTo(s(x), s(y));
ctx.strokeStyle = 'red';
ctx.setLineDash([5, 5]);
ctx.stroke();
ctx.setLineDash([0]);
}
function drawAngle(angle) {
ctx.beginPath();
ctx.moveTo(s(radius), s(0));
ctx.arc(s(0), s(0), radius * w / 2,
0, // 'arc' method also starts from positive X axis (3 o'clock)
angle,
true // Math.atan2 returns the anti-clockwise angle
);
ctx.strokeStyle = ctx.fillStyle = 'blue';
ctx.stroke();
ctx.font = '20px/1 sans-serif';
ctx.fillText('∂: ' + angle.toFixed(2), s(0), s(0));
}
// below methods will add the w / 2 offset
// because canvas coords set 0, 0 at top-left corner
// converts from [-1 ~ 1] to px
function s(value) {
return value * w / 2 + (w / 2);
}
// converts from px to [-1 ~ 1]
function as(value) {
return (value - w / 2) / (w / 2);
}
<canvas id="canvas" width="500" height="500"></canvas>
So now, if we go back to your image, it currently points to the top (positive Y axis), while the angle you just measured is realtive to the x axis, so it doesn't point where you intended.
Now we know the problem, the solution is quite easy:
either apply the + Math.PI / 2 offset to your angle like you did,
either modify your original image so that it points to the positive X axis directly.
The coordinate system on canvas works with 0° pointing right. This means anything you want to point "up" must be initially drawn right.
All you need to do in this case is to change this drawing:
to
pointing "up" 0°
and you can strip the math back to what you'd expect it to be.
var ctx = c.getContext("2d"), img = new Image;
img.onload = go; img.src = "https://i.stack.imgur.com/Yj9DU.jpg";
function draw(pos) {
var cx = c.width>>1,
cy = c.height>>1,
angle = Math.atan2(pos.y - cy, pos.x - cx);
ctx.setTransform(1,0,0,1,cx, cy);
ctx.rotate(angle);
ctx.drawImage(img, -img.width>>1, -img.height>>1);
}
function go() {
ctx.globalCompositeOperation = "copy";
window.onmousemove = function(e) {draw({x: e.clientX, y: e.clientY})}
}
html, body {margin:0;background:#ccc}
#c {background:#fff}
<canvas id=c width=600 height=600></canvas>
When you do arctangents in math class, you're generally dealing with an y-axis that increases going upwards. In most computer graphics systems, however, including canvas graphics, y increases going downward. [erroneous statement deleted]
Edit: I have to admit what I wrote before was wrong for two reasons:
A change in the direction of the axis would be compensated for by adding π, not π/2.
The canvas context rotate function rotates clockwise for positive angles, and that alone should compensate for the flip of the y-axis.
I played around with a copy of your code in Plunker, and now I realize the 90° rotation simply compensates for the starting orientation of the graphic image you're drawing. If the arrowhead pointed right to start with, instead of straight up, you wouldn't need to add π/2.
I encountered the same problem and was able to achieve the desired result with a following axis 'trick':
// Default usage (works fine if your image / shape points to the RIGHT)
let angle = Math.atan2(delta_y, delta_x);
// 'Tricky' usage (works fine if your image / shape points to the LEFT)
let angle = Math.atan2(delta_y, -delta_x);
// 'Tricky' usage (works fine if your image / shape points to the BOTTOM)
let angle = Math.atan2(delta_x, delta_y);
// 'Tricky' usage (works fine if your image / shape points to the TOP)
let angle = Math.atan2(delta_x, -delta_y);
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>
I am trying to create a canvas object which I can use to create an image from (using canvas.toDataURL()).
One of the key elements of this canvas, has to be the background gradient set using the following css:
background: -webkit-linear-gradient(-45deg, #1e5799 0%,#2989d8 36%,#207cca 71%,#7db9e8 100%);.
As you can see this is set using a certain angle (-45deg).
Is there any way for me to create this using canvas and also being able to create an image from this which includes this background?
This doesn't work when manually setting the css-background property, as toDataURL does not take into account any css. I have also looked at drawing it into the canvas myself, but ctx.createLinearGradient does not support drawing of angles.
How can I achieve a canvas which allows toDataURL which includes my desired background?
Grabbing the background of the canvas element will not work as it is not part of the canvas bitmap (2D context in this case).
You have to use the createLinearGradient for that. As you say, it does not support an angle directly, but creates a gradient using a line (x1,y1)-(x2,y2).
This means we can use a little trigonometry to produce the angle we want.
If you want to create a line at an angle just do:
var x2 = length * Math.cos(angle); // angle in radians
var y2 = length * Math.sin(angle); // angle in radians
Now you can use this with createLinearGradient:
var gr = ctx.createLinearGradient(0, 0, x2, y2);
Example
var ctx = document.querySelector("canvas").getContext("2d"),
angle = 45 * Math.PI / 180,
x2 = 300 * Math.cos(angle),
y2 = 300 * Math.sin(angle),
gr = ctx.createLinearGradient(0, 0, x2, y2);
gr.addColorStop(0, "black");
gr.addColorStop(1, "blue");
ctx.fillStyle = gr;
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
var uri = ctx.canvas.toDataURL();
console.log(uri);
<canvas></canvas>
hey guys i have a function that strokes a line based on the angle received from the user and also moves an image using some basic maths. the only problem is i am unable to rotate image based on that angle as if i put this inside animation frame loop it doesn't work .please help
function move()
{
var theCanvas=document.getElementById("canvas1");
var context=theCanvas.getContext("2d");
context.clearRect(0, 0, theCanvas.width, theCanvas.height );
// context.fillStyle = "#EEEEEE";
//context.fillRect(0, 0,theCanvas.width, theCanvas.height );
context.beginPath();
context.setLineDash([3,2]);
context.lineWidth=10;
context.strokeStyle="black";
context.moveTo(x1,y1);
context.lineTo(x2,y2);
context.stroke();
context.drawImage(srcImg,x1+x_fact,y1-100+y_fact);
x_fact=x_fact+0.5;
y_fact=m*x_fact+c;
requestAnimationFrame(move);
}
move();
now please suggest me a way to rotate the image on the angle input only once so it can face according to the path and move in it.
thank you in advance.
If you want to rotate the image by its corner use something like this:
... your other code here ...
var x = x1+x_fact, // for simplicity
y = y1-100+y_fact;
context.save(); // save state
context.translate(x, y); // translate to origin of rotation
context.rotate(angle); // rotate, provide angle in radians
context.drawImage(srcImg, 0, 0); // as we are translated we draw at [0,0]
context.restore(); // restore state removing transforms
If you want to rotate it by center simply translate, rotate and then translate back 50% of the image's width and height.
save/restore are relative expensive operations - you can simply reset transformation all together after each draw if you don't need transformations elsewhere:
context.setTransform(1, 0, 0, 1, 0, 0); // instead of restore(), remove save()
I am working on a program in Javascript while I tried to rotate my image I have drawn. I tried to search on google to find my answer but all I got was how to rotate the whole canvas. What I am searching for is a way to rotate just an image (think like this. I want to rotate a warrior dependent on the direction he is walking in). I tried many different codes but all went to the same, rotate the whole canvas.
Here is an example of what I used:
ctx.rotate(20*Math.PI/180);
and also:
var angle = 0; //init angle
images[0].onload = function () {
ctx.drawImage(images[0], 0, 0);
setInterval(function () {
ctx.save();
ctx.clearRect(-ctx.canvas.width / 2, -ctx.canvas.height / 2, ctx.canvas.width, ctx.canvas.height);
ctx.rotate(Math.PI / 180 * (angle += 10)); //rotating at 10 degrees interval..
ctx.drawImage(images[0], 0, 0);
ctx.translate(ctx.canvas.width / 2, ctx.canvas.height / 2);
ctx.restore();
}, 16);
}
please help me out
The rotating of the canvas is part of how to create the rotation relative to the image.
To keep the image's visible position the same and not move it, you have to center it first.
Without centering the image you will only acheive the visual effect of rotating the entire canvas.
You have to throw in a bit of math to get it to rotate around the center of the image.
ctx.save();
ctx.translate(x, y); /*X and Y of the image, center point of the image */
ctx.rotate((Math.PI / 180) * rotation); /*rotation is in degrees, so this converts it to rads */
ctx.drawImage(img, -(img.width/2), -(img.height/2)); /* Draw and center the image */
ctx.restore();