使用drawImage将大图像复制到画布会导致大量内存使用

时间:2016-07-19 15:44:01

标签: javascript html5 canvas

我有一个23552px到8192px的大图像,用于将多个1024px到1024px的单个图像组合成一个图像,类似于精灵图像用于组合图标/资产的方式。

我正在使用Javascript Image对象加载该图片。

然后我使用drawImage将1024 x 1024个瓷砖中的一个复制到大图像中,然后复制到1024 x 1024相同尺寸的屏幕外画布上。

然后,我使用drawImage将该屏幕外画布复制到相同大小的另一个屏幕外画布上。通常,在我的真实应用程序中,第二个画布实际上是屏幕画布,但为了简化问题的再现,我只是将第二个画布保留在屏幕上。

从第一个画布到第二个画布的drawImage导致Mac上Chrome和Firefox的内存使用量大约增加700MB。我无法弄清楚它为何如此大量,但是有这个非常简单的用例导致它。

几秒钟后700MB的使用情况会被垃圾收集,所以我不认为这是一个内存泄漏,只是一个巨大的内存使用量。在我的真实案例中,700MB的使用情况是让一些人在他们的机器上内存不足并导致他们陷入可怕的“Aw Snap!”。在Chrome中。

以下是Plunker下方代码的链接,我正在使用的图片位于Imgur。在Plunker上,您可以调出活动监视器或用于监视内存使用情况的任何内容,然后单击按钮开始运行下面的代码,并通过>观察进程的内存使用量增加。 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";    
  }

1 个答案:

答案 0 :(得分:0)

23552 by 8192太大

像所有人说的那样。方式太大了。同时也不可能看到比设备显示器更多的像素。因此,不仅RAM使用得很好,大多数使用的RAM甚至都不能作为像素观看,缩小时可以组合成单个像素,放大时可以组合成屏幕。

始终有解决方案

任何浏览器都可以显示任何分辨率图像,例如365 Gigapixels,任何浏览器都可用于创建任何分辨率的图像(如果您准备等待)

我在使用canvas的浏览器和客户端脚本方面有很多经验,但我不是这个特定领域的专家。请参阅最后一段,了解解决问题的第一站应该是什么。我提出的其余部分是概念解决方案,只是众多可能解决方案中的一种。

可能的解决方案

操作你说你想要可视化23 * 8 184瓷砖中包含的数据。

除了顶级机器之外,你不能将Image多个瓷砖存储在任何东西上。但是,您可以根据需要加载它们,并且一旦缓存加载就会很快。

用户只能看到显示器可以显示的像素数,因此用于显示数据的图像不应超过显示分辨率。你有一个宽图像格式(Aspect 2.875:1),所以我假设你想要水平平移。我们可以适应宽幅格式,因为它不会出现问题(显示最大分辨率超过2-3倍的图像应该被认为太大)

由于图像分辨率如此之高,因此可以假设用户可以放大并以一对一的分辨率查看像素。

所以情况将是全高清(全屏)的1080HD显示。

以正确的方面创建屏幕外画布(OSC)以适合1080 * 2.875 3105 * 1080(屏幕外画布的像素预算)。由于我假设的所有缩放都是均匀的,我们可以使用自然图像垂直res(VR = 8192)作为我们的计算参考,即8192.我们将通过虚拟垂直分辨率VVR来进行缩放。对于完全缩放(像素一对一),VVR = 8192,一半是VVR = 4096,查看图像,以便使用所有显示像素(缩放以填充),即VVR = 1080(显示器)解析度)。要查看所有图像(缩放以适合)VVR = Math.min(显示Res Hor / 23552,显示Res垂直/ 8192)* VR = ~0.0815(注意显示屏的顶部和底部将为空,在此案例)

使用VR计算平铺比例TS = VVR / VR。然后将每个图块作为图像加载,并使用尺度TS将该图像渲染到OSC。在(VVR <垂直显示res(1080))下的比例下,您将需要加载所有184个图块以获得完整图像,在其他比例(放大)处将减少图块的数量。只加载适合OSC的内容,始终开始在视图中心加载切片。缩放时使用OSC以新的比例缩回自身,以便在您等待图块重新渲染时为用户提供一些反馈。在缩放或缩小时进行平移时,将OSC重新渲染,这样您就可以保持已加载的图块并仅加载边缘处缺少的图块进行平移,或者首先加载边缘图块然后在缩小时居中。

