0

Let's say I have cat.png at 800x600 pixel.

Then I put it into img and apply CSS object-fit like below:

#cat {
  width: 100px;
  height: 100px;
  object-fit: cover;
}

<img src="cat.png" id="cat">

Now I have on screen a 100x100 image.

How to transfer this final result into canvas, so that I could get the dataUrl and save as cat_thumb.png at 100x100 pixel?

These codes simply ignore the CSS applied to the img:

var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var img = document.getElementById("cat");
ctx.drawImage(img,0,0,100,100);
console.log(c.toDataURL());
Brett DeWoody
  • 59,771
  • 29
  • 135
  • 184
Coisox
  • 1,002
  • 1
  • 10
  • 22

1 Answers1

1

Looks like there are 2 fixes needed:

  • Use the img height and width in drawImage to use the height and width of the CSS being applied to the image.
  • Use img.onload to wait for the image to be loaded before creating the canvas, adding the image and using toDataURL().

Because you're using object-fit: cover to crop the image we'll also need to some some calculation on how to crop the image before its placed into the canvas.

One side effect of this method, the downsampling when transferring to the canvas creates a noticeable loss in quality. This is less of an issue when the resizing of the image is less, and becomes more noticeable when downsizing a large image to a much smaller size.

I have added a very simple downsampling loop to reduce the effects of this. In short, it creates an offscreen canvas, and scales it down by 50% until the image is close to the desired size.

More info and possible solutions for this issue can be found in many other questions/answers.

var imgEl = document.getElementById("cat");
var img = new Image();
img.crossOrigin = "anonymous";
img.src = imgEl.src;

img.onload = function() {
  // Create an offscreen canvas for downsampling the image
  var oc = document.createElement("canvas");
  var occtx = oc.getContext("2d");

  oc.width = img.width * 0.5;
  oc.height = img.height * 0.5;
  occtx.drawImage(img, 0, 0, oc.width, oc.height);
  
  var c = document.getElementById("myCanvas");
  var ctx = c.getContext("2d");
  var imgRatio = imgEl.width / imgEl.height;
  var sx, sy, sWidth, sHeight;

  if (imgRatio > 1) {
    sx = 0;
    sy = (oc.height / imgRatio) / 2;
    sWidth = oc.width;
    sHeight = oc.height / imgRatio;
  } else if (imgRatio < 1) {
    sx = (oc.width * imgRatio) / 2;
    sy = 0;
    sWidth = oc.width * imgRatio;
    sHeight = oc.height;
  } else {
    sx = 0;
    sy = 0;
    sWidth = oc.width;
    sHeight = oc.height;
  }

  c.width = imgEl.width;
  c.height = imgEl.height;
  ctx.drawImage(oc, sx, sy, sWidth, sHeight, 0, 0, imgEl.width, imgEl.height);
  console.log(c.toDataURL());
}
.img-container {
  position: relative;
  margin-top: 0;
}

#cat {
  width: 300px;
  height: 150px;
  object-fit: cover;
}
<div class="img-container"><img src="https://i.imgur.com/nZmjnkH.jpg" id="cat" crossOrigin="anonymous"></div>

<canvas id="myCanvas"></canvas>
Brett DeWoody
  • 59,771
  • 29
  • 135
  • 184
  • Hi, thank you for your help. It did not meet my desire result. Try to use non square photo, the will be nicely cropped and center the image by the CSS. But on the canvas, the image is distorted and not being cropped – Coisox Jul 17 '17 at 16:41
  • I have updated the solution to work for non-square crops. – Brett DeWoody Jul 18 '17 at 00:34
  • How do you expect your downsampling to do anything? You are drawing the original `img` on this offscreen canvas. You can do it 1 billion times, it would be just the same as to do only the last step. – Kaiido Jul 18 '17 at 01:26
  • @Kaiido thanks for pointing out the error, I've corrected it. – Brett DeWoody Jul 18 '17 at 01:54
  • Thank you! Its working for my current problem. But it would be better if the code could take all possibilities like css rotate. Is it not possible to somehow "screenshot" the whole web then crop using coordinate which we can calculate using jquery position()? – Coisox Jul 27 '17 at 03:36