使用 libpng 将位图缓冲区快速编码为 png

Posted

技术标签:

【中文标题】使用 libpng 将位图缓冲区快速编码为 png【英文标题】:fast encode bitmap buffer to png using libpng 【发布时间】:2014-04-16 20:26:38 【问题描述】:

我的目标是使用 C/C++ 将 32 位位图 (BGRA) 缓冲区实时转换为 png 图像。为了实现它,我使用 libpng 库来转换位图缓冲区,然后写入 png 文件。然而,在目标臂板(四核处理器)上以单线程执行似乎需要大量时间(约 5 秒)。在分析时,我发现 libpng 压缩过程(放气算法)占用了 90% 以上的时间。所以我试图通过以某种方式使用并行化来减少它。这里的最终目标是至少在 0.5 秒内完成。

现在因为一个 png 可以有多个 IDAT 块,所以我想用多个 IDAT 并行编写 png。采用以下方法编写具有多个 IDAT 的自定义 png 文件

   1. Write PNG IHDR chunk
   2. Write IDAT chunks in parallel
      i.   Split input buffer in 4 parts.
      ii.  compress each part in parallel using zlib "compress" function.
      iii. compute CRC of chunk  "IDAT"+zlib compressed data .
      iv.  create IDAT chunk i.e.  "IDAT"+zlib compressed data+ CRC.
      v.   Write length of IDAT chunk created.
      vi.  Write complete chunk in sequence.
   3. write IEND chunk

现在的问题是通过此方法创建的 png 文件无效或损坏。谁能指出

    我做错了什么? 是否有任何 zlib 压缩或多线程 png 创建的快速实现,最好是在 C/C++ 中? 还有其他实现目标的替代方法吗?

注意:PNG specification 后面是创建块

更新: 此方法适用于并行创建 IDAT

    1. add one filter byte before each row of input image. 
    2. split image in four equal parts. <-- may not be required passing pointer to buffer and their offsets
    3. Compress Image Parts in parallel
            (A)for first image part
                --deflateinit(zstrm,Z_BEST_SPEED)
                --deflate(zstrm, Z_FULL_FLUSH)
                --deflateend(zstrm)
                --store compressed buffer and its length
                --store adler32 for current chunk, a1=zstrm->adler <--adler is of uncompressed data
            (B)for second and third image part
                --deflateinit(zstrm,Z_BEST_SPEED)
                --deflate(zstrm, Z_FULL_FLUSH)
                --deflateend(zstrm)
                --store compressed buffer and its length
                --strip first 2-bytes, reduce length by 2
                --store adler32 for current chunk zstrm->adler,a2,a3 similar to A <--adler is of uncompressed data
            (C) for last image part
                --deflateinit(zstrm,Z_BEST_SPEED)
                --deflate(zstrm, Z_FINISH)
                --deflateend(zstrm)
                --store compressed buffer and its length
                --strip first 2-bytes and last 4-bytes of buffer, reduce length by 6
                --here last 4 bytes should be equal to ztrm->adler,a4=zstrm->adler <--adler is of uncompressed data

    4. adler32_combine() all four parts i.e. a1,a2,a3 & a4 <--last arg is length of uncompressed data used to calculate adler32 of 2nd arg
    5. store total length of compressed buffers <--to be used in calculating CRC of complete IDAT & to be written before IDaT in file
    6. Append "IDAT" to Final chunk
    7. Append all four compressed parts in sequence to Final chunk
    8. Append adler32 checksum computed in step 4 to Final chunk
    9. Append CRC of Final chunk i.e."IDAT"+data+adler

    To be written in png file in this manner: [PNG_HEADER][PNG_DATA][PNG_END]
    where [PNG_DATA] ->Length(4-bytes)+"IDAT"(4-bytes)+data+adler(4-bytes)+CRC(4-bytes)

【问题讨论】:

Parallelization of PNG file creation with C++, libpng and OpenMP的可能重复 @timrau 我看过前面提到的帖子。在那篇文章中,作者实现了压缩并在 png 文件中仅创建了单个 IDAT 块,而在我的情况下,我正在尝试并行化并编写多个 IDAT。所以我想知道用多个IDAT并行编写png文件的正确方法是什么? 对步骤的评论:您不需要第 3 步,因为这些已在每个线程中计算,并且是您要剥离的三组四个字节。只是不要丢弃那些。然后当前步骤 4 将移动到当前步骤 5 之后。 您应该显示deflateInitdeflatedeflateEnd 的代码。 我不明白您在步骤 6-9 中的确切含义。另请注意,您需要一个块 CRC。 【参考方案1】:

