Javascript, how can I get image width and height from a blob?
Asked Answered
R

3

6

So this is my code snippet (unnecessary code lines are removed):

let imgInput = document.getElementById('id_photo');
imgInput.addEventListener('change', function (e) {
    if (e.target.files) {
        for (let i = 0; i < e.target.files.length; i++) {

        let imageFile = e.target.files[i];
        var reader = new FileReader();

        reader.onload = function (e) {
            var img = document.getElementById(nevermindID);
            img.onload = function() {
                var shape = resizeImage(img) // resizing image for future canvas drawing, returns [width, height] (resized)
                
                var canvas = document.createElement("canvas")
                canvas.width = shape[0]
                canvas.height = shape[1]
                var ctx = canvas.getContext("2d")
                ctx.drawImage(img, 0, 0, shape[0], shape[1])
                // converting to base64 logic and the rest code
            }
            img.src = e.target.result;
        }
        reader.readAsDataURL(imageFile);

    }
}
});

I want to get the uploaded original image's width and height so I can resize it. I can't do it from imageFile as it returns undefined and can't get it from the img variable as it originally depends on my css (in css the <img> tag is in a <div> tag which is not fullscreen, let's say it is 400 pixels). So correspondingly, if I upload 5000x5000 image it will not be the original-sized (hope you got it). My problem is that because my css resizes that div and elements inside, I can't get normal width and height of my original image, so I can resize it then draw with canvas and then convert it to base64.

I try to add this code to img.onload:

var blobURL = URL.createObjectURL(imageFile)

Here I access my original image (which is imageFile), create blob for it and get that blob link. But I can't access that blob link image's width and height. Any suggestions?

Note: I think the problem is in img.onload itself. As in this line I pass that img:

ctx.drawImage(img, 0, 0, shape[0], shape[1])

FYI. This is how it looks when I upload my image. It is about 90x90 pixels here. And that causes the problem with canvas, as img.onload takes those dimensions (90x90) and not the original dimensions which is about 5000x5000 enter image description here

The resized image looks like: enter image description here

Edited:

If anyone is curious, I can paste my solved code version here so you can adjust to it. Leave a comment below.

Restless answered 7/6, 2022 at 15:58 Comment(0)
C
10

You can use the Image constructor

const img = new Image();
img.src = imageDataUrl;
img.onload = () => {
   // img.width
   // img.height
 };
Chemurgy answered 7/6, 2022 at 16:29 Comment(2)
Yes! That totally works fine for me. Thanks dude! This code is enough to solve my problem :) good luck!Restless
excellent and very easy to understand.Bethanybethe
M
8

The best is to create an ImageBitmap from this Blob.

In some implementations this avoids having the decoding of the image being made on the main thread and blocking your page, and is anyway a much lighter path than the HTMLImageElement one in every implementation (avoids a network request, the creation of a DOM object, a lot of internal checks etc.).
If you only want to get the size of the image, this also allows you to release the bitmap data from the browser's memory, thanks to the .close() method, while the HTMLImageElement will generally try to cache its bitmap data even after the original DOM element has been destroyed.
And as a bonus, it even works in Web Workers.

(async () => {
  const imgBlob = await getImgBlob();
  const bmp = await createImageBitmap(imgBlob);
  const { width, height } = bmp;
  bmp.close(); // free memory
  console.log(width, height);
})();


// simple helper to get the Blob of an image
async function getImgBlob() {
  const resp = await fetch("https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png");
  return resp.ok ? resp.blob() : Promise.reject(resp.status);
}

If you need to target older browsers, you can use this polyfill of mine.

The only drawback of this method is that it works only for bitmap images, you can't create an ImageBitmap from a Blob of an SVG image, because this kind of image can have no intrinsic dimensions of its own.


And since you are gonna draw that image on a canvas, note that you can also obviously pass this ImageBitmap to drawImage() (before you close it), and that it also even allows you to do the resizing directly:

