PageRenderTime 47ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/items/pngcanvas.py

https://github.com/flaxter/CityRank
Python | 292 lines | 260 code | 21 blank | 11 comment | 37 complexity | 5e817006fe497957315d10ea9401aa11 MD5 | raw file
  1. #!/usr/bin/env python
  2. """Simple PNG Canvas for Python"""
  3. __version__ = "0.8"
  4. __author__ = "Rui Carmo (http://the.taoofmac.com)"
  5. __copyright__ = "CC Attribution-NonCommercial-NoDerivs 2.0 Rui Carmo"
  6. __contributors__ = ["http://collaboa.weed.rbse.com/repository/file/branches/pgsql/lib/spark_pr.rb"], ["Eli Bendersky"]
  7. import zlib, struct
  8. signature = struct.pack("8B", 137, 80, 78, 71, 13, 10, 26, 10)
  9. # alpha blends two colors, using the alpha given by c2
  10. def blend(c1, c2):
  11. return [c1[i]*(0xFF-c2[3]) + c2[i]*c2[3] >> 8 for i in range(3)]
  12. # calculate a new alpha given a 0-0xFF intensity
  13. def intensity(c,i):
  14. return [c[0],c[1],c[2],(c[3]*i) >> 8]
  15. # calculate perceptive grayscale value
  16. def grayscale(c):
  17. return int(c[0]*0.3 + c[1]*0.59 + c[2]*0.11)
  18. # calculate gradient colors
  19. def gradientList(start,end,steps):
  20. delta = [end[i] - start[i] for i in range(4)]
  21. grad = []
  22. for i in range(steps+1):
  23. grad.append([start[j] + (delta[j]*i)/steps for j in range(4)])
  24. return grad
  25. class PNGCanvas:
  26. def __init__(self, width, height,bgcolor=[0xff,0xff,0xff,0xff],color=[0,0,0,0xff]):
  27. self.canvas = []
  28. self.width = width
  29. self.height = height
  30. self.color = color #rgba
  31. bgcolor = bgcolor[0:3] # we don't need alpha for background
  32. for i in range(height):
  33. self.canvas.append([bgcolor] * width)
  34. def point(self,x,y,color=None):
  35. if x<0 or y<0 or x>self.width-1 or y>self.height-1: return
  36. if color == None: color = self.color
  37. self.canvas[y][x] = blend(self.canvas[y][x],color)
  38. def _rectHelper(self,x0,y0,x1,y1):
  39. x0, y0, x1, y1 = int(x0), int(y0), int(x1), int(y1)
  40. if x0 > x1: x0, x1 = x1, x0
  41. if y0 > y1: y0, y1 = y1, y0
  42. return [x0,y0,x1,y1]
  43. def verticalGradient(self,x0,y0,x1,y1,start,end):
  44. x0, y0, x1, y1 = self._rectHelper(x0,y0,x1,y1)
  45. grad = gradientList(start,end,y1-y0)
  46. for x in range(x0, x1+1):
  47. for y in range(y0, y1+1):
  48. self.point(x,y,grad[y-y0])
  49. def rectangle(self,x0,y0,x1,y1):
  50. x0, y0, x1, y1 = self._rectHelper(x0,y0,x1,y1)
  51. self.polyline([[x0,y0],[x1,y0],[x1,y1],[x0,y1],[x0,y0]])
  52. def filledRectangle(self,x0,y0,x1,y1):
  53. x0, y0, x1, y1 = self._rectHelper(x0,y0,x1,y1)
  54. for x in range(x0, x1+1):
  55. for y in range(y0, y1+1):
  56. self.point(x,y,self.color)
  57. def copyRect(self,x0,y0,x1,y1,dx,dy,destination):
  58. x0, y0, x1, y1 = self._rectHelper(x0,y0,x1,y1)
  59. for x in range(x0, x1+1):
  60. for y in range(y0, y1+1):
  61. destination.canvas[dy+y-y0][dx+x-x0] = self.canvas[y][x]
  62. def blendRect(self,x0,y0,x1,y1,dx,dy,destination,alpha=0xff):
  63. x0, y0, x1, y1 = self._rectHelper(x0,y0,x1,y1)
  64. for x in range(x0, x1+1):
  65. for y in range(y0, y1+1):
  66. rgba = self.canvas[y][x] + [alpha]
  67. destination.point(dx+x-x0,dy+y-y0,rgba)
  68. # draw a line using Xiaolin Wu's antialiasing technique
  69. def line(self,x0, y0, x1, y1):
  70. # clean params
  71. x0, y0, x1, y1 = int(x0), int(y0), int(x1), int(y1)
  72. if y0>y1:
  73. y0, y1, x0, x1 = y1, y0, x1, x0
  74. dx = x1-x0
  75. if dx < 0:
  76. sx = -1
  77. else:
  78. sx = 1
  79. dx *= sx
  80. dy = y1-y0
  81. # 'easy' cases
  82. if dy == 0:
  83. for x in range(x0,x1,sx):
  84. self.point(x, y0)
  85. return
  86. if dx == 0:
  87. for y in range(y0,y1):
  88. self.point(x0, y)
  89. self.point(x1, y1)
  90. return
  91. if dx == dy:
  92. for x in range(x0,x1,sx):
  93. self.point(x, y0)
  94. y0 = y0 + 1
  95. return
  96. # main loop
  97. self.point(x0, y0)
  98. e_acc = 0
  99. if dy > dx: # vertical displacement
  100. e = (dx << 16) / dy
  101. for i in range(y0,y1-1):
  102. e_acc_temp, e_acc = e_acc, (e_acc + e) & 0xFFFF
  103. if (e_acc <= e_acc_temp):
  104. x0 = x0 + sx
  105. w = 0xFF-(e_acc >> 8)
  106. self.point(x0, y0, intensity(self.color,(w)))
  107. y0 = y0 + 1
  108. self.point(x0 + sx, y0, intensity(self.color,(0xFF-w)))
  109. self.point(x1, y1)
  110. return
  111. # horizontal displacement
  112. e = (dy << 16) / dx
  113. for i in range(x0,x1-sx,sx):
  114. e_acc_temp, e_acc = e_acc, (e_acc + e) & 0xFFFF
  115. if (e_acc <= e_acc_temp):
  116. y0 = y0 + 1
  117. w = 0xFF-(e_acc >> 8)
  118. self.point(x0, y0, intensity(self.color,(w)))
  119. x0 = x0 + sx
  120. self.point(x0, y0 + 1, intensity(self.color,(0xFF-w)))
  121. self.point(x1, y1)
  122. def polyline(self,arr):
  123. for i in range(0,len(arr)-1):
  124. self.line(arr[i][0],arr[i][1],arr[i+1][0], arr[i+1][1])
  125. def dump(self):
  126. raw_list = []
  127. for y in range(self.height):
  128. raw_list.append(chr(0)) # filter type 0 (None)
  129. for x in range(self.width):
  130. raw_list.append(struct.pack("!3B",*self.canvas[y][x]))
  131. raw_data = ''.join(raw_list)
  132. # 8-bit image represented as RGB tuples
  133. # simple transparency, alpha is pure white
  134. return signature + \
  135. self.pack_chunk('IHDR', struct.pack("!2I5B",self.width,self.height,8,2,0,0,0)) + \
  136. self.pack_chunk('tRNS', struct.pack("!6B",0xFF,0xFF,0xFF,0xFF,0xFF,0xFF)) + \
  137. self.pack_chunk('IDAT', zlib.compress(raw_data,9)) + \
  138. self.pack_chunk('IEND', '')
  139. def pack_chunk(self,tag,data):
  140. to_check = tag + data
  141. return struct.pack("!I",len(data)) + to_check + struct.pack("!I", zlib.crc32(to_check) & 0xFFFFFFFF)
  142. def load(self,f):
  143. assert f.read(8) == signature
  144. self.canvas=[]
  145. for tag, data in self.chunks(f):
  146. if tag == "IHDR":
  147. ( width,
  148. height,
  149. bitdepth,
  150. colortype,
  151. compression, filter, interlace ) = struct.unpack("!2I5B",data)
  152. self.width = width
  153. self.height = height
  154. if (bitdepth,colortype,compression, filter, interlace) != (8,2,0,0,0):
  155. raise TypeError('Unsupported PNG format')
  156. # we ignore tRNS because we use pure white as alpha anyway
  157. elif tag == 'IDAT':
  158. raw_data = zlib.decompress(data)
  159. rows = []
  160. i = 0
  161. for y in range(height):
  162. filtertype = ord(raw_data[i])
  163. i = i + 1
  164. cur = [ord(x) for x in raw_data[i:i+width*3]]
  165. if y == 0:
  166. rgb = self.defilter(cur,None,filtertype)
  167. else:
  168. rgb = self.defilter(cur,prev,filtertype)
  169. prev = cur
  170. i = i+width*3
  171. row = []
  172. j = 0
  173. for x in range(width):
  174. pixel = rgb[j:j+3]
  175. row.append(pixel)
  176. j = j + 3
  177. self.canvas.append(row)
  178. def defilter(self,cur,prev,filtertype,bpp=3):
  179. if filtertype == 0: # No filter
  180. return cur
  181. elif filtertype == 1: # Sub
  182. xp = 0
  183. for xc in range(bpp,len(cur)):
  184. cur[xc] = (cur[xc] + cur[xp]) % 256
  185. xp = xp + 1
  186. elif filtertype == 2: # Up
  187. for xc in range(len(cur)):
  188. cur[xc] = (cur[xc] + prev[xc]) % 256
  189. elif filtertype == 3: # Average
  190. xp = 0
  191. for xc in range(len(cur)):
  192. cur[xc] = (cur[xc] + (cur[xp] + prev[xc])/2) % 256
  193. xp = xp + 1
  194. elif filtertype == 4: # Paeth
  195. xp = 0
  196. for i in range(bpp):
  197. cur[i] = (cur[i] + prev[i]) % 256
  198. for xc in range(bpp,len(cur)):
  199. a = cur[xp]
  200. b = prev[xc]
  201. c = prev[xp]
  202. p = a + b - c
  203. pa = abs(p - a)
  204. pb = abs(p - b)
  205. pc = abs(p - c)
  206. if pa <= pb and pa <= pc:
  207. value = a
  208. elif pb <= pc:
  209. value = b
  210. else:
  211. value = c
  212. cur[xc] = (cur[xc] + value) % 256
  213. xp = xp + 1
  214. else:
  215. raise TypeError('Unrecognized scanline filter type')
  216. return cur
  217. def chunks(self,f):
  218. while 1:
  219. try:
  220. length = struct.unpack("!I",f.read(4))[0]
  221. tag = f.read(4)
  222. data = f.read(length)
  223. crc = struct.unpack("!i",f.read(4))[0]
  224. except:
  225. return
  226. #if zlib.crc32(tag + data) != crc:
  227. # raise IOError
  228. yield [tag,data]
  229. if __name__ == '__main__':
  230. width = 128
  231. height = 64
  232. print "Creating Canvas..."
  233. c = PNGCanvas(width,height)
  234. c.color = [0xff,0,0,0xff]
  235. c.rectangle(0,0,width-1,height-1)
  236. print "Generating Gradient..."
  237. c.verticalGradient(1,1,width-2, height-2,[0xff,0,0,0xff],[0x20,0,0xff,0x80])
  238. print "Drawing Lines..."
  239. c.color = [0,0,0,0xff]
  240. c.line(0,0,width-1,height-1)
  241. c.line(0,0,width/2,height-1)
  242. c.line(0,0,width-1,height/2)
  243. # Copy Rect to Self
  244. print "Copy Rect"
  245. c.copyRect(1,1,width/2-1,height/2-1,0,height/2,c)
  246. # Blend Rect to Self
  247. print "Blend Rect"
  248. c.blendRect(1,1,width/2-1,height/2-1,width/2,0,c)
  249. # Write test
  250. print "Writing to file..."
  251. f = open("test.png", "wb")
  252. f.write(c.dump())
  253. f.close()
  254. # Read test
  255. print "Reading from file..."
  256. f = open("/var/www/media/handle.png", "rb")
  257. d = PNGCanvas(12,12)
  258. d.load(f)
  259. d.copyRect(1,1,6,6,0,height/2,c)
  260. f.close()
  261. # Write back
  262. print "Writing to new file..."
  263. f = open("recycle.png","wb")
  264. f.write(c.dump())
  265. f.close()