view tools/image_transforms.py @ 2:7ac913487a09 default tip

README modified
author tomasz-bednarz
date Tue, 26 Nov 2013 13:41:11 +1100
parents 64374d852e36
children
line wrap: on
line source

"""Transformations on images"""
from numpy import random
from numpy import array
from PIL import Image
from PIL import ImageDraw
from PIL import ImageOps

def tileify(image_data, num_blocks, max_shift):
    """
    Breaks image up into rectangles and shifts them a random distance
    Areas not covered by the moved rectangles are the negative of
    the original image
    """
    width = image_data.size[0]
    height = image_data.size[1]
    row_block_size = round(height / num_blocks)
    col_block_size = round(width / num_blocks)
    negative_image = ImageOps.invert(image_data)
    pixels = array(image_data)
    for row in xrange(num_blocks):
        inner_row_val = int(row_block_size * row)
        outer_row_val = int(row_block_size * row + row_block_size)
        for col in xrange(num_blocks):
            inner_col_val = int(col_block_size * col)
            outer_col_val = int(col_block_size * col + col_block_size)
            block = pixels[
                inner_row_val:outer_row_val,
                inner_col_val:outer_col_val
            ]
            location = calculate_random_location(
                inner_row_val,
                inner_col_val,
                max_shift
            )
            negative_image.paste(Image.fromarray(block), location)
    return negative_image

def calculate_random_location(x, y, max_shift):
    """
    Calculates random location near some starting point
    Inputs
        x - coordinate point where 0,0 is upper left corner
        y - coordinate point where 0,0 is upper left corner
        max_shift - the furthest distance from the point to shift
    """
    x_offset = random.random_integers(-max_shift, max_shift)
    y_offset = random.random_integers(-max_shift, max_shift)
    location = (
        y + y_offset,
        x + x_offset
    )
    return location

def filmify_image(image_data):
    """
    Makes the image look like 35mm film
    Inputs:
        PIL Image
    Returns:
        Modified image data
    """
    # print 'Filming'
    # Size of photo in side the film
    film_size = (400, 300)
    border_size = 30
    film_hole_size = (8, 10)

    filmed_image = modify_photo(image_data, film_size)
    filmed_image = add_film_border(filmed_image, film_size, border_size)

    # Center strip in the border space
    strip_upper_offset = int(round((border_size - film_hole_size[1])/2))
    strip_lower_offset = film_size[1] + border_size + strip_upper_offset
    # Bring strip in towards the photo
    offset = int(round(film_hole_size[1]/2))
    strip_upper_offset = strip_upper_offset + offset
    strip_lower_offset = strip_lower_offset - offset

    place_film_strip(
        filmed_image,
        strip_upper_offset,
        strip_lower_offset,
        film_hole_size
    )

    return filmed_image

def add_film_border(image_data, film_size, border_size):
    """
    Creates a black border around the image
    Inputs
        image_data - image to be manipulated
        film_size - size of the internal picture
        border_size - how wide you want the border on the top and bottom to be
    Output
        image with border
    """
    image_data = ImageOps.expand(
        image_data,
        border=border_size,
        fill='black'
    )
    # Crop to cut half of border from right and left of image
    crop_box = (
        int(round(border_size/2)),
        0,
        film_size[0] + int(round(border_size * 1.5)),
        film_size[1] + border_size * 2
    )
    image_data = image_data.crop(crop_box)
    return image_data

def place_film_strip(image_data, y_offset_upper, y_offset_lower, film_hole_size):
    """
    Create strip of film_holes at y_offset
    """
    hole_distance = 15
    left_film_buffer = 2
    for x_offset in range(left_film_buffer, image_data.size[0], hole_distance):
        place_film_hole(image_data, (x_offset, y_offset_upper), film_hole_size)
        place_film_hole(image_data, (x_offset, y_offset_lower), film_hole_size)

def place_film_hole(image_data, offset, film_hole_size):
    """
    Puts film hole at offset into the image provided
    Inputs:
        image_data - image file to have film hole pasted
        offset - 2 tuple with x and y of the top left corner of film hole
        film_hole_size - size of the rectangle to be made
    Outputs
        Original image with rectangle in location based on the offset
    """
    corner_radius = 2
    film_hole_color = 'white'
    film_hole = round_rectangle(film_hole_size, corner_radius, film_hole_color)
    image_data.paste(film_hole, offset)

def round_corner(radius, fill):
    """
    Draw a round corner
    This code came from http://nadiana.com/pil-tutorial-basic-advanced-drawing
    """
    corner = Image.new('RGBA', (radius, radius), (0, 0, 0, 0))
    draw = ImageDraw.Draw(corner)
    draw.pieslice((0, 0, radius * 2, radius * 2), 180, 270, fill=fill)
    return corner

def round_rectangle(size, radius, fill):
    """
    Draw a rounded rectangle
    This code came from http://nadiana.com/pil-tutorial-basic-advanced-drawing
    """
    width, height = size
    rectangle = Image.new('RGBA', size, fill)
    corner = round_corner(radius, fill)
    rectangle.paste(corner, (0, 0))
    # Rotate the corner and paste it
    rectangle.paste(corner.rotate(90), (0, height - radius))
    rectangle.paste(corner.rotate(180), (width - radius, height - radius))
    rectangle.paste(corner.rotate(270), (width - radius, 0))
    return rectangle

def modify_photo(image_data, film_size):
    """
    Make the image grayscale and invert the colors
    All manipulations for the original photo go here
    """
    modified_image = ImageOps.grayscale(image_data)
    # Image.ANTIALIAS is best for down sizing
    modified_image = modified_image.resize(film_size, Image.ANTIALIAS)
    modified_image = ImageOps.invert(modified_image)
    modified_image = ImageOps.flip(modified_image)
    return modified_image