HTML5 Canvas rotate function - javascript

function drawNumbers(){
var rad, num;
cx.font= "30px Arial";
cx.textAlign = "center";
cx.textBaseline = "middle";
//numbers around the inner circumference
for(num=1; num < 13; num++){
rad = num * Math.PI/6; //angle for every number
cx.rotate(rad);
cx.translate(0, -175);
cx.rotate(-rad);
cx.fillText(num.toString(),0,0);
cx.rotate(rad);
cx.translate(0, 175);
cx.rotate(-rad);
}
}
function drawHands(){
//getting the time
var time = new Date();
var hours = time.getHours();
var minutes = time.getMinutes();
var seconds = time.getSeconds();
//setting the radians based on the time
//hour hand
hours %= 12;
hours = (hours * Math.PI/6) + (minutes * Math.PI/360) + (seconds * Math.PI/21600);
hands(hours, radius * 0.04, radius * 0.5);
//minute hand
minutes = (minutes * Math.PI/30) + (seconds * Math.PI/1800);
hands(minutes, radius * 0.03, radius * 0.65);
//second hand
seconds = (seconds * Math.PI/30);
hands(seconds, radius * 0.01, radius * 0.68);
}
function hands(ang, width, length){
cx.beginPath();
cx.lineWidth = width;
cx.lineJoin = "round";
cx.lineCap = "round";
cx.moveTo(0, 0);
cx.rotate(ang);
cx.lineTo(0, -length);
cx.stroke();
cx.rotate(-ang);
}
I was learning the HTML5 canvas in W3Schools and the tutorial was teaching how to make a working clock.
1. I just don't understand how the extra rotates work in the functions.
2. When applying a rotate function, does it always rotate from the center of origin (0, 0) of the canvas?

When you call the rotate function it rotates the entire canvas, imagine holding a painting and then tilting it. It happens around the origin always. The way to rotate around a different point is to translate the entire canvas first, then rotate it.
Going back to the painting analogy, if we have rotated our painting, once we have drawn our line, we need to then restore the painting to being upright. Thus we rotate(-ang). If we had translated we would also have to undo our transation in a similar manner.
Rotating and why we undo rotates
In the code below you can see I'm drawing a base black rectangle, and then calling a function which rotates the canvas by 0.5 radians and draws another rectangle twice. I haven't undone my rotation so the 3rd rectangle is actually rotated at 1 radian.
let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
function drawRotatedRectangle() {
ctx.rotate(0.5);
ctx.fillRect(0, 0, 60, 50);
}
// Draw base rectangle
ctx.fillRect(0, 0, 60, 50);
// Rotate and draw second rectangle
ctx.fillStyle = "#FF0000";
drawRotatedRectangle();
// rotate and draw third rectangle
ctx.fillStyle = "#00FF00";
drawRotatedRectangle();
<canvas id="canvas"></canvas>
To fix this we modify the drawRotatedRectangle() function to undo all translations and rotations that it made:
let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
function drawRotatedRectangle() {
ctx.rotate(0.5);
ctx.fillRect(0, 0, 60, 50);
ctx.rotate(-0.5);
}
// Draw base rectangle
ctx.fillRect(0, 0, 60, 50);
// Rotate and draw second rectangle
ctx.fillStyle = "#FF0000";
drawRotatedRectangle();
// rotate and draw third rectangle
ctx.fillStyle = "#00FF00";
drawRotatedRectangle();
<canvas id="canvas"></canvas>
Now we see the red (hidden) rectangle and the green rectangle are at the same angle.
Rotating around a different point to the origin
To demonstrate how we can rotate around a different location to the origin, we can first translate where our context origin is and then rotate our canvas. Below I move the origin to the center of the base rectangle, rotate the canvas and draw a rotated rectangle ontop of the base rectangle. Again the translations and rotations are restored in order of most recently applied.
let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
function drawRotatedRectangle() {
// Move origin to center of rectangle
ctx.translate(30, 25);
// Rotate 0.5 radians
ctx.rotate(0.5);
// Draw rectangle where the center of the rectangle is the origin
ctx.fillRect(-30, -25, 60, 50);
// Undo our rotate
ctx.rotate(-0.5);
// Undo our translate
ctx.translate(-30, -25);
}
// Draw base rectangle
ctx.fillRect(0, 0, 60, 50);
// Rotate and draw second rectangle
ctx.fillStyle = "#FF0000";
drawRotatedRectangle();
// rotate and draw third rectangle
ctx.fillStyle = "#00FF00";
drawRotatedRectangle();
<canvas id="canvas"></canvas>
Edit - Handling Rounding Errors
As Kaiido mentioned in the comments, the rotate(a) function will round the input it is given so simply doing the reverse rotate(-a) function will not return you to the original transformation.
The solution they suggested is to set the transformation matrix to the desired location with setTransform(), in these example we are only returning to the original transform of the canvas so we can use the identity matrix:
ctx.setTransform(1,0,0,1,0,0)
Alternitively, you can also use save() and restore() methods. These will act like pushing the current state of the canvas context to a stack and when you restore() it will pop the latest state from the stack returning you to the previous transform. This article by Jakob Jenkov explains this method further with some examples.

