19

I want to resize image at client side using JavaScript. I found 2 solutions, one is using .toDataURL() function and the other is using .toBlob() function. Both solutions are worked. Well, I just curious what is the difference between those two functions? which one is better? or when should I use .toDataURL() function or .toBlob() function?

Here is the code I used to output those two function, and I got very slightly different in image size (bytes) and image color (I'm not sure about this one). Is something wrong with the code?

<html>
    <head>
        <title>Php code compress the image</title>
    </head>
    <body>
        <input id="file" type="file" onchange="fileInfo();"><br>
        <div>
            <h3>Original Image</h3>
            <img id="source_image"/>
            <button onclick="resizeImage()">Resize Image</button>
            <button onclick="compressImage()">Compress Image</button>
            <h1 id="source_image_size"></h1>
        </div>
        <div>
            <h3>Resized Image</h3>
            <h1> image from dataURL function </h1>
            <img id="result_resize_image_dataURL"/>

            <h1> image from toBlob function </h1>
            <img id="result_resize_image_toBlob"/>
        </div>
        <div>
            <fieldset>
                <legend>Console output</legend>
                <div id='console_out'></div>
            </fieldset>
        </div>

        <script>
            //Console logging (html)
            if (!window.console)
            console = {};

            var console_out = document.getElementById('console_out');
            var output_format = "jpg";

            console.log = function(message) {
                console_out.innerHTML += message + '<br />';
                console_out.scrollTop = console_out.scrollHeight;
            }

            var encodeButton = document.getElementById('jpeg_encode_button');
            var encodeQuality = document.getElementById('jpeg_encode_quality');

            function fileInfo(){
                var preview = document.getElementById('source_image');
                var file   = document.querySelector('input[type=file]').files[0];
                var reader  = new FileReader();
                reader.addEventListener("load", function(e) {
                    preview.src = e.target.result;
                }, false);
                if (file) {
                    reader.readAsDataURL(file);
                }
            }
            function resizeImage() {
                var loadedData = document.getElementById('source_image');
                var result_image = document.getElementById('result_resize_image_dataURL');

                var cvs = document.createElement('canvas'),ctx;
                cvs.width = Math.round(loadedData.width/4);
                cvs.height = Math.round(loadedData.height/4);
                var ctx = cvs.getContext("2d").drawImage(loadedData, 0, 0, cvs.width, cvs.height);
                cvs.toBlob(function(blob) {
                    var newImg = document.getElementById('result_resize_image_toBlob'),
                        url = URL.createObjectURL(blob);
                    newImg.onload = function() {
                        // no longer need to read the blob so it's revoked
                        URL.revokeObjectURL(url);
                    };

                    newImg.src = url;
                    console.log(blob.size/1024);
                }, 'image/jpeg', 0.92);

                var newImageData = cvs.toDataURL('image/jpeg', 0.92);
                var result_image_obj = new Image();
                result_image_obj.src = newImageData;
                result_image.src = result_image_obj.src;
                var head = 'data:image/png;base64,';
                var imgFileSize = ((newImageData.length - head.length)*3/4)/1024;
                console.log(imgFileSize);
            }

Edited:

Based on Result of html5 Canvas getImageData or toDataURL - Which takes up more memory?, its said that

"DataURL (BASE64) is imageData compressed to JPG or PNG, then converted to string, and this string is larger by 37% (info) than BLOB."

What is the string mean? is it same as size in bytes? using the code I provided above, the size difference is less than 1Kb (less than 1%). is .toBlob() always better than .toDataURL() function? or there is a specific condition where it would be better to use .toDataURL() function?

Heretic Monkey
  • 11,687
  • 7
  • 53
  • 122
marahman
  • 247
  • 3
  • 8
  • 3
    Possible duplicate of [Result of html5 Canvas getImageData or toDataURL - Which takes up more memory?](https://stackoverflow.com/questions/11896740/result-of-html5-canvas-getimagedata-or-todataurl-which-takes-up-more-memory) – Temo Tchanukvadze Nov 24 '19 at 18:07
  • Stack Overflow strives to keep things as opinion-free as possible, so no one's (hopefully) going to tell you which one is "better". Both functions serve their purpose -- it's up to you to decide, under your specific circumstances, which one is more appropriate. – Heretic Monkey Nov 25 '19 at 00:33
  • 1
    @HereticMonkey I disagree, in this particular case, there is a clear "better" option. toDataURL serves no purpose that toBlob doesn't serve. It's a bit like "should I use
    or css to center my elements" kind of question, except that there is no official deprecation message (yet) for toDataURL because of the unfortunate wide use it has...
    – Kaiido Nov 25 '19 at 05:38
  • @Kaiido It's not like `
    ` vs. CSS because `toDataURL` and `toBlob` are both defined in HTML 5, not one in HTML one in CSS. But to be clear; we should not be voting on which one is "better", but rather making statements of fact, from which readers can draw their own conclusions. Your answer does that, almost. It would be better if there were citations for assertions such as "an early error in the specs", considering [it's still in HTML 5.3, with no reference to potential deprecation or obsolesence](https://www.w3.org/TR/html53/semantics-scripting.html#dom-htmlcanvaselement-todataurl)
    – Heretic Monkey Nov 25 '19 at 13:58
  • Well the truth is that there have been discussions about superseding even `toBlob` in favor of a Promise based *asBlob* or *convertToBlob* as is available on OffscreenCanvas. But, no, there is no plan to ever remove it from the specs, as I said, it's use is really too widespread, no way to come back now, but no reasons either to use it in new applications. Ps: the w3c definitions are not followed anymore, nobody else understands anything like html5.x you should read the whatwg instead (where [toDataURL](https://html.spec.whatwg.org/multipage/canvas.html#dom-canvas-todataurl) will also last) – Kaiido Nov 25 '19 at 14:16

1 Answers1

47

Definitely go with toBlob().
toDataURL is really just an early error in the specs, and had it been defined a few months later, it certainly wouldn't be here anymore, since we can do the same but better using toBlob.

toDataURL() is synchronous and will block your UI while doing the different operations

  • move the bitmap from GPU to CPU
  • conversion to the image format
  • conversion to base64 string

toBlob() on the other hand will only do the first bullet synchronously, but will do the conversion to image format in a non blocking manner. Also, it will simply not do the third bullet.
So in raw operations, this means toBlob() does less, in a better way.

toDataURL result takes way more memory than toBlob's.

The data-URL returned by toDataURL is an USVString, which contains the full binary data compressed in base64.
As the quote in your question said, base64 encoding in itself means that the binary data will now be ~37% bigger. But here it's not only encoded to base64, it is stored using UTF-16 encoding, which means that every ascii character will take twice the memory needed by raw ascii text, and we jump to a 174% bigger file than its original binary data...
And it's not over... Every time you'll use this string somewhere, e.g as the src of a DOM element*, or when sending it through a network request, this string can get reassigned in memory once again.
*Though modern browsers seem to have mechanism to handle this exact case

You (almost) never need a data-url.

Everything you can do with a data-url, you can do the same better with a Blob and a Blob-URI.

To display, or locally link to the binary data (e.g for user to download it), use a Blob-URI, using the URL.createObjectURL() method.
It is just a pointer to the binary data held in memory that the Blob itself points to. This means you can duplicate the blob-URI as many times as you wish, it will always be a ~100 chars UTF-16 string, and the binary data will not move.

If you wish to send the binary data to a server, send it as binary directly, or through a multipart/formdata request.

If you wish to save it locally, then use IndexedDB, which is able to save binary files. Storing binary files in LocalStorage is a very bad idea as the Storage object has to be loaded at every page-load.

The only case you may need data-urls is if you wish to create standalone documents that need to embed binary data, accessible after the current document is dead. But even then, to create a data-url version of the image from your canvas, use a FileReader to which you'd pass the Blob returned by canvas.toBlob(). That would make for a complete asynchronous conversion, avoiding your UI to be blocked for no good reasons.

Community
  • 1
  • 1
Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • 1
    This is a fantastic answer. Thanks so much for the detailed explanation. What are the cases where a dataURL would be justified? – Crashalot Jul 19 '20 at 22:46
  • related question: we store history state for a manipulated graphic with base64, but this answer suggests performance could improve dramatically with blob URLs. to be thorough, are there any drawbacks with blob URLs vs. base64 images? – Crashalot Jul 20 '20 at 00:03
  • @Crashalot I said "You (almost) never need a data-url", not `toDataURL`, as I explain later even in the rare case you may need a data-url, toBlob + FileReader.readAsDataURL would be better. Now, one case where toDataURL could be justified is when you actually need the synchronicity. For instance, I did use it in some projects to tests if a canvas was tainted (e.g by cross-origin data). The idea was to draw the canvas to test on a 1x1px canvas and call that 1px canvas' toDataURL. Being a single pixel canvas, the drawback was not too big, and the result was returned sync. – Kaiido Jul 20 '20 at 01:38
  • 3
    @Crashalot I can't guarantee that it will **dramatically* improve the perfs, but it should be at least easier on memory. The drawbacks of blob:// URLs are that they die with the document that did create it (you can't store in LocalStorage, though for this case, you could store the Blob in IndexedDB), and it's a bit more complex to free the memory used by the linked Blob. With a data:// URI, as soon as you unreference the string, the memory can be freed, with a blob:// URI, you need to explicitly call URL.revokeObjectURL, if you loose the blob:// URI, the blob will stay until the doc dies. – Kaiido Jul 20 '20 at 01:46
  • thanks for the explanations! last question: the OP hinted at differences in quality (eg image color). are there known differences, and if so, which method is better at preserving the original image quality? – Crashalot Jul 20 '20 at 03:27
  • @Crashalot that will be implementation dependent but I think most current browsers use the same options for both calls, i.e no quality difference. – Kaiido Jul 20 '20 at 03:30
  • thanks! what do you mean by “implementation dependent”? when would wild using a blob cause quality loss compared to dataURLs? – Crashalot Jul 20 '20 at 03:59
  • I mean that this is not tied by specs, implementers are free to set different settings between toDataURL and toBlob, but if one were to do that, they'd probably use slower but better quality settings for async toBlob than for sync toDataURL. – Kaiido Jul 20 '20 at 04:05
  • one last question if you don't mind: is there a difference between canvas or filereader to convert an image to a dataURL? there's conflicting information on stackoverflow about which is better, so would love your perspective. thanks again for all your help. – Crashalot Jul 20 '20 at 08:28
  • Convert from what? If you have an url or directly binary data, then use a FileReader, if you have a canvas, then use toBlob + a FileReader. https://stackoverflow.com/a/42916772/3702797 – Kaiido Jul 20 '20 at 08:31
  • yes, an image URL. thanks for the pointer. i read somewhere that using filereader affects the image quality but cannot find the link now. from your answer, it seems like canvas definitely does, however. so it's safe to assume filereader doesn't blur images on retina machines or otherwise affect image quality, if you have an url or a local file object? thanks again for your help! – Crashalot Jul 20 '20 at 09:00
  • No FileReader doesn't transform the data, it only reads it and displays it in different ways. – Kaiido Jul 20 '20 at 09:02
  • perfect, just received it! will delete all messages related to this once you read this. thanks again! – Crashalot Jul 20 '20 at 09:25