52
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://static.reddit.com/reddit.com.header.png', true);

xhr.responseType = 'arraybuffer';

xhr.onload = function(e) {
  if (this.status == 200) {
    var uInt8Array = new Uint8Array(this.response);
    var byte3 = uInt8Array[4]; 

    var bb = new WebKitBlobBuilder();
    bb.append(xhr.response);
    var blob = bb.getBlob('image/png'); 
    var base64 = window.btoa(blob);
    alert(base64);

  }
};

xhr.send();

Basically, what I am trying to do here is retrieve an image, and convert it to base64.

From reading in the comments here, it states:

"Sure. After fetching a resource as an ArrayBuffer, create a blob from it. Once you have that, you could base64 encode the file/blob directly window.btoa() or FileReader.readAsDataURL()."

However, blob is just [object blob], while I need to get the binary from the image so I can convert it to base64 and display it in a img tag using data.

Anyone know how to achieve this?

Thank you in advance!

Marius Mucenicu
  • 1,685
  • 2
  • 16
  • 25
Nick Bennet
  • 533
  • 1
  • 5
  • 4
  • I find it very strange that you fetch image date with XHR... DOes it even work cross origin wise? Is your domain in reddit's Access-Control-Allow-Origin list? – Rudie Nov 05 '11 at 19:03
  • 1
    That's just an example, the actual domain is localhost – Nick Bennet Nov 05 '11 at 19:05

4 Answers4

74

Don't use BlobBuilder in Chrome (tested in OSX Chrome, Firefox 12, Safari 6, iOS Chrome, iOS Safari):

ex1 : http://jsfiddle.net/malraux/xGUsu/ (principle)

ex2: http://jsfiddle.net/xGUsu/78/ (working with full example)

var xhr = new XMLHttpRequest();
xhr.open('GET', 'doodle.png', true);

xhr.responseType = 'arraybuffer';

// Process the response when the request is ready.
xhr.onload = function(e) {
  if (this.status == 200) {
    // Create a binary string from the returned data, then encode it as a data URL.
    var uInt8Array = new Uint8Array(this.response);
    var i = uInt8Array.length;
    var binaryString = new Array(i);
    while (i--)
    {
      binaryString[i] = String.fromCharCode(uInt8Array[i]);
    }
    var data = binaryString.join('');

    var base64 = window.btoa(data);

    document.getElementById("myImage").src="data:image/png;base64," + base64;
  }
};

xhr.send();

Note: This code is over 7 years old at this point. While it should still function in most browsers, here's an updated version based on a suggestion by @TypeError that will only work in more modern browsers with the possible exception of iOS Safari (which may or may not support responseType = 'blob' - make sure to test!):

var xhr = new XMLHttpRequest();
xhr.open('get', 'doodle.png', true);

// Load the data directly as a Blob.
xhr.responseType = 'blob';

xhr.onload = () => {
  document.querySelector('#myimage').src = URL.createObjectURL(this.response);
};

