Pure Python PNG Generator (PPyPNG)

This snippet introduces a python2 script for generating PNG (Portable Network Graphics) image files. It is a straight conversion from a public domain C program.

The module has a limited capability for creating PNG files - it can only generate files with a 256-color palette.

The following python2 program uses it to create this example PNG image file, in this case something like a Sparkline.

demopng.py
# demopng.py - tiny file based CMS for static web sites
# Copyright (c) 2008 Niall McCarroll  
# Distributed under the MIT/X11 License (http://www.mccarroll.net/snippets/license.txt)

from ppypng import PNGCanvas

palette = [(255,255,255),(255,0,255),(0,0,255)]

width = 64
height = 32
xstep = 4

data = [10,11,25,26,29,30,22,23,20,18,21,16,14,12,4,5]

canvas = PNGCanvas(height,width,palette)

for x in xrange(0,16):
    for xs in xrange(0,xstep):
    	for y in xrange(0,data[x]-3):
        	canvas.addpixel((x*xstep)+xs,y,1)
    	for y in xrange(data[x]-3,data[x]):
        	canvas.addpixel((x*xstep)+xs,y,2)

f = open("demo.png","w")

canvas.write(f)

The code for ppypng.py is shown below.

ppypng.py
# pypng.py Converted to Python from Original C-Code by Niall McCarroll, 2008:
#
# This code was written by Martin Hinner <mhi@penguin.cz> in 2000,
# no copyright is claimed. This code is in the public domain;
# do with it what you wish.
#

from zlib import adler32, crc32
from math import log
   
