Display image from blob using javascript and websockets
Asked Answered
M

6

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://mcmap.net/q/42153/-show-image-from-blob-in-javascript And the Base64 class is from here: https://mcmap.net/q/42154/-how-can-you-encode-a-string-to-base64-in-javascript

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!!

Mcdermott answered 18/6, 2012 at 19:31 Comment(4)
Maybe you can try do decode your encoded image elsewhere to be sure that your encoding/decoding method is correct.Havildar
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?Howling
I've already try btoa() and it is indeed giving a different outcome that is still not working tho.Mcdermott
I've just found that link: https://mcmap.net/q/42155/-websockets-sending-image-to-clients-connected 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: 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) )Mcdermott
P
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://mcmap.net/q/42154/-how-can-you-encode-a-string-to-base64-in-javascript):

// 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;
}
Pettis answered 18/6, 2012 at 23:12 Comment(1)
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;Nonessential
E
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);
};
Erdda answered 21/11, 2014 at 12:58 Comment(4)
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.Dzoba
@Dzoba Which browser do you use?Erdda
Firefox 48.0.2 and Internet Explorer 11 on Windows 10. In Edge browser on the same OS neither yours nor my version work.Dzoba
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.Dzoba
M
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!

Mcdermott answered 19/6, 2012 at 14:6 Comment(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.Pettis
H
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;

};
Hilda answered 29/10, 2016 at 16:45 Comment(0)
R
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');
};
Repose answered 10/2, 2017 at 9:34 Comment(1)
Don't forget to release the URL to avoid wasting memory if you are building a single page app: revokeObjectURLMoniz
S
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.

Springbok answered 1/11, 2020 at 18:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.