【问题标题】:Javascript: Render PNG stored as Uint8Array onto Canvas element without Data URIJavascript:将存储为 Uint8Array 的 PNG 渲染到没有数据 URI 的 Canvas 元素上
【发布时间】:2012-12-06 17:10:19
【问题描述】:

我正在尝试渲染存储在 javascript Uint8Array 中的 PNG 图像。我最初尝试的代码如下:

var imgByteStr = String.fromCharCode.apply(null, this.imgBytes);

var pageImg = new Image();
pageImg.onload = function(e) {

    // Draw onto the canvas
    canvasContext.drawImage(pageImg, 0, 0);

};

// Attempt to generate the Data URI from the binary
// 3rd-party library adds toBase64() as a string function
pageImg.src="data:image/png;base64,"+encodeURIComponent(imgByteStr.toBase64());

但是,我发现由于某种原因,onload 函数从未运行(我在 chrome 中设置了断点,但它根本没有命中)。我发现如果我将 src 设置为 URL 而不是 Data URI,它可以正常工作。但是,由于一些限制,我不能直接引用图像的 URL,即必须从 UInt8Array 加载。

此外,更复杂的是,这些图像的大小可能是兆字节。根据我的阅读,尝试将数据 URI 用于非常大的图像存在兼容性问题。因此,我非常犹豫要不要使用它们。

因此,问题是 如何在不使用数据 URI 或直接引用图像 URL 的情况下将此字节数组作为 PNG 呈现到画布上下文中?


我也尝试过使用类似下面的方法来直接使用putImageData 函数来操作图像数据。请记住,我不是 100% 了解此功能的工作原理

var imgdata = canvasContext.createImageData(this.baseHeight, this.baseWidth);
var imgdatalen = imgdata.data.length;
for(var i=0; i<imgdatalen; i++) {
    imgdata.data[i] = _this.imgBytes[i];
}
canvasContext.putImageData(imgdata, 0, 0);

它来自一个博客,我已经关闭了它的标签,因此对没有给予适当信任的灵感表示歉意。


另外,当我慢慢地敲打这件事时,我在 Chrome SECURITY_ERR: DOM Exception 18 中遇到了一个错误。事实证明,一旦使用 drawImage 将图像加载到画布中,如果没有一些额外的解决方法,就无法检索它。关于这个话题的Chromium Blog post 特别有用

【问题讨论】:

  • 你查看putImageData()了吗?
  • 只是为了好奇:图像数据是如何进入数组的?
  • @David:我已经研究了一下,但我的理解是这是针对原始图像数据,而不是 PNG 编码的图像数据。我试试看
  • @raghaw:它是通过异步 HTTP GET 请求下载的。之所以不能在这里直接引用 URL,是因为下一步(一旦我得到这个工作)是加密 GET 请求的结果,然后在 JS 中解密。因此,从长远来看,图像 URL 根本不会返回图像,而是一个加密的二进制文件。这是一个很长的故事,但我一直坚持;)
  • 我知道这可能超出您的控制范围,但重新实现浏览器对加密的现有支持似乎......令人沮丧。你不能只使用HTTPS吗? FWIW 我在浏览器中对图像数据做了一些工作,但我不确定如何将编码的 PNG 字节转换为 32 位 ARGB 数据。也许您可以使用 Blob,然后将其分配为 Image 的 src,然后将图像绘制到画布上。这仅适用于某些浏览器。这对你来说是个问题吗?

标签: javascript html5-canvas


【解决方案1】:

完整工作示例:(另存为 .html 文件并打开)

1:创建 Uint8Array。

2:用调试模式填充它。

3:将其粘贴到页面上的画布元素。

输出应如下所示:

<!DOCTYPE HTML >
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title> U8A_TO_CANVAS </title>
    <meta name   ="author" 
          content="John Mark Isaac Madison">
    <!-- EMAIL: J4M4I5M7@HOTMAIL.com           -->
</head>
<body>
<canvas id="CANVAS_ID" 
         width ="512" 
         height="512">
</canvas>
<script>

var com     =  4 ; //:4 components( RGBA )
var wid     = 512;
var hig     = 512;
var tot_com = wid*hig*com;//:total#components
var u8a     = new Uint8Array( tot_com );

DrawDebugPattern  ( u8a , wid, hig              );
Uint8ArrayToCanvas( u8a , wid, hig, "CANVAS_ID" );

function Uint8ArrayToCanvas( 
    u8a, //:uint8Array
    wid, //:width__of_u8a_data_in_pixels
    hig, //:height_of_u8a_data_in_pixels
    nam, //:name_id_of_canvas_on_dom
){

    //:Get Canvas:
    var can = document.getElementById( nam );
    if(!can){ throw "[FailedToGetCanvas]"; }

    //:Get Canvas's 2D Context:
    var ctx = can.getContext("2d");
    if(!ctx){ throw "[FailedToGetContext]"; }

    //:Use U8A to create image data object:    
    var UAC = new Uint8ClampedArray( u8a,wid,hig);
    var DAT = new ImageData(UAC, wid, hig);

    //:Paste Data Into Canvas:     
    var ORG_X = 0;                         
    var ORG_Y = 0;                         
    ctx.putImageData( DAT, ORG_X, ORG_Y );  
}

