How to Load Binary Image Data Using JavaScript and Xmlhttprequest

Binary image loaded from server to image with javascript

If the character encoding of the XMLHttpRequest has been set to something that won't change the binary data, or you've set the response type, you can then run .responseText through btoa (putting it in base64 and letting you assign it as a data URI) or access .response for the binary data, respectively.


Assuming your instance is named xhr and you're using the charset method before xhr.send but after xhr.open do

xhr.overrideMimeType("text/plain; charset=x-user-defined");

then when you're 200 OK

var dataURI = 'data:image/jpeg;base64,' + btoa(xhr.responseText);

Which you can then set as a src of an <img>.


Again assuming xhr, this time .response method; between .open and .send,

xhr.responseType = "arraybuffer";

Then at 200 OK

var arrayBufferView = new Uint8Array(xhr.response), // can choose 8, 16 or 32 depending on how you save your images
blob = new Blob([arrayBufferView], {'type': 'image\/jpeg'}),
objectURL = window.URL.createObjectURL(blob);

Which you can then set as a src of an <img>. Example

XMLHttpRequest: Explicitly ENABLE browser-caching (binary data)

I resolved the issue by doing the obvious:

  1. Request the image via hidden img (by settings src) first. That way the image gets cached by the browser.

  2. Request the image binary data via AJAX afterwards. Since the browser then uses the above cached data, I can analyze the image data, without issueing another request.

  3. Update the src of the "visible" image.

That way I achieve what I need: Viewing a "live" image with prior data analysis wthout having to request the image twice.

How can I set an HTML5 canvas ImageData from an XMLHttpRequest response?

Loading images via XMLHttpRequest

Image files are not pixel arrays

The data you get is not a pixel array it is image data. You can read the data directly and decode it but that is a lot of work, png has many different internal formats and compression methods. And why bother when all the code to do that is already available within the browser.

Normally I would I leave it up to the browser to do all the fetching but because there are no progress events on images and games can need a lot of image data I created this to handle the problem of loading with a meaningful progress display. It does the same as you are trying to do.

Once you have the data loaded you need to get the browser to decode it for you. To do that you need to convert the data you have to a DataURL. I do that in the function arrayToImage which converts the typed array to a data url with the appropriate image header.

Then it is just a matter of creating an image and setting the source to the data URL. It is rather ugly as it requires you to create the data buffer, then the url string, then the browser makes another copy to finally get the image. (way too much memory used) If you want it as an imageData array you need to render the image to a canvas and grab the data from there.

Example image loading with (real) progress events

Below is the code, it will fail if the image does not allow cross site access, and its only benefit is that you get progress events, which is included in the snippet.

