【问题标题】:How do I read text chunks quicker with libpng?如何使用 libpng 更快地阅读文本块?
【发布时间】:2021-04-02 18:42:42
【问题描述】:

使用 libpng,我正在尝试在 44 兆字节的 PNG 图像中提取文本块(最好验证 PNG 数据没有格式错误(例如缺少 IEND 等)) .我可以用png_read_pngpng_get_text 来做到这一点,但对我来说它花费的时间太长了,0.47 秒,我很确定这是因为图像中有大量的IDAT 块。我怎样才能更快地做到这一点?


我不需要像素,所以我尝试让 libpng 忽略 IDAT 块。

为了让 libpng 忽略 IDAT 块,我试过了:

  1. png_read_info(p_png, p_png_information); png_read_image(p_png, nullptr); png_read_end(p_png, p_png_information); 跳过 IDAT 块;崩溃失败。
  2. png_set_keep_unknown_chunks 使 libpng unknow 关于 IDATpng_set_read_user_chunk_fn(p_png, nullptr, discard_an_unknown_chunk)discard_an_unknown_chunk 是一个函数,它执行 return 1;)以丢弃未知块;第一个 IDAT 块发生了一个奇怪的 CRC 错误并且失败了。

但没能做到。


编辑

在 Windows 10 上作为 Node.js C++ 插件运行,主要用 C++ 编写,具有 i9-9900K CPU @ 3.6 GHz 和千兆字节内存。

使用fs.readFileSync读取SSD上的图像文件,这是一个返回Buffer的Node.js方法,并将其扔给libpng进行处理。

是的,起初,我将长时间的计算归咎于 libpng。现在我看到可能有其他原因导致延迟。 (如果是这样的话,这个问题将是一个糟糕的问题,XY problem。)谢谢你的 cmets。我将再次更彻底地检查我的代码。


编辑 2

由于将 PNG 数据输入提供给 C++ 插件的每一步都保持不变,因此我最终仅使用我的 C 指针魔术和一些 C++ 魔术手动挑选和解码文本块。而且,性能令人印象深刻(处理时间为 0.0020829 秒),几乎是立竿见影的。不知道为什么以及如何。

B:\__A2MSUB\image-processing-utility>npm run test

> image-processing-utility@1.0.0 test B:\__A2MSUB\image-processing-utility
> node tests/test.js

----- “read_png_text_chunks (manual decoding, not using libpng.)” -----
[
  {
    type: 'tEXt',
    keyword: 'date:create',
    language_tag: null,
    translated_keyword: null,
    content: '2020-12-13T22:01:22+09:00',
    the_content_is_compressed: false
  },
  {
    type: 'tEXt',
    keyword: 'date:modify',
    language_tag: null,
    translated_keyword: null,
    content: '2020-12-13T21:53:58+09:00',
    the_content_is_compressed: false
  }
]
----- “read_png_text_chunks (manual decoding, not using libpng.)” took 0.013713 seconds.

B:\__A2MSUB\image-processing-utility>

【问题讨论】:

  • 你为什么不在分析器下运行它,看看时间花在哪里,而不是猜测? Libpng 是开源的,Vtune 现在是免费的。
  • 也许您可以通过 Dropbox、Google Drive 或其他方式共享 PNG? “最好验证 PNG 数据” 到底是什么意思?验证什么?这个大文件的像素尺寸和位深度/颜色类型是多少 - 因为 a)您的磁盘必须读取 44MB,这在 150MB/s 磁盘上可能需要 0.3 秒,b)您可能正在分配 GB 的内存来扩展压缩文件PNG转换成。
  • 您还可以澄清一下您的环境 - 在带有 USB 2.0 记忆棒 0.47s 的 Raspberry Pi Zero 上会令人印象深刻。

标签: c++ node.js c libpng


【解决方案1】:

我必须做类似的事情,但我希望 libpng 进行所有元数据块解析(例如,eXIfgAMApHYszEXtcHRM 等块)。其中一些块可以出现在IDAT 之后,这意味着仅使用png_read_info 无法读取元数据。 (获得它们的唯一方法是对图像进行完整解码,这很昂贵,然后调用png_read_end。)

我的解决方案是创建一个合成 PNG 字节流,通过使用 png_set_read_fn 的读取回调集将其馈送到 libpng。在该回调中,我跳过源 PNG 文件中的所有 IDAT 块,当我到达 IEND 块时,我改为发出一个长度为零的 IDAT 块。

现在我调用png_read_info:它解析它看到的所有块中的所有元数据,在第一个IDAT 处停止,在我的合成PNG 流中它实际上是源PNG 图像的结尾。现在我有了所有的元数据,可以通过 png_get_xxx 函数查询 libpng 了。

创建合成 PNG 流的读取回调有点复杂,因为它被 libpng 多次调用,每次都针对流的一小部分。我使用一个简单的状态机解决了这个问题,该状态机逐步处理源 PNG,即时生成合成 PNG 流。如果您在调用png_read_info 之前在内存中预先生成合成PNG 流,则可以避免这些复杂性:没有任何真正的IDATs,您的完整合成PNG 流肯定会很小...

虽然我在这里没有要分享的基准,但最终的解决方案很快,因为 IDATs 被完全跳过而不被解码。 (在读取 32 位块长度后,我使用文件搜索跳过源 PNG 中的每个 IDAT。)

【讨论】:

  • 多么聪明的方法,只有 libpng!
【解决方案2】:

您可以使用pngcheck 检查文件中所有正确的PNG 块是否以正确的顺序、不重复且校验和正确。它是开源的,所以你可以看看它是如何工作的。

如果添加参数-7,不仅可以查看结构,还可以提取文本:

pngcheck -7 a.png

输出

File: a.png (60041572 bytes)
date:create:
    2020-12-24T13:22:41+00:00
date:modify:
    2020-12-24T13:22:41+00:00
OK: a.png (10000x1000, 48-bit RGB, non-interlaced, -0.1%).

我在我的 MacBook Pro 上生成了一个 60MB 的 PNG,上面的检查需要 0.067 秒。

【讨论】:

    猜你喜欢
    • 2011-06-07
    • 2012-09-29
    • 2022-08-06
    • 1970-01-01
    • 1970-01-01
    • 2023-03-27
    • 2015-08-07
    • 2018-11-15
    • 1970-01-01
    相关资源
    最近更新 更多