HTML5 Canvas atan2 off by 90 degrees - javascript

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

Related

HTML5 Canvas - create striped canvas with lineWidth and createPattern()

I want to create a striped pattern with HTML5 canvas, where the thickness of lines for the striped pattern should be configurable using the property lineWidth.
After I read this answer, I understood that for coord x,y from moveTo()/lineTo(), I need to add like 2.5 for the ctx.lineWidth =5 or maybe create a formula based on thickness like this example. But I can't figure out how to change the values of those coordinates so the pattern remains striped like on the right, not random like in left
Below is my code. How should I calculate the coordonates x,y?
function createStrippedPattern(color) {
const pattern = document.createElement('canvas');
// create a 10x10 px canvas for the pattern's base shape
pattern.width = 10;
pattern.height = 10;
// get the context for drawing
const context = pattern.getContext('2d');
context.strokeStyle = color;
context.lineWidth = 5;
// draw 1st line of the shape
context.beginPath();
context.moveTo(2, 0);
context.lineTo(10, 8);
context.stroke();
// draw 2st line of the shape
context.beginPath();
context.moveTo(0, 8);
context.lineTo(2, 10);
context.stroke();
return context.createPattern(pattern, 'repeat');
};
function fillWithPattern(targetCanvas, patternCanvas) {
const ctx = targetCanvas.getContext('2d', {
antialias: false,
depth: false
});
const width = targetCanvas.width;
const height = targetCanvas.height;
ctx.fillStyle = patternCanvas;
ctx.fillRect(0, 0, width, height);
return targetCanvas;
}
fillWithPattern(
document.getElementById("targetCanvas"),
createStrippedPattern("red")
);
<canvas id="targetCanvas" width=30 height=30></canvas>
Code logic problems
The size of the pattern needs to match the slope of the line. That size must be expanded to allow for a set spacing between the lines.
Your code has a fixed size that does not match the slope of either of the lines you draw.
The lines you draw are both in different directions. You will never get them to create a repeatable pattern.
The code you have given is too ambiguous for me to understand what you wish to achieve thus the example adds some constraints that considers my best guess at your requirements.
Tileable striped pattern
The function in the example below creates a striped repeatable (tilded) pattern.
The function createStripedPattern(lineWidth, spacing, slope, color) requires 4 arguments.
lineWidth width of the line to draw
spacing distance between lines. Eg if lineWidth is 5 and spacing is 10 then the space between the lines is the same width as the line.
slope The slope of the line eg 45 degree slope is 1. I have only tested value >= 1 and am not sure if it will work below 1.
Nor have I tested very large slopes. The point of the example is to show how to draw the line on the pattern to repeat without holes.
color Color of line to draw.
The function works by creating a canvas that will fit the constraints given by the arguments. It then draws a line from the top left to bottom right corners. This leaves a gap in the repeating pattern at the top right and bottom left corners.
To fill the missing pixels two more lines are drawn. One through the top right corner and the other through the bottom left.
Note you could also just copy the canvas onto itself (offset to the corners) to fill the missing corner pixels. For pixel art type patterns this may be preferable.
Note that canvas sizes are integer values and lines are rendered at sub pixel accuracy. For very small input values there will be artifact as the relative error between the canvas (integer) pixel size and required (floating point) size grows larger
Example
The example contains the function to create the pattern as outlined above and then renders some examples.
The first canvas has inset patterns with each pattern increasing the line width will keeping the spacing and slope constant.
The second canvas just fills with a fixed lineWidth as 4, spacing as 8 and a slope of 3
function createAARotatedPattern(lineWidth, spacing, ang, color) {
const can = document.createElement('canvas');
const w = can.width = 2;
const h = can.height = spacing;
const ctx = can.getContext('2d');
ctx.fillStyle = color;
ctx.fillRect(0, 0, 2, lineWidth);
const pat = ctx.createPattern(can, 'repeat');
const xAx = Math.cos(ang);
const xAy = Math.sin(ang);
pat.setTransform(new DOMMatrix([xAx, xAy, -xAy, xAx, 0, 0]));
return pat;
}
function createStripedPattern(lineWidth, spacing, slope, color) {
const can = document.createElement('canvas');
const len = Math.hypot(1, slope);
const w = can.width = 1 / len + spacing + 0.5 | 0; // round to nearest pixel
const h = can.height = slope / len + spacing * slope + 0.5 | 0;
const ctx = can.getContext('2d');
ctx.strokeStyle = color;
ctx.lineWidth = lineWidth;
ctx.beginPath();
// Line through top left and bottom right corners
ctx.moveTo(0, 0);
ctx.lineTo(w, h);
// Line through top right corner to add missing pixels
ctx.moveTo(0, -h);
ctx.lineTo(w * 2, h);
// Line through bottom left corner to add missing pixels
ctx.moveTo(-w, 0);
ctx.lineTo(w, h * 2);
ctx.stroke();
return ctx.createPattern(can, 'repeat');
};
function fillWithPattern(canvas, pattern, inset = 0) {
const ctx = canvas.getContext('2d');
ctx.clearRect(inset, inset, canvas.width - inset * 2, canvas.height - inset * 2);
ctx.fillStyle = pattern;
ctx.fillRect(inset, inset, canvas.width - inset * 2, canvas.height - inset * 2);
return canvas;
}
fillWithPattern(targetCanvas, createStripedPattern(2, 6, 2, "#000"));
fillWithPattern(targetCanvas, createStripedPattern(3, 6, 2, "#000"), 50);
fillWithPattern(targetCanvas, createStripedPattern(4, 6, 2, "#000"), 100);
fillWithPattern(targetCanvas1, createStripedPattern(4, 8, 3, "#000"));
var y = 0;
var ang = 0;
const ctx = targetCanvas2.getContext('2d');
while (y < targetCanvas2.height) {
ctx.fillStyle = createAARotatedPattern(2, 5, ang, "#000");
ctx.fillRect(0, y, targetCanvas2.width, 34);
y += 40;
ang += 2 * Math.PI / (targetCanvas2.height / 40);
}
<canvas id="targetCanvas" width="300" height="300"></canvas>
<canvas id="targetCanvas1" width="300" height="300"></canvas>
<canvas id="targetCanvas2" width="300" height="600"></canvas>
Update
The above example now includes a second method createAARotatedPattern(lineWidth, spacing, ang, color) that uses the pattern transform. ang replaces slope from the original function and represents the angle of the pattern in radians.
It works by drawing the pattern aligned to the x axis and then rotates the pattern via a DOMMatrix.
It will create a pattern at any angle, though personally the quality can at times be less than the first method.
The example has a 3 canvas with strips showing the pattern drawn at various angles. (Note you do not have to recreate the pattern to change the angle)

