【问题标题】:Get pixel data back from programmatically generated image从以编程方式生成的图像中取回像素数据
【发布时间】:2018-02-22 17:05:54
【问题描述】:

我在客户端 JavaScript 代码中生成像素数组并将其转换为 blob。然后我将 blob 的 URL 作为 image.src 传递,并在 image.onload 回调中撤销它。我没有保留对前面步骤生成的数据的任何引用,因此这些数据可能会被 GC 释放。

以这种方式生成的图像很多,而且效果很好。但有时用户可能希望通过单击图像附近的保存按钮来保存生成的图像。我不想再次生成图像,因为生成速度很慢,并且图像已经生成并且在屏幕上可见。所以我想从图像中取回我的像素。我尝试再次创建画布,在其上绘制图像,然后调用 toBlob,但浏览器将此图像视为跨源并抛出异常:“无法在 'HTMLCanvasElement' 上执行 'toBlob':可能无法导出受污染的画布”。我在 canvas.toDataUrl 和 canvasContext.getImageData 中遇到了类似的错误。

这个问题有解决办法吗?

我也尝试创建画布而不是图像,但是当我创建第二个画布时,第一个画布的内容会清除。

已添加 此错误仅发生在 Chrome 和其他 WebKit 浏览器中。 Firefox 和 MS Edge 工作正常。当我注释掉撤销 blob url 的代码行时,此错误消失了 - 我可以在画布上绘制图像并获取其像素数据而不会出现 CORS 问题。但是这样做是没有意义的,因为我已经有没有被删除的 blob。 但是我的页面可能会生成许多图像 - 它取决于其用户并且是无限的。图像的大小也是无限的——生成 4096x4096 的图像可能很有用。所以我想尽可能地减少我的页面的内存消耗。所有这些图像都应该是可下载的。大多数图像的生成使用先前生成的图像,因此要重新生成链中的最后一个图像,我必须重新生成所有图像。

所以我需要一个仅适用于 Chrome 浏览器的解决方法。

添加了 2 个 我试图在 JS Fiddle 中重现这个问题,但不能。但是在本地我的代码不起作用 - 我在本地开发了我的应用程序,但我没有尝试在服务器上运行它。在你的电脑上创建 test.html 文件并在浏览器中打开它(本地,没有服务器):

<html>
<body>
<pre id="log"></pre>
</body>
<script>
var log = document.getElementById("log");
var canvas = document.createElement("canvas");
canvas.width = canvas.height = 256;
var ctx = canvas.getContext("2d");
canvas.toBlob(function(blob) {
    var img = new Image();
    var blobUrl = URL.createObjectURL(blob);
    img.src = blobUrl;
    img.onload = function()
    {
        URL.revokeObjectURL(blobUrl);
        ctx.drawImage(img, 0, 0);
        try { canvas.toBlob(function(blob) { log.textContent += 'success\n'; }); }
        catch(e) {log.textContent += e.message + '\n';}
    };
});
</script>
</html>

它将打印Failed to execute 'toBlob' on 'HTMLCanvasElement': Tainted canvases may not be exported.。 所以,我认为,我的解决方法是检测该页面是在 WebKit 浏览器和file:/// 协议的组合上运行的。而且我可以将撤销 blob URL 推迟到仅针对此组合的页面卸载。

【问题讨论】:

  • 您在哪个浏览器上收到此错误?听起来像一个错误。是的,如果您撤销 blobURI,那么通过画布是您唯一的选择,因为浏览器会从内存中删除数据(有点像您在提供文件后确实从服务器中删除了文件)。但是您制作了多少张图片,有多少张可以下载?你不能只在页面卸载时撤销它们吗?按需复制每张图片很难吗?
  • 因为你已经撤销了图片的URL,所以无法再访问数据了。我唯一能说的是,当您确定不再需要数据时,您应该只撤销图像 URL,因为这样您就可以将该 blob URL 设置为具有 download 属性的锚点的 href,而无需将其重绘到画布上。
  • @Kaiido 这不是错误。 Blob URI 在字符串中使用不同的协议,因此将被视为跨域图像。
  • @PatrickRoberts 不,blobURI 不应被视为跨域资源。它们来自浏览器的内存,甚至没有 HTTP 请求。
  • @Kaiido according to the definition in the W3C specification,“URL 的来源”和“Blob URL 的来源”的定义有冲突,所以存在一些浏览器目前可能没有的问题将其视为同域请求。但无论如何,由于 URL 已从 blob 存储中撤消,因此使用该 URL 无论如何都会产生网络错误。

标签: javascript image canvas blob


【解决方案1】:

这听起来确实像是 chrome 实现中的一个错误,you may want to report it

似乎发生的事情是,chrome 仅在第一次在画布上绘制图像时检查图像是否已通过干净的原点加载。

由于此时您已经撤销了 blobURI,它无法将此 URI 映射到干净的源(file:// 协议在 chrome 上非常严格)。

但似乎有一个简单的解决方法:

在撤销 blobURI 之前,通过在 a [the reviver]* 画布上绘制此图像,chrome 会将图像标记为干净,并会记住它。
*[edit] 实际上它似乎需要是同一个画布......

因此您可以简单地预先创建导出画布,并在其上绘制每个图像(为了节省内存,您甚至可以在不使用它进行导出时将其设置为 1x1px)之前 你确实撤销了图像的 src:

// somewhere accessible to the export logic
var reviver = document.createElement('canvas').getContext('2d');

// in your canvas to img logic
canvas.toBlob(function(blob){
  var img = new Image();
  img.onload = function(){
    reviver.canvas.width = reviver.canvas.height = 1; // save memory
    reviver.drawImage(this, 0,0); // mark our image as origin clean
    URL.revokeObjectURL(this.src);
  }
  img.src = URL.createObjectURL(blob);
  probablySaveInAnArray(img);
};

// and when you want to save your images to disk
function reviveBlob(img){
  reviver.canvas.width = img.width;
  reviver.canvas.height = img.height;
  reviver.drawImage(img,0,0);
  reviver.canvas.toBlob(...
}

但请注意,此方法将创建一个 Blob,而不是检索以前的 Blob,此时可能已被 GarbageCollector 收集。但不幸的是,这是您唯一的方法,因为您确实撤销了 blobURI...

【讨论】:

  • 谢谢。我认为,这个技巧对我来说是理想的。新的 blob 是我真正需要的,因为我的初始 blob 是以 BMP 文件格式生成的,以避免压缩+解压缩,但用户想要保存压缩图像 - PNG 或 JPG。
  • @gammaker 很高兴它也适合你。如果您要将错误报告给 chrome,请告诉我,否则我会在明天报告。
  • 此错误已在 Chrome 61 中修复 - 在我的应用程序中有效,无需解决方法。因此,当window.location.protocolfile: 时,我更改了代码以有条件地为Chrome &lt; 61 应用此解决方法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-29
相关资源
最近更新 更多