# 
 | 
# The Python Imaging Library. 
 | 
# $Id$ 
 | 
# 
 | 
# 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) 
 | 
  
 | 
# -------------------------------------------------------------------- 
 | 
# PIL USM components, from Kevin Cazabon. 
 | 
  
 | 
def gaussian_blur(im, radius=None): 
 | 
    """ PIL_usm.gblur(im, [radius])""" 
 | 
  
 | 
    if radius is None: 
 | 
        radius = 5.0 
 | 
  
 | 
    im.load() 
 | 
  
 | 
    return im.im.gaussian_blur(radius) 
 | 
  
 | 
gblur = gaussian_blur 
 | 
  
 | 
def unsharp_mask(im, radius=None, percent=None, threshold=None): 
 | 
    """ PIL_usm.usm(im, [radius, percent, threshold])""" 
 | 
  
 | 
    if radius is None: 
 | 
        radius = 5.0 
 | 
    if percent is None: 
 | 
        percent = 150 
 | 
    if threshold is None: 
 | 
        threshold = 3 
 | 
  
 | 
    im.load() 
 | 
  
 | 
    return im.im.unsharp_mask(radius, percent, threshold) 
 | 
  
 | 
usm = unsharp_mask 
 |