Creating a PNG file in Python
Asked Answered
D

5

23

I have an application where I would like to be able to generate PNG images from data in Python. I've done some searching and found "PIL" which looked pretty outdated. Is there some other library that would be better for this?

Dominiquedominium answered 18/12, 2011 at 19:42 Comment(1)
you use good old PIL (if you are not already developping beyond py3.2)Firdausi
F
11

Is there some other library that would be better for this?

The png package would be a reasonable common choice.

Here's the project description:

PyPNG allows PNG image files to be read and written using pure Python.

Fa answered 18/12, 2011 at 19:47 Comment(0)
C
58

Simple PNG files can be generated quite easily from pure Python code - all you need is the standard zlib module and some bytes-encoding to write the chunks. Here is a complete example that the casual reader may use as a starter for their own png generator:

#! /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]]))
Colcannon answered 14/9, 2014 at 16:21 Comment(3)
A little more depth than most of us, probably including OP, are looking for, but +1 for sharing so much information. This is great reference material if I ever want to go deeper with pngs.Farthest
This example is awesome. Any change you can change that to show how to add a palette (for some basic red/green/etc colors and have an alpha channel)?Avoid
I wanted to provide a very short answer. Obviously there are quite a number of options to build and encode a picture. Luckily the PNG standard is quite clear on what to put into each chunk (w3.org/TR/PNG). To avoid bit stuffing one would simply look for the options to always encode a value into exactly one byte. ///// For an RGBA that can be very simple: a PLTE chunk has no encoding options => it is always exactly 8bit per color channel, not more and not less. So each entry is 4 bytes (R,G,B,A). The IHDR bitdepth refers to the IDAT - with 8bit one can write just index bytes again.Colcannon
M
19

Here's a Python3 example:

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)
Miles answered 16/4, 2019 at 19:13 Comment(0)
F
11

Is there some other library that would be better for this?

The png package would be a reasonable common choice.

Here's the project description:

PyPNG allows PNG image files to be read and written using pure Python.

Fa answered 18/12, 2011 at 19:47 Comment(0)
S
2

After doing some searching, I found this site: https://www.daniweb.com/programming/software-development/code/454765/save-a-pygame-drawing. It just uses good old pygame.

Line 37: (pg.image.save(win, fname)) saves whatever is drawn on the pygame surface as a file.

EDIT

Here is the actual code:

"""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
Statics answered 23/3, 2021 at 22:16 Comment(1)
@DanielM Thank you for the tip, I will change it to include the actual code.Statics
C
-4

I'm aware that this question was asked over 9 years ago, but in the age of Python 3.8, you can just open the file in "Writing Binary" mode:

f = open("filename.png", 'wb')
# data being whatever data you wanted to write
f.write(data)
Cepheus answered 19/5, 2021 at 21:16 Comment(1)
It doesn't address the actual problem though.Topsyturvydom

© 2022 - 2024 — McMap. All rights reserved.