【问题标题】:How to set parameters in Python zlib module如何在 Python zlib 模块中设置参数
【发布时间】:2011-06-04 11:18:21
【问题描述】:

我想写一个 Python 程序来制作 PNG 文件。我的大问题是生成 CRC 和 IDAT 块中的数据。 Python 2.6.4 确实有一个 zlib 模块,但需要额外的设置。 PNG 规范要求使用 zlib 的 deflate 方法压缩 IDAT 数据,窗口大小为 32768 字节,但我找不到如何在 Python zlib 模块中设置这些参数。

对于每个块的 CRC,zlib 模块文档表明它包含一个 CRC 函数。我相信将该 CRC 函数调用为 crc32(data,-1) 将生成我需要的 CRC,但如有必要,我可以翻译 PNG 规范中给出的 C 代码。

请注意,我可以为 IDAT 块生成 PNG 文件的其余部分和 要压缩的数据,我只是不知道如何正确在执行初始过滤步骤后压缩 IDAT 块的图像数据。

编辑:

PyPNG 的问题是它不会写入文本块。一个小烦恼是必须将图像作为(R,G,B)数据进行处理;我更喜欢直接操作像素的调色板值,然后定义调色板值和颜色数据之间的关联。我也不确定 PyPNG 是否利用图像数据中的 1 位、2 位和 4 位调色板值允许的“压缩”来适应一个字节中的多个像素。

【问题讨论】:

  • 为什么不使用 PNG 库而不是 zlib 库?
  • 正如我在编辑中指出的那样,PyPNG 不会写入文本块。
  • (我是 PyPNG 的维护者)基于调色板的图像不是我经常玩的东西,但 PyPNG 确实对它们提供了非常全面的支持。是的,您可以随心所欲地制作调色板图像,请参阅this example from the documentation(我同意这可能不清楚)。是的,您可以拥有每像素 2 位的调色板图像,这会将 4 个像素打包成一个字节。在创建 png.Writer 实例时只需指定 bitdepth=2 (这不清楚)。当然,这也适用于其他位深度。
  • 你是对的,你不能用 PyPNG 添加一个文本块。那是Issue 4,现在我知道有人想要它,我可能很快就会添加这个功能。您愿意共同创建一个好的 API 吗?

标签: python png zlib crc32


【解决方案1】:

您不想使用一些现有的软件来生成您的 PNG 吗? PyPNG呢?