Related

Why do stroked rectangles tend to go outside the canvas?

I've been experimenting with the <canvas> recently, and I noticed a strange behaviour when stroking rectangles near the origin (0, 0) of the canvas.
// canvas context
var ctx = document.querySelector('#screen').getContext('2d');
// draw a rectangle
ctx.fillStyle = 'orange';
ctx.fillRect(0, 0, 100, 100);
// stroke a border for the rectangle
ctx.lineWidth = 20;
ctx.strokeRect(0, 0, 100, 100);
<canvas id="screen"></canvas>
What went wrong?
In the example above, the rectangle itself was drawn at (0, 0) as intended, but its border (the stroked rectangle) seems to be drawn at an offset.
Generally, when stroking a rectangle at a position away from the origin, this effect is omitted —
Meaning that the stroked rectangles aren't being drawn starting at the position specified, but at an offset, I suppose.
Why is that?
The stroke is centered around the coordinates that your primitve is defined at. In the case of your rectangle with stroke width of 20, drawing this at the top left of the canvas will cause half of the strokes width to be drawn outside of the canvas boundary.
Adjusting the coordinates of strokeRect() to 10,10,.. causes the rectangle to be offset from the canvas origin, meaning that the full stroke of 20 pixels will be visible from the top-left of the canvas:
ctx.lineWidth = 20;
ctx.strokeRect(10, 10, 100, 100);
Consider the following adjustments, made to ensure the stroke is fully visible around the drawn rectangle:
var canvas = document.querySelector('#screen');
// Set the width and height to specify dimensions of canvas (in pixels)
// Choosing a 100x100 square matches the strokeRect() drawn below and
// therefore achieves the appearance of a symmetric stroke
canvas.width = 100;
canvas.height = 100;
// canvas context
var ctx = canvas.getContext('2d');
// draw a rectangle
ctx.fillStyle = 'orange';
ctx.fillRect(10, 10, 90, 90);
// stroke a border for the rectangle
ctx.lineWidth = 20;
var halfStroke = ctx.lineWidth * 0.5;
ctx.strokeRect(halfStroke, halfStroke, 100 - (halfStroke * 2), 100 - (halfStroke * 2));
<canvas id="screen"></canvas>
Update
Here is a visualisation of the stroke in relation to the line/rectangle edge provided by Ibrahim Mahrir:

Label connected to circle, curved side on leaflet map

