Canvas toDataURL() 返回空白图像

20

我正在使用glfx.js编辑我的图片,但是当我尝试使用toDataURL()函数获取该图像的数据时,我得到了一个空白图像(与原始图像大小相同)。

奇怪的是,在Chrome中脚本运行完美。

需要提及的是,该图片是在使用onload事件加载到canvas中的:

           img.onload = function(){

                try {
                    canvas = fx.canvas();
                } catch (e) {
                    alert(e);
                    return;
                }

                // convert the image to a texture
                texture = canvas.texture(img);

                // draw and update canvas
                canvas.draw(texture).update();

                // replace the image with the canvas
                img.parentNode.insertBefore(canvas, img);
                img.parentNode.removeChild(img);

            }

同时,我的图片路径在同一个域名下;

问题(在Firefox中)是当我点击保存按钮时。Chrome返回预期结果,但Firefox会返回以下内容:

data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA7YAAAIWCAYAAABjkRHCAAAHxklEQVR4nO3BMQEAAADCoPVPbQZ/oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
... [ lots of A s ] ... 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAzwD6aAABkwvPRgAAAABJRU5ErkJggg==

这个结果的原因是什么,我该如何解决?


你正在编辑的图片是否在同一个域名下?这只是为了排除一些显而易见的问题。 - A1rPun
是的,我会编辑我的帖子来提到这一点!在Firefox中存在一个问题(而不是Chrome),对于不在同一域上的图像。 - Ionel Lupu
似乎有一个异步操作正在某个地方进行。如果在调用toDataURL()时图像没有加载完成,画布将会是空白的。 - user1693593
图像已加载到画布上,我可以看到并编辑它。问题在于toDataURL未从画布获取正确的信息。 - Ionel Lupu
1
作为额外的检查事项,如果画布的宽度或高度为0,则根据文档,getDataURL()返回"data:,"。在错误地将画布大小设置为0后,我遇到了这个问题。 - Patrick
1个回答

34

很可能在您绘制到画布和调用toDataURL之间存在一些异步事件。默认情况下,每次合成后画布会被清除。通过使用preserveDrawingBuffer: true创建WebGL上下文来防止画布被清除。

var gl = canvas.getContext("webgl", {preserveDrawingBuffer: true});

或者确保在退出呈现事件之前调用toDataURL。例如,如果你这样做:

function render() {
  drawScene(); 
  requestAnimationFrame(render);
}
render();

还有其他地方要做这件事

someElement.addEventListener('click', function() {
  var data = someCanvas.toDataURL();
}, false);

这两个事件,即animation frameclick不同步,canvas可能会在它们之间被清除。注意:由于canvas是双缓冲的,因此它看起来没有被清除,但影响该缓冲区的其他命令如toDataURL等却已经被清除。

解决方法要么使用preserveDrawingBuffer,要么在相同的事件中调用toDataURL。例如:

var captureFrame = false;

function render() {
  drawScene(); 

  if (captureFrame) {
    captureFrame = false;
    var data = someCanvas.toDataURL();
    ...
  }

  requestAnimationFrame(render);
}
render();

someElement.addEventListener('click', function() {
  captureFrame = true;
}, false);

什么是 preserveDrawingBuffer: false 的意义,它是默认设置吗?不必保留绘图缓冲区可以显著提高性能,特别是在移动设备上。另一种看待这个问题的方式是浏览器需要两份画布。你正在绘制的一个和正在显示的一个。它有两种处理这两个缓冲区的方法。(A) 双缓冲。让你在一个上面绘制,在另一个上面显示,在完成渲染后交换缓冲区,这可以通过退出任何发出绘制命令的事件来推断 (B)复制你正在绘制的缓冲区的内容到正在显示的缓冲区中。交换比复制快得多。因此,默认情况下使用交换。实际发生的情况取决于浏览器。唯一的要求是如果preserveDrawingBufferfalse,则组合后必须清除绘图缓冲区(这是另一个异步事件,因此是不可预测的)。如果preserveDrawingBuffertrue,那么它必须进行复制,以便保存绘图缓冲区的内容。

请注意,一旦画布具有上下文,它将始终具有相同的上下文。换句话说,假设你更改了初始化WebGL上下文的代码,但仍然想设置preserveDrawingBuffer: true

有至少两种方法。

首先找到画布,在其上获取上下文

由于稍后的代码最终将获得相同的上下文。

<script>
document.querySelector('#somecanvasid').getContext(
    'webgl', {preserveDrawingBuffer: true});
</script>
<script src="script/that/will/use/somecanvasid.js"></script>

因为您已经为该画布创建了上下文,所以无论在之后添加了什么脚本,都将获得相同的上下文。

扩展getContext

<script>
HTMLCanvasElement.prototype.getContext = function(origFn) {
  return function(type, attributes) {
    if (type === 'webgl') {
      attributes = Object.assign({}, attributes, {
        preserveDrawingBuffer: true,
      });
    }
    return origFn.call(this, type, attributes);
  };
}(HTMLCanvasElement.prototype.getContext);
</script>
<script src="script/that/will/use/webgl.js"></script>
在这种情况下,任何在增强了getContext之后创建的webgl上下文都将设置preserveDrawingBuffer为true。

1
因为我正在使用glfx.js库,所以我没有直接访问画布和渲染方法。我调用了toDataURL函数事件,并设置了5秒的超时时间,但它没有起作用。我不认为这里有任何异步操作...难道还有其他问题吗? - Ionel Lupu
使用5秒的超时调用toDataURL是一个异步事件。你正在使用JavaScript。你有glfx.js的源代码。改变它。 - gman
我通过使用glfx函数(update())更新画布元素成功解决了这个问题。但是我不知道为什么这个问题只出现在Firefox中而不是Chrome中。谢谢! - Ionel Lupu
1
这只是因为规范在这里存在歧义。WebGL规范实际上是这样说的:如果向WebGL上下文发出绘制命令,则在下一次将网页与页面合成时,画布的绘制缓冲区将被清除。合成发生的时间是异步的。换句话说,在您绘制某些内容和合成发生以及缓冲区被清除之间,您不知道会发生什么事件以及何时会发生。这意味着在每个浏览器中都是不同的。在Chrome的情况下,您会在清除之前获得画布,在FF中则是在清除之后。 - gman
这就解释了。谢谢! - Ionel Lupu
在 WebGL 版本中,我在创建画布时执行以下操作(我还使用模板缓冲区):gl = canvas.getContext("webgl", {stencil : true, preserveDrawingBuffer : true}); - wcochran

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