【问题标题】:LodePNG crashes when encoding PNG files with size greater than 15000 * 15000 pixels对大小大于 15000 * 15000 像素的 PNG 文件进行编码时,LodePNG 崩溃
【发布时间】:2014-10-18 20:45:01
【问题描述】:

我一直在使用 LodePNG 的 lodepng_encode24_file 对一些 24 位 RGB 图像文件进行编码,到目前为止效果非常好。但是,我注意到当我为其提供大于 15360*15360 像素大小的数据集(14336*14336 像素图像编码良好)时,它似乎崩溃了。

可以通过简单地替换行来获得 32 位情况(崩溃前的最大大小略低)的这种行为的最小示例

  unsigned width = 512, height = 512;

  unsigned width = 1024*14, height = 1024*14;

LodePNG's example_encode.c file 中,并执行它。

我之前遇到过 C 代码崩溃的问题,因为我将大型数组分配给堆栈内存(其最大大小通常在 2MB 左右)而不是堆内存,所以作为 C 的新用户,我的第一反应是看看是否堆内存大小有上限。

但是,according to this answer,堆内存没有限制,所以肯定有其他问题。

我的第二个猜测是,崩溃是由于 PNG 格式本身支持的最大图像尺寸的固有限制。但是,according to this answer 及其下方的注释,PNG 支持的最大文件大小约为 4,000,000,000 * 4,000,000,000 像素,所以这也不是罪魁祸首。

有没有人猜测可能出了什么问题?其他人在尝试时能够重现此错误吗?

编辑:就 RAM 消耗而言,我有 8GB RAM,减去硬件保留、使用中、修改和待机内存(Windows 资源监视器实用程序使用的术语)我进行计算时有大约 4GB 的可用 RAM。对于 15000*15000 大小的 32 位图像,需要的空间小于 1GB。同样,当我成功编码 14000*14000 24 位图像时,我的可用 RAM 在编码过程的任何时候都不会低于 3GB,所以我认为 RAM 用完不是问题。

【问题讨论】:

  • 当然堆内存是有限制的,事实上有几个。首先是您的系统可以处理的限制,例如 32 位系统最多只能寻址 4GB。然后是系统可用的实际物理和交换空间量,减去操作系统所需的内存以及系统上运行的所有其他进程。最后,有一个限制,当你分配内存时,它必须是一个大的连续块,如果没有这么大的块可用,则分配失败。
  • 根据您生成图像的方式,您可能希望每行对其进行编码 - 这样您只需要在内存中有一行。 PNG 格式对此很简单(撇开隔行扫描的 PNG),而 libpng 允许该模式或写入(和读取),请参见例如png_write_row()。 (我在我的 Java 库 PNGJ 中做同样的事情)。我不知道 LodePNG。
  • 似乎是内存碎片问题,或 LodePNG 实现限制/错误。刚刚尝试使用 libpng 保存 16384x16384 24 位图像,工作正常。
  • @user2802841:你没有使用 LodePNG,是吗?
  • 我强烈认为观察到的行为是 LodePNG 中的错误。我刚刚编译了当前版本,按照 OP 创建了示例程序并运行它。它确实崩溃了。在 Valgrind 下运行它揭示了 LodePNG 代码中的内存问题。

标签: c png


【解决方案1】:

我相信您低估了您的程序使用了多少内存。假设您在 Windows 上,那么您可能只有 2 GB 的内存可供进程使用(请参阅here)。然后为 14336x14336 图像分配一个 882 MB 的大块。然后,LodePNG 代码会执行更多分配,很可能等于或大于此图像大小。

我只手动跟踪了LodePNG code,但它似乎在内存中创建了一个缓冲区并以块的形式写入该缓冲区。它在重新分配时进行了最小调整(在lodepng_chunk_append() 中,仅足以容纳数据加上 12 个字节),这意味着它必须进行大量的重新分配。这可能(或最终将)将内存碎片化到一个非常大的缓冲区不可用的点。

即使堆内存没有碎片,想想当您尝试将realloc() 一个 800MB 缓冲区转换为 801MB 缓冲区时会发生什么。如果堆管理器是“智能”的,它可能会起作用,但一个幼稚的管理器总共需要 1601MB ......如果你的堆大小为 2000MB 并且你已经使用了 822MB,那么这是不可用的。

其中很多(大部分)是推测,但您可以自己进行一些测试来模拟分配和重新分配几个大块内存,看看您是否可以重现内存不足的情况。

附录: 虽然上述情况可能是正确的,但在这种情况下,它实际上并不是导致崩溃的原因。从实际运行和跟踪代码来看,问题是LodePng.c 中的第 5558 行:

size_t size = (w * h * lodepng_get_bpp(&info.color) + 7) / 8;

w * h 返回 24 时,当w * h 大于 178,956,970 时,此计算会导致整数溢出。对于方形图像,这是 13378 x 13378。这会导致分配的输出缓冲区大小不正确,从而导致缓冲区稍后写入时溢出。

快速解决方法是将这一行更改为:

 size_t size = (size_t) ((unsigned long long) w * h * lodepng_get_bpp(&info.color) / 8 + 1);

虽然我不确定这是否适用于所有 BPP 值,但它仍然存在溢出问题,尽管尺寸较大。我建议联系 LodePNG 的作者以实施适当的修复。

【讨论】:

  • 哎呀 - 引入浮动?这比 int 的精度要低得多。更好的做法是更改括号:((lodepng_get_bpp(&info.color) + 7) / 8),尽管这只是部分修复,因为它不允许 MAX_INT * MAX_INT 具有超过 1 字节颜色规范的图像。 (这可能超出了 LodePng 的作者对典型用途的想象。)
  • Nonono float,至少对于使用 long 的 64 位系统来说可以:size_t size = (w * h * (long) lodepng_get_bpp(&info.color) + 7) / 8;
  • @Jongware:您的提议并不是对原始代码的完全替代。
  • 使用浮动确实感觉有点“错误”。转换为 64 位整数然后再转换为 32 位将是理想的。
  • @alk:对。我假设 bpp 可以安全地缩小比例,但 PNG 也允许 lower 位深度。最大“原始”大小可以是 2³¹ * 2³¹ (earlier SO question on the same),位深度高达惊人的 64 位 RGBA。 ...远远超出我系统当前的能力。
猜你喜欢
  • 1970-01-01
  • 2013-02-10
  • 1970-01-01
  • 1970-01-01
  • 2021-03-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多