使用Python创建PNG文件

21
我有一个应用程序,我希望能够使用Python从数据生成PNG图像。我做了一些搜索,发现了一个叫做“PIL”的库,但它看起来相当过时。是否有其他更适合这个需求的库?

1
如果你还没有使用 Python 3.2 以上版本,你可以使用好用的 PIL 库(Python Imaging Library)。 - joaquin
5个回答

57

使用纯Python代码可以轻松生成简单的PNG文件 - 你只需要标准的zlib模块和一些字节编码来写入块。下面是一个完整的示例,普通读者可以用作他们自己的png生成器的起点:

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

10
比大多数人(可能包括OP)期望的更深入一些,但感谢分享这么多信息。如果我将来想要深入了解PNG,这是很好的参考资料。 - mightypile
这个例子很棒。你能否更改一下,展示如何添加一个调色板(包括一些基本的红/绿等颜色,并带有一个透明通道)? - 576i
我想提供一个非常简短的答案。显然,有许多选项可以构建和编码图片。幸运的是,PNG标准非常清楚每个块应该放置什么(https://www.w3.org/TR/PNG)。为了避免比特填充,只需寻找永远将一个值编码为一个字节的选项即可。///// 对于RGBA来说,这可能非常简单:PLTE块没有编码选项=>每个颜色通道始终精确地为8位,不多不少。因此,每个条目都是4个字节(R,G,B,A)。IHDR位深度指的是IDAT - 使用8位,可以再次写入索引字节。 - Guido U. Draheim

17

这是一个Python3的示例:

import png

width = 255
height = 255
img = []
for y in range(height):
    row = ()
    for x in range(width):
        row = row + (x, max(0, 255 - x - y), y)
    img.append(row)
with open('gradient.png', 'wb') as f:
    w = png.Writer(width, height, greyscale=False)
    w.write(f, img)

11

有没有其他更适合的库?

pypng 库可能是一个比较常见的选择。

以下是该项目的描述:

PyPNG 允许使用纯 Python 读写 PNG 图像文件。


2
经过一番搜索,我找到了这个网站:https://www.daniweb.com/programming/software-development/code/454765/save-a-pygame-drawing。它只使用了古老但实用的pygame库。
第37行(pg.image.save(win, fname))会将在pygame表面上绘制的任何内容保存为文件。 编辑 以下是实际代码:
"""pg_draw_circle_save101.py
draw a blue solid circle on a white background
save the drawing to an image file
for result see http://prntscr.com/156wxi
tested with Python 2.7 and PyGame 1.9.2 by vegaseat  16may2013
"""

import pygame as pg

# pygame uses (r, g, b) color tuples
white = (255, 255, 255)
blue = (0, 0, 255)

width = 300
height = 300

# create the display window
win = pg.display.set_mode((width, height))
# optional title bar caption
pg.display.set_caption("Pygame draw circle and save")
# default background is black, so make it white
win.fill(white)

# draw a blue circle
# center coordinates (x, y)
center = (width//2, height//2)
radius = min(center)
# width of 0 (default) fills the circle
# otherwise it is thickness of outline
width = 0
# draw.circle(Surface, color, pos, radius, width)
pg.draw.circle(win, blue, center, radius, width)

# now save the drawing
# can save as .bmp .tga .png or .jpg
fname = "circle_blue.png"
pg.image.save(win, fname)
print("file {} has been saved".format(fname))

# update the display window to show the drawing
pg.display.flip()


# (press escape key or click window title bar x to exit)
while True:
for event in pg.event.get():
    if event.type == pg.QUIT:
        # most reliable exit on x click
        pg.quit()
        raise SystemExit
    elif event.type == pg.KEYDOWN:
        # optional exit with escape key
        if event.key == pg.K_ESCAPE:
            pg.quit()
            raise SystemExit

@DanielM 感谢您的提示,我会将其更改为包含实际代码。 - Party-with-Programming

-3

我知道这个问题已经问了9年了,但在Python 3.8时代,你可以直接以“写二进制”模式打开文件:

f = open("filename.png", 'wb')
# data being whatever data you wanted to write
f.write(data)

但它并没有解决实际问题。 - S.B

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接