33

I'm currently working on a WebSocket application that is displaying images send by a C++ server. I've seen a couple of topics around there but I can't seem to get rid of this error in Firefox:

Image corrupt or truncated: data:image/png;base64,[some data]

Here's the Javascript code I'm using to display my blob:

socket.onmessage = function(msg) {
    var blob = msg.data;

    var reader = new FileReader();
    reader.onloadend = function() {
        var string = reader.result;
        var buffer = Base64.encode(string);
        var data = "data:image/png;base64,"+buffer;

        var image = document.getElementById('image');
        image.src = data;
    };
    reader.readAsBinaryString(blob);
}

I'm using the image of a red dot that I found on this topic: https://stackoverflow.com/a/4478878/1464608 And the Base64 class is from here: https://stackoverflow.com/a/246813/1464608

But the base64 outcome I get doesn't match and Firefox retrieves me an error of the image being corrupted.

I know this ain't much informations but I don't have a clue where to look :/ Any help is more than welcome!!

Community
  • 1
  • 1
guitio2002
  • 563
  • 2
  • 5
  • 13
  • Maybe you can try do decode your encoded image elsewhere to be sure that your encoding/decoding method is correct. – Boris Guéry Jun 18 '12 at 19:35
  • Try comparing the result of `Base64.encode(string)` to `btoa(string)`. Most base64 libraries operate a bit differently that `btoa` for high-value bytes; perhaps that's your issue? – apsillers Jun 18 '12 at 20:05
  • I've already try btoa() and it is indeed giving a different outcome that is still not working tho. – guitio2002 Jun 18 '12 at 20:06
  • I've just found that link: http://stackoverflow.com/a/10469264/1464608 stating websockets cannot send images or anything else than binary data. So I'm converting my binary data to base64 in C++ instead using this: http://www.adp-gmbh.ch/cpp/common/base64.html This way I have my image correctly displaying in Firefox. However, the image size is like 30% bigger! Is it really impossible to send a PNG image straight away using WebSockets? And aren't images binary data by the way? (sorry if this is a stupid question :o) ) – guitio2002 Jun 18 '12 at 20:13

6 Answers6

39

I think the cleanest solution would be to change the base64 encoder to operate directly on a Uint8Array instead of a string.

Important: You'll need to set the binaryType of the web socket to "arraybuffer" for this.

The onmessage method should look like this:

socket.onmessage = function(msg) {
    var arrayBuffer = msg.data;
    var bytes = new Uint8Array(arrayBuffer);

    var image = document.getElementById('image');
    image.src = 'data:image/png;base64,'+encode(bytes);
};

