[Image-SIG] PNG enhancements

Fred L. Drake Fred L. Drake, Jr." <fdrake@acm.org
Mon, 20 Jul 1998 10:44:33 -0400 (EDT)


--5ydeQFjoGe
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit



  I've attached a patch to PngImagePlugin.py which adds support for
the zTXt, bKGD, and gIFg chunks.  The tEXt and zTXt chunks contain
text information; this is now stored in the "text" element of the info 
dictionary; the value is another dictionary of key/value pairs.  Each
value is a list of (string, <compressed>) pairs, where <compressed> is 
a boolean indicating whether the string was compressed (a zTXt chunk)
or uncompressed (a tEXt chunk).  The bKGD chunk is used to add a
"background" field to the info dictionary, and the gIFg chunk is used
to determine a "duration" field.


  -Fred

--
Fred L. Drake, Jr.	     <fdrake@acm.org>
Corporation for National Research Initiatives
1895 Preston White Dr.	    Reston, VA  20191



--5ydeQFjoGe
Content-Type: text/plain
Content-Description: improve PNG support for PIL
Content-Disposition: inline;
	filename="PngImagePlugin.patch"
Content-Transfer-Encoding: 7bit

*** PIL/PngImagePlugin.py	Sun Jul 19 10:25:14 1998
--- /home/fdrake/lib/python/PngImagePlugin.py	Mon Jul 20 10:40:38 1998
***************
*** 88,93 ****
--- 88,96 ----
      def close(self):
  	del self.queue
  	self.fp = None
+         if self.__dict__.has_key("crc"):
+             # using crc_skip(); remove circular reference
+             del self.__dict__["crc"]
  
      def push(self, cid, pos, len):
  
***************
*** 167,172 ****
--- 170,178 ----
  	# image data
  	self.im_tile = [("zip", (0,0)+self.im_size, pos, self.im_rawmode)]
  	self.im_idat = len
+ 
+         # This error is to separate the "header" chunks from the remaining
+         # chunks; see PngImageFile.__init__().
  	raise EOFError
  
      def chunk_IEND(self, pos, len):
***************
*** 193,202 ****
  
  	# text
  	s = self.fp.read(len)
! 	[k, v] = string.split(s, "\0")
! 	self.im_info[k] = v
  	return s
  
  
  # --------------------------------------------------------------------
  # PNG reader
--- 199,266 ----
  
  	# text
  	s = self.fp.read(len)
! 	[k, v] = string.split(s, "\0", 1)
!         self.add_text(k, v)
  	return s
  
+     def chunk_zTXt(self, pos, len):
+         s = self.fp.read(len)
+ 	[k, d] = string.split(s, "\0", 1)
+         if not d:
+             raise SyntaxError, "zTXt chunk missing compression method"
+         m, z = d[0], d[1:]
+         v = _inflate(z)
+         self.add_text(k, v, 1)
+         return s
+ 
+     def add_text(self, keyword, value, compressed=0):
+         # If there is already a chunk with this keyword, the value of
+         # info[k] will become a list containing all values, otherwise (if
+         # there is no value already), only a single string will be used.
+         value = (value, compressed)
+         try:
+             text = self.im_info["text"]
+         except KeyError:
+             text = self.im_info["text"] = {}
+         if text.has_key(keyword):
+             text[keyword].append(value)
+         else:
+             text[keyword] = [value]
+ 
+     def chunk_bKGD(self, pos, len):
+         s = self.fp.read(len)
+         if len == 1:
+             self.im_info["background"] = ord(s)
+         elif len == 2:
+             self.im_info["background"] = ord(s[0]) << 8 | ord(s[1])
+         elif len == 6:
+             # tuple (R, G, B)
+             self.im_info["background"] = ((ord(s[0]) << 8 | ord(s[1])),
+                                           (ord(s[2]) << 8 | ord(s[3])),
+                                           (ord(s[4]) << 8 | ord(s[5])))
+         elif Image.DEBUG:
+             print "unsupported background chunk in mode", self.im_mode
+         return s
+ 
+     def chunk_gIFg(self, pos, len):
+         s = self.fp.read(len)
+         if len != 4:
+             raise SyntaxError, "invalid gIFg chunk length"
+         import struct
+         disp, input, delay = struct.unpack(">BBH", s)
+         self.im_info["duration"] = int(delay)
+         return s
+ 
+ 
+ def _deflate(data):
+     import zlib
+     co = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED,
+                           -zlib.MAX_WBITS)
+     return co.compress(data) + co.flush()
+ 
+ def _inflate(data):
+     import zlib
+     return zlib.decompress(data, -zlib.MAX_WBITS)
  
  # --------------------------------------------------------------------
  # PNG reader
