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

问题描述 投票:7回答:2

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

到现在为止还挺好!现在,我的问题是找到一种方法如何并行化包含压缩图像数据的IDAT块的生成。通常,libpng函数png_write_row在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?非常感谢你!

c++ parallel-processing png openmp libpng
2个回答
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是单线程的,所以我没有进行任何并行化,但是我确实将编码分成几个独立的部分(“虚拟”,因为在部分之间保留了部分字节状态),这在很大程度上相当于一样。

祝好运!


4
投票

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

如果您对结果感兴趣,可以在https://github.com/anvio/png-parallel找到完整的概念证明

© www.soinside.com 2019 - 2024. All rights reserved.