10

The Google API I'm using is transmitting images only as binary data.

I have absolutly no idea how to put this into a data URI to display it, thanks for any help!

The call I'm talking about is this API call.

As you can see, it says:

The server returns bytes of the photo.

For the call (it's an extension), I use the chrome_ex_oauth methods. Maybe I need to add something into the header to get real binary data, not string as it comes in right now...

What I need to do is to convert the resulting binary into data URI so I can display it.


Ok, I get this out of the XHR request

enter image description here

Now, I dont know binary stuff much. This is somehow encoded binary data i assume? I tried to put this into btoa and other base64 encoders, everything throws an error. I tried to overrideMimeType with different things and the "response" changed in some strange ways, but nothing accepts the data.

So now I have this code:

var nxhr = new XMLHttpRequest();
nxhr.onreadystatechange = function (data) {
    if (nxhr.readyState == 4) {
        console.log(nxhr);
    }
};
nxhr.open(method, url, true);
nxhr.setRequestHeader('GData-Version', '3.0');
nxhr.setRequestHeader('Authorization', oauth.getAuthorizationHeader(url, method, params));
nxhr.send('Data to send');

Anybody else has any idea how to get this for me not understandable response into a data uri???

Thanks for any help

Luke
  • 8,235
  • 3
  • 22
  • 36
  • binary... thousands [of] ugly characters... you do know binary only has two values, correct? – Joseph Marikle Dec 06 '11 at 02:43
  • 3
    FileReader? That sounds like [tag:Java], not [tag:JavaScript]. How about showing an [SSCCE](http://sscce.org)? Otherwise, I've really got no idea what you're asking about. – Matt Ball Dec 06 '11 at 02:43
  • @Joseph, maybe he just really hates the look of ones and zeroes. – Kevin Ennis Dec 06 '11 at 02:47
  • 1
    html5 HAS FileReader ;-) Yeah Joseph, I know :) But put binary data into string, you will see more then two values, right? – Luke Dec 06 '11 at 13:44
  • When you put binary in a string, it only displays the characters it takes from a series of 1-byte character values. If you store a 4-byte integer somewhere and try to read it through an array of 4 * 1 byte, you will get 4 different characters. String representation is irrelevant, the bit sequence is. –  Dec 18 '11 at 22:50
  • It throws an error? What error? Anyway, maybe [just try reading it as binary](http://nagoon97.wordpress.com/2008/04/06/reading-binary-files-using-ajax/). – Ry- Dec 20 '11 at 17:29

5 Answers5

17

After conducting some tests, here is my answer:

To simply display the image using the <img> tag, you must first encode the result binary with Base64. You can do this in two different ways:

  1. Using Javascript: Use a Base64 encoder function, such as this one. After you encode the result binary data, you can display the image using the <img> tag like so: <img src="data:image/*;base64,[BASE64 ENCODED BINARY]" />. You must replace [BASE64 ENCODED BINARY] with the actual encoded binary of the image. I'm assuming you already know how to change HTML element attributes through Javascript, it's fairly easy to put the encoded binary into the src attribute of the <img> tag.

  2. Using PHP (my personal preference): Once you submit a GET request to the API, it will return you the binary. Simply use the PHP base64_encode() function.

    <img src="data:image/*;base64,<?php echo base64_encode($result); ?>" />

Where, the $result variable is what you get from the API call. You can use the PHP cURL library.

I hope this helps.

  • Thanks, although I tried already one Base64 encoder (which told me an error "only can handle ascii codes") I am going to try yours. As I said, its an extension (for chrome) where I can only access via JS. Further more I have to rely on this chrome_ex_oauth which makes the Ajax calles for me. While Im going to try your solution, I would like to ask you to look at the oauth, maybe there is something, that makes the returned data unusable for my need. http://code.google.com/chrome/extensions/tut_oauth.html – Luke Dec 20 '11 at 10:44
  • Did you add the callback function for image processing? Under "Send signed API requests," the example uses `GET` with a callback function. This function's output argument "`resp`" is the response data. In your case, this is the binary data. Since you're only developing for Chrome, use `btoa()` function to convert this binary to Base64 and add it to the end of `data:image/*;base64,[BASE64 BINARY]`, use that as the URI for the image. –  Dec 20 '11 at 14:11
  • Im sorry to tell, that this doesnt work. Neither btoa nor other base64 encoders can handle what comes in to me. I tried overrideMimeType on the XHR object with different char sets, I tried almost everything. – Luke Dec 20 '11 at 16:05
  • OK, let's dig deeper into the code. Can you give me a snippet of your code where you do the `GET` request to pick up a photo? If you don't want to display it here, you can send it to my e-mail, in my profile. –  Dec 20 '11 at 16:11
  • From what I understand, you're not using OAuth `sendSignedRequest` method. The XMLHttpRequest response that you get is indeed what I expected. Let's say you have ``. Change the image source with `document.getElementById("imageTag").src = "data:image/*;base64," + btoa(nxhr.response);`. –  Dec 20 '11 at 16:21
  • Yeah, i was using the xmlhttprequest because there i can set headers etc. it works the same like in sendsignedrequest. The problem is, that btoa(nxhr.response) throws an error... – Luke Dec 20 '11 at 16:36
  • btoa(nxhr.response) Error: INVALID_CHARACTER_ERR: DOM Exception 5 – Luke Dec 20 '11 at 16:37
  • @Luke This is why I don't like Javascript! :-P Please have a look at this question and answer: http://stackoverflow.com/questions/8022425/getting-blob-data-from-xhr-request. It seems like at the end, you're both doing the same thing :) Let me know how this goes. –  Dec 20 '11 at 16:57
  • Hey Ozbekov, I found a solution, look at an answer of mine. Thanks anyways a lot for your help! +1!!! – Luke Dec 20 '11 at 17:22
  • Great, no problem ;) I could use the bounty if it's ok :) –  Dec 20 '11 at 17:52
