我在客户端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,直到只为此组合卸载页面。
答案 0 :(得分:2)
这确实听起来像Chrome实现中的一个错误you may want to report it。
似乎发生的事情是,只有在画布上第一次绘制图像时,chrome才会检查图像是否已通过干净的原点加载。
由于此时您已经撤消了blobURI,因此无法将此URI映射到干净的源(file://
协议对chrome非常严格)。
但似乎有一个简单的解决方法:
通过在撤消blobURI之前在 a [reviver] *画布上绘制此图像,chrome会将图像标记为干净,并将记住它。
* [edit] 实际上它似乎需要是同一个画布...
所以你可以事先创建你的导出画布,并在其上绘制每个图像(为了节省内存,你甚至可以将它设置为1x1px,当你不使用它进行导出时)之前< / strong>你撤销图像的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(...
}
但请注意,此方法将创建一个 new Blob,而不是检索前一个Blob,这可能是GarbageCollector目前收集的。但遗憾的是,自从你撤销blobURI后,这是唯一的方法......