function DrawDebugPattern(u8a,wid,hig){

    var com     = 4      ; //:RGBA==4components.
    var tot_pix = wid*hig;//:total#pixels

    //:Calculate point in center of canvas:
    var cen_x = wid/2;
    var cen_y = hig/2;

    //:Define a circle's radius:
    var rad_c = Math.min(wid,hig) / 2;

    //:Make a pattern on array:
    var d   = 0; //:d_is_for_distance
    var ci  = 0; //:Component_Index
    var pi  = 0; //:Pixel_Index
    var px  = 0; //:Pixel_X_Coord
    var py  = 0; //:Pixel_Y_Coord
    for( pi = 0; pi < tot_pix; pi++ ){

        //:Calculate index of first component
        //:of current pixel:
        ci = pi * com;

        //:Index_To_XY_Formula:
        px =  pi    % wid ;
        py = (pi-px)/ wid ;

        //:Decide if pixel is inside circle:
        var dx = (cen_x-px); //:delta_x
        var dy = (cen_y-py); //:delta_y
        d=Math.sqrt( (dx*dx)+(dy*dy) );
        if( d < rad_c ){
            //:INSIDE_CIRCLE:
            u8a[ ci + 0 ] = 0  ; //:Red
            u8a[ ci + 1 ] = 255; //:Green
            u8a[ ci + 2 ] = 0  ; //:Blue
            u8a[ ci + 3 ] = 255; //:Alpha
        }else{
            //:OUTSIDE_CIRCLE:
            u8a[ ci + 0 ] = 0  ; //:Red
            u8a[ ci + 1 ] = 0  ; //:Green
            u8a[ ci + 2 ] = 64 ; //:Blue
            u8a[ ci + 3 ] = 255; //:Alpha
        }

    }
}
</script>
</body>
<!-- In IE: Script cannot be outside of body.  -->
</html>

【讨论】:

    【解决方案2】:

    如果你已经有一个 UInt8Array,你应该考虑使用BlobcreateObjectURLcreateObjectURL 分配了一个特殊的 URL,允许浏览器访问内部创建的二进制数据 blob,就好像它是外部加载的文件一样。

    Where it is supportedcreateObjectURL 是大数据 URI 的绝佳替代品。您可能仍需要在 Opera、IE

    var myArray; //= your data in a UInt8Array
    var blob = new Blob([myArray], {'type': 'image/png'});
    var url = URL.createObjectURL(blob); //possibly `webkitURL` or another vendor prefix for old browsers.
    

    【讨论】:

    • 很好的答案,值得注意的是,在我将 UInt8Array 包装在一个数组中之前,我遇到了这个问题,就像这段代码说的那样。有时您只需要按照说明进行操作 :)。 var blob = new Blob([myArray], {'type': 'image/png'});
    【解决方案3】:

    如果您在内存中有 PNG 数据(我认为这就是您所描述的),那么您可以从中创建图像。在 HTML 中它看起来像:

     <img src="data:image/png;base64,KSjs9JdldhAlisflAkshf==" />
    

    在 JavaScript 中你也可以这样做:

    var image = document.createElement('img');
        image.src = 'data:image/png;base64,' + base64Data;
    

    请注意,如果您没有 PNG 数据,则可以更改 MIME 类型。

    然后您可以使用context.drawImage(image, 0, 0) 或类似方法将image 绘制到您的画布上。

    所以剩下的部分就是将Uint8Array 的内容编码为 Base 64 数据。

    var array = new Uint8Array(),
        base64Data = btoa(String.fromCharCode.apply(null, array));
    

    btoa 函数不是标准的,所以某些浏览器可能不支持它。但是似乎most do。否则,您可能会发现 some code I use 很有帮助。

    我自己没有测试过这些!

    【讨论】:

    • btoa 绝对是我用来使用本机函数进行编码的第 3 方库的绝佳替代品。我对数据 URI(这是您推荐的方法)最大的担忧是大小,特别是在为其设计的移动浏览器上,这是我在查看 this question 后遇到的问题。
    • 在尝试通过数据 URL 显示图像之前,我在 Chrome 中遇到了限制。如果它大于 2MB,Chrome 会在标签的进程中出错。其他浏览器有更高的限制。我使用了在这个 Chromium 错误报告中找到的解决方法:code.google.com/p/chromium/issues/detail?id=69227
    • 最后,我选择了使用数据 URI,因为似乎没有其他解决方案适合需要。我只需要确保图像本身足够小。
    • 将 Uint8Array 转换为 base64 数据的建议解决方案将引发大图像的调用堆栈大小错误。见stackoverflow.com/a/38437518/3826852(总之,用TextEncoder代替String.fromCharCode.apply)
    猜你喜欢
    • 2017-03-06
    • 2012-09-21
    • 2016-05-17
    • 2014-07-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-25
    相关资源
    最近更新 更多