3

我想编写一个生成 PNG 文件的 Python 程序。我的大问题是生成 CRC 和 IDAT 块中的数据。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 的问题在于它不会写入文本块。一个小烦恼是必须将图像作为(R,G,B)数据进行处理;我更喜欢直接操作像素的调色板值,然后定义调色板值和颜色数据之间的关联。我也不确定 PyPNG 是否利用图像数据中的 1、2 和 4 位调色板值允许的“压缩”来适应一个字节中的多个像素。

4

6 回答 6

1

即使你因为文本块的原因不能使用 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 中执行此操作的代码不是特别好,请参阅 write_passes () 函数。实际压缩数据并写入块的位是这样的:

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

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

于 2011-03-16T10:22:46.017 回答
0

你不想使用一些现有的软件来生成你的 PNG 吗?PyPNG怎么样?

于 2011-01-06T05:53:26.487 回答
0

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

于 2011-01-06T06:53:59.050 回答
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

于 2011-01-06T09:19:51.970 回答
0

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

长答案:

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

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

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

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

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

SO:这个问题(请参阅 MizardX 和我自己的答案,以及对每个问题的评论)

docs:zlib 网站上的手册

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

请查看zlib 规范 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)
于 2011-01-06T22:13:58.770 回答
0

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]]))
于 2014-09-14T16:13:31.923 回答