0

I want to resize (as in reduce the filesize if it exceeds limit) an Image using canvas. I see that the resultant image size drawn on canvas, keeping the width and height constant, results in different file size depending upon what has been drawn on canvas.

I am willing to reduce the resolution further provided I get the desired file size.

How do I make sure whatever the resolution I get file(image) size within the limit?

Refer to this fiddle

var el = document.getElementById('f');

el.addEventListener('change', onChange);

function onChange(e) {
  var file = this.files[0];

  console.log("CHANGING");

  var fr = new FileReader();
  fr.onload = function(e) {
    drawOnCanvas(e.target.result);
  }
  fr.readAsDataURL(file);
}


function drawOnCanvas(imgSrc) {
  var canv = document.createElement('canvas'),
    img = new Image();

  console.log("DRAWING");

  canv.width = canv.height = 500;
  ctx = canv.getContext('2d');

  img.onload = function() {
    console.log("IMAGE LOADED");
    ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, canv.width, canv.height);

    try {
      document.body.removeChild(document.querySelector('canvas'));
    } catch (e) {}

    document.body.appendChild(canv);

    blob = canvasToFile(canv.toDataURL());

    alert("FILE SIZE " + blob.size);
  }

  img.src = imgSrc;
}

function canvasToFile(dataUrl) {
  var encData = dataUrl.split(',')[1],
    binString = atob(encData),
    binData = new Uint8Array(binString.length),
    i, blob;

  for (i = 0; i < binString.length; i++) {
    binData[i] = binString.charCodeAt(i);
  }

  blob = new Blob([binData], {
    type: 'image/jpeg'
  });

  return blob;
}
<p>Try uploading different image file and see the difference in size for same output resolution:</p>
<input type="file" id="f">
Kaiido
  • 123,334
  • 13
  • 219
  • 285
Vivek
  • 4,786
  • 2
  • 18
  • 27
  • This question would need a rewriting but it seems you are looking for the quality option of `canvas.toDataURL('image/jpeg', quality)` which a 0-1 float. Note that you can change `'image/jpeg'` with `'image/webp'` in browsers supporting webp format. `'image/png'` doesn't have such a quality option. – Kaiido Sep 07 '15 at 14:51
  • Can you please explain your problem clearly??...You want to reduce the image size on canvas?...Is that you want? – AkshayJ Sep 08 '15 at 05:38
  • @AkshayJ Yes, I want to resize an image. Also making sure it is under a specific file size(2MB in my case). – Vivek Sep 08 '15 at 10:25
  • @Vivek, didn't check your snippet before, sorry, but that's why it's required to incorporate code in your question. So note that your final blob, even if set with `'image/jpeg'` header type will actually contain png data, thus the size difference. But anyway, canvas will render it as a raw image and then re-compress it to jpeg if you do use the `canvas.toDataURL('image/jpeg')` method, so you may experience some differences in size too. – Kaiido Sep 09 '15 at 03:10
  • Also, in the fiddle, you are actually changing the image width and height, except if you do provide a 500x500 image – Kaiido Sep 09 '15 at 03:27

1 Answers1

3

What you are experiencing is that the default type argument for canvas.toDataURL(type, encoderOptions) is image/png, and also that it's actually a base64 encode of the image data.

  • For the former, most browsers do support image/jpeg as image type. This may reduce the data size, thanks to jpg compression, but this will above all enable the encoderOptions parameter of the method, which could be translated to quality parameter and requires a 0 to 1 float.
    Note that you can't be sure that the data will be lighter if converted into jpg though : if the image is mainly composed of transparent pixels, then png will be lighter. Also, remember that jpeg compression will reduce the quality of your image, see the last snippet to see what 0 results in :)

  • For the later, ( you can find an explanation of the why here ), the solution is to transform back the dataURL to a blob.

Here is a snippet to demonstrate it :

