如何使用 libpng 更快地读取文本块?

How do I read text chunks quicker with libpng?

使用 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 未知 关于 IDAT,并且 png_set_read_user_chunk_fn(p_png, nullptr, discard_an_unknown_chunk)discard_an_unknown_chunk 是一个函数 return 1;) 丢弃未知块;第一个 IDAT 块发生奇怪的 CRC 错误并失败。

并没有做到。


编辑

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

fs.readFileSync读取SSD上的图片文件,Node.js方法返回Buffer,丢给libpng处理

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


编辑 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>

您可以使用 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%).

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

我不得不做类似的事情,但我希望 libpng 进行所有元数据块解析(例如 eXIfgAMApHYszEXt , cHRM, 等块)。其中一些块可以出现在 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。)