How do I load binary image data using Javascript and XMLHttpRequest?

Ref:http://stackoverflow.com/questions/1095102/how-do-i-load-binary-image-data-using-javascript-and-xmlhttprequest
Ref:http://emilsblog.lerch.org/2009/07/javascript-hacks-using-xhr-to-load.html 
// one-time code
if(/msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent)) {
    var IEBinaryToArray_ByteStr_Script =
    "<!-- IEBinaryToArray_ByteStr -->\r\n"+
    "<script type='text/vbscript'>\r\n"+
    "Function IEBinaryToArray_ByteStr(Binary)\r\n"+
    "   IEBinaryToArray_ByteStr = CStr(Binary)\r\n"+
    "End Function\r\n"+
    "Function IEBinaryToArray_ByteStr_Last(Binary)\r\n"+
    "   Dim lastIndex\r\n"+
    "   lastIndex = LenB(Binary)\r\n"+
    "   if lastIndex mod 2 Then\r\n"+
    "       IEBinaryToArray_ByteStr_Last = Chr( AscB( MidB( Binary, lastIndex, 1 ) ) )\r\n"+
    "   Else\r\n"+
    "       IEBinaryToArray_ByteStr_Last = "+'""'+"\r\n"+
    "   End If\r\n"+
    "End Function\r\n"+
    "</script>\r\n";

    // inject VBScript
    document.write(IEBinaryToArray_ByteStr_Script);
}


// each time you make a request for a binary resource:
var req = (function() {
    if (window.XMLHttpRequest) {
        return new window.XMLHttpRequest();
    }
    else {
        try {
            return new ActiveXObject("MSXML2.XMLHTTP");
        }
        catch(ex) {
            return null;
        }
    }
})();

var fileContents = "";
var filesize = -1;
var readByteAt = function(i){
    return fileContents.charCodeAt(i) & 0xff;
};

req.open("GET", url, true);

if(/msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent)) {
    // IE-specific logic here
    // helper to convert from responseBody to a "responseText" like thing
    var convertResponseBodyToText = function (binary) {
        var byteMapping = {};
        for ( var i = 0; i < 256; i++ ) {
            for ( var j = 0; j < 256; j++ ) {
                byteMapping[ String.fromCharCode( i + j * 256 ) ] =
                    String.fromCharCode(i) + String.fromCharCode(j);
            }
        }
        var rawBytes = IEBinaryToArray_ByteStr(binary);
        var lastChr = IEBinaryToArray_ByteStr_Last(binary);
        return rawBytes.replace(/[\s\S]/g,
                                function( match ) { return byteMapping[match]; }) + lastChr;
    };

    req.setRequestHeader("Accept-Charset", "x-user-defined");
    req.onreadystatechange = function(event){
        if (req.readyState == 4) {
            if (req.status == 200) {
                fileContents = convertResponseBodyToText(req.responseBody);
                fileSize = fileContents.length-1;
                // invoke a callback here, if you like...
            }
            else{
                alert("download failed, status " + req.status);
            }
        }
    };
    req.send();

} else {
    // ff/Gecko/Webkit specific stuff here
    req.onreadystatechange = function(aEvt) {
        if (req.readyState == 4) { // completed
            if(req.status == 200){ // status == OK
                fileContents = binStream.req.responseText;
                filesize = fileContents.length;
                // invoke a callback here, if you like...
            }
            else {
                alert("download failed, status " + req.status);
            }
        }
    };
    // coerce response type
    req.overrideMimeType('text/plain; charset=x-user-defined');
    req.send(null);
}
Ref:  http://stackoverflow.com/questions/1095102/how-do-i-load-binary-image-data-using-javascript-and-xmlhttprequest
 
Ref:http://emilsblog.lerch.org/2009/07/javascript-hacks-using-xhr-to-load.html
 

Javascript Hacks: Using XHR to load binary data

Tuesday, July 07, 2009
I recently needed to get image data from a server using Javascript, base64 encode it, and post that data back to an application. While the details of why I needed to do this are a bit complex, I believe that getting image data through an XMLHttpRequest object and base 64 enconding it will become more valuable in terms of client-side image manipulation using the data URI scheme for image tags.

