# # The Python Imaging Library. # $Id: //modules/pil/PIL/Image.py#47 $ # # the Image class wrapper # # partial release history: # 1995-09-09 fl Created # 1996-03-11 fl PIL release 0.0 (proof of concept) # 1996-04-30 fl PIL release 0.1b1 # 1996-05-27 fl PIL release 0.1b2 # 1996-11-04 fl PIL release 0.2b1 # 1996-12-08 fl PIL release 0.2b2 # 1996-12-16 fl PIL release 0.2b3 # 1997-01-14 fl PIL release 0.2b4 # 1998-07-02 fl PIL release 0.3b1 # 1998-07-17 fl PIL release 0.3b2 # 1999-01-01 fl PIL release 1.0b1 # 1999-02-08 fl PIL release 1.0b2 # 1999-07-28 fl PIL release 1.0 final # 2000-06-07 fl PIL release 1.1 # 2000-10-20 fl PIL release 1.1.1 # 2001-05-07 fl PIL release 1.1.2 # 2002-01-14 fl PIL release 1.2b1 (imToolkit) # 2002-03-15 fl PIL release 1.1.3 # 2003-05-10 fl PIL release 1.1.4 # # Copyright (c) 1997-2003 by Secret Labs AB. All rights reserved. # Copyright (c) 1995-2003 by Fredrik Lundh. # # See the README file for information on usage and redistribution. # VERSION = "1.1.4" class _imaging_not_installed: # module placeholder def __getattr__(self, id): raise ImportError("The _imaging C module is not installed") try: # give Tk a chance to set up the environment, in case we're # using an _imaging module linked against libtcl/libtk import FixTk except ImportError: pass try: # If the _imaging C module is not present, you can still use # the "open" function to identify files, but you cannot load # them. Note that other modules should not refer to _imaging # directly; import Image and use the Image.core variable instead. import _imaging core = _imaging del _imaging except ImportError, v: import string core = _imaging_not_installed() if str(v)[:20] == "Module use of python": # The _imaging C module is present, but not compiled for # the right version (windows only). Print a warning, if # possible. try: import warnings warnings.warn( "The _imaging extension was built for another version " "of Python; most PIL functions will be disabled", RuntimeWarning ) except (ImportError, NameError, AttributeError): pass # sorry import ImagePalette import os, string, sys # type stuff from types import IntType, StringType, TupleType try: UnicodeStringType = type(unicode("")) def isStringType(t): return isinstance(t, StringType) or isinstance(t, UnicodeStringType) except NameError: def isStringType(t): return isinstance(t, StringType) def isTupleType(t): return isinstance(t, TupleType) def isImageType(t): return hasattr(t, "im") def isDirectory(f): return isStringType(f) and os.path.isdir(f) from operator import isNumberType, isSequenceType # # Debug level DEBUG = 0 # # Constants (also defined in _imagingmodule.c!) NONE = 0 # transpose FLIP_LEFT_RIGHT = 0 FLIP_TOP_BOTTOM = 1 ROTATE_90 = 2 ROTATE_180 = 3 ROTATE_270 = 4 # transforms AFFINE = 0 EXTENT = 1 PERSPECTIVE = 2 # Not yet implemented QUAD = 3 MESH = 4 # resampling filters NONE = 0 NEAREST = 0 ANTIALIAS = 1 # 3-lobed lanczos LINEAR = BILINEAR = 2 CUBIC = BICUBIC = 3 # dithers NONE = 0 NEAREST = 0 ORDERED = 1 # Not yet implemented RASTERIZE = 2 # Not yet implemented FLOYDSTEINBERG = 3 # default # palettes/quantizers WEB = 0 ADAPTIVE = 1 # categories NORMAL = 0 SEQUENCE = 1 CONTAINER = 2 # -------------------------------------------------------------------- # Registries ID = [] OPEN = {} MIME = {} SAVE = {} EXTENSION = {} # -------------------------------------------------------------------- # Modes supported by this version _MODEINFO = { # official modes "1": ("L", "L", ("1",)), "L": ("L", "L", ("L",)), "I": ("L", "I", ("I",)), "F": ("L", "F", ("F",)), "P": ("RGB", "L", ("P",)), "RGB": ("RGB", "L", ("R", "G", "B")), "RGBX": ("RGB", "L", ("R", "G", "B", "X")), "RGBA": ("RGB", "L", ("R", "G", "B", "A")), "CMYK": ("RGB", "L", ("C", "M", "Y", "K")), "YCbCr": ("RGB", "L", ("Y", "Cb", "Cr")), # Experimental modes include I;16, I;16B, RGBa, BGR;15, # and BGR;24. Use these modes only if you know exactly # what you're doing... } MODES = _MODEINFO.keys() MODES.sort() # raw modes that may be memory mapped. NOTE: if you change this, you # may have to modify the stride calculation in map.c too! _MAPMODES = ("L", "P", "RGBX", "RGBA", "CMYK", "I;16", "I;16B") ## # Get "base" mode. Given a mode, this function returns "L" for # images that contain grayscale data, and "RGB" for images that # contain color data. # # @param mode Input mode. # @return "L" or "RGB". # @exception KeyError The input mode was not a standard mode. def getmodebase(mode): # corresponding "base" mode (grayscale or colour) return _MODEINFO[mode][0] ## # Get storage type mode. Given a mode, this function returns a # single-layer mode suitable for storing individual bands. # # @param mode Input mode. # @return "L", "I", or "F". # @exception KeyError The input mode was not a standard mode. def getmodetype(mode): # storage type (per band) return _MODEINFO[mode][1] ## # Get list of individual band names. Given a mode, this function # returns a tuple containing the names of individual bands (use # getmodetype to get the mode used to store each individual # band. # # @param mode Input mode. # @return A tuple containing band names. The length of the tuple # gives the number of bands in an image of the given mode. # @exception KeyError The input mode was not a standard mode. def getmodebands(mode): # return list of subcomponents return len(_MODEINFO[mode][2]) # -------------------------------------------------------------------- # Helpers _initialized = 0 ## # Explicitly load standard file format drivers. def preinit(): "Load standard file format drivers." global _initialized if _initialized >= 1: return for m in ("Bmp", "Gif", "Jpeg", "Ppm", "Png", "Tiff"): try: __import__("%sImagePlugin" % m, globals(), locals(), []) except ImportError: pass # ignore missing driver for now _initialized = 1 ## # Explicitly load all available file format drivers. def init(): "Load all file format drivers." global _initialized if _initialized >= 2: return visited = {} directories = sys.path try: directories = directories + [os.path.dirname(__file__)] except NameError: pass # only check directories (including current, if present in the path) for directory in filter(isDirectory, directories): fullpath = os.path.abspath(directory) if visited.has_key(fullpath): continue for file in os.listdir(directory): if file[-14:] == "ImagePlugin.py": f, e = os.path.splitext(file) try: sys.path.insert(0, directory) try: __import__(f, globals(), locals(), []) finally: del sys.path[0] except ImportError: if DEBUG: print "Image: failed to import", print f, ":", sys.exc_value visited[fullpath] = None if OPEN or SAVE: _initialized = 2 # -------------------------------------------------------------------- # Codec factories (used by tostring/fromstring and ImageFile.load) def _getdecoder(mode, decoder_name, args, extra=()): # tweak arguments if args is None: args = () elif not isTupleType(args): args = (args,) try: # get decoder decoder = getattr(core, decoder_name + "_decoder") # print decoder, (mode,) + args + extra return apply(decoder, (mode,) + args + extra) except AttributeError: raise IOError("decoder %s not available" % decoder_name) def _getencoder(mode, encoder_name, args, extra=()): # tweak arguments if args is None: args = () elif not isTupleType(args): args = (args,) try: # get encoder encoder = getattr(core, encoder_name + "_encoder") # print encoder, (mode,) + args + extra return apply(encoder, (mode,) + args + extra) except AttributeError: raise IOError("encoder %s not available" % encoder_name) # -------------------------------------------------------------------- # Simple expression analyzer class _E: def __init__(self, data): self.data = data def __coerce__(self, other): return self, _E(other) def __add__(self, other): return _E((self.data, "__add__", other.data)) def __mul__(self, other): return _E((self.data, "__mul__", other.data)) def _getscaleoffset(expr): stub = ["stub"] data = expr(_E(stub)).data try: (a, b, c) = data # simplified syntax if (a is stub and b == "__mul__" and isNumberType(c)): return c, 0.0 if (a is stub and b == "__add__" and isNumberType(c)): return 1.0, c except TypeError: pass try: ((a, b, c), d, e) = data # full syntax if (a is stub and b == "__mul__" and isNumberType(c) and d == "__add__" and isNumberType(e)): return c, e except TypeError: pass raise ValueError("illegal expression") # -------------------------------------------------------------------- # Implementation wrapper ## # This class represents an image object. To create Image objects, use # the appropriate factory functions. There's hardly ever any reason # to call the Image constructor directly. # # @see #open # @see #new # @see #fromstring class Image: format = None format_description = None def __init__(self): self.im = None self.mode = "" self.size = (0, 0) self.palette = None self.info = {} self.category = NORMAL self.readonly = 0 def _new(self, im): new = Image() new.im = im new.mode = im.mode new.size = im.size new.palette = self.palette if im.mode == "P": new.palette = ImagePalette.ImagePalette() try: new.info = self.info.copy() except AttributeError: # fallback (pre-1.5.2) new.info = {} for k, v in self.info: new.info[k] = v return new _makeself = _new # compatibility def _copy(self): self.load() self.im = self.im.copy() self.readonly = 0 def _dump(self, file=None, format=None): import tempfile if not file: file = tempfile.mktemp() self.load() if not format or format == "PPM": self.im.save_ppm(file) else: file = file + "." + format self.save(file, format) return file ## # Returns a string containing pixel data. # # @param encoder_name What encoder to use. The default is to # use the standard "raw" encoder. # @param *args Extra arguments to the encoder. # @return An 8-bit string. def tostring(self, encoder_name="raw", *args): "Return image as a binary string" # may pass tuple instead of argument list if len(args) == 1 and isTupleType(args[0]): args = args[0] if encoder_name == "raw" and args == (): args = self.mode self.load() # unpack data e = _getencoder(self.mode, encoder_name, args) e.setimage(self.im) data = [] while 1: l, s, d = e.encode(65536) data.append(d) if s: break if s < 0: raise RuntimeError("encoder error %d in tostring" % s) return string.join(data, "") ## # Returns the image converted to an X11 bitmap. This method # only works for mode "1" images. # # @param name The name prefix to use for the bitmap variables. # @return A string containing an X11 bitmap. # @exception ValueError If the mode is not "1" def tobitmap(self, name="image"): "Return image as an XBM bitmap" self.load() if self.mode != "1": raise ValueError("not a bitmap") data = self.tostring("xbm") return string.join(["#define %s_width %d\n" % (name, self.size[0]), "#define %s_height %d\n"% (name, self.size[1]), "static char %s_bits[] = {\n" % name, data, "};"], "") ## # Same as the fromstring function, but loads data # into the current image. def fromstring(self, data, decoder_name="raw", *args): "Load data to image from binary string" # may pass tuple instead of argument list if len(args) == 1 and isTupleType(args[0]): args = args[0] # default format if decoder_name == "raw" and args == (): args = self.mode # unpack data d = _getdecoder(self.mode, decoder_name, args) d.setimage(self.im) s = d.decode(data) if s[0] >= 0: raise ValueError("not enough image data") if s[1] != 0: raise ValueError("cannot decode image data") ## # Allocates storage for the image and loads the pixel data. In # normal cases, you don't need to call this method, since the # Image class automatically loads an opened image when it is # accessed for the first time. def load(self): "Explicitly load pixel data." if self.im and self.palette and self.palette.dirty: # realize palette apply(self.im.putpalette, self.palette.getdata()) self.palette.dirty = 0 self.palette.mode = "RGB" self.palette.rawmode = None if self.info.has_key("transparency"): self.im.putpalettealpha(self.info["transparency"], 0) self.palette.mode = "RGBA" ## # Verify file contents. For data read from a file, this method # attempts to determine if the file is broken, without actually # decoding the image data. If this method finds any problems, it # raises suitable exceptions. If you need to load the image after # using this method, you must reopen the image file. def verify(self): "Verify file contents." pass ## # Returns a converted copy of an image. For the "P" mode, this # translates pixels through the palette. If mode is omitted, a # mode is chosen so that all information in the image and the # palette can be represented without a palette. #
# The current version supports all possible conversions between # "L", "RGB" and "CMYK." #
# When translating a colour image to black and white (mode "L"), # the library uses the ITU-R 601-2 luma transform: #
# L = R * 299/1000 + G * 587/1000 + B * 114/1000 #
# When translating a greyscale image into a bilevel image (mode # "1"), all non-zero values are set to 255 (white). To use other # thresholds, use the point method. # # @def convert(mode, matrix=None) # @param mode The requested mode. # @param matrix An optional conversion matrix. If given, this # should be 4- or 16-tuple containing floating point values. # @return An Image object. def convert(self, mode=None, data=None, dither=None, palette=WEB, colors=256): "Convert to other pixel format" if not mode: # determine default mode if self.mode == "P": self.load() if self.palette: mode = self.palette.mode else: mode = "RGB" else: return self.copy() self.load() if data: # matrix conversion if mode not in ("L", "RGB"): raise ValueError("illegal conversion") im = self.im.convert_matrix(mode, data) return self._new(im) if mode == "P" and palette == ADAPTIVE: im = self.im.quantize(colors) return self._new(im) # colourspace conversion if dither is None: dither = FLOYDSTEINBERG try: im = self.im.convert(mode, dither) except ValueError: try: # normalize source image and try again im = self.im.convert(getmodebase(self.mode)) im = im.convert(mode, dither) except KeyError: raise ValueError("illegal conversion") return self._new(im) def quantize(self, colors=256, method=0, kmeans=0, palette=None): # methods: # 0 = median cut # 1 = maximum coverage # NOTE: this functionality will be moved to the extended # quantizer interface in a later versions of PIL. self.load() if palette: # use palette from reference image palette.load() if palette.mode != "P": raise ValueError("bad mode for palette image") if self.mode != "RGB" and self.mode != "L": raise ValueError( "only RGB or L mode images can be quantized to a palette" ) im = self.im.convert("P", 1, palette.im) return self._makeself(im) im = self.im.quantize(colors, method, kmeans) return self._new(im) ## # Copies the image. Use this method if you wish to paste things # into an image, but still retain the original. # # @return An Image object. def copy(self): "Copy raster data" self.load() im = self.im.copy() return self._new(im) ## # Returns a rectangular region from the current image. The box is # a 4-tuple defining the left, upper, right, and lower pixel # coordinate. #
# This is a lazy operation. Changes to the source image may or # may not be reflected in the cropped image. To break the # connection, call the load method on the cropped copy. # # @return An Image object. def crop(self, box=None): "Crop region from image" self.load() if box is None: return self.copy() # lazy operation return _ImageCrop(self, box) ## # Configures the image file loader so it returns a version of the # image that as closely as possible matches the given mode and # size. For example, you can use this method to convert a colour # JPEG to greyscale while loading it, or to extract a 128x192 # version from a PCD file. #
# Note that this method modifies the Image object in place. If # the image has already been loaded, this method has no effect. # # @param mode The requested mode. # @param size The requested size. def draft(self, mode, size): "Configure image decoder" pass ## # Filter image by the given filter. For a list of available # filters, see the ImageFilter module. # # @param filter Filter kernel. # @return An Image object. # @see ImageFilter def filter(self, filter): "Apply environment filter to image" self.load() from ImageFilter import Filter if not isinstance(filter, Filter): filter = filter() if self.im.bands == 1: return self._new(filter.filter(self.im)) # fix to handle multiband images since _imaging doesn't ims = [] for c in range(self.im.bands): ims.append(self._new(filter.filter(self.im.getband(c)))) return merge(self.mode, ims) ## # Returns a tuple containing the name of each band. For example, # getbands on an RGB image returns ("R", "G", "B"). # # @return A tuple containing band names. def getbands(self): "Get band names" return _MODEINFO[self.mode][2] ## # Calculates the bounding box of the non-zero regions in the # image. # @return The bounding box is returned as a 4-tuple defining the # left, upper, right, and lower pixel coordinate. If the image # is completely empty, this method returns None. def getbbox(self): "Get bounding box of actual data (non-zero pixels) in image" self.load() return self.im.getbbox() ## # Returns the contents of an image as a sequence object containing # pixel values. The sequence object is flattened, so that values # for line one follow directly after the values of line zero, and # so on. #
# Note that the sequence object returned by this method is an # internal PIL data type, which only supports certain sequence # operations. To convert it to an ordinary sequence (e.g. for # printing), use list(im.getdata()). # # @param band What band to return. The default is to return # all bands. To return a single band, pass in the index # value (e.g. 0 to get the "R" band from an "RGB" image). # @return A sequence-like object. def getdata(self, band = None): "Get image data as sequence object." self.load() if band is not None: return self.im.getband(band) return self.im # could be abused ## # Get the the minimum and maximum pixel values for each band in # the image. # # @return For a single-band image, a 2-tuple containing the # minimum and maximum pixel value. For a multi-band image, # a tuple containing one 2-tuple for each band. def getextrema(self): "Get min/max value" self.load() if self.im.bands > 1: extrema = [] for i in range(self.im.bands): extrema.append(self.im.getband(i).getextrema()) return tuple(extrema) return self.im.getextrema() ## # Returns the pixel value at a given position. # # @param xy The coordinate, given as (x, y). # @return The pixel value. If the image is a multi-layer image, # this method returns a tuple. def getpixel(self, xy): "Get pixel value" self.load() return self.im.getpixel(xy) def getprojection(self): "Get projection to x and y axes" self.load() x, y = self.im.getprojection() return map(ord, x), map(ord, y) ## # Returns a histogram for the image. The histogram is returned as # a list of pixel counts, one for each pixel value in the source # image. If the image has more than one band, the histograms for # all bands are concatenated (for example, the histogram for an # "RGB" image contains 768 values). #
# A bilevel image (mode "1") is treated as a greyscale ("L") image # by this method. #
# If a mask is provided, the method returns a histogram for those # parts of the image where the mask image is non-zero. The mask # image must have the same size as the image, and be either a # bi-level image (mode "1") or a greyscale image ("L"). # # @def histogram(mask=None) # @param mask An optional mask. # @return A list containing pixel counts. def histogram(self, mask=None, extrema=None): "Take histogram of image" self.load() if mask: mask.load() return self.im.histogram((0, 0), mask.im) if self.mode in ("I", "F"): if extrema is None: extrema = self.getextrema() return self.im.histogram(extrema) return self.im.histogram() ## # (Deprecated) Returns a copy of the image where the data has been # offset by the given distances. Data wraps around the edges. If # yoffset is omitted, it is assumed to be equal to xoffset. #
# This method is deprecated. New code should use the offset # function in the ImageChops module. # # @param xoffset The horizontal distance. # @param yoffset The vertical distance. If omitted, both # distances are set to the same value. # @return An Image object. def offset(self, xoffset, yoffset=None): "(deprecated) Offset image in horizontal and/or vertical direction" import ImageChops return ImageChops.offset(self, xoffset, yoffset) ## # Pastes another image into this image. The box argument is either # a 2-tuple giving the upper left corner, a 4-tuple defining the # left, upper, right, and lower pixel coordinate, or None (same as # (0, 0)). If a 4-tuple is given, the size of the pasted image # must match the size of the region. #
# If the modes don't match, the pasted image is converted to the # mode of this image (see the convert method for details). #
# Instead of an image, the source can be a integer or tuple # containing pixel values. The method then fills the region # with the given colour. When creating RGB images, you can # also use colour strings as supported by the ImageColor module. #
# If a mask is given, this method updates only the regions # indicated by the mask. You can use either "1", "L" or "RGBA" # images (in the latter case, the alpha band is used as mask). # Where the mask is 255, the given image is copied as is. Where # the mask is 0, the current value is preserved. Intermediate # values can be used for transparency effects. #
# Note that if you paste an "RGBA" image, the alpha band is # ignored. You can work around this by using the same image as # both source image and mask. # # @param im Source image or pixel value (integer or tuple). # @param box A 4-tuple giving the region to paste into. If a # 2-tuple is used instead, it's treated as the upper left # corner. If None is used instead, the source is pasted # into the upper left corner. # @param mask An optional mask image. # @return An Image object. def paste(self, im, box=None, mask=None): "Paste other image into region" if box is None: # cover all of self box = (0, 0) + self.size if len(box) == 2: # lower left corner given; get size from image or mask if isImageType(im): box = box + (box[0]+im.size[0], box[1]+im.size[1]) else: box = box + (box[0]+mask.size[0], box[1]+mask.size[1]) if isStringType(im): import ImageColor im = ImageColor.getcolor(im, self.mode) elif isImageType(im): im.load() if self.mode != im.mode: if self.mode != "RGB" or im.mode not in ("RGBA", "RGBa"): # should use an adapter for this! im = im.convert(self.mode) im = im.im self.load() if self.readonly: self._copy() if mask: mask.load() self.im.paste(im, box, mask.im) else: self.im.paste(im, box) ## # Map image through lookup table or function. # # @param lut A lookup table, containing 256 values per band in the # image. A function can be used instead, it should take a single # argument. The function is called once for each possible pixel # value, and the resulting table is applied to all bands of the # image. # @param mode Output mode (default is same as input). In the # current version, this can only be used if the source image # has mode "L" or "P", and the output has mode "1". # @return An Image object. def point(self, lut, mode=None): "Map image through lookup table" if self.mode in ("I", "I;16", "F"): # floating point; lut must be a valid expression scale, offset = _getscaleoffset(lut) self.load() im = self.im.point_transform(scale, offset); else: # integer image; use lut and mode self.load() if not isSequenceType(lut): # if it isn't a list, it should be a function lut = map(lut, range(256)) * self.im.bands im = self.im.point(lut, mode) return self._new(im) ## # Replace the alpha layer in the current image. The image must be # an "RGBA" image, and the band must be either "L" or "1". # # @param im The new alpha layer. def putalpha(self, im): "Set alpha layer" if self.mode != "RGBA" or im.mode not in ("1", "L"): raise ValueError("illegal image mode") im.load() self.load() if im.mode == "1": im = im.convert("L") self.im.putband(im.im, 3) ## # Copy pixel data to this image. This method copies data from a # sequence object into the image, starting at the upper left # corner (0, 0), and continuing until either the image or the # sequence ends. The scale and offset values are used to adjust # the sequence values: pixel = value*scale + offset. # # @param data A sequence object. # @param scale An optional scale value. The default is 1.0. # @param offset An optional offset value. The default is 0.0. def putdata(self, data, scale=1.0, offset=0.0): "Put data from a sequence object into an image." self.load() # hmm... self.im.putdata(data, scale, offset) ## # Attach a palette to a "P" or "L" image. The palette sequence # should contain 768 integer values, where each group of three # values represent the red, green, and blue values for the # corresponding pixel index. Instead of an integer sequence, you # can use an 8-bit string. # # @def putpalette(data) # @param data A palette sequence. def putpalette(self, data, rawmode="RGB"): "Put palette data into an image." self.load() if self.mode not in ("L", "P"): raise ValueError("illegal image mode") if not isStringType(data): data = string.join(map(chr, data), "") self.mode = "P" self.palette = ImagePalette.raw(rawmode, data) self.palette.mode = "RGB" self.load() # install new palette ## # Modifies the pixel at the given position. The colour is given as # a single numerical value for single-band images, and a tuple for # multi-band images. #
# Note that this method is relatively slow. For more extensive # changes, use paste or the ImageDraw module # instead. # # @param xy The pixel coordinate, given as (x, y). # @param value The pixel value. # @see #Image.paste # @see #Image.putdata # @see ImageDraw def putpixel(self, xy, value): "Set pixel value" self.load() return self.im.putpixel(xy, value) ## # Returns a resized copy of an image. # # @def resize(size, filter=NEAREST) # @param size The requested size in pixels, as a 2-tuple: # (width, height). # @param filter An optional resampling filter. This can be # one of NEAREST (use nearest neighbour), BILINEAR # (linear interpolation in a 2x2 environment), BICUBIC # (cubic spline interpolation in a 4x4 environment), or # ANTIALIAS (a high-quality downsampling filter). # If omitted, or if the image has mode "1" or "P", it is # set NEAREST. # @return An Image object. def resize(self, size, resample=NEAREST): "Resize image" if resample not in (NEAREST, BILINEAR, BICUBIC, ANTIALIAS): raise ValueError("unknown resampling filter") self.load() if self.mode in ("1", "P"): resample = NEAREST if resample == ANTIALIAS: # requires stretch support (imToolkit & PIL 1.1.3) try: im = self.im.stretch(size, resample) except AttributeError: raise ValueError("unsupported resampling filter") else: im = self.im.resize(size, resample) return self._new(im) ## # Returns a rotated image. This method returns a copy of an # image, rotated the given number of degrees counter clockwise # around its centre. # # @def rotate(angle, filter=NEAREST) # @param angle In degrees counter clockwise. # @param filter An optional resampling filter. This can be # one of NEAREST (use nearest neighbour), BILINEAR # (linear interpolation in a 2x2 environment), or BICUBIC # (cubic spline interpolation in a 4x4 environment). # If omitted, or if the image has mode "1" or "P", it is # set NEAREST. # @return An Image object. def rotate(self, angle, resample=NEAREST): "Rotate image. Angle given as degrees counter-clockwise." if resample not in (NEAREST, BILINEAR, BICUBIC): raise ValueError("unknown resampling filter") self.load() if self.mode in ("1", "P"): resample = NEAREST return self._new(self.im.rotate(angle, resample)) ## # Saves the image under the given filename. If no format is # specified, the format to use is determined from the filename # extension, if possible. #
# Keyword options can be used to provide additional instructions # to the writer. If a writer doesn't recognise an option, it is # silently ignored. The available options are described later in # this handbook. #
# You can use a file object instead of a filename. In this case, # you must always specify the format. The file object must # implement the seek, tell, and write # methods, and be opened in binary mode. # # @def save(file, format=None, **options) # @param file File name or file object. # @param format Optional format override. If omitted, the # format to use is determined from the filename extension. # If a file object was used instead of a filename, this # parameter should always be used. # @param **options Extra parameters to the image writer. # @return None def save(self, fp, format=None, **params): "Save image to file or stream" if isStringType(fp): import __builtin__ filename = fp fp = __builtin__.open(fp, "wb") close = 1 else: if hasattr(fp, "name") and isStringType(fp.name): filename = fp.name else: filename = "" close = 0 self.encoderinfo = params self.encoderconfig = () self.load() preinit() ext = string.lower(os.path.splitext(filename)[1]) try: if not format: format = EXTENSION[ext] SAVE[string.upper(format)](self, fp, filename) except KeyError, v: init() if not format: format = EXTENSION[ext] SAVE[string.upper(format)](self, fp, filename) if close: fp.close() ## # Seeks to the given frame in a sequence file. If you seek beyond # the end of the sequence, the method raises an EOFError # exception. When a sequence file is opened, the library # automatically seeks to frame 0. #
# Note that in the current version of the library, most sequence # formats only allows you to seek to the next frame. # # @param frame Frame number, starting at 0. # @exception EOFError Attempt to seek beyond the end of the sequence. # @see #Image.tell def seek(self, frame): "Seek to given frame in sequence file" # overridden by file handlers if frame != 0: raise EOFError ## # Displays an image. This method is mainly intended for # debugging purposes. #
# On Unix platforms, this method saves the image to a temporary # PPM file, and calls the xv utility. #
# On Windows, it saves the image to a temporary BMP file, and uses # the standard BMP display utility to show it (usually Paint). # # @def show(title=None) # @param title Optional title to use for the image window, # where possible. def show(self, title=None, command=None): "Display image (for debug purposes only)" try: import ImageTk ImageTk._show(self, title) # note: caller must enter mainloop! except: _showxv(self, title, command) ## # Split image into individual bands. This methods returns a tuple # of individual image bands from an image. For example, splitting # an "RGB" image creates three new images each containing a copy # of one of the original bands (red, green, blue). # # @return A tuple containing bands. def split(self): "Split image into bands" ims = [] self.load() for i in range(self.im.bands): ims.append(self._new(self.im.getband(i))) return tuple(ims) ## # Returns the current frame number. # # @return Frame number, starting with 0. # @see #Image.seek def tell(self): "Return current frame number" return 0 ## # Make thumbnail. This method modifies the image to contain a # thumbnail version of itself, no larger than the given size. # This method calculates an appropriate thumbnail size to preserve # the aspect of the image, calls the draft method to # configure the file reader (where applicable), and finally # resizes the image. #
# Note that the bilinear and bicubic filters in the current # version of PIL are not well-suited for thumbnail generation. # You should use ANTIALIAS unless speed is much more # important than quality. #
# Also note that this function modifies the Image object in place. # If you need to use the full resolution image as well, apply this # method to a copy of the original image. # # @param size Requested size. # @param resample Optional resampling filter. This can be one # of NEAREST, BILINEAR, BICUBIC, or # ANTIALIAS (best quality). If omitted, it defaults # to NEAREST (this will be changed to ANTIALIAS in # future versions). # @return None def thumbnail(self, size, resample=NEAREST): "Create thumbnail representation (modifies image in place)" # FIXME: the default resampling filter will be changed # to ANTIALIAS in future versions # preserve aspect ratio x, y = self.size if x > size[0]: y = y * size[0] / x; x = size[0] if y > size[1]: x = x * size[1] / y; y = size[1] size = x, y if size == self.size: return self.draft(None, size) self.load() try: im = self.resize(size, resample) except ValueError: if resample != ANTIALIAS: raise im = self.resize(size, NEAREST) # fallback self.im = im.im self.mode = im.mode self.size = size self.readonly = 0 # FIXME: the different tranform methods need further explanation # instead of bloating the method docs, add a separate chapter. ## # Transform image. This method creates a new image with the # given size, and the same mode as the original, and copies # data to the new image using the given transform. #
# @def transform(size, method, data, resample=NEAREST) # @param size The output size. # @param method The transformation method. This is one of # EXTENT (cut out a rectangular subregion), AFFINE # (affine transform), QUAD (map a quadrilateral to a # rectangle), or MESH (map a number of source quadrilaterals # in one operation). # @param data Extra data to the transformation method. # @param resample Optional resampling filter. It can be one of # NEAREST (use nearest neighbour), BILINEAR # (linear interpolation in a 2x2 environment), or # BICUBIC (cubic spline interpolation in a 4x4 # environment). If omitted, or if the image has mode # "1" or "P", it is set to NEAREST. # @return An Image object. def transform(self, size, method, data=None, resample=NEAREST, fill=1): "Transform image" import ImageTransform if isinstance(method, ImageTransform.Transform): method, data = method.getdata() if data is None: raise ValueError("missing method data") im = new(self.mode, size, None) if method == MESH: # list of quads for box, quad in data: im.__transformer(box, self, QUAD, quad, resample, fill) else: im.__transformer((0, 0)+size, self, method, data, resample, fill) return im def __transformer(self, box, image, method, data, resample=NEAREST, fill=1): "Transform into current image" # FIXME: this should be turned into a lazy operation (?) w = box[2]-box[0] h = box[3]-box[1] if method == AFFINE: # change argument order to match implementation data = (data[2], data[0], data[1], data[5], data[3], data[4]) elif method == EXTENT: # convert extent to an affine transform x0, y0, x1, y1 = data xs = float(x1 - x0) / w ys = float(y1 - y0) / h method = AFFINE data = (x0 + xs/2, xs, 0, y0 + ys/2, 0, ys) elif method == QUAD: # quadrilateral warp. data specifies the four corners # given as NW, SW, SE, and NE. nw = data[0:2]; sw = data[2:4]; se = data[4:6]; ne = data[6:8] x0, y0 = nw; As = 1.0 / w; At = 1.0 / h data = (x0, (ne[0]-x0)*As, (sw[0]-x0)*At, (se[0]-sw[0]-ne[0]+x0)*As*At, y0, (ne[1]-y0)*As, (sw[1]-y0)*At, (se[1]-sw[1]-ne[1]+y0)*As*At) else: raise ValueError("unknown transformation method") if resample not in (NEAREST, BILINEAR, BICUBIC): raise ValueError("unknown resampling filter") image.load() self.load() if image.mode in ("1", "P"): resample = NEAREST self.im.transform2(box, image.im, method, data, resample, fill) ## # Returns a flipped or rotated copy of an image. # # @param method One of FLIP_LEFT_RIGHT, FLIP_TOP_BOTTOM, # ROTATE_90, ROTATE_180, or ROTATE_270. def transpose(self, method): "Transpose image (flip or rotate in 90 degree steps)" self.load() im = self.im.transpose(method) return self._new(im) # -------------------------------------------------------------------- # Lazy operations class _ImageCrop(Image): def __init__(self, im, box): Image.__init__(self) self.mode = im.mode self.size = box[2]-box[0], box[3]-box[1] self.__crop = box self.im = im.im def load(self): # lazy evaluation! if self.__crop: self.im = self.im.crop(self.__crop) self.__crop = None # FIXME: future versions should optimize crop/paste # sequences! # -------------------------------------------------------------------- # Factories # # Debugging def _wedge(): "Create greyscale wedge (for debugging only)" return Image()._new(core.wedge("L")) ## # Creates a new image with the given mode and size. # # @param mode The mode to use for the new image. # @param size A 2-tuple, containing (width, height) # @param color What colour to use for the image. Default is black. # If given, this should be a single integer or floating point value # for single-band modes, and a tuple for multi-band modes (one value # per band). When creating RGB images, you can also use colour # strings as supported by the ImageColor module. If the colour is # None, the image is not initialised. # @return An Image object. def new(mode, size, color=0): "Create a new image" if color is None: # don't initialize return Image()._new(core.new(mode, size)) if isStringType(color): # css3-style specifier import ImageColor color = ImageColor.getcolor(color, mode) return Image()._new(core.fill(mode, size, color)) ## # Creates an image memory from pixel data in a string. #
# In its simplest form, this function takes three arguments # (mode, size, and unpacked pixel data). #
# You can also use any pixel decoder supported by PIL. For more # information on available decoders, see the section Writing Your Own File Decoder. #
# Note that this function decodes pixel data only, not entire images. # If you have an entire image in a string, wrap it in a StringIO # object, and use open to load it. # # @param mode The image mode. # @param size The image size. # @param data An 8-bit string containing raw data for the given mode. # @param decoder_name What decoder to use. # @param *args Additional parameters for the given decoder. # @return An Image object. def fromstring(mode, size, data, decoder_name="raw", *args): "Load image from string" # may pass tuple instead of argument list if len(args) == 1 and isTupleType(args[0]): args = args[0] if decoder_name == "raw" and args == (): args = mode im = new(mode, size) im.fromstring(data, decoder_name, args) return im ## # Creates an image memory from pixel data in a string or byte buffer. #
# This function is similar to fromstring, but it data in # the byte buffer, where possible. Images created by this function # are usually marked as readonly. #
# Note that this function decodes pixel data only, not entire images. # If you have an entire image in a string, wrap it in a StringIO # object, and use open to load it. # # @param mode The image mode. # @param size The image size. # @param data An 8-bit string or other buffer object containing raw # data for the given mode. # @param decoder_name What decoder to use. # @param *args Additional parameters for the given decoder. # @return An Image object. def frombuffer(mode, size, data, decoder_name="raw", *args): "Load image from string or buffer" # may pass tuple instead of argument list if len(args) == 1 and isTupleType(args[0]): args = args[0] if decoder_name == "raw": if args == (): args = mode, 0, -1 if args[0] in _MAPMODES: im = new(mode, (1,1)) im = im._new( core.map_buffer(data, size, decoder_name, None, 0, args) ) im.readonly = 1 return im return apply(fromstring, (mode, size, data, decoder_name, args)) ## # Opens and identifies the given image file. #
# This is a lazy operation; this function identifies the file, but the # actual image data is not read from the file until you try to process # the data (or call the load method). # # @def open(file, mode="r") # @param file A filename (string) or a file object. The file object # must implement read, seek, and tell methods, # and be opened in binary mode. # @param mode The mode. If given, this argument must be "r". # @return An Image object. # @exception IOError If the file cannot be found, or the image cannot be # opened and identified. # @see #new def open(fp, mode="r"): "Open an image file, without loading the raster data" if mode != "r": raise ValueError("bad mode") if isStringType(fp): import __builtin__ filename = fp fp = __builtin__.open(fp, "rb") else: filename = "" prefix = fp.read(16) preinit() for i in ID: try: factory, accept = OPEN[i] if not accept or accept(prefix): fp.seek(0) return factory(fp, filename) except (SyntaxError, IndexError, TypeError): pass init() for i in ID: try: factory, accept = OPEN[i] if not accept or accept(prefix): fp.seek(0) return factory(fp, filename) except (SyntaxError, IndexError, TypeError): pass raise IOError("cannot identify image file") # # Image processing. ## # Creates a new image by interpolating between the given images, using # a constant alpha. # #
# out = image1 * (1.0 - alpha) + image2 * alpha ## # @param im1 The first image. # @param im2 The second image. Must have the same mode and size as # the first image. # @param alpha The interpolation alpha factor. If alpha is 0.0, a # copy of the first image is returned. If alpha is 1.0, a copy of # the second image is returned. There are no restrictions on the # alpha value. If necessary, the result is clipped to fit into # the allowed output range. # @return An Image object. def blend(im1, im2, alpha): "Interpolate between images." im1.load() im2.load() return im1._new(core.blend(im1.im, im2.im, alpha)) ## # Creates a new image by interpolating between the given images, # using the mask as alpha. # # @param image1 The first image. # @param image2 The second image. Must have the same mode and # size as the first image. # @param mask A mask image. This image can can have mode # "1", "L", or "RGBA", and most have the same size as the # other two images. def composite(image1, image2, mask): "Create composite image by blending images using a transparency mask" image = image2.copy() image.paste(image1, None, mask) return image ## # Applies the function (which should take one argument) to each pixel # in the given image. If the image has more than one band, the same # function is applied to each band. Note that the function is # evaluated once for each possible pixel value, so you cannot use # random components or other generators. # # @def eval(image, function) # @param image The input image. # @param function A function object, taking one integer argument. # @return An Image object. def eval(image, *args): "Evaluate image expression" return image.point(args[0]) ## # Creates a new image from a number of single-band images. # # @param mode The mode to use for the output image. # @param bands A sequence containing one single-band image for # each band in the output image. All bands must have the # same size. # @return An Image object. def merge(mode, bands): "Merge a set of single band images into a new multiband image." if getmodebands(mode) != len(bands) or "*" in mode: raise ValueError("wrong number of bands") for im in bands[1:]: if im.mode != getmodetype(mode): raise ValueError("mode mismatch") if im.size != bands[0].size: raise ValueError("size mismatch") im = core.new(mode, bands[0].size) for i in range(getmodebands(mode)): bands[i].load() im.putband(bands[i].im, i) return bands[0]._new(im) # -------------------------------------------------------------------- # Plugin registry ## # Register an image file plugin. This function should not be used # in application code. # # @param id An image format identifier. # @param factory An image file factory method. # @param accept An optional function that can be used to quickly # reject images having another format. def register_open(id, factory, accept=None): id = string.upper(id) ID.append(id) OPEN[id] = factory, accept ## # Register an image MIME type. This function should not be used # in application code. # # @param id An image format identifier. # @param mimetype The image MIME type for this format. def register_mime(id, mimetype): MIME[string.upper(id)] = mimetype ## # Register an image save function. This function should not be # used in application code. # # @param id An image format identifier. # @param driver A function to save images in this format. def register_save(id, driver): SAVE[string.upper(id)] = driver ## # Register an image extension. This function should not be # used in application code. # # @param id An image format identifier. # @param extension An extension used for this format. def register_extension(id, extension): EXTENSION[string.lower(extension)] = string.upper(id) # -------------------------------------------------------------------- # Simple display support def _showxv(self, title=None, command=None): if os.name == "nt": format = "BMP" if not command: command = "start" elif os.environ.get("OSTYPE") == "darwin": format = "JPEG" if not command: command = "open -a /Applications/Preview.app" else: format = None if not command: command = "xv" if title: command = command + " -name \"%s\"" % title if self.mode == "I;16": # @PIL88 @PIL101 # "I;16" isn't an 'official' mode, but we still want to # provide a simple way to show 16-bit images. base = "L" else: base = getmodebase(self.mode) if base != self.mode and self.mode != "1": file = self.convert(base)._dump(format=format) else: file = self._dump(format=format) if os.name == "nt": os.system("%s %s" % (command, file)) # FIXME: this leaves temporary files around... elif os.environ.get("OSTYPE") == "darwin": # on darwin open returns immediately resulting in the temp # file removal while app is opening os.system("(%s %s; sleep 20; rm -f %s)&" % (command, file, file)) else: os.system("(%s %s; rm -f %s)&" % (command, file, file))