var input = document.querySelector('input');
var canvas = document.createElement('canvas'),
ctx = canvas.getContext('2d');

function draw(){
  var img = new Image();
  img.src = URL.createObjectURL(this.files[0]);
  img.onload = function(){
      canvas.width = this.naturalWidth;
      canvas.height = this.naturalHeight;
      ctx.drawImage(this, 0,0);
      var noData = canvas.toDataURL();
      png_data.innerHTML = 'dataURL length: '+noData.length;
      var noBlob = dataURItoBlob(noData);
      png_blob.innerHTML = 'blob size: '+ noBlob.size;
      var jpgData10 = canvas.toDataURL('image/jpeg', 1),
          jpgData5 = canvas.toDataURL('image/jpeg', 0.5),
          jpgData0 = canvas.toDataURL('image/jpeg', 0);
      jpg_data.innerHTML = 'quality = 1 : '+ jpgData10.length+'<br>';
      jpg_data.innerHTML += 'quality = 0.5 : '+ jpgData5.length+'<br>';
      jpg_data.innerHTML += 'quality = 0 :'+ jpgData0.length+'<br>';
      var jpgBlob10 = dataURItoBlob(jpgData10);
      jpg_blob.innerHTML = 'blob quality = 1 : '+jpgBlob10.size+'<br>';
      var jpgBlob5 = dataURItoBlob(jpgData5);
      jpg_blob.innerHTML += 'blob quality = .5 : '+jpgBlob5.size+'<br>';
      var jpgBlob0 = dataURItoBlob(jpgData0);
      jpg_blob.innerHTML += 'blob quality = 0 : '+jpgBlob0.size;
    }
  this.previousElementSibling.innerHTML = 'orginal size : '+this.files[0].size;
  }
input.onchange = draw;


// Taken from https://stackoverflow.com/a/5100158/3702797
function dataURItoBlob(dataURI) {
    var byteString;
    if (dataURI.split(',')[0].indexOf('base64') >= 0)
        byteString = atob(dataURI.split(',')[1]);
    else
        byteString = unescape(dataURI.split(',')[1]);
    var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
    var ia = new Uint8Array(byteString.length);
    for (var i = 0; i < byteString.length; i++) {
        ia[i] = byteString.charCodeAt(i);
    }
    return new Blob([ia], {type:mimeString});
}
<div></div>
<input type="file"/>
<br>PNG : 
<div id="png_data"></div>
<div id="png_blob"></div>
<br>Jpeg :
<div id="jpg_data"></div>
<div id="jpg_blob"></div>

canvas.toDataURL('image/jpeg', 0) demo ?

var canvas = document.createElement('canvas'),
    ctx = canvas.getContext('2d');
document.querySelector('input').onchange = function(){
    var img = new Image();
    img.src = URL.createObjectURL(this.files[0]);
    img.onload = function(){
        canvas.width = this.naturalWidth;
        canvas.height = this.naturalHeight;
        ctx.drawImage(this, 0,0);
        document.images[0].src = canvas.toDataURL('image/jpeg', 0);
      }
    }
<input type="file"/>
<img/>

Also, if you need to make your file lighter than a specific size, I wrote something on another question, which limited the data size to 3Mb (just change maxSize to your needs and remove the localStorage part).

Community
  • 1
  • 1
Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • Adjusting quality is fine but what quality to use with an image to achieve a desired image size? – Vivek Sep 14 '15 at 09:57
  • Except trying each quality and then check their blob size, there is no way. However, you may try to guess it by checking the original file size, type and the image height, width. – Kaiido Sep 14 '15 at 10:06
  • That's what I am doing right now but with resolution. – Vivek Sep 14 '15 at 10:35
  • 1
    @Kaiido I tried your snippet but it seems that the JPEG blob size with quality 1 is bigger than the original one. Is this expected? Anyway to make that the same with the original file size? – Mikko Paderes Jun 14 '16 at 13:49