Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/PIL/ImageOps.py
diff options
context:
space:
mode:
Diffstat (limited to 'PIL/ImageOps.py')
-rw-r--r--PIL/ImageOps.py408
1 files changed, 408 insertions, 0 deletions
diff --git a/PIL/ImageOps.py b/PIL/ImageOps.py
new file mode 100644
index 0000000..89b5e72
--- /dev/null
+++ b/PIL/ImageOps.py
@@ -0,0 +1,408 @@
+#
+# The Python Imaging Library.
+# $Id: ImageOps.py 2760 2006-06-19 13:31:40Z fredrik $
+#
+# standard image operations
+#
+# History:
+# 2001-10-20 fl Created
+# 2001-10-23 fl Added autocontrast operator
+# 2001-12-18 fl Added Kevin's fit operator
+# 2004-03-14 fl Fixed potential division by zero in equalize
+# 2005-05-05 fl Fixed equalize for low number of values
+#
+# Copyright (c) 2001-2004 by Secret Labs AB
+# Copyright (c) 2001-2004 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+import Image
+import operator
+
+##
+# (New in 1.1.3) The <b>ImageOps</b> module contains a number of
+# 'ready-made' image processing operations. This module is somewhat
+# experimental, and most operators only work on L and RGB images.
+#
+# @since 1.1.3
+##
+
+#
+# helpers
+
+def _border(border):
+ if type(border) is type(()):
+ if len(border) == 2:
+ left, top = right, bottom = border
+ elif len(border) == 4:
+ left, top, right, bottom = border
+ else:
+ left = top = right = bottom = border
+ return left, top, right, bottom
+
+def _color(color, mode):
+ if Image.isStringType(color):
+ import ImageColor
+ color = ImageColor.getcolor(color, mode)
+ return color
+
+def _lut(image, lut):
+ if image.mode == "P":
+ # FIXME: apply to lookup table, not image data
+ raise NotImplementedError("mode P support coming soon")
+ elif image.mode in ("L", "RGB"):
+ if image.mode == "RGB" and len(lut) == 256:
+ lut = lut + lut + lut
+ return image.point(lut)
+ else:
+ raise IOError, "not supported for this image mode"
+
+#
+# actions
+
+##
+# Maximize (normalize) image contrast. This function calculates a
+# histogram of the input image, removes <i>cutoff</i> percent of the
+# lightest and darkest pixels from the histogram, and remaps the image
+# so that the darkest pixel becomes black (0), and the lightest
+# becomes white (255).
+#
+# @param image The image to process.
+# @param cutoff How many percent to cut off from the histogram.
+# @param ignore The background pixel value (use None for no background).
+# @return An image.
+
+def autocontrast(image, cutoff=0, ignore=None):
+ "Maximize image contrast, based on histogram"
+ histogram = image.histogram()
+ lut = []
+ for layer in range(0, len(histogram), 256):
+ h = histogram[layer:layer+256]
+ if ignore is not None:
+ # get rid of outliers
+ try:
+ h[ignore] = 0
+ except TypeError:
+ # assume sequence
+ for ix in ignore:
+ h[ix] = 0
+ if cutoff:
+ # cut off pixels from both ends of the histogram
+ # get number of pixels
+ n = 0
+ for ix in range(256):
+ n = n + h[ix]
+ # remove cutoff% pixels from the low end
+ cut = n * cutoff / 100
+ for lo in range(256):
+ if cut > h[lo]:
+ cut = cut - h[lo]
+ h[lo] = 0
+ else:
+ h[lo] = h[lo] - cut
+ cut = 0
+ if cut <= 0:
+ break
+ # remove cutoff% samples from the hi end
+ cut = n * cutoff / 100
+ for hi in range(255, -1, -1):
+ if cut > h[hi]:
+ cut = cut - h[hi]
+ h[hi] = 0
+ else:
+ h[hi] = h[hi] - cut
+ cut = 0
+ if cut <= 0:
+ break
+ # find lowest/highest samples after preprocessing
+ for lo in range(256):
+ if h[lo]:
+ break
+ for hi in range(255, -1, -1):
+ if h[hi]:
+ break
+ if hi <= lo:
+ # don't bother
+ lut.extend(range(256))
+ else:
+ scale = 255.0 / (hi - lo)
+ offset = -lo * scale
+ for ix in range(256):
+ ix = int(ix * scale + offset)
+ if ix < 0:
+ ix = 0
+ elif ix > 255:
+ ix = 255
+ lut.append(ix)
+ return _lut(image, lut)
+
+##
+# Colorize grayscale image. The <i>black</i> and <i>white</i>
+# arguments should be RGB tuples; this function calculates a colour
+# wedge mapping all black pixels in the source image to the first
+# colour, and all white pixels to the second colour.
+#
+# @param image The image to colourize.
+# @param black The colour to use for black input pixels.
+# @param white The colour to use for white input pixels.
+# @return An image.
+
+def colorize(image, black, white):
+ "Colorize a grayscale image"
+ assert image.mode == "L"
+ black = _color(black, "RGB")
+ white = _color(white, "RGB")
+ red = []; green = []; blue = []
+ for i in range(256):
+ red.append(black[0]+i*(white[0]-black[0])/255)
+ green.append(black[1]+i*(white[1]-black[1])/255)
+ blue.append(black[2]+i*(white[2]-black[2])/255)
+ image = image.convert("RGB")
+ return _lut(image, red + green + blue)
+
+##
+# Remove border from image. The same amount of pixels are removed
+# from all four sides. This function works on all image modes.
+#
+# @param image The image to crop.
+# @param border The number of pixels to remove.
+# @return An image.
+# @see Image#Image.crop
+
+def crop(image, border=0):
+ "Crop border off image"
+ left, top, right, bottom = _border(border)
+ return image.crop(
+ (left, top, image.size[0]-right, image.size[1]-bottom)
+ )
+
+##
+# Deform the image.
+#
+# @param image The image to deform.
+# @param deformer A deformer object. Any object that implements a
+# <b>getmesh</b> method can be used.
+# @param resample What resampling filter to use.
+# @return An image.
+
+def deform(image, deformer, resample=Image.BILINEAR):
+ "Deform image using the given deformer"
+ return image.transform(
+ image.size, Image.MESH, deformer.getmesh(image), resample
+ )
+
+##
+# Equalize the image histogram. This function applies a non-linear
+# mapping to the input image, in order to create a uniform
+# distribution of grayscale values in the output image.
+#
+# @param image The image to equalize.
+# @param mask An optional mask. If given, only the pixels selected by
+# the mask are included in the analysis.
+# @return An image.
+
+def equalize(image, mask=None):
+ "Equalize image histogram"
+ if image.mode == "P":
+ image = image.convert("RGB")
+ h = image.histogram(mask)
+ lut = []
+ for b in range(0, len(h), 256):
+ histo = filter(None, h[b:b+256])
+ if len(histo) <= 1:
+ lut.extend(range(256))
+ else:
+ step = (reduce(operator.add, histo) - histo[-1]) / 255
+ if not step:
+ lut.extend(range(256))
+ else:
+ n = step / 2
+ for i in range(256):
+ lut.append(n / step)
+ n = n + h[i+b]
+ return _lut(image, lut)
+
+##
+# Add border to the image
+#
+# @param image The image to expand.
+# @param border Border width, in pixels.
+# @param fill Pixel fill value (a colour value). Default is 0 (black).
+# @return An image.
+
+def expand(image, border=0, fill=0):
+ "Add border to image"
+ left, top, right, bottom = _border(border)
+ width = left + image.size[0] + right
+ height = top + image.size[1] + bottom
+ out = Image.new(image.mode, (width, height), _color(fill, image.mode))
+ out.paste(image, (left, top))
+ return out
+
+##
+# Returns a sized and cropped version of the image, cropped to the
+# requested aspect ratio and size.
+# <p>
+# The <b>fit</b> function was contributed by Kevin Cazabon.
+#
+# @param size The requested output size in pixels, given as a
+# (width, height) tuple.
+# @param method What resampling method to use. Default is Image.NEAREST.
+# @param bleed Remove a border around the outside of the image (from all
+# four edges. The value is a decimal percentage (use 0.01 for one
+# percent). The default value is 0 (no border).
+# @param centering Control the cropping position. Use (0.5, 0.5) for
+# center cropping (e.g. if cropping the width, take 50% off of the
+# left side, and therefore 50% off the right side). (0.0, 0.0)
+# will crop from the top left corner (i.e. if cropping the width,
+# take all of the crop off of the right side, and if cropping the
+# height, take all of it off the bottom). (1.0, 0.0) will crop
+# from the bottom left corner, etc. (i.e. if cropping the width,
+# take all of the crop off the left side, and if cropping the height
+# take none from the top, and therefore all off the bottom).
+# @return An image.
+
+def fit(image, size, method=Image.NEAREST, bleed=0.0, centering=(0.5, 0.5)):
+ """
+ This method returns a sized and cropped version of the image,
+ cropped to the aspect ratio and size that you request.
+ """
+
+ # by Kevin Cazabon, Feb 17/2000
+ # kevin@cazabon.com
+ # http://www.cazabon.com
+
+ # ensure inputs are valid
+ if type(centering) != type([]):
+ centering = [centering[0], centering[1]]
+
+ if centering[0] > 1.0 or centering[0] < 0.0:
+ centering [0] = 0.50
+ if centering[1] > 1.0 or centering[1] < 0.0:
+ centering[1] = 0.50
+
+ if bleed > 0.49999 or bleed < 0.0:
+ bleed = 0.0
+
+ # calculate the area to use for resizing and cropping, subtracting
+ # the 'bleed' around the edges
+
+ # number of pixels to trim off on Top and Bottom, Left and Right
+ bleedPixels = (
+ int((float(bleed) * float(image.size[0])) + 0.5),
+ int((float(bleed) * float(image.size[1])) + 0.5)
+ )
+
+ liveArea = (
+ bleedPixels[0], bleedPixels[1], image.size[0] - bleedPixels[0] - 1,
+ image.size[1] - bleedPixels[1] - 1
+ )
+
+ liveSize = (liveArea[2] - liveArea[0], liveArea[3] - liveArea[1])
+
+ # calculate the aspect ratio of the liveArea
+ liveAreaAspectRatio = float(liveSize[0])/float(liveSize[1])
+
+ # calculate the aspect ratio of the output image
+ aspectRatio = float(size[0]) / float(size[1])
+
+ # figure out if the sides or top/bottom will be cropped off
+ if liveAreaAspectRatio >= aspectRatio:
+ # liveArea is wider than what's needed, crop the sides
+ cropWidth = int((aspectRatio * float(liveSize[1])) + 0.5)
+ cropHeight = liveSize[1]
+ else:
+ # liveArea is taller than what's needed, crop the top and bottom
+ cropWidth = liveSize[0]
+ cropHeight = int((float(liveSize[0])/aspectRatio) + 0.5)
+
+ # make the crop
+ leftSide = int(liveArea[0] + (float(liveSize[0]-cropWidth) * centering[0]))
+ if leftSide < 0:
+ leftSide = 0
+ topSide = int(liveArea[1] + (float(liveSize[1]-cropHeight) * centering[1]))
+ if topSide < 0:
+ topSide = 0
+
+ out = image.crop(
+ (leftSide, topSide, leftSide + cropWidth, topSide + cropHeight)
+ )
+
+ # resize the image and return it
+ return out.resize(size, method)
+
+##
+# Flip the image vertically (top to bottom).
+#
+# @param image The image to flip.
+# @return An image.
+
+def flip(image):
+ "Flip image vertically"
+ return image.transpose(Image.FLIP_TOP_BOTTOM)
+
+##
+# Convert the image to grayscale.
+#
+# @param image The image to convert.
+# @return An image.
+
+def grayscale(image):
+ "Convert to grayscale"
+ return image.convert("L")
+
+##
+# Invert (negate) the image.
+#
+# @param image The image to invert.
+# @return An image.
+
+def invert(image):
+ "Invert image (negate)"
+ lut = []
+ for i in range(256):
+ lut.append(255-i)
+ return _lut(image, lut)
+
+##
+# Flip image horizontally (left to right).
+#
+# @param image The image to mirror.
+# @return An image.
+
+def mirror(image):
+ "Flip image horizontally"
+ return image.transpose(Image.FLIP_LEFT_RIGHT)
+
+##
+# Reduce the number of bits for each colour channel.
+#
+# @param image The image to posterize.
+# @param bits The number of bits to keep for each channel (1-8).
+# @return An image.
+
+def posterize(image, bits):
+ "Reduce the number of bits per color channel"
+ lut = []
+ mask = ~(2**(8-bits)-1)
+ for i in range(256):
+ lut.append(i & mask)
+ return _lut(image, lut)
+
+##
+# Invert all pixel values above a threshold.
+#
+# @param image The image to posterize.
+# @param threshold All pixels above this greyscale level are inverted.
+# @return An image.
+
+def solarize(image, threshold=128):
+ "Invert all values above threshold"
+ lut = []
+ for i in range(256):
+ if i < threshold:
+ lut.append(i)
+ else:
+ lut.append(255-i)
+ return _lut(image, lut)