I'm using CSS to try and create a label (which is a popup that always remains on the map) attached to a circle. The following link will lead to the image of what I'm trying to do: Image. In order to achieve this I've been using the following code:
$(popup._container.firstChild).css({
background: "-webkit-radial-gradient(-29px" + percentZoom + ", circle closest-corner, rgba(0, 0, 0, 0) 0, rgba(0, 0, 0, 0) 58px, white 59px)"
});
Before, I was calculating the percentZoom depending on the radius of the circle and the zoom where the map is now.
var percent = (50 * presentCircleRadius) / 300000 //when the radius is 300000 the percentage should be 50%
var percentZoom = (percent * zoom) / 6; // then calculate it the exact zoom that should be used depending on the zoom. Being 6 the default one.
This didn't work or it had many issues when I zoomed in on the map (considering that the circle doesn't really change but the curvature seems to becoming flatter).
I tried using canvas as well to get the result that I wanted it, but I had issues. I was using two arches to build the top part and the bottom part, then thought about using two rectangles to create the two parts to the right of the circle. The problem with this it's that the circle is transparent and it's meant to start on the edge of it, if I used this solution the rectangle would appear in the middle of the circle.
var canvas = document.getElementById('myCanvas1');
var context = canvas.getContext('2d');
var x = canvas.width / 2;
var y = canvas.height / 2;
var radius = 75;
var startAngle = 1.1 * Math.PI;
var endAngle = 1.9 * Math.PI;
var counterClockwise = false;
context.beginPath();
context.arc(x, y, radius, 1.6 * Math.PI, 0 * Math.PI, counterClockwise);
context.lineWidth = 15;
// line color
context.strokeStyle = 'black';
context.stroke();
context.beginPath();
context.arc(x, y, radius, 0 * Math.PI, 0.4 * Math.PI, counterClockwise);
context.lineWidth = 15;
// line color
context.strokeStyle = 'red';
context.stroke();
context.beginPath();
context.lineWidth = "10";
context.strokeStyle = "blue";
context.rect(x, y - radius, 150, radius);
context.stroke();
<canvas id="myCanvas1" width="578" height="250"></canvas>
So I thought of using lines instead of rectangles to create the right part of the label: fiddle, the problem with this solution is, as mention before, as you zoom the curvature will change and I found no way to calculate exactly where the lines on the top and on the bottom should start.
Is there a way to do what I want to do: Make it so that the label follows the curvature of the circle as you zoom in and out and if so how can I make it so considering that there might be more than one circle per zoom with different radius?

Crop image like pie chart

I want to crop image over another image like a pie-chart to create a loading animation. I was thinking of using raphaeljs, but couldn't find any information about image cropping in pie-chart style.
Here are the sample images:
Start state:
End state:
What it should look like:
Just draw a semi-transparent filled arc on top of the image (adjust alpha value to your pleasing):
var ctx = document.querySelector("canvas").getContext("2d"),
img = new Image;
img.onload = draw;
img.src = "http://i.imgur.com/hQ5Pljv.png";
function draw(){
var cx = 157, cy = 159, r = 150,
pst = 0,
ang = Math.PI * 2 * (pst/100),
dlt = 2;
// animate the following part
(function loop() {
ctx.drawImage(img, 0, 0);
ctx.beginPath();
ctx.moveTo(cx, cy);
ctx.arc(cx, cy, r, 0, ang);
ctx.fillStyle = "rgba(0,0,0,0.33)"; // adjust alpha here
ctx.fill();
pst += dlt;
if (pst <= 0 || pst >= 100) dlt = -dlt;
ang = Math.PI * 2 * (pst/100);
requestAnimationFrame(loop)
})()
}
<canvas width=320 height=320></canvas>
Method two - compositing
Use two steps to clip the same arc above to use images instead:
Draw arc, this will be the composite data
Change comp. mode to source-atop - next drawing replaces the drawn arc
Draw secondary image in
Change comp. mode to destination-atop - next drawing will fill all non-pixels
Draw main image in
Demo:
var ctx = document.querySelector("canvas").getContext("2d"),
img1 = new Image, img2 = new Image, cnt=2;
img1.onload = img2.onload = loader;
img1.src = "http://i.imgur.com/hQ5Pljv.png";
img2.src = "http://i.imgur.com/k70j3qp.jpg";
function loader(){if (!--cnt) draw()};
function draw(){
var cx = 157, cy = 159, r = 150,
pst = 0, ang = Math.PI * 2 * (pst/100), dlt = 2;
// animate the following part
(function loop() {
ctx.clearRect(0, 0, 320, 320); // clear canvas, or set last comp mode to "copy"
// first arc
ctx.beginPath();
ctx.moveTo(cx, cy);
ctx.arc(cx, cy, r, 0, ang);
ctx.fill(); // this will be comp. basis for the next steps
// comp mode secondary image
ctx.globalCompositeOperation = "source-atop"; // replaces filled arc
ctx.drawImage(img2, 0, 0);
// comp mode main image
ctx.globalCompositeOperation = "destination-atop"; // fills all non-pixels
ctx.drawImage(img1, 0, 0);
pst += dlt; if (pst <= 0 || pst >= 100) dlt = -dlt; ang = Math.PI * 2 * (pst/100);
ctx.globalCompositeOperation = "source-over"; // reset comp. mode
requestAnimationFrame(loop)
})()
}
<canvas width=320 height=320></canvas>
You'll want an algorithm along the lines of:
Draw image A onto canvas 1
Clear canvas 2
Draw a partial circle on canvas 2, for the current state of the spinner, filled with white
Blit image B onto canvas 2, using the multiplicative blending mode
Blit canvas 2 onto canvas 1, using standard (replace) blending
Canvas 2 should contain the second image, masked by the section you want to use. Overlaying that onto canvas 1, provided you handle transparency properly, should give the effect you want.
You can also use two SVG circles with image backgrounds and do this trivially, assuming your target browsers support SVG.

