🏠 back to Observable

rotating a long image file to fit in the browser

I took a panoramic photograph on my phone, and I’d like to show this image horizontally (currently it loads vertically). I can rotate it, but only shows part of the image, and the height and width of the image element appear to still defined as the original orientation.

{
var id = 'rotate-90';//The ID of the <img> element you want to rotate
var deg = 90;//The rotation angle, in degrees
document.getElementById(id).style = 'transform: rotate(' + deg + 'deg)';
}

Any simple tricks for rotating an image so that it fits in the viewing area using JavaScript?

I would do this in Canvas rather than CSS:

That way, the resulting rotated image can be used as a normal element rather than needing a container and absolute/relative positioning. Also, you can click Save Image As… to download the rotated image.

(Side note: don’t use document.getElementById in Observable! See antipattern #4.)

1 Like

Wow. Thank you. Your canvas version looks frighteningly complicated. Just to keep me from jumping back and forth between the notebook and this forum in the future, here’s Mike’s rewrite:

new Promise((resolve, reject) => {
  const image = new Image;
  image.onerror = reject;
  image.onload = () => {
    const canvas = document.createElement("canvas");
    canvas.style.maxWidth = "100%";
    canvas.width = image.naturalHeight;
    canvas.height = image.naturalWidth;
    const context = canvas.getContext("2d");
    context.translate(image.naturalHeight, 0);
    context.rotate(Math.PI / 2);
    context.drawImage(image, 0, 0);
    resolve(canvas);
  };
  image.src = "https://s3.amazonaws.com/c.loud/11-sandisfield/images/downstairs/office/20191004_130006.jpg";
})

I’ll read more into canvas, but I’m guessing there are no real ‘shortcuts’ around defining all the size elements and then applying these seemingly complex mathematical transformations?

Also, the issue may be on my end, but I am no longer seeing the image render into the cell (I’ll check another computer momentarily).

Well, there’s two things going on here.

The first is loading the image and using a Promise and the load event to wait for the image data. You can’t draw the image to the canvas, or know its dimensions, before its loaded. So you have to wait. You could do that part separately like so:

image = new Promise((resolve, reject) => {
  const image = new Image;
  image.onerror = reject;
  image.onload = () => resolve(image);
  image.src = "https://s3.amazonaws.com/c.loud/11-sandisfield/images/downstairs/office/20191004_130006.jpg";
})

There’s a fetchImage helper in this notebook if you want:

(If you want to be able to read the image data, you’ll also need to set image.crossOrigin = "anonymous", but that doesn’t appear necessary here since you’re just rendering to a Canvas to display.)

The second part of the notebook is creating the Canvas element given the loaded image. Broken apart, that would be:

canvas = {
  const canvas = document.createElement("canvas");
  canvas.style.maxWidth = "100%";
  canvas.width = image.naturalHeight;
  canvas.height = image.naturalWidth;
  const context = canvas.getContext("2d");
  context.translate(image.naturalHeight, 0);
  context.rotate(Math.PI / 2);
  context.drawImage(image, 0, 0);
  return canvas;
}

If you wanted, you could use the DOM.context2d helper to shorten it slightly:

canvas = {
  const context = DOM.context2d(image.naturalHeight, image.naturalWidth, 1);
  context.canvas.style.maxWidth = "100%";
  context.translate(image.naturalHeight, 0);
  context.rotate(Math.PI / 2);
  context.drawImage(image, 0, 0);
  return context.canvas;
}
1 Like

Incredible, Mike. Thank you.

[And I see that the image initially not appearing had to do with loading… and my very slow Internet connection :wink: ]