class PNGCanvas:
    
    def __init__(self,height,width,palette):
        self.height = height
        self.width = width
        self.bits = 8
        self.data = [0 for i in xrange(0,width*height)]
        self.group_bytes=(self.width / (8 / self.bits) + 1)
        self.colors=(1 << self.bits)
        self.crc = 0L
        self.init_palette(palette)
        self.png_magic = "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A"

    def init_palette(self,palette):
        self.plt = []
        for (r,g,b) in palette:
            self.plt.append((r<<16)|(g<<8)|b)
	for p in xrange(0,256-len(palette)):
            self.plt.append(0)
    
    def addpixel(self,x,y,col):
	y = (self.height-1) - y
       	self.data[x+(y*self.width)] = col
        
    def pixel(self,x,y):
        return self.data[x+(y*self.width)]
        
    def write(self,file):

        i = 0
        j = 0
        k = 0
        zcrc = 0
        zero = 0xF1
        filter = 0
    
        self.fd = file

        self.resetcrc()
    
        self.fd.write(self.png_magic)
    
        self.beginchunk("IHDR", 0x0d)
        self.writelongcrc(self.width)            # width 
        self.writelongcrc(self.height)       # height 
        self.writebytecrc(self.bits)            # bit depth 
        self.writebytecrc(3)            # color type 
        self.writebytecrc(0)            # compression 
        self.writebytecrc(0)             # filter 
        self.writebytecrc(0)            # interlace 
        self.endchunk()
    
        self.beginchunk("PLTE", self.colors * 3);
        i = 0
        while i < self.colors:
            j = 0
            while j < 3:
                self.writebytecrc((self.plt[i] >> ((2 - j) * 8)) & 0xFF)
                j += 1
            i += 1       
        self.endchunk()
    
        self.beginchunk("IDAT", (self.height * (self.group_bytes + 4 + 1)) + 4 + 2)
        self.writewordcrc(((0x0800 + 30) / 31) * 31 )        # compression method 
    
        zcrc = 1L
        i = 0
        while i < self.height:
            if i == (self.height-1):    
                self.writebytecrc(0x01) 
            else:
                self.writebytecrc(0)
            self.Xwritewordcrc(self.group_bytes)
            self.Xwritewordcrc(~self.group_bytes)
         
            # write PNG row filter - 0 = unfiltered 
            zcrc = adler32(chr(filter),zcrc)
            self.writebytecrc(filter)
            
            k = 0
            while k < self.width:
                c = self.pixel(k,i)
                zcrc = adler32(chr(c),zcrc)
                self.writebytecrc(c)
                k += 1
                
            i += 1
    
        self.writelongcrc(zcrc)
        # print "zcrc = " + str(zcrc)
    
        self.endchunk()
    
        self.beginchunk("IEND", 0)
        self.endchunk()
    
        
        


    def resetcrc(self):
        self.crc = 0
    
    def writelong(self,l):
        self.fd.write(chr((l>>24)&0xFF))
        self.fd.write(chr((l>>16)&0xFF))
        self.fd.write(chr((l>>8)&0xFF))
        self.fd.write(chr(l&0xFF))
        
    def writelongcrc(self,l):
        c3 = chr((l>>24)&0xFF)
        c2 = chr((l>>16)&0xFF)
        c1 = chr((l>>8)&0xFF)
        c0 = chr(l&0xFF)
        
        self.crc = crc32(c3,self.crc)
        self.fd.write(c3)
        self.crc = crc32(c2,self.crc)
        self.fd.write(c2)
        self.crc = crc32(c1,self.crc)
        self.fd.write(c1)
        self.crc = crc32(c0,self.crc)
        self.fd.write(c0)
    
    
    def Xwritelongcrc(self,l):
        c3 = chr((l>>24)&0xFF)
        c2 = chr((l>>16)&0xFF)
        c1 = chr((l>>8)&0xFF)
        c0 = chr(l&0xFF)
        
        self.crc = crc32(c0,self.crc)
        self.fd.write(c0)
        self.crc = crc32(c1,self.crc)
        self.fd.write(c1)
        self.crc = crc32(c2,self.crc)
        self.fd.write(c2)
        self.crc = crc32(c3,self.crc)
        self.fd.write(c3)
    
    
    
    def writewordcrc(self,s):
    
        c1 = chr((s>>8)&0xFF)
        c0 = chr(s&0xFF)
       
        self.crc = crc32(c1,self.crc)
        self.fd.write(c1)
        self.crc = crc32(c0,self.crc)
        self.fd.write(c0)
    
    def Xwritewordcrc(self,s):
     
        c1 = chr((s>>8)&0xFF)
        c0 = chr(s&0xFF)
       
        self.crc = crc32(c0,self.crc)
        self.fd.write(c0)
        self.crc = crc32(c1,self.crc)
        self.fd.write(c1)
    
    def writebytecrc(self,c):
    
        self.crc = crc32(chr(c),self.crc)
        self.fd.write(chr(c))
    
    
    def beginchunk(self,name,len):
            
        self.writelong(len)
        self.resetcrc()
        self.crc = crc32(name,self.crc)
        self.fd.write(name)
    
    
    def endchunk(self):
       
        self.writelong(self.crc)

(Added December 2013) A python3 version of the code, ppypng_python3.py is included below.

ppypng_python3.py
# pypng_python3.py Converted to Python from Original C-Code by Niall McCarroll, 2008 (Ported to Python3 2013):
#
# This code was written by Martin Hinner <mhi@penguin.cz> in 2000,
# no copyright is claimed. This code is in the public domain;
# do with it what you wish.
#

from zlib import adler32, crc32
from math import log
   