5

Ok I found the solution...

First of all, the request must override the returend Type into x-user-defined

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

After that the data is untouched by the browser.

Use the following Base64 encoder

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;
            }
        };

There is the magic stuff posted by mozilla which didnt let me encode the stuff correctly

bytebuffer[jnx] = input.charCodeAt(inx++) & 0xff

The final code would look then like this...

oauth.authorize(function () {
    var method = "GET", params = {}, url = photo.href;

    var nxhr = new XMLHttpRequest();
    nxhr.onreadystatechange = function (data) {
        if (nxhr.readyState == 4) {
            console.log("<img src='data:image/*;base64," + Base64.encodeBinary(nxhr.response) + "' />");
        }
    };
    nxhr.open(method, url, true);
    nxhr.setRequestHeader('GData-Version', '3.0');
    nxhr.setRequestHeader('Authorization', oauth.getAuthorizationHeader(url, method, params));
    nxhr.overrideMimeType('text\/plain; charset=x-user-defined'); 
});

P.S. If you put the "data:image/*" into the browser window directly, it will download the file and would not be able to open it. But if you put it directly into an img src it works fine!

apaderno
  • 28,547
  • 16
  • 75
  • 90
Luke
  • 8,235
  • 3
  • 22
  • 36
2

All the other solutions are obsolete. No Base64 is needed. Check out my answer on Getting BLOB data from XHR request.

Community
  • 1
  • 1
Janus Troelsen
  • 20,267
  • 14
  • 135
  • 196
1

I know this is a very old question, but since this is in reference to images and binary data over XHR, the secret sauce for me is in two important steps:

Step 1: Ensure you use a custom user-defined character set to prevent the browser from altering the content. e.g.

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

Step 2: Use String.fromCharCode(response.charCodeAt(...) & 0xff) to get past the issue with btoa() not being able to handle binary strings. This was inspired by the gist @ https://gist.github.com/graylikeme/867848 (and it is currently black magic to me right now).

