def validate_qtables(qtables): if qtables is None: return qtables if isStringType(qtables): try: lines = [int(num) for line in qtables.splitlines() for num in line.split("#", 1)[0].split()] except ValueError: raise ValueError("Invalid quantization table") else: qtables = [lines[s : s + 64] for s in xrange(0, len(lines), 64)] if isinstance(qtables, (tuple, list, dict)): if isinstance(qtables, dict): qtables = convert_dict_qtables(qtables) elif isinstance(qtables, tuple): qtables = list(qtables) if not (0 < len(qtables) < 5): raise ValueError("None or too many quantization tables") for idx, table in enumerate(qtables): try: if len(table) != 64: raise table = array.array("b", table) except TypeError: raise ValueError("Invalid quantization table") else: qtables[idx] = list(table) return qtables
def validate_qtables(qtables): if qtables is None: return qtables if isStringType(qtables): try: lines = [ int(num) for line in qtables.splitlines() for num in line.split('#', 1)[0].split() ] except ValueError: raise ValueError("Invalid quantization table") else: qtables = [lines[s:s + 64] for s in range(0, len(lines), 64)] if isinstance(qtables, (tuple, list, dict)): if isinstance(qtables, dict): qtables = convert_dict_qtables(qtables) elif isinstance(qtables, tuple): qtables = list(qtables) if not (0 < len(qtables) < 5): raise ValueError("None or too many quantization tables") for idx, table in enumerate(qtables): try: if len(table) != 64: raise table = array.array('b', table) except TypeError: raise ValueError("Invalid quantization table") else: qtables[idx] = list(table) return qtables
def __init__(self, profile): # accepts a string (filename), a file-like object, or a low-level # profile object if isStringType(profile): self._set(core.profile_open(profile), profile) elif hasattr(profile, "read"): self._set(core.profile_frombytes(profile.read())) else: self._set(profile) # assume it's already a profile
def test_is_path(self): # Arrange fp = "filename.ext" # Act it_is = _util.isStringType(fp) # Assert self.assertTrue(it_is)
def test_is_string_type(self): # Arrange color = "red" # Act it_is = _util.isStringType(color) # Assert self.assertTrue(it_is)
def test_is_not_string_type(self): # Arrange color = (255, 0, 0) # Act it_is_not = _util.isStringType(color) # Assert self.assertFalse(it_is_not)
def setink(self, ink): # compatibility if warnings: warnings.warn("'setink' is deprecated; use keyword arguments instead", DeprecationWarning, stacklevel=2) if isStringType(ink): ink = ImageColor.getcolor(ink, self.mode) if self.palette and not isinstance(ink, numbers.Number): ink = self.palette.getcolor(ink) self.ink = self.draw.draw_ink(ink, self.mode)
def _getink(self, ink, fill=None): if ink is None and fill is None: if self.fill: fill = self.ink else: ink = self.ink else: if ink is not None: if isStringType(ink): ink = ImageColor.getcolor(ink, self.mode) if self.palette and not isinstance(ink, numbers.Number): ink = self.palette.getcolor(ink) ink = self.draw.draw_ink(ink, self.mode) if fill is not None: if isStringType(fill): fill = ImageColor.getcolor(fill, self.mode) if self.palette and not isinstance(fill, numbers.Number): fill = self.palette.getcolor(fill) fill = self.draw.draw_ink(fill, self.mode) return ink, fill
def setink(self, ink): # compatibility if warnings: warnings.warn( "'setink' is deprecated; use keyword arguments instead", DeprecationWarning, stacklevel=2) if isStringType(ink): ink = ImageColor.getcolor(ink, self.mode) if self.palette and not isinstance(ink, numbers.Number): ink = self.palette.getcolor(ink) self.ink = self.draw.draw_ink(ink, self.mode)
def __init__(self, profile): """ :param profile: Either a string representing a filename, a file like object containing a profile or a low-level profile object """ if isStringType(profile): self._set(core.profile_open(profile), profile) elif hasattr(profile, "read"): self._set(core.profile_frombytes(profile.read())) else: self._set(profile) # assume it's already a profile
def __init__(self, profile): """ :param profile: Either a string representing a filename, a file like object containing a profile or a low-level profile object """ if isStringType(profile): self._set(core.profile_open(profile), profile) elif hasattr(profile, "read"): self._set(core.profile_frombytes(profile.read())) elif isinstance(profile, _imagingcms.CmsProfile): self._set(profile) else: raise TypeError("Invalid type for Profile")
def save(self, fp): o16 = self.o16 o32 = self.o32 fp.write(o16(len(self.tags))) # always write in ascending tag order tags = sorted(self.tags.items()) directory = [] append = directory.append offset = fp.tell() + len(self.tags) * 12 + 4 stripoffsets = None # pass 1: convert tags to binary format for tag, value in tags: typ = None if tag in self.tagtype: typ = self.tagtype[tag] if Image.DEBUG: print("Tag %s, Type: %s, Value: %s" % (tag, typ, value)) if typ == 1: # byte data if isinstance(value, tuple): data = value = value[-1] else: data = value elif typ == 7: # untyped data data = value = b"".join(value) elif isStringType(value[0]): # string data if isinstance(value, tuple): value = value[-1] typ = 2 # was b'\0'.join(str), which led to \x00a\x00b sorts # of strings which I don't see in in the wild tiffs # and doesn't match the tiff spec: 8-bit byte that # contains a 7-bit ASCII code; the last byte must be # NUL (binary zero). Also, I don't think this was well # excersized before. data = value = b"" + value.encode('ascii', 'replace') + b"\0" else: # integer data if tag == STRIPOFFSETS: stripoffsets = len(directory) typ = 4 # to avoid catch-22 elif tag in (X_RESOLUTION, Y_RESOLUTION) or typ == 5: # identify rational data fields typ = 5 if isinstance(value[0], tuple): # long name for flatten value = tuple(itertools.chain.from_iterable(value)) elif not typ: typ = 3 for v in value: if v >= 65536: typ = 4 if typ == 3: data = b"".join(map(o16, value)) else: data = b"".join(map(o32, value)) if Image.DEBUG: from PIL import TiffTags tagname = TiffTags.TAGS.get(tag, "unknown") typname = TiffTags.TYPES.get(typ, "unknown") print("save: %s (%d)" % (tagname, tag), end=' ') print("- type: %s (%d)" % (typname, typ), end=' ') if tag in (COLORMAP, IPTC_NAA_CHUNK, PHOTOSHOP_CHUNK, ICCPROFILE, XMP): size = len(data) print("- value: <table: %d bytes>" % size) else: print("- value:", value) # figure out if data fits into the directory if len(data) == 4: append((tag, typ, len(value), data, b"")) elif len(data) < 4: append((tag, typ, len(value), data + (4 - len(data)) * b"\0", b"")) else: count = len(value) if typ == 5: count = count // 2 # adjust for rational data field append((tag, typ, count, o32(offset), data)) offset += len(data) if offset & 1: offset += 1 # word padding # update strip offset data to point beyond auxiliary data if stripoffsets is not None: tag, typ, count, value, data = directory[stripoffsets] assert not data, "multistrip support not yet implemented" value = o32(self.i32(value) + offset) directory[stripoffsets] = tag, typ, count, value, data # pass 2: write directory to file for tag, typ, count, value, data in directory: if Image.DEBUG > 1: print(tag, typ, count, repr(value), repr(data)) fp.write(o16(tag) + o16(typ) + o32(count) + value) # -- overwrite here for multi-page -- fp.write(b"\0\0\0\0") # end of directory # pass 3: write auxiliary data to file for tag, typ, count, value, data in directory: fp.write(data) if len(data) & 1: fp.write(b"\0") return offset
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)) quality = info.get("quality", 0) subsampling = info.get("subsampling", -1) qtables = info.get("qtables") if quality == "keep": quality = 0 subsampling = "keep" qtables = "keep" elif quality in presets: preset = presets[quality] quality = 0 subsampling = preset.get('subsampling', -1) qtables = preset.get('quantization') elif not isinstance(quality, int): raise ValueError("Invalid quality setting") else: if subsampling in presets: subsampling = presets[subsampling].get('subsampling', -1) if isStringType(qtables) and qtables in presets: qtables = presets[qtables].get('quantization') if subsampling == "4:4:4": subsampling = 0 elif subsampling == "4:2:2": subsampling = 1 elif subsampling == "4:1:1": subsampling = 2 elif subsampling == "keep": if im.format != "JPEG": raise ValueError( "Cannot use 'keep' when original image is not a JPEG") subsampling = get_sampling(im) def validate_qtables(qtables): if qtables is None: return qtables if isStringType(qtables): try: lines = [ int(num) for line in qtables.splitlines() for num in line.split('#', 1)[0].split() ] except ValueError: raise ValueError("Invalid quantization table") else: qtables = [lines[s:s + 64] for s in range(0, len(lines), 64)] if isinstance(qtables, (tuple, list, dict)): if isinstance(qtables, dict): qtables = convert_dict_qtables(qtables) elif isinstance(qtables, tuple): qtables = list(qtables) if not (0 < len(qtables) < 5): raise ValueError("None or too many quantization tables") for idx, table in enumerate(qtables): try: if len(table) != 64: raise table = array.array('b', table) except TypeError: raise ValueError("Invalid quantization table") else: qtables[idx] = list(table) return qtables if qtables == "keep": if im.format != "JPEG": raise ValueError( "Cannot use 'keep' when original image is not a JPEG") qtables = getattr(im, "quantization", None) qtables = validate_qtables(qtables) extra = b"" 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 += (b"\xFF\xE2" + size + b"ICC_PROFILE\0" + o8(i) + o8(len(markers)) + marker) i += 1 # get keyword arguments im.encoderconfig = ( quality, # "progressive" is the official name, but older documentation # says "progression" # FIXME: issue a warning if the wrong form is used (post-1.1.7) "progressive" in info or "progression" in info, info.get("smooth", 0), "optimize" in info, info.get("streamtype", 0), dpi[0], dpi[1], subsampling, qtables, extra, info.get("exif", b"")) # if we optimize, libjpeg needs a buffer big enough to hold the whole image # in a shot. Guessing on the size, at im.size bytes. (raw pizel size is # channels*size, this is a value that's been used in a django patch. # https://github.com/jdriscoll/django-imagekit/issues/50 bufsize = 0 if "optimize" in info or "progressive" in info or "progression" in info: # keep sets quality to 0, but the actual value may be high. if quality >= 95 or quality == 0: bufsize = 2 * im.size[0] * im.size[1] else: bufsize = im.size[0] * im.size[1] # The exif info needs to be written as one block, + APP1, + one spare byte. # Ensure that our buffer is big enough bufsize = max(ImageFile.MAXBLOCK, bufsize, len(info.get("exif", b"")) + 5) ImageFile._save(im, fp, [("jpeg", (0, 0) + im.size, 0, rawmode)], bufsize)
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)) quality = info.get("quality", 0) subsampling = info.get("subsampling", -1) qtables = info.get("qtables") if quality == "keep": quality = 0 subsampling = "keep" qtables = "keep" elif quality in presets: preset = presets[quality] quality = 0 subsampling = preset.get('subsampling', -1) qtables = preset.get('quantization') elif not isinstance(quality, int): raise ValueError("Invalid quality setting") else: if subsampling in presets: subsampling = presets[subsampling].get('subsampling', -1) if isStringType(qtables) and qtables in presets: qtables = presets[qtables].get('quantization') if subsampling == "4:4:4": subsampling = 0 elif subsampling == "4:2:2": subsampling = 1 elif subsampling == "4:1:1": subsampling = 2 elif subsampling == "keep": if im.format != "JPEG": raise ValueError( "Cannot use 'keep' when original image is not a JPEG") subsampling = get_sampling(im) def validate_qtables(qtables): if qtables is None: return qtables if isStringType(qtables): try: lines = [int(num) for line in qtables.splitlines() for num in line.split('#', 1)[0].split()] except ValueError: raise ValueError("Invalid quantization table") else: qtables = [lines[s:s+64] for s in range(0, len(lines), 64)] if isinstance(qtables, (tuple, list, dict)): if isinstance(qtables, dict): qtables = convert_dict_qtables(qtables) elif isinstance(qtables, tuple): qtables = list(qtables) if not (0 < len(qtables) < 5): raise ValueError("None or too many quantization tables") for idx, table in enumerate(qtables): try: if len(table) != 64: raise table = array.array('b', table) except TypeError: raise ValueError("Invalid quantization table") else: qtables[idx] = list(table) return qtables if qtables == "keep": if im.format != "JPEG": raise ValueError( "Cannot use 'keep' when original image is not a JPEG") qtables = getattr(im, "quantization", None) qtables = validate_qtables(qtables) extra = b"" 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 += (b"\xFF\xE2" + size + b"ICC_PROFILE\0" + o8(i) + o8(len(markers)) + marker) i += 1 # get keyword arguments im.encoderconfig = ( quality, # "progressive" is the official name, but older documentation # says "progression" # FIXME: issue a warning if the wrong form is used (post-1.1.7) "progressive" in info or "progression" in info, info.get("smooth", 0), "optimize" in info, info.get("streamtype", 0), dpi[0], dpi[1], subsampling, qtables, extra, info.get("exif", b"") ) # if we optimize, libjpeg needs a buffer big enough to hold the whole image # in a shot. Guessing on the size, at im.size bytes. (raw pizel size is # channels*size, this is a value that's been used in a django patch. # https://github.com/jdriscoll/django-imagekit/issues/50 bufsize = 0 if "optimize" in info or "progressive" in info or "progression" in info: # keep sets quality to 0, but the actual value may be high. if quality >= 95 or quality == 0: bufsize = 2 * im.size[0] * im.size[1] else: bufsize = im.size[0] * im.size[1] # The exif info needs to be written as one block, + APP1, + one spare byte. # Ensure that our buffer is big enough bufsize = max(ImageFile.MAXBLOCK, bufsize, len(info.get("exif", b"")) + 5) ImageFile._save(im, fp, [("jpeg", (0, 0)+im.size, 0, rawmode)], bufsize)
def _save(im, fp, filename): try: rawmode, prefix, photo, format, bits, extra = SAVE_INFO[im.mode] except KeyError: raise IOError("cannot write mode %s as TIFF" % im.mode) ifd = ImageFileDirectory(prefix) compression = im.encoderinfo.get('compression', im.info.get('compression', 'raw')) libtiff = WRITE_LIBTIFF or compression != 'raw' # required for color libtiff images ifd[PLANAR_CONFIGURATION] = getattr(im, '_planar_configuration', 1) # -- multi-page -- skip TIFF header on subsequent pages if not libtiff and fp.tell() == 0: # tiff header (write via IFD to get everything right) # PIL always starts the first IFD at offset 8 fp.write(ifd.prefix + ifd.o16(42) + ifd.o32(8)) ifd[IMAGEWIDTH] = im.size[0] ifd[IMAGELENGTH] = im.size[1] # write any arbitrary tags passed in as an ImageFileDirectory info = im.encoderinfo.get("tiffinfo", {}) if Image.DEBUG: print("Tiffinfo Keys: %s" % info.keys) keys = list(info.keys()) for key in keys: ifd[key] = info.get(key) try: ifd.tagtype[key] = info.tagtype[key] except: pass # might not be an IFD, Might not have populated type # additions written by Greg Couch, [email protected] # inspired by image-sig posting from Kevin Cazabon, [email protected] if hasattr(im, 'tag'): # preserve tags from original TIFF image file for key in (RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION, IPTC_NAA_CHUNK, PHOTOSHOP_CHUNK, XMP): if key in im.tag: ifd[key] = im.tag[key] ifd.tagtype[key] = im.tag.tagtype.get(key, None) # preserve ICC profile (should also work when saving other formats # which support profiles as TIFF) -- 2008-06-06 Florian Hoech if "icc_profile" in im.info: ifd[ICCPROFILE] = im.info["icc_profile"] if "description" in im.encoderinfo: ifd[IMAGEDESCRIPTION] = im.encoderinfo["description"] if "resolution" in im.encoderinfo: ifd[X_RESOLUTION] = ifd[Y_RESOLUTION] \ = _cvt_res(im.encoderinfo["resolution"]) if "x resolution" in im.encoderinfo: ifd[X_RESOLUTION] = _cvt_res(im.encoderinfo["x resolution"]) if "y resolution" in im.encoderinfo: ifd[Y_RESOLUTION] = _cvt_res(im.encoderinfo["y resolution"]) if "resolution unit" in im.encoderinfo: unit = im.encoderinfo["resolution unit"] if unit == "inch": ifd[RESOLUTION_UNIT] = 2 elif unit == "cm" or unit == "centimeter": ifd[RESOLUTION_UNIT] = 3 else: ifd[RESOLUTION_UNIT] = 1 if "software" in im.encoderinfo: ifd[SOFTWARE] = im.encoderinfo["software"] if "date time" in im.encoderinfo: ifd[DATE_TIME] = im.encoderinfo["date time"] if "artist" in im.encoderinfo: ifd[ARTIST] = im.encoderinfo["artist"] if "copyright" in im.encoderinfo: ifd[COPYRIGHT] = im.encoderinfo["copyright"] dpi = im.encoderinfo.get("dpi") if dpi: ifd[RESOLUTION_UNIT] = 2 ifd[X_RESOLUTION] = _cvt_res(dpi[0]) ifd[Y_RESOLUTION] = _cvt_res(dpi[1]) if bits != (1, ): ifd[BITSPERSAMPLE] = bits if len(bits) != 1: ifd[SAMPLESPERPIXEL] = len(bits) if extra is not None: ifd[EXTRASAMPLES] = extra if format != 1: ifd[SAMPLEFORMAT] = format ifd[PHOTOMETRIC_INTERPRETATION] = photo if im.mode == "P": lut = im.im.getpalette("RGB", "RGB;L") ifd[COLORMAP] = tuple(i8(v) * 256 for v in lut) # data orientation stride = len(bits) * ((im.size[0] * bits[0] + 7) // 8) ifd[ROWSPERSTRIP] = im.size[1] ifd[STRIPBYTECOUNTS] = stride * im.size[1] ifd[STRIPOFFSETS] = 0 # this is adjusted by IFD writer # no compression by default: ifd[COMPRESSION] = COMPRESSION_INFO_REV.get(compression, 1) if libtiff: if Image.DEBUG: print("Saving using libtiff encoder") print(ifd.items()) _fp = 0 if hasattr(fp, "fileno"): fp.seek(0) _fp = os.dup(fp.fileno()) # ICC Profile crashes. blocklist = [STRIPOFFSETS, STRIPBYTECOUNTS, ROWSPERSTRIP, ICCPROFILE] atts = {} # bits per sample is a single short in the tiff directory, not a list. atts[BITSPERSAMPLE] = bits[0] # Merge the ones that we have with (optional) more bits from # the original file, e.g x,y resolution so that we can # save(load('')) == original file. for k, v in itertools.chain(ifd.items(), getattr(im, 'ifd', {}).items()): if k not in atts and k not in blocklist: if type(v[0]) == tuple and len(v) > 1: # A tuple of more than one rational tuples # flatten to floats, # following tiffcp.c->cpTag->TIFF_RATIONAL atts[k] = [float(elt[0]) / float(elt[1]) for elt in v] continue if type(v[0]) == tuple and len(v) == 1: # A tuple of one rational tuples # flatten to floats, # following tiffcp.c->cpTag->TIFF_RATIONAL atts[k] = float(v[0][0]) / float(v[0][1]) continue if type(v) == tuple and len(v) > 2: # List of ints? if type(v[0]) in (int, float): atts[k] = list(v) continue if type(v) == tuple and len(v) == 2: # one rational tuple # flatten to float, # following tiffcp.c->cpTag->TIFF_RATIONAL atts[k] = float(v[0]) / float(v[1]) continue if type(v) == tuple and len(v) == 1: v = v[0] # drop through if isStringType(v): atts[k] = bytes(v.encode('ascii', 'replace')) + b"\0" continue else: # int or similar atts[k] = v if Image.DEBUG: print(atts) # libtiff always expects the bytes in native order. # we're storing image byte order. So, if the rawmode # contains I;16, we need to convert from native to image # byte order. if im.mode in ('I;16B', 'I;16'): rawmode = 'I;16N' a = (rawmode, compression, _fp, filename, atts) # print (im.mode, compression, a, im.encoderconfig) e = Image._getencoder(im.mode, 'libtiff', a, im.encoderconfig) e.setimage(im.im, (0, 0) + im.size) while True: # undone, change to self.decodermaxblock: l, s, d = e.encode(16 * 1024) if not _fp: fp.write(d) if s: break if s < 0: raise IOError("encoder error %d when writing image file" % s) else: offset = ifd.save(fp) ImageFile._save(im, fp, [("raw", (0, 0) + im.size, offset, (rawmode, stride, 1))]) # -- helper for multi-page save -- if "_debug_multipage" in im.encoderinfo: # just to access o32 and o16 (using correct byte order) im._debug_multipage = ifd
def save(self, fp): o16 = self.o16 o32 = self.o32 fp.write(o16(len(self.tags))) # always write in ascending tag order tags = sorted(self.tags.items()) directory = [] append = directory.append offset = fp.tell() + len(self.tags) * 12 + 4 stripoffsets = None # pass 1: convert tags to binary format for tag, value in tags: typ = None if tag in self.tagtype: typ = self.tagtype[tag] if Image.DEBUG: print("Tag %s, Type: %s, Value: %s" % (tag, typ, value)) if typ == 1: # byte data if isinstance(value, tuple): data = value = value[-1] else: data = value elif typ == 7: # untyped data data = value = b"".join(value) elif typ in (11, 12): # float value tmap = {11: 'f', 12: 'd'} if not isinstance(value, tuple): value = (value,) a = array.array(tmap[typ], value) if self.prefix != native_prefix: a.byteswap() data = a.tostring() elif isStringType(value[0]): # string data if isinstance(value, tuple): value = value[-1] typ = 2 # was b'\0'.join(str), which led to \x00a\x00b sorts # of strings which I don't see in in the wild tiffs # and doesn't match the tiff spec: 8-bit byte that # contains a 7-bit ASCII code; the last byte must be # NUL (binary zero). Also, I don't think this was well # exercised before. data = value = b"" + value.encode('ascii', 'replace') + b"\0" else: # integer data if tag == STRIPOFFSETS: stripoffsets = len(directory) typ = 4 # to avoid catch-22 elif tag in (X_RESOLUTION, Y_RESOLUTION) or typ == 5: # identify rational data fields typ = 5 if isinstance(value[0], tuple): # long name for flatten value = tuple(itertools.chain.from_iterable(value)) elif not typ: typ = 3 for v in value: if v >= 65536: typ = 4 if typ == 3: data = b"".join(map(o16, value)) else: data = b"".join(map(o32, value)) if Image.DEBUG: from PIL import TiffTags tagname = TiffTags.TAGS.get(tag, "unknown") typname = TiffTags.TYPES.get(typ, "unknown") print("save: %s (%d)" % (tagname, tag), end=' ') print("- type: %s (%d)" % (typname, typ), end=' ') if tag in (COLORMAP, IPTC_NAA_CHUNK, PHOTOSHOP_CHUNK, ICCPROFILE, XMP): size = len(data) print("- value: <table: %d bytes>" % size) else: print("- value:", value) # figure out if data fits into the directory if len(data) == 4: append((tag, typ, len(value), data, b"")) elif len(data) < 4: append((tag, typ, len(value), data + (4-len(data))*b"\0", b"")) else: count = len(value) if typ == 5: count = count // 2 # adjust for rational data field append((tag, typ, count, o32(offset), data)) offset += len(data) if offset & 1: offset += 1 # word padding # update strip offset data to point beyond auxiliary data if stripoffsets is not None: tag, typ, count, value, data = directory[stripoffsets] assert not data, "multistrip support not yet implemented" value = o32(self.i32(value) + offset) directory[stripoffsets] = tag, typ, count, value, data # pass 2: write directory to file for tag, typ, count, value, data in directory: if Image.DEBUG > 1: print(tag, typ, count, repr(value), repr(data)) fp.write(o16(tag) + o16(typ) + o32(count) + value) # -- overwrite here for multi-page -- fp.write(b"\0\0\0\0") # end of directory # pass 3: write auxiliary data to file for tag, typ, count, value, data in directory: fp.write(data) if len(data) & 1: fp.write(b"\0") return offset
def _save(im, fp, filename): try: rawmode, prefix, photo, format, bits, extra = SAVE_INFO[im.mode] except KeyError: raise IOError("cannot write mode %s as TIFF" % im.mode) ifd = ImageFileDirectory(prefix) compression = im.encoderinfo.get('compression', im.info.get('compression', 'raw')) libtiff = WRITE_LIBTIFF or compression != 'raw' # required for color libtiff images ifd[PLANAR_CONFIGURATION] = getattr(im, '_planar_configuration', 1) # -- multi-page -- skip TIFF header on subsequent pages if not libtiff and fp.tell() == 0: # tiff header (write via IFD to get everything right) # PIL always starts the first IFD at offset 8 fp.write(ifd.prefix + ifd.o16(42) + ifd.o32(8)) ifd[IMAGEWIDTH] = im.size[0] ifd[IMAGELENGTH] = im.size[1] # write any arbitrary tags passed in as an ImageFileDirectory info = im.encoderinfo.get("tiffinfo", {}) if Image.DEBUG: print("Tiffinfo Keys: %s" % info.keys) keys = list(info.keys()) for key in keys: ifd[key] = info.get(key) try: ifd.tagtype[key] = info.tagtype[key] except: pass # might not be an IFD, Might not have populated type # additions written by Greg Couch, [email protected] # inspired by image-sig posting from Kevin Cazabon, [email protected] if hasattr(im, 'tag'): # preserve tags from original TIFF image file for key in (RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION, IPTC_NAA_CHUNK, PHOTOSHOP_CHUNK, XMP): if key in im.tag: ifd[key] = im.tag[key] ifd.tagtype[key] = im.tag.tagtype.get(key, None) # preserve ICC profile (should also work when saving other formats # which support profiles as TIFF) -- 2008-06-06 Florian Hoech if "icc_profile" in im.info: ifd[ICCPROFILE] = im.info["icc_profile"] for key, name, cvt in [ (IMAGEDESCRIPTION, "description", lambda x: x), (X_RESOLUTION, "resolution", _cvt_res), (Y_RESOLUTION, "resolution", _cvt_res), (X_RESOLUTION, "x_resolution", _cvt_res), (Y_RESOLUTION, "y_resolution", _cvt_res), (RESOLUTION_UNIT, "resolution_unit", lambda x: {"inch": 2, "cm": 3, "centimeter": 3}.get(x, 1)), (SOFTWARE, "software", lambda x: x), (DATE_TIME, "date_time", lambda x: x), (ARTIST, "artist", lambda x: x), (COPYRIGHT, "copyright", lambda x: x)]: name_with_spaces = name.replace("_", " ") if "_" in name and name_with_spaces in im.encoderinfo: warnings.warn("%r is deprecated; use %r instead" % (name_with_spaces, name), DeprecationWarning) ifd[key] = cvt(im.encoderinfo[name.replace("_", " ")]) if name in im.encoderinfo: ifd[key] = cvt(im.encoderinfo[name]) dpi = im.encoderinfo.get("dpi") if dpi: ifd[RESOLUTION_UNIT] = 2 ifd[X_RESOLUTION] = _cvt_res(dpi[0]) ifd[Y_RESOLUTION] = _cvt_res(dpi[1]) if bits != (1,): ifd[BITSPERSAMPLE] = bits if len(bits) != 1: ifd[SAMPLESPERPIXEL] = len(bits) if extra is not None: ifd[EXTRASAMPLES] = extra if format != 1: ifd[SAMPLEFORMAT] = format ifd[PHOTOMETRIC_INTERPRETATION] = photo if im.mode == "P": lut = im.im.getpalette("RGB", "RGB;L") ifd[COLORMAP] = tuple(i8(v) * 256 for v in lut) # data orientation stride = len(bits) * ((im.size[0]*bits[0]+7)//8) ifd[ROWSPERSTRIP] = im.size[1] ifd[STRIPBYTECOUNTS] = stride * im.size[1] ifd[STRIPOFFSETS] = 0 # this is adjusted by IFD writer # no compression by default: ifd[COMPRESSION] = COMPRESSION_INFO_REV.get(compression, 1) if libtiff: if Image.DEBUG: print("Saving using libtiff encoder") print(ifd.items()) _fp = 0 if hasattr(fp, "fileno"): try: fp.seek(0) _fp = os.dup(fp.fileno()) except io.UnsupportedOperation: pass # ICC Profile crashes. blocklist = [STRIPOFFSETS, STRIPBYTECOUNTS, ROWSPERSTRIP, ICCPROFILE] atts = {} # bits per sample is a single short in the tiff directory, not a list. atts[BITSPERSAMPLE] = bits[0] # Merge the ones that we have with (optional) more bits from # the original file, e.g x,y resolution so that we can # save(load('')) == original file. for k, v in itertools.chain(ifd.items(), getattr(im, 'ifd', {}).items()): if k not in atts and k not in blocklist: if type(v[0]) == tuple and len(v) > 1: # A tuple of more than one rational tuples # flatten to floats, # following tiffcp.c->cpTag->TIFF_RATIONAL atts[k] = [float(elt[0])/float(elt[1]) for elt in v] continue if type(v[0]) == tuple and len(v) == 1: # A tuple of one rational tuples # flatten to floats, # following tiffcp.c->cpTag->TIFF_RATIONAL atts[k] = float(v[0][0])/float(v[0][1]) continue if (type(v) == tuple and (len(v) > 2 or (len(v) == 2 and v[1] == 0))): # List of ints? # Avoid divide by zero in next if-clause if type(v[0]) in (int, float): atts[k] = list(v) continue if type(v) == tuple and len(v) == 2: # one rational tuple # flatten to float, # following tiffcp.c->cpTag->TIFF_RATIONAL atts[k] = float(v[0])/float(v[1]) continue if type(v) == tuple and len(v) == 1: v = v[0] # drop through if isStringType(v): atts[k] = bytes(v.encode('ascii', 'replace')) + b"\0" continue else: # int or similar atts[k] = v if Image.DEBUG: print(atts) # libtiff always expects the bytes in native order. # we're storing image byte order. So, if the rawmode # contains I;16, we need to convert from native to image # byte order. if im.mode in ('I;16B', 'I;16'): rawmode = 'I;16N' a = (rawmode, compression, _fp, filename, atts) # print (im.mode, compression, a, im.encoderconfig) e = Image._getencoder(im.mode, 'libtiff', a, im.encoderconfig) e.setimage(im.im, (0, 0)+im.size) while True: # undone, change to self.decodermaxblock: l, s, d = e.encode(16*1024) if not _fp: fp.write(d) if s: break if s < 0: raise IOError("encoder error %d when writing image file" % s) else: offset = ifd.save(fp) ImageFile._save(im, fp, [ ("raw", (0, 0)+im.size, offset, (rawmode, stride, 1)) ]) # -- helper for multi-page save -- if "_debug_multipage" in im.encoderinfo: # just to access o32 and o16 (using correct byte order) im._debug_multipage = ifd
def _color(color, mode): if isStringType(color): from PIL import ImageColor color = ImageColor.getcolor(color, mode) return color
def _save(im, fp, filename): try: rawmode, prefix, photo, format, bits, extra = SAVE_INFO[im.mode] except KeyError: raise IOError("cannot write mode %s as TIFF" % im.mode) ifd = ImageFileDirectory(prefix) compression = im.encoderinfo.get('compression',im.info.get('compression','raw')) libtiff = compression in ["tiff_ccitt", "group3", "group4", "tiff_jpeg", "tiff_adobe_deflate", "tiff_thunderscan", "tiff_deflate", "tiff_sgilog", "tiff_sgilog24", "tiff_raw_16"] # -- multi-page -- skip TIFF header on subsequent pages if not libtiff and fp.tell() == 0: # tiff header (write via IFD to get everything right) # PIL always starts the first IFD at offset 8 fp.write(ifd.prefix + ifd.o16(42) + ifd.o32(8)) ifd[IMAGEWIDTH] = im.size[0] ifd[IMAGELENGTH] = im.size[1] # additions written by Greg Couch, [email protected] # inspired by image-sig posting from Kevin Cazabon, [email protected] if hasattr(im, 'tag'): # preserve tags from original TIFF image file for key in (RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION): if key in im.tag.tagdata: ifd[key] = im.tag.tagdata.get(key) # preserve some more tags from original TIFF image file # -- 2008-06-06 Florian Hoech ifd.tagtype = im.tag.tagtype for key in (IPTC_NAA_CHUNK, PHOTOSHOP_CHUNK, XMP): if key in im.tag: ifd[key] = im.tag[key] # preserve ICC profile (should also work when saving other formats # which support profiles as TIFF) -- 2008-06-06 Florian Hoech if "icc_profile" in im.info: ifd[ICCPROFILE] = im.info["icc_profile"] if "description" in im.encoderinfo: ifd[IMAGEDESCRIPTION] = im.encoderinfo["description"] if "resolution" in im.encoderinfo: ifd[X_RESOLUTION] = ifd[Y_RESOLUTION] \ = _cvt_res(im.encoderinfo["resolution"]) if "x resolution" in im.encoderinfo: ifd[X_RESOLUTION] = _cvt_res(im.encoderinfo["x resolution"]) if "y resolution" in im.encoderinfo: ifd[Y_RESOLUTION] = _cvt_res(im.encoderinfo["y resolution"]) if "resolution unit" in im.encoderinfo: unit = im.encoderinfo["resolution unit"] if unit == "inch": ifd[RESOLUTION_UNIT] = 2 elif unit == "cm" or unit == "centimeter": ifd[RESOLUTION_UNIT] = 3 else: ifd[RESOLUTION_UNIT] = 1 if "software" in im.encoderinfo: ifd[SOFTWARE] = im.encoderinfo["software"] if "date time" in im.encoderinfo: ifd[DATE_TIME] = im.encoderinfo["date time"] if "artist" in im.encoderinfo: ifd[ARTIST] = im.encoderinfo["artist"] if "copyright" in im.encoderinfo: ifd[COPYRIGHT] = im.encoderinfo["copyright"] dpi = im.encoderinfo.get("dpi") if dpi: ifd[RESOLUTION_UNIT] = 2 ifd[X_RESOLUTION] = _cvt_res(dpi[0]) ifd[Y_RESOLUTION] = _cvt_res(dpi[1]) if bits != (1,): ifd[BITSPERSAMPLE] = bits if len(bits) != 1: ifd[SAMPLESPERPIXEL] = len(bits) if extra is not None: ifd[EXTRASAMPLES] = extra if format != 1: ifd[SAMPLEFORMAT] = format ifd[PHOTOMETRIC_INTERPRETATION] = photo if im.mode == "P": lut = im.im.getpalette("RGB", "RGB;L") ifd[COLORMAP] = tuple(i8(v) * 256 for v in lut) # data orientation stride = len(bits) * ((im.size[0]*bits[0]+7)//8) ifd[ROWSPERSTRIP] = im.size[1] ifd[STRIPBYTECOUNTS] = stride * im.size[1] ifd[STRIPOFFSETS] = 0 # this is adjusted by IFD writer ifd[COMPRESSION] = COMPRESSION_INFO_REV.get(compression,1) # no compression by default if libtiff: if Image.DEBUG: print ("Saving using libtiff encoder") print (ifd.items()) _fp = 0 if hasattr(fp, "fileno"): fp.seek(0) _fp = os.dup(fp.fileno()) blocklist = [STRIPOFFSETS, STRIPBYTECOUNTS, ROWSPERSTRIP, ICCPROFILE] # ICC Profile crashes. atts={} # Merge the ones that we have with (optional) more bits from # the original file, e.g x,y resolution so that we can # save(load('')) == original file. for k,v in itertools.chain(ifd.items(), getattr(im, 'ifd', {}).items()): if k not in atts and k not in blocklist: if type(v[0]) == tuple and len(v) > 1: # A tuple of more than one rational tuples # flatten to floats, following tiffcp.c->cpTag->TIFF_RATIONAL atts[k] = [float(elt[0])/float(elt[1]) for elt in v] continue if type(v[0]) == tuple and len(v) == 1: # A tuple of one rational tuples # flatten to floats, following tiffcp.c->cpTag->TIFF_RATIONAL atts[k] = float(v[0][0])/float(v[0][1]) continue if type(v) == tuple and len(v) > 2: # List of ints? # BitsPerSample is one example, I get (8,8,8) # UNDONE continue if type(v) == tuple and len(v) == 2: # one rational tuple # flatten to float, following tiffcp.c->cpTag->TIFF_RATIONAL atts[k] = float(v[0])/float(v[1]) continue if type(v) == tuple and len(v) == 1: v = v[0] # drop through if isStringType(v): atts[k] = bytes(v.encode('ascii', 'replace')) + b"\0" continue else: # int or similar atts[k] = v if Image.DEBUG: print (atts) # libtiff always returns the bytes in native order. # we're expecting image byte order. So, if the rawmode # contains I;16, we need to convert from native to image # byte order. if im.mode in ('I;16B', 'I;16'): rawmode = 'I;16N' a = (rawmode, compression, _fp, filename, atts) # print (im.mode, compression, a, im.encoderconfig) e = Image._getencoder(im.mode, compression, a, im.encoderconfig) e.setimage(im.im, (0,0)+im.size) while 1: l, s, d = e.encode(16*1024) # undone, change to self.decodermaxblock if not _fp: fp.write(d) if s: break if s < 0: raise IOError("encoder error %d when writing image file" % s) else: offset = ifd.save(fp) ImageFile._save(im, fp, [ ("raw", (0,0)+im.size, offset, (rawmode, stride, 1)) ]) # -- helper for multi-page save -- if "_debug_multipage" in im.encoderinfo: #just to access o32 and o16 (using correct byte order) im._debug_multipage = ifd