0

I'm trying to write a function that takes x,y coordinates and rotation, and inserts an image that is rotated around the image's central point into a canvas. This code works fine:

let img = new Image(),
 canvas = document.getElementById('canvas'),
 ctx = canvas.getContext('2d'),
 step = 0, 
 drawImage = (ctx, img, x, y, degrees,  w = 150, h = 150) => {
  ctx.save();
  ctx.translate(x+w/4, y+h/4);
  ctx.rotate(degrees*Math.PI/180.0);
  ctx.translate(-x-w/4, -y-h/4);
  ctx.drawImage(img, x, y, w, h);
  ctx.restore();
 },
 animate = () => {
  ctx.globalCompositeOperation = 'destination-over';
  // clear canvas
  ctx.clearRect(0, 0, window.innerWidth, window.innerHeight); 

  drawImage(ctx, img, 0,  0, step)
 

  step++;
  window.requestAnimationFrame(animate);
 }


img.src = "http://upload.wikimedia.org/wikipedia/commons/d/d2/Svg_example_square.svg";

animate();
* { margin:0; padding:0; } /* to remove the top and left whitespace */
 html, body { width:100%; height:100%; } /* just to be sure these are full screen*/
 canvas { display:block;} /* To remove the scrollbars */
<canvas id="canvas" width="300" height="300">

But when I try to resize the canvas

canvas.width =window.innerWidth;
canvas.height = window.innerHeight;

This warps the image and also changes the rotation-point, the effect is more clear on a bigger window. How can I have a canvas that fills up the screen and treats it as though I set the size with the HTML-properties?

let img = new Image(),
 canvas = document.getElementById('canvas'),
 ctx = canvas.getContext('2d'),
 step = 0, 
 drawImage = (ctx, img, x, y, degrees,  w = 300, h = 300) => {
  ctx.save();
  ctx.translate(x+w/4, y+h/4);
  ctx.rotate(degrees*Math.PI/180.0);
  ctx.translate(-x-w/4, -y-h/4);
  ctx.drawImage(img, x, y, w, h);
  ctx.restore();
 },
 animate = () => {
  ctx.globalCompositeOperation = 'destination-over';
  // clear canvas
  ctx.clearRect(0, 0, window.innerWidth, window.innerHeight); 

  drawImage(ctx, img, 0,  0, step)
 

  step++;
  window.requestAnimationFrame(animate);
 }


img.src = "http://upload.wikimedia.org/wikipedia/commons/d/d2/Svg_example_square.svg";
canvas.width =window.innerWidth;
canvas.height = window.innerHeight;

animate();
 * { margin:0; padding:0; } /* to remove the top and left whitespace */
 html, body { width:100%; height:100%; } /* just to be sure these are full screen*/
 canvas { display:block;} /* To remove the scrollbars */
<canvas id="canvas" width="300" height="300">
ford04
  • 66,267
  • 20
  • 199
  • 171
Himmators
  • 14,278
  • 36
  • 132
  • 223

2 Answers2

2

That's a Chrome "bug", related to the bad svg image you ask it to draw.

The svg you draw doesn't have an absolute width or height attribute, nor a viewBox one.

<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
  <rect width="150" height="150" fill="rgb(0, 255, 0)" stroke-width="1" stroke="rgb(0, 0, 0)"/>
</svg>

This means that it has no intrisic size nor any intrinsic ratio. In HTML context, browsers can still set a default size, by applying the rules described in Inline, replaced elements.

By following these rules, an <img> holding your svg in a relatively sized container will have a computed size of 300 x 150px (width => 300px, height => 2:1 * width => 150px). However, its intrisic size is 0 and Firefox will report this through the naturalXXX properties, while Chrome will set these to 300 and 150.

onload = e => {
  const img = document.querySelector('img');
  console.log(
    'computed',
    img.width, // 300
    img.height // 150
  );
  console.log(
    'intrinsic',
    img.naturalWidth, // 0 in FF, 300 in Chrome
    img.naturalHeight // 0 in FF, 150 in Chrome
  );
}
img { border: 1px solid blue}
<img src="http://upload.wikimedia.org/wikipedia/commons/d/d2/Svg_example_square.svg">

