我使用 HTML5 画布和 Vanilla Javascript 创建了一个小型 HTML5 图像查看器。
一切都运行良好,除了一件事,那就是旋转、翻转和镜像等变换后的平移。画布向错误的方向移动,这是完全有道理的。
我能够解决这个问题,“旋转后平移”。我有一个矩阵,用于校正不同角度(0、90、180 和 270 度)。也许不是最漂亮的解决方案,但它确实有效。
但是当我翻转或镜像画布时,画布仍然向错误的方向移动。现在我可以尝试以类似的方式解决它,就像我解决“旋转后平移”问题一样,但这似乎不对。我知道应该采取不同的做法,但是怎么做呢?
在我的代码中,我已注释掉“旋转后平移”解决方案。有人可以给我一个例子,如何以正确的方式解决这个问题?
代码有限的Codepen: https://codepen.io/r-w-c/pen/RwEZjGr
Codepen 包含查看器的完整代码: https://codepen.io/r-w-c/pen/dywRKJL
const IMGURL = "https://i.imgur.com/hjNvQge.jpg";
const CANVASWITH = 500;
const CANVASHEIGHT = 325;
const PANSTEP = 20;
/*
var rotateMatrix = {};
rotateMatrix.left = [];
rotateMatrix.left[0] = [-1, 0];
rotateMatrix.left[90] = [0, 1];
rotateMatrix.left[180] = [1, 0];
rotateMatrix.left[270] = [0, -1];
rotateMatrix.right = [];
rotateMatrix.right[0] = [1, 0];
rotateMatrix.right[90] = [0, -1];
rotateMatrix.right[180] = [-1, 0];
rotateMatrix.right[270] = [0, 1];
rotateMatrix.up = [];
rotateMatrix.up[0] = [0, -1];
rotateMatrix.up[90] = [-1, 0];
rotateMatrix.up[180] = [0, 1];
rotateMatrix.up[270] = [1, 0];
rotateMatrix.down = [];
rotateMatrix.down[0] = [0, 1];
rotateMatrix.down[90] = [1, 0];
rotateMatrix.down[180] = [0, -1];
rotateMatrix.down[270] = [-1, 0];
*/
var canvas,
context,
image,
scaledImgWidth,
scaledImgHeight,
startX,
startY,
canvasCenterX,
canvasCenterY,
scale,
currentRotation;
window.onload = () => {
loadImage();
addEventListeners();
};
function loadImage() {
canvas = document.getElementById("canvas");
canvas.width = CANVASWITH;
canvas.height = CANVASHEIGHT;
context = canvas.getContext("2d");
image = new Image();
image.onload = function () {
init();
reset();
};
image.src = IMGURL;
}
function init() {
scale = Math.min(canvas.width / image.width, canvas.height / image.height);
scaledImgWidth = image.width * scale;
scaledImgHeight = image.height * scale;
startX = (canvas.width - scaledImgWidth) / 2;
startY = (canvas.height - scaledImgHeight) / 2;
canvasCenterX = canvas.width / 2;
canvasCenterY = canvas.height / 2;
context.save();
}
function drawImageToCanvas() {
context.save();
context.setTransform(1, 0, 0, 1, 0, 0);
context.clearRect(0, 0, canvas.width, canvas.height);
context.restore();
context.drawImage(image, startX, startY, scaledImgWidth, scaledImgHeight);
}
function pan(x, y) {
context.translate(x, y);
drawImageToCanvas();
}
function rotate(degrees) {
context.translate(canvasCenterX, canvasCenterY);
context.rotate((Math.PI / 180) * degrees);
context.translate(-canvasCenterX, -canvasCenterY);
drawImageToCanvas();
}
function mirror() {
context.translate(canvasCenterX, canvasCenterY);
context.scale(-1, 1);
context.translate(-canvasCenterX, -canvasCenterY);
drawImageToCanvas();
}
function flip() {
context.translate(canvasCenterX, canvasCenterY);
context.scale(1, -1);
context.translate(-canvasCenterX, -canvasCenterY);
//currentRotation = (currentRotation + 360 + 180) % 360;
drawImageToCanvas();
}
function reset() {
context.restore();
context.save();
drawImageToCanvas();
//currentRotation = 0;
}
function addEventListeners() {
document.getElementById("rotateRight").addEventListener("click", function () {
//currentRotation = (currentRotation + 90) % 360;
rotate(90);
});
document
.getElementById("rotateLeft")
.addEventListener("click", function (event) {
//currentRotation = (currentRotation + 270) % 360;
rotate(-90);
});
document.getElementById("mirror").addEventListener("click", function (event) {
mirror();
});
document.getElementById("flip").addEventListener("click", function (event) {
flip();
});
document
.getElementById("panLeft")
.addEventListener("click", function (event) {
pan(-PANSTEP, 0);
//pan(
//rotateMatrix.left[currentRotation][0] * PANSTEP,
//rotateMatrix.left[currentRotation][1] * PANSTEP
//);
});
document
.getElementById("panRight")
.addEventListener("click", function (event) {
pan(PANSTEP, 0);
//pan(
//rotateMatrix.right[currentRotation][0] * PANSTEP,
//rotateMatrix.right[currentRotation][1] * PANSTEP
//);
});
document.getElementById("panUp").addEventListener("click", function (event) {
pan(0, -PANSTEP);
//pan(
//rotateMatrix.up[currentRotation][0] * PANSTEP,
//rotateMatrix.up[currentRotation][1] * PANSTEP
//);
});
document
.getElementById("panDown")
.addEventListener("click", function (event) {
pan(0, PANSTEP);
//pan(
//rotateMatrix.down[currentRotation][0] * PANSTEP,
//rotateMatrix.down[currentRotation][1] * PANSTEP
//);
});
}
.canvas-container {
flex-direction: column;
border: 2px solid #333;
padding: 10px;
}
canvas {
background: #777;
cursor: crosshair;
}
.canvas-controls {
padding: 5px;
}
button span {
display: inline-block;
width: 30px;
}
span.rot {
transform: rotate(90deg);
}
<div class="canvas-container">
<canvas id="canvas">
</canvas>
<div class="canvas-controls flex">
<button id="panLeft" title="Pan left"><span>⇐</span></button>
<button id="panRight" title="Pan right"><span>⇒</span></button>
<button id="panUp" title="Pan up"><span>⇑</span></button>
<button id="panDown" title="Pan down"><span>⇓</span></button>
<button id="rotateLeft" title="Rotate left"><span>⟲</span></button>
<button id="rotateRight" title="Rolotate right"><span>⟳</span></button>
<button id="mirror" title="Mirror"><span>⇹</span></button>
<button id="flip" title="Flip"><span class="rot">⇹</span></button>
</div>
</div>
更多
翻转或镜像画布后平移时遇到的问题与画布变换如何影响平移方向有关。当您应用翻转或镜像等变换时,它会更改画布的坐标系,从而影响平移的方向。
要正确解决此问题,您需要跟踪画布的当前变换状态并相应地调整平移。您可以通过维护变换矩阵并将其应用于平移操作来实现此目的。您可以通过以下方式修改代码来实现此目的:
const transformMatrix = {
a: 1, // Horizontal scaling
b: 0, // Vertical skewing
c: 0, // Horizontal skewing
d: 1, // Vertical scaling
e: 0, // Horizontal translation (panning)
f: 0, // Vertical translation (panning)
};
function applyMatrix() {
context.setTransform(
transformMatrix.a,
transformMatrix.b,
transformMatrix.c,
transformMatrix.d,
transformMatrix.e,
transformMatrix.f
);
}
function pan(x, y) {
transformMatrix.e += x;
transformMatrix.f += y;
applyMatrix();
drawImageToCanvas();
}
function mirror() {
transformMatrix.a *= -1; // Flip horizontally
applyMatrix();
drawImageToCanvas();
}
function flip() {
transformMatrix.d *= -1; // Flip vertically
applyMatrix();
drawImageToCanvas();
}
function reset() {
// Reset the transformation matrix
transformMatrix.a = 1;
transformMatrix.b = 0;
transformMatrix.c = 0;
transformMatrix.d = 1;
transformMatrix.e = 0;
transformMatrix.f = 0;
applyMatrix();
drawImageToCanvas();
}
通过这些修改,您现在可以跟踪当前的变换矩阵并使用 setTransform 将其应用到画布。这可确保平移、翻转和镜像正确地协同工作,因为它们考虑了画布的当前变换状态。
请记住在事件侦听器中相应地更新平移、镜像和翻转功能。通过这些更改,您的画布在翻转或镜像后应该可以正确平移,而无需针对不同角度添加任何额外的矩阵。
代码片段:
const IMGURL = "https://i.imgur.com/hjNvQge.jpg";
const CANVASWITH = 500;
const CANVASHEIGHT = 325;
const PANSTEP = 20;
const transformMatrix = {
a: 1, // Horizontal scaling
b: 0, // Vertical skewing
c: 0, // Horizontal skewing
d: 1, // Vertical scaling
e: 0, // Horizontal translation (panning)
f: 0, // Vertical translation (panning)
};
var canvas,
context,
image,
scaledImgWidth,
scaledImgHeight,
startX,
startY,
canvasCenterX,
canvasCenterY,
scale;
window.onload = () => {
loadImage();
addEventListeners();
};
function loadImage() {
canvas = document.getElementById("canvas");
canvas.width = CANVASWITH;
canvas.height = CANVASHEIGHT;
context = canvas.getContext("2d");
image = new Image();
image.onload = function() {
init();
reset();
};
image.src = IMGURL;
}
function init() {
scale = Math.min(canvas.width / image.width, canvas.height / image.height);
scaledImgWidth = image.width * scale;
scaledImgHeight = image.height * scale;
startX = (canvas.width - scaledImgWidth) / 2;
startY = (canvas.height - scaledImgHeight) / 2;
canvasCenterX = canvas.width / 2;
canvasCenterY = canvas.height / 2;
context.save();
}
function drawImageToCanvas() {
context.save();
context.setTransform(1, 0, 0, 1, 0, 0);
context.clearRect(0, 0, canvas.width, canvas.height);
context.restore();
context.drawImage(image, startX, startY, scaledImgWidth, scaledImgHeight);
}
function pan(x, y) {
transformMatrix.e += x;
transformMatrix.f += y;
applyMatrix();
drawImageToCanvas();
}
function rotate(degrees) {
context.translate(canvasCenterX, canvasCenterY);
context.rotate((Math.PI / 180) * degrees);
context.translate(-canvasCenterX, -canvasCenterY);
drawImageToCanvas();
}
function mirror() {
transformMatrix.a *= -1; // Flip horizontally
applyMatrix();
drawImageToCanvas();
}
function flip() {
transformMatrix.d *= -1; // Flip vertically
applyMatrix();
drawImageToCanvas();
}
function reset() {
// Reset the transformation matrix
transformMatrix.a = 1;
transformMatrix.b = 0;
transformMatrix.c = 0;
transformMatrix.d = 1;
transformMatrix.e = 0;
transformMatrix.f = 0;
applyMatrix();
drawImageToCanvas();
}
function applyMatrix() {
context.setTransform(
transformMatrix.a,
transformMatrix.b,
transformMatrix.c,
transformMatrix.d,
transformMatrix.e,
transformMatrix.f
);
}
function addEventListeners() {
document.getElementById("rotateRight").addEventListener("click", function() {
rotate(90);
});
document
.getElementById("rotateLeft")
.addEventListener("click", function(event) {
rotate(-90);
});
document.getElementById("mirror").addEventListener("click", function(event) {
mirror();
});
document.getElementById("flip").addEventListener("click", function(event) {
flip();
});
document
.getElementById("panLeft")
.addEventListener("click", function(event) {
pan(-PANSTEP, 0);
});
document
.getElementById("panRight")
.addEventListener("click", function(event) {
pan(PANSTEP, 0);
});
document.getElementById("panUp").addEventListener("click", function(event) {
pan(0, -PANSTEP);
});
document
.getElementById("panDown")
.addEventListener("click", function(event) {
pan(0, PANSTEP);
});
}
.canvas-container {
flex-direction: column;
border: 2px solid #333;
padding: 10px;
}
canvas {
background: #777;
cursor: crosshair;
}
.canvas-controls {
padding: 5px;
}
button span {
display: inline-block;
width: 30px;
}
span.rot {
transform: rotate(90deg);
}
<div class="canvas-container">
<canvas id="canvas">
</canvas>
<div class="canvas-controls flex">
<button id="panLeft" title="Pan left"><span>⇐</span></button>
<button id="panRight" title="Pan right"><span>⇒</span></button>
<button id="panUp" title="Pan up"><span>⇑</span></button>
<button id="panDown" title="Pan down"><span>⇓</span></button>
<button id="rotateLeft" title="Rotate left"><span>⟲</span></button>
<button id="rotateRight" title="Rolotate right"><span>⟳</span></button>
<button id="mirror" title="Mirror"><span>⇹</span></button>
<button id="flip" title="Flip"><span class="rot">⇹</span></button>
</div>
</div>