diff options
Diffstat (limited to 'PIL/Image.py')
-rw-r--r-- | PIL/Image.py | 2093 |
1 files changed, 2093 insertions, 0 deletions
diff --git a/PIL/Image.py b/PIL/Image.py new file mode 100644 index 0000000..04ed316 --- /dev/null +++ b/PIL/Image.py @@ -0,0 +1,2093 @@ +# +# The Python Imaging Library. +# $Id: Image.py 2933 2006-12-03 12:08:22Z fredrik $ +# +# 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 +# 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-03-15 fl PIL release 1.1.3 +# 2003-05-10 fl PIL release 1.1.4 +# 2005-03-28 fl PIL release 1.1.5 +# 2006-12-02 fl PIL release 1.1.6 +# +# Copyright (c) 1997-2006 by Secret Labs AB. All rights reserved. +# Copyright (c) 1995-2006 by Fredrik Lundh. +# +# See the README file for information on usage and redistribution. +# + +VERSION = "1.1.6" + +try: + import warnings +except ImportError: + warnings = None + +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 (use + # __import__ to hide this from naive packagers; we don't really + # depend on Tk unless ImageTk is used, and that module already + # imports Tkinter) + __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: + core = _imaging_not_installed() + if str(v)[:20] == "Module use of python" and warnings: + # The _imaging C module is present, but not compiled for + # the right version (windows only). Print a warning, if + # possible. + warnings.warn( + "The _imaging extension was built for another version " + "of Python; most PIL functions will be disabled", + RuntimeWarning + ) + +import ImageMode +import ImagePalette + +import os, string, sys + +# type stuff +from types import IntType, StringType, TupleType + +try: + UnicodeStringType = type(unicode("")) + ## + # (Internal) Checks if an object is a string. If the current + # Python version supports Unicode, this checks for both 8-bit + # and Unicode strings. + def isStringType(t): + return isinstance(t, StringType) or isinstance(t, UnicodeStringType) +except NameError: + def isStringType(t): + return isinstance(t, StringType) + +## +# (Internal) Checks if an object is a tuple. + +def isTupleType(t): + return isinstance(t, TupleType) + +## +# (Internal) Checks if an object is an image object. + +def isImageType(t): + return hasattr(t, "im") + +## +# (Internal) Checks if an object is a string, and that it points to a +# directory. + +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 +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 = { + # NOTE: this table will be removed in future versions. use + # getmode* functions or ImageMode descriptors instead. + + # 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... + +} + +if sys.byteorder == 'little': + _ENDIAN = '<' +else: + _ENDIAN = '>' + +_MODE_CONV = { + # official modes + "1": ('|b1', None), + "L": ('|u1', None), + "I": ('%si4' % _ENDIAN, None), # FIXME: is this correct? + "F": ('%sf4' % _ENDIAN, None), # FIXME: is this correct? + "P": ('|u1', None), + "RGB": ('|u1', 3), + "RGBX": ('|u1', 4), + "RGBA": ('|u1', 4), + "CMYK": ('|u1', 4), + "YCbCr": ('|u1', 4), +} + +def _conv_type_shape(im): + shape = im.size[::-1] + typ, extra = _MODE_CONV[im.mode] + if extra is None: + return shape, typ + else: + return shape+(extra,), typ + + +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") + +## +# Gets the "base" mode for given 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 If the input mode was not a standard mode. + +def getmodebase(mode): + return ImageMode.getmode(mode).basemode + +## +# Gets the 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 If the input mode was not a standard mode. + +def getmodetype(mode): + return ImageMode.getmode(mode).basetype + +## +# Gets a list of individual band names. Given a mode, this function +# returns a tuple containing the names of individual bands (use +# {@link #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 If the input mode was not a standard mode. + +def getmodebandnames(mode): + return ImageMode.getmode(mode).bands + +## +# Gets the number of individual bands for this mode. +# +# @param mode Input mode. +# @return The number of bands in this mode. +# @exception KeyError If the input mode was not a standard mode. + +def getmodebands(mode): + return len(ImageMode.getmode(mode).bands) + +# -------------------------------------------------------------------- +# Helpers + +_initialized = 0 + +## +# Explicitly loads standard file format drivers. + +def preinit(): + "Load standard file format drivers." + + global _initialized + if _initialized >= 1: + return + + try: + import BmpImagePlugin + except ImportError: + pass + try: + import GifImagePlugin + except ImportError: + pass + try: + import JpegImagePlugin + except ImportError: + pass + try: + import PpmImagePlugin + except ImportError: + pass + try: + import PngImagePlugin + except ImportError: + pass +# try: +# import TiffImagePlugin +# except ImportError: +# pass + + _initialized = 1 + +## +# Explicitly initializes the Python Imaging Library. This function +# loads 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 + + def __getattr__(self, name): + if name == "__array_interface__": + # numpy array interface support + new = {} + shape, typestr = _conv_type_shape(self) + new['shape'] = shape + new['typestr'] = typestr + new['data'] = self.tostring() + return new + raise AttributeError(name) + + ## + # 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) + + bufsize = max(65536, self.size[0] * 4) # see RawEncode.c + + data = [] + while 1: + l, s, d = e.encode(bufsize) + 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, "};"], "") + + ## + # Loads this image with pixel data from a string. + # <p> + # This method is similar to the {@link #fromstring} function, but + # loads data into this image instead of creating a new image + # object. + + 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. + # + # @return An image access object. + + 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" + if self.im: + return self.im.pixel_access(self.readonly) + + ## + # Verifies the contents of a file. 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 this image. For the "P" mode, this + # method 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. + # <p> + # The current version supports all possible conversions between + # "L", "RGB" and "CMYK." + # <p> + # When translating a colour image to black and white (mode "L"), + # the library uses the ITU-R 601-2 luma transform: + # <p> + # <b>L = R * 299/1000 + G * 587/1000 + B * 114/1000</b> + # <p> + # 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 {@link #Image.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 version 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 this 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 this image. The box is a + # 4-tuple defining the left, upper, right, and lower pixel + # coordinate. + # <p> + # 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 {@link #Image.load} method on the cropped + # copy. + # + # @param The crop rectangle, as a (left, upper, right, lower)-tuple. + # @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. + # <p> + # 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 + + def _expand(self, xmargin, ymargin=None): + if ymargin is None: + ymargin = xmargin + self.load() + return self._new(self.im.expand(xmargin, ymargin, 0)) + + ## + # Filters this image using the given filter. For a list of + # available filters, see the <b>ImageFilter</b> 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 in this image. + # For example, <b>getbands</b> on an RGB image returns ("R", "G", "B"). + # + # @return A tuple containing band names. + + def getbands(self): + "Get band names" + + return ImageMode.getmode(self.mode).bands + + ## + # 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 a list of colors used in this image. + # + # @param maxcolors Maximum number of colors. If this number is + # exceeded, this method returns None. The default limit is + # 256 colors. + # @return An unsorted list of (count, pixel) values. + + def getcolors(self, maxcolors=256): + "Get colors from image, up to given limit" + + self.load() + if self.mode in ("1", "L", "P"): + h = self.im.histogram() + out = [] + for i in range(256): + if h[i]: + out.append((h[i], i)) + if len(out) > maxcolors: + return None + return out + return self.im.getcolors(maxcolors) + + ## + # Returns the contents of this 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. + # <p> + # 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 <b>list(im.getdata())</b>. + # + # @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 + + ## + # Gets 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 a PyCObject that points to the internal image memory. + # + # @return A PyCObject object. + + def getim(self): + "Get PyCObject pointer to internal image memory" + + self.load() + return self.im.ptr + + + ## + # Returns the image palette as a list. + # + # @return A list of color values [r, g, b, ...], or None if the + # image has no palette. + + def getpalette(self): + "Get palette contents." + + self.load() + try: + return map(ord, self.im.getpalette()) + except ValueError: + return None # no palette + + + ## + # 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) + + ## + # Returns the horizontal and vertical projection. + # + # @return Two sequences, indicating where there are non-zero + # pixels along the X-axis and the Y-axis, respectively. + + 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). + # <p> + # A bilevel image (mode "1") is treated as a greyscale ("L") image + # by this method. + # <p> + # 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. + # <p> + # This method is deprecated. New code should use the <b>offset</b> + # function in the <b>ImageChops</b> 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" + if warnings: + warnings.warn( + "'offset' is deprecated; use 'ImageChops.offset' instead", + DeprecationWarning, stacklevel=2 + ) + 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. + # <p> + # If the modes don't match, the pasted image is converted to the + # mode of this image (see the {@link #Image.convert} method for + # details). + # <p> + # 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. + # <p> + # 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. + # <p> + # 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 An optional 4-tuple giving the region to paste into. + # If a 2-tuple is used instead, it's treated as the upper left + # corner. If omitted or None, the source is pasted into the + # upper left corner. + # <p> + # If an image is given as the second argument and there is no + # third, the box defaults to (0, 0), and the second argument + # is interpreted as a mask image. + # @param mask An optional mask image. + # @return An Image object. + + def paste(self, im, box=None, mask=None): + "Paste other image into region" + + if isImageType(box) and mask is None: + # abbreviated paste(im, mask) syntax + mask = box; box = None + + 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): + size = im.size + elif isImageType(mask): + size = mask.size + else: + # FIXME: use self.size here? + raise ValueError( + "cannot determine region size; use 4-item box" + ) + box = box + (box[0]+size[0], box[1]+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) + + ## + # Maps this image through a 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" + + self.load() + + if not isSequenceType(lut): + # if it isn't a list, it should be a function + if self.mode in ("I", "I;16", "F"): + # check if the function can be used with point_transform + scale, offset = _getscaleoffset(lut) + return self._new(self.im.point_transform(scale, offset)) + # for other modes, convert the function to a table + lut = map(lut, range(256)) * self.im.bands + + if self.mode == "F": + # FIXME: _imaging returns a confusing error message for this case + raise ValueError("point operation not supported for this mode") + + return self._new(self.im.point(lut, mode)) + + ## + # Adds or replaces the alpha layer in this image. If the image + # does not have an alpha layer, it's converted to "LA" or "RGBA". + # The new layer must be either "L" or "1". + # + # @param im The new alpha layer. This can either be an "L" or "1" + # image having the same size as this image, or an integer or + # other color value. + + def putalpha(self, alpha): + "Set alpha layer" + + self.load() + if self.readonly: + self._copy() + + if self.mode not in ("LA", "RGBA"): + # attempt to promote self to a matching alpha mode + try: + mode = getmodebase(self.mode) + "A" + try: + self.im.setmode(mode) + except (AttributeError, ValueError): + # do things the hard way + im = self.im.convert(mode) + if im.mode not in ("LA", "RGBA"): + raise ValueError # sanity check + self.im = im + self.mode = self.im.mode + except (KeyError, ValueError): + raise ValueError("illegal image mode") + + if self.mode == "LA": + band = 1 + else: + band = 3 + + if isImageType(alpha): + # alpha layer + if alpha.mode not in ("1", "L"): + raise ValueError("illegal image mode") + alpha.load() + if alpha.mode == "1": + alpha = alpha.convert("L") + else: + # constant alpha + try: + self.im.fillband(band, alpha) + except (AttributeError, ValueError): + # do things the hard way + alpha = new("L", self.size, alpha) + else: + return + + self.im.putband(alpha.im, band) + + ## + # Copies 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: <b>pixel = value*scale + offset</b>. + # + # @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() + if self.readonly: + self._copy() + + self.im.putdata(data, scale, offset) + + ## + # Attaches a palette to this image. The image must be a "P" or + # "L" image, and the palette sequence must 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 (either a list or a string). + + 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. + # <p> + # Note that this method is relatively slow. For more extensive + # changes, use {@link #Image.paste} or the <b>ImageDraw</b> 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() + if self.readonly: + self._copy() + + return self.im.putpixel(xy, value) + + ## + # Returns a resized copy of this 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 <b>NEAREST</b> (use nearest neighbour), <b>BILINEAR</b> + # (linear interpolation in a 2x2 environment), <b>BICUBIC</b> + # (cubic spline interpolation in a 4x4 environment), or + # <b>ANTIALIAS</b> (a high-quality downsampling filter). + # If omitted, or if the image has mode "1" or "P", it is + # set <b>NEAREST</b>. + # @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 copy of this image. This method returns a + # copy of this 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 <b>NEAREST</b> (use nearest neighbour), <b>BILINEAR</b> + # (linear interpolation in a 2x2 environment), or <b>BICUBIC</b> + # (cubic spline interpolation in a 4x4 environment). + # If omitted, or if the image has mode "1" or "P", it is + # set <b>NEAREST</b>. + # @param expand Optional expansion flag. If true, expands the output + # image to make it large enough to hold the entire rotated image. + # If false or omitted, make the output image the same size as the + # input image. + # @return An Image object. + + def rotate(self, angle, resample=NEAREST, expand=0): + "Rotate image. Angle given as degrees counter-clockwise." + + if expand: + import math + angle = -angle * math.pi / 180 + matrix = [ + math.cos(angle), math.sin(angle), 0.0, + -math.sin(angle), math.cos(angle), 0.0 + ] + def transform(x, y, (a, b, c, d, e, f)=matrix): + return a*x + b*y + c, d*x + e*y + f + + # calculate output size + w, h = self.size + xx = [] + yy = [] + for x, y in ((0, 0), (w, 0), (w, h), (0, h)): + x, y = transform(x, y) + xx.append(x) + yy.append(y) + w = int(math.ceil(max(xx)) - math.floor(min(xx))) + h = int(math.ceil(max(yy)) - math.floor(min(yy))) + + # adjust center + x, y = transform(w / 2.0, h / 2.0) + matrix[2] = self.size[0] / 2.0 - x + matrix[5] = self.size[1] / 2.0 - y + + return self.transform((w, h), AFFINE, matrix) + + 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 this image under the given filename. If no format is + # specified, the format to use is determined from the filename + # extension, if possible. + # <p> + # 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. + # <p> + # 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 <b>seek</b>, <b>tell</b>, and <b>write</b> + # 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 + # @exception KeyError If the output format could not be determined + # from the file name. Use the format option to solve this. + # @exception IOError If the file could not be written. The file + # may have been created, and may contain partial data. + + def save(self, fp, format=None, **params): + "Save image to file or stream" + + if isStringType(fp): + filename = fp + else: + if hasattr(fp, "name") and isStringType(fp.name): + filename = fp.name + else: + filename = "" + + # may mutate self! + self.load() + + self.encoderinfo = params + self.encoderconfig = () + + preinit() + + ext = string.lower(os.path.splitext(filename)[1]) + + if not format: + try: + format = EXTENSION[ext] + except KeyError: + init() + try: + format = EXTENSION[ext] + except KeyError: + raise KeyError(ext) # unknown extension + + try: + save_handler = SAVE[string.upper(format)] + except KeyError: + init() + save_handler = SAVE[string.upper(format)] # unknown format + + if isStringType(fp): + import __builtin__ + fp = __builtin__.open(fp, "wb") + close = 1 + else: + close = 0 + + try: + save_handler(self, fp, filename) + finally: + # do what we can to clean up + if close: + fp.close() + + ## + # Seeks to the given frame in this sequence file. If you seek + # beyond the end of the sequence, the method raises an + # <b>EOFError</b> exception. When a sequence file is opened, the + # library automatically seeks to frame 0. + # <p> + # 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 If the call attempts 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 this image. This method is mainly intended for + # debugging purposes. + # <p> + # On Unix platforms, this method saves the image to a temporary + # PPM file, and calls the <b>xv</b> utility. + # <p> + # 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)" + + _showxv(self, title, command) + + ## + # Split this image into individual bands. This method 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 this image into a 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 {@link + # #Image.draft} method to configure the file reader (where + # applicable), and finally resizes the image. + # <p> + # Note that the bilinear and bicubic filters in the current + # version of PIL are not well-suited for thumbnail generation. + # You should use <b>ANTIALIAS</b> unless speed is much more + # important than quality. + # <p> + # 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 {@link #Image.copy} of the original image. + # + # @param size Requested size. + # @param resample Optional resampling filter. This can be one + # of <b>NEAREST</b>, <b>BILINEAR</b>, <b>BICUBIC</b>, or + # <b>ANTIALIAS</b> (best quality). If omitted, it defaults + # to <b>NEAREST</b> (this will be changed to ANTIALIAS in a + # future version). + # @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 = max(y * size[0] / x, 1); x = size[0] + if y > size[1]: x = max(x * size[1] / y, 1); 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. + + ## + # Transforms this 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. + # <p> + # @def transform(size, method, data, resample=NEAREST) + # @param size The output size. + # @param method The transformation method. This is one of + # <b>EXTENT</b> (cut out a rectangular subregion), <b>AFFINE</b> + # (affine transform), <b>PERSPECTIVE</b> (perspective + # transform), <b>QUAD</b> (map a quadrilateral to a + # rectangle), or <b>MESH</b> (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 + # <b>NEAREST</b> (use nearest neighbour), <b>BILINEAR</b> + # (linear interpolation in a 2x2 environment), or + # <b>BICUBIC</b> (cubic spline interpolation in a 4x4 + # environment). If omitted, or if the image has mode + # "1" or "P", it is set to <b>NEAREST</b>. + # @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): + + # 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 == PERSPECTIVE: + # change argument order to match implementation + data = (data[2], data[0], data[1], + data[5], data[3], data[4], + data[6], data[7]) + 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 this image. + # + # @param method One of <b>FLIP_LEFT_RIGHT</b>, <b>FLIP_TOP_BOTTOM</b>, + # <b>ROTATE_90</b>, <b>ROTATE_180</b>, or <b>ROTATE_270</b>. + + 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) + + x0, y0, x1, y1 = box + if x1 < x0: + x1 = x0 + if y1 < y0: + y1 = y0 + + self.mode = im.mode + self.size = x1-x0, y1-y0 + + self.__crop = x0, y0, x1, y1 + + 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) in pixels. +# @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. +# <p> +# In its simplest form, this function takes three arguments +# (mode, size, and unpacked pixel data). +# <p> +# You can also use any pixel decoder supported by PIL. For more +# information on available decoders, see the section <a +# href="pil-decoder.htm"><i>Writing Your Own File Decoder</i></a>. +# <p> +# Note that this function decodes pixel data only, not entire images. +# If you have an entire image in a string, wrap it in a +# <b>StringIO</b> object, and use {@link #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 + +## +# (New in 1.1.4) Creates an image memory from pixel data in a string +# or byte buffer. +# <p> +# This function is similar to {@link #fromstring}, but uses data in +# the byte buffer, where possible. This means that changes to the +# original buffer object are reflected in this image). Not all modes +# can share memory; supported modes include "L", "RGBX", "RGBA", and +# "CMYK". +# <p> +# Note that this function decodes pixel data only, not entire images. +# If you have an entire image file in a string, wrap it in a +# <b>StringIO</b> object, and use {@link #open} to load it. +# <p> +# In the current version, the default parameters used for the "raw" +# decoder differs from that used for {@link fromstring}. This is a +# bug, and will probably be fixed in a future release. The current +# release issues a warning if you do this; to disable the warning, +# you should provide the full set of parameters. See below for +# details. +# +# @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. For the +# default encoder ("raw"), it's recommended that you provide the +# full set of parameters: +# <b>frombuffer(mode, size, data, "raw", mode, 0, 1)</b>. +# @return An Image object. +# @since 1.1.4 + +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 == (): + if warnings: + warnings.warn( + "the frombuffer defaults may change in a future release; " + "for portability, change the call to read:\n" + " frombuffer(mode, size, data, 'raw', mode, 0, 1)", + RuntimeWarning, stacklevel=2 + ) + args = mode, 0, -1 # may change to (mode, 0, 1) post-1.1.6 + 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)) + + +## +# (New in 1.1.6) Create an image memory from an object exporting +# the array interface (using the buffer protocol). +# +# If obj is not contiguous, then the tostring method is called +# and {@link frombuffer} is used. +# +# @param obj Object with array interface +# @param mode Mode to use (will be determined from type if None) +# @return An image memory. + +def fromarray(obj, mode=None): + arr = obj.__array_interface__ + shape = arr['shape'] + ndim = len(shape) + try: + strides = arr['strides'] + except KeyError: + strides = None + if mode is None: + typestr = arr['typestr'] + if not (typestr[0] == '|' or typestr[0] == _ENDIAN or + typestr[1:] not in ['u1', 'b1', 'i4', 'f4']): + raise TypeError("cannot handle data-type") + typestr = typestr[:2] + if typestr == 'i4': + mode = 'I' + elif typestr == 'f4': + mode = 'F' + elif typestr == 'b1': + mode = '1' + elif ndim == 2: + mode = 'L' + elif ndim == 3: + mode = 'RGB' + elif ndim == 4: + mode = 'RGBA' + else: + raise TypeError("Do not understand data.") + ndmax = 4 + bad_dims=0 + if mode in ['1','L','I','P','F']: + ndmax = 2 + elif mode == 'RGB': + ndmax = 3 + if ndim > ndmax: + raise ValueError("Too many dimensions.") + + size = shape[:2][::-1] + if strides is not None: + obj = obj.tostring() + + return frombuffer(mode, size, obj, "raw", mode, 0, 1) + +## +# Opens and identifies the given image file. +# <p> +# 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 {@link #Image.load} method). +# +# @def open(file, mode="r") +# @param file A filename (string) or a file object. The file object +# must implement <b>read</b>, <b>seek</b>, and <b>tell</b> 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 two input images, using +# a constant alpha. +# +# <pre> +# out = image1 * (1.0 - alpha) + image2 * alpha +# </pre> +# +# @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 two input 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 must 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 + +## +# Registers 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 + +## +# Registers 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 + +## +# Registers 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(image, title=None, command=None): + + if os.name == "nt": + format = "BMP" + elif sys.platform == "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 image.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(image.mode) + if base != image.mode and image.mode != "1": + file = image.convert(base)._dump(format=format) + else: + file = image._dump(format=format) + + if os.name == "nt": + command = "start /wait %s && del /f %s" % (file, file) + elif sys.platform == "darwin": + # on darwin open returns immediately resulting in the temp + # file removal while app is opening + command = "(%s %s; sleep 20; rm -f %s)&" % (command, file, file) + else: + command = "(%s %s; rm -f %s)&" % (command, file, file) + + os.system(command) |