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.
CodePudding user response:
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>
CodePudding user response:
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
¢erY
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>