How to get pixel range in scaled canvas? - javascript

In the following code, I am drawing a circle on the center, but because it is scaled, the circle is in bottom right corner!
var canvas = document.getElementById('mycanvas');
context = canvas.getContext("2d");
context.canvas.width = 400;
context.canvas.height = 200;
context.clearRect(0, 0, context.canvas.width, context.canvas.height); // Clears the canvas
var scale = 2;
context.scale(scale, scale);
context.beginPath();
context.fillStyle = "#ff2626"; // Red color
context.arc(context.canvas.width / 2, context.canvas.height / 2, 10, 0, Math.PI * 2); //center
context.fill();
context.closePath();
canvas {
border: solid 1px #ccc;
}
<HTML>
<body>
<canvas id="mycanvas"></canvas>
</body>
</HTML>
As you probably know, canvas width and height is not related to what is drawing inside it, especially when you scale it. in other word, when you scale a canvas using context.scale(scale_x, scale_y); it will scale all shapes inside the canvas. I am wondering to know, is there any way to get the canvas pixel range?
I want to know the X on left edge and right edge and the Y on top and bottom edges when a canvas is scaled.

Dividing the coordinates by scale should do the trick:
var canvas = document.getElementById('mycanvas');
context = canvas.getContext("2d");
context.canvas.width = 400;
context.canvas.height = 200;
context.clearRect(0, 0, context.canvas.width, context.canvas.height); // Clears the canvas
var scale = 2;
context.scale(scale, scale);
context.beginPath();
context.fillStyle = "#ff2626"; // Red color
context.arc(context.canvas.width /2/scale, context.canvas.height / 2/scale, 10, 0, Math.PI * 2); //center
context.fill();
context.closePath();
canvas {
border: solid 1px #ccc;
}
<HTML>
<body>
<canvas id="mycanvas"></canvas>
</body>
</HTML>
And on a side note, context.canvas.width /(2*scale) is cleaner than context.canvas.width /2/scale, but I kept it like that just to show the division by scale.

You don't need this pixel range.
You have to understand how canvas transformations work and embrace it rather than trying to do the math yourself.
If we take the canvas as a real canvas, or as a sheet of paper, then we can say that the transformation matrix controls the position of this sheet of paper relative to a fixed camera.
The key point is that, the coordinates you provide to the canvas drawing methods are still the ones that are on the un-transformed sheet of paper, no matter how you did rotate, translate or scale it.
Also, initially, we do hold this sheet of paper by its top left corner, this is known as the transformation-origin; all the transformations like rotate and scale will be done from this point, and this is why when you did scale by 2, the center coordinates are now in the bottom right corner of what the camera sees.
So if you wish to scale to the center of your canvas, you need to first move the transformation-origin so it's at the center of our camera, then you can scale your canvas, and finally you just go back again by half the size of the canvas so your drawings are in the center. This can be achieved quite easily by two calls: one to the absolute setTransform, which is able to apply both the scaling and the initial translation required to set our transformation origin, and one to translate, which is relative to the current transform matrix:
const canvas = document.getElementById( 'canvas' );
const ctx = canvas.getContext( '2d' );
const scale = 2;
const cx = canvas.width / 2;
const cy = canvas.height / 2;
// before transformation in red
ctx.fillStyle = 'red';
ctx.beginPath();
ctx.arc( cx, cy, 30, 0, Math.PI * 2);
ctx.fill();
// with transformation in semi-opaque green
ctx.globalAlpha = 0.5;
ctx.fillStyle = 'green';
// scale with origin set to the center of canvas
ctx.setTransform(scale, 0, 0, scale, cx, cy);
// move back origin to the new top left corner of the visible area
ctx.translate( -cx, -cy );
/* the two previous lines are effectively the same as
ctx.translate( cx, cy );
ctx.scale( scale, scale );
ctx.translate( -cx, -cy );
and as
ctx.setTransform(
scale, 0, 0,
scale, cx - (cx * scale) , cy - (cy * scale)
);
*/
ctx.beginPath();
ctx.arc( cx, cy, 30, 0, Math.PI * 2);
ctx.fill();
canvas { border: 1px solid }
<canvas id="canvas"></canvas>
As you can already see in this little example, you are not limited to a single transformation matrix per frame, you can very well compose your image by layering a few different transformations:
const canvas = document.getElementById( 'canvas' );
const ctx = canvas.getContext( '2d' );
const { width, height } = canvas;
const cx = width / 2;
const cy = height / 2;
const transform = {
angle: 0,
scale: 1,
x: 0,
y: 0
};
const img = new Image();
img.onload = anim;
img.src = "https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png";
function anim() {
updateTransform();
draw();
requestAnimationFrame( anim );
}
function updateTransform() {
transform.x += Math.cos( transform.angle );
transform.y += Math.sin( transform.angle );
transform.scale = (Math.sin( transform.angle ) / 3) + 1;
transform.angle += Math.PI / 180;
}
function draw() {
// reset the transformation matrix
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect( 0, 0, width, height );
drawImage();
drawCircleOnImage();
drawCentralSquare();
drawCameraCross();
}
// the image is moved by our 'tranform' object
function drawImage() {
ctx.setTransform( transform.scale, 0, 0, transform.scale, transform.x, transform.y );
ctx.drawImage( img, 0, 0 );
}
// A circle which will be moved with the image
function drawCircleOnImage() {
ctx.setTransform( transform.scale, 0, 0, transform.scale, transform.x, transform.y );
ctx.beginPath();
ctx.arc( cx, cy, 50, 0, Math.PI * 2 );
ctx.stroke()
}
// a square, always at the center of view,
// but which scale follows our transform object
function drawCentralSquare() {
ctx.setTransform( transform.scale, 0, 0, transform.scale, cx, cy );
ctx.translate( -cx, -cy );
ctx.strokeRect( cx - 50, cy - 50, 100, 100 );
}
// a cross, always at the center of the view, and untransformed
function drawCameraCross() {
ctx.setTransform( 1, 0, 0, 1, 0, 0 );
ctx.beginPath();
ctx.moveTo( cx - 10, cy );
ctx.lineTo( cx + 10, cy );
ctx.moveTo( cx, cy - 10 );
ctx.lineTo( cx, cy + 10 );
ctx.stroke();
}
canvas { border: 1px solid }
<canvas id="canvas" width="800" height="600"></canvas>
And this with no maths from our part.

