FabricJS ClipTo shape with angle attribute on Image - javascript

I'm stuck with a problem, where I have to crop a shape out of an image. Everything works until I'm not giving an angle to the shape.
I'd like to know how am i supposed to modify my test case to fix this issue.
JSFiddle example
bg.clipTo = function(ctx) {
ctx.save();
ctx.rotate((angle * -1) * (Math.PI / 180));
ctx.rect(left, top, width, height);
ctx.restore();
ctx.stroke();
};

Related

HTML5 Canvas atan2 off by 90 degrees

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

Transparent circles not showing up [HTML5/JavaScript]

I'm trying to draw fireflies on a canvas. I have a image of a 1x1 white pixel and I want to have a transparent circle surrounding it to simulate a glow. So far, I've managed to draw the circle, but when I try to change the global alpha of my 2d context, the image doesn't draw and neither does the circle. This has been confusing me for a while because I draw the image before I draw its surrounding circle. How can I go about fixing this?
My code:
thatBug.draw = function () {
ctx.drawImage(bugImage, thatBug.x, thatBug.y, thatBug.size, thatBug.size);
ctx.save();
ctx.globalAlpha(0.4);
ctx.beginPath();
ctx.arc(thatBug.x, thatBug.y, thatBug.size + thatBug.glowAmt, 0, 2 * Math.PI, false);
ctx.fillStyle = 'white';
ctx.fill();
ctx.restore();
};
Fixed it myself. ctx.globalAlpha(0.4) should be globalAlpha = 0.4

Canvas to use liniear gradient background set with an angle

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>

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

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