End Eesult: Here's a simplified version of what that comes out to. Note that typically you'd also be doing this because you need to send Authorization headers or something too, but that's not included in this example.

<div id="output-el"></div>
<script>
    var xhr = new XMLHttpRequest();
    xhr.addEventListener('readystatechange', function() {
        var outputEl = document.getElementById('xhr-output');

        // Done?
        if (this.readyState === 4) {

            // Make sure this is an image.
            var contentType = this.getResponseHeader('content-type');
            if (contentType !== null && contentType.indexOf('image/') === 0) {
                // Prepare binary response so we can base64 encode it (and btoa() function doesn't freak out).
                // Thanks to: https://gist.github.com/graylikeme/867848
                var response = this.responseText;
                var binary = '';
                for(i=0; i < response.length; i++) {
                    binary += String.fromCharCode(response.charCodeAt(i) & 0xff);
                }

                // Drop into data URI.
                var encoded = btoa(binary);
                outputEl.innerHTML = `<img alt="image preview" src="data:${contentType};base64,${encoded}">`;

            } else {
                console.log('Got a non-image response:', this.responseText);
            }
        }
    });

    // Make sure browser doesn't alter the response before we handle it.
    xhr.overrideMimeType('text\/plain; charset=x-user-defined');
    xhr.open('GET', 'https://example.com/path/to/some/image.jpg');
    xhr.send();
</script>
patricknelson
  • 977
  • 10
  • 16
1

If you're using a data: URI, I take it you don't care about older browsers. In that case, use btoa() as suggested in How can you encode a string to Base64 in JavaScript?, and fall back on the alternative mentioned in the second answer. Then, the data: URI is simple:

data:image/*;base64,<the btoa output>
Community
  • 1
  • 1
Ry-
  • 218,210
  • 55
  • 464
  • 476
  • AFAIK, Internet Explorer supports `img data` since IE7. IE7 should be pretty much old enough. Also, btoa() functions are part of Gecko DOM and don't work on IE. –  Dec 19 '11 at 00:14
  • @Ozbekov: It's not just Gecko. Safari and Chrome support it too. Edited to include what I meant to though. – Ry- Dec 19 '11 at 00:15
  • But IE doesn't, which is exactly why I provided an external function for encoding. –  Dec 19 '11 at 00:17
  • @Ozbekov: And why I edited my answer to mention the second answer which includes an alternative function. – Ry- Dec 19 '11 at 00:17
  • 2
    Hey guys, dont worry, just using chrome,nothing else, it will be a chrome extension. Ha, isnt that great, to not worry about IE? :) – Luke Dec 20 '11 at 11:17
  • The question asker is looking for a method for converting **binary** data (image content directly from an XHR request) to base64 encoded. The issue with `btoa()` is that for some reason it cannot handle UTF-8 character sets. In fact, I'm here because, unintuitively, `btoa()` won't work since it triggers the error: "Failed to execute 'btoa' on 'Window': The string to be encoded contains characters outside of the Latin1 range." – patricknelson Mar 26 '21 at 20:02
  • @chunk_split: UTF-8 doesn’t come into play here (the image is not UTF-8, and neither does `btoa` expect UTF-8). To use `btoa`, you need a string that comes from encoding the image’s bytes as Latin-1 (e.g. `String.fromCharCode(...bytes)` from an array of bytes instead of XHR text). Or at least that was the 2011 way. Nowadays for base64 encoding in general I would avoid going through binary strings and use a package that operates on a `Uint8Array` (which is inexplicably not built into browsers like `btoa` is). – Ry- Mar 27 '21 at 06:49
  • @Ry-♦ As it turns out in my case I had to include `xhr.overrideMimeType()` to prevent the browser from altering the response so that `btoa()` could properly encode an image such that it could be interpreted as a data URI. See my answer below for a bit more context. But you're right, I only assumed that was the culprit, when in reality it had to *something* to do with the MIME type handling in the browser. Still not certain what is happening, however. – patricknelson Apr 02 '21 at 21:21