animating a rotated image in canvas - javascript

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()

Related

How to draw lines unaffected by scaling and zooming

I'm trying to build an app where user can add various objects (rectangles, circles) and he can use mouse wheel to zoom-in and zoom-out.
For this zooming I set up event handler like this:
TheCanvas.on('mouse:wheel', function(options){
var p = new fabric.Point(
options.e.clientX,
options.e.clientY
);
var direction = (options.e.deltaY > 0) ? 0.9 : 1.1;
var newZoom = TheCanvas.getZoom() * direction;
// restrict too big/small zoom here:
if ((newZoom > 50) || (newZoom < 0.7)) return false;
TheCanvas.zoomToPoint( p, newZoom );
}
Everything worked fine until now. Now I want to draw a crosshair over all objects on the canvas. Something like this:
So I made my own custom object like:
CrossHairClass = fabric.util.createClass(fabric.Object, {
strokeDashArray: [1,2], // I want lines to be dashed
........
My problem is:
When user zooms with the mouse wheel, my cross-hair lines zoom their thickness too and also small dashes get bigger. But I don't want that. I want my cross-hair lines be a "hair" lines = ideally 1 pixel thick all the time regardless zoom factor of the canvas. And fine dashed line too.
Render function of my Class:
_render: function (ctx) {
// I tried it like this
var zoom = TheCanvas.getZoom();
var scale = (1/zoom) * 3.333; // with this scale it visually looked the best
// I have to scale it in X and Y while I want small dashes to stay small and also thickness of the line to stay "hair-line"
this.scaleX = this.scaleY = scale;
this.width = CROSSHAIR_SIZE / scale; // my constant from elsewhere
ctx.lineWidth = 1;
ctx.beginPath();
// this example is for horizontal line only
ctx.moveTo(-this.width / 2, 0);
ctx.lineTo(this.width / 2, 0);
this._renderStroke(ctx);
}
I tried various combinations of multiplying or dividing by scale factor or zoom factor but if I finally had lines thin, I couldn't keep their size, which must be constant (in pixels) regardless of canvas zoom. Please help.
P.S.: now I got an idea. Maybe I should create another canvas, over my current canvas and draw this crosshair on the upper canvas, which will not zoom?
EDIT 1
Based on the answer from #andreabogazzi I tried various approaches, but this finally worked out! Thanks! :)
_render: function (ctx) {
var zoom = TheCanvas.getZoom();
// ctx.save(); // this made no difference
// ctx.setTransform(1/zoom, 0, 0, 1/zoom, 0, 0); // this didn't work
this.setTransformMatrix([1/zoom, 0, 0, 1/zoom, 0, 0]);
ctx.strokStyle = 'red';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(-this.widthHalf, 0); // widthHalf computed elsewhere
ctx.lineTo(this.widthHalf, 0);
this._renderStroke(ctx); // I use this instead of ctx.stroke() while this ensures my line is still nicely dashed
// ctx.restore(); // this made no difference
}
Since you created a custom class, you have to invert the zoom of your canvas before drawing.
On the _render function of your subclass, since you should be positioned in the center of your crosshair, apply a transform matrix of scale type, with scale factor of 1/zoomLevel and everything should work.
I would say the correct way is:
_render: function (ctx) {
var zoom = TheCanvas.getZoom();
ctx.save(); // this is done anyway but if you add custom ctx transform is good practice to wrap it in a save/restore couple
ctx.transform(1/zoom, 0, 0, 1/zoom, 0, 0);
ctx.strokStyle = 'red';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(-this.widthHalf, 0); // widthHalf computed elsewhere
ctx.lineTo(this.widthHalf, 0);
this._renderStroke(ctx); // I use this instead of ctx.stroke() while this ensures my line is still nicely dashed
ctx.restore(); // this is done anyway but if you add custom ctx transform is good practice to wrap it in a save/restore couple
}
Now it happens that this object get cached from the fabricJS cache system that will probably create the cache depending on the canvas zoom too.
I have no understanding of the final use of this object, but you should include this calculation also in the cache canvas size calculation.

image and canvas rotation

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

canvas rotate makes translate go in different direction

I have an image in a canvas which users can rotate and move, using the rotate() and translate() methods.
The moving works fine, until the image is rotated.
Once rotated, translate moves the canvas relative to its rotation.. how do I avoid this?
my code on 'mousemove':
var $canvas = $('#viewport')[0];
var context = $canvas.getContext('2d');
// Clear the canvas first
context.clearRect(0, 0, $canvas.width, $canvas.width);
context.save();
// Do any rotating first
// Move to center
context.translate($canvas.width/2, $canvas.height/2);
// Rotate
context.rotate(degrees*Math.PI/180, $canvas.width, $canvas.width);
// Move back to top-left
context.translate(-$canvas.width/2, -$canvas.height/2);
// Move the canvas to dragged point
context.translate(translate_pos.x, translate_pos.y);
// Draw the image
context.drawImage(image, 0, 0, image.width, image.height);
context.restore();

draw donut path using canvas element

I want to draw a dounut path using canvas. It contains the inner and outer arch connecting with line. But I am getting wrongly canvas image. Please see the below image.
Expected:
This is my code.
this.ctx.beginPath();
this.ctx.moveTo(options.x, options.y);
this.ctx.arc(options.x, options.y, options.radius, options.start, options.end, false);
this.ctx.lineTo(options.x, options.y);
this.ctx.arc(options.x, options.y, options.innerR, options.start, options.end, false);
this.ctx.closePath();
Anyone please help me to solve this issue.
Thanks,
Bharathi.
When moving your "pen" to (options.x, options.y) and then drawing a circle around this point, your "pen" first has to go to the starting position of your arc. Here the line is drawn that you don't want to have on your canvas.
To solve this problem, you have to calculate the starting position of your outer circle (depending on the start angle). You should try with sin or cos to calculate your "new" x and y.
It would then look something like
var newX = options.x + options.radius * cos(options.start);
var newY = options.y + options.radius * sin(options.start);
Then move to this position
this.ctx.moveTo(newX, newY);
And draw the circle around the old x and y
this.ctx.arc(options.x, options.y, options.radius, options.start, options.end, false);
For the inner circle and the end positions you can calculate it similar to this.
I have done it using css
var gradient = ctx.createLinearGradient(0, 0, canvas.width, 0);
gradient.addColorStop(0, "#008B8B");
gradient.addColorStop(0.75, "#F5DEB3");
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
Just remove the last two lines from my above code you will see that the inner circle appears again
SEE DEMO HERE

How do i just rotate a image in Javascript?

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

Categories

Resources