【讨论】:

    【解决方案2】:

    有些库可以为您编写 PNG 文件,例如 PIL。这将更容易和更快,而且作为额外的奖励,您可以读写大量格式。

    【讨论】:

      【解决方案3】:

      看起来您将不得不求助于使用 ctypes “手动”调用 zlib -- 没那么难:

      >>> import ctypes                                                     
      >>> z = ctypes.cdll.LoadLibrary("libzip.so.1")
      >>> z.zlibVersion.restype=ctypes.c_char_p
      >>> z.zlibVersion()
      '1.2.3'
      

      您可以在此处查看 zlib 库文档:http://zlib.net/manual.html

      【讨论】:

        【解决方案4】:

        简答:(1)“deflate”和“32Kb 窗口”是默认设置(2)使用 adler32 而不是 crc32

        长答案:

        """ PNG 规范要求使用 zlib 的 deflate 方法压缩 IDAT 数据,窗口大小为 32768 字节,但我在 Python zlib 模块中找不到如何设置这些参数。"""

        您不需要设置它们。这些是默认值。

        如果你真的想为 zlib 指定非默认参数,你可以使用 zlib.compressobj() ...它有几个 Python 文档中没有记录的参数。阅读材料:

        来源:Python's gzip.py(查看它如何调用 zlib.compressobj)

        来源:Python's zlibmodule.c(查看其默认值)

        SO:This question(请参阅 MizardX 和我自己的答案,以及每个人的 cmets)

        文档:zlib site 上的 manual

        """至于每个块的 CRC,zlib 模块文档表明它包含一个 CRC 函数。我相信将该 CRC 函数调用为 crc32(data,-1) 会生成我需要的 CRC,不过如有必要,我可以翻译 PNG 规范中给出的 C 代码。"""

        请查看the zlib specification aka RFC 1950 ...它说使用的校验和是adler32

        zlib compress 或 compressobj 输出将包含适当的 CRC;为什么你认为你需要自己做?

        编辑所以你确实需要一个 CRC-32。好消息:zlib.crc32() 将完成这项工作:

        代码:

        import zlib
        
        crc_table = None
        
        def make_crc_table():
          global crc_table
          crc_table = [0] * 256
          for n in xrange(256):
            c = n
            for k in xrange(8):
                if c & 1:
                    c = 0xedb88320L ^ (c >> 1)
                else: 
                    c = c >> 1
            crc_table[n] = c
        
        make_crc_table()    
        
        """
        /* Update a running CRC with the bytes buf[0..len-1]--the CRC
        should be initialized to all 1's, and the transmitted value
        is the 1's complement of the final running CRC (see the
        crc() routine below)). */
        """
        def update_crc(crc, buf):
          c = crc
          for byte in buf:
            c = crc_table[int((c ^ ord(byte)) & 0xff)] ^ (c >> 8)
          return c
        
        # /* Return the CRC of the bytes buf[0..len-1]. */
        def crc(buf):
          return update_crc(0xffffffffL, buf) ^ 0xffffffffL
        
        if __name__ == "__main__":
            tests = [
                "",
                "\x00",
                "\x01",
                "Twas brillig and the slithy toves did gyre and gimble in the wabe",
                ]
        
            for test in tests:
                model = crc(test) & 0xFFFFFFFFL
                zlib_result = zlib.crc32(test) & 0xFFFFFFFFL
                print (model, zlib_result, model == zlib_result)
        

        Python 2.7 的输出如下。还使用 Python 2.1 到 2.6(含)和 1.5.2 JFTHOI 进行了测试。

        (0L, 0L, True)
        (3523407757L, 3523407757L, True)
        (2768625435L, 2768625435L, True)
        (4186783197L, 4186783197L, True)
        

        【讨论】:

        • zlib 输出包含 adler32 校验和,但 PNG 规范要求每个 PNG 块都具有块本身的 CRC 校验和。对于未压缩的块;例如,IHDR、PLTE 和文本;这个 CRC 是唯一的校验和;但对于 zlib 压缩块;例如,IDAT、zTXt;这有点多余,因为块中的 zlib 数据包含其自己的 adler32 校验和,但 PNG 规范要求所有块上的 CRC。我希望能够使用一个已经编写好的函数来计算这个 CRC,但是,正如我所说,我可以为 PNG CRC 算法 if编写代码> 我必须。
        • 我意识到我应该注意到 PNG CRC 算法的描述位于 libpng.org/pub/png/spec/1.2/PNG-Structure.html#CRC-algorithm 。回想起来,对我来说很明显我需要提供 PNG CRC 算法的细节来说明 Python zlib 提供的 crc32 是否会正确生成 PNG 块所需的 CRC。这个特定的组件不是那么重要,因为我可以将该页面上链接中给出的 C 代码翻译成 Python。
        • @fagricipni:好的,我明白你关于 PNG 要求其块的 CRC-32 校验和的观点。如果 zlib.crc32 没有完成这项工作,我会感到非常惊讶:zlib 和 PNG 来自同一个稳定器(或多或少),而 AFAIK 只有一个没有后缀/下标/上标的 CRC-32。请注意,PNG 规范中示例代码在 Python 中的实现可能相当慢。
        【解决方案5】:

        即使出于文本块的原因您不能使用 PyPNG,您也可以使用它的代码! (它是麻省理工学院许可的)。以下是块的编写方式:

        def write_chunk(outfile, tag, data=''): """ 将 PNG 块写入输出文件,包括长度和 校验和。 """ # http://www.w3.org/TR/PNG/#5Chunk-layout outfile.write(struct.pack("!I", len(data))) outfile.write(标签) outfile.write(数据) 校验和 = zlib.crc32(标签) 校验和 = zlib.crc32(数据,校验和) outfile.write(struct.pack("!i", checksum))

        注意使用 zlib.crc32 创建 CRC 校验和,并注意校验和如何在标签和数据上运行。

        对于压缩 IDAT 块,您基本上只需使用 zlib。正如其他人所指出的,adler 校验和和默认窗口大小都可以(顺便说一下,PNG 规范不要求窗口大小为 32768,它要求窗口最多为 32768 字节;这有点奇怪,因为无论如何,32768 是当前版本的 zlib 规范所允许的最大窗口大小)。

        在 PyPNG 中执行此操作的代码不是特别好,请参阅 the write_passes() function。实际压缩数据并写入块的位是这样的:

        压缩器 = zlib.compressobj() 压缩=compressor.compress(tostring(数据)) 如果 len(压缩): # 打印 >> sys.stderr, len(data), len(compressed) write_chunk(输出文件,'IDAT',压缩)

        PyPNG 从不使用扫描线过滤。部分原因是它在 Python 中会非常慢,部分原因是我还没有编写代码。如果你有 Python 代码来进行过滤,这将是对 PyPNG 的一个非常受欢迎的贡献。 :)

        【讨论】:

        • 在测试过程中我发现你需要在写块之前使用compressor.flush()!
        • 这是一个错误报告吗?
        【解决方案6】:

        zlib.crc32 工作正常,zlib 压缩器具有正确的 png 生成默认值。

        对于从 Python 代码中寻找 png 生成的普通读者,这里有一个完整的示例,您可以将其用作您自己的 png 生成器代码的入门 - 您只需要标准的 zlib 模块和一些字节编码:

        #! /usr/bin/python
        """ Converts a list of list into gray-scale PNG image. """
        __copyright__ = "Copyright (C) 2014 Guido Draheim"
        __licence__ = "Public Domain"
        
        import zlib
        import struct
        
        def makeGrayPNG(data, height = None, width = None):
            def I1(value):
                return struct.pack("!B", value & (2**8-1))
            def I4(value):
                return struct.pack("!I", value & (2**32-1))
            # compute width&height from data if not explicit
            if height is None:
                height = len(data) # rows
            if width is None:
                width = 0
                for row in data:
                    if width < len(row):
                        width = len(row)
            # generate these chunks depending on image type
            makeIHDR = True
            makeIDAT = True
            makeIEND = True
            png = b"\x89" + "PNG\r\n\x1A\n".encode('ascii')
            if makeIHDR:
                colortype = 0 # true gray image (no palette)
                bitdepth = 8 # with one byte per pixel (0..255)
                compression = 0 # zlib (no choice here)
                filtertype = 0 # adaptive (each scanline seperately)
                interlaced = 0 # no
                IHDR = I4(width) + I4(height) + I1(bitdepth)
                IHDR += I1(colortype) + I1(compression)
                IHDR += I1(filtertype) + I1(interlaced)
                block = "IHDR".encode('ascii') + IHDR
                png += I4(len(IHDR)) + block + I4(zlib.crc32(block))
            if makeIDAT:
                raw = b""
                for y in xrange(height):
                    raw += b"\0" # no filter for this scanline
                    for x in xrange(width):
                        c = b"\0" # default black pixel
                        if y < len(data) and x < len(data[y]):
                            c = I1(data[y][x])
                        raw += c
                compressor = zlib.compressobj()
                compressed = compressor.compress(raw)
                compressed += compressor.flush() #!!
                block = "IDAT".encode('ascii') + compressed
                png += I4(len(compressed)) + block + I4(zlib.crc32(block))
            if makeIEND:
                block = "IEND".encode('ascii')
                png += I4(0) + block + I4(zlib.crc32(block))
            return png
        
        def _example():
            with open("cross3x3.png","wb") as f:
                f.write(makeGrayPNG([[0,255,0],[255,255,255],[0,255,0]]))
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-07-23
          • 1970-01-01
          • 2017-08-23
          • 2017-10-06
          • 2015-06-06
          相关资源
          最近更新 更多