Now, when you are to draw this image on a canvas, there is no such CSS rules to dictate how this computed size should be calculated, and UAs do not agree to what should happen when you drawing this image on a canvas.
Firefox will simply not draw it (remember, they set naturalXXX to 0. Chrome will use some magic heuristics to draw it the best it thinks it should be drawn. And that's what I call a bug.
If the HTMLImageElement used in drawImage has no srcset (i.e its density is 1) drawImage(img, x, y) should produce the same result as drawImage(img, x, y, img.naturalWidth, img.naturalHeight).
However, given the magic they use for these images with no intrinsic size, it's not what happens.

So you see it distorted in Chrome because you ask it to be drawn as a 300 x 300 rectangle.

const img = new Image(),
  canvas = document.getElementById('canvas'),
  ctx = canvas.getContext('2d'),
  draw = async () => {
    const w = img.naturalWidth,
          h = img.naturalHeight;
    console.log('reported img size', w, h);
    ctx.fillRect(0, 0, w, h);
    await wait(2000);
    console.log('drawImage with reported size');
    ctx.drawImage(img, 0, 0, w, h);    
    await wait(2000);
    console.log('default drawImage');   
    ctx.drawImage(img, 0, 0);
  };


img.src = "http://upload.wikimedia.org/wikipedia/commons/d/d2/Svg_example_square.svg";
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

img.onload = draw;

function wait(ms) {
  return new Promise(res => setTimeout(res, ms));
}
<canvas id="canvas" width="300" height="300">

So you could try to measure the ratio you need to make it though drawImage(img, x, y, w, h) the same as when using only 3 arguments, but the best is probably to use a correct svg, with absolute width and height set, which will make it work in every browsers:

let img = new Image(),
  canvas = document.getElementById('canvas'),
  ctx = canvas.getContext('2d'),
  step = 0,
  drawImage = (ctx, img, x, y, degrees, w = 300, h = 300) => {
    ctx.save();
    ctx.translate(x + w / 4, y + h / 4);
    ctx.rotate(degrees * Math.PI / 180.0);
    ctx.translate(-x - w / 4, -y - h / 4);
    ctx.drawImage(img, x, y, w, h);
    ctx.restore();
  },
  animate = () => {
    ctx.globalCompositeOperation = 'destination-over';
    // clear canvas
    ctx.clearRect(0, 0, window.innerWidth, window.innerHeight);

    drawImage(ctx, img, 0, 0, step)

    step++;
    window.requestAnimationFrame(animate);
  }


canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

img.onload = animate;

fetch("https://upload.wikimedia.org/wikipedia/commons/d/d2/Svg_example_square.svg")
  .then(resp => resp.text())
  .then(markup => {
    const mime = 'image/svg+xml';
    const doc = new DOMParser().parseFromString(markup, mime);
    doc.documentElement.setAttribute('width', 150);
    doc.documentElement.setAttribute('height', 150);
    img.src = URL.createObjectURL(
      new Blob( [new XMLSerializer().serializeToString(doc)], { type: mime } )
    );
  })
  .catch(console.error);
* { margin:0; padding:0; } /* to remove the top and left whitespace */
html, body { width:100%; height:100%; } /* just to be sure these are full screen*/
canvas { display:block;} /* To remove the scrollbars */
<canvas id="canvas" width="300" height="300">
Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • Might you consider taking a look [here](https://stackoverflow.com/questions/55451493/)? It seems right up your alley, and the question is getting a significant number of views despite having been inactive for a while, so people would probably appreciate a good answer. I tried looking through the standard myself but I couldn't quite fit the pieces into a cohesive answer. I'll award a bounty. – Snow Sep 25 '19 at 05:11
0

Try to change

canvas.width =window.innerWidth;
canvas.height = window.innerHeight;

With

canvas.style.width = "100%";
canvas.style.height = "100%";

let img = new Image(),
 canvas = document.getElementById('canvas'),
 ctx = canvas.getContext('2d'),
 step = 0, 
 drawImage = (ctx, img, x, y, degrees,  w = 300, h = 300) => {
  ctx.save();
  ctx.translate(x+w/4, y+h/4);
  ctx.rotate(degrees*Math.PI/180.0);
  ctx.translate(-x-w/4, -y-h/4);
  ctx.drawImage(img, x, y, w, h);
  ctx.restore();
 },
 animate = () => {
  ctx.globalCompositeOperation = 'destination-over';
  // clear canvas
  ctx.clearRect(0, 0, window.innerWidth, window.innerHeight); 

  drawImage(ctx, img, 0,  0, step)
 

  step++;
  window.requestAnimationFrame(animate);
 }


img.src = "http://upload.wikimedia.org/wikipedia/commons/d/d2/Svg_example_square.svg";
canvas.style.width = "100%";
canvas.style.height = "100%";


animate();
* { margin:0; padding:0; } /* to remove the top and left whitespace */
 html, body { width:100%; height:100%; } /* just to be sure these are full screen*/
 canvas { display:block;} /* To remove the scrollbars */
<canvas id="canvas">
Musevarg
  • 181
  • 6