使用JavaScript和WebSockets显示来自Blob的图像

33

我目前正在开发一个WebSocket应用程序,该应用程序会显示由C++服务器发送的图像。我看到了一些相关主题,但是在Firefox浏览器中似乎无法解决以下错误:

图像损坏或截断:data:image/png;base64,[一些数据]

这是我用来显示blob的JavaScript代码:

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);
}
我使用在这个主题上找到的红点图片:https://dev59.com/xm855IYBdhLWcg3wMRVW#4478878,而 Base64 类来自于这里:https://dev59.com/_HVC5IYBdhLWcg3wlyIo#246813 但是我得到的 base64 结果不匹配,Firefox 提示图片损坏。
虽然我没有太多信息,但我不知道该去哪里寻找 :/ 任何帮助都将不胜感激!!

也许你可以尝试在其他地方解码已编码的图像,以确保你的编码/解码方法是正确的。 - Boris Guéry
尝试将Base64.encode(string)的结果与btoa(string)进行比较。大多数base64库对于高值字节的操作与btoa有所不同;也许这就是你的问题所在? - apsillers
我已经尝试过btoa(),但它确实给出了一个不同的结果,仍然无法正常工作。 - guitio2002
我刚刚发现这个链接:http://stackoverflow.com/a/10469264/1464608,指出WebSockets不能发送图像或任何二进制数据以外的东西。 所以我用C ++将我的二进制数据转换为base64,而不是使用这个:http://www.adp-gmbh.ch/cpp/common/base64.html这样一来,我就可以在Firefox中正确显示我的图像了。 但是,图像的大小增加了约30%!真的无法直接使用WebSockets发送PNG图像吗?顺便问一下,图像不都是二进制数据吗?(如果这是一个愚蠢的问题,请原谅:o)) - guitio2002
6个回答

39

我认为最干净的解决方案是将base64编码器直接作用于Uint8Array而非字符串。

重要提示:你需要将WebSocket的binaryType设置为"arraybuffer"。

onmessage方法应该像这样:

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

转换后的编码器应该看起来像这样(基于https://dev59.com/_HVC5IYBdhLWcg3wlyIo#246813):

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

3
谢谢。这确实是一个好的解决方案。对于我来说,我需要添加Base64编码。我的返回类型看起来像:return "data:image/jpg;base64,"+output; - abanmitra

21

您可以简单写:

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

对我没用。但是,当我删除了 var bytes...var blob... 这两行代码,并将 reader.readAsDataURL(blob); 改为 reader.readAsDataURL(arrayBuffer); 时,它就起作用了 - 而且更简单。 - Headcrab
@Headcrab 你用哪个浏览器? - Nikita Koksharov
Firefox 48.0.2和Internet Explorer 11在Windows 10上可以使用。在同一操作系统的Edge浏览器中,无论是您的版本还是我的版本都无法正常工作。 - Headcrab
与 Chrome 相同。不知道是否有关系,但是 HTML 文件并没有由我的测试服务器提供,我直接从同一台机器的硬盘上打开它,例如 file:///C:/blahblah/client.html,然后它与用 Python 编写的测试服务器建立 WebSocket 连接,后者以二进制模式读取 PNG 文件并按原样提供服务。 - Headcrab

14

谢谢,它运行得很好!!

因此,我想分享我的最终JavaScript代码:

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

不太明白为什么使用 blob 版本会有那么多麻烦,但这个方法解决了问题!


1
问题在于将二进制数据转换为(字符)字符串,这取决于所使用的字符编码而产生不同的结果。它可能适用于ISO-8859-1,因为那里所有的代码点都映射到相应的Unicode代码点。对于UTF-8,可能会出现问题,因为输入中的两个或多个字节可能被解码为单个字符,并且某些字节值可能是非法的UTF-8代码点。 - Stefan Haustein

5
另一个选择
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;

};

4

感谢其他答案的帮助,我通过websocket接收到了一个jpeg图像并在新窗口中显示:

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

2
不要忘记释放URL,以避免浪费内存,如果您正在构建单页应用程序:revokeObjectURL - steampowered

2

使用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)即使不指定MIME类型,它也可以工作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接