You have to consider your scale so ex (width/2)/scale
var canvas = document.getElementById('mycanvas');
context = canvas.getContext("2d");
context.canvas.width = 400;
context.canvas.height = 200;
context.clearRect(context.canvas.width, context.canvas.height, context.canvas.width, context.canvas.height); // Clears the canvas
var scale = 2;
context.scale(scale, scale);
context.beginPath();
context.fillStyle = "#ff2626"; // Red color
context.arc((context.canvas.width/2)/scale , (context.canvas.height/2)/scale, 10, 0, Math.PI * 2); //center
context.fill();
context.closePath();
canvas {
border: solid 1px #ccc;
}
<HTML>
<body>
<canvas id="mycanvas"></canvas>
</body>
</HTML>

Related

How to draw an isometric 3D cube with 3 perfectly identical faces with fillRect?

I would like to create an isometric 3D cube with fillRect whose 3 faces have the same dimensions as the image below:
Edit: I want to do it with fillRect. The reason for this is that I will draw images on the 3 faces of the cube afterwards. This will be very easy to do since I will use exactly the same transformations as for drawing the faces.
Edit 2: I didn't specify that I want to avoid using an external library so that the code is as optimized as possible. I know that it is possible to calculate the 3 matrices beforehand to draw the 3 faces and make a perfect isometric cube.
Edit 3: As my example code showed, I want to be able to set the size of the side of the isometric cube on the fly (const faceSize = 150).
I have a beginning of code but I have several problems:
The faces are not all the same dimensions
I don't know how to draw the top face
const faceSize = 150;
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
// Top Face (not big enough)
ctx.save();
ctx.translate(centerX, centerY);
ctx.scale(1, .5);
ctx.rotate(-45 * Math.PI / 180);
ctx.fillStyle = 'yellow';
ctx.fillRect(0, -faceSize, faceSize, faceSize);
ctx.restore();
// Left Face (not high enough)
ctx.save();
ctx.translate(centerX, centerY);
ctx.transform(1, .5, 0, 1, 0, 0);
ctx.fillStyle = 'red';
ctx.fillRect(-faceSize, 0, faceSize, faceSize);
ctx.restore();
// Right Face (not high enough)
ctx.save();
ctx.translate(centerX, centerY);
ctx.transform(1, -.5, 0, 1, 0, 0);
ctx.fillStyle = 'blue';
ctx.fillRect(0, 0, faceSize, faceSize);
ctx.restore();
<canvas width="400" height="400"></canvas>
I used a large part of #enhzflep's code which I adapted so that the width of the cube is dynamically changeable.
All the code seems mathematically correct, I just have a doubt about the value 1.22 given as a parameter to scaleSelf. Why was this precise value chosen?
Here is the code:
window.addEventListener('load', onLoad, false);
const canvas = document.createElement('canvas');
function onLoad() {
//canvas.width = cubeWidth;
//canvas.height = faceSize * 2;
canvas.width = 400;
canvas.height = 400;
document.body.appendChild(canvas);
drawCube(canvas);
}
function drawCube() {
const scale = Math.abs(Math.sin(Date.now() / 1000) * canvas.width / 200); // scale effect
const faceSize = 100 * scale;
const radians = 30 * Math.PI / 180;
const cubeWidth = faceSize * Math.cos(radians) * 2;
const centerPosition = {
x: canvas.width / 2,
y: canvas.height / 2
};
const ctx = canvas.getContext('2d');
ctx.save();
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
const defaultMat = [1, 0, 0, 1, 0, 0];
// Left (red) side
const leftMat = new DOMMatrix(defaultMat);
leftMat.translateSelf(centerPosition.x - cubeWidth / 2, centerPosition.y - faceSize / 2);
leftMat.skewYSelf(30);
ctx.setTransform(leftMat);
ctx.fillStyle = '#F00';
ctx.fillRect(0, 0, cubeWidth / 2, faceSize);
// Right (blue) side
const rightMat = new DOMMatrix(defaultMat);
rightMat.translateSelf(centerPosition.x, centerPosition.y);
rightMat.skewYSelf(-30);
ctx.setTransform(rightMat);
ctx.fillStyle = '#00F';
ctx.fillRect(0, 0, cubeWidth / 2, faceSize);
// Top (yellow) side
const topMat = new DOMMatrix(defaultMat);
const toOriginMat = new DOMMatrix(defaultMat);
const fromOriginMat = new DOMMatrix(defaultMat);
const rotMat = new DOMMatrix(defaultMat);
const scaleMat = new DOMMatrix(defaultMat);
toOriginMat.translateSelf(-faceSize / 2, -faceSize / 2);
fromOriginMat.translateSelf(centerPosition.x, centerPosition.y - faceSize / 2);
rotMat.rotateSelf(0, 0, -45);
scaleMat.scaleSelf(1.22, (faceSize / cubeWidth) * 1.22);
topMat.preMultiplySelf(toOriginMat);
topMat.preMultiplySelf(rotMat);
topMat.preMultiplySelf(scaleMat);
topMat.preMultiplySelf(fromOriginMat);
ctx.setTransform(topMat);
ctx.fillStyle = '#FF0';
ctx.fillRect(0, 0, faceSize, faceSize);
ctx.restore();
requestAnimationFrame(drawCube);
}
Here's a quick n dirty approach to the problem. It's too hot here for me to really think very clearly about this question. (I struggle with matrix maths too)
There's 2 things I think worth mentioning, each of which has an effect on the scaling operation.
width and height of the finished figure (and your posted example image) are different.
I think it's the ratio of the distance between (opposite) corners of the untransformed rectangle which fills 1/4 of the canvas, and the finished yellow side which affect the scaling.
Also, note that I'm drawing a square of canvas.height/2 sidelength for the yellow side, whereas I was drawing a rectangle for the red and blue sides.
In the scaling section, width/4 and height/4 are both shorthand for (width/2)/2 and (height/2)/2. width/2 and height/2 give you a rectangle filling 1/2 of the canvas, with a centre (middle of the square) located at (width/2)/2, (height/2)/2 - height/4 means something different in the translation section (even though it's the same number)
With that said, here's the sort of thing I was talking about earlier.
"use strict";
window.addEventListener('load', onLoaded, false);
function onLoaded(evt)
{
let width = 147;
let height = 171;
let canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
document.body.appendChild(canvas);
drawIsoDemo(canvas);
}
function drawIsoDemo(destCanvas)
{
let ctx = destCanvas.getContext('2d');
let width = destCanvas.width;
let height = destCanvas.height;
ctx.fillStyle = '#000';
ctx.fillRect(0,0,width,height);
var idMatVars = [1,0, 0,1, 0,0];
// left (red) side
let leftMat = new DOMMatrix( idMatVars );
leftMat.translateSelf( 0, 0.25*height );
leftMat.skewYSelf(30);
ctx.save();
ctx.transform( leftMat.a, leftMat.b, leftMat.c, leftMat.d, leftMat.e, leftMat.f);
ctx.fillStyle = '#F00';
ctx.fillRect(0,0,width/2,height/2);
ctx.restore();
// right (blue) side
let rightMat = new DOMMatrix( idMatVars );
rightMat.translateSelf( 0.5*width, 0.5*height );
rightMat.skewYSelf(-30);
ctx.save();
ctx.transform( rightMat.a, rightMat.b, rightMat.c, rightMat.d, rightMat.e, rightMat.f);
ctx.fillStyle = '#00F';
ctx.fillRect(0,0,width/2,height/2);
ctx.restore();
// top (yellow) side
let topMat = new DOMMatrix( idMatVars );
let toOriginMat = new DOMMatrix( idMatVars );
let fromOriginMat = new DOMMatrix(idMatVars);
let rotMat = new DOMMatrix(idMatVars);
let scaleMat = new DOMMatrix(idMatVars);
toOriginMat.translateSelf(-height/4, -height/4);
fromOriginMat.translateSelf(width/2,height/4);
rotMat.rotateSelf(0,0,-45);
scaleMat.scaleSelf(1.22,((height/2)/width)*1.22);
topMat.preMultiplySelf(toOriginMat);
topMat.preMultiplySelf(rotMat);
topMat.preMultiplySelf(scaleMat);
topMat.preMultiplySelf(fromOriginMat);
ctx.save();
ctx.transform( topMat.a, topMat.b, topMat.c, topMat.d, topMat.e, topMat.f);
ctx.fillStyle = '#FF0';
ctx.fillRect(0,0,height/2,height/2);
ctx.restore();
}
If we overlay a circle on your isometric cube, we can see that the outer vertices are spaced equally apart. In fact it's always 60°, which is no wonder as it's a hexagon.
So all we have to do is obtaining the coordinates for the outer vertices. This is quite easy as we can make a further assumption: if you look at the shape again, you'll notice that the length of each of the cube's sides seems to be the radius of the circle.
With the help of a little trigonometry and a for-loop which increments by 60 degrees, we can put calculate and put all those vertices into an array and finally connect those vertices to draw the cube.
Here's an example:
let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
function drawCube(x, y, sideLength) {
let vertices = [new Point(x, y)];
for (let a = 0; a < 6; a++) {
vertices.push(new Point(x + Math.cos(((a * 60) - 30) * Math.PI / 180) * sideLength, y + Math.sin(((a * 60) - 30) * Math.PI / 180) * sideLength));
}
ctx.fillStyle = "#ffffff";
ctx.beginPath();
ctx.moveTo(vertices[0].x, vertices[0].y);
ctx.lineTo(vertices[5].x, vertices[5].y);
ctx.lineTo(vertices[6].x, vertices[6].y);
ctx.lineTo(vertices[1].x, vertices[1].y);
ctx.lineTo(vertices[0].x, vertices[0].y);
ctx.fill();
ctx.fillStyle = "#a0a0a0";
ctx.beginPath();
ctx.moveTo(vertices[0].x, vertices[0].y);
ctx.lineTo(vertices[1].x, vertices[1].y);
ctx.lineTo(vertices[2].x, vertices[2].y);
ctx.lineTo(vertices[3].x, vertices[3].y);
ctx.lineTo(vertices[0].x, vertices[0].y);
ctx.fill();
ctx.fillStyle = "#efefef";
ctx.beginPath();
ctx.moveTo(vertices[0].x, vertices[0].y);
ctx.lineTo(vertices[3].x, vertices[3].y);
ctx.lineTo(vertices[4].x, vertices[4].y);
ctx.lineTo(vertices[5].x, vertices[5].y);
ctx.lineTo(vertices[0].x, vertices[0].y);
ctx.fill();
}
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
drawCube(200, 150, 85);
canvas {
background: #401fc1;
}
<canvas id="canvas" width="400" height="300"></canvas>
EDIT
What you want to achieve is ain't that easily simply because the CanvasRenderingContext2D API actually does not offer a skewing/shearing transform.
Nevertheless with the help of a third-party library we're able to transform the three sides in an orthographic way. It's called perspective.js
Still we need to calculate the outer vertices but instead of using the moveTo/lineTo commands, we forward the coordinates to perspective.js to actually do the perspective distortion of some source images.
Here's another example:
let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
function drawCube(x, y, sideLength) {
let vertices = [new Point(x, y)];
for (let a = 0; a < 6; a++) {
vertices.push(new Point(x + Math.cos(((a * 60) - 30) * Math.PI / 180) * sideLength, y + Math.sin(((a * 60) - 30) * Math.PI / 180) * sideLength));
}
let p = new Perspective(ctx, images[0]);
p.draw([
[vertices[5].x, vertices[5].y],
[vertices[6].x, vertices[6].y],
[vertices[1].x, vertices[1].y],
[vertices[0].x, vertices[0].y]
]);
p = new Perspective(ctx, images[1]);
p.draw([
[vertices[0].x, vertices[0].y],
[vertices[1].x, vertices[1].y],
[vertices[2].x, vertices[2].y],
[vertices[3].x, vertices[3].y]
]);
p = new Perspective(ctx, images[2]);
p.draw([
[vertices[4].x, vertices[4].y],
[vertices[5].x, vertices[5].y],
[vertices[0].x, vertices[0].y],
[vertices[3].x, vertices[3].y]
]);
}
function loadImages(index) {
let image = new Image();
image.onload = function(e) {
images.push(e.target);
if (index + 1 < sources.length) {
loadImages(index + 1);
} else {
drawCube(200, 150, 125, e.target);
}
}
image.src = sources[index];
}
let sources = ["https://picsum.photos/id/1079/200/300", "https://picsum.photos/id/76/200/300", "https://picsum.photos/id/79/200/300"];
let images = [];
loadImages(0);
canvas {
background: #401fc1;
}
<script src="https://cdn.rawgit.com/wanadev/perspective.js/master/dist/perspective.min.js"></script>
<canvas id="canvas" width="400" height="300"></canvas>

HTML5 Canvas animate the rotation of image around Y axis

I have an image on a canvas:
var canvas = document.getElementsByTagName("canvas")[0];
var ctx = canvas.getContext("2d");
var img = new Image();
img.onload = function(){
ctx.drawImage(img, 0, 0, 200, 350);
}
img.src = "/img.png";
Can an image be rotated around the Y axis? The 2d Canvas API only seems to have a rotate function that rotates around the Z-axis.
You can rotate around whatever axis you want. Using the save-transform-restore method covered in the linked question, we can do something similar by transforming a DOMMatrix object and applying it to the canvas.
Sample:
// Untransformed draw position
const position = {x: 0, y: 0};
// In degrees
const rotation = { x: 0, y: 0, z: 0};
// Rotation relative to here (this is the center of the image)
const rotPt = { x: img.width / 2, y: img.height / 2 };
ctx.save();
ctx.setTransform(new DOMMatrix()
.translateSelf(position.x + rotPt.x, position.y + rotPt.y)
.rotateSelf(rotation.x, rotation.y, rotation.z)
);
ctx.drawImage(img, -rotPt.x, -rotPt.y);
ctx.restore();
This isn't a "true" 3d, of course (the rendering context is "2d" after all). I.e. It's not a texture applied to some polygons. All it's doing is rotating and scaling the image to give the illusion. If you want that kind of functionality, you'll want to look at a WebGL library.
Demo:
I drew a cyan rectangle around the image to show the untransformed position.
Image source from MDN (see snippet for url).
const canvas = document.getElementsByTagName("canvas")[0];
const ctx = canvas.getContext("2d");
const img = new Image();
img.onload = draw;
img.src = "https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage/canvas_drawimage.jpg";
let rotCounter = 0;
let motionCounter = 0;
function draw() {
// Untransformed draw position
const position = {
x: motionCounter % canvas.width,
y: motionCounter % canvas.height
};
// In degrees
const rotation = {
x: rotCounter * 1.2,
y: rotCounter,
z: 0
};
// Rotation relative to here
const rotPt = {
x: img.width / 2,
y: img.height / 2
};
ctx.save();
ctx.setTransform(new DOMMatrix()
.translateSelf(position.x + rotPt.x, position.y + rotPt.y)
.rotateSelf(rotation.x, rotation.y, rotation.z)
);
// Rotate relative to this point
ctx.drawImage(img, -rotPt.x, -rotPt.y);
ctx.restore();
// Position
ctx.strokeStyle = 'cyan';
ctx.strokeRect(position.x, position.y, img.width, img.height);
ctx.strokeStyle = 'black';
rotCounter++;
motionCounter++;
}
function render() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
draw();
requestAnimationFrame(render);
}
render();
canvas {
border: 1px solid black;
}
<canvas width=600 height=400></canvas>
Rotate without perceptive
If you only want to rotate around the y Axis without perspective then you can easily do it on the 2D canvas as follows.
Assuming you have loaded the image the following will rotate around the image Y axis and have the y axis align to an arbitrary line.
The arguments for rotateImg(img, axisX, axisY, rotate, centerX, centerY)
img a valid image.
axisX, axisY vector that is the direction of the y axis. To match image set to - axisX = 0, axisY = 1
rotate amount to rotate around image y axis in radians. 0 has image facing screen.
centerX & centerY where to place the center of the image
function rotateImg(img, axisX, axisY, rotate, centerX, centerY) {
const iw = img.naturalWidth;
const ih = img.naturalHeight;
// Normalize axis
const axisLen = Math.hypot(axisX, axisY);
const nAx = axisX / axisLen;
const nAy = axisY / axisLen;
// Get scale along image x to match rotation
const wScale = Math.cos(rotate);
// Set transform to draw content
ctx.setTransform(nAy * wScale, -nAx * wScale, nAx, nAy, centerX, centerY);
// Draw image normally relative to center. In this case half width and height up
// to the left.
ctx.drawImage(img, -iw * 0.5, -ih * 0.5, iw, ih);
// to reset transform use
// ctx.setTransform(1,0,0,1,0,0);
}
Demo
The demo uses a slightly modified version of the above function. If given an optional color as the last argument. It will shade the front face using the color and render the back face as un-shaded with that color.
The demo rotates the image and slowly rotates the direction of the image y axis.
const ctx = canvas.getContext("2d");
var W = canvas.width, H = canvas.height;
const img = new Image;
img.src = "https://i.stack.imgur.com/C7qq2.png?s=256&g=1";
img.addEventListener("load", () => requestAnimationFrame(renderLoop), {once:true});
function rotateImg(img, axisX, axisY, rotate, centerX, centerY, backCol) {
const iw = img.naturalWidth;
const ih = img.naturalHeight;
const axisLen = Math.hypot(axisX, axisY);
const nAx = axisX / axisLen;
const nAy = axisY / axisLen;
const wScale = Math.cos(rotate);
ctx.setTransform(nAy * wScale, -nAx * wScale, nAx, nAy, centerX, centerY);
ctx.globalAlpha = 1;
ctx.drawImage(img, -iw * 0.5, -ih * 0.5, iw, ih);
if (backCol) {
ctx.globalAlpha = wScale < 0 ? 1 : 1 - wScale;
ctx.fillStyle = backCol;
ctx.fillRect(-iw * 0.5, -ih * 0.5, iw, ih);
}
}
function renderLoop(time) {
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(0, 0, W, H);
rotateImg(img, Math.cos(time / 4200), Math.sin(time / 4200), time / 500, W * 0.5, H * 0.5, "#268C");
requestAnimationFrame(renderLoop);
}
canvas {border: 1px solid black; background: #147;}
<canvas id="canvas" width="300" height="300"></canvas>

How do I rotate an object in canvas and js; my example is not rotating how I expect

I'm trying to rotate a rectangle about it's center but it's not rotating how I expect.
Here's an example:
https://jsfiddle.net/37ur8dfk/1/
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');
let x = 100;
let y = 100;
let w = 100;
let h = 50;
// Draw a red dot to highlight the point I want to rotate the rectangle around
ctx.fillStyle = '#ff0000';
ctx.beginPath();
ctx.arc(x, y, 4, 0, 2 * Math.PI);
ctx.closePath();
ctx.fill();
// Attempt to rotate the rectangle aroumd x,y
ctx.save();
ctx.fillStyle = '#000000';
ctx.translate(-x, -y);
ctx.rotate(10 * Math.PI/180);
ctx.translate(x, y);
ctx.fillRect(x - w/2, y - h/2, w, h);
ctx.restore();
I have the center of the rectangle as x,y coords. I then translate it by -x,-y to change it's origin to 0,0. Then I rotate it by some degrees, but it does not seem to be rotating about the 0,0 coords. It's my understanding that rotate should rotate the entire context about the origin, or 0,0.
Please take a look at the jsfiddle to see what I mean.
What am I missing here?
You got it inversed.
You are not translating the rectangle, but the context's transformation matrix.
Think of this as a sheet of paper and an arm with pen.
When you translate your context, the arm is moving in the direction provided. When you rotate the context, the arm is rotating.
So to set your rectangle's center as the rotation origin you first need to move the arm so that the pen is in the center of the rectangle, then you'll be able to rotate. And we move back the arm to its initial position so that the x and y coords match.
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
let x = 100;
let y = 100;
let w = 100;
let h = 50;
// Attempt to rotate the rectangle aroumd x,y
ctx.save();
ctx.fillStyle = '#000000';
// move to transformation-origin
ctx.translate(x, y);
// transform
ctx.rotate(10 * Math.PI/180);
// go back to where we were
ctx.translate(-x, -y);
ctx.fillRect(x - w/2, y - h/2, w, h);
ctx.restore();
// Draw a red dot to highlight the point I want to rotate the rectangle around
ctx.fillStyle = '#ff0000';
ctx.beginPath();
ctx.arc(x, y, 4, 0, 2 * Math.PI);
ctx.closePath();
ctx.fill();
<canvas id="canvas" width="500" height="400"></canvas>
Try (This will allow a rotation around the dot)
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');
let x = 100;
let y = 100;
let w = 100;
let h = 50;
let angle = Math.PI/8;
ctx.save();
ctx.fillStyle = '#000000';
ctx.translate(x, y);
ctx.rotate(angle);
ctx.fillRect(0, 0, w, h);
ctx.restore();
ctx.save();
ctx.fillStyle = '#ff0000';
ctx.beginPath();
ctx.arc(x, y, 4, 0, 2 * Math.PI);
ctx.closePath();
ctx.fill();
ctx.restore();
canvas {
border: 1px solid black;
}
<canvas id="canvas" width="500" height="400"></canvas>

How to make a circle appear on any color html5 canvas

I have an arc on a canvas that moves around wherever the mouse is. it is stroked onto the canvas with the color black. if the mouse goes over something black, the circle disappears. i would like it if the circle could change color, depending on what it is being drawn over. could anyone help me?
here is some code:
ctx.beginPath()
ctx.clearRect(0, 0, brush.prePos.x + brush.size*2, brush.prePos.y + brush.size*2)
ctx.arc(pos.x, pos.y, brush.size / 4, 0, Math.PI*2)
ctx.stroke()
ctx.closePath()
You can try different compositing modes, particularly XOR. From the MDN example:
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
ctx.globalCompositeOperation = 'xor';
#SamiHult's answer is a good answer. Using globalCompositeOperation will do the trick. Here comes a demo:
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
let cw = canvas.width = 300,
cx = cw / 2;
let ch = canvas.height = 300,
cy = ch / 2;
// the mouse
let m = {}
// draw a black circle
ctx.beginPath();
ctx.arc(100,100,45,0,2*Math.PI);
ctx.fill();
canvas.addEventListener("mousemove",(evt)=>{
m = oMousePos(canvas, evt);
ctx.clearRect(0,0,cw,ch);
// draw a circle stroked black following the mouse
drawCircle(m);
// draw a black circle
ctx.beginPath();
ctx.arc(100,100,45,0,2*Math.PI);
ctx.fill();
// the important part:
ctx.globalCompositeOperation = "xor";
})
function drawCircle(p){
ctx.beginPath();
ctx.arc(p.x,p.y,10,0,2*Math.PI);
ctx.stroke();
}
function oMousePos(canvas, evt) {
var ClientRect = canvas.getBoundingClientRect();
return { //objeto
x: Math.round(evt.clientX - ClientRect.left),
y: Math.round(evt.clientY - ClientRect.top)
}
}
canvas {
border:1px solid;
}
<canvas id="canvas"></canvas>

HTML 5 Canvas, rotate everything

I made a cylinder gauge, very similar to this one:
It is drawn using about 7 or so functions... mine is a little different. It is very fleixble in that I can set the colors, transparency, height, width, whether there is % text shown and a host of other options. But now I have a need for the same thing, but all rotated 90 deg so that I can set the height long and the width low to generate something more like this:
I found ctx.rotate, but no mater where it goes all the shapes fall apart.. ctx.save/restore appears to do nothing, I tried putting that in each shape drawing function. I tried modifying, for example, the drawOval function so that it would first rotate the canvas if horizontal was set to one; but it appeared to rotate it every single iteration, even with save/restore... so the top cylinder would rotate and the bottom would rotate twice or something. Very tough to tell what is really happening. What am I doing wrong? I don't want to duplicate all this code and spend hours customizing it, just to produce something I already have but turned horizontal. Erg! Help.
Option 1
To rotate everything just apply a transform to the element itself:
canvas.style.transform = "rotate(90deg)"; // or -90 depending on need
canvas.style.webkitTransform = "rotate(90deg)";
Option 2
Rotate context before drawing anything and before using any save(). Unlike the CSS version you will first need to translate to center, then rotate, and finally translate back.
You will need to make sure width and height of canvas is swapped before this is performed.
ctx.translate(ctx.canvas.width * 0.5, ctx.canvas.height * 0.5); // center
ctx.rotate(Math.PI * 0.5); // 90°
ctx.translate(-ctx.canvas.width * 0.5, -ctx.canvas.height * 0.5);
And of course, as an option 3, you can recalculate all your values to go along the other axis.
Look at the rotate function in this example. You want to do a translation to the point you want to rotate around.
example1();
example2();
function rotate(ctx, degrees, x, y, fn) {
ctx.save();
ctx.translate(x, y);
ctx.rotate(degrees * (Math.PI / 180));
fn();
ctx.restore();
}
function rad(deg) {
return deg * (Math.PI / 180);
}
function example2() {
var can = document.getElementById("can2");
var ctx = can.getContext('2d');
var w = can.width;
var h = can.height;
function drawBattery() {
var percent = 60;
ctx.beginPath();
ctx.arc(35,50, 25,0,rad(360));
ctx.moveTo(35+percent+25,50);
ctx.arc(35+percent,50,25,0,rad(360));
ctx.stroke();
ctx.beginPath();
ctx.fillStyle = "rgba(0,255,0,.5)";
ctx.arc(35,50,25,0,rad(360));
ctx.arc(35+percent,50,25,0,rad(360));
ctx.rect(35,25,percent,50);
ctx.fill();
ctx.beginPath();
ctx.lineWidth = 2;
ctx.strokeStyle = "#666666";
ctx.moveTo(135,25);
ctx.arc(135,50,25, rad(270), rad(269.9999));
//ctx.moveTo(35,75);
ctx.arc(35,50,25,rad(270),rad(90), true);
ctx.lineTo(135,75);
ctx.stroke();
}
drawBattery();
can = document.getElementById("can3");
ctx = can.getContext('2d');
w = can.width;
h = can.height;
rotate(ctx, -90, 0, h, drawBattery);
}
function example1() {
var can = document.getElementById('can');
var ctx = can.getContext('2d');
var color1 = "#FFFFFF";
var color2 = "#FFFF00";
var color3 = "rgba(0,155,255,.5)"
var text = 0;
function fillBox() {
ctx.save();
ctx.fillStyle = color3;
ctx.fillRect(0, 0, can.width / 2, can.height);
ctx.restore();
}
function drawBox() {
ctx.save();
ctx.beginPath();
ctx.strokeStyle = ctx.fillStyle = color1;
ctx.rect(10, 10, 50, 180);
ctx.font = "30px Arial";
ctx.fillText(text, 25, 45);
ctx.stroke();
ctx.beginPath();
ctx.strokeStyle = color2;
ctx.lineWidth = 10;
ctx.moveTo(10, 10);
ctx.lineTo(60, 10);
ctx.stroke();
ctx.restore();
}
fillBox();
rotate(ctx, 90, can.width, 0, fillBox);
text = "A";
drawBox();
color1 = "#00FFFF";
color2 = "#FF00FF";
text = "B";
rotate(ctx, 90, can.width, 0, drawBox);
centerRotatedBox()
function centerRotatedBox() {
ctx.translate(can.width / 2, can.height / 2);
for (var i = 0; i <= 90; i += 10) {
var radians = i * (Math.PI / 180);
ctx.save();
ctx.rotate(radians);
ctx.beginPath();
ctx.strokeStyle = "#333333";
ctx.rect(0, 0, 50, 50)
ctx.stroke();
ctx.restore();
}
}
}
#can,
#can2,
#can3 {
border: 1px solid #333333
}
<canvas id="can" width="200" height="200"></canvas>
<canvas id="can2" width="200" height="100"></canvas>
<canvas id="can3" width="100" height="200"></canvas>

Categories

Resources