如何在Python zlib模块中设置参数

时间:2011-01-06 05:48:37

标签: python png zlib crc32

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

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

请注意,我可以生成剩余的PNG文件和要压缩的数据用于IDAT块,我只是不知道如何正确在实施初始过滤步骤后,压缩IDAT块的图像数据。

编辑:

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

6 个答案:

答案 0 :(得分:1)

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

def write_chunk(outfile, tag, data=''):
    """
    Write a PNG chunk to the output file, including length and
    checksum.
    """

    # http://www.w3.org/TR/PNG/#5Chunk-layout
    outfile.write(struct.pack("!I", len(data)))
    outfile.write(tag)
    outfile.write(data)
    checksum = zlib.crc32(tag)
    checksum = zlib.crc32(data, checksum)
    outfile.write(struct.pack("!i", checksum))

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

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

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

                compressor = zlib.compressobj()
                compressed = compressor.compress(tostring(data))
                if len(compressed):
                    # print >> sys.stderr, len(data), len(compressed)
                    write_chunk(outfile, 'IDAT', compressed)

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

答案 1 :(得分:0)

您不想使用某些现有软件来生成PNG吗? PyPNG怎么样?

答案 2 :(得分:0)

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

答案 3 :(得分:0)

看起来你将不得不求助于使用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 :(得分:0)

简答:(1)“deflate”和“32Kb window”是默认值(2)使用adler32而不是crc32

答案很长:

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

您无需设置它们。这些都是默认值。

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

来源:Python's gzip.py(请参阅它如何调用zlib.compressobj)

来源:Python's zlibmodule.c(参见默认值)

SO:This question(参见MizardX和我自己的回答,以及对每个的评论)

docs:manual

上的zlib site

“”“对于每个块的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)

答案 5 :(得分:0)

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

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

#! /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]]))