HTML5画布缩放和平移后如何获取坐标

8

enter image description hereenter image description here

背景:我有一个 HTML5 画布并在上面绘制了一张图片。当图片第一次加载时,它以 100% 的比例加载。该图片为 5000 x 5000 像素,而画布大小为 600 x 600 像素。因此,在加载时,我只能看到前 600 x 像素和 600 y 像素的部分。我可以对画布上的图片进行缩放和平移。

我的问题:我正在尝试找出一个算法,可以返回相对于图片而不是画布的鼠标点击像素坐标,同时考虑了缩放和平移。我知道已经有很多关于这个问题的讨论,但我看到的都没有解决我的问题。我的问题是当我有多个平移和缩放时。我可以进行一次缩放并获得正确的坐标,然后我可以再次缩放并再次获得正确的坐标,但是一旦我进行多次缩放或平移,坐标就会偏差。

以下是我目前所拥有的内容:

//get pixel coordinates from canvas mousePos.x, mousePos.y
(mousePos.x - x_translation)/scale //same for mousePos.y

annotationCanvas.addEventListener('mouseup',function(evt){
                     dragStart = null;
                     if (!dragged) {
                       var mousePos = getMousePos(canvas, evt);
                       var message1 =  " mouse  x: " + (mousePos.x) + '  ' + "mouse y: " + (mousePos.y);
                       var message =  "  x: " + ((mousePos.x + accX)/currentZoom*currentZoom) + '  ' + "y: " + ((mousePos.y + accY)/currentZoom);
                       console.log(message);
                       console.log(message1);
                       console.log("zoomAcc = " + zoomAcc);
                       console.log("currentZoom = " + currentZoom);
                       ctx.fillStyle="#FF0000";
                       ctx.fillRect((mousePos.x + accX)/currentZoom, (mousePos.y + accY)/currentZoom, -5, -5);

                     }
             },true);
//accX and accY are the cumulative shift for x and y respectively, and xShift and xShift yShift are the incremental shifts of x and y respectively

当前缩放比例为累积缩放比例,zoomAcc是该点的单次迭代缩放比例。因此,在这种情况下,当我放大时,zoomAcc始终为1.1,currentZoom = currentZoom * zoomAcc。

为什么这是错误的?如果有人能够向我展示如何跟踪这些变换,然后将它们应用于mousePos.x和mousePos.y,我将不胜感激。

谢谢。

更新:

在图像中,绿点是我点击的位置,红点是使用markE方法计算该点的位置。 m值是您的markE方法中的矩阵值。


那你最终是怎么解决问题的呢?我也卡在同样的问题上了 :( - AkshayJ
2个回答

16

当您命令上下文进行翻译和缩放时,这被称为画布变换。

画布变换基于可以由6个数组元素表示的矩阵:

// an array representing the canvas affine transformation matrix 
var matrix=[1,0,0,1,0,0];

如果您执行context.translate或context.scale并同时更新矩阵,则可以使用该矩阵将未转换的X/Y坐标(如鼠标事件)转换为转换后的图像坐标。

context.translate:

您可以同时执行context.translate(x,y)并在矩阵中跟踪该平移,方法如下:

// do the translate
// but also save the translate in the matrix
function translate(x,y){
    matrix[4] += matrix[0] * x + matrix[2] * y;
    matrix[5] += matrix[1] * x + matrix[3] * y;
    ctx.translate(x,y);
}

context.scale:

您可以使用 context.scale(x,y) 同时进行缩放操作并跟踪矩阵的变化:

// do the scale
// but also save the scale in the matrix
function scale(x,y){
    matrix[0] *= x;
    matrix[1] *= x;
    matrix[2] *= y;
    matrix[3] *= y;    
    ctx.scale(x,y);
}

将鼠标坐标转换为变换后的图像坐标

问题在于浏览器不知道您已经转换了画布坐标系,因此浏览器返回的鼠标坐标是相对于浏览器窗口而不是相对于变换后的画布的。

幸运的是,变换矩阵一直跟踪您所有累积的平移和缩放。

您可以像这样将浏览器窗口坐标转换为变换后的坐标:

// convert mouseX/mouseY coordinates
// into transformed coordinates

function getXY(mouseX,mouseY){
    newX = mouseX * matrix[0] + mouseY * matrix[2] + matrix[4];
    newY = mouseX * matrix[1] + mouseY * matrix[3] + matrix[5];
    return({x:newX,y:newY});
}

markE,感谢您提供详细的答案。我已经按照您的指示操作了,但是第一次缩放时我得到了正确的坐标,但在此之后移动时,它就偏离了。我已经在我的问题中添加了一张图片。 - flash
由于画布的“宽度”和“高度”属性,您缺少自动缩放。我猜这可以通过将实际宽度和高度的比例添加到画布的宽度和高度来解决。 - Domi
另外,请注意某些操作会重置画布的内部变换矩阵,例如设置 canvas.widthheight。请确保在此之后重置您的矩阵。 - Domi
context.rotate() 和 context.transform() 在哪里? - cuixiping
@cuixiping。这个问题是关于缩放和平移的。这里有另一个帖子,讲述了使用变换矩阵进行旋转:https://dev59.com/ypbks4cB2Jgan1znMZHc#18437802。顺便说一下,`context.transform`用于修改现有的上下文矩阵。你也可以使用`context.setTransform`来覆盖现有的上下文矩阵。;-) - markE

1

有一个DOMMatrix对象可以对坐标进行变换。我通过将我的xy坐标放入DOMPoint中,并使用由CanvasRenderingContext2D.getTransform返回的DOMMatrix的方法来计算已翻译和旋转形状的坐标。这使得点击处理程序能够确定在画布上被点击的形状。这段代码显然执行了markE的答案中的计算:

const oldX = 1, oldY = 1; // your values here
const transform = context.getTransform();
// Destructure to get the x and y values out of the transformed DOMPoint.
const { x, y } = transform.transformPoint(new DOMPoint(oldX, oldY));

DOMMatrix还具有平移、缩放和其他操作的方法,因此您不再需要手动编写这些内容。MDN没有完全记录它们,但链接到一个页面,其中包含非变异可变变换方法的规范。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接