My Image doesn’t take the full width of the canvas when rotated

Tags: ,



So I was playing around the canvas and I tried rotating images loaded from my device hard disk as seen below:

class imageEdits {
    constructor(canvas, imageSrc, canvasWidth, canvasHeight) {
        this.canvas = canvas;
        this.ctx = this.canvas.getContext("2d");
        this.image = new Image();
        this.image.src = imageSrc;
        this.cWidth = canvasWidth;
        this.cHeight = canvasHeight;
    }

    rotateImage = deg => {
        this.ctx.save();

        function degToRad(deg) {
            return (1 / 57.3) * deg;
        }

        let imageHeight = this.image.naturalHeight,
            imageWidth = this.image.naturalWidth;

        if (deg !== 0 && deg !== 180) {
            imageHeight = this.image.naturalWidth;
            imageWidth = this.image.naturalHeight;
        } else {
            (imageHeight = this.image.naturalHeight),
                (imageWidth = this.image.naturalWidth);
        }
        const {
            canvasStyle: { height, width, scale, aspectRatio },
        } = this.computeAspectRatio(imageHeight, imageWidth);
        console.log({ height, width, scale, aspectRatio });
        const halfWidth = width / 2;
        const halfHeight = height / 2;
        this.ctx.translate(halfWidth, halfHeight);

        this.ctx.rotate(degToRad(deg));
        this.canvas.style.transform = `scale3d(${scale}, ${scale}, 1)`;
        this.canvas.style.backgroundColor = "rgb(0,0,0)";
        this.canvas.style.transformOrigin = `top left`;
        console.log({ width, height });
        this.ctx.drawImage(this.image, -halfWidth, -halfHeight, width, height);

        this.ctx.restore();
    };

    computeAspectRatio = (imageHeight, imageWidth) => {
        const height = imageHeight;
        const width = imageWidth;

        (scale = 1), (canvasStyle = {});

        this.canvas.width = width;
        this.canvas.height = height;

        const scaleX = this.cWidth / width;
        const scaleY = this.cHeight / height;

        if (scaleX > scaleY) scale = scaleY;
        else scale = scaleX;

        canvasStyle = { height, width, scale };

        return { canvasStyle };
    };
}

The problem with the code is that when I rotate the image to the inverse of its aspect ratio which is 90degree of 180degree the image gets centered and doesn’t take the full width or height as the case may be of the canvas.

here is a jsfiddle of the working code

And this is what my expected output should look like

But instead this is what I get

Please does anyone see what I am doing wrong? Thanks in advance 🙂

Answer

In general a rotation around the center is done by translating the context to the mid-point of the canvas, rotating the context and finally drawing the image at the negative half of it’s width horizontally and negative half of it’s height vertically.

What makes things a bit harder in your case is that the image should always fill the entire canvas, while maintaining it’s correct aspect ratio. To do this we would need to know the exact width & height of the image – or more precisely it’s bounding box – at a given angle. Luckily we just have to deal with four angles, so it’s just a matter of swapping the width & height at 90° and 270° – as you already did.

Now that we know the image’s dimensions we need to compute the scale along both axes and see which one of those doesn’t exceed the canvas width & height after multiplication.

This scale is then used to scale the context – not the css scale you used to size the canvas itself.

Here’s an example based on your code (just click on ‘Run code snippet’):

const canvas = document.getElementById("edit-canvas");
const ctx = canvas.getContext("2d");
const canvasWidth = 320;
const canvasHeight = 200;

let deg = 0;
let image;

canvas.width = canvasWidth;
canvas.height = canvasHeight;

function degToRad(deg) {
  return (1 / 57.3) * deg;
}

function draw() {
  let scale, imageHeight, imageWidth, scaleX, scaleY;
  if (deg != 0 && deg != 180) {
    imageHeight = image.width;
    imageWidth = image.height;
  } else {
    imageHeight = image.height;
    imageWidth = image.width;
  }
  scaleX = canvasWidth / imageWidth;
  scaleY = canvasHeight / imageHeight;

  if (imageWidth * scaleX <= canvasWidth && imageHeight * scaleX <= canvasHeight) {
    scale = scaleX;
  } else {
    scale = scaleY;
  }

  ctx.save();
  ctx.clearRect(0, 0, canvasWidth, canvasHeight);

  ctx.translate(canvasWidth / 2, canvasHeight / 2);
  ctx.rotate(degToRad(deg));
  ctx.scale(scale, scale);
  ctx.drawImage(image, -image.width / 2, -image.height / 2, image.width, image.height);

  ctx.restore();
}

image = new Image();
image.onload = draw;
image.src = "https://picsum.photos/id/1079/300/200";

document.getElementById("rotate").addEventListener("click", () => {
  deg += 90;
  if (deg == 360) deg = 0;

  draw();
});
<div class="canvas-container">
  <input type="button" id="rotate" style="padding: 10px; font-size: 16px; position: absolute" value="Rotate" />
  <canvas id="edit-canvas" style="border: 1px solid #000; margin-left: 10px;background-color: #c1f0c1;"></canvas>
</div>


Source: stackoverflow