| # | 
| # The Python Imaging Library. | 
| # $Id$ | 
| # | 
| # JPEG (JFIF) file handling | 
| # | 
| # See "Digital Compression and Coding of Continous-Tone Still Images, | 
| # Part 1, Requirements and Guidelines" (CCITT T.81 / ISO 10918-1) | 
| # | 
| # History: | 
| # 1995-09-09 fl   Created | 
| # 1995-09-13 fl   Added full parser | 
| # 1996-03-25 fl   Added hack to use the IJG command line utilities | 
| # 1996-05-05 fl   Workaround Photoshop 2.5 CMYK polarity bug | 
| # 1996-05-28 fl   Added draft support, JFIF version (0.1) | 
| # 1996-12-30 fl   Added encoder options, added progression property (0.2) | 
| # 1997-08-27 fl   Save mode 1 images as BW (0.3) | 
| # 1998-07-12 fl   Added YCbCr to draft and save methods (0.4) | 
| # 1998-10-19 fl   Don't hang on files using 16-bit DQT's (0.4.1) | 
| # 2001-04-16 fl   Extract DPI settings from JFIF files (0.4.2) | 
| # 2002-07-01 fl   Skip pad bytes before markers; identify Exif files (0.4.3) | 
| # 2003-04-25 fl   Added experimental EXIF decoder (0.5) | 
| # 2003-06-06 fl   Added experimental EXIF GPSinfo decoder | 
| # 2003-09-13 fl   Extract COM markers | 
| # 2009-09-06 fl   Added icc_profile support (from Florian Hoech) | 
| # 2009-03-06 fl   Changed CMYK handling; always use Adobe polarity (0.6) | 
| # 2009-03-08 fl   Added subsampling support (from Justin Huff). | 
| # | 
| # Copyright (c) 1997-2003 by Secret Labs AB. | 
| # Copyright (c) 1995-1996 by Fredrik Lundh. | 
| # | 
| # See the README file for information on usage and redistribution. | 
| # | 
|   | 
| __version__ = "0.6" | 
|   | 
| import array, struct | 
| import string | 
| import Image, ImageFile | 
|   | 
| def i16(c,o=0): | 
|     return ord(c[o+1]) + (ord(c[o])<<8) | 
|   | 
| def i32(c,o=0): | 
|     return ord(c[o+3]) + (ord(c[o+2])<<8) + (ord(c[o+1])<<16) + (ord(c[o])<<24) | 
|   | 
| # | 
| # Parser | 
|   | 
| def Skip(self, marker): | 
|     n = i16(self.fp.read(2))-2 | 
|     ImageFile._safe_read(self.fp, n) | 
|   | 
| def APP(self, marker): | 
|     # | 
|     # Application marker.  Store these in the APP dictionary. | 
|     # Also look for well-known application markers. | 
|   | 
|     n = i16(self.fp.read(2))-2 | 
|     s = ImageFile._safe_read(self.fp, n) | 
|   | 
|     app = "APP%d" % (marker&15) | 
|   | 
|     self.app[app] = s # compatibility | 
|     self.applist.append((app, s)) | 
|   | 
|     if marker == 0xFFE0 and s[:4] == "JFIF": | 
|         # extract JFIF information | 
|         self.info["jfif"] = version = i16(s, 5) # version | 
|         self.info["jfif_version"] = divmod(version, 256) | 
|         # extract JFIF properties | 
|         try: | 
|             jfif_unit = ord(s[7]) | 
|             jfif_density = i16(s, 8), i16(s, 10) | 
|         except: | 
|             pass | 
|         else: | 
|             if jfif_unit == 1: | 
|                 self.info["dpi"] = jfif_density | 
|             self.info["jfif_unit"] = jfif_unit | 
|             self.info["jfif_density"] = jfif_density | 
|     elif marker == 0xFFE1 and s[:5] == "Exif\0": | 
|         # extract Exif information (incomplete) | 
|         self.info["exif"] = s # FIXME: value will change | 
|     elif marker == 0xFFE2 and s[:5] == "FPXR\0": | 
|         # extract FlashPix information (incomplete) | 
|         self.info["flashpix"] = s # FIXME: value will change | 
|     elif marker == 0xFFE2 and s[:12] == "ICC_PROFILE\0": | 
|         # Since an ICC profile can be larger than the maximum size of | 
|         # a JPEG marker (64K), we need provisions to split it into | 
|         # multiple markers. The format defined by the ICC specifies | 
|         # one or more APP2 markers containing the following data: | 
|         #   Identifying string      ASCII "ICC_PROFILE\0"  (12 bytes) | 
|         #   Marker sequence number  1, 2, etc (1 byte) | 
|         #   Number of markers       Total of APP2's used (1 byte) | 
|         #   Profile data            (remainder of APP2 data) | 
|         # Decoders should use the marker sequence numbers to | 
|         # reassemble the profile, rather than assuming that the APP2 | 
|         # markers appear in the correct sequence. | 
|         self.icclist.append(s) | 
|     elif marker == 0xFFEE and s[:5] == "Adobe": | 
|         self.info["adobe"] = i16(s, 5) | 
|         # extract Adobe custom properties | 
|         try: | 
|             adobe_transform = ord(s[1]) | 
|         except: | 
|             pass | 
|         else: | 
|             self.info["adobe_transform"] = adobe_transform | 
|   | 
| def COM(self, marker): | 
|     # | 
|     # Comment marker.  Store these in the APP dictionary. | 
|   | 
|     n = i16(self.fp.read(2))-2 | 
|     s = ImageFile._safe_read(self.fp, n) | 
|   | 
|     self.app["COM"] = s # compatibility | 
|     self.applist.append(("COM", s)) | 
|   | 
| def SOF(self, marker): | 
|     # | 
|     # Start of frame marker.  Defines the size and mode of the | 
|     # image.  JPEG is colour blind, so we use some simple | 
|     # heuristics to map the number of layers to an appropriate | 
|     # mode.  Note that this could be made a bit brighter, by | 
|     # looking for JFIF and Adobe APP markers. | 
|   | 
|     n = i16(self.fp.read(2))-2 | 
|     s = ImageFile._safe_read(self.fp, n) | 
|     self.size = i16(s[3:]), i16(s[1:]) | 
|   | 
|     self.bits = ord(s[0]) | 
|     if self.bits != 8: | 
|         raise SyntaxError("cannot handle %d-bit layers" % self.bits) | 
|   | 
|     self.layers = ord(s[5]) | 
|     if self.layers == 1: | 
|         self.mode = "L" | 
|     elif self.layers == 3: | 
|         self.mode = "RGB" | 
|     elif self.layers == 4: | 
|         self.mode = "CMYK" | 
|     else: | 
|         raise SyntaxError("cannot handle %d-layer images" % self.layers) | 
|   | 
|     if marker in [0xFFC2, 0xFFC6, 0xFFCA, 0xFFCE]: | 
|         self.info["progressive"] = self.info["progression"] = 1 | 
|   | 
|     if self.icclist: | 
|         # fixup icc profile | 
|         self.icclist.sort() # sort by sequence number | 
|         if ord(self.icclist[0][13]) == len(self.icclist): | 
|             profile = [] | 
|             for p in self.icclist: | 
|                 profile.append(p[14:]) | 
|             icc_profile = string.join(profile, "") | 
|         else: | 
|             icc_profile = None # wrong number of fragments | 
|         self.info["icc_profile"] = icc_profile | 
|         self.icclist = None | 
|   | 
|     for i in range(6, len(s), 3): | 
|         t = s[i:i+3] | 
|         # 4-tuples: id, vsamp, hsamp, qtable | 
|         self.layer.append((t[0], ord(t[1])/16, ord(t[1])&15, ord(t[2]))) | 
|   | 
| def DQT(self, marker): | 
|     # | 
|     # Define quantization table.  Support baseline 8-bit tables | 
|     # only.  Note that there might be more than one table in | 
|     # each marker. | 
|   | 
|     # FIXME: The quantization tables can be used to estimate the | 
|     # compression quality. | 
|   | 
|     n = i16(self.fp.read(2))-2 | 
|     s = ImageFile._safe_read(self.fp, n) | 
|     while len(s): | 
|         if len(s) < 65: | 
|             raise SyntaxError("bad quantization table marker") | 
|         v = ord(s[0]) | 
|         if v/16 == 0: | 
|             self.quantization[v&15] = array.array("b", s[1:65]) | 
|             s = s[65:] | 
|         else: | 
|             return # FIXME: add code to read 16-bit tables! | 
|             # raise SyntaxError, "bad quantization table element size" | 
|   | 
|   | 
| # | 
| # JPEG marker table | 
|   | 
| MARKER = { | 
|     0xFFC0: ("SOF0", "Baseline DCT", SOF), | 
|     0xFFC1: ("SOF1", "Extended Sequential DCT", SOF), | 
|     0xFFC2: ("SOF2", "Progressive DCT", SOF), | 
|     0xFFC3: ("SOF3", "Spatial lossless", SOF), | 
|     0xFFC4: ("DHT", "Define Huffman table", Skip), | 
|     0xFFC5: ("SOF5", "Differential sequential DCT", SOF), | 
|     0xFFC6: ("SOF6", "Differential progressive DCT", SOF), | 
|     0xFFC7: ("SOF7", "Differential spatial", SOF), | 
|     0xFFC8: ("JPG", "Extension", None), | 
|     0xFFC9: ("SOF9", "Extended sequential DCT (AC)", SOF), | 
|     0xFFCA: ("SOF10", "Progressive DCT (AC)", SOF), | 
|     0xFFCB: ("SOF11", "Spatial lossless DCT (AC)", SOF), | 
|     0xFFCC: ("DAC", "Define arithmetic coding conditioning", Skip), | 
|     0xFFCD: ("SOF13", "Differential sequential DCT (AC)", SOF), | 
|     0xFFCE: ("SOF14", "Differential progressive DCT (AC)", SOF), | 
|     0xFFCF: ("SOF15", "Differential spatial (AC)", SOF), | 
|     0xFFD0: ("RST0", "Restart 0", None), | 
|     0xFFD1: ("RST1", "Restart 1", None), | 
|     0xFFD2: ("RST2", "Restart 2", None), | 
|     0xFFD3: ("RST3", "Restart 3", None), | 
|     0xFFD4: ("RST4", "Restart 4", None), | 
|     0xFFD5: ("RST5", "Restart 5", None), | 
|     0xFFD6: ("RST6", "Restart 6", None), | 
|     0xFFD7: ("RST7", "Restart 7", None), | 
|     0xFFD8: ("SOI", "Start of image", None), | 
|     0xFFD9: ("EOI", "End of image", None), | 
|     0xFFDA: ("SOS", "Start of scan", Skip), | 
|     0xFFDB: ("DQT", "Define quantization table", DQT), | 
|     0xFFDC: ("DNL", "Define number of lines", Skip), | 
|     0xFFDD: ("DRI", "Define restart interval", Skip), | 
|     0xFFDE: ("DHP", "Define hierarchical progression", SOF), | 
|     0xFFDF: ("EXP", "Expand reference component", Skip), | 
|     0xFFE0: ("APP0", "Application segment 0", APP), | 
|     0xFFE1: ("APP1", "Application segment 1", APP), | 
|     0xFFE2: ("APP2", "Application segment 2", APP), | 
|     0xFFE3: ("APP3", "Application segment 3", APP), | 
|     0xFFE4: ("APP4", "Application segment 4", APP), | 
|     0xFFE5: ("APP5", "Application segment 5", APP), | 
|     0xFFE6: ("APP6", "Application segment 6", APP), | 
|     0xFFE7: ("APP7", "Application segment 7", APP), | 
|     0xFFE8: ("APP8", "Application segment 8", APP), | 
|     0xFFE9: ("APP9", "Application segment 9", APP), | 
|     0xFFEA: ("APP10", "Application segment 10", APP), | 
|     0xFFEB: ("APP11", "Application segment 11", APP), | 
|     0xFFEC: ("APP12", "Application segment 12", APP), | 
|     0xFFED: ("APP13", "Application segment 13", APP), | 
|     0xFFEE: ("APP14", "Application segment 14", APP), | 
|     0xFFEF: ("APP15", "Application segment 15", APP), | 
|     0xFFF0: ("JPG0", "Extension 0", None), | 
|     0xFFF1: ("JPG1", "Extension 1", None), | 
|     0xFFF2: ("JPG2", "Extension 2", None), | 
|     0xFFF3: ("JPG3", "Extension 3", None), | 
|     0xFFF4: ("JPG4", "Extension 4", None), | 
|     0xFFF5: ("JPG5", "Extension 5", None), | 
|     0xFFF6: ("JPG6", "Extension 6", None), | 
|     0xFFF7: ("JPG7", "Extension 7", None), | 
|     0xFFF8: ("JPG8", "Extension 8", None), | 
|     0xFFF9: ("JPG9", "Extension 9", None), | 
|     0xFFFA: ("JPG10", "Extension 10", None), | 
|     0xFFFB: ("JPG11", "Extension 11", None), | 
|     0xFFFC: ("JPG12", "Extension 12", None), | 
|     0xFFFD: ("JPG13", "Extension 13", None), | 
|     0xFFFE: ("COM", "Comment", COM) | 
| } | 
|   | 
|   | 
| def _accept(prefix): | 
|     return prefix[0] == "\377" | 
|   | 
| ## | 
| # Image plugin for JPEG and JFIF images. | 
|   | 
| class JpegImageFile(ImageFile.ImageFile): | 
|   | 
|     format = "JPEG" | 
|     format_description = "JPEG (ISO 10918)" | 
|   | 
|     def _open(self): | 
|   | 
|         s = self.fp.read(1) | 
|   | 
|         if ord(s[0]) != 255: | 
|             raise SyntaxError("not a JPEG file") | 
|   | 
|         # Create attributes | 
|         self.bits = self.layers = 0 | 
|   | 
|         # JPEG specifics (internal) | 
|         self.layer = [] | 
|         self.huffman_dc = {} | 
|         self.huffman_ac = {} | 
|         self.quantization = {} | 
|         self.app = {} # compatibility | 
|         self.applist = [] | 
|         self.icclist = [] | 
|   | 
|         while 1: | 
|   | 
|             s = s + self.fp.read(1) | 
|   | 
|             i = i16(s) | 
|   | 
|             if MARKER.has_key(i): | 
|                 name, description, handler = MARKER[i] | 
|                 # print hex(i), name, description | 
|                 if handler is not None: | 
|                     handler(self, i) | 
|                 if i == 0xFFDA: # start of scan | 
|                     rawmode = self.mode | 
|                     if self.mode == "CMYK": | 
|                         rawmode = "CMYK;I" # assume adobe conventions | 
|                     self.tile = [("jpeg", (0,0) + self.size, 0, (rawmode, ""))] | 
|                     # self.__offset = self.fp.tell() | 
|                     break | 
|                 s = self.fp.read(1) | 
|             elif i == 0 or i == 65535: | 
|                 # padded marker or junk; move on | 
|                 s = "\xff" | 
|             else: | 
|                 raise SyntaxError("no marker found") | 
|   | 
|     def draft(self, mode, size): | 
|   | 
|         if len(self.tile) != 1: | 
|             return | 
|   | 
|         d, e, o, a = self.tile[0] | 
|         scale = 0 | 
|   | 
|         if a[0] == "RGB" and mode in ["L", "YCbCr"]: | 
|             self.mode = mode | 
|             a = mode, "" | 
|   | 
|         if size: | 
|             scale = max(self.size[0] / size[0], self.size[1] / size[1]) | 
|             for s in [8, 4, 2, 1]: | 
|                 if scale >= s: | 
|                     break | 
|             e = e[0], e[1], (e[2]-e[0]+s-1)/s+e[0], (e[3]-e[1]+s-1)/s+e[1] | 
|             self.size = ((self.size[0]+s-1)/s, (self.size[1]+s-1)/s) | 
|             scale = s | 
|   | 
|         self.tile = [(d, e, o, a)] | 
|         self.decoderconfig = (scale, 1) | 
|   | 
|         return self | 
|   | 
|     def load_djpeg(self): | 
|   | 
|         # ALTERNATIVE: handle JPEGs via the IJG command line utilities | 
|   | 
|         import tempfile, os | 
|         file = tempfile.mktemp() | 
|         os.system("djpeg %s >%s" % (self.filename, file)) | 
|   | 
|         try: | 
|             self.im = Image.core.open_ppm(file) | 
|         finally: | 
|             try: os.unlink(file) | 
|             except: pass | 
|   | 
|         self.mode = self.im.mode | 
|         self.size = self.im.size | 
|   | 
|         self.tile = [] | 
|   | 
|     def _getexif(self): | 
|         # Extract EXIF information.  This method is highly experimental, | 
|         # and is likely to be replaced with something better in a future | 
|         # version. | 
|         import TiffImagePlugin, StringIO | 
|         def fixup(value): | 
|             if len(value) == 1: | 
|                 return value[0] | 
|             return value | 
|         # The EXIF record consists of a TIFF file embedded in a JPEG | 
|         # application marker (!). | 
|         try: | 
|             data = self.info["exif"] | 
|         except KeyError: | 
|             return None | 
|         file = StringIO.StringIO(data[6:]) | 
|         head = file.read(8) | 
|         exif = {} | 
|         # process dictionary | 
|         info = TiffImagePlugin.ImageFileDirectory(head) | 
|         info.load(file) | 
|         for key, value in info.items(): | 
|             exif[key] = fixup(value) | 
|         # get exif extension | 
|         try: | 
|             file.seek(exif[0x8769]) | 
|         except KeyError: | 
|             pass | 
|         else: | 
|             info = TiffImagePlugin.ImageFileDirectory(head) | 
|             info.load(file) | 
|             for key, value in info.items(): | 
|                 exif[key] = fixup(value) | 
|         # get gpsinfo extension | 
|         try: | 
|             file.seek(exif[0x8825]) | 
|         except KeyError: | 
|             pass | 
|         else: | 
|             info = TiffImagePlugin.ImageFileDirectory(head) | 
|             info.load(file) | 
|             exif[0x8825] = gps = {} | 
|             for key, value in info.items(): | 
|                 gps[key] = fixup(value) | 
|         return exif | 
|   | 
| # -------------------------------------------------------------------- | 
| # stuff to save JPEG files | 
|   | 
| RAWMODE = { | 
|     "1": "L", | 
|     "L": "L", | 
|     "RGB": "RGB", | 
|     "RGBA": "RGB", | 
|     "RGBX": "RGB", | 
|     "CMYK": "CMYK;I", # assume adobe conventions | 
|     "YCbCr": "YCbCr", | 
| } | 
|   | 
| def _save(im, fp, filename): | 
|   | 
|     try: | 
|         rawmode = RAWMODE[im.mode] | 
|     except KeyError: | 
|         raise IOError("cannot write mode %s as JPEG" % im.mode) | 
|   | 
|     info = im.encoderinfo | 
|   | 
|     dpi = info.get("dpi", (0, 0)) | 
|   | 
|     subsampling = info.get("subsampling", -1) | 
|     if subsampling == "4:4:4": | 
|         subsampling = 0 | 
|     elif subsampling == "4:2:2": | 
|         subsampling = 1 | 
|     elif subsampling == "4:1:1": | 
|         subsampling = 2 | 
|   | 
|     extra = "" | 
|   | 
|     icc_profile = info.get("icc_profile") | 
|     if icc_profile: | 
|         ICC_OVERHEAD_LEN = 14 | 
|         MAX_BYTES_IN_MARKER = 65533 | 
|         MAX_DATA_BYTES_IN_MARKER = MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN | 
|         markers = [] | 
|         while icc_profile: | 
|             markers.append(icc_profile[:MAX_DATA_BYTES_IN_MARKER]) | 
|             icc_profile = icc_profile[MAX_DATA_BYTES_IN_MARKER:] | 
|         i = 1 | 
|         for marker in markers: | 
|             size = struct.pack(">H", 2 + ICC_OVERHEAD_LEN + len(marker)) | 
|             extra = extra + ("\xFF\xE2" + size + "ICC_PROFILE\0" + chr(i) + chr(len(markers)) + marker) | 
|             i = i + 1 | 
|   | 
|     # get keyword arguments | 
|     im.encoderconfig = ( | 
|         info.get("quality", 0), | 
|         # "progressive" is the official name, but older documentation | 
|         # says "progression" | 
|         # FIXME: issue a warning if the wrong form is used (post-1.1.7) | 
|         info.has_key("progressive") or info.has_key("progression"), | 
|         info.get("smooth", 0), | 
|         info.has_key("optimize"), | 
|         info.get("streamtype", 0), | 
|         dpi[0], dpi[1], | 
|         subsampling, | 
|         extra, | 
|         ) | 
|   | 
|     ImageFile._save(im, fp, [("jpeg", (0,0)+im.size, 0, rawmode)]) | 
|   | 
| def _save_cjpeg(im, fp, filename): | 
|     # ALTERNATIVE: handle JPEGs via the IJG command line utilities. | 
|     import os | 
|     file = im._dump() | 
|     os.system("cjpeg %s >%s" % (file, filename)) | 
|     try: os.unlink(file) | 
|     except: pass | 
|   | 
| # -------------------------------------------------------------------q- | 
| # Registry stuff | 
|   | 
| Image.register_open("JPEG", JpegImageFile, _accept) | 
| Image.register_save("JPEG", _save) | 
|   | 
| Image.register_extension("JPEG", ".jfif") | 
| Image.register_extension("JPEG", ".jpe") | 
| Image.register_extension("JPEG", ".jpg") | 
| Image.register_extension("JPEG", ".jpeg") | 
|   | 
| Image.register_mime("JPEG", "image/jpeg") |