I'm using DOMMatrix to set transform on a canvas context, but I am not sure how to "fix" the translation after the rotate. Currently the code I've written rotates an image around a given point on the canvas, which works fine. The issue is after the rotation, the translate moves relative to the rotation, which is not what I want. I want the translation to be relative to the canvas.
I implemented this function which I read "fixes" the translation after rotation, but it doesn't seem to do what I need:
function rotate(x, y, rotation) {
const panXX = x * Math.cos((rotation * Math.PI) / 180);
const panXY = y * Math.sin((rotation * Math.PI) / 180);
const panYY = y * Math.cos((rotation * Math.PI) / 180);
const panYX = x * Math.sin((rotation * Math.PI) / 180);
const panX = panXX panXY;
const panY = panYY - panYX;
return { x: panX, y: panY };
}
Is there a way of doing this, either by modifying the rotate
function above, or the DOMMatrix
, or a different way entirely?
let rotation = 0;
let scale = 1;
let x = 0;
let y = 0;
let startX = 0;
let startY = 0;
let lastX = 0;
let lastY = 0;
let pointerDown = false;
const canvas = document.querySelector("#canvas");
const ctx = canvas.getContext("2d");
const imgWidth = 480;
const imgHeight = 300;
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
resizeCanvas();
window.addEventListener("resize", resizeCanvas);
const img = new Image();
img.crossOrigin = "anonymous";
img.src = "https://i.imgur.com/3q3kNGh.png";
function onPointerDown(event) {
pointerDown = true;
startX = (event.clientX - canvas.offsetLeft) / imgWidth;
startY = (event.clientY - canvas.offsetTop) / imgHeight;
}
function onPointerMove(event) {
if (!pointerDown) return;
x = lastX ((event.clientX - canvas.offsetLeft) / imgWidth - startX);
y = lastY ((event.clientY - canvas.offsetTop) / imgHeight - startY);
}
function onPointerUp() {
pointerDown = false;
lastX = x;
lastY = y;
}
window.addEventListener("pointerdown", onPointerDown);
window.addEventListener("pointermove", onPointerMove);
window.addEventListener("pointerup", onPointerUp);
window.addEventListener("keydown", (event) => {
const key = event.key.toLowerCase();
switch (key) {
case "r":
rotation = (rotation 5) % 360;
break;
case "-":
scale = Math.max(0, scale - 0.1);
break;
case "=":
scale = Math.min(2, scale 0.1);
break;
default:
break;
}
});
function rotate(x, y, rotation) {
const panXX = x * Math.cos((rotation * Math.PI) / 180);
const panXY = y * Math.sin((rotation * Math.PI) / 180);
const panYY = y * Math.cos((rotation * Math.PI) / 180);
const panYX = x * Math.sin((rotation * Math.PI) / 180);
const panX = panXX panXY;
const panY = panYY - panYX;
return { x: panX, y: panY };
}
(function draw() {
requestAnimationFrame(draw);
const imgX = imgWidth * x;
const imgY = imgHeight * y;
const { x: tX, y: tY } = rotate(imgX, imgY, rotation);
const ox = canvas.width / 2 - imgX;
const oy = canvas.height / 2 - imgY;
const matrix = new DOMMatrix()
.translate(ox, oy)
.rotate(rotation)
.translate(-ox, -oy)
.translate(tX, tY)
.scale(scale);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.setTransform(matrix);
ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
ctx.resetTransform();
ctx.fillStyle = "rgba(255, 0, 0, 0.5)";
ctx.fillRect(canvas.width / 2 - 5, canvas.height / 2 - 5, 10, 10);
})();
html,
body {
margin: 0;
padding: 0;
}
canvas {
display: block;
}
pre {
position: absolute;
bottom: 0;
left: 0;
padding: 0.5em;
pointer-events: none;
user-select: none;
}
<canvas id="canvas"></canvas>
<pre>
Hotkeys
---
Rotate: r
Zoom out: -
Zoom in: =
</pre>
CodePudding user response:
Applying rotate()
to mouse-movements, it might work as expected.
function onPointerMove(event) {
if (!pointerDown) return;
//x = lastX ((event.clientX - canvas.offsetLeft) / imgWidth - startX);
//y = lastY ((event.clientY - canvas.offsetTop) / imgHeight - startY);
let deltaX = (event.clientX - canvas.offsetLeft) / imgWidth - startX;
let deltaY = (event.clientY - canvas.offsetTop) / imgHeight - startY;
const { x: dX, y: dY } = rotate(deltaX * imgWidth, deltaY * imgHeight, rotation);
x = lastX dX / imgWidth;
y = lastY dY / imgHeight;
}
CodePudding user response:
You are close. Your order of transformations were wrong. Here is the correct one:
const matrix = new DOMMatrix()
.translate(imgWidth/2, imgHeight/2)
.rotate(rotation)
.scale(scale)
.translate(-imgWidth/2, -imgHeight/2)
.translate(tX / scale, tY / scale);
You are forgetting that css transformations and canvas/svg transformations are a little bit different. In CSS the origin is the center of the element. In SVG/canvas, the origin is the upper left corner (0,0). IN the latter case, center the object to the origin, apply scale/rotate and THEN do the transformation.