get canvas mouse coordinates after transformation using ctx.getTransformation()
Asked Answered
A

1

2

I am using the following function to get mouse coordinates on canvas after performing rotations.

function getWindowToCanvas(canvas, x, y) {
  const ctx = canvas.getContext("2d");
  var transform = ctx.getTransform();
  var rect = canvas.getBoundingClientRect();
  var screenX = (x - rect.left) * (canvas.width / rect.width);
  var screenY = (y - rect.top) * (canvas.height / rect.height);

  if (transform.isIdentity) {
    return {
      x: screenX,
      y: screenY
    };
  } else {
    console.log(transform.invertSelf());
    const invMat = transform.invertSelf();
    return {
      x: Math.round(screenX * invMat.a + screenY * invMat.c + invMat.e),
      y: Math.round(screenX * invMat.b + screenY * invMat.d + invMat.f)
    };
  }
}

I used the inverted transform matrix after reading html5-canvas-transformation-algorithm and best-way-to-transform-mouse-coordinates-to-html5-canvass-transformed-context

I am letting the user draw rectangles with the mouse, and I need to get the mouse x,y coordinates after transformations, but once the canvas is rotated (say by 90 deg) then the rectangles no longer follow the mouse pointer.

Does anyone know what I'm doing wrong?

Anomalous answered 9/12, 2019 at 8:41 Comment(6)
where is part that you draw with ? the method that tracks the mouse if you actually make the coordinates relative to the whole window the unexpected behavior wont happenMowry
Hi Youssef, I am trying to find a better way to make the coordinates relative to the whole window, but I do not know a better way than var rect = canvas.getBoundingClientRect(); and then var relativeToWindowX = (event.clientX - rect.left) * (canvas.width / rect.width) Anomalous
event.clientX comes from canvas' onmousedown and onmousemove events when the user is clicking and dragging on the canvas.Anomalous
Do one step at the time. Have a look at jsfiddle.net/60map523 for translating mouse clicks into canvas coordinates. Second step is to convert canvas coordinates into transformed coordinates.Lindblad
What is it you are "really" trying to do? Can you set up a complete example? Sounds like there would be much easier way, like e.g reset the transforms to identity before drawing or even store the transformation in each of your objects: jsfiddle.net/en8df6o0/2Psittacosis
@Psittacosis I will set up a complete demo soon. User uploads photo, can rotate and crop. Rotating animates the image rotating to new position. The user can then set a crop box, and do fine-tune rotation while the crop box is open. Ultimately I'd like to leave the transform as identity, but had trouble doing this in the beginning. I will look at the jsfiddle thank you.Anomalous
A
2

Thanks to @MarkusJarderot and jsFiddle getting mouse coordinates from un-rotated canvas I was able to get a solution that is close to perfect. I don't quite understand it, but it works much better.

function getWindowToCanvas(canvas, e) {

 //first calculate normal mouse coordinates
  e = e || window.event;
  var target = e.target || e.srcElement,
    style = target.currentStyle || window.getComputedStyle(target, null),
    borderLeftWidth = parseInt(style["borderLeftWidth"], 10),
    borderTopWidth = parseInt(style["borderTopWidth"], 10),
    rect = target.getBoundingClientRect(),
    offsetX = e.clientX - borderLeftWidth - rect.left,
    offsetY = e.clientY - borderTopWidth - rect.top;
  let x = (offsetX * target.width) / target.clientWidth;
  let y = (offsetY * target.height) / target.clientHeight;

  //then adjust coordinates for the context's transformations
  const ctx = canvas.getContext("2d");
  var transform = ctx.getTransform();
  const invMat = transform.invertSelf();
  return {
    x: x * invMat.a + y * invMat.c + invMat.e,
    y: x * invMat.b + y * invMat.d + invMat.f
  };
}

The only issue remaining is that, when rotated say 45deg, drawing a rectangle with ctx.rect() draws a rectangle that parallels with respect to the canvas, not to the window, so the rectangle is slanted even though it is finally in the right place. I want to draw rectangles with respect to the window, not the canvas. However, this may just be how ctx.rect() works, and I'll need to update later. For now, this could help others.

UPDATE Figured out original bug. Since I didn't understand why my original function was not working, used the above solution to start trouble-shooting it. It turns out that the reason the above code did not work is because I was calling console.log(transform.invertSelf()) to see the transform while I was debugging. This mutated the transform. So, when I called var invMat = transform.invertSelf() right after, I inverted it yet again! I should have paid attention to the 'self' in 'invertSelf'.

This function now works

function getWindowToCanvas(canvas, x, y) {
  var rect = canvas.getBoundingClientRect();
  var screenX = (x - rect.left) * (canvas.width / rect.width);
  var screenY = (y - rect.top) * (canvas.height / rect.height);
  const ctx = canvas.getContext("2d");
  var transform = ctx.getTransform();
  if (transform.isIdentity) {
    return {
      x: screenX,
      y: screenY
    };
  } else {
    //   console.log(transform.invertSelf()); //don't invert twice!!
    const invMat = transform.invertSelf();

    return {
      x: Math.round(screenX * invMat.a + screenY * invMat.c + invMat.e),
      y: Math.round(screenX * invMat.b + screenY * invMat.d + invMat.f)
    };
  }
}

Anomalous answered 10/12, 2019 at 2:1 Comment(1)
lol you didn't mention it in the question , but one of the problems of js as always is debugging it,better use the debugger keyword instead of console.log more efficient and it will limit the number of code you should clean , instead of multiple console.logMowry

© 2022 - 2024 — McMap. All rights reserved.