#!/usr/bin/env python
"""Simple PNG Canvas for Python - updated for bytearray()"""
__version__ = "1.0.1"
__author__ = "Rui Carmo (http://the.taoofmac.com)"
__copyright__ = "CC Attribution-NonCommercial-NoDerivs 2.0 Rui Carmo"
__contributors__ = ["http://collaboa.weed.rbse.com/repository/file/branches/pgsql/lib/spark_pr.rb"], ["Eli Bendersky"]importos, sys, zlib, struct
signature= struct.pack("8B", 137, 80, 78, 71, 13, 10, 26, 10)#alpha blends two colors, using the alpha given by c2
defblend(c1, c2):return [c1[i]*(0xFF-c2[3]) + c2[i]*c2[3] >> 8 for i in range(3)]#compute a new alpha given a 0-0xFF intensity
defintensity(c,i):return [c[0],c[1],c[2],(c[3]*i) >> 8]#compute perceptive grayscale value
defgrayscale(c):return int(c[0]*0.3 + c[1]*0.59 + c[2]*0.11)#compute gradient colors
defgradientList(start,end,steps):
delta= [end[i] - start[i] for i in range(4)]
grad=[]for i in range(steps+1):
grad.append([start[j]+ (delta[j]*i)/steps for j in range(4)])returngradclassPNGCanvas:def __init__(self, width, height, bgcolor=bytearray([0xff,0xff,0xff,0xff]),color=bytearray([0,0,0,0xff])):
self.width=width
self.height=height
self.color= color #rgba
self.bgcolor =bgcolor
self.canvas= bytearray(self.bgcolor * 4 * width *height)def_offset(self, x, y):return y * self.width * 4 + x * 4
def point(self,x,y,color=None):if x<0 or y<0 or x>self.width-1 or y>self.height-1: return
if color ==None:
color=self.color
o=self._offset(x,y)
self.canvas[o:o+3] = blend(self.canvas[o:o+3],bytearray(color))def_rectHelper(self,x0,y0,x1,y1):
x0, y0, x1, y1=int(x0), int(y0), int(x1), int(y1)if x0 > x1: x0, x1 =x1, x0if y0 > y1: y0, y1 =y1, y0return[x0,y0,x1,y1]defverticalGradient(self,x0,y0,x1,y1,start,end):
x0, y0, x1, y1=self._rectHelper(x0,y0,x1,y1)
grad= gradientList(start,end,y1-y0)for x in range(x0, x1+1):for y in range(y0, y1+1):
self.point(x,y,grad[y-y0])defrectangle(self,x0,y0,x1,y1):
x0, y0, x1, y1=self._rectHelper(x0,y0,x1,y1)
self.polyline([[x0,y0],[x1,y0],[x1,y1],[x0,y1],[x0,y0]])deffilledRectangle(self,x0,y0,x1,y1):
x0, y0, x1, y1=self._rectHelper(x0,y0,x1,y1)for x in range(x0, x1+1):for y in range(y0, y1+1):
self.point(x,y,self.color)defcopyRect(self,x0,y0,x1,y1,dx,dy,destination):
x0, y0, x1, y1=self._rectHelper(x0,y0,x1,y1)for x in range(x0, x1+1):for y in range(y0, y1+1):
d= destination._offset(dx+x-x0,dy+y-y0)
o=self._offset(x,y)
destination.canvas[d:d+4] = self.canvas[o:o+4]def blendRect(self,x0,y0,x1,y1,dx,dy,destination,alpha=0xff):
x0, y0, x1, y1=self._rectHelper(x0,y0,x1,y1)for x in range(x0, x1+1):for y in range(y0, y1+1):
o=self._offset(x,y)
rgba= self.canvas[o:o+4]
rgba[3] =alpha
destination.point(dx+x-x0,dy+y-y0,rgba)#draw a line using Xiaolin Wu's antialiasing technique
defline(self,x0, y0, x1, y1):#clean params
x0, y0, x1, y1 =int(x0), int(y0), int(x1), int(y1)if y0>y1:
y0, y1, x0, x1=y1, y0, x1, x0
dx= x1-x0if dx <0:
sx= -1
else:
sx= 1dx*=sx
dy= y1-y0#'easy' cases
if dy ==0:for x inrange(x0,x1,sx):
self.point(x, y0)return
if dx ==0:for y inrange(y0,y1):
self.point(x0, y)
self.point(x1, y1)return
if dx ==dy:for x inrange(x0,x1,sx):
self.point(x, y0)
y0= y0 + 1
return
#main loop
self.point(x0, y0)
e_acc=0if dy > dx: #vertical displacement
e = (dx << 16) /dyfor i in range(y0,y1-1):
e_acc_temp, e_acc= e_acc, (e_acc + e) & 0xFFFF
if (e_acc <=e_acc_temp):
x0= x0 +sx
w= 0xFF-(e_acc >> 8)
self.point(x0, y0, intensity(self.color,(w)))
y0= y0 + 1self.point(x0+ sx, y0, intensity(self.color,(0xFF-w)))
self.point(x1, y1)return
#horizontal displacement
e = (dy << 16) /dxfor i in range(x0,x1-sx,sx):
e_acc_temp, e_acc= e_acc, (e_acc + e) & 0xFFFF
if (e_acc <=e_acc_temp):
y0= y0 + 1w= 0xFF-(e_acc >> 8)
self.point(x0, y0, intensity(self.color,(w)))
x0= x0 +sx
self.point(x0, y0+ 1, intensity(self.color,(0xFF-w)))
self.point(x1, y1)defpolyline(self,arr):for i in range(0,len(arr)-1):
self.line(arr[i][0],arr[i][1],arr[i+1][0], arr[i+1][1])defdump(self):
scanlines=bytearray()for y inrange(self.height):
scanlines.append('\0') #filter type 0 (None)
#print y * self.width * 4, (y+1) * self.width * 4
#print self.canvas[y * self.width * 4:(y+1) * self.width * 4]
scanlines.extend(self.canvas[(y * self.width * 4):((y+1) * self.width * 4)])#image represented as RGBA tuples, no interlacing
return signature +\
self.pack_chunk('IHDR', struct.pack("!2I5B",self.width,self.height,8,6,0,0,0)) +\
self.pack_chunk('IDAT', zlib.compress(str(scanlines),9)) +\
self.pack_chunk('IEND', '')defpack_chunk(self,tag,data):
to_check= tag +datareturn struct.pack("!I",len(data)) + to_check + struct.pack("!I", zlib.crc32(to_check) & 0xFFFFFFFF)defload(self,f):assert f.read(8) ==signaturefor tag, data inself.chunks(f):if tag == "IHDR":
( width,
height,
bitdepth,
colortype,
compression, filter, interlace )= struct.unpack("!2I5B",data)
self.width=width
self.height=height
self.canvas= bytearray(self.bgcolor * 4 * width *height)if (bitdepth,colortype,compression, filter, interlace) != (8,6,0,0,0):raise TypeError('Unsupported PNG format')#we ignore tRNS for the moment
elif tag == 'IDAT':
raw_data=zlib.decompress(data)
rows=[]
i=0for y inrange(height):
filtertype=ord(raw_data[i])
i= i + 1cur= [ord(x) for x in raw_data[i:i+width*4]]if y ==0:
rgba= self.defilter(cur,None,filtertype,4)else:
rgba= self.defilter(cur,prev,filtertype,4)
prev=cur
i= i + width * 4row=[]
j=0for x inrange(width):
self.point(x,y,rgba[j:j+4])
j= j + 4
def defilter(self,cur,prev,filtertype,bpp=3):if filtertype == 0: #No filter
returncurelif filtertype == 1: #Sub
xp =0for xc inrange(bpp,len(cur)):
cur[xc]= (cur[xc] + cur[xp]) % 256xp= xp + 1
elif filtertype == 2: #Up
for xc inrange(len(cur)):
cur[xc]= (cur[xc] + prev[xc]) % 256
elif filtertype == 3: #Average
xp =0for xc inrange(len(cur)):
cur[xc]= (cur[xc] + (cur[xp] + prev[xc])/2) % 256xp= xp + 1
elif filtertype == 4: #Paeth
xp =0for i inrange(bpp):
cur[i]= (cur[i] + prev[i]) % 256
for xc inrange(bpp,len(cur)):
a=cur[xp]
b=prev[xc]
c=prev[xp]
p= a + b -c
pa= abs(p -a)
pb= abs(p -b)
pc= abs(p -c)if pa <= pb and pa <=pc:
value=aelif pb <=pc:
value=belse:
value=c
cur[xc]= (cur[xc] + value) % 256xp= xp + 1
else:raise TypeError('Unrecognized scanline filter type')returncurdefchunks(self,f):while 1:try:
length= struct.unpack("!I",f.read(4))[0]
tag= f.read(4)
data=f.read(length)
crc= struct.unpack("!i",f.read(4))[0]except:return
if zlib.crc32(tag + data) !=crc:raiseIOErroryield[tag,data]if __name__ == '__main__':
width= 512height= 512
print "Creating Canvas..."c=PNGCanvas(width,height)
c.color= bytearray([0xff,0,0,0xff])
c.rectangle(0,0,width-1,height-1)print "Generating Gradient..."c.verticalGradient(1,1,width-2, height-2,[0xff,0,0,0xff],[0x20,0,0xff,0x80])print "Drawing Lines..."c.color= [0,0,0,0xff]
c.line(0,0,width-1,height-1)
c.line(0,0,width/2,height-1)
c.line(0,0,width-1,height/2)#Copy Rect to Self
print "Copy Rect"c.copyRect(1,1,width/2-1,height/2-1,1,height/2,c)#Blend Rect to Self
print "Blend Rect"c.blendRect(1,1,width/2-1,height/2-1,width/2,0,c)#Write test
print "Writing to file..."f= open("test.png", "wb")
f.write(c.dump())
f.close()#Read test
print "Reading from file..."f= open("test.png", "rb")
c.load(f)
f.close()#Write back
print "Writing to new file..."f= open("recycle.png","wb")
f.write(c.dump())
f.close()