即使 PNG 数据流中有多个 IDAT 块,它们仍然包含单个 zlib 压缩数据流。第一个 IDAT 的前两个字节是 zlib 头,最后一个 IDAT 的最后四个字节是整个数据流的 zlib“adler32”校验和(除了 2 字节头),在压缩之前计算。

zlib.net/pigz 正在开发一个并行 gzip (pigz)。当调用“pigz -z”时,它将生成 zlib 数据流而不是 gzip 数据流。

为此,您无需拆分输入文件,因为并行压缩发生在 pigz 内部。

【讨论】:

感谢格伦的回复。据我了解,压缩后的 zlib 数据流有 2 字节标头和 4 字节尾标。如果我在并行压缩的同时剥离它们,最后将它们全部组合起来。最后添加 2 字节的 zlib 标头并手动计算并附加 4 字节的 adler32 校验和,它会是有效的 png 吗? 是的,我是这么理解的。从第一段中剥离 4 字节的尾部。从其余段中剥离 2 字节标头和 4 字节尾标。计算原始完整数据流上的 adler32 校验和(可能在拆分过程中)。将该 4 字节校验和附加到最后一段。然后以长度和“IDAT”开始每个段,并在每个段的末尾加上一个 crc32 校验和。在 SourceForge(“pmt”项目的子目录)的“pngzop”项目中查看源文件,尤其是重新组装 IDAT 的 pngzop_zlib_to_idat.c 程序。 如果我理解正确的话,adler32 校验和不需要 zlib 压缩数据,可以更早地计算完整的输入缓冲区并直接附加到最后一个 IDAT 段? 不,您不能使用单独创建的 deflate 流并将它们连接起来以形成单个 deflate 流。第一个在最后一个块上设置了最后一位,这结束了解压缩。【参考方案2】:

在您的步骤ii 中,您需要使用deflate(),而不是compress()。在前三个部分使用Z_FULL_FLUSH,在最后一部分使用Z_FINISH。然后,您可以将它们连接到单个流中,从最后三个中提取两字节标头(将标头保留在第一个标头上),并从最后一个中提取四字节检查值。对于所有这些,您都可以从strm-&gt;adler 获取校验值。保存那些。

使用adler32_combine() 将您保存的四个检查值组合成一个检查值以供完整输入。然后,您可以将其添加到流的末尾。

你有它。

【讨论】:

1.您的意思是我不能将 zlib 压缩数据块单独写入单独的 IDAT 吗? 2. 是 zlib 2-byte header+data 的最后 4-byte adler checksum 还是只有 zlib data 部分? 3. 如果说我有 2 个 adler 和 s1 和 s2,如果我在 s2 之前通过 s1,adler32_combine() 是否会返回相同的最终值,反之亦然? 4. 如果我通过 Z_FULL_FLUSH 或 Z_FINISH 来放气,得到的压缩缓冲区仅相差一位?两种情况下的 adler 校验和是否相同? 1.是的。正如 Glenn 所说,单独的 IDAT 组合在一起是一个 zlib 流。 2.两者都不。校验和是未压缩的数据。 3.我不明白你说的反之亦然。您可以计算a = adler32(A)b = adler32(B),然后adler32_combine(a, b, len(B)) 将给出与adler32(AB) 相同的结果。这里AB 是字节序列,AB 是它们的串联。 4a。不会。Z_FULL_FLUSH 将在末尾写入一个额外的空块,以将 deflate 流带到字节边界。

以上是关于使用 libpng 将位图缓冲区快速编码为 png的主要内容,如果未能解决你的问题,请参考以下文章

使用 C++、libpng 和 OpenMP 并行创建 PNG 文件

Linux下libpng库的运用

使用 libpng 将 OpenGL 屏幕像素保存为 PNG

libpng 从内存缓冲区加载文件

压缩为 png 时位图颜色变化

使用 libpng 过滤器