Pure Python library for saving and loading PNG images without external dependencies
—
The Image class provides a convenient container for PNG image data with automatic format handling and multiple output methods. It bridges PNG reading and writing operations with a simple, high-level interface.
The Image class encapsulates PNG image data and metadata, providing convenient methods for saving and manipulating image data.
class Image:
def __init__(self, rows, info):
"""
Initialize Image container with pixel data and metadata.
Parameters:
- rows: iterable of pixel rows
- info: dict containing PNG metadata (width, height, bitdepth, etc.)
Note: This constructor is not intended for direct use. Create Image
objects using png.from_array() or Reader methods that return Images.
"""
def save(self, file):
"""
Save image to a named file.
Parameters:
- file: str, path to output PNG file
"""
def write(self, file):
"""
Write image to an open file object.
Parameters:
- file: file-like object opened in binary write mode
"""
def stream(self):
"""
Convert row iterator to accessible list.
Returns:
list: List of pixel rows, where each row is a sequence of pixel values
Note: This method consumes the row iterator, so it can only be called once.
"""import png
# Create Image from 2D array
pixel_data = [
[255, 0, 0, 255, 255, 0], # Red, Yellow pixels (RGB)
[0, 255, 0, 0, 0, 255], # Green, Blue pixels
[128, 128, 128, 64, 64, 64] # Two grey pixels
]
# Create Image object
image = png.from_array(pixel_data, 'RGB')
# Save to file
image.save('array_image.png')import png
# Create a simple greyscale gradient
width, height = 64, 64
pixels = []
for y in range(height):
row = []
for x in range(width):
# Diagonal gradient
value = int(255 * (x + y) / (width + height - 2))
row.append(value)
pixels.append(row)
# Create Image
image = png.from_array(pixels, 'L') # 'L' for greyscale
# Access pixel data as list
pixel_rows = image.stream()
print(f"Image has {len(pixel_rows)} rows")
print(f"First row has {len(pixel_rows[0])} pixels")
print(f"Top-left pixel value: {pixel_rows[0][0]}")
print(f"Bottom-right pixel value: {pixel_rows[-1][-1]}")import png
# Read existing PNG as Image
reader = png.Reader(filename='input.png')
width, height, rows, info = reader.read()
# Create Image container
image = png.Image(rows, info)
# Save with different filename
image.save('copy.png')
# Or write to file object with custom handling
with open('processed.png', 'wb') as f:
image.write(f)import png
# Create test image
original_data = [
[100, 150, 200], # Row 1
[120, 170, 220], # Row 2
[140, 190, 240] # Row 3
]
image = png.from_array(original_data, 'L')
# Get pixel data for processing
rows = image.stream()
# Process pixels (brighten by 20%)
processed_rows = []
for row in rows:
new_row = []
for pixel in row:
# Brighten pixel, clamp to 255
new_pixel = min(255, int(pixel * 1.2))
new_row.append(new_pixel)
processed_rows.append(new_row)
# Create new image with processed data
processed_image = png.from_array(processed_rows, 'L')
processed_image.save('brightened.png')import png
def process_large_image(input_file, output_file):
"""Process large PNG file without loading entire image into memory"""
# Read image metadata without loading all pixel data
reader = png.Reader(filename=input_file)
width, height, rows, info = reader.read()
# Process rows one at a time
def processed_rows():
for row in rows:
# Apply some processing to each row
processed_row = [min(255, pixel + 50) for pixel in row]
yield processed_row
# Write processed image
writer = png.Writer(width=width, height=height, **info)
with open(output_file, 'wb') as f:
writer.write(f, processed_rows())
# Usage
process_large_image('large_input.png', 'large_output.png')import png
# Create image with metadata
pixels = [[x for x in range(100)] for y in range(50)]
image = png.from_array(pixels, 'L')
# The Image object contains the info dict with metadata
# Access through the internal structure (for advanced use)
print("Image dimensions available through save/write operations")
# To access metadata, work with Reader directly
reader = png.Reader(filename='test.png')
width, height, rows, info = reader.read()
print(f"Width: {width}")
print(f"Height: {height}")
print(f"Bit depth: {info.get('bitdepth', 'unknown')}")
print(f"Color type: {info.get('greyscale', 'color')}")
print(f"Has alpha: {info.get('alpha', False)}")
print(f"Interlaced: {info.get('interlace', False)}")import png
# Chain Image operations for fluent workflow
def create_test_pattern():
"""Create a test pattern image"""
width, height = 32, 32
pixels = []
for y in range(height):
row = []
for x in range(width):
# Create checkerboard pattern
if (x // 4 + y // 4) % 2:
r, g, b = 255, 255, 255 # White
else:
r, g, b = 0, 0, 0 # Black
row.extend([r, g, b])
pixels.append(row)
return png.from_array(pixels, 'RGB')
# Create and save in one operation
test_image = create_test_pattern()
test_image.save('test_pattern.png')
# Or use for further processing
pixel_data = test_image.stream()
print(f"Created {len(pixel_data)}x{len(pixel_data[0])//3} RGB image")Install with Tessl CLI
npx tessl i tessl/pypi-pypng