为什么我的画布在转换为图像后变成空白? [英] Why does my canvas go blank after converting to image?

查看:25
本文介绍了为什么我的画布在转换为图像后变成空白?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试转换 此页面上的 canvas 元素 使用以下代码片段转换为 png(例如在 JavaScript 控制台中输入):

I am trying to convert the canvas element on this page to a png using the following snippet (e.g. enter in JavaScript console):

(function convertCanvasToImage(canvas) {
  var image = new Image();
  image.src = canvas.toDataURL("image/png");
  return image;
})($$('canvas')[0]);

不幸的是,我得到的 png 完全是空白的.另请注意,调整页面大小后,原始画布变为空白.

Unfortunately, the png I get is completely blank. Notice also that the original canvas goes blank after resizing the page.

为什么 canvas 变成空白?如何将此 canvas 转换为 png?

Why does the canvas go blank? How can I convert this canvas to a png?

推荐答案

Kevin Reid 的 preserveDrawingBuffer 建议是正确的,但(通常)有更好的选择.tl;dr 是最后的代码.

Kevin Reid's preserveDrawingBuffer suggestion is the correct one, but there is (usually) a better option. The tl;dr is the code at the end.

将渲染网页的最终像素放在一起,并与渲染 WebGL 内容进行协调可能会很昂贵.通常的流程是:

It can be expensive to put together the final pixels of a rendered webpage, and coordinating that with rendering WebGL content even more so. The usual flow is:

  1. JavaScript 向 WebGL 上下文发出绘图命令
  2. JavaScript 返回,将控制权返回给主浏览器事件循环
  3. WebGL 上下文将绘图缓冲区(或其内容)转交给合成器,以便集成到当前在屏幕上呈现的网页中
  4. 页面,带有 WebGL 内容,显示在屏幕上

请注意,这与大多数 OpenGL 应用程序不同.在这些情况下,渲染的内容通常直接显示,而不是与页面上的一堆其他内容合成,其中一些实际上可能位于 WebGL 内容之上并与 WebGL 内容混合.

Note that this is different from most OpenGL applications. In those, rendered content is usually displayed directly, rather than being composited with a bunch of other stuff on a page, some of which may actually be on top of and blended with the WebGL content.

WebGL 规范已更改为在第 3 步之后将绘图缓冲区视为基本为空.您在 devtools 中运行的代码将在第 4 步之后出现,这就是您获得空缓冲区的原因.对规范的这种更改允许在第 3 步之后的消隐基本上是硬件中实际发生的情况(如在许多移动 GPU 中)的平台上大幅提高性能.如果您想解决此问题,有时在第 3 步之后制作 WebGL 内容的副本,则浏览器必须始终在第 3 步之前制作绘图缓冲区的副本,这会导致您的帧率下降在某些平台上急剧下降.

The WebGL spec was changed to treat the drawing buffer as essentially empty after Step 3. The code you're running in devtools is coming after Step 4, which is why you get an empty buffer. This change to the spec allowed big performance improvements on platforms where blanking after Step 3 is basically what actually happens in hardware (like in many mobile GPUs). If you want work around this to sometimes make copies of the WebGL content after step 3, the browser would have to always make a copy of the drawing buffer before step 3, which is going to make your framerate drop precipitously on some platforms.

您可以完全做到这一点,并通过将 preserveDrawingBuffer 设置为 true 来强制浏览器复制并保持图像内容可访问.来自规范:

You can do exactly that and force the browser to make the copy and keep the image content accessible by setting preserveDrawingBuffer to true. From the spec:

这个默认行为可以通过设置 WebGLContextAttributes 对象的preserveDrawingBuffer 属性来改变.如果此标志为真,则绘图缓冲区的内容将被保留,直到作者清除或覆盖它们.如果此标志为 false,则在渲染函数返回后尝试使用此上下文作为源图像执行操作可能会导致未定义的行为.这包括 readPixels 或 toDataURL 调用,或将此上下文用作另一个上下文的 texImage2D 或 drawImage 调用的源图像.

This default behavior can be changed by setting the preserveDrawingBuffer attribute of the WebGLContextAttributes object. If this flag is true, the contents of the drawing buffer shall be preserved until the author either clears or overwrites them. If this flag is false, attempting to perform operations using this context as a source image after the rendering function has returned can lead to undefined behavior. This includes readPixels or toDataURL calls, or using this context as the source image of another context's texImage2D or drawImage call.

在您提供的示例中,代码只是更改上下文创建行:

In the example you provided, the code is just changing the context creation line:

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

请记住,它会在某些浏览器中强制使用较慢的路径,并且性能会受到影响,具体取决于您渲染的内容和方式.在大多数桌面浏览器中你应该没问题,在这些浏览器中实际上不需要制作副本,而且这些确实构成了绝大多数支持 WebGL 的浏览器......但只是现在.

Just keep in mind that it will force that slower path in some browsers and performance will suffer, depending on what and how you are rendering. You should be fine in most desktop browsers, where the copy doesn't actually have to be made, and those do make up the vast majority of WebGL capable browsers...but only for now.

但是,还有另一种选择(规范的下一段中提到的有些令人困惑).

However, there is another option (as somewhat confusingly mentioned in the next paragraph in the spec).

本质上,您在第 2 步之前自己制作副本:在您的所有绘制调用完成之后,但在您将控制权从您的代码返回到浏览器之前.这是当 WebGL 绘图缓冲区仍然完好无损并且可以访问时,您应该可以轻松访问像素.您使用与其他方式相同的 toDataUrlreadPixels 调用,重要的是时机.

Essentially, you make the copy yourself before step 2: after all your draw calls have finished but before you return control to the browser from your code. This is when the WebGL drawing buffer is still in tact and is accessible, and you should have no trouble accessing the pixels then. You use the the same toDataUrl or readPixels calls you would use otherwise, it's just the timing that's important.

在这里,您可以两全其美.您获得了绘图缓冲区的副本,但您不会在每一帧中都为它付费,即使是那些您不需要副本的帧(可能是其中的大部分),就像您对 preserveDrawingBuffer<所做的那样/code> 设置为 true.

Here you get the best of both worlds. You get a copy of the drawing buffer, but you don't pay for it in every frame, even those in which you didn't need a copy (which may be most of them), like you do with preserveDrawingBuffer set to true.

在您提供的示例中,只需将您的代码添加到 drawScene 的底部,您应该会在下方看到画布的副本:

In the example you provided, just add your code to the bottom of drawScene and you should see the copy of the canvas right below:

function drawScene() {
  ...

  var webglImage = (function convertCanvasToImage(canvas) {
    var image = new Image();
    image.src = canvas.toDataURL('image/png');
    return image;
  })(document.querySelectorAll('canvas')[0]);

  window.document.body.appendChild(webglImage);
}

这篇关于为什么我的画布在转换为图像后变成空白?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