class canvas:
    
    def __init__(self,height,width,palette):
        self.height = height
        self.width = width
        self.bits = 8
        self.data = [chr(0) for i in range(0,width*height)]
        self.group_bytes=int(self.width / (8 / self.bits) + 1)
        self.colors=(1 << self.bits)
        self.crc = 0
        self.init_palette(palette)
        self.png_magic = b"\x89\x50\x4E\x47\x0D\x0A\x1A\x0A"

    def init_palette(self,palette):
        self.plt = []
        for (r,g,b) in palette:
            self.plt.append((r<<16)|(g<<8)|b)
    
    def addpixel(self,x,y,col):
        self.data[x+(y*self.width)] = col
        
    def pixel(self,x,y):
        return self.data[x+(y*self.width)]
        
    def write(self,file):

        i = 0
        j = 0
        k = 0
        zcrc = 0
        zero = 0xF1
        filter = 0
    
        self.fd = file

        self.resetcrc()
    
        self.fd.write(self.png_magic)
    
        self.beginchunk(b"IHDR", 0x0d)
        self.writelongcrc(self.width)	    # width 
        self.writelongcrc(self.height)       # height 
        self.writebytecrc(self.bits)	    # bit depth 
        self.writebytecrc(3)	    # color type 
        self.writebytecrc(0)	    # compression 
        self.writebytecrc(0) 	    # filter 
        self.writebytecrc(0)	    # interlace 
        self.endchunk()
    
        self.beginchunk(b"PLTE", self.colors * 3);
        i = 0
        while i < self.colors:
            j = 0
            while j < 3:
                self.writebytecrc((self.plt[i] >> ((2 - j) * 8)) & 0xFF)
                j += 1
            i += 1       
        self.endchunk()
    
        self.beginchunk(b"IDAT", (self.height * (self.group_bytes + 4 + 1)) + 4 + 2)
        self.writewordcrc(((0x0800 + 30) // 31) * 31 )	# compression method 
    
        zcrc = 1
        i = 0
        while i < self.height:
            if i == (self.height-1):    
                self.writebytecrc(0x01) 
            else:
                self.writebytecrc(0)
            self.Xwritewordcrc(self.group_bytes)
            self.Xwritewordcrc(~self.group_bytes)
         
            # write PNG row filter - 0 = unfiltered 
            zcrc = adler32(bytes([filter]),zcrc)
            self.writebytecrc(filter)
            
            k = 0
            while k < self.width:
                c = self.pixel(k,i)
                zcrc = adler32(bytes([c]),zcrc)
                self.writebytecrc(c)
                k += 1
                
            i += 1
    
        self.writelongcrc(zcrc)
        # print "zcrc = " + str(zcrc)
    
        self.endchunk()
    
        self.beginchunk(b"IEND", 0)
        self.endchunk()
    
    def resetcrc(self):
        self.crc = 0
    
    def writelong(self,l):
        self.fd.write(bytes([(l>>24)&0xFF,(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF]))
        
    def writelongcrc(self,l):
        c3 = bytes([(l>>24)&0xFF])
        c2 = bytes([(l>>16)&0xFF])
        c1 = bytes([(l>>8)&0xFF])
        c0 = bytes([l&0xFF])
        
        self.crc = crc32(c3,self.crc)
        self.fd.write(c3)
        self.crc = crc32(c2,self.crc)
        self.fd.write(c2)
        self.crc = crc32(c1,self.crc)
        self.fd.write(c1)
        self.crc = crc32(c0,self.crc)
        self.fd.write(c0)
    
    
    def Xwritelongcrc(self,l):
        c3 = bytes([(l>>24)&0xFF])
        c2 = bytes([(l>>16)&0xFF])
        c1 = bytes([(l>>8)&0xFF])
        c0 = bytes([l&0xFF])
        
        self.crc = crc32(c0,self.crc)
        self.fd.write(c0)
        self.crc = crc32(c1,self.crc)
        self.fd.write(c1)
        self.crc = crc32(c2,self.crc)
        self.fd.write(c2)
        self.crc = crc32(c3,self.crc)
        self.fd.write(c3)
    
    
    
    def writewordcrc(self,s):
    
        c1 = bytes([(s>>8)&0xFF])
        c0 = bytes([s&0xFF])
       
        self.crc = crc32(c1,self.crc)
        self.fd.write(c1)
        self.crc = crc32(c0,self.crc)
        self.fd.write(c0)
    
    def Xwritewordcrc(self,s):
     
        c1 = bytes([(s>>8)&0xFF])
        c0 = bytes([s&0xFF])
       
        self.crc = crc32(c0,self.crc)
        self.fd.write(c0)
        self.crc = crc32(c1,self.crc)
        self.fd.write(c1)
    
    def writebytecrc(self,c):
        c = bytes([c])
        self.crc = crc32(c,self.crc)
        self.fd.write(c)
    
    
    def beginchunk(self,name,len):
            
        self.writelong(len)
        self.resetcrc()
        self.crc = crc32(name,self.crc)
        self.fd.write(name)
    
    
    def endchunk(self):
       
        self.writelong(self.crc)
 
Anti-spam check
Leave a comment