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?
6 Answers
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]]))
3 Comments
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)
1 Comment
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.
Comments
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
1 Comment
import matplotlib.pyplot as plt
import numpy as np
# Create the figure and axis
fig, ax = plt.subplots(figsize=(10, 8))
# Building outline (a two-floor building)
# Outer building boundary
building = plt.Rectangle((0.05, 0.05), 0.9, 0.9, fill=None, edgecolor='black', linewidth=3)
ax.add_patch(building)
# Floor division: horizontal line dividing floors
ax.plot([0.05, 0.95], [0.5, 0.5], color='black', linewidth=3)
# Vertical divisions for rooms on each floor
# Top floor: two rooms (left and right)
ax.plot([0.525, 0.525], [0.5, 0.95], color='black', linewidth=3)
# Bottom floor: three rooms (left, center, right)
ax.plot([0.35, 0.35], [0.05, 0.5], color='black', linewidth=3)
ax.plot([0.65, 0.65], [0.05, 0.5], color='black', linewidth=3)
# Function to draw a sound wave within a given room
def draw_sound_wave(x_start, x_end, y_base, amplitude, frequency, phase, color, linestyle='-'):
x = np.linspace(x_start, x_end, 500)
y = amplitude * np.sin(2 * np.pi * frequency * (x - x_start) + phase) + y_base
ax.plot(x, y, color=color, linewidth=2, linestyle=linestyle)
# Top floor: left room (scientific interpretation: precise sine wave)
draw_sound_wave(0.07, 0.51, 0.75, 0.03, 4, 0, 'red')
# Top floor: right room (artistic interpretation: modulated wave with phase shift)
draw_sound_wave(0.54, 0.93, 0.75, 0.04, 6, np.pi/3, 'blue', linestyle='--')
# Bottom floor: left room (clean sine wave)
draw_sound_wave(0.07, 0.33, 0.3, 0.02, 8, np.pi/4, 'green')
# Bottom floor: center room (combination of two waves for a complex pattern)
draw_sound_wave(0.36, 0.64, 0.3, 0.02, 10, np.pi/2, 'purple')
draw_sound_wave(0.36, 0.64, 0.3, 0.015, 15, 0, 'orange', linestyle=':')
# Bottom floor: right room (abstract pattern)
draw_sound_wave(0.67, 0.93, 0.3, 0.03, 5, np.pi/6, 'magenta')
# Adding text labels for clarity
ax.text(0.5, 0.98, 'Building Sound Visualization', ha='center', fontsize=16, weight='bold', transform=ax.transAxes)
ax.text(0.3, 0.83, 'Scientific', ha='center', fontsize=12, color='red')
ax.text(0.8, 0.83, 'Artistic', ha='center', fontsize=12, color='blue')
ax.text(0.2, 0.38, 'Clean Wave', ha='center', fontsize=10, color='green')
ax.text(0.5, 0.38, 'Complex Pattern', ha='center', fontsize=10, color='purple')
ax.text(0.8, 0.38, 'Abstract', ha='center', fontsize=10, color='magenta')
# Remove ticks and set axis limits
ax.set_xticks([])
ax.set_yticks([])
ax.set_xlim(0, 1)
ax.set_ylim(0, 1)
ax.set_aspect('equal')
# Save the image as PNG
plt.savefig("building_sound_visualization.png", dpi=300, bbox_inches='tight')
plt.show()
1 Comment
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)