你可以买得起另一个屏幕外的画布来保存当前OSC的可见部分,以阻止渲染到自身时可能出现的一些反馈瑕疵,但是你应该从规则的可用RAM中减去该画布的RAM(有关RAM预算的详细信息,请参阅此文档。

Notes 1,2

中的更多内容

这是如何呈现大幅面图像数据集的基础知识。

改善此解决方案的一些方法

加载并刷新屏幕外的画布。

在OSC与完整图像相同的完美世界中,缩放将是平滑和实时的。遗憾的是,所提出的解决方案存在一些不必要的瑕疵,当放大显示屏时会变得模糊,当平移和缩小边缘处的像素时,将丢失并需要等待切片填充。这是不可避免的。

预测和预加载图块

您可以通过预测用户将要执行的操作来增加刷新OSC的时间,并在确实发生该操作时预先准备好切片。预加载的磁贴数量取决于可用的RAM。由于您无法知道有多少可用(浏览器不提供该信息),您可以使用经验法则来获取数字。

Thumb预算规则

计算磁贴的RAM预算。 RAM用于以设备显示分辨率* 9存储图像。因此,如果屏幕分辨率为1920 * 1080,则显示RAM为1920 * 1080 * 4 = 8Mb,然后为9(3 * 3),得到72Mb。然后通过瓦片RAM 1024 * 1024 = 1MB对~72个瓦片进行划分和下限,您可以合理地确保设备具有可用性(请注意,具有低分辨率的设备可能是具有预算意识的设备和显示比率可用RAM的大小会更低。我将乘数从9减少到4(2 * 2)

多分辨率Tiles

您可以通过创建许多不同的图块集来增加图块加载性能。基本图块集为1024 * 1024像素(全分辨率),但在缩小时,大多数像素都会丢失。不是加载4个图块来将它们全部绘制为一半,而是在该缩放处预渲染一个新的图块集,以便一个1024×1024图块可以容纳4个原始图块,因此您只需要加载1/4的图块。多少您创建的图块集将由许多因素决定,每个集合的分辨率加倍是最简单的,但如果用户停止在不匹配图块集的缩放处,则需要在更高的res处加载图块。在最大缩小时,图块集可能与该缩放不匹配。因为它很容易玩,服务器/缓存存储很丰富,最好的结果可以通过实验找到(和用户案例研究,类似于文档底部的注释2)

已编码或已解码的图片

存储在高速缓存/存储设备中的图像是图像的编码表示。编码通常包括压缩(jpg / webp的有损和png / gif的无损..)当你创建和加载图像时,像素被解码以创建位图。解码图像比编码图像大得多,存储184个图块,因为解码图像正在推动RAM限制,但您可以将编码图像作为BASE64存储在8位类型阵列中。您还可以在BASE64上执行简单快速的BASE64到二进制流打包(因为BASE64不会使用每3个字节编码的6位),因此键入的数组大小与映像磁盘大小相匹配。您可以通过AJAX或文件加载器

加载编码图像

如果您不需要拼贴的Alpha通道,请使用JPEG。高质量的JPEG&#34;或质量设置50的平均压缩大约为15:1(对于RGB)和20:1对于RAM rgba(加载图像())。在这种质量压缩工件刚开始变得随意观察者可见。在此压缩时,完整的磁贴集将需要~40Mb来将所有磁贴存储为编码位打包的JPEG,以便在RAM预算内完成1920 x 1080的案例解决方案机器res

警告与将编码图像存储在RAM中相关联。浏览器甚至跨浏览器版本的Javascript在每秒指令时都不一致。由于这种方法使用相同数量少的数百万次指令,即使是最小的性能因素也会对整个加载产生重大影响,并将BASE64url呈现给Image.src进行解码。您应该在预期使用的浏览器,浏览器版本,设备和设备CPU类型上彻底测试任何实现此方法的尝试。它应该针对从缓存/本地磁盘加载编码图像进行测试。只有在有明显好处的情况下才使用此方法。由于执行此操作不是一大堆代码,因此您只能选择将此方法用于可提高性能的平台。

所以要结束你应该开始......:)

从哪里开始

现成的解决方案

所有这些都是很多工作,并且有这种类型的图像显示的企业和开源解决方案值得研究,应该是实施自己的解决方案之前的第一站。我只介绍了一些改进应用程序的方法,以避免过多的RAM使用(和浏览器崩溃)有很多涉及和很多时间花在其他人构建和优化这种类型的应用程序上我不是意味着在这个特定领域的专家和购物将是一个好处,即使它只是给你一些提示。

注1

当放大时,如果要查看的数据可能会导致大量垂直平移,则可以更改屏幕外的画布分辨率,如果平移预期为任何方向,则将其设置为正方形,如果平移主要是横向保持宽广。

注2

如果您的应用是基于服务器的记录,则所有用户都将其平移为两个数字。水平(HP)和垂直(VP)平移的像素数。在会话结束时将统计信息发送到服务器,然后您将服务器划分为HP / VP以获得所有用户的平移方向比率。如果此比率接近1,您知道在缩放时创建一个方形屏幕外画布,如果远大于1,您知道用户更可能水平平移以保持宽视角,而小于1则使其更高(不要#39 ; t超过像素预算))

相关问题