home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / pypil112.zip / PIL-1.1.2.zip / Lib / site-packages / PIL / PngImagePlugin.py < prev    next >
Text File  |  2001-05-03  |  12KB  |  489 lines

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