Using raw image data from ajax request for data URI

55,073

Solution 1

Thanks for that. I've done a bit more digging on this and it turns out there is a solution at least on current versions of Firefox and Chrome (EDIT: IE10 works too). You can use XMLHttpRequest2 and use a typed array (Uint8Array). The following code works:

<!DOCTYPE html>
<html>
<head>
<script type='text/javascript'>

function init()
{
    var xmlHTTP = new XMLHttpRequest();
    xmlHTTP.open('GET','/images/photos/badger.jpg',true);

    // Must include this line - specifies the response type we want
    xmlHTTP.responseType = 'arraybuffer';

    xmlHTTP.onload = function(e)
    {

        var arr = new Uint8Array(this.response);


        // Convert the int array to a binary string
        // We have to use apply() as we are converting an *array*
        // and String.fromCharCode() takes one or more single values, not
        // an array.
        var raw = String.fromCharCode.apply(null,arr);

        // This works!!!
        var b64=btoa(raw);
        var dataURL="data:image/jpeg;base64,"+b64;
        document.getElementById("image").src = dataURL;
    };

    xmlHTTP.send();
}

</script>
</head>
<body onload='init()'>
<img id="image" alt="data url loaded image" />
</body>
</html>

Basically you ask for a binary response, then create an 8-bit unsigned int view of the data before converting it back into a (binary-friendly) string String.fromCharCode(). The apply is necessary as String.fromCharCode() does not accept an array argument. You then use btoa(), create your data url and it then works.

The following resources were useful for this:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays?redirectlocale=en-US&redirectslug=JavaScript%2FTyped_arrays

and

http://www.html5rocks.com/en/tutorials/file/xhr2/

Nick

Solution 2

Nick's answer works very well. But when I did this with a fairly large file, I got a stack overflow on

var raw = String.fromCharCode.apply(null,arr);

Generating the raw string in chunks worked well for me.

var raw = '';
var i,j,subArray,chunk = 5000;
for (i=0,j=arr.length; i<j; i+=chunk) {
   subArray = arr.subarray(i,i+chunk);
   raw += String.fromCharCode.apply(null, subArray);
}

Solution 3

I had trouble with the ArrayBuffer -> String -> Base64 method described above, but ran across another method using Blob that worked great. It's not a way to convert raw data to Base 64 (as in the title), but it is a way to display raw image data (as in the actual question):

var xhr = new XMLHttpRequest();
xhr.responseType = 'arraybuffer';
xhr.onload = function() {
    var blb = new Blob([xhr.response], {type: 'image/png'});
    var url = (window.URL || window.webkitURL).createObjectURL(blb);
    image.src = url;
}

xhr.open('GET', 'http://whatever.com/wherever');
xhr.send();

All credit goes to Jan Miksovsky, author of this fiddle. I just stumbled across it and thought it'd make a useful addition to this discussion.

Solution 4

Modern ES6 powered solution for image downloading: (without specifying image type)

async function downloadImageFromUrl(url) {    // returns dataURL
  const xmlHTTP = new XMLHttpRequest();
  xmlHTTP.open('GET', url, true);
  xmlHTTP.responseType = 'blob';
  const imageBlob = await new Promise((resolve, reject) => {
    xmlHTTP.onload = e => xmlHTTP.status >= 200 && xmlHTTP.status < 300 && xmlHTTP.response.type.startsWith('image/') ? resolve(xmlHTTP.response) : reject(Error(`wrong status or type: ${xmlHTTP.status}/${xmlHTTP.response.type}`));
    xmlHTTP.onerror = reject;
    xmlHTTP.send();
  });
  return blobToDataUrl(imageBlob);
}

function blobToDataUrl(blob) { return new Promise(resolve => {
  const reader = new FileReader();    // https://developer.mozilla.org/en-US/docs/Using_files_from_web_applications
  reader.onload = e => resolve(e.target.result);
  reader.readAsDataURL(blob);
})}

Usage:

downloadImageFromUrl('https://a.b/img.png').then(console.log, console.error)
Share:
55,073
nickw
Author by

nickw

Updated on September 09, 2020

Comments

  • nickw
    nickw over 3 years

    I'm trying to use a combination of Ajax and data URIs to load a JPEG image and extract its EXIF data with a single HTTP request. I am modifying a library (https://github.com/kennydude/photosphere) to do this; currently this library uses two HTTP requests to set the source of the image and to get the EXIF data.

    Getting the EXIF works, no problem. However I am having difficulty using the raw data from the ajax request as source for the image.

    Source code for a small test of the technique:

    <!DOCTYPE html>
    <html>
    <head>
    <script type='text/javascript'>
    
    function init()
    {
        // own ajax library - using it to request a test jpg image
        new Ajax().sendRequest
        (
            "/images/photos/badger.jpg",
             { method : "GET",
                callback: function(xmlHTTP)
                {
    
                    var encoded = btoa (unescape(encodeURIComponent(xmlHTTP.responseText)));
                    var dataURL="data:image/jpeg;base64,"+encoded;
    
                    document.getElementById("image").src = dataURL;
                }
            }
        );
    }
    
    </script>
    <script type="text/javascript" src="http://www.free-map.org.uk/0.6/js/lib/Ajax.js"></script>
    </head>
    <body onload='init()'>
    <img id="image" alt="data url loaded image" />
    </body>
    </html>
    

    I get what looks like sensible jpeg data sent back, and the length (in bytes) of the raw data and the base64-encoded-then-unencoded-again raw data is the same. However the attempt to set the image src fails on both Firefox (25) and Chrome (31) (current versions) - chrome displays "broken image" icon suggesting the src is an invalid format.

    I used this mozilla page for info on base64 encoding/decoding:

    https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding

    Any idea what might be wrong? Looking around I can create the base64 encoded image server side but can it be done client side like this? For one thing, base64 encoding server side obviously increases the data size and the whole purpose of this exercise is to cut down the amount of data being transferred from the server, as well as the number of requests.

    Thanks, Nick

  • nickw
    nickw over 10 years
    I've done a bit more digging and it turns out there is a solution to this, at least in modern HTML5 browsers (works on current Firefox/Chrome).
  • RoccoB
    RoccoB almost 10 years
    Also, for IE9 support, you'll have to include typedarray.js from this project github.com/inexorabletash/polyfill. This defines Uint8Array.
  • Jim Bergman
    Jim Bergman over 9 years
    This trick is necessary to make it work on iOS and Android. THANK YOU for posting this!
  • RoccoB
    RoccoB over 9 years
    Great post. I can finally comment now, but please see my answer below if you run into a stack overflow error because your file is too big!
  • tatactic
    tatactic almost 8 years
    Nice done! I had to do some tests but now, this works like a charm for me! in addition to this, if you right click to open the file in another tab, you don't have such a long URL compared to the ArrayBuffer -> String -> Base64 method. (btw : 'webkitURL' is deprecated. Use 'URL' instead)