(async () => {
  const canvas = document.querySelector("canvas");
  const ctx = canvas.getContext("2d");
  const imgBlob = await getImgBlob();
  const originalBmp = await createImageBitmap(imgBlob);
  const { width, height } = originalBmp;
  // we create the resized from the ImageBitmap for even faster processing
  // but we could obviously reuse the Blob here too
  const resizedBmp  = await createImageBitmap(originalBmp, { resizeWidth: 90, resizeHeight: 90 });
  originalBmp.close();
  canvas.width  = width;
  canvas.height = height;
  ctx.drawImage(resizedBmp, 0, 0, width, height);
  resizedBmp.close();
})().catch(console.error);

// simple helper to get the Blob of an image
async function getImgBlob() {
  const resp = await fetch("https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png");
  return resp.ok ? resp.blob() : Promise.reject(resp.status);
}
<canvas></canvas>
Margaret answered 7/6, 2022 at 22:43 Comment(1)
typescript const bitmap: ImageBitmap = await createImageBitmap(blob); const { width, height } = bitmap; console.log(width, height); Dene
T
4

clientHeight, clientWidth, offsetHeight, offsetWidth, height, width would give you relative dimensions. To get original dimensions you must use .naturalHeight and .naturalWidth.

HTMLImageElement.naturalWidth
The HTMLImageElement interface's read-only naturalWidth property returns the intrinsic (natural), density-corrected width of the image in CSS pixels.

This is the width the image is if drawn with nothing constraining its width; if you neither specify a width for the image nor place the image inside a container that limits or expressly specifies the image width, this is the number of CSS pixels wide the image will be.

You can read more here

You snippet can be updated with .naturalWidth and .naturalHeight as

let imgInput = document.getElementById('id_photo');
imgInput.addEventListener('change', function (e) {
  if (e.target.files) {
    for (let i = 0; i < e.target.files.length; i++) {
      
      let imageFile = e.target.files[i];
      var reader = new FileReader();
      
      reader.onload = function (e) {
        var img = document.getElementById('displayImage');
        img.onload = function() {
          debugger;
          var shape = resizeImage(img) // resizing image for future canvas drawing, returns [width, height] (resized)
          // or just
          // var shape = [image.naturalWidth, image.naturalHeight]
          var canvas = document.createElement("canvas")
          canvas.width = shape[0]
          canvas.height = shape[1]
          var ctx = canvas.getContext("2d")
          ctx.drawImage(img, 0, 0, shape[0], shape[1])
          
          document.body.appendChild(canvas); // added
          // converting to base64 logic and the rest code
        }
        img.src = e.target.result;
      }
      reader.readAsDataURL(imageFile);
      
    }
  }
});
function resizeImage(image){
  return [image.naturalWidth, image.naturalHeight]
}
img{
  width: 100px;
  heigh: 100px;
}
<img id='displayImage'/>
<input type='file' multiple id='id_photo'/>

Also if you added an image just to get src you can try new Image() instead to skip creating an extra DOM element.

let imgInput = document.getElementById('id_photo');
imgInput.addEventListener('change', function (e) {
  if (e.target.files) {
    Array.from(e.target.files).forEach(file=>{
      const fileReader = new FileReader();
      fileReader.readAsDataURL(file);
      fileReader.onload = (fe) => {
        const image = new Image();
        image.src = fe.currentTarget.result;
        image.onload = function(ie){
          console.log('Width: ' + this.naturalHeight);
          console.log('Height: ' + this.naturalWidth);
        }
      }
    })
  }
});
<input type='file' multiple id='id_photo'/>
Tamatamable answered 7/6, 2022 at 17:37 Comment(3)
Beware that naturalWidth and naturalHeight may be tempered by the srcset attribute: jsfiddle.net/w07qdhzsMargaret
Yes. But getting an image from the file selector not fetching from the DOM element.Kauffman
And in that case .width and .height will also match the intrinsic size.Margaret

© 2022 - 2024 — McMap. All rights reserved.