xhr.send(); 
Scott A
  • 7,745
  • 3
  • 33
  • 46
  • 1
    xhr.responseType = 'blob' isn't implemented in chrome, it needs to be an arraybuffer. See http://code.google.com/p/chromium/issues/detail?id=52486 – Nick Bennet Nov 05 '11 at 19:06
  • 2
    THANK YOU! Just what I was looking for! Scott A, you're a god in my eyes. Confirmed it works :) – Nick Bennet Nov 05 '11 at 20:16
  • 1
    Is there a way to do this without iterating through the data byte-by-byte? – metadaddy Mar 06 '12 at 05:59
  • @metadaddy: take note that the Blob constructor is only available in the newest browsers. – Scott A Mar 31 '13 at 19:26
  • Can i use the above given code for xhr.open('POST', 'doodle.png', true); ? – V_PULL Feb 06 '14 at 10:18
  • @ScottA: Why not to [load the file as a raw text via ajax](http://jsfiddle.net/Ft9d3/7/), and process the string using str[i] ? – Hugolpz Jul 23 '14 at 18:13
  • @Hugolpz The trick is getting it into base64 so it can be used as a data URL. Newer browsers have some of this built in. – Scott A Jul 23 '14 at 18:44
  • 1
    @ScottA: Ok, I previously didn't realize that the file we load is_not / is_different_from `base64`. Images are in something else, [BLOB](http://stackoverflow.com/tags/blob/info), "a collection of binary data stored as a single entity". So you must convert first into base64. Got it. – Hugolpz Jul 24 '14 at 06:02
  • @ScottA: You may be able to give a solution to this extension of your answer. [Turn XMLhttpRequest into a function fails: asynchronity or other?](http://stackoverflow.com/questions/24939562/) – Hugolpz Jul 24 '14 at 16:52
53

You can fetch a Blob and use window.URL.createObjectURL. This prevents building giant strings and copying everything a couple of times.

var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://i.imgur.com/sBJOoTm.png', true);

xhr.responseType = 'blob';

xhr.onload = function(e) {
  if (this.status == 200) {
    var blob = this.response;
    document.getElementById("myImage").src = window.URL.createObjectURL(blob);
  }
};

xhr.onerror = function(e) {
  alert("Error " + e.target.status + " occurred while receiving the document.");
};

xhr.send();
<img id="myImage">

Example (same code): http://jsfiddle.net/ysangkok/sJxXk/86/ . Works in Firefox and Chrome 25+. And all other browsers except Opera Mini: http://caniuse.com/#search=Blob

Janus Troelsen
  • 20,267
  • 14
  • 135
  • 196
  • 2
    The Blob constructor is only available in the newest browsers. For example, if you have to support IE8 or 9 you won't be able to use it. – Scott A Mar 31 '13 at 19:27
  • 2
    @JanusTroelsen Which means absolutely nothing if you have to support one of those browsers (and it's a greater percentage than is used by Firefox regardless). :-) Incidentally, IE10 is only < 4%, so you're essentially arguing that developers should completely ignore IE, and if you look at NetApplications instead of StatCounter then IE 8+9 is 44% of the desktop. Having said that, your Blob solution doesn't work on: Safari 6.0.2 on OS X, Firefox 12 on OS X, or Safari and Chrome on my iPhone 4s either. – Scott A Apr 01 '13 at 15:49
  • 2
    @JanusTroelsen And now you're making a political argument, not a practical one. Non-free vs. free isn't an option for those of us who have to work for a living in the real world, but if you're able to support yourself and stick to an unforgiving stance then more power to you. I personally had a very large corporate client that was still using IE6 internally as recently as 18 months ago, and that is now on IE8. I didn't have the option to tell them that I was only going to support Chrome or Firefox because they were free software. – Scott A Apr 01 '13 at 18:04
  • 1
    @JanusTroelsen, Why not just set the `responseType` to blob instead of doing it indirectly via `arraybuffer`? See this code: http://jsfiddle.net/RE9YJ/ – Pacerier May 02 '14 at 11:11
  • In AngularJS, pass in `{responseType: 'blob'}` as a config option to `$http`. – z0r Oct 15 '15 at 00:48
  • In Chromium 37, Ubuntu 12.04 after several successful attempts, it gives net::ERR_FAILED afterwards. About 20 first requests are ok, then all are (failed). Browser restart always helps. The only solution I have found so far is to use responsetype: 'arraybuffer' (which always works) and convert it into blob using the above reply. – user109764 Oct 24 '15 at 19:08
  • Useful information user109764, but I think it should be noted that Chromium 37 is unsupported everywhere, and has a very small user base. – Janus Troelsen Oct 24 '15 at 23:06
  • The problem seemed to be about the disk low space issue. I suspected it was it, but my /home partition was big enough. While /tmp was of 'overflow' filesystem and was of 1mb only. After I restarted my computer, the tmp-overflow problem as well Chromium-blob were gone! – user109764 Oct 24 '15 at 23:27
  • then for saving the file in IE 11 or Egde: window.navigator.msSaveOrOpenBlob(blob, 'filename.pdf'); – Oleksii Kyslytsyn Apr 13 '17 at 09:19
  • Uncaught TypeError: Failed to execute 'createObjectURL' on 'URL': No function was found that matched the signature provided. at XMLHttpRequest.xhttp.onreadystatechange Am receiving this error in chrome latest version. Any help for this. @JanusTroelsen – abhilash-ram Jun 29 '18 at 08:56
9

XMLHttpRequest

var xmlhttp = new XMLHttpRequest();
xmlhttp.open('GET', 'http://RestServiceURL-Returns Image', true);
xmlhttp.setRequestHeader('Content-type','application/x-www-form-urlencoded');
xmlhttp.responseType = 'arraybuffer/blob';
xmlhttp.send();

creating blob image in 3-ways.

  • window.URL.createObjectURL
  • FileReader (caniuse)
  • Base64String

    xmlhttp.onload = function() {
        var blob = new Blob([this.response], {type: 'image/png'}); 
        console.log(blob, blob.type, this.response, typeof this.response);  
    
        var image = document.getElementById('my-image');
    
        1)image.src = window.URL.createObjectURL(blob);
    
        2)var fileReader = new window.FileReader();
        fileReader.readAsDataURL(blob);
        fileReader.onloadend = function() { 
        image.src = fileReader.result;
        }
    
        3)var base64String = btoa(String.fromCharCode.apply(null, new Uint8Array(this.response)));
        image.src = 'data:image/png;base64,'+base64String;
    };
    

