【发布时间】:2016-11-22 14:48:41
【问题描述】:
我有一个 23552 像素 x 8192 像素的大图像,用于将许多 1024 像素 x 1024 像素的单个图像组合成一个图像,类似于使用精灵图像组合图标/资产的方式。
我正在使用 Javascript Image 对象加载该图像。
然后,我使用 drawImage 从大图像中复制一个 1024 x 1024 的图块,并将其复制到相同尺寸 1024 x 1024 的屏幕外画布中。
然后,我使用 drawImage 将该屏幕外画布复制到另一个相同大小的屏幕外画布上。通常,在我的实际应用程序中,第二个画布实际上是屏幕上的画布,但为了简化问题的重现,我只是将第二个画布保留在屏幕外。
从第一个画布到第二个画布的 drawImage 导致 Mac 上的 Chrome 和 Firefox 的内存使用量增加了大约 700MB。我一直无法弄清楚为什么它会这么大,但是有一个非常简单的用例会导致它。
几秒钟后的 700MB 使用量被垃圾收集,所以我不认为这是内存泄漏,只是一个巨大的内存使用量。在我的真实案例中,700MB 的使用量让一些人在他们的机器上内存不足,并导致他们得到可怕的“Aw Snap!”在 Chrome 中。
这是Plunker 上以下代码的链接,我使用的图像在Imgur 上。在 Plunker 上你可以拉起 Activity Monitor 或者任何你用来监控内存使用的东西,然后点击 Button 开始运行下面的代码,并且观察进程的内存使用增加了 > 700MB。
function drawcanvas() {
var oImage = new Image();
oImage.onload = function() {
console.log('image loaded');
// tile size in pixels, the image loaded is 23552 x 8192, which has 23 x 8 tiles
var tileSize = 1024;
// create a canvas that is not on DOM
var hiddenCanvas = document.createElement('canvas');
hiddenCanvas.width = tileSize;
hiddenCanvas.height = tileSize;
var hiddenContext = hiddenCanvas.getContext('2d');
// create another canvas that is not on DOM
var masterCanvas = document.createElement('canvas');
masterCanvas.width = tileSize;
masterCanvas.height = tileSize;
var masterContext = masterCanvas.getContext('2d');
// 1) drawing one tile, 1024 x 1024, from the big image into a canvas that is 1024 x 1024
// this causes negligible memory increaase by itself
hiddenContext.drawImage(oImage, 0, 0, tileSize, tileSize, 0, 0, tileSize, tileSize);
// 2) copy the 1024 x 1024 canvas into another canvas that is 1024 x 1024
// this causes about 700MB of memory usage in both Chrome & Firefox on Mac
masterContext.drawImage(hiddenContext.canvas, 0, 0, tileSize, tileSize, 0, 0, tileSize, tileSize);
console.log('processing done');
};
oImage.src = "http://i.imgur.com/VcIOEJF.png";
}
【问题讨论】:
-
我必须假设您只为桌面编码,因为这么大的图像会淹没移动设备。台式机通常具有带有自己的 GPU 内存的 GPU。您的图像太大,您可能正在耗尽 GPU 内存,因此 GPU 必须使用其他内存资源来完成渲染 - 因此您的内存使用量。虽然拥有一个巨大的 spritesheet 可以简化您的编码要求,但它会给您的用户设备带来负担。我建议重新编码不要使用你巨大的精灵表。
-
@markE 你是对的,这只会在桌面上。但是使用 700MB 对我来说似乎有点过分了,这是我的好奇心之一。 23552 x 8192 = 1.92 亿像素。所以要获得 700MB,它必须每个像素存储 4 个字节,我猜每个通道 rgba 是 1 个字节。但是没有意义的是为什么它会在第二次 drawImage 调用中将整个图像读入内存,而我只是在两个画布之间移动 1024 x 1024 像素?此时,原始图像的大小应该无关紧要。
-
是的,图像存储为原始位图,因此您认为图像“成本”为 23552x8192x4 是正确的。如果 GPU 内存已用尽(并且它可能是用那么大的图像),那么为了获取您的 1kX1k 小节,必须重新读取整个 imageData 以拉出该小节。我强烈建议重新编码不要使用你巨大的精灵表。我知道一个巨大的精灵表让你的编码生活更轻松,但这对你的用户来说并不公平。 ;-)
-
23552 像素 x 8192 像素 - 绉纱! - 这将耗尽大部分 PC 内存。我认为您需要一种不同的方法...
-
每个 1k x 1k 瓦片代表一组不同的数据,这些数据将堆叠在一起以将其全部可视化。每一层都可以有选择地隐藏和自定义颜色,1k x 1k 是一个实用的分辨率,可以用典型的桌面分辨率显示它,因此平铺的图像可以使它只需要发生一个网络请求,但我们仍然可以单独访问每一层。恰好有 23 * 8 组数据,这些数据组成了一个很大的初始图像来获取所有数据。
标签: javascript html canvas