Trying to calculate mouse x or y (or ratio of both) with given angle to give a distance

I have the xy coordinates from before and during a drag event, this.x and this.y```` are the current coordinates,this.lastXandthis.lastY``` are the origin.
What I need to do is given a radian of the source element, determine which mouse coordinate to use, IE if the angle is 0 then the x coordinates is used to give a "distance" if the degrees are 90 then the y coordinates are used
if the radian is 0.785398 then both x and y would need to be used.
I have the following code for one axis, but this only flips the y coordinates
let leftPosition;
if (this.walls[this.dragItem.wall].angle < Math.PI / 2) {
leftPosition = Math.round((-(this.y - this.lastY) / this.scale + this.dragItem.origin.left));
} else {
leftPosition = Math.round(((this.y - this.lastY) / this.scale + this.dragItem.origin.left));
}
I have an example here https://engine.owuk.co.uk
what I need to do is have the radian dictate what x or y coordinate is used to control the drag of the item by calculating the leftPosition, I have been loosing my mind trying to get this to work :(
The Math.sin and Math.cos is what you need, here is an example
<canvas id="c" width=300 height=150></canvas>
<script>
const ctx = document.getElementById('c').getContext('2d');
function drawShape(size, angle, numPoints, color) {
ctx.beginPath();
for (j = 0; j < numPoints; j++) {
a = angle * Math.PI / 180
x = size * Math.sin(a)
y = size * Math.cos(a)
ctx.lineTo(x, y);
angle += 360 / numPoints
}
ctx.fillStyle = color;
ctx.fill();
}
ctx.translate(80, 80);
drawShape(55, 0, 7, "green");
drawShape(45, 0, 5, "red");
drawShape(35, 0, 3, "blue");
ctx.translate(160, 0);
drawShape(55, 15, 7, "green");
drawShape(45, 35, 5, "red");
drawShape(35, 25, 3, "blue");
</script>
Here is a theoretical answer to your problem.
In the simplest way, you have an object within a segment that has to move relative to the position of the mouse, but constrained by the segment's vector.
Here is a visual representation:
So with the mouse at the red arrow, the blue circle needs to move to the light blue.
(the shortest distance between a line and a point)
How do we do that?
Let's add everything we can to that image:
The segment and the mouse form a triangle and we can calculate the length of all sides of that triangle.
The distance between two points is an easy Pythagorean calculation:
https://ncalculators.com/geometry/length-between-two-points-calculator.htm
Then we need the height of the triangle where the base is our segment:
https://tutors.com/math-tutors/geometry-help/how-to-find-the-height-of-a-triangle
That will give us the distance from our mouse to the segment, and we do know the angle by adding the angle of the segment + 90 degrees (or PI/2 in radians) that is all that we need to calculate the position of our light blue circle.
Of course, we will need to also add some min/max math to not exceed the boundaries of the segment, but if you made it this far that should be easy pickings.
I was able to make the solution to my issue
let position;
const sin = Math.sin(this.walls[this.dragItem.wall].angle);
const cos = Math.cos(this.walls[this.dragItem.wall].angle);
position = Math.round(((this.x - this.lastX) / this.scale * cos + (this.y - this.lastY) / this.scale * sin) + this.dragItem.origin.left);

Creating a Gradient Path Fill JavaScript

I've been recently adding shadows to a project. I've ended up with something that I like, but the shadows are a solid transparent color throughout. I would prefer them to be a fading gradient as they go further.
What I currently have:
What I'd like to achieve:
Right now I'm using paths to draw my shadows on a 2D Canvas. The code that is currently in place is the following:
// Check if edge is invisible from the perspective of origin
var a = points[points.length - 1];
for (var i = 0; i < points.length; ++i, a = b)
{
var b = points[i];
var originToA = _vec2(origin, a);
var normalAtoB = _normal(a, b);
var normalDotOriginToA = _dot(normalAtoB, originToA);
// If the edge is invisible from the perspective of origin it casts
// a shadow.
if (normalDotOriginToA < 0)
{
// dot(a, b) == cos(phi) * |a| * |b|
// thus, dot(a, b) < 0 => cos(phi) < 0 => 90° < phi < 270°
var originToB = _vec2(origin, b);
ctx.beginPath();
ctx.moveTo(a.x, a.y);
ctx.lineTo(a.x + scale * originToA.x,
a.y + scale * originToA.y);
ctx.lineTo(b.x + scale * originToB.x,
b.y + scale * originToB.y);
ctx.lineTo(b.x, b.y);
ctx.closePath();
ctx.globalAlpha = _shadowIntensity / 2;
ctx.fillStyle = 'black';
ctx.fillRect(_innerX, _innerY, _innerWidth, _innerHeight);
ctx.globalAlpha = _shadowIntensity;
ctx.fill();
ctx.globalAlpha = 1;
}
}
Suggestions on how I could go about achieving this? Any and all help is highly appreciated.
You can use composition + the new filter property on the context which takes CSS filters, in this case blur.
You will have to do it in several steps - normally this falls under the 3D domain, but we can "fake" it in 2D as well by rendering a shadow-map.
Here we render a circle shape along a line represented by length and angle, number of iterations, where each iteration increasing the blur radius. The strength of the shadow is defined by its color and opacity.
If the filter property is not available in the browser it can be replaced by a manual blur (there are many out there such as StackBoxBlur and my own rtblur), or simply use a radial gradient.
For multiple use and speed increase, "cache" or render to an off-screen canvas and when done composite back to the main canvas. This will require you to calculate the size based on max blur radius as well as initial radius, then render it centered at angle 0°. To draw use drawImage() with a local transform transformed based on start of shadow, then rotate and scale (not shown below as being a bit too broad).
In the example below it is assumed that the main object is drawn on top after the shadow has been rendered.
The main function takes the following arguments:
renderShadow(ctx, x, y, radius, angle, length, blur, iterations)
// ctx - context to use
// x/y - start of shadow
// radius - shadow radius (assuming circle shaped)
// angle - angle in radians. 0° = right
// length - core-length in pixels (radius/blur adds to real length)
// blur - blur radius in pixels. End blur is radius * iterations
// iterations - line "resolution"/quality, also affects total end blur
Play around with shape, shadow color, blur radius etc. to find the optimal result for your scene.
Demo
Result if browser supports filter:
var ctx = c.getContext("2d");
// render shadow
renderShadow(ctx, 30, 30, 30, Math.PI*0.25, 300, 2.5, 20);
// show main shape
ctx.beginPath();
ctx.moveTo(60, 30);
ctx.arc(30, 30, 30, 0, 6.28);
ctx.fillStyle = "rgb(0,140,200)";
ctx.fill();
function renderShadow(ctx, x, y, radius, angle, length, blur, iterations) {
var step = length / iterations, // calc number of steps
stepX = step * Math.cos(angle), // calc angle step for x based on steps
stepY = step * Math.sin(angle); // calc angle step for y based on steps
for(var i = iterations; i > 0; i--) { // run number of iterations
ctx.beginPath(); // create some shape, here circle
ctx.moveTo(x + radius + i * stepX, y + i * stepY); // move to x/y based on step*ite.
ctx.arc(x + i * stepX, y + i * stepY, radius, 0, 6.28);
ctx.filter = "blur(" + (blur * i) + "px)"; // set filter property
ctx.fillStyle = "rgba(0,0,0,0.5)"; // shadow color
ctx.fill();
}
ctx.filter = "none"; // reset filter
}
<canvas id=c width=450 height=350></canvas>

How can i draw a Square in HTML5 Canvas at run time?

I am working on a HTML5 Project.There is a drawing graphics API to draw Rectangle (fillRectStrokeRect).But how can i draw a SQUARE. I have tried the following way to draw it
CODE
getMouse(e);
x2=mx; y2=my;
var width=endX-startX;
var height=endY-startY;
annCanvasContext.beginPath();
annCanvasContext.lineWidth=borderWidth;
var centerX=width/2;
var centerY=width/2;
var radius=width/2;
annCanvasContext.arc(centerX+5, centerY+5, radius, 0, 2 * Math.PI, false);
annCanvasContext.stroke();
Use fillRect or strokeRect with the width and height being equal.
var x = 0, y = 0,
side = 10;
ctx.fillRect(x, y, side, side);
Demo
As you say in the comments, if you want to fit the largest square in a circle, it's more Math related than about code. I'll trying explaining it to you, but you'll probably find better, more visual explanations elsewhere on the Internet.
Draw the diameter of the circle in a way that it divides your square into two equal parts. Now one part is a right angled triangle, which has two of its sides equal. We know the diameter. Using the Pythogorean theorem, you get this equation:
side^2 + side^2 = diameter^2.
Let's find the side now.
2(side^2) = diameter^2
side^2 = (diameter^2)/2
side = Math.sqrt( (diameter^2)/2 )
Now, to turn this into code.
var ctx = document.getElementById('canvas').getContext('2d'),
radius = 20;
ctx.canvas.addEventListener('click', function (e){
ctx.fillStyle = 'black';
ctx.arc(e.pageX, e.pageY, radius, 0, Math.PI*2, false);
ctx.fill();
ctx.beginPath();
ctx.fillStyle = 'red';
var diameter = radius * 2;
var side = Math.sqrt( (diameter * diameter)/2 );
ctx.fillRect(e.pageX - side/2, e.pageY - side/2, side, side);
ctx.closePath();
}, false);
This would draw a square inside a circle wherever you click on the canvas.
Demo

Rotate 'note' on the canvas to always touch the upper left corner

The final code that worked for me was:
<canvas id="bg-admin-canvas" width="500" height="500" style="margin:15px; background:#09F;"></canvas>
<script>
var postit = function(width,height,angle){
var canvas = document.getElementById("bg-admin-canvas");
var ctx = canvas.getContext("2d");
var radians = angle * Math.PI / 180;
var move = width*Math.sin(radians);
if(angle < 0 ){ ctx.translate(0,-move); }else{ ctx.translate(move,0); }
ctx.rotate(radians);
var gradient = ctx.createLinearGradient(0,height,width/2,height/2);
gradient.addColorStop(0.05,"rgba(0,0,0,0)");
gradient.addColorStop(0.5,"rgba(0,0,0,0.3)");
ctx.fillStyle = gradient;
ctx.fillRect(0,0,width,height);
ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(width, 0);
ctx.lineTo(width,height);
ctx.lineTo(width-width*.8,height-height*.02);
ctx.quadraticCurveTo(0+width*.02,height-height*.02,0+width*.02,(height - height*.2));
ctx.closePath();
var gradient = ctx.createLinearGradient(0,height,width/2,height/2);
gradient.addColorStop(0,'#f7f8b9');
gradient.addColorStop(1,'#feffcf');
ctx.fillStyle = gradient;
ctx.fill();
ctx.beginPath();
ctx.moveTo(width-width*.8,height-height*.02);
ctx.quadraticCurveTo(0+width*.02,height-height*.02,0+width*.02,(height - height*.2));
ctx.quadraticCurveTo(width*.05,height-height*.05,width*.1,height-height*.1);
ctx.quadraticCurveTo(width*.1,height-height*.07,width-width*.8,height-height*.02);
ctx.closePath();
ctx.fillStyle = '#ffffff';
ctx.fill();
var gradient = ctx.createLinearGradient(0,height,width*.1,height-height*.1);
gradient.addColorStop(0,"rgba(222,222,163,0.8)");
gradient.addColorStop(1,'#feffcf');
ctx.fillStyle = gradient;
ctx.fill();
}
postit(300, 300, 10);
</script>
Hi,
I made a quick and dirty "post-it" note with html5's canvas and some js.
I want to be able to rotate them anyway I want so I tried to use the translate. The example below I have a translate of 0,250 just so you could see the whole thing.
Ideally, I know if my canvas was 300,300 then I would
ctx.translate(150,150);
ctx.rotate(-30);
ctx.translate(-150,-150);
Of course since I'm rotating a square it gets cut off.
How would I rotate the square and move it on the canvas so the whole thing is showing but at the very top left edge of the canvas?
I added an image with my thinking of just getting the height of a triangle and moving it that much, but when translated, it doesn't seem to work just right.
I'll paste my whole function so you can look at it, but if you have any ideas, I would appreciate it. This isn't important, just messing around today.
var postit = function(width,height,angle){
var canvas = jQuery("#bg-admin-canvas").get(0);
var ctx = canvas.getContext("2d");
/*var area = (width*width*Math.sin(angle))/2;
var h = (area*2) / width + 30;
ctx.translate(0,h);
*/
//ctx.translate(150,150);
ctx.translate(0,250);
ctx.rotate(angle*Math.PI / 180);
//ctx.translate(-150,-150);
var gradient = ctx.createLinearGradient(0,height,width/2,height/2);
gradient.addColorStop(0.05,"rgba(0,0,0,0)");
gradient.addColorStop(0.5,"rgba(0,0,0,0.3)");
ctx.fillStyle = gradient;
ctx.fillRect(0,0,width,height);
ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(width, 0);
ctx.lineTo(width,height);
ctx.lineTo(width-width*.8,height-height*.02);
ctx.quadraticCurveTo(0+width*.02,height-height*.02,0+width*.02,(height - height*.2));
ctx.closePath();
var gradient = ctx.createLinearGradient(0,height,width/2,height/2);
gradient.addColorStop(0,'#f7f8b9');
gradient.addColorStop(1,'#feffcf');
ctx.fillStyle = gradient;
ctx.fill();
ctx.beginPath();
ctx.moveTo(width-width*.8,height-height*.02);
ctx.quadraticCurveTo(0+width*.02,height-height*.02,0+width*.02,(height - height*.2));
ctx.quadraticCurveTo(width*.05,height-height*.05,width*.1,height-height*.1);
ctx.quadraticCurveTo(width*.1,height-height*.07,width-width*.8,height-height*.02);
ctx.closePath();
ctx.fillStyle = '#ffffff';
ctx.fill();
var gradient = ctx.createLinearGradient(0,height,width*.1,height-height*.1);
gradient.addColorStop(0,"rgba(222,222,163,0.8)");
gradient.addColorStop(1,'#feffcf');
ctx.fillStyle = gradient;
ctx.fill();
}
postit(300, 300, -35);
MORE INFO
Phrog, I think you know what I'm trying to do. This image shows what I want to do:
Now, the only thing is, I want to be able to pass in any width and height and angle and make the adjustment on the fly.
As an example with the following code:
var canvas = document.getElementById("bg-admin-canvas");
var ctx = canvas.getContext("2d");
ctx.arc(0,0,3,0,360,true); ctx.fill();
ctx.translate(50, 50);
ctx.arc(0,0,3,0,360,true); ctx.fill();
ctx.translate(-25, -25);
ctx.arc(0,0,3,0,360,true); ctx.fill();
I get the following image:
Now, if I add a rotate in there like this:
var canvas = document.getElementById("bg-admin-canvas");
var ctx = canvas.getContext("2d");
ctx.arc(0,0,3,0,360,true); ctx.fill();
ctx.translate(50, 50);
ctx.arc(0,0,3,0,360,true); ctx.fill();
ctx.rotate(30*Math.PI/180);
ctx.translate(-25, -25);
ctx.arc(0,0,3,0,360,true); ctx.fill();
I now have a sloped coordinates as the result is:
As I found, this is because the coordinates are no longer horizontal and vertical.
So, with this rotated coordinate structure, I can't figure out how to move my square (which could be any size and rotated at any angle) back to the left and top (so it fits in as little space as possible)
Does that make sense?
In short:
Translate the context in the Y direction only to put the corner where it should be.
Rotate the context around this offset point.
Draw your object at 0,0.
Here is an interactive, working example, which you can see online here:
http://phrogz.net/tmp/canvas_rotate_square_in_corner.html
<!DOCTYPE HTML>
<html lang="en"><head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>HTML5 Canvas Rotate Square in Corner</title>
<style type="text/css" media="screen">
body { background:#eee; margin:2em; text-align:center }
canvas { display:block; margin:auto; background:#fff; border:1px solid #ccc }
</style>
</head><body>
<canvas width="250" height="200"></canvas>
<script type="text/javascript" charset="utf-8">
var can = document.getElementsByTagName('canvas')[0];
var ctx = can.getContext('2d');
ctx.strokeStyle = '#600'; ctx.lineWidth = 2; ctx.lineJoin = 'round';
ctx.fillStyle = '#ff0'
document.body.onmousemove = function(evt){
var w=140, h=120;
var angle = evt ? (evt.pageX - can.offsetLeft)/100 : 0;
angle = Math.max(Math.min(Math.PI/2,angle),0);
ctx.clearRect(0,0,can.width,can.height); ctx.beginPath();
ctx.save();
ctx.translate(1,w*Math.sin(angle)+1);
ctx.rotate(-angle);
ctx.fillRect(0,0,w,h);
ctx.strokeRect(0,0,w,h);
ctx.restore();
};
document.body.onmousemove();
</script>
</body></html>
Analysis
In the above diagram, point A is the upper-left corner of our post-it note and point B is the upper-right corner. We have rotated the post-it note -a radians from the normal angle (clockwise rotations are positive, counter-clockwise are negative).
We can see that the point A stays on the y axis as the post-it rotates, so we only need to calculate how far down the y axis to move it. This distance is expressed in the diagram as BD. From trigonometry we know that
sin(a) = BD / AB
Rearranging this formula gives us
BD = AB * sin(a)
We know that AB is the width of our post-it note. A few details:
Because our angle will be expressed as a negative number, and the sin of a negative number yields a negative result, but because we want a positive result, we must either negate the result
BD = -AB * sin(-a)
or just 'cheat' and use a positive angle:
BD = AB * sin(a)
We need to remember to translate our context before we rotate it, so that we first move directly down the axis to establish our origin at the right spot.
Remember that rotations in HTML5 Canvas use radians (not degrees). If you want to rotate by 20 degrees, you need to convert that to radians by multiplying by Math.PI/180:
ctx.rotate( 20*Math.PI/180 );
This also applies to the arc command; you should be doing ctx.arc(x,y,r,0,Math.PI*2,false); for a full circle.
You should create you canvas element and then rotate it using CSS. It would keep your canvas intact and only rotate the element itself.
Here is some example css rules:
-webkit-transform: rotate(-30deg);
-moz-transform: rotate(-30deg);
Refer to http://snook.ca/archives/html_and_css/css-text-rotation

Categories

Resources