Converting ArrayBuffer to Blob to ArrayBuffer

1)var dataView = new DataView(arrayBuffer);
var blob = new Blob([dataView], { type: mimeString });


2)fileReader.readAsArrayBuffer(blob);
var arrayBuffer;
fileReader.onload = function() {
    arrayBuffer = this.result;
};
Yash
  • 9,250
  • 2
  • 69
  • 74
7

Same solution as suggested by Janus Troelsen with promise added...

Note! when using createObjectURL - don't forget to call revokeObjectURL

//  Load blob (promise)
function loadBlob( url ){
    return new Promise( (resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open('GET', url, true);
        xhr.responseType = 'blob';        
        xhr.onload  = () => resolve(xhr.response);
        xhr.onerror = () => reject(xhr.statusText);        
        xhr.send();
    });
}

//  Create image from blob (createObjectURL)
function imageFromBlob( blob ){ 
    const img = new Image();
    img.onload = () => URL.revokeObjectURL(img.src);
    img.src = URL.createObjectURL(blob);    
    return img;
}


//  Create image from blob if loaded successfully
loadBlob('https://unsplash.it/960/540?random')
    .then( blob => {
        document.body.appendChild( imageFromBlob(blob) );      
    })
    .catch( error => {
        console.log('Could not load image');
    })
    


//  Alternate version adding promise to xhr
//  if you like to trigger xhr.send() yourself
function xhrBlob(url){
    const xhr = new XMLHttpRequest();
    xhr.open('GET', url, true);
    xhr.responseType = 'blob';        
    xhr.promise = new Promise((resolve, reject) => {
        xhr.onload  = () => resolve(xhr.response);
        xhr.onerror = () => reject(xhr.statusText);  
    });
    xhr.load = ( onsuccess = () => {}, onerror = () => {} ) => { 
        xhr.promise.then(onsuccess).catch(onerror);
        xhr.send();
        return xhr;
    }
    return xhr;
}


//  Using load callbacks
xhrBlob('https://unsplash.it/960/540?random')
    .load( 
        //  on sussess
        blob => {
            document.body.appendChild( imageFromBlob(blob) );      
        },
        //  on error
        error => {
            console.log('Could not load image');
        }
    );
    
 //  Using promise (delayed)
const image = xhrBlob('https://unsplash.it/960/540?random');

    //  Promise handlers
    image.promise
    .then( blob => {
        document.body.appendChild( imageFromBlob(blob) );      
    })
    .catch( error => {
        console.log('Could not load image');
    });
 
 //  Load image (will trigger promise handlers)
 setTimeout(image.load, 3000);
img {
  width: 100%;
}
Jakob E
  • 4,476
  • 1
  • 18
  • 21