#!/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"] import os, 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 def blend(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 def intensity(c,i): return [c[0],c[1],c[2],(c[3]*i) >> 8] # compute perceptive grayscale value def grayscale(c): return int(c[0]*0.3 + c[1]*0.59 + c[2]*0.11) # compute gradient colors def gradientList(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)]) return grad class PNGCanvas: 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, x0 if y0 > y1: y0, y1 = y1, y0 return [x0,y0,x1,y1] def verticalGradient(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]) def rectangle(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]]) def filledRectangle(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) def copyRect(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 def line(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-x0 if dx < 0: sx = -1 else: sx = 1 dx *= sx dy = y1-y0 # 'easy' cases if dy == 0: for x in range(x0,x1,sx): self.point(x, y0) return if dx == 0: for y in range(y0,y1): self.point(x0, y) self.point(x1, y1) return if dx == dy: for x in range(x0,x1,sx): self.point(x, y0) y0 = y0 + 1 return # main loop self.point(x0, y0) e_acc = 0 if dy > dx: # vertical displacement e = (dx << 16) / dy for 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 + 1 self.point(x0 + sx, y0, intensity(self.color,(0xFF-w))) self.point(x1, y1) return # horizontal displacement e = (dy << 16) / dx for 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 + 1 w = 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) def polyline(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]) def dump(self): scanlines = bytearray() for y in range(self.height): scanlines.append('