| # | 
| # The Python Imaging Library. | 
| # $Id$ | 
| # | 
| # IFUNC IM file handling for PIL | 
| # | 
| # history: | 
| # 1995-09-01 fl   Created. | 
| # 1997-01-03 fl   Save palette images | 
| # 1997-01-08 fl   Added sequence support | 
| # 1997-01-23 fl   Added P and RGB save support | 
| # 1997-05-31 fl   Read floating point images | 
| # 1997-06-22 fl   Save floating point images | 
| # 1997-08-27 fl   Read and save 1-bit images | 
| # 1998-06-25 fl   Added support for RGB+LUT images | 
| # 1998-07-02 fl   Added support for YCC images | 
| # 1998-07-15 fl   Renamed offset attribute to avoid name clash | 
| # 1998-12-29 fl   Added I;16 support | 
| # 2001-02-17 fl   Use 're' instead of 'regex' (Python 2.1) (0.7) | 
| # 2003-09-26 fl   Added LA/PA support | 
| # | 
| # Copyright (c) 1997-2003 by Secret Labs AB. | 
| # Copyright (c) 1995-2001 by Fredrik Lundh. | 
| # | 
| # See the README file for information on usage and redistribution. | 
| # | 
|   | 
|   | 
| __version__ = "0.7" | 
|   | 
| import re, string | 
| import Image, ImageFile, ImagePalette | 
|   | 
|   | 
| # -------------------------------------------------------------------- | 
| # Standard tags | 
|   | 
| COMMENT = "Comment" | 
| DATE = "Date" | 
| EQUIPMENT = "Digitalization equipment" | 
| FRAMES = "File size (no of images)" | 
| LUT = "Lut" | 
| NAME = "Name" | 
| SCALE = "Scale (x,y)" | 
| SIZE = "Image size (x*y)" | 
| MODE = "Image type" | 
|   | 
| TAGS = { COMMENT:0, DATE:0, EQUIPMENT:0, FRAMES:0, LUT:0, NAME:0, | 
|          SCALE:0, SIZE:0, MODE:0 } | 
|   | 
| OPEN = { | 
|     # ifunc93/p3cfunc formats | 
|     "0 1 image": ("1", "1"), | 
|     "L 1 image": ("1", "1"), | 
|     "Greyscale image": ("L", "L"), | 
|     "Grayscale image": ("L", "L"), | 
|     "RGB image": ("RGB", "RGB;L"), | 
|     "RLB image": ("RGB", "RLB"), | 
|     "RYB image": ("RGB", "RLB"), | 
|     "B1 image": ("1", "1"), | 
|     "B2 image": ("P", "P;2"), | 
|     "B4 image": ("P", "P;4"), | 
|     "X 24 image": ("RGB", "RGB"), | 
|     "L 32 S image": ("I", "I;32"), | 
|     "L 32 F image": ("F", "F;32"), | 
|     # old p3cfunc formats | 
|     "RGB3 image": ("RGB", "RGB;T"), | 
|     "RYB3 image": ("RGB", "RYB;T"), | 
|     # extensions | 
|     "LA image": ("LA", "LA;L"), | 
|     "RGBA image": ("RGBA", "RGBA;L"), | 
|     "RGBX image": ("RGBX", "RGBX;L"), | 
|     "CMYK image": ("CMYK", "CMYK;L"), | 
|     "YCC image": ("YCbCr", "YCbCr;L"), | 
| } | 
|   | 
| # ifunc95 extensions | 
| for i in ["8", "8S", "16", "16S", "32", "32F"]: | 
|     OPEN["L %s image" % i] = ("F", "F;%s" % i) | 
|     OPEN["L*%s image" % i] = ("F", "F;%s" % i) | 
| for i in ["16", "16L", "16B"]: | 
|     OPEN["L %s image" % i] = ("I;%s" % i, "I;%s" % i) | 
|     OPEN["L*%s image" % i] = ("I;%s" % i, "I;%s" % i) | 
| for i in ["32S"]: | 
|     OPEN["L %s image" % i] = ("I", "I;%s" % i) | 
|     OPEN["L*%s image" % i] = ("I", "I;%s" % i) | 
| for i in range(2, 33): | 
|     OPEN["L*%s image" % i] = ("F", "F;%s" % i) | 
|   | 
|   | 
| # -------------------------------------------------------------------- | 
| # Read IM directory | 
|   | 
| split = re.compile(r"^([A-Za-z][^:]*):[ \t]*(.*)[ \t]*$") | 
|   | 
| def number(s): | 
|     try: | 
|         return int(s) | 
|     except ValueError: | 
|         return float(s) | 
|   | 
| ## | 
| # Image plugin for the IFUNC IM file format. | 
|   | 
| class ImImageFile(ImageFile.ImageFile): | 
|   | 
|     format = "IM" | 
|     format_description = "IFUNC Image Memory" | 
|   | 
|     def _open(self): | 
|   | 
|         # Quick rejection: if there's not an LF among the first | 
|         # 100 bytes, this is (probably) not a text header. | 
|   | 
|         if not "\n" in self.fp.read(100): | 
|             raise SyntaxError, "not an IM file" | 
|         self.fp.seek(0) | 
|   | 
|         n = 0 | 
|   | 
|         # Default values | 
|         self.info[MODE] = "L" | 
|         self.info[SIZE] = (512, 512) | 
|         self.info[FRAMES] = 1 | 
|   | 
|         self.rawmode = "L" | 
|   | 
|         while 1: | 
|   | 
|             s = self.fp.read(1) | 
|   | 
|             # Some versions of IFUNC uses \n\r instead of \r\n... | 
|             if s == "\r": | 
|                 continue | 
|   | 
|             if not s or s[0] == chr(0) or s[0] == chr(26): | 
|                 break | 
|   | 
|             # FIXME: this may read whole file if not a text file | 
|             s = s + self.fp.readline() | 
|   | 
|             if len(s) > 100: | 
|                 raise SyntaxError, "not an IM file" | 
|   | 
|             if s[-2:] == '\r\n': | 
|                 s = s[:-2] | 
|             elif s[-1:] == '\n': | 
|                 s = s[:-1] | 
|   | 
|             try: | 
|                 m = split.match(s) | 
|             except re.error, v: | 
|                 raise SyntaxError, "not an IM file" | 
|   | 
|             if m: | 
|   | 
|                 k, v = m.group(1,2) | 
|   | 
|                 # Convert value as appropriate | 
|                 if k in [FRAMES, SCALE, SIZE]: | 
|                     v = string.replace(v, "*", ",") | 
|                     v = tuple(map(number, string.split(v, ","))) | 
|                     if len(v) == 1: | 
|                         v = v[0] | 
|                 elif k == MODE and OPEN.has_key(v): | 
|                     v, self.rawmode = OPEN[v] | 
|   | 
|                 # Add to dictionary. Note that COMMENT tags are | 
|                 # combined into a list of strings. | 
|                 if k == COMMENT: | 
|                     if self.info.has_key(k): | 
|                         self.info[k].append(v) | 
|                     else: | 
|                         self.info[k] = [v] | 
|                 else: | 
|                     self.info[k] = v | 
|   | 
|                 if TAGS.has_key(k): | 
|                     n = n + 1 | 
|   | 
|             else: | 
|   | 
|                 raise SyntaxError, "Syntax error in IM header: " + s | 
|   | 
|         if not n: | 
|             raise SyntaxError, "Not an IM file" | 
|   | 
|         # Basic attributes | 
|         self.size = self.info[SIZE] | 
|         self.mode = self.info[MODE] | 
|   | 
|         # Skip forward to start of image data | 
|         while s and s[0] != chr(26): | 
|             s = self.fp.read(1) | 
|         if not s: | 
|             raise SyntaxError, "File truncated" | 
|   | 
|         if self.info.has_key(LUT): | 
|             # convert lookup table to palette or lut attribute | 
|             palette = self.fp.read(768) | 
|             greyscale = 1 # greyscale palette | 
|             linear = 1 # linear greyscale palette | 
|             for i in range(256): | 
|                 if palette[i] == palette[i+256] == palette[i+512]: | 
|                     if palette[i] != chr(i): | 
|                         linear = 0 | 
|                 else: | 
|                     greyscale = 0 | 
|             if self.mode == "L" or self.mode == "LA": | 
|                 if greyscale: | 
|                     if not linear: | 
|                         self.lut = map(ord, palette[:256]) | 
|                 else: | 
|                     if self.mode == "L": | 
|                         self.mode = self.rawmode = "P" | 
|                     elif self.mode == "LA": | 
|                         self.mode = self.rawmode = "PA" | 
|                     self.palette = ImagePalette.raw("RGB;L", palette) | 
|             elif self.mode == "RGB": | 
|                 if not greyscale or not linear: | 
|                     self.lut = map(ord, palette) | 
|   | 
|         self.frame = 0 | 
|   | 
|         self.__offset = offs = self.fp.tell() | 
|   | 
|         self.__fp = self.fp # FIXME: hack | 
|   | 
|         if self.rawmode[:2] == "F;": | 
|   | 
|             # ifunc95 formats | 
|             try: | 
|                 # use bit decoder (if necessary) | 
|                 bits = int(self.rawmode[2:]) | 
|                 if bits not in [8, 16, 32]: | 
|                     self.tile = [("bit", (0,0)+self.size, offs, | 
|                                  (bits, 8, 3, 0, -1))] | 
|                     return | 
|             except ValueError: | 
|                 pass | 
|   | 
|         if self.rawmode in ["RGB;T", "RYB;T"]: | 
|             # Old LabEye/3PC files.  Would be very surprised if anyone | 
|             # ever stumbled upon such a file ;-) | 
|             size = self.size[0] * self.size[1] | 
|             self.tile = [("raw", (0,0)+self.size, offs, ("G", 0, -1)), | 
|                          ("raw", (0,0)+self.size, offs+size, ("R", 0, -1)), | 
|                          ("raw", (0,0)+self.size, offs+2*size, ("B", 0, -1))] | 
|         else: | 
|             # LabEye/IFUNC files | 
|             self.tile = [("raw", (0,0)+self.size, offs, (self.rawmode, 0, -1))] | 
|   | 
|     def seek(self, frame): | 
|   | 
|         if frame < 0 or frame >= self.info[FRAMES]: | 
|             raise EOFError, "seek outside sequence" | 
|   | 
|         if self.frame == frame: | 
|             return | 
|   | 
|         self.frame = frame | 
|   | 
|         if self.mode == "1": | 
|             bits = 1 | 
|         else: | 
|             bits = 8 * len(self.mode) | 
|   | 
|         size = ((self.size[0] * bits + 7) / 8) * self.size[1] | 
|         offs = self.__offset + frame * size | 
|   | 
|         self.fp = self.__fp | 
|   | 
|         self.tile = [("raw", (0,0)+self.size, offs, (self.rawmode, 0, -1))] | 
|   | 
|     def tell(self): | 
|   | 
|         return self.frame | 
|   | 
| # | 
| # -------------------------------------------------------------------- | 
| # Save IM files | 
|   | 
| SAVE = { | 
|     # mode: (im type, raw mode) | 
|     "1": ("0 1", "1"), | 
|     "L": ("Greyscale", "L"), | 
|     "LA": ("LA", "LA;L"), | 
|     "P": ("Greyscale", "P"), | 
|     "PA": ("LA", "PA;L"), | 
|     "I": ("L 32S", "I;32S"), | 
|     "I;16": ("L 16", "I;16"), | 
|     "I;16L": ("L 16L", "I;16L"), | 
|     "I;16B": ("L 16B", "I;16B"), | 
|     "F": ("L 32F", "F;32F"), | 
|     "RGB": ("RGB", "RGB;L"), | 
|     "RGBA": ("RGBA", "RGBA;L"), | 
|     "RGBX": ("RGBX", "RGBX;L"), | 
|     "CMYK": ("CMYK", "CMYK;L"), | 
|     "YCbCr": ("YCC", "YCbCr;L") | 
| } | 
|   | 
| def _save(im, fp, filename, check=0): | 
|   | 
|     try: | 
|         type, rawmode = SAVE[im.mode] | 
|     except KeyError: | 
|         raise ValueError, "Cannot save %s images as IM" % im.mode | 
|   | 
|     try: | 
|         frames = im.encoderinfo["frames"] | 
|     except KeyError: | 
|         frames = 1 | 
|   | 
|     if check: | 
|         return check | 
|   | 
|     fp.write("Image type: %s image\r\n" % type) | 
|     if filename: | 
|         fp.write("Name: %s\r\n" % filename) | 
|     fp.write("Image size (x*y): %d*%d\r\n" % im.size) | 
|     fp.write("File size (no of images): %d\r\n" % frames) | 
|     if im.mode == "P": | 
|         fp.write("Lut: 1\r\n") | 
|     fp.write("\000" * (511-fp.tell()) + "\032") | 
|     if im.mode == "P": | 
|         fp.write(im.im.getpalette("RGB", "RGB;L")) # 768 bytes | 
|     ImageFile._save(im, fp, [("raw", (0,0)+im.size, 0, (rawmode, 0, -1))]) | 
|   | 
| # | 
| # -------------------------------------------------------------------- | 
| # Registry | 
|   | 
| Image.register_open("IM", ImImageFile) | 
| Image.register_save("IM", _save) | 
|   | 
| Image.register_extension("IM", ".im") |