***************
*** 232,237 ****
--- 296,302 ----
  		break
  
  	    except AttributeError:
+                 # presumably an unknown chunk type
  		if Image.DEBUG:
  		    print cid, pos, len, "(unknown)"
  		s = self.fp.read(len)
***************
*** 311,317 ****
      "L;2": ("L;2", chr(2)+chr(0)),
      "L;4": ("L;4", chr(4)+chr(0)),
      "L":   ("L", chr(8)+chr(0)),
-     "I":   ("I;16B", chr(16)+chr(0)),
      "P;1": ("P;1", chr(1)+chr(3)),
      "P;2": ("P;2", chr(2)+chr(3)),
      "P;4": ("P;4", chr(4)+chr(3)),
--- 376,381 ----
***************
*** 386,392 ****
  	raise IOError, "cannot write mode %s as PNG" % mode
  
      if check:
! 	return check
  
      #
      # write minimal PNG file
--- 450,456 ----
  	raise IOError, "cannot write mode %s as PNG" % mode
  
      if check:
!         return check
  
      #
      # write minimal PNG file
***************
*** 400,413 ****
  	  chr(0),				# 11: filter category
  	  chr(0))				# 12: interlace flag
  
      if im.mode == "P":
! 	chunk(fp, "PLTE", im.im.getpalette("RGB"))
  
      if 0:
  	# FIXME: to be supported some day
  	chunk(fp, "gAMA", o32(int(gamma * 100000.0)))
  
      ImageFile._save(im, _idat(fp, chunk), [("zip", (0,0)+im.size, 0, rawmode)])
  
      chunk(fp, "IEND", "")
  
--- 464,512 ----
  	  chr(0),				# 11: filter category
  	  chr(0))				# 12: interlace flag
  
+     colortype = ord(mode[-1])           	# "color type" from the spec.
+     info = im.info.copy()
      if im.mode == "P":
!         pal = im.im.getpalette("RGB")
! 	chunk(fp, "PLTE", pal)
!         if im.info.has_key("transparency"):
!             tRNS_data = None
!             if colortype == 3:
!                 v = [0] * (len(pal) / 3)
!                 v[im.info["transparency"]] = 1
!                 tRNS_data = string.join(map(chr, v), '')
!             elif colortype in (4, 6):
!                 # prohibited
!                 pass
!             elif colortype in (0, 2):
!                 # not yet implemented
!                 pass
!             else:
!                 raise RuntimeError, "bad colortype value -- internal error"
!             if tRNS_data:
!                 chunk(fp, "tRNS", tRNS_data)
!         if im.info.has_key("background") and colortype == 3:
!             bKGD_data = chr(im.info["background"])
!             chunk(fp, "bKGD", bKGD_data)
!     if im.info.has_key("duration"):
!         import struct
!         disp, input, delay = 0, 0, im.info["duration"]
!         gIFg_data = struct.pack(">BBH", disp, input, delay)
!         chunk(fp, "gIFg", gIFg_data)
  
      if 0:
  	# FIXME: to be supported some day
  	chunk(fp, "gAMA", o32(int(gamma * 100000.0)))
  
      ImageFile._save(im, _idat(fp, chunk), [("zip", (0,0)+im.size, 0, rawmode)])
+ 
+     if im.info.has_key("text"):
+         for k, v in info["text"].items():
+             if type(v) is type(()) and len(v) == 2:
+                 v, c = v
+                 if c:
+                     v = _deflate(v)
+                 chunk(fp, c and "zTXt" or "tEXt", v)
  
      chunk(fp, "IEND", "")
  

--5ydeQFjoGe--