How to clear part of canvas?

Here is an example!
I am trying to reset the green arc inside drawValueArc() so that each time you click the change button, the green arc is removed and redrawn. How can I remove it without removing the entire canvas? Also, as an aside, I have noticed that Math.random() * 405 * Math.PI / 180 doesn't actually always result in an arc that fits inside the gray arc, sometimes it is larger than the gray arc, why is this?
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var cx = 150;
var cy = 150;
var startRadians = 135 * Math.PI / 180;
var endRadians = 405 * Math.PI / 180;
//main arc
ctx.beginPath();
ctx.arc(cx, cy, 58, startRadians, endRadians, false);
ctx.strokeStyle="rgb(220,220,220)";
ctx.lineWidth = 38;
ctx.stroke();
$('#setRandomValue').click(function(){
drawValueArc(Math.random() * 405 * Math.PI / 180);
});
function drawValueArc(val){
//ctx.clearRect(0, 0, W, H);
ctx.beginPath();
ctx.arc(cx, cy, 58, startRadians, val, false);
ctx.strokeStyle = "green";
ctx.lineWidth = 38;
ctx.stroke();
}
Drawing past boundary
The problem you are facing is in first instance the fact you are drawing before and after a 0-degree on the circle. This can be complicated to handle as you need to split in two draws: one for the part up to 0 (360) and one 0 to the remaining part.
There is a simple trick you can use to make this easier to deal with and that is to deal with all angles from 0 and use an offset when you draw.
Demo using redraw base (I moved it to jsfiddle as jsbin did not work for me):
http://jsfiddle.net/3dGLR/
Demo using off-screen canvas
http://jsfiddle.net/AbdiasSoftware/Dg9Jj/
First, some optimizations and settings for the offset:
var startRadians = 0; //just deal with angles
var endRadians = 300;
var deg2rad = Math.PI / 180; //pre-calculate this to save some cpu cycles
var offset = 122; //adjust this to modify rotation
We will now let the main function, drawArc() do all calculations for us so we can focus on the numbers - here we also offset the values:
function drawArc(color, start, end) {
ctx.beginPath();
ctx.arc(cx, cy, 58,
(startRadians + offset) * deg2rad,
(end + offset) * deg2rad, false);
ctx.strokeStyle = color;
ctx.lineWidth = 38;
ctx.stroke();
}
Clearing the previous arc
There are several techniques to clear the previous drawn arc:
You can draw the base arc to an off-screen canvas and use drawImage() to erase the old.
You can do as in the following example, just re-draw it with the base color
As with 2. but subtracting the green arc and draw the base color from the end of the green arc to the end of the base arc.
clearing the whole canvas with fillRect or clearRect.
1 and 3 are the fastest, while 4 is the slowest.
With out re-factored function (drawArc) it's as easy as this:
function drawValueArc(val) {
drawArc("rgb(220,220,220)", startRadians, endRadians);
drawArc("green", startRadians, val);
}
As everything now is 0-based concerning start we really don't need to give any other argument than 0 to the drawArc instead of startRadians. Use the new offset to offset the start position and adjust the endRadians to where you want it to stop.
As you can see in the demo, using this technique keeps everything in check without the need to draw in split.
Tip: if you notice green artifacts on the edges: this is due to anti-alias. Simply reduce the line width for the green color by 2 pixels (see demo 2, off-screen canvas).