// creates an image from a binary array// buf   : is the image as an arrayBuffer// type  : is the mime image type "png", "jpg", etc...// returns a promise that has the imagefunction arrayToImage(buf, type) {    // define variables    var url, chars, bWord, i, data, len, count, stream, wordMask, imagePromise;    // define functions    imagePromise = function (resolve, reject) { // function promises to return an image        var image = new Image(); // create an image        image.onload = function () { // it has loaded            resolve(image); // fore fill the promise        }        image.onerror = function () { // something rotten has happened            reject(image); // crossing the fingers        }        image.src = url; // use the created data64URL to ceate the image    }
wordMask = 0b111111; // mask for word base 64 word stream = 0; // to hold incoming bits; count = 0; // number of bits in stream; // 64 characters used to encode the 64 values of the base64 word chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
data = new Uint8Array(buf); // convert to byte array len = data.byteLength; // get the length; url = 'data:image/' + type.toLowerCase() + ';base64,'; // String to hold the image URL
// get each byte and put it on the bit stream for (i = 0; i < len; i++) { stream |= data[i]; // add byte to bit stream count += 8; // add the number of bits added to stream if (count === 12) { // if there are two 6bit words on the stream url += chars[(stream >> 6) & wordMask] + chars[stream & wordMask]; // encode both words and add to base 64 string stream = 0; // stream is empty now so just zero count = 0; // no bits on the stream } else { url += chars[(stream >> (count - 6)) & wordMask]; // encode top 6 bits and add to base64 string count -= 6; //decrease the bit count by the 6 removed bits stream <<= 8; // make room for next 8 bits } } if (count > 0) { // there could be 2 or 4 remaining bits url += chars[(stream >> (count + 2)) & wordMask]; // shift them back to B64 word size and encode } // data url constructed for image so lets promise to create it return new Promise(imagePromise); // return the promise}// loads an image via ajax providing progress data// WARNING cross domain images will fail if they have a CORS header prohibiting your domain from access// filename : url of the image file// progress : progress call back. This is called on progress events// returns a promise of an imagevar loadImage = function(filename,progress){ // declare variables var imagePromise; // declare functions imagePromise = function(resolve, reject){ // promise an image // decalare vars; var ajax, image, load, failed; // decalare functions failed = function (reason) { reject("Shit happens"); } // pass on the bad news load = function (e) { // handle load event // declare vars var type, loaded; // decalare functions loaded = function (image) { resolve(image);} // resolve the promise of an image
if(e.currentTarget.status !== 200){ // anything but OK reject the promise and say sorry reject("Bummer dude! Web says '"+e.currentTarget.status+"'"); }else{ type = filename.split(".").pop(); // ok we have the image as a binary get the type // now convert it to an image arrayToImage(e.currentTarget.response,type) // return a promise .then(loaded) // all good resolve the promise we made .catch(failed); // failed could be a bug in the soup. } }; ajax = new XMLHttpRequest(); // create the thingy that does the thing ajax.overrideMimeType('text/plain; charset=x-user-defined'); // no not an image. ajax.responseType = 'arraybuffer'; // we want it as an arraybuffer to save space and time ajax.onload = load; // set the load function ajax.onerror = failed; // on error ajax.onprogress = progress; // set the progress callback ajax.open('GET', filename, true); // point to the image url ajax.send(); // command the broswer to wrangle this image from the server gods } return new Promise(imagePromise);}

// the progress display. Something that looks profesional but still hates the status quo.var displayProgress = function(event){ // event is the progress event // decalre vars var w,h,x,y,p,str; w = ctx.canvas.width; // get the canvas size h = ctx.canvas.height; x = w/2-w/4; // locate the progress bar w /= 2; // make it in the center y = h/2-10; if(event.lengthComputable){ // does the progress know whats coming p = event.loaded/event.total; // yes so get the fraction found str = Math.floor(p*100)+"%"; // make it text for the blind }else{ p = event.loaded/1024; // dont know how much is comine so get number killobytes str = Math.floor(p) + "k"; // for the gods p /= 50; // show it in blocks of 50k }
ctx.strokeStyle = "white"; // draw the prgress bar in black and white ctx.fillStyle = "black"; ctx.lineWidth = 2; // give it go fast lines ctx.beginPath(); ctx.rect(x,y,w,20); // set up the draw ctx.fill(); // fill ctx.stroke(); // then stroke
ctx.fillStyle = "white"; // draw text in white ctx.font = "16px verdana"; // set the font ctx.textAlign = "center"; // centre it ctx.textBaseline = "middle"; // in the middle please ctx.fillText(str,x+w/2,y+10); // draw the text in the center
ctx.globalCompositeOperation = "difference"; // so the text is inverted when bar ontop ctx.beginPath(); ctx.fillRect(x+3,y+3,(p*(w-6))%w,14); // draw the bar, make sure it cycles if we dont know what coming
ctx.globalCompositeOperation = "source-over"; // resore the comp state}var canvas = document.createElement("canvas");canvas.width = window.innerWidth;canvas.height = window.innerHeight;document.body.appendChild(canvas);ctx = canvas.getContext("2d");
// The image name. var imageName = "https://upload.wikimedia.org/wikipedia/commons/thumb/c/cb/Broadway_tower_edit.jpg/800px-Broadway_tower_edit.jpg"; // lets load the image and see if all this actualy works. loadImage(imageName, displayProgress) .then(function (image) { // well what do you know it works ctx.drawImage(image, 0, 0, ctx.canvas.width, ctx.canvas.height); // draw the image on the canvas to prove it }) .catch(function (reason) { console.log(reason); // did not load, that sucks! })

How to get image using XMLHttpRequest in javascript

Don't use XMLHttpRequest. Instead, just set the src of the img element to your .net script:

function b() {
var img = document.getElementById("myimg");
img.src = "Default.aspx";
}

If this doesn't work because the browser thinks it's the same image file just add some junk query param to the url:

img.src = "Default.aspx?q=" + Math.random();

Downloading an image using XMLHttpRequest in a userscript

XMLHttpRequest will not work cross-domain, but since this is a userscript Chrome now supports GM_xmlhttpRequest() in userscripts only.

Something like this should work, note that it is asynchronous:

GM_xmlhttpRequest ( {
method: 'GET',
url: 'https://fbcdn-photos-a.akamaihd.net/hphotos-ak-ash4/299595_10150290138650735_543370734_8021370_355110168_n.jpg?dl=1',
onload: function (responseDetails) {
alert(responseDetails.statusText);
}
} );




As for getting and using the actual image data, that is a major pain to work out.

  • You can use the new .responseType = "blob"; functionality in Firefox but Chrome does not yet support it.

  • In Chrome or Firefox, for the same domain only, you can use the new XHR2 like so:

    See it in action at jsBin.

    BlobBuilder             = window.MozBlobBuilder || window.WebKitBlobBuilder || window.BlobBuilder;

    var url = "http://jsbin.com/images/gear.png";
    var request = new XMLHttpRequest();
    request.open ("GET", url, false);
    request.responseType = "arraybuffer";
    request.send (null);

    if (request.status === 200) {
    var bb = new BlobBuilder ();
    bb.append (request.response); // Note: not request.responseText

    var blob = bb.getBlob ('image/png');
    var reader = new FileReader ();
    reader.onload = function (zFR_Event) {
    $("body").prepend ('<p>New image: <img src="' + zFR_Event.target.result + '"></p>')
    };

    reader.readAsDataURL (blob);
    }


  • Unfortunately, GM_xmlhttpRequest() does not (yet) support setting responseType.


So, for GM script or userscript applications, we have to use a custom base64 encoding scheme like in "Javascript Hacks: Using XHR to load binary data".

The script code becomes something like:

var imgUrl              = "http://jsbin.com/images/gear.png";

GM_xmlhttpRequest ( {
method: 'GET',
url: imgUrl,
onload: function (respDetails) {
var binResp = customBase64Encode (respDetails.responseText);

/*-- Here, we just demo that we have a valid base64 encoding
by inserting the image into the page.
We could just as easily AJAX-off the data instead.
*/
var zImgPara = document.createElement ('p');
var zTargetNode = document.querySelector ("body *"); //1st child

zImgPara.innerHTML = 'Image: <img src="data:image/png;base64,'
+ binResp + '">';
zTargetNode.parentNode.insertBefore (zImgPara, zTargetNode);
},
overrideMimeType: 'text/plain; charset=x-user-defined'
} );

function customBase64Encode (inputStr) {
var
bbLen = 3,
enCharLen = 4,
inpLen = inputStr.length,
inx = 0,
jnx,
keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
+ "0123456789+/=",
output = "",
paddingBytes = 0;
var
bytebuffer = new Array (bbLen),
encodedCharIndexes = new Array (enCharLen);

while (inx < inpLen) {
for (jnx = 0; jnx < bbLen; ++jnx) {
/*--- Throw away high-order byte, as documented at:
https://developer.mozilla.org/En/Using_XMLHttpRequest#Handling_binary_data
*/
if (inx < inpLen)
bytebuffer[jnx] = inputStr.charCodeAt (inx++) & 0xff;
else
bytebuffer[jnx] = 0;
}

/*--- Get each encoded character, 6 bits at a time.
index 0: first 6 bits
index 1: second 6 bits
(2 least significant bits from inputStr byte 1
+ 4 most significant bits from byte 2)
index 2: third 6 bits
(4 least significant bits from inputStr byte 2
+ 2 most significant bits from byte 3)
index 3: forth 6 bits (6 least significant bits from inputStr byte 3)
*/
encodedCharIndexes[0] = bytebuffer[0] >> 2;
encodedCharIndexes[1] = ( (bytebuffer[0] & 0x3) << 4) | (bytebuffer[1] >> 4);
encodedCharIndexes[2] = ( (bytebuffer[1] & 0x0f) << 2) | (bytebuffer[2] >> 6);
encodedCharIndexes[3] = bytebuffer[2] & 0x3f;

//--- Determine whether padding happened, and adjust accordingly.
paddingBytes = inx - (inpLen - 1);
switch (paddingBytes) {
case 1:
// Set last character to padding char
encodedCharIndexes[3] = 64;
break;
case 2:
// Set last 2 characters to padding char
encodedCharIndexes[3] = 64;
encodedCharIndexes[2] = 64;
break;
default:
break; // No padding - proceed
}

/*--- Now grab each appropriate character out of our keystring,
based on our index array and append it to the output string.
*/
for (jnx = 0; jnx < enCharLen; ++jnx)
output += keyStr.charAt ( encodedCharIndexes[jnx] );
}
return output;
}


Related Topics



Leave a reply



Submit