This would allow a Javascript developer, for instance, to load an existing image (say, a photo), without base64 encoding it on the server, load it into an image tag with a data URI, and make direct manipulations on that image.

Unfortunately, this area is relatively new and browsers have a lot of differences. Data URI support is still very new, inconsistent, and limited. In the meantime, here is how you get that base64 encoded image in the first place:

Internet Explorer:

IE has a property of XMLHttpRequest object for binary data ResponseBody. This contains exactly what we need, but unfortunately the property is not visible to Javascript. Since the string returned to Javascript by ResponseText will be terminated at the first null value, we must use ResponseBody. This requires a bit of VBScript, which can do one of the following things:
  1. Get the numeric value of each unsigned byte and turn that into a number in a string of comma delimited numbers. This is less efficient, but gets you in and out of VBScript as quickly as possible, allowing a generic base64 encoding routine. This is the route I followed (it may be less efficient, but it pales in comparison with the XHR request just made):
    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    Function BinaryArrayToAscCSV( aBytes )
     Dim j, sOutput
            sOutput = "BinaryArrayToAscCSV"
     For j = 1 to LenB(aBytes)
      sOutput= sOutput & AscB( MidB(aBytes,j,1) )
      sOutput= sOutput & ","
     Next
     BinaryArrayToAscCSV = sOutput
    End Function
  2. Base 64 encode it directly in VBScript.

Once this is done, we can then base64 encode it using a fairly generic function in Javascript:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
Base64 = {
  
 // private property
 _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
 
 encodeBinaryArrayAsString : function(input){
  var ascArr;
  var output = "";
  var bytebuffer;
  var encodedCharIndexes = new Array(4);
   
  var inx = 0;
  ascArr = input.substring("BinaryArrayToAscCSV".length, input.length - 1).split(',');
  while(inx < ascArr.length){
   // Fill byte buffer array
   bytebuffer = new Array(3);
   for(jnx = 0; jnx < bytebuffer.length; jnx++)
    if(inx < ascArr.length)
     bytebuffer[jnx] = parseInt(ascArr[inx++]);
    else
     bytebuffer[jnx] = 0;
      
   // Get each encoded character, 6 bits at a time
   // index 1: first 6 bits
   encodedCharIndexes[0] = bytebuffer[0] >> 2; 
   // index 2: second 6 bits (2 least significant bits from input byte 1 + 4 most significant bits from byte 2)
   encodedCharIndexes[1] = ((bytebuffer[0] & 0x3) << 4) | (bytebuffer[1] >> 4); 
   // index 3: third 6 bits (4 least significant bits from input byte 2 + 2 most significant bits from byte 3)
   encodedCharIndexes[2] = ((bytebuffer[1] & 0x0f) << 2) | (bytebuffer[2] >> 6); 
   // index 3: forth 6 bits (6 least significant bits from input byte 3)
   encodedCharIndexes[3] = bytebuffer[2] & 0x3f; 
    
   // Determine whether padding happened, and adjust accordingly
   paddingBytes = inx - (ascArr.length - 1);
   switch(paddingBytes){
    case 2:
     // Set last 2 characters to padding char
     encodedCharIndexes[3] = 64;
     encodedCharIndexes[2] = 64;
     break;
    case 1:
     // Set last character to padding char
     encodedCharIndexes[3] = 64;
     break;
    default:
     break; // No padding - proceed
   }
   // Now we will grab each appropriate character out of our keystring
   // based on our index array and append it to the output string
   for(jnx = 0; jnx < encodedCharIndexes.length; jnx++)
    output += this._keyStr.charAt(encodedCharIndexes[jnx]);    
  }
  return output;
 }
};



Firefox:

