Downsizing image dimensions via pure JS leads to image size inflation (in bytes)

前端 未结 2 752
再見小時候
再見小時候 2020-11-27 22:48

I\'m a server-side dev learning the ropes of client side manipulation, starting with pure JS.

Currently I\'m using pure JS to resize the dimensions of images upload

相关标签:
2条回答
  • This is a recommendation and not really a fix (or a solution).

    If you've run into this problem, make sure you compare the file sizes of the two images once you've completed the resize operation. If the new file is larger, then simply fallback to the source image.

    0 讨论(0)
  • 2020-11-27 23:21

    Why Canvas is not the best option to shrink an image file size.

    I won't go into too much details, nor in depth explanations, but I will try to explain to you the basics of what you encountered.

    Here are a few concepts you need to understand (at least partially).

    • What is a lossy image format (like JPEG)
    • What happens when you draw an image to a canvas
    • What happens when you export a canvas image to an image format

    Lossy Image Format.

    Image formats can be divided in three categories:

    • raw Image formats
    • lossless image formats (tiff, png, gif, bmp, webp ...)
    • lossy image formats (jpeg, ...)

    Lossless image formats generally simply compress the data in a table mapping pixel colors to the pixel positions where this color is used.

    On the other hand, Lossy image formats will discard information and produce approximation of the data (artifacts) from the raw image in order to create a perceptively similar image rendering, using less data.

    Approximation (artifacts) works because the decompression algorithm knows that it will have to spread the color information on a given area, and thus it doesn't have to keep every pixels information.

    But once the algorithm has treated the raw image, and produced the new one, there is no way to find back the lost data.


    Drawing an image to the canvas.

    When you draw an image on a canvas, the browser will convert the image information to a raw image format.
    It won't store any information about what image format was passed to it, and in the case of a lossy image, every pixels contained in the artifacts will become a first class citizen as every other pixels.


    Exporting a canvas image

    The canvas 2D API has three methods to export its raw data:

    • getImageData. Which will return the raw pixels RGBA values
    • toDataURL. Which will apply a compression algorithm corresponding to the MIME you passed as argument, synchronously.
    • toBlob. Similar to toDataURL, but asynchronously.

    The case we are interested in is the one of toDataURL and toBlob along with the "image/jpeg" MIME.
    Remember that when calling this method, the browser only sees the current raw pixel data it has on the canvas. So it will apply once again the jpeg algorithm, removing some data, and producing new approximations (artifacts) from this raw image.

    So, yes, there is an 0-1 quality parameter available for lossy compression in these methods, so one could think that we could try to know what was the original loss level used to generate the original image, but even then, since we actually produced new image data in the drawing-to-canvas step, the algorithm might not be able to produce a good spreading scheme for these artifacts.

    An other thing to take into consideration, mostly for toDataURL, is that browsers have to be as fast as possible when doing these operations, and thus they will generally prefer speed over compression quality.


    Alright, the canvas is not good for it. What then?

    Not so easy for jpeg images... jpegtran claims it can do a lossless scaling of your jpeg images, so I guess it should be possible to make a js port too, but I don't know any...



    Special note about lossless formats

    Note that your resizing algorithm can also produce bigger png files, here is an example case, but I'll let the reader guess why this happens:

    var ctx= c.getContext('2d');
    c.width = 501;
    for(var i = 0; i<500; i+=10) {
      ctx.moveTo(i+.5, 0);
      ctx.lineTo(i+.5, 150);
    }
    ctx.stroke();
    
    c.toBlob(b=>console.log('original', b.size));
    
    c2.width = 500;
    c2.height = (500 / 501) * c.height;
    c2.getContext('2d').drawImage(c, 0, 0, c2.width, c2.height);
    c2.toBlob(b=>console.log('resized', b.size));
    <canvas id="c"></canvas>
    <canvas id="c2"></canvas>

    0 讨论(0)
提交回复
热议问题