Jacobian
Jacobian

Reputation: 10802

JavaScript drawImage incorrectly crops the source canvas image on some computers

I have a canvas on my page and I have an instrument which allows users to select an area, which he/she wants to crop. It looks like so:

enter image description here

To crop an image I calculate top-left pixel coordinate [xmin, ymin] and bottom-right coordinate [xmax,ymax]. And cropping itself is done like so:

context.drawImage(image,xmin,ymin,xmax-xmin,ymax-ymin,0,0,xmax-xmin,ymax-ymin);

And the problem is, on some random computers and some random browsers, this code produces correct chunk and on some others - incorrect. That is why I can not provide even a demo or a fiddle. Locally, on my laptop it's ok on all browsers (FF, IE, Opera, Chrome,Safari), but on my users computers it is incorrect.

So, my question is, what may be wrong with this call:

context.drawImage(image,xmin,ymin,xmax-xmin,ymax-ymin,0,0,xmax-xmin,ymax-ymin);

Is there something, I should take into account to make this code cross-browser and cross-platform?

Upvotes: 1

Views: 791

Answers (1)

Kaiido
Kaiido

Reputation: 136638

Your problem is* that some of your users have their browser's zoom-level set to something else than 100%.

With your current logic, you assume the image is displayed at its original size, which is the one used by drawImage. But when zoomed in or out, the browser does scale the rendered image, making the coordinates of the cursor wrong with regard to the natural size of your image.

To circumvent this, you need to scale your coordinates relatively to the ratio displayedSize / naturalSize.

Here is a simple example.

onload = function() {
  const ctx = c.getContext('2d');
  c.width = img.width;
  c.height = img.height;

  img.onmousedown = c.onmousedown = handleMouseDown;
  img.onmousemove = c.onmousemove = handleMouseMove;
  img.onmouseup = c.onmouseup = handleMouseUp;
  
  var rect = { min_x: 0, min_y: 0, max_x: 0, max_y: 0, updating: false};
  
  draw();
  
  function handleMouseDown(evt) {
    evt.preventDefault();
    var targetBB = evt.target.getBoundingClientRect();
    rect.updating = true;
    rect.min_x = rect.max_x = evt.clientX - targetBB.left;
    rect.min_y = rect.max_y = evt.clientY - targetBB.top;
    draw();
  }
  function handleMouseUp(evt) {
    rect.updating = false;
    draw();
  }
  function handleMouseMove(evt) {
    if(!rect.updating) return;
    var targetBB = evt.target.getBoundingClientRect();
    rect.max_x = evt.clientX - targetBB.left;
    rect.max_y = evt.clientY - targetBB.top;
    draw();
  }
  function draw() {
    ctx.filter = 'blur(2px)';
    ctx.drawImage(img, 0,0);
    ctx.filter = 'none';
    var dx = Math.min(rect.min_x, rect.max_x),
      dy = Math.min(rect.min_y, rect.max_y),
      dw = Math.abs(rect.min_x - rect.max_x),
      dh = Math.abs(rect.min_y - rect.max_y);
    if(!dh || !dw) return;
    ctx.strokeRect(dx, dy, dw, dh);
    var ratio_W = img.clientWidth / img.naturalWidth,
      ratio_H = img.clientHeight / img.naturalHeight,
      sx = dx * ratio_W,
      sy = dy * ratio_H,
      sw = dw * ratio_W,
      sh = dh * ratio_H;
    ctx.drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh);
  }
}
<h3>Try to zoom-in/out your browser</h3>
<img id="img" src="https://i.sstatic.net/ujq5W.png">
<canvas id="c"></canvas>

*According to your comment.

Upvotes: 2

Related Questions