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

时间:2012-05-31 03:57:06

标签: c++ parallel-processing png openmp libpng

我目前正在尝试在C ++中实现基于libpng的PNG编码器,该编码器使用OpenMP来加速压缩过程。 该工具已经能够从各种图像格式生成PNG文件。 我将完整的源代码上传到pastebin.com,以便您了解到目前为止我所做的工作:http://pastebin.com/8wiFzcgV

到目前为止,真好!现在,我的问题是找到一种方法如何并行化包含压缩图像数据的IDAT块的生成。通常,libpng函数png_write_row在for循环中被调用,该for循环具有指向结构的指针,该结构包含关于PNG文件的所有信息和具有单个图像行的像素数据的行指针。

(Pastebin文件中的第114-117行)

//Loop through image
for (i = 0, rp = info_ptr->row_pointers; i < png_ptr->height; i++, rp++) {
    png_write_row(png_ptr, *rp);
}

然后,Libpng压缩一行接一行,并用压缩数据填充内部缓冲区。一旦缓冲区已满,压缩数据就会在IDAT块中刷新到映像文件。

我的方法是将图像分成多个部分,让一个线程压缩第1行到第10行,另一个线程压缩11到20,依此类推。但是由于libpng正在使用内部缓冲区,它并不像我想象的那么容易:)我不得不让libpng将压缩数据写入每个线程的单独缓冲区。之后我需要一种方法以正确的顺序连接缓冲区,这样我就可以将它们一起写入输出图像文件。

那么,是否有人知道我如何使用OpenMP和一些调整到libpng?非常感谢你!

2 个答案:

答案 0 :(得分:8)

这对于评论来说太长了,但实际上也不是答案 -

我不确定如果不修改libpng(或编写自己的编码器)就可以做到这一点。在任何情况下,如果您了解如何实现PNG压缩,它将有所帮助:

在高级别,图像是一组像素行(通常是表示RGBA元组的32位值)。

每行可以独立地应用filter - 过滤器的唯一目的是使行更“可压缩”。例如,“sub”过滤器使每个像素的值与它与左边的值之间的差值。这种delta编码乍一看似乎很傻,但如果相邻像素之间的颜色相似(往往是这种情况),那么无论它们所代表的实际颜色如何,结果值都非常小。压缩这些数据更容易,因为它更重复。

向下一级,图像数据可以看作是一个字节流(行不再相互区分)。这些字节被压缩,产生另一个字节流。压缩数据被任意分解为每个IDAT块的段(任何你想要的!)(每个块都有一点簿记开销,包括CRC校验和)。

最低级别将我们带到了有趣的部分,即压缩步骤本身。 PNG格式使用zlib压缩数据格式。 zlib本身只是一个包装器(包含更多的簿记,包括Adler-32校验和),围绕真正的压缩数据格式deflate(zip文件也使用它)。 deflate支持两种压缩技术:霍夫曼编码(根据字符串中每个不同字节出现的频率,将一些字节串表示为最佳数量所需的位数)和LZ77编码(允许已经重复的字符串)发生了被引用而不是两次写入输出。)

关于并行化压缩压缩的棘手部分是,通常,压缩输入流的一部分要求前一部分在需要引用时也可用。 但是,就像PNG可以有多个IDAT块一样,deflate被分解成多个“块”。一个块中的数据可以引用另一个块中的先前编码数据,但它不具有(当然,如果不是,它可能会影响压缩率)。

因此,并行化deflate的一般策略是将输入分解为多个部分(使压缩率保持较高),将每个部分压缩成一系列块,然后粘合块在一起(这实际上很棘手,因为块并不总是在字节边界上结束 - 但是你可以放置一个空的非压缩块(类型00),它将对齐到字节边界,在两个部分之间)。然而,这并非易事,需要控制最低级别的压缩(手动创建deflate块),创建适当的zlib包装器,覆盖所有块,并将所有这些填充到IDAT块中。

如果您想使用自己的实现,我建议您阅读我为压缩PNG而明确创建的my own zlib/deflate implementation(和how I use it)(它是用Haxe for Flash编写的,但应该比较容易移植到C ++)。由于Flash是单线程的,我不进行任何并行化,但是我确实将编码分成几个独立的部分(“虚拟”,因为在部分之间保留了部分字节状态),这在多个帧中很大程度上是同样的事情。

祝你好运!

答案 1 :(得分:4)

我终于得到它来并行化压缩过程。 正如Cameron在对他的回答的评论中所提到的,我不得不从zstreams中删除zlib标题以组合它们。由于zlib提供了一个名为Z_SYNC_FLUSH的选项,它可以用于所有块(除了必须用Z_FINISH写入的最后一个块)以写入字节边界,因此不需要剥离页脚。因此,您可以简单地连接流输出。最终,必须在所有线程上计算adler32校验和,并将其复制到组合zstream的末尾。

如果您对结果感兴趣,可以在https://github.com/anvio/png-parallel

找到完整的概念证明