PageRenderTime 46ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 1ms

/gourmet-0.15.9/src/lib/PngImagePluginUpToDate.py

#
Python | 540 lines | 433 code | 59 blank | 48 comment | 20 complexity | 3c2142736f9e87dfa17e443f9dfdd783 MD5 | raw file
  1. #
  2. # The Python Imaging Library.
  3. #
  4. # THIS IS A UGLY, UGLY HACK! WE GRAB THIS FILE FROM THE PYTHON IMAGING
  5. # LIBRARY 1.5b AND MAKE A FEW UGLY CHANGES TO ALLOW SAVING PNG'S PROPERLY
  6. # WITHOUT DEPENDING ON THE BETA PIL.
  7. #
  8. #
  9. # PNG support code
  10. #
  11. # See "PNG (Portable Network Graphics) Specification, version 1.0;
  12. # W3C Recommendation", 1996-10-01, Thomas Boutell (ed.).
  13. #
  14. # history:
  15. # 1996-05-06 fl Created (couldn't resist it)
  16. # 1996-12-14 fl Upgraded, added read and verify support (0.2)
  17. # 1996-12-15 fl Separate PNG stream parser
  18. # 1996-12-29 fl Added write support, added getchunks
  19. # 1996-12-30 fl Eliminated circular references in decoder (0.3)
  20. # 1998-07-12 fl Read/write 16-bit images as mode I (0.4)
  21. # 2001-02-08 fl Added transparency support (from Zircon) (0.5)
  22. # 2001-04-16 fl Don't close data source in "open" method (0.6)
  23. # 2004-02-24 fl Don't even pretend to support interlaced files (0.7)
  24. # 2004-08-31 fl Do basic sanity check on chunk identifiers (0.8)
  25. # 2004-09-20 fl Added PngInfo chunk container
  26. #
  27. # Copyright (c) 1997-2004 by Secret Labs AB
  28. # Copyright (c) 1996 by Fredrik Lundh
  29. #
  30. # See the README file for information on usage and redistribution.
  31. #
  32. __version__ = "0.8"
  33. import re, string
  34. import Image, ImageFile, ImagePalette
  35. def i16(c):
  36. return ord(c[1]) + (ord(c[0])<<8)
  37. def i32(c):
  38. return ord(c[3]) + (ord(c[2])<<8) + (ord(c[1])<<16) + (ord(c[0])<<24)
  39. is_cid = re.compile("\w\w\w\w").match
  40. _MAGIC = "\211PNG\r\n\032\n"
  41. _MODES = {
  42. # supported bits/color combinations, and corresponding modes/rawmodes
  43. (1, 0): ("1", "1"),
  44. (2, 0): ("L", "L;2"),
  45. (4, 0): ("L", "L;4"),
  46. (8, 0): ("L", "L"),
  47. (16,0): ("I", "I;16B"),
  48. (8, 2): ("RGB", "RGB"),
  49. (16,2): ("RGB", "RGB;16B"),
  50. (1, 3): ("P", "P;1"),
  51. (2, 3): ("P", "P;2"),
  52. (4, 3): ("P", "P;4"),
  53. (8, 3): ("P", "P"),
  54. (8, 4): ("LA", "LA"),
  55. (16,4): ("RGBA", "LA;16B"), # LA;16B->LA not yet available
  56. (8, 6): ("RGBA", "RGBA"),
  57. (16,6): ("RGBA", "RGBA;16B"),
  58. }
  59. # --------------------------------------------------------------------
  60. # Support classes. Suitable for PNG and related formats like MNG etc.
  61. class ChunkStream:
  62. def __init__(self, fp):
  63. self.fp = fp
  64. self.queue = []
  65. if not hasattr(Image.core, "crc32"):
  66. self.crc = self.crc_skip
  67. def read(self):
  68. "Fetch a new chunk. Returns header information."
  69. if self.queue:
  70. cid, pos, len = self.queue[-1]
  71. del self.queue[-1]
  72. self.fp.seek(pos)
  73. else:
  74. s = self.fp.read(8)
  75. cid = s[4:]
  76. pos = self.fp.tell()
  77. len = i32(s)
  78. if not is_cid(cid):
  79. raise SyntaxError, "broken PNG file (chunk %s)" % repr(cid)
  80. return cid, pos, len
  81. def close(self):
  82. self.queue = self.crc = self.fp = None
  83. def push(self, cid, pos, len):
  84. self.queue.append((cid, pos, len))
  85. def call(self, cid, pos, len):
  86. "Call the appropriate chunk handler"
  87. if Image.DEBUG:
  88. print "STREAM", cid, pos, len
  89. return getattr(self, "chunk_" + cid)(pos, len)
  90. def crc(self, cid, data):
  91. "Read and verify checksum"
  92. crc1 = Image.core.crc32(data, Image.core.crc32(cid))
  93. crc2 = i16(self.fp.read(2)), i16(self.fp.read(2))
  94. if crc1 != crc2:
  95. raise SyntaxError, "broken PNG file"\
  96. "(bad header checksum in %s)" % cid
  97. def crc_skip(self, cid, data):
  98. "Read checksum. Used if the C module is not present"
  99. self.fp.read(4)
  100. def verify(self, endchunk = "IEND"):
  101. # Simple approach; just calculate checksum for all remaining
  102. # blocks. Must be called directly after open.
  103. cids = []
  104. while 1:
  105. cid, pos, len = self.read()
  106. if cid == endchunk:
  107. break
  108. self.crc(cid, ImageFile._safe_read(self.fp, len))
  109. cids.append(cid)
  110. return cids
  111. # --------------------------------------------------------------------
  112. # PNG chunk container (for use with save(pnginfo=))
  113. class PngInfo:
  114. def __init__(self):
  115. self.chunks = []
  116. def add(self, cid, data):
  117. self.chunks.append((cid, data))
  118. def add_text(self, key, value, zip=0):
  119. if zip:
  120. import zlib
  121. self.add("zTXt", key + "\0\0" + zlib.compress(value))
  122. else:
  123. self.add("tEXt", key + "\0" + value)
  124. # --------------------------------------------------------------------
  125. # PNG image stream (IHDR/IEND)
  126. class PngStream(ChunkStream):
  127. def __init__(self, fp):
  128. ChunkStream.__init__(self, fp)
  129. # local copies of Image attributes
  130. self.im_info = {}
  131. self.im_size = (0,0)
  132. self.im_mode = None
  133. self.im_tile = None
  134. self.im_palette = None
  135. def chunk_IHDR(self, pos, len):
  136. # image header
  137. s = ImageFile._safe_read(self.fp, len)
  138. self.im_size = i32(s), i32(s[4:])
  139. try:
  140. self.im_mode, self.im_rawmode = _MODES[(ord(s[8]), ord(s[9]))]
  141. except:
  142. pass
  143. if ord(s[12]):
  144. self.im_info["interlace"] = 1
  145. if ord(s[11]):
  146. raise SyntaxError, "unknown filter category"
  147. return s
  148. def chunk_IDAT(self, pos, len):
  149. # image data
  150. self.im_tile = [("zip", (0,0)+self.im_size, pos, self.im_rawmode)]
  151. self.im_idat = len
  152. raise EOFError
  153. def chunk_IEND(self, pos, len):
  154. # end of PNG image
  155. raise EOFError
  156. def chunk_PLTE(self, pos, len):
  157. # palette
  158. s = ImageFile._safe_read(self.fp, len)
  159. if self.im_mode == "P":
  160. self.im_palette = "RGB", s
  161. return s
  162. def chunk_tRNS(self, pos, len):
  163. # transparency
  164. s = ImageFile._safe_read(self.fp, len)
  165. if self.im_mode == "P":
  166. i = string.find(s, chr(0))
  167. if i >= 0:
  168. self.im_info["transparency"] = i
  169. elif self.im_mode == "L":
  170. self.im_info["transparency"] = i16(s)
  171. return s
  172. def chunk_gAMA(self, pos, len):
  173. # gamma setting
  174. s = ImageFile._safe_read(self.fp, len)
  175. self.im_info["gamma"] = i32(s) / 100000.0
  176. return s
  177. def chunk_tEXt(self, pos, len):
  178. # text
  179. s = ImageFile._safe_read(self.fp, len)
  180. try:
  181. k, v = string.split(s, "\0", 1)
  182. except ValueError:
  183. k = s; v = "" # fallback for broken tEXt tags
  184. if k:
  185. self.im_info[k] = v
  186. return s
  187. # --------------------------------------------------------------------
  188. # PNG reader
  189. def _accept(prefix):
  190. return prefix[:8] == _MAGIC
  191. ##
  192. # Image plugin for PNG images.
  193. class PngImageFile(ImageFile.ImageFile):
  194. format = "PNG"
  195. format_description = "Portable network graphics"
  196. def _open(self):
  197. if self.fp.read(8) != _MAGIC:
  198. raise SyntaxError, "not a PNG file"
  199. #
  200. # Parse headers up to the first IDAT chunk
  201. self.png = PngStream(self.fp)
  202. while 1:
  203. #
  204. # get next chunk
  205. cid, pos, len = self.png.read()
  206. try:
  207. s = self.png.call(cid, pos, len)
  208. except EOFError:
  209. break
  210. except AttributeError:
  211. if Image.DEBUG:
  212. print cid, pos, len, "(unknown)"
  213. s = ImageFile._safe_read(self.fp, len)
  214. self.png.crc(cid, s)
  215. #
  216. # Copy relevant attributes from the PngStream. An alternative
  217. # would be to let the PngStream class modify these attributes
  218. # directly, but that introduces circular references which are
  219. # difficult to break if things go wrong in the decoder...
  220. # (believe me, I've tried ;-)
  221. self.mode = self.png.im_mode
  222. self.size = self.png.im_size
  223. self.info = self.png.im_info
  224. self.tile = self.png.im_tile
  225. if self.png.im_palette:
  226. rawmode, data = self.png.im_palette
  227. self.palette = ImagePalette.raw(rawmode, data)
  228. self.__idat = len # used by load_read()
  229. def verify(self):
  230. "Verify PNG file"
  231. if self.fp is None:
  232. raise RuntimeError("verify must be called directly after open")
  233. # back up to beginning of IDAT block
  234. self.fp.seek(self.tile[0][2] - 8)
  235. self.png.verify()
  236. self.png.close()
  237. self.fp = None
  238. def load_prepare(self):
  239. "internal: prepare to read PNG file"
  240. if self.info.get("interlace"):
  241. raise IOError("cannot read interlaced PNG files")
  242. ImageFile.ImageFile.load_prepare(self)
  243. def load_read(self, bytes):
  244. "internal: read more image data"
  245. while self.__idat == 0:
  246. # end of chunk, skip forward to next one
  247. self.fp.read(4) # CRC
  248. cid, pos, len = self.png.read()
  249. if cid not in ["IDAT", "DDAT"]:
  250. self.png.push(cid, pos, len)
  251. return ""
  252. self.__idat = len # empty chunks are allowed
  253. # read more data from this chunk
  254. if bytes <= 0:
  255. bytes = self.__idat
  256. else:
  257. bytes = min(bytes, self.__idat)
  258. self.__idat = self.__idat - bytes
  259. return self.fp.read(bytes)
  260. def load_end(self):
  261. "internal: finished reading image data"
  262. self.png.close()
  263. self.png = None
  264. # --------------------------------------------------------------------
  265. # PNG writer
  266. def o16(i):
  267. return chr(i>>8&255) + chr(i&255)
  268. def o32(i):
  269. return chr(i>>24&255) + chr(i>>16&255) + chr(i>>8&255) + chr(i&255)
  270. _OUTMODES = {
  271. # supported PIL modes, and corresponding rawmodes/bits/color combinations
  272. "1": ("1", chr(1)+chr(0)),
  273. "L;1": ("L;1", chr(1)+chr(0)),
  274. "L;2": ("L;2", chr(2)+chr(0)),
  275. "L;4": ("L;4", chr(4)+chr(0)),
  276. "L": ("L", chr(8)+chr(0)),
  277. "LA": ("LA", chr(8)+chr(4)),
  278. "I": ("I;16B", chr(16)+chr(0)),
  279. "P;1": ("P;1", chr(1)+chr(3)),
  280. "P;2": ("P;2", chr(2)+chr(3)),
  281. "P;4": ("P;4", chr(4)+chr(3)),
  282. "P": ("P", chr(8)+chr(3)),
  283. "RGB": ("RGB", chr(8)+chr(2)),
  284. "RGBA":("RGBA", chr(8)+chr(6)),
  285. }
  286. def putchunk(fp, cid, *data):
  287. "Write a PNG chunk (including CRC field)"
  288. data = string.join(data, "")
  289. fp.write(o32(len(data)) + cid)
  290. fp.write(data)
  291. hi, lo = Image.core.crc32(data, Image.core.crc32(cid))
  292. fp.write(o16(hi) + o16(lo))
  293. class _idat:
  294. # wrap output from the encoder in IDAT chunks
  295. def __init__(self, fp, chunk):
  296. self.fp = fp
  297. self.chunk = chunk
  298. def write(self, data):
  299. self.chunk(self.fp, "IDAT", data)
  300. def _save(im, fp, filename, chunk=putchunk, check=0, pnginfo=None):
  301. # save an image to disk (called by the save method)
  302. print "calling our _save"
  303. mode = im.mode
  304. # we hack around encoderinfo...
  305. im.encoderinfo = {}
  306. if mode == "P":
  307. #
  308. # attempt to minimize storage requirements for palette images
  309. if im.encoderinfo.has_key("bits"):
  310. # number of bits specified by user
  311. n = 1 << im.encoderinfo["bits"]
  312. else:
  313. # check palette contents
  314. n = 256 # FIXME
  315. if n <= 2:
  316. bits = 1
  317. elif n <= 4:
  318. bits = 2
  319. elif n <= 16:
  320. bits = 4
  321. else:
  322. bits = 8
  323. if bits != 8:
  324. mode = "%s;%d" % (mode, bits)
  325. # encoder options
  326. if im.encoderinfo.has_key("dictionary"):
  327. dictionary = im.encoderinfo["dictionary"]
  328. else:
  329. dictionary = ""
  330. im.encoderconfig = (im.encoderinfo.has_key("optimize"), dictionary)
  331. # get the corresponding PNG mode
  332. try:
  333. rawmode, mode = _OUTMODES[mode]
  334. except KeyError:
  335. raise IOError, "cannot write mode %s as PNG" % mode
  336. if check:
  337. return check
  338. #
  339. # write minimal PNG file
  340. fp.write(_MAGIC)
  341. chunk(fp, "IHDR",
  342. o32(im.size[0]), o32(im.size[1]), # 0: size
  343. mode, # 8: depth/type
  344. chr(0), # 10: compression
  345. chr(0), # 11: filter category
  346. chr(0)) # 12: interlace flag
  347. if im.mode == "P":
  348. chunk(fp, "PLTE", im.im.getpalette("RGB"))
  349. if im.encoderinfo.has_key("transparency"):
  350. if im.mode == "P":
  351. transparency = max(0, min(255, im.encoderinfo["transparency"]))
  352. chunk(fp, "tRNS", chr(255) * transparency + chr(0))
  353. elif im.mode == "L":
  354. transparency = max(0, min(65535, im.encoderinfo["transparency"]))
  355. chunk(fp, "tRNS", o16(transparency))
  356. else:
  357. raise IOError, "cannot use transparency for this mode"
  358. if 0:
  359. # FIXME: to be supported some day
  360. chunk(fp, "gAMA", o32(int(gamma * 100000.0)))
  361. #info = im.encoderinfo.get("pnginfo")
  362. info = pnginfo
  363. if info:
  364. for cid, data in info.chunks:
  365. chunk(fp, cid, data)
  366. ImageFile._save(im, _idat(fp, chunk), [("zip", (0,0)+im.size, 0, rawmode)])
  367. chunk(fp, "IEND", "")
  368. try:
  369. fp.flush()
  370. except:
  371. pass
  372. # --------------------------------------------------------------------
  373. # PNG chunk converter
  374. def getchunks(im, **params):
  375. """Return a list of PNG chunks representing this image."""
  376. class collector:
  377. data = []
  378. def write(self, data):
  379. pass
  380. def append(self, chunk):
  381. self.data.append(chunk)
  382. def append(fp, cid, *data):
  383. data = string.join(data, "")
  384. hi, lo = Image.core.crc32(data, Image.core.crc32(cid))
  385. crc = o16(hi) + o16(lo)
  386. fp.append((cid, data, crc))
  387. fp = collector()
  388. try:
  389. im.encoderinfo = params
  390. _save(im, fp, None, append)
  391. finally:
  392. del im.encoderinfo
  393. return fp.data
  394. # --------------------------------------------------------------------
  395. # Registry
  396. #Image.register_open("PNG", PngImageFile, _accept)
  397. #Image.register_save("PNG", _save)
  398. #Image.register_extension("PNG", ".png")
  399. #Image.register_mime("PNG", "image/png")