How do I rotate a single object on an html 5 canvas?

I'm trying to figure out how to rotate a single object on an html 5 canvas.
For example: http://screencast.com/t/NTQ5M2E3Mzct - I want each one of those cards to be rotated at a different degree.
So far, all I've seen are articles and examples that demonstrate ways to rotate the entire canvas. Right now, I'm guessing I'll have to rotate the canvas, draw an image, and then rotate the canvas back to it's original position before drawing the second image. If that's the case, then just let me know! I just have a feeling that there's another way.
Anyone have any idea?
I ran into the same problem in a recent project (where I kicked rotating aliens all over the place). I just used this humble function that does the same thing and can be used the same way as ctx.rotate but can be passed an angle. Works fine for me.
function drawImageRot(img,x,y,width,height,deg){
// Store the current context state (i.e. rotation, translation etc..)
ctx.save()
//Convert degrees to radian
var rad = deg * Math.PI / 180;
//Set the origin to the center of the image
ctx.translate(x + width / 2, y + height / 2);
//Rotate the canvas around the origin
ctx.rotate(rad);
//draw the image
ctx.drawImage(img,width / 2 * (-1),height / 2 * (-1),width,height);
// Restore canvas state as saved from above
ctx.restore();
}
Yay, my first answer!
Unfortunately in the HTML5 canvas element you can't rotate individual elements.
Animation works like drawing in MS Paint: You draw something, make a screen.. use the eraser to remove some stuff, draw something differently, make a screen.. Draw something else on top, make a screen.. etc etc.
If you have an existing item on the canvas - you'll have to erase it ( use ctx.fillRect() or clearRect() for example ), and then draw the rotated object.
If you're not sure how to rotate it while drawing in the first place:
ctx.save();
ctx.rotate(0.17);
// draw your object
ctx.restore();
To rotate a individual object you have to set the transformation matrix. This is really simple:
var context = document.getElementById('pageCanvas').getContext('2d');
var angle = 0;
function convertToRadians(degree) {
return degree*(Math.PI/180);
}
function incrementAngle() {
angle++;
if(angle > 360) {
angle = 0;
}
}
function drawRandomlyColoredRectangle() {
// clear the drawing surface
context.clearRect(0,0,1280,720);
// you can also stroke a rect, the operations need to happen in order
incrementAngle();
context.save();
context.lineWidth = 10;
context.translate(200,200);
context.rotate(convertToRadians(angle));
// set the fill style
context.fillStyle = '#'+Math.floor(Math.random()*16777215).toString(16);
context.fillRect(-25,-25,50,50);
context.strokeRect(-25,-25,50,50);
context.restore();
}
// Ideally use getAnimationFrame but for simplicity:
setInterval(drawRandomlyColoredRectangle, 20);
<canvas width="1280" height="720" id="pageCanvas">
You do not have a canvas enabled browser
</canvas>
Basically, to make an object rotate properly without having other shape rotating around, you need to:
save the context: ctx.save()
move the pivot point to the desired location: ctx.translate(200, 200);
rotate: context.rotate(45 * Math.PI / 180);
draw the shape, sprite, whatever: ctx.draw...
reset the pivot: ctx.translate(-200, -200);
restore the context to its original state: ctx.restore();
function spinDrawing() {
ctx.save();
ctx.translate(200, 200);
context.rotate(45 * Math.PI / 180);
ctx.draw //your drawing function
ctx.translate(-200, -200);
ctx.restore();
}
Caveats: After you translating , the origin of the canvas changed, which means when you drawing the shape, the coordinate of the shape should be aligned accordingly.
Shapes drawn outside the list mentioned above won´t be affected. I hope it helps.
This html/javascript code might shed some light on the matter:
<!DOCTYPE html>
<html>
<body>
<canvas id="myCanvas" width="233" height="233" style="border:1px solid #d3d3d3;">
your browser does not support the canvas tag </canvas>
<script type="text/javascript">
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
var canvasWidth=233;
var canvasHeight=233;
var rectWidth=100;
var rectHeight=150;
var x=30;
var y=30;
var translateX= x+(rectWidth/2);
var translateY= y+(rectHeight/2);
ctx.fillRect(x,y,rectWidth,rectHeight);
ctx.translate(translateX,translateY);
ctx.rotate(5*Math.PI/64); /* just a random rotate number */
ctx.translate(-translateX,-translateY);
ctx.fillRect(x,y,rectWidth,rectHeight);
</script>
</body>
</html>
I find it helpful to see the math related to rotating, I hope this was helpful to you too.
<!DOCTYPE html>
<html>
<body>
<canvas id="myCanvas" width="500" height="450" style="border:1px solid #d3d3d3;">
</canvas>
<Button id = "right" onclick = "rotateRight()">Right</option>
<Button id = "left" onclick = "rotateLeft()">Left</option>
<script src = "zoom.js">
</script>
<script>
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
createRect();
function rotateRight()
{
ctx.save();
ctx.clearRect(0,0,500,450);
ctx.translate(c.width/2,c.height/2);
ctx.rotate(10*Math.PI/180 );
ctx.translate(-c.width/2,-c.height/2);
createRect();
}
function rotateLeft()
{
ctx.save();
ctx.clearRect(0,0,500,450);
ctx.translate(c.width/2,c.height/2);
ctx.rotate(-10*Math.PI/180 );
ctx.translate(-c.width/2,-c.height/2);
createRect();
}
function createRect()
{
ctx.beginPath();
ctx.fillStyle = "#AAAA00";
ctx.fillRect(250,250,90,50);
}
</script>
</body>
</html>
To rotate an object you can use rotate() method. Here the example how to rotate a rectangular object to 135 degrees of clockwise.
<script>
var canvas = document.getElementById('Canvas01');
var ctx = canvas.getContext('2d');
var rectWidth = 100;
var rectHeight = 50;
//create line
ctx.strokeStyle= '#ccc';
ctx.beginPath();
ctx.moveTo(canvas.width / 2, 0);
ctx.lineTo(canvas.width / 2, canvas.height);
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.moveTo(0, canvas.height/2);
ctx.lineTo(canvas.width, canvas.height/2);
ctx.stroke();
ctx.closePath();
// translate ctx to center of canvas
ctx.translate(canvas.width / 2, canvas.height / 2);
// rotate the rect to 135 degrees of clockwise
ctx.rotate((Math.PI / 180)*135);
ctx.fillStyle = 'blue';
ctx.fillRect(0, 0, rectWidth, rectHeight);
</script>
</body>
Here the demo and you can try yourself: http://okeschool.com/examples/canvas/html5-canvas-rotate
I found this question because I had a bunch of stuff on a canvas, drawn with canvas lines, painstakingly, and then decided some of them should be rotated. Not wanting to do a whole bunch of complex stuff again I wanted to rotate what I had. A simple solution I found was this:
ctx.save();
ctx.translate(x+width_of_item/2,y+height_of_item/2);
ctx.rotate(degrees*(Math.PI/180));
ctx.translate(-(x+width_of_item/2),-(y+height_of_item/2));
// THIS IS THE STUFF YOU WANT ROTATED
// do whatever it is you need to do here, moveto and lineto is all i used
// I would expect anything to work. use normal grid coordinates as if its a
// normal 0,0 in the top left kind of grid
ctx.stroke();
ctx.restore();
Anyway - it might not be particularly elegant but its a dead easy way to rotate one particular element onto your canvas.
Look at all those rotated elements!

Categories