The converted encoder should then look like this (based on https://stackoverflow.com/a/246813/1464608):

// public method for encoding an Uint8Array to base64
function encode (input) {
    var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
    var output = "";
    var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
    var i = 0;

    while (i < input.length) {
        chr1 = input[i++];
        chr2 = i < input.length ? input[i++] : Number.NaN; // Not sure if the index 
        chr3 = i < input.length ? input[i++] : Number.NaN; // checks are needed here

        enc1 = chr1 >> 2;
        enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
        enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
        enc4 = chr3 & 63;

        if (isNaN(chr2)) {
            enc3 = enc4 = 64;
        } else if (isNaN(chr3)) {
            enc4 = 64;
        }
        output += keyStr.charAt(enc1) + keyStr.charAt(enc2) +
                  keyStr.charAt(enc3) + keyStr.charAt(enc4);
    }
    return output;
}
Nadeen Udantha
  • 60
  • 1
  • 3
  • 9
Stefan Haustein
  • 18,427
  • 3
  • 36
  • 51
  • 3
    Thank you. It is really a good solution. For me I have to add Base64 Encoding. My return type looks like: return "data:image/jpg;base64,"+output; – abanmitra Feb 09 '15 at 14:09
21

You may write it much simpler:

socket.onmessage = function(msg) {
   var arrayBuffer = msg.data;
   var bytes = new Uint8Array(arrayBuffer);
   var blob = new Blob([bytes.buffer]);

   var image = document.getElementById('image');

   var reader = new FileReader();
   reader.onload = function(e) {
       image.src = e.target.result;
   };
   reader.readAsDataURL(blob);
};
tanguy_k
  • 11,307
  • 6
  • 54
  • 58
Nikita Koksharov
  • 10,283
  • 1
  • 62
  • 71
  • Didn't work for me. However, it did work after I removed `var bytes...` and `var blob...` lines and then changed `reader.readAsDataURL(blob);` to `reader.readAsDataURL(arrayBuffer);` - which is even simpler yet. – Headcrab Sep 07 '16 at 04:08
  • @Headcrab Which browser do you use? – Nikita Koksharov Sep 07 '16 at 07:16
  • Firefox 48.0.2 and Internet Explorer 11 on Windows 10. In Edge browser on the same OS neither yours nor my version work. – Headcrab Sep 07 '16 at 08:01
  • Same with Chrome. Don't know if it matters, but the html file isn't served by my test server, I open it directly from the hard drive of the same machine, e. g. like file:///C:/blahblah/client.html, then it establishes websocket connection with a test server written in python, which reads a PNG file in binary mode and serves it as-is. – Headcrab Sep 07 '16 at 08:14
14

Thanks, it's working great!!

So I figure I'd share my final javascript code:

var socket = new WebSocket('ws://'+host+':'+port, protocol);
socket.binaryType = 'arraybuffer';

try {
    socket.onopen = function() {
        document.getElementById('status').style.backgroundColor = '#40ff40';
        document.getElementById('status').textContent = 'Connection opened';
    }

    socket.onmessage = function(msg) {
        var arrayBuffer = msg.data;
        var bytes = new Uint8Array(arrayBuffer);

        var image = document.getElementById('image');
        image.src = 'data:image/png;base64,'+encode(bytes);
    }

    socket.onclose = function(){
        document.getElementById('status').style.backgroundColor = '#ff4040';
        document.getElementById('status').textContent = 'Connection closed';
    }
} catch(exception) {
    alert('Error:'+exception);
}

don't really understand why the blob version is so tricky but this did the trick!

guitio2002
  • 563
  • 2
  • 5
  • 13
  • 1
    The problem is converting binary data to a (character) string, which delivers different results depending on the character encoding used. It may work for ISO-8859-1 because there all code points map to the corresponding unicode code points. For UTF-8, there will probably be problems because two or more bytes in the input may get decoded as a single character, and some byte values may be illegal UTF-8 code points. – Stefan Haustein Jun 19 '12 at 18:57
5

Another alternative

let urlObject;

socket.onmessage = function(msg) {
    const arrayBuffer = msg.data;
    const image = document.getElementById('image');

    if (urlObject) {
        URL.revokeObjectURL(urlObject) // only required if you do that multiple times
    }
    urlObject = URL.createObjectURL(new Blob([arrayBuffer]));

    image.src = urlObject;

};
Walle Cyril
  • 3,087
  • 4
  • 23
  • 55
4

Thanks to the other answers, I managed to receive a jpeg image by websocket and display it in a new window :

socket.binaryType = "arraybuffer";                 
socket.onmessage = function (msg) {
    var bytes = new Uint8Array(msg.data);
    var blob = new Blob([bytes.buffer]);
    window.open(URL.createObjectURL(blob),'Name','resizable=1');
};
Soviut
  • 88,194
  • 49
  • 192
  • 260
J.Jacobs
  • 703
  • 6
  • 17
  • 2
    Don't forget to release the URL to avoid wasting memory if you are building a single page app: [revokeObjectURL](https://developer.mozilla.org/en-US/docs/Web/API/URL/revokeObjectURL) – steampowered Jul 30 '20 at 21:14
2

This is very simple using a Blob:

// Small red dot image
const content = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 5, 0, 0, 0, 5, 8, 6, 0, 0, 0, 141, 111, 38, 229, 0, 0, 0, 28, 73, 68, 65, 84, 8, 215, 99, 248, 255, 255, 63, 195, 127, 6, 32, 5, 195, 32, 18, 132, 208, 49, 241, 130, 88, 205, 4, 0, 14, 245, 53, 203, 209, 142, 14, 31, 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130]);

document.getElementById('my-img').src = URL.createObjectURL(
  new Blob([content.buffer], { type: 'image/png' } /* (1) */)
);
Should display a small red dot: <img id="my-img">

(1) It also works without specifying the MIME type.

tanguy_k
  • 11,307
  • 6
  • 54
  • 58