如何使用 libpng 更快地读取文本块?
How do I read text chunks quicker with libpng?
使用 libpng,我试图在 44 兆字节的 PNG 图像中提取文本块(最好验证 PNG 数据没有格式错误(例如缺少 IEND
, 等等)).我可以用 png_read_png
和 png_get_text
做到这一点,但对我来说太长了,0.47 秒,我很确定这是因为大量的 IDAT
块图像有。我怎样才能更快地做到这一点?
我不需要像素,所以我尝试让 libpng 忽略 IDAT
块。
要让 libpng 忽略 IDAT
个块,我试过:
png_read_info(p_png, p_png_information); png_read_image(p_png, nullptr); png_read_end(p_png, p_png_information);
跳过 IDAT
个块;崩溃并失败。
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 进行所有元数据块解析(例如 eXIf
、gAMA
、pHYs
、zEXt
, 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 流,则可以避免这些复杂性:没有任何真正的 IDAT
s,您的完整合成 PNG 流必然很小...
虽然我没有基准可以在这里分享,但最终的解决方案很快,因为 IDAT
s 被完全跳过并且没有被解码。 (在读取 32 位块长度后,我使用文件搜索跳过源 PNG 中的每个 IDAT
。)
使用 libpng,我试图在 44 兆字节的 PNG 图像中提取文本块(最好验证 PNG 数据没有格式错误(例如缺少 IEND
, 等等)).我可以用 png_read_png
和 png_get_text
做到这一点,但对我来说太长了,0.47 秒,我很确定这是因为大量的 IDAT
块图像有。我怎样才能更快地做到这一点?
我不需要像素,所以我尝试让 libpng 忽略 IDAT
块。
要让 libpng 忽略 IDAT
个块,我试过:
png_read_info(p_png, p_png_information); png_read_image(p_png, nullptr); png_read_end(p_png, p_png_information);
跳过IDAT
个块;崩溃并失败。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 进行所有元数据块解析(例如 eXIf
、gAMA
、pHYs
、zEXt
, 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 流,则可以避免这些复杂性:没有任何真正的 IDAT
s,您的完整合成 PNG 流必然很小...
虽然我没有基准可以在这里分享,但最终的解决方案很快,因为 IDAT
s 被完全跳过并且没有被解码。 (在读取 32 位块长度后,我使用文件搜索跳过源 PNG 中的每个 IDAT
。)