def saveGif(self, filename): with open(filename, 'wb') as f: #TODO: check if file is writable? #Write the header f.write(self.headerBytes()) #Write the screen descriptor f.write(self.screenDescriptorBytes()) #write global colour palette, if any if self.globalPalette: assert(len(self.globalPalette) == 768) f.write(self.globalPalette) if len(self.Frames)>1: f.write(self.loopControlBytes()) for GCE, ID, img in self.Frames: if len(self.Frames)>1: f.write(GCE.toBytes()) f.write(ID.toBytes()) #convert to new image. imOut = self.convertRGBtoIndexed(img, self.globalPalette) imOut.encoderconfig = (8, False) #no interlace. f.write(b'\x08') ImageFile._save(imOut, f, [("gif", (0,0)+imOut.size, 0, RAWMODE[imOut.mode])]) f.write(b"\0") #end of image data. #Write end of file (0x3B) f.write(b';')
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).encode('ascii')) if filename: fp.write(("Name: %s\r\n" % filename).encode('ascii')) fp.write(("Image size (x*y): %d*%d\r\n" % im.size).encode('ascii')) fp.write(("File size (no of images): %d\r\n" % frames).encode('ascii')) if im.mode == "P": fp.write(b"Lut: 1\r\n") fp.write(b"\000" * (511-fp.tell()) + b"\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))])
def _save(im, fp, filename): if im.mode == "1": rawmode, head = "1;I", b"P4" elif im.mode == "L": rawmode, head = "L", b"P5" elif im.mode == "I": if im.getextrema()[1] < 2 ** 16: rawmode, head = "I;16B", b"P5" else: rawmode, head = "I;32B", b"P5" elif im.mode == "RGB": rawmode, head = "RGB", b"P6" elif im.mode == "RGBA": rawmode, head = "RGB", b"P6" else: raise IOError("cannot write mode %s as PPM" % im.mode) fp.write(head + ("\n%d %d\n" % im.size).encode("ascii")) if head == b"P6": fp.write(b"255\n") if head == b"P5": if rawmode == "L": fp.write(b"255\n") elif rawmode == "I;16B": fp.write(b"65535\n") elif rawmode == "I;32B": fp.write(b"2147483648\n") ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))])
def _save(im, fp, filename): if im.mode != "1": raise IOError("cannot write mode %s as MSP" % im.mode) # create MSP header header = [0] * 16 header[0], header[1] = i16(b"Da"), i16(b"nM") # version 1 header[2], header[3] = im.size header[4], header[5] = 1, 1 header[6], header[7] = 1, 1 header[8], header[9] = im.size checksum = 0 for h in header: checksum = checksum ^ h header[12] = checksum # FIXME: is this the right field? # header for h in header: fp.write(o16(h)) # image body ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 32, ("1", 0, 1))])
def getdata(im, offset = (0, 0), **params): """Return a list of strings representing this image. The first string is a local image header, the rest contains encoded image data.""" class collector: data = [] def write(self, data): self.data.append(data) im.load() # make sure raster data is available fp = collector() try: im.encoderinfo = params # local image header fp.write(b"," + o16(offset[0]) + # offset o16(offset[1]) + o16(im.size[0]) + # size o16(im.size[1]) + o8(0) + # flags o8(8)) # bits ImageFile._save(im, fp, [("gif", (0,0)+im.size, 0, RAWMODE[im.mode])]) fp.write(b"\0") # end of image data finally: del im.encoderinfo return fp.data
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)) # 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.5) 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] ) if im.mode == "CMYK": # invert it so it's handled correctly in Photoshop/etc. - Kevin Cazabon. im = ImageChops.invert(im) ImageFile._save(im, fp, [("jpeg", (0,0)+im.size, 0, rawmode)])
def getdata(im, offset=(0, 0), **params): """Return a list of strings representing this image. The first string is a local image header, the rest contains encoded image data.""" class Collector(object): data = [] def write(self, data): self.data.append(data) im.load() # make sure raster data is available fp = Collector() try: im.encoderinfo = params # local image header _get_local_header(fp, im, offset, 0) ImageFile._save(im, fp, [("gif", (0, 0)+im.size, 0, RAWMODE[im.mode])]) fp.write(b"\0") # end of image data finally: del im.encoderinfo return fp.data
def encode_image(img): img = img.convert('P', palette=Image.ADAPTIVE, colors=256) palbytes = img.palette.palette # assert len(img.palette) in (2, 4, 8, 0x10, 0x20, 0x40, 0x80, 0x100) imio = io.BytesIO() ImageFile._save(img, imio, [("gif", (0, 0)+img.size, 0, 'P')]) return ( # GCE b'\x21' # extension block + b'\xf9' # graphic control extension + b'\x04' # block size + b'\x00' # flags + b'\x00\x00' # frame delay + b'\x00' # transparent color index + b'\x00' # block terminator # Image headers + b'\x2c' # image block + b'\x00\x00' # image x + b'\x00\x00' # image y + struct.pack('<H', img.width) # image width + struct.pack('<H', img.height) # image height + bytes((0x80 | int(math.log(len(palbytes)//3, 2.0))-1,)) # flags (local palette and palette size) # Palette + palbytes # LZW code size + b'\x08' # Image data. We're using pillow here because I was too lazy finding a suitable LZW encoder. + imio.getbuffer() # End of image data + b'\x00' )
def _save(im, fp, filename, eps=1): """EPS Writer for the Python Imaging Library.""" # # make sure image data is available im.load() # # determine postscript image mode if im.mode == "L": operator = (8, 1, "image") elif im.mode == "RGB": operator = (8, 3, "false 3 colorimage") elif im.mode == "CMYK": operator = (8, 4, "false 4 colorimage") else: raise ValueError("image mode is not supported") class NoCloseStream: def __init__(self, fp): self.fp = fp def __getattr__(self, name): return getattr(self.fp, name) def close(self): pass base_fp = fp fp = io.TextIOWrapper(NoCloseStream(fp), encoding='latin-1') if eps: # # write EPS header fp.write("%!PS-Adobe-3.0 EPSF-3.0\n") fp.write("%%Creator: PIL 0.1 EpsEncode\n") #fp.write("%%CreationDate: %s"...) fp.write("%%%%BoundingBox: 0 0 %d %d\n" % im.size) fp.write("%%Pages: 1\n") fp.write("%%EndComments\n") fp.write("%%Page: 1 1\n") fp.write("%%ImageData: %d %d " % im.size) fp.write("%d %d 0 1 1 \"%s\"\n" % operator) # # image header fp.write("gsave\n") fp.write("10 dict begin\n") fp.write("/buf %d string def\n" % (im.size[0] * operator[1])) fp.write("%d %d scale\n" % im.size) fp.write("%d %d 8\n" % im.size) # <= bits fp.write("[%d 0 0 -%d 0 %d]\n" % (im.size[0], im.size[1], im.size[1])) fp.write("{ currentfile buf readhexstring pop } bind\n") fp.write(operator[2] + "\n") fp.flush() ImageFile._save(im, base_fp, [("eps", (0,0)+im.size, 0, None)]) fp.write("\n%%%%EndBinary\n") fp.write("grestore end\n") fp.flush()
def _save(im, fp, filename, check=0): try: rawmode, bits, colors = SAVE[im.mode] except KeyError: raise IOError("cannot write mode %s as BMP" % im.mode) if check: return check info = im.encoderinfo dpi = info.get("dpi", (96, 96)) # 1 meter == 39.3701 inches ppm = tuple(map(lambda x: int(x * 39.3701), dpi)) stride = ((im.size[0] * bits + 7) // 8 + 3) & (~3) header = 40 # or 64 for OS/2 version 2 offset = 14 + header + colors * 4 image = stride * im.size[1] # bitmap header fp.write( b"BM" + o32(offset + image) + o32(0) + o32(offset) # file type (magic) # file size # reserved ) # image data offset # bitmap info header fp.write( o32(header) + o32(im.size[0]) # info header size + o32(im.size[1]) # width + o16(1) # height + o16(bits) # planes + o32(0) # depth + o32(image) # compression (0=uncompressed) + o32(ppm[0]) # size of bitmap + o32(ppm[1]) + o32(colors) # resolution + o32(colors) # colors used ) # colors important fp.write(b"\0" * (header - 40)) # padding (for OS/2 format) if im.mode == "1": for i in (0, 255): fp.write(o8(i) * 4) elif im.mode == "L": for i in range(256): fp.write(o8(i) * 4) elif im.mode == "P": fp.write(im.im.getpalette("RGB", "BGRX")) ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, stride, -1))])
def _save(im, fp, filename): if _imaging_gif: # call external driver try: _imaging_gif.save(im, fp, filename) return except IOError: pass # write uncompressed file if im.mode in RAWMODE: im_out = im else: # convert on the fly (EXPERIMENTAL -- I'm not sure PIL # should automatically convert images on save...) if Image.getmodebase(im.mode) == "RGB": palette_size = 256 if im.palette: palette_size = len(im.palette.getdata()[1]) // 3 im_out = im.convert("P", palette=1, colors=palette_size) else: im_out = im.convert("L") # header try: palette = im.encoderinfo["palette"] except KeyError: palette = None im.encoderinfo["optimize"] = im.encoderinfo.get("optimize", True) header, used_palette_colors = getheader(im_out, palette, im.encoderinfo) for s in header: fp.write(s) flags = 0 if get_interlace(im): flags = flags | 64 # local image header get_local_header(fp, im, (0, 0), flags) im_out.encoderconfig = (8, get_interlace(im)) ImageFile._save(im_out, fp, [("gif", (0, 0)+im.size, 0, RAWMODE[im_out.mode])]) fp.write(b"\0") # end of image data fp.write(b";") # end of file try: fp.flush() except: pass
def _save(im, fp, filename, check=0): try: version, bits, planes, rawmode = SAVE[im.mode] except KeyError: raise ValueError("Cannot save %s images as PCX" % im.mode) if check: return check # bytes per plane stride = (im.size[0] * bits + 7) // 8 # stride should be even stride += stride % 2 # Stride needs to be kept in sync with the PcxEncode.c version. # Ideally it should be passed in in the state, but the bytes value # gets overwritten. if Image.DEBUG: print ("PcxImagePlugin._save: xwidth: %d, bits: %d, stride: %d" % ( im.size[0], bits, stride)) # under windows, we could determine the current screen size with # "Image.core.display_mode()[1]", but I think that's overkill... screen = im.size dpi = 100, 100 # PCX header fp.write( o8(10) + o8(version) + o8(1) + o8(bits) + o16(0) + o16(0) + o16(im.size[0]-1) + o16(im.size[1]-1) + o16(dpi[0]) + o16(dpi[1]) + b"\0"*24 + b"\xFF"*24 + b"\0" + o8(planes) + o16(stride) + o16(1) + o16(screen[0]) + o16(screen[1]) + b"\0"*54 ) assert fp.tell() == 128 ImageFile._save(im, fp, [("pcx", (0,0)+im.size, 0, (rawmode, bits*planes))]) if im.mode == "P": # colour palette fp.write(o8(12)) fp.write(im.im.getpalette("RGB", "RGB")) # 768 bytes elif im.mode == "L": # greyscale palette fp.write(o8(12)) for i in range(256): fp.write(o8(i)*3)
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(im, fp, filename): if im.mode[0] != "F": im = im.convert('F') hdr = makeSpiderHeader(im) if len(hdr) < 256: raise IOError("Error creating Spider header") # write the SPIDER header fp.writelines(hdr) rawmode = "F;32NF" # 32-bit native floating point ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 0, (rawmode, 0, 1))])
def _save(im, fp, filename): if im.mode == "1": rawmode, head = "1;I", b"P4" elif im.mode == "L": rawmode, head = "L", b"P5" elif im.mode == "RGB": rawmode, head = "RGB", b"P6" elif im.mode == "RGBA": rawmode, head = "RGB", b"P6" else: raise IOError("cannot write mode %s as PPM" % im.mode) fp.write(head + ("\n%d %d\n" % im.size).encode('ascii')) if head != b"P4": fp.write(b"255\n") ImageFile._save(im, fp, [("raw", (0,0)+im.size, 0, (rawmode, 0, 1))])
def _save(im, fp, filename): if im.mode != "1": raise IOError("cannot write mode %s as XBM" % im.mode) fp.write(("#define im_width %d\n" % im.size[0]).encode('ascii')) fp.write(("#define im_height %d\n" % im.size[1]).encode('ascii')) hotspot = im.encoderinfo.get("hotspot") if hotspot: fp.write(("#define im_x_hot %d\n" % hotspot[0]).encode('ascii')) fp.write(("#define im_y_hot %d\n" % hotspot[1]).encode('ascii')) fp.write(b"static char im_bits[] = {\n") ImageFile._save(im, fp, [("xbm", (0, 0) + im.size, 0, None)]) fp.write(b"};\n")
def _save(im, fp, filename, check=0): try: rawmode, bits, colors = SAVE[im.mode] except KeyError: raise IOError("cannot write mode %s as BMP" % im.mode) if check: return check stride = ((im.size[0]*bits+7)//8+3)&(~3) header = 40 # or 64 for OS/2 version 2 offset = 14 + header + colors * 4 image = stride * im.size[1] # bitmap header fp.write(b"BM" + # file type (magic) o32(offset+image) + # file size o32(0) + # reserved o32(offset)) # image data offset # bitmap info header fp.write(o32(header) + # info header size o32(im.size[0]) + # width o32(im.size[1]) + # height o16(1) + # planes o16(bits) + # depth o32(0) + # compression (0=uncompressed) o32(image) + # size of bitmap o32(1) + o32(1) + # resolution o32(colors) + # colors used o32(colors)) # colors important fp.write(b"\0" * (header - 40)) # padding (for OS/2 format) if im.mode == "1": for i in (0, 255): fp.write(o8(i) * 4) elif im.mode == "L": for i in range(256): fp.write(o8(i) * 4) elif im.mode == "P": fp.write(im.im.getpalette("RGB", "BGRX")) ImageFile._save(im, fp, [("raw", (0,0)+im.size, 0, (rawmode, stride, -1))])
def _save(im, fp, filename, check=0): # check if im.mode is compatible with MRC (see Bmp...) if check: return check header = MrcHeader() header['width'] = im.size[0] header['height'] = im.size[1] header['depth'] = 1 header['mode'] = pilmode_mrcmode[im.mode] header.tofile(fp) rawmode = mrcmode_rawmode[header['mode']] tile = [("raw", (0,0)+im.size, header.headerlen, (rawmode, 0, 1))] print 'savetile:', tile ImageFile._save(im, fp, tile)
def _save(im, fp, filename): if filename.endswith('.j2k'): kind = 'j2k' else: kind = 'jp2' # Get the keyword arguments info = im.encoderinfo offset = info.get('offset', None) tile_offset = info.get('tile_offset', None) tile_size = info.get('tile_size', None) quality_mode = info.get('quality_mode', 'rates') quality_layers = info.get('quality_layers', None) num_resolutions = info.get('num_resolutions', 0) cblk_size = info.get('codeblock_size', None) precinct_size = info.get('precinct_size', None) irreversible = info.get('irreversible', False) progression = info.get('progression', 'LRCP') cinema_mode = info.get('cinema_mode', 'no') fd = -1 if hasattr(fp, "fileno"): try: fd = fp.fileno() except: fd = -1 im.encoderconfig = ( offset, tile_offset, tile_size, quality_mode, quality_layers, num_resolutions, cblk_size, precinct_size, irreversible, progression, cinema_mode, fd ) ImageFile._save(im, fp, [('jpeg2k', (0, 0)+im.size, 0, kind)])
def _save(im, fp, filename, check=0): try: version, bits, planes, rawmode = SAVE[im.mode] except KeyError: raise ValueError("Cannot save %s images as PCX" % im.mode) if check: return check # bytes per plane stride = (im.size[0] * bits + 7) // 8 # under windows, we could determine the current screen size with # "Image.core.display_mode()[1]", but I think that's overkill... screen = im.size dpi = 100, 100 # PCX header fp.write( o8(10) + o8(version) + o8(1) + o8(bits) + o16(0) + o16(0) + o16(im.size[0]-1) + o16(im.size[1]-1) + o16(dpi[0]) + o16(dpi[1]) + b"\0"*24 + b"\xFF"*24 + b"\0" + o8(planes) + o16(stride) + o16(1) + o16(screen[0]) + o16(screen[1]) + b"\0"*54 ) assert fp.tell() == 128 ImageFile._save(im, fp, [("pcx", (0,0)+im.size, 0, (rawmode, bits*planes))]) if im.mode == "P": # colour palette fp.write(o8(12)) fp.write(im.im.getpalette("RGB", "RGB")) # 768 bytes elif im.mode == "L": # greyscale palette fp.write(o8(12)) for i in range(256): fp.write(o8(i)*3)
def _save(im, fp, filename, check=0): try: rawmode, bits, colormaptype, imagetype = SAVE[im.mode] except KeyError: raise IOError("cannot write mode %s as TGA" % im.mode) if check: return check if colormaptype: colormapfirst, colormaplength, colormapentry = 0, 256, 24 else: colormapfirst, colormaplength, colormapentry = 0, 0, 0 if im.mode == "RGBA": flags = 8 else: flags = 0 orientation = im.info.get("orientation", -1) if orientation > 0: flags = flags | 0x20 fp.write( b"\000" + o8(colormaptype) + o8(imagetype) + o16(colormapfirst) + o16(colormaplength) + o8(colormapentry) + o16(0) + o16(0) + o16(im.size[0]) + o16(im.size[1]) + o8(bits) + o8(flags) ) if colormaptype: fp.write(im.im.getpalette("RGB", "BGR")) ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, orientation))])
def _save(im, fp, filename): if im.mode[0] != "F": im = im.convert('F') hdr = makeSpiderHeader(im) if len(hdr) < 256: raise IOError("Error creating Spider header") # write the SPIDER header try: fp = open(filename, 'wb') except: raise IOError("Unable to open %s for writing" % filename) fp.writelines(hdr) rawmode = "F;32NF" # 32-bit native floating point ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 0, (rawmode, 0, 1))]) fp.close()
def _save(im, fp, filename, chunk=putchunk, check=0): # save an image to disk (called by the save method) mode = im.mode if mode == "P": # # attempt to minimize storage requirements for palette images if "bits" in im.encoderinfo: # number of bits specified by user n = 1 << im.encoderinfo["bits"] else: # check palette contents n = 256 # FIXME if n <= 2: bits = 1 elif n <= 4: bits = 2 elif n <= 16: bits = 4 else: bits = 8 if bits != 8: mode = "%s;%d" % (mode, bits) # encoder options if "dictionary" in im.encoderinfo: dictionary = im.encoderinfo["dictionary"] else: dictionary = b"" im.encoderconfig = ("optimize" in im.encoderinfo, im.encoderinfo.get("compress_level", -1), im.encoderinfo.get("compress_type", -1), dictionary) # get the corresponding PNG mode try: rawmode, mode = _OUTMODES[mode] except KeyError: raise IOError("cannot write mode %s as PNG" % mode) if check: return check # # write minimal PNG file fp.write(_MAGIC) chunk( fp, b"IHDR", o32(im.size[0]), o32(im.size[1]), # 0: size mode, # 8: depth/type b'\0', # 10: compression b'\0', # 11: filter category b'\0') # 12: interlace flag if im.mode == "P": palette_bytes = (2**bits) * 3 chunk(fp, b"PLTE", im.im.getpalette("RGB")[:palette_bytes]) if "transparency" in im.encoderinfo: if im.mode == "P": # limit to actual palette size alpha_bytes = 2**bits if isinstance(im.encoderinfo["transparency"], bytes): chunk(fp, b"tRNS", im.encoderinfo["transparency"][:alpha_bytes]) else: transparency = max(0, min(255, im.encoderinfo["transparency"])) alpha = b'\xFF' * transparency + b'\0' chunk(fp, b"tRNS", alpha[:alpha_bytes]) elif im.mode == "L": transparency = max(0, min(65535, im.encoderinfo["transparency"])) chunk(fp, b"tRNS", o16(transparency)) elif im.mode == "RGB": red, green, blue = im.encoderinfo["transparency"] chunk(fp, b"tRNS", o16(red) + o16(green) + o16(blue)) else: raise IOError("cannot use transparency for this mode") else: if im.mode == "P" and im.im.getpalettemode() == "RGBA": alpha = im.im.getpalette("RGBA", "A") alpha_bytes = 2**bits chunk(fp, b"tRNS", alpha[:alpha_bytes]) if 0: # FIXME: to be supported some day chunk(fp, b"gAMA", o32(int(gamma * 100000.0))) dpi = im.encoderinfo.get("dpi") if dpi: chunk(fp, b"pHYs", o32(int(dpi[0] / 0.0254 + 0.5)), o32(int(dpi[1] / 0.0254 + 0.5)), b'\x01') info = im.encoderinfo.get("pnginfo") if info: for cid, data in info.chunks: chunk(fp, cid, data) # ICC profile writing support -- 2008-06-06 Florian Hoech if "icc_profile" in im.info: # ICC profile # according to PNG spec, the iCCP chunk contains: # Profile name 1-79 bytes (character string) # Null separator 1 byte (null character) # Compression method 1 byte (0) # Compressed profile n bytes (zlib with deflate compression) try: import ICCProfile p = ICCProfile.ICCProfile(im.info["icc_profile"]) name = p.tags.desc.get( "ASCII", p.tags.desc.get( "Unicode", p.tags.desc.get( "Macintosh", p.tags.desc.get("en", {}).get("US", "ICC Profile")))).encode( "latin1", "replace")[:79] except ImportError: name = b"ICC Profile" data = name + b"\0\0" + zlib.compress(im.info["icc_profile"]) chunk(fp, b"iCCP", data) ImageFile._save(im, _idat(fp, chunk), [("zip", (0, 0) + im.size, 0, rawmode)]) chunk(fp, b"IEND", b"") try: fp.flush() except: pass
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 _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): if _imaging_gif: # call external driver try: _imaging_gif.save(im, fp, filename) return except IOError: pass # write uncompressed file try: rawmode = RAWMODE[im.mode] imOut = im except KeyError: # convert on the fly (EXPERIMENTAL -- I'm not sure PIL # should automatically convert images on save...) if Image.getmodebase(im.mode) == "RGB": imOut = im.convert("P") rawmode = "P" else: imOut = im.convert("L") rawmode = "L" # header try: palette = im.encoderinfo["palette"] except KeyError: palette = None header, usedPaletteColors = getheader(imOut, palette, im.encoderinfo) for s in header: fp.write(s) flags = 0 try: interlace = im.encoderinfo["interlace"] except KeyError: interlace = 1 # workaround for @PIL153 if min(im.size) < 16: interlace = 0 if interlace: flags = flags | 64 try: transparency = im.encoderinfo["transparency"] except KeyError: pass else: transparency = int(transparency) # optimize the block away if transparent color is not used transparentColorExists = True # adjust the transparency index after optimize if usedPaletteColors is not None and len(usedPaletteColors) < 256: for i in range(len(usedPaletteColors)): if usedPaletteColors[i] == transparency: transparency = i transparentColorExists = True break else: transparentColorExists = False # transparency extension block if transparentColorExists: fp.write(b"!" + o8(249) + # extension intro o8(4) + # length o8(1) + # transparency info present o16(0) + # duration o8(transparency) # transparency index + o8(0)) # local image header fp.write(b"," + o16(0) + o16(0) + # bounding box o16(im.size[0]) + # size o16(im.size[1]) + o8(flags) + # flags o8(8)) # bits imOut.encoderconfig = (8, interlace) ImageFile._save(imOut, fp, [("gif", (0, 0) + im.size, 0, rawmode)]) fp.write(b"\0") # end of image data fp.write(b";") # end of file try: fp.flush() except: pass
def _save(im, fp, filename): resolution = im.encoderinfo.get("resolution", 72.0) # # make sure image data is available im.load() xref = [0] * (5 + 1) # placeholders class TextWriter: def __init__(self, fp): self.fp = fp def __getattr__(self, name): return getattr(self.fp, name) def write(self, value): self.fp.write(value.encode('latin-1')) fp = TextWriter(fp) fp.write("%PDF-1.2\n") fp.write("% created by PIL PDF driver " + __version__ + "\n") # # Get image characteristics width, height = im.size # FIXME: Should replace ASCIIHexDecode with RunLengthDecode (packbits) # or LZWDecode (tiff/lzw compression). Note that PDF 1.2 also supports # Flatedecode (zip compression). bits = 8 params = None if im.mode == "1": filter = "/ASCIIHexDecode" colorspace = "/DeviceGray" procset = "/ImageB" # grayscale bits = 1 elif im.mode == "L": filter = "/DCTDecode" # params = "<< /Predictor 15 /Columns %d >>" % (width-2) colorspace = "/DeviceGray" procset = "/ImageB" # grayscale elif im.mode == "P": filter = "/ASCIIHexDecode" colorspace = "[ /Indexed /DeviceRGB 255 <" palette = im.im.getpalette("RGB") for i in range(256): r = i8(palette[i * 3]) g = i8(palette[i * 3 + 1]) b = i8(palette[i * 3 + 2]) colorspace = colorspace + "%02x%02x%02x " % (r, g, b) colorspace = colorspace + b"> ]" procset = "/ImageI" # indexed color elif im.mode == "RGB": filter = "/DCTDecode" colorspace = "/DeviceRGB" procset = "/ImageC" # color images elif im.mode == "CMYK": filter = "/DCTDecode" colorspace = "/DeviceCMYK" procset = "/ImageC" # color images else: raise ValueError("cannot save mode %s" % im.mode) # # catalogue xref[1] = fp.tell() _obj(fp, 1, Type="/Catalog", Pages="2 0 R") _endobj(fp) # # pages xref[2] = fp.tell() _obj(fp, 2, Type="/Pages", Count=1, Kids="[4 0 R]") _endobj(fp) # # image op = io.BytesIO() if filter == "/ASCIIHexDecode": if bits == 1: # FIXME: the hex encoder doesn't support packed 1-bit # images; do things the hard way... data = im.tostring("raw", "1") im = Image.new("L", (len(data), 1), None) im.putdata(data) ImageFile._save(im, op, [("hex", (0, 0) + im.size, 0, im.mode)]) elif filter == "/DCTDecode": Image.SAVE["JPEG"](im, op, filename) elif filter == "/FlateDecode": ImageFile._save(im, op, [("zip", (0, 0) + im.size, 0, im.mode)]) elif filter == "/RunLengthDecode": ImageFile._save(im, op, [("packbits", (0, 0) + im.size, 0, im.mode)]) else: raise ValueError("unsupported PDF filter (%s)" % filter) xref[3] = fp.tell() _obj( fp, 3, Type="/XObject", Subtype="/Image", Width=width, # * 72.0 / resolution, Height=height, # * 72.0 / resolution, Length=len(op.getvalue()), Filter=filter, BitsPerComponent=bits, DecodeParams=params, ColorSpace=colorspace) fp.write("stream\n") fp.fp.write(op.getvalue()) fp.write("\nendstream\n") _endobj(fp) # # page xref[4] = fp.tell() _obj(fp, 4) fp.write("<<\n/Type /Page\n/Parent 2 0 R\n"\ "/Resources <<\n/ProcSet [ /PDF %s ]\n"\ "/XObject << /image 3 0 R >>\n>>\n"\ "/MediaBox [ 0 0 %d %d ]\n/Contents 5 0 R\n>>\n" %\ (procset, int(width * 72.0 /resolution) , int(height * 72.0 / resolution))) _endobj(fp) # # page contents op = TextWriter(io.BytesIO()) op.write("q %d 0 0 %d 0 0 cm /image Do Q\n" % (int(width * 72.0 / resolution), int(height * 72.0 / resolution))) xref[5] = fp.tell() _obj(fp, 5, Length=len(op.fp.getvalue())) fp.write("stream\n") fp.fp.write(op.fp.getvalue()) fp.write("\nendstream\n") _endobj(fp) # # trailer startxref = fp.tell() fp.write("xref\n0 %d\n0000000000 65535 f \n" % len(xref)) for x in xref[1:]: fp.write("%010d 00000 n \n" % x) fp.write("trailer\n<<\n/Size %d\n/Root 1 0 R\n>>\n" % len(xref)) fp.write("startxref\n%d\n%%%%EOF\n" % startxref) fp.flush()
def _save(im, fp, filename, save_all=False): im.encoderinfo.update(im.info) if _imaging_gif: # call external driver try: _imaging_gif.save(im, fp, filename) return except IOError: pass # write uncompressed file if im.mode in RAWMODE: im_out = im.copy() else: im_out = _convert_mode(im, True) # header try: palette = im.encoderinfo["palette"] except KeyError: palette = None im.encoderinfo["optimize"] = im.encoderinfo.get("optimize", True) if save_all: previous = None first_frame = None for im_frame in ImageSequence.Iterator(im): im_frame = _convert_mode(im_frame) # To specify duration, add the time in milliseconds to getdata(), # e.g. getdata(im_frame, duration=1000) if not previous: # global header first_frame = getheader(im_frame, palette, im.encoderinfo)[0] first_frame += getdata(im_frame, (0, 0), **im.encoderinfo) else: if first_frame: for s in first_frame: fp.write(s) first_frame = None # delta frame delta = ImageChops.subtract_modulo(im_frame, previous.copy()) bbox = delta.getbbox() if bbox: # compress difference for s in getdata(im_frame.crop(bbox), bbox[:2], **im.encoderinfo): fp.write(s) else: # FIXME: what should we do in this case? pass previous = im_frame if first_frame: save_all = False if not save_all: header = getheader(im_out, palette, im.encoderinfo)[0] for s in header: fp.write(s) flags = 0 if get_interlace(im): flags = flags | 64 # local image header _get_local_header(fp, im, (0, 0), flags) im_out.encoderconfig = (8, get_interlace(im)) ImageFile._save(im_out, fp, [("gif", (0, 0) + im.size, 0, RAWMODE[im_out.mode])]) fp.write(b"\0") # end of image data fp.write(b";") # end of file if hasattr(fp, "flush"): fp.flush()
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_v2(prefix=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) 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 DEBUG: print("Tiffinfo Keys: %s" % list(info)) if isinstance(info, ImageFileDirectory_v1): info = info.to_v2() for key in info: 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_v2'): # 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_v2: ifd[key] = im.tag_v2[key] ifd.tagtype[key] = im.tag_v2.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 in [(IMAGEDESCRIPTION, "description"), (X_RESOLUTION, "resolution"), (Y_RESOLUTION, "resolution"), (X_RESOLUTION, "x_resolution"), (Y_RESOLUTION, "y_resolution"), (RESOLUTION_UNIT, "resolution_unit"), (SOFTWARE, "software"), (DATE_TIME, "date_time"), (ARTIST, "artist"), (COPYRIGHT, "copyright")]: 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] = im.encoderinfo[name.replace("_", " ")] if name in im.encoderinfo: ifd[key] = im.encoderinfo[name] dpi = im.encoderinfo.get("dpi") if dpi: ifd[RESOLUTION_UNIT] = 2 ifd[X_RESOLUTION] = dpi[0] ifd[Y_RESOLUTION] = 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 DEBUG: print("Saving using libtiff encoder") print("Items: %s" % sorted(ifd.items())) _fp = 0 if hasattr(fp, "fileno"): try: fp.seek(0) _fp = os.dup(fp.fileno()) except io.UnsupportedOperation: pass # STRIPOFFSETS and STRIPBYTECOUNTS are added by the library # based on the data in the strip. # ICCPROFILE crashes. blocklist = [STRIPOFFSETS, STRIPBYTECOUNTS, 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. legacy_ifd = {} if hasattr(im, 'tag'): legacy_ifd = im.tag.to_v2() for k, v in itertools.chain(ifd.items(), getattr(im, 'tag_v2', {}).items(), legacy_ifd.items()): if k not in atts and k not in blocklist: if isinstance(v, unicode if bytes is str else str): atts[k] = v.encode('ascii', 'replace') + b"\0" else: atts[k] = v if DEBUG: print("Converted items: %s" % sorted(atts.items())) # 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_frame(im, fp, cert_flag): # Return bytes for one frame try: info = im.encoderinfo except: im.encoderinfo = {} info = im.encoderinfo image_data = io.BytesIO() ns = im.header if im.mode == 'L': bit_depth = 8 else: bit_depth = 24 if ns['image_compression_algo'] == "RAW": im.encoderconfig = () encoder = ('raw', (0, 0) + im.size, 0, (im.mode, 0, 1)) bit_depth = 8 ImageFile._save(im, image_data, [encoder]) elif ns['image_compression_algo'] == "RAW_PACKED": im.encoderconfig() encoder = ('raw', (0, 0) + im.size, 0, (im.mode, 0, 1)) bit_depth = 8 ImageFile._save(im, image_data, [encoder]) elif ns['image_compression_algo'] == "WSQ": im.encoderconfig() encoder = ('wsq', (0, 0) + im.size, 0, (12, )) bit_depth = 8 ImageFile._save(im, image_data, [encoder]) elif ns['image_compression_algo'] == "JPEG": info['quality'] = 'maximum' info['dpi'] = (im.header.get('horizontal_image_sampling_rate', 500), im.header.get('vertical_image_sampling_rate', 500)) PIL.JpegImagePlugin._save(im, image_data, "") elif ns['image_compression_algo'] == "JPEG2000_LOSSY": info['quality_mode'] = "rates" info['quality_layers'] = (15, ) # XXX parameters needed? (up to 15 compression max according to the specs) PIL.Jpeg2KImagePlugin._save(im, image_data, "non.j2k") elif ns['image_compression_algo'] == "JPEG2000_LOSSLESS": info['quality_mode'] = "rates" info['quality_layers'] = (0, ) # XXX parameters needed? (up to 15 compression max according to the specs) PIL.Jpeg2KImagePlugin._save(im, image_data, "non.j2k") else: raise SyntaxError("Unknown compression algo " + ns['image_compression_algo']) image_data = image_data.getvalue() dt = ns.get('capture_datetime', datetime.datetime.now()) rheader = b'' rheader += struct.pack(">HBBBBBH", dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, int(dt.microsecond / 1000)) rheader += struct.pack(">s2s2sB", ns.get('capture_device_technology_id', b'\x00'), ns.get('capture_device_vendor_id', b'\x00\x00'), ns.get('capture_device_type_id', b'\x00\x00'), len(ns.get('quality_records', []))) for q in ns.get('quality_records', []): rheader += struct.pack(">B2s2s", q.score, q.algo_vendor_id, q.algo_id) if cert_flag: rheader += struct.pack(">B", len(ns.get('certification_records', []))) for c in ns.get('certification_records', []): rheader += struct.pack(">2s1s", c.authority_id, c.scheme_id) rheader += struct.pack( ">BBBHHHHBBBHHI", POSITION[ns.get('position', 'UNKNOWN')], ns['number'], UNIT[ns.get('scale_units', 'PPI')], ns.get('horizontal_scan_sampling_rate', 500), ns.get('vertical_scan_sampling_rate', 500), ns.get('horizontal_image_sampling_rate', 500), ns.get('vertical_image_sampling_rate', 500), bit_depth, COMPRESSION[ns.get('image_compression_algo', 'RAW')], IMPRESSION[ns.get('impression_type', 'UNKNOWN')], im.size[0], im.size[1], len(image_data)) # Write the frame fp.write(struct.pack(">I", 4 + len(rheader) + len(image_data))) fp.write(rheader) fp.write(image_data)
def _save(im, fp, filename, save_all=False): im.encoderinfo.update(im.info) if _imaging_gif: # call external driver try: _imaging_gif.save(im, fp, filename) return except IOError: pass # write uncompressed file if im.mode in RAWMODE: im_out = im.copy() else: im_out = _convert_mode(im, True) # header try: palette = im.encoderinfo["palette"] except KeyError: palette = None im.encoderinfo["optimize"] = im.encoderinfo.get("optimize", True) if save_all: previous = None first_frame = None append_images = im.encoderinfo.get("append_images", []) if "duration" in im.encoderinfo: duration = im.encoderinfo["duration"] else: duration = None frame_count = 0 for imSequence in [im]+append_images: for im_frame in ImageSequence.Iterator(imSequence): encoderinfo = im.encoderinfo.copy() im_frame = _convert_mode(im_frame) if isinstance(duration, (list, tuple)): encoderinfo["duration"] = duration[frame_count] frame_count += 1 # To specify duration, add the time in milliseconds to getdata(), # e.g. getdata(im_frame, duration=1000) if not previous: # global header first_frame = getheader(im_frame, palette, encoderinfo)[0] first_frame += getdata(im_frame, (0, 0), **encoderinfo) else: if first_frame: for s in first_frame: fp.write(s) first_frame = None # delta frame delta = ImageChops.subtract_modulo(im_frame, previous.copy()) bbox = delta.getbbox() if bbox: # compress difference encoderinfo['include_color_table'] = True for s in getdata(im_frame.crop(bbox), bbox[:2], **encoderinfo): fp.write(s) else: # FIXME: what should we do in this case? pass previous = im_frame if first_frame: save_all = False if not save_all: header = getheader(im_out, palette, im.encoderinfo)[0] for s in header: fp.write(s) flags = 0 if get_interlace(im): flags = flags | 64 # local image header _get_local_header(fp, im, (0, 0), flags) im_out.encoderconfig = (8, get_interlace(im)) ImageFile._save(im_out, fp, [("gif", (0, 0)+im.size, 0, RAWMODE[im_out.mode])]) fp.write(b"\0") # end of image data fp.write(b";") # end of file if hasattr(fp, "flush"): fp.flush()
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 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 isinstance(qtables, basestring): 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 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 = extra + (b"\xFF\xE2" + size + b"ICC_PROFILE\0" + o8(i) + o8(len(markers)) + marker) i = 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"")) ImageFile._save(im, fp, [("jpeg", (0, 0) + im.size, 0, rawmode)])
def _save(im, fp, filename): if _imaging_gif: # call external driver try: _imaging_gif.save(im, fp, filename) return except IOError: pass # write uncompressed file try: rawmode = RAWMODE[im.mode] imOut = im except KeyError: # convert on the fly (EXPERIMENTAL -- I'm not sure PIL # should automatically convert images on save...) if Image.getmodebase(im.mode) == "RGB": palette_size = 256 if im.palette: palette_size = len(im.palette.getdata()[1]) // 3 imOut = im.convert("P", palette=1, colors=palette_size) rawmode = "P" else: imOut = im.convert("L") rawmode = "L" # header try: palette = im.encoderinfo["palette"] except KeyError: palette = None im.encoderinfo["optimize"] = im.encoderinfo.get("optimize", True) if im.encoderinfo["optimize"]: # When the mode is L, and we optimize, we end up with # im.mode == P and rawmode = L, which fails. # If we're optimizing the palette, we're going to be # in a rawmode of P anyway. rawmode = 'P' header, usedPaletteColors = getheader(imOut, palette, im.encoderinfo) for s in header: fp.write(s) flags = 0 try: interlace = im.encoderinfo["interlace"] except KeyError: interlace = 1 # workaround for @PIL153 if min(im.size) < 16: interlace = 0 if interlace: flags = flags | 64 try: transparency = im.encoderinfo["transparency"] except KeyError: pass else: transparency = int(transparency) # optimize the block away if transparent color is not used transparentColorExists = True # adjust the transparency index after optimize if usedPaletteColors is not None and len(usedPaletteColors) < 256: for i in range(len(usedPaletteColors)): if usedPaletteColors[i] == transparency: transparency = i transparentColorExists = True break else: transparentColorExists = False # transparency extension block if transparentColorExists: fp.write(b"!" + o8(249) + # extension intro o8(4) + # length o8(1) + # transparency info present o16(0) + # duration o8(transparency) # transparency index + o8(0)) # local image header fp.write(b"," + o16(0) + o16(0) + # bounding box o16(im.size[0]) + # size o16(im.size[1]) + o8(flags) + # flags o8(8)) # bits imOut.encoderconfig = (8, interlace) ImageFile._save(imOut, fp, [("gif", (0,0)+im.size, 0, rawmode)]) fp.write(b"\0") # end of image data fp.write(b";") # end of file try: fp.flush() except: pass
def _save(im, fp, filename): try: rawmode, byteorder, photo, format, bits, extra = SAVE_INFO[im.mode] except KeyError: raise IOError("cannot write mode %s as TIFF" % im.mode) ifd = ImageFileDirectory(BYTEORDER_TO_PREFIX[byteorder]) #seb -- multi-page -- added `if` if 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)) #seb debug print "DEBUG: 000", fp.tell() 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 im.tag.tagdata.has_key(key): ifd[key] = im.tag.tagdata.get(key) if im.encoderinfo.has_key("description"): ifd[IMAGEDESCRIPTION] = im.encoderinfo["description"] if im.encoderinfo.has_key("resolution"): ifd[X_RESOLUTION] = ifd[Y_RESOLUTION] \ = _cvt_res(im.encoderinfo["resolution"]) if im.encoderinfo.has_key("x resolution"): ifd[X_RESOLUTION] = _cvt_res(im.encoderinfo["x resolution"]) if im.encoderinfo.has_key("y resolution"): ifd[Y_RESOLUTION] = _cvt_res(im.encoderinfo["y resolution"]) if im.encoderinfo.has_key("resolution unit"): 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 im.encoderinfo.has_key("software"): ifd[SOFTWARE] = im.encoderinfo["software"] if im.encoderinfo.has_key("date time"): ifd[DATE_TIME] = im.encoderinfo["date time"] if im.encoderinfo.has_key("artist"): ifd[ARTIST] = im.encoderinfo["artist"] if im.encoderinfo.has_key("copyright"): 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(map(lambda v: ord(v) * 256, 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] = 1 # no compression offset = ifd.save(fp) ImageFile._save(im, fp, [("raw", (0, 0) + im.size, offset, (rawmode, stride, 1))]) #seb -- multi-page -- if im.encoderinfo.has_key("_debug_multipage"): #just to access o32 and o16 (using correct byte order) im._debug_multipage = ifd
def _save(im, fp, filename, eps=1): """EPS Writer for the Python Imaging Library.""" # # make sure image data is available im.load() # # determine postscript image mode if im.mode == "L": operator = (8, 1, "image") elif im.mode == "RGB": operator = (8, 3, "false 3 colorimage") elif im.mode == "CMYK": operator = (8, 4, "false 4 colorimage") else: raise ValueError("image mode is not supported") class NoCloseStream: def __init__(self, fp): self.fp = fp def __getattr__(self, name): return getattr(self.fp, name) def close(self): pass base_fp = fp fp = NoCloseStream(fp) if sys.version_info[0] > 2: fp = io.TextIOWrapper(fp, encoding='latin-1') if eps: # # write EPS header fp.write("%!PS-Adobe-3.0 EPSF-3.0\n") fp.write("%%Creator: PIL 0.1 EpsEncode\n") # fp.write("%%CreationDate: %s"...) fp.write("%%%%BoundingBox: 0 0 %d %d\n" % im.size) fp.write("%%Pages: 1\n") fp.write("%%EndComments\n") fp.write("%%Page: 1 1\n") fp.write("%%ImageData: %d %d " % im.size) fp.write("%d %d 0 1 1 \"%s\"\n" % operator) # # image header fp.write("gsave\n") fp.write("10 dict begin\n") fp.write("/buf %d string def\n" % (im.size[0] * operator[1])) fp.write("%d %d scale\n" % im.size) fp.write("%d %d 8\n" % im.size) # <= bits fp.write("[%d 0 0 -%d 0 %d]\n" % (im.size[0], im.size[1], im.size[1])) fp.write("{ currentfile buf readhexstring pop } bind\n") fp.write(operator[2] + "\n") fp.flush() ImageFile._save(im, base_fp, [("eps", (0, 0)+im.size, 0, None)]) fp.write("\n%%%%EndBinary\n") fp.write("grestore end\n") fp.flush()
def _save(im, fp, filename, chunk=putchunk, check=0): # save an image to disk (called by the save method) mode = im.mode if mode == "P": # # attempt to minimize storage requirements for palette images if "bits" in im.encoderinfo: # number of bits specified by user n = 1 << im.encoderinfo["bits"] else: # check palette contents n = 256 # FIXME if n <= 2: bits = 1 elif n <= 4: bits = 2 elif n <= 16: bits = 4 else: bits = 8 if bits != 8: mode = "%s;%d" % (mode, bits) # encoder options if "dictionary" in im.encoderinfo: dictionary = im.encoderinfo["dictionary"] else: dictionary = b"" im.encoderconfig = ("optimize" in im.encoderinfo, dictionary) # get the corresponding PNG mode try: rawmode, mode = _OUTMODES[mode] except KeyError: raise IOError("cannot write mode %s as PNG" % mode) if check: return check # # write minimal PNG file fp.write(_MAGIC) chunk(fp, b"IHDR", o32(im.size[0]), o32(im.size[1]), # 0: size mode, # 8: depth/type b'\0', # 10: compression b'\0', # 11: filter category b'\0') # 12: interlace flag if im.mode == "P": chunk(fp, b"PLTE", im.im.getpalette("RGB")) if "transparency" in im.encoderinfo: if im.mode == "P": transparency = max(0, min(255, im.encoderinfo["transparency"])) chunk(fp, b"tRNS", b'\xFF' * transparency + b'\0') elif im.mode == "L": transparency = max(0, min(65535, im.encoderinfo["transparency"])) chunk(fp, b"tRNS", o16(transparency)) elif im.mode == "RGB": red, green, blue = im.encoderinfo["transparency"] chunk(fp, b"tRNS", o16(red) + o16(green) + o16(blue)) else: raise IOError("cannot use transparency for this mode") if 0: # FIXME: to be supported some day chunk(fp, b"gAMA", o32(int(gamma * 100000.0))) dpi = im.encoderinfo.get("dpi") if dpi: chunk(fp, b"pHYs", o32(int(dpi[0] / 0.0254 + 0.5)), o32(int(dpi[1] / 0.0254 + 0.5)), b'\x01') info = im.encoderinfo.get("pnginfo") if info: for cid, data in info.chunks: chunk(fp, cid, data) # ICC profile writing support -- 2008-06-06 Florian Hoech if "icc_profile" in im.info: # ICC profile # according to PNG spec, the iCCP chunk contains: # Profile name 1-79 bytes (character string) # Null separator 1 byte (null character) # Compression method 1 byte (0) # Compressed profile n bytes (zlib with deflate compression) try: import ICCProfile p = ICCProfile.ICCProfile(im.info["icc_profile"]) name = p.tags.desc.get("ASCII", p.tags.desc.get("Unicode", p.tags.desc.get("Macintosh", p.tags.desc.get("en", {}).get("US", "ICC Profile")))).encode("latin1", "replace")[:79] except ImportError: name = b"ICC Profile" data = name + b"\0\0" + zlib.compress(im.info["icc_profile"]) chunk(fp, b"iCCP", data) ImageFile._save(im, _idat(fp, chunk), [("zip", (0,0)+im.size, 0, rawmode)]) chunk(fp, b"IEND", b"") try: fp.flush() except: pass
def _save(im, fp, filename, chunk=putchunk, check=0): # save an image to disk (called by the save method) mode = im.mode if mode == "P": # # attempt to minimize storage requirements for palette images if "bits" in im.encoderinfo: # number of bits specified by user colors = 1 << im.encoderinfo["bits"] else: # check palette contents if im.palette: colors = max(min(len(im.palette.getdata()[1])//3, 256), 2) else: colors = 256 if colors <= 2: bits = 1 elif colors <= 4: bits = 2 elif colors <= 16: bits = 4 else: bits = 8 if bits != 8: mode = "%s;%d" % (mode, bits) # encoder options if "dictionary" in im.encoderinfo: dictionary = im.encoderinfo["dictionary"] else: dictionary = b"" im.encoderconfig = ("optimize" in im.encoderinfo, im.encoderinfo.get("compress_level", -1), im.encoderinfo.get("compress_type", -1), dictionary) # get the corresponding PNG mode try: rawmode, mode = _OUTMODES[mode] except KeyError: raise IOError("cannot write mode %s as PNG" % mode) if check: return check # # write minimal PNG file fp.write(_MAGIC) chunk(fp, b"IHDR", o32(im.size[0]), o32(im.size[1]), # 0: size mode, # 8: depth/type b'\0', # 10: compression b'\0', # 11: filter category b'\0') # 12: interlace flag if im.mode == "P": palette_byte_number = (2 ** bits) * 3 palette_bytes = im.im.getpalette("RGB")[:palette_byte_number] while len(palette_bytes) < palette_byte_number: palette_bytes += b'\0' chunk(fp, b"PLTE", palette_bytes) transparency = im.encoderinfo.get('transparency', im.info.get('transparency', None)) if transparency or transparency == 0: if im.mode == "P": # limit to actual palette size alpha_bytes = 2**bits if isinstance(transparency, bytes): chunk(fp, b"tRNS", transparency[:alpha_bytes]) else: transparency = max(0, min(255, transparency)) alpha = b'\xFF' * transparency + b'\0' chunk(fp, b"tRNS", alpha[:alpha_bytes]) elif im.mode == "L": transparency = max(0, min(65535, transparency)) chunk(fp, b"tRNS", o16(transparency)) elif im.mode == "RGB": red, green, blue = transparency chunk(fp, b"tRNS", o16(red) + o16(green) + o16(blue)) else: if "transparency" in im.encoderinfo: # don't bother with transparency if it's an RGBA # and it's in the info dict. It's probably just stale. raise IOError("cannot use transparency for this mode") else: if im.mode == "P" and im.im.getpalettemode() == "RGBA": alpha = im.im.getpalette("RGBA", "A") alpha_bytes = 2**bits chunk(fp, b"tRNS", alpha[:alpha_bytes]) dpi = im.encoderinfo.get("dpi") if dpi: chunk(fp, b"pHYs", o32(int(dpi[0] / 0.0254 + 0.5)), o32(int(dpi[1] / 0.0254 + 0.5)), b'\x01') info = im.encoderinfo.get("pnginfo") if info: for cid, data in info.chunks: chunk(fp, cid, data) # ICC profile writing support -- 2008-06-06 Florian Hoech if im.info.get("icc_profile"): # ICC profile # according to PNG spec, the iCCP chunk contains: # Profile name 1-79 bytes (character string) # Null separator 1 byte (null character) # Compression method 1 byte (0) # Compressed profile n bytes (zlib with deflate compression) name = b"ICC Profile" data = name + b"\0\0" + zlib.compress(im.info["icc_profile"]) chunk(fp, b"iCCP", data) ImageFile._save(im, _idat(fp, chunk), [("zip", (0, 0)+im.size, 0, rawmode)]) chunk(fp, b"IEND", b"") if hasattr(fp, "flush"): fp.flush()
def _save(im, fp, filename): if _imaging_gif: # call external driver try: _imaging_gif.save(im, fp, filename) return except IOError: pass # write uncompressed file try: rawmode = RAWMODE[im.mode] imOut = im except KeyError: # convert on the fly (EXPERIMENTAL -- I'm not sure PIL # should automatically convert images on save...) if Image.getmodebase(im.mode) == "RGB": imOut = im.convert("P") rawmode = "P" else: imOut = im.convert("L") rawmode = "L" # header try: palette = im.encoderinfo["palette"] except KeyError: palette = None if im.palette: # use existing if possible palette = im.palette.getdata()[1] header, usedPaletteColors = getheader(imOut, palette, im.encoderinfo) for s in header: fp.write(s) flags = 0 try: interlace = im.encoderinfo["interlace"] except KeyError: interlace = 1 # workaround for @PIL153 if min(im.size) < 16: interlace = 0 if interlace: flags = flags | 64 try: transparency = im.encoderinfo["transparency"] except KeyError: pass else: transparency = int(transparency) # optimize the block away if transparent color is not used transparentColorExists = True # adjust the transparency index after optimize if usedPaletteColors is not None and len(usedPaletteColors) < 256: for i in range(len(usedPaletteColors)): if usedPaletteColors[i] == transparency: transparency = i transparentColorExists = True break else: transparentColorExists = False # transparency extension block if transparentColorExists: fp.write( b"!" + o8(249) + o8(4) # extension intro + o8(1) # length + o16(0) # transparency info present + o8(transparency) # duration # transparency index + o8(0) ) # local image header fp.write( b"," + o16(0) + o16(0) + o16(im.size[0]) + o16(im.size[1]) + o8(flags) + o8(8) # bounding box # size # flags ) # bits imOut.encoderconfig = (8, interlace) ImageFile._save(imOut, fp, [("gif", (0, 0) + im.size, 0, rawmode)]) fp.write(b"\0") # end of image data fp.write(b";") # end of file try: fp.flush() except: pass
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 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 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 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 = extra + (b"\xFF\xE2" + size + b"ICC_PROFILE\0" + o8(i) + o8(len(markers)) + marker) i = 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: if quality >= 95: 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, chunk=putchunk, check=0): # save an image to disk (called by the save method) mode = im.mode if mode == "P": # # attempt to minimize storage requirements for palette images if "bits" in im.encoderinfo: # number of bits specified by user colors = 1 << im.encoderinfo["bits"] else: # check palette contents if im.palette: colors = max(min(len(im.palette.getdata()[1]) // 3, 256), 2) else: colors = 256 if colors <= 2: bits = 1 elif colors <= 4: bits = 2 elif colors <= 16: bits = 4 else: bits = 8 if bits != 8: mode = "%s;%d" % (mode, bits) # encoder options if "dictionary" in im.encoderinfo: dictionary = im.encoderinfo["dictionary"] else: dictionary = b"" im.encoderconfig = ( "optimize" in im.encoderinfo, im.encoderinfo.get("compress_level", -1), im.encoderinfo.get("compress_type", -1), dictionary, ) # get the corresponding PNG mode try: rawmode, mode = _OUTMODES[mode] except KeyError: raise IOError("cannot write mode %s as PNG" % mode) if check: return check # # write minimal PNG file fp.write(_MAGIC) chunk( fp, b"IHDR", o32(im.size[0]), o32(im.size[1]), # 0: size mode, # 8: depth/type b"\0", # 10: compression b"\0", # 11: filter category b"\0", ) # 12: interlace flag if im.mode == "P": palette_byte_number = (2 ** bits) * 3 palette_bytes = im.im.getpalette("RGB")[:palette_byte_number] while len(palette_bytes) < palette_byte_number: palette_bytes += b"\0" chunk(fp, b"PLTE", palette_bytes) transparency = im.encoderinfo.get("transparency", im.info.get("transparency", None)) if transparency or transparency == 0: if im.mode == "P": # limit to actual palette size alpha_bytes = 2 ** bits if isinstance(transparency, bytes): chunk(fp, b"tRNS", transparency[:alpha_bytes]) else: transparency = max(0, min(255, transparency)) alpha = b"\xFF" * transparency + b"\0" chunk(fp, b"tRNS", alpha[:alpha_bytes]) elif im.mode == "L": transparency = max(0, min(65535, transparency)) chunk(fp, b"tRNS", o16(transparency)) elif im.mode == "RGB": red, green, blue = transparency chunk(fp, b"tRNS", o16(red) + o16(green) + o16(blue)) else: if "transparency" in im.encoderinfo: # don't bother with transparency if it's an RGBA # and it's in the info dict. It's probably just stale. raise IOError("cannot use transparency for this mode") else: if im.mode == "P" and im.im.getpalettemode() == "RGBA": alpha = im.im.getpalette("RGBA", "A") alpha_bytes = 2 ** bits chunk(fp, b"tRNS", alpha[:alpha_bytes]) dpi = im.encoderinfo.get("dpi") if dpi: chunk(fp, b"pHYs", o32(int(dpi[0] / 0.0254 + 0.5)), o32(int(dpi[1] / 0.0254 + 0.5)), b"\x01") info = im.encoderinfo.get("pnginfo") if info: for cid, data in info.chunks: chunk(fp, cid, data) # ICC profile writing support -- 2008-06-06 Florian Hoech if im.info.get("icc_profile"): # ICC profile # according to PNG spec, the iCCP chunk contains: # Profile name 1-79 bytes (character string) # Null separator 1 byte (null character) # Compression method 1 byte (0) # Compressed profile n bytes (zlib with deflate compression) name = b"ICC Profile" data = name + b"\0\0" + zlib.compress(im.info["icc_profile"]) chunk(fp, b"iCCP", data) ImageFile._save(im, _idat(fp, chunk), [("zip", (0, 0) + im.size, 0, rawmode)]) chunk(fp, b"IEND", b"") if hasattr(fp, "flush"): fp.flush()
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(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_v2(prefix=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) 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 DEBUG: print("Tiffinfo Keys: %s" % list(info)) if isinstance(info, ImageFileDirectory_v1): info = info.to_v2() for key in info: 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_v2'): # 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_v2: ifd[key] = im.tag_v2[key] ifd.tagtype[key] = im.tag_v2.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 in [(IMAGEDESCRIPTION, "description"), (X_RESOLUTION, "resolution"), (Y_RESOLUTION, "resolution"), (X_RESOLUTION, "x_resolution"), (Y_RESOLUTION, "y_resolution"), (RESOLUTION_UNIT, "resolution_unit"), (SOFTWARE, "software"), (DATE_TIME, "date_time"), (ARTIST, "artist"), (COPYRIGHT, "copyright")]: 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] = im.encoderinfo[name.replace("_", " ")] if name in im.encoderinfo: ifd[key] = im.encoderinfo[name] dpi = im.encoderinfo.get("dpi") if dpi: ifd[RESOLUTION_UNIT] = 2 ifd[X_RESOLUTION] = dpi[0] ifd[Y_RESOLUTION] = 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 DEBUG: print("Saving using libtiff encoder") print("Items: %s" % sorted(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. legacy_ifd = {} if hasattr(im, 'tag'): legacy_ifd = im.tag.to_v2() for k, v in itertools.chain(ifd.items(), getattr(im, 'tag_v2', {}).items(), legacy_ifd.items()): if k not in atts and k not in blocklist: if isinstance(v, unicode if bytes is str else str): atts[k] = v.encode('ascii', 'replace') + b"\0" else: atts[k] = v if DEBUG: print("Converted items: %s" % sorted(atts.items())) # 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(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.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 = dict([(k, v) for (k, (v, )) in ifd.items() if k not in blocklist]) try: # pull in more bits from the original file, e.g x,y resolution # so that we can save(load('')) == original file. for k, v in 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) == 1: # int or similar atts[k] = v[0] continue if type(v) == str: atts[k] = v continue except: # if we don't have an ifd here, just punt. pass if Image.DEBUG: print(atts) a = (rawmode, compression, _fp, filename, atts) 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
def _save(im, fp, filename, check=0): try: rawmode, bits, colors = SAVE[im.mode] except KeyError: raise IOError("cannot write mode %s as BMP" % im.mode) if check: return check info = im.encoderinfo dpi = info.get("dpi", (96, 96)) # 1 meter == 39.3701 inches ppm = tuple(map(lambda x: int(x * 39.3701), dpi)) stride = ((im.size[0]*bits+7)//8+3) & (~3) header = 108 if im.mode == 'RGBA' else 40 # or 64 for OS/2 version 2 offset = 14 + header + colors*4 image = stride * im.size[1] red_mask = 0x00ff0000 green_mask = 0x0000ff00 blue_mask = 0x000000ff alpha_mask = 0xff000000 # bitmap header fp.write(b"BM" + # file type (magic) o32(offset+image+16) + # file size o32(0) + # reserved o32(offset+16)) # image data offset width,height = im.size # bitmap info header fp.write(o32(header+16) + # info header size o32(width) + # width o32(height) + # height o16(1) + # planes o16(bits) + # depth o32(3) + # compression (0=uncompressed) o32(image) + # size of bitmap o32(ppm[0]) + o32(ppm[1]) + # resolution o32(colors) + # colors used o32(colors) # colors important #o32(red_mask) + # red channel ma #o32(green_mask) + # green channel mask #o32(blue_mask) + # blue channel mask #o32(alpha_mask) ) # This was commented out because although it works, some # decoders do not support images with a BI_BITFIELDS compression # if im.mode == 'RGBA': fp.write(o32(red_mask) + # red channel mask o32(green_mask) + # green channel mask o32(blue_mask) + # blue channel mask o32(alpha_mask) + # alpha channel mask 'BGRs' + # Color Space o8(0)*0x24 + # ciexyztriple color space endpoints o32(0) + # red gamma o32(0) + # green gamma o32(0) # blue gamma ) fp.write(bytearray(16)) if im.mode == "1": for i in (0, 255): fp.write(o8(i) * 4) elif im.mode == "L": for i in range(256): fp.write(o8(i) * 4) elif im.mode == "P": fp.write(im.im.getpalette("RGB", "BGRX")) #fp.write( ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 0, (rawmode, stride, -1))])
def _save(im, fp, filename, check=0): if im.mode == "P": # we assume this is a color Palm image with the standard colormap, # unless the "info" dict has a "custom-colormap" field rawmode = "P" bpp = 8 version = 1 elif (im.mode == "L" and "bpp" in im.encoderinfo and im.encoderinfo["bpp"] in (1, 2, 4)): # this is 8-bit grayscale, so we shift it to get the high-order bits, # and invert it because # Palm does greyscale from white (0) to black (1) bpp = im.encoderinfo["bpp"] im = im.point( lambda x, shift=8-bpp, maxval=(1 << bpp)-1: maxval - (x >> shift)) # we ignore the palette here im.mode = "P" rawmode = "P;" + str(bpp) version = 1 elif im.mode == "L" and "bpp" in im.info and im.info["bpp"] in (1, 2, 4): # here we assume that even though the inherent mode is 8-bit grayscale, # only the lower bpp bits are significant. # We invert them to match the Palm. bpp = im.info["bpp"] im = im.point(lambda x, maxval=(1 << bpp)-1: maxval - (x & maxval)) # we ignore the palette here im.mode = "P" rawmode = "P;" + str(bpp) version = 1 elif im.mode == "1": # monochrome -- write it inverted, as is the Palm standard rawmode = "1;I" bpp = 1 version = 0 else: raise IOError("cannot write mode %s as Palm" % im.mode) if check: return check # # make sure image data is available im.load() # write header cols = im.size[0] rows = im.size[1] rowbytes = int((cols + (16//bpp - 1)) / (16 // bpp)) * 2 transparent_index = 0 compression_type = _COMPRESSION_TYPES["none"] flags = 0 if im.mode == "P" and "custom-colormap" in im.info: flags = flags & _FLAGS["custom-colormap"] colormapsize = 4 * 256 + 2 colormapmode = im.palette.mode colormap = im.getdata().getpalette() else: colormapsize = 0 if "offset" in im.info: offset = (rowbytes * rows + 16 + 3 + colormapsize) // 4 else: offset = 0 fp.write(o16b(cols) + o16b(rows) + o16b(rowbytes) + o16b(flags)) fp.write(o8(bpp)) fp.write(o8(version)) fp.write(o16b(offset)) fp.write(o8(transparent_index)) fp.write(o8(compression_type)) fp.write(o16b(0)) # reserved by Palm # now write colormap if necessary if colormapsize > 0: fp.write(o16b(256)) for i in range(256): fp.write(o8(i)) if colormapmode == 'RGB': fp.write( o8(colormap[3 * i]) + o8(colormap[3 * i + 1]) + o8(colormap[3 * i + 2])) elif colormapmode == 'RGBA': fp.write( o8(colormap[4 * i]) + o8(colormap[4 * i + 1]) + o8(colormap[4 * i + 2])) # now convert data to raw form ImageFile._save( im, fp, [("raw", (0, 0)+im.size, 0, (rawmode, rowbytes, 1))]) fp.flush()