Firefox works a little differently, as there is no RequestBody property. In this case, RequestText is not truncated as long as you override the mime type coming from the server, forcing Firefox to pass the data unaltered. All we need to do is compensate for binary data coming back and being placed in a Unicode Javascript string. To compensate, we can AND each character with 0xFF to throw away the high-order byte (see https://developer.mozilla.org/En/Using_XMLHttpRequest#Handling_binary_data). The resulting encoding function looks like this:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
Base64 = {
  
 // private property
 _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
 
 encodeBinary : function(input){
  var output = "";
  var bytebuffer;
  var encodedCharIndexes = new Array(4);
  var inx = 0;
  var paddingBytes = 0;
    
  while(inx < input.length){
   // Fill byte buffer array
   bytebuffer = new Array(3);
   for(jnx = 0; jnx < bytebuffer.length; jnx++)
    if(inx < input.length)
     bytebuffer[jnx] = input.charCodeAt(inx++) & 0xff; // throw away high-order byte, as documented at: https://developer.mozilla.org/En/Using_XMLHttpRequest#Handling_binary_data
    else
     bytebuffer[jnx] = 0;
    
   // Get each encoded character, 6 bits at a time
   // index 1: first 6 bits
   encodedCharIndexes[0] = bytebuffer[0] >> 2; 
   // index 2: second 6 bits (2 least significant bits from input byte 1 + 4 most significant bits from byte 2)
   encodedCharIndexes[1] = ((bytebuffer[0] & 0x3) << 4) | (bytebuffer[1] >> 4); 
   // index 3: third 6 bits (4 least significant bits from input byte 2 + 2 most significant bits from byte 3)
   encodedCharIndexes[2] = ((bytebuffer[1] & 0x0f) << 2) | (bytebuffer[2] >> 6); 
   // index 3: forth 6 bits (6 least significant bits from input byte 3)
   encodedCharIndexes[3] = bytebuffer[2] & 0x3f; 
    
   // Determine whether padding happened, and adjust accordingly
   paddingBytes = inx - (input.length - 1);
   switch(paddingBytes){
    case 2:
     // Set last 2 characters to padding char
     encodedCharIndexes[3] = 64;
     encodedCharIndexes[2] = 64;
     break;
    case 1:
     // Set last character to padding char
     encodedCharIndexes[3] = 64;
     break;
    default:
     break; // No padding - proceed
   }
   // Now we will grab each appropriate character out of our keystring
   // based on our index array and append it to the output string
   for(jnx = 0; jnx < encodedCharIndexes.length; jnx++)
    output += this._keyStr.charAt(encodedCharIndexes[jnx]);
  }
  return output;
 };

Ideally we'd combine these two functions into a single encoding function, but I've left them separate for clarity. Note also that these techniques do not appear to work for Safari, Chrome or Opera. It should work for IE6 if using the correct ActiveX XHR object, but I was not supporting IE6. I did a spot check on Safari/Chrome/Opera and they were not working, but I did not investigate as they were not supported browsers for my implementation. The actual XHR function I used was:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
LoadBinaryResource = function(url) {
  var req = new XMLHttpRequest(); 
  req.open('GET', url, false); 
 
  if (req.overrideMimeType)
    req.overrideMimeType('text/plain; charset=x-user-defined'); 
  req.send(null); 
  if (req.status != 200) return ''
  if (typeof(req.responseBody) !== 'undefined') return BinaryArrayToAscCSV(req.responseBody);
  return req.responseText; 
}
 
LoadBinaryResourceAsBase64 = function(url) {
  var data = LoadBinaryResource(url);
   
  if (data.indexOf("BinaryArrayToAscCSV") !== -1)
    return Base64.encodeBinaryArrayAsString(data);
  else
    return Base64.encodeBinary(data); 
}


6 comments:

Anonymous said...
Thank you for this wonderful javascript. Unfortunately, it seems firefox has a max limit on the size of the ResponseText. I was trying to load a large jpeg image (2.47MB) using this technique (by base64 encoding it and then use the img src="data:image/jpeg;base64,XXXX" trick, but the responseText tops at 8192 bytes.
timrice said...
You can use the JScript "VBArray" object to get at these bytes in IE (without using VBScript):

var data = new VBArray(xhr.responseBody).toArray();


http://msdn.microsoft.com/en-us/library/y39d47w8(v=VS.85).aspx
 

评论

此博客中的热门博文

XML, XSL, HTML

Input in element.eleme.io

Data URI是由RFC 2397 ACE