6
\$\begingroup\$

This program takes in an image, and saves an "arrowed" version of that image to /tmp/image.png. Example input and output is below the code.

from PIL import Image, ImageDraw
import operator
from pprint import pprint
import sys

base_width = 10
arrow_base_height = 15
arrow_point_height = 15
margin = (20,20)
do_outline = True
outline = (0, 0, 0) # black
background_color  = (255, 255, 255) # white

arrow_width = base_width / 2
total_arrow_height = arrow_base_height + arrow_point_height
total_arrow_width = 2 * base_width

def drawArrow(coords, color):
    if do_outline:
        draw.polygon(coords, fill=color, outline=outline)
    else:
        draw.polygon(coords, fill=color)

def to_real_coordinates(coords):
    # translates the coords to pixels on the picture
    return translate(coords, margin)

def translate(coords, vector):
    # Translates a list of coordinate tuples over a vector tuple
    t_coords = []
    for cord in coords:
        t_coords.append(tuple(map(operator.add, cord, vector)))
    return t_coords

def mirror(coords):
    # Takes a list of coordinate tuples and mirrors it across the first element of
    #    the first tuple
    # Formula: 2 * base - original
    m_coords = []
    base = coords[0]
    double_base = tuple(map(operator.mul, base, len(base)* (2,) ))
    for cord in coords:
        m_coords.append(tuple(map(operator.sub, double_base, cord)))
    return m_coords

def get_arrow_coords():
    coords = [
        (0, 0),
        (arrow_base_height, 0),
        (arrow_base_height, arrow_width),
        (arrow_base_height + arrow_point_height, - arrow_width),
        (arrow_base_height, -3 * arrow_width),
        (arrow_base_height, - base_width),
        (0, - base_width)
    ]
    return coords

if __name__ == "__main__":
    orig = Image.open(sys.argv[1]).transpose(Image.ROTATE_90)
    pix = orig.load()
    new_size = (1024,1024)
    actual_size = (new_size[0] + 2 * margin[0], new_size[1] + 2*margin[1])
    im = Image.new("RGB", actual_size, background_color)
    draw = ImageDraw.Draw(im)
    arrow = get_arrow_coords()
    m_arrow = mirror(arrow)
    for i in range(new_size[0] / total_arrow_height):
        for j in range((new_size[1] / total_arrow_width)):
            color = pix[
                i * total_arrow_height * orig.size[0] / new_size[0],
                j * total_arrow_width * orig.size[1] / new_size[1]
            ]
            # calculate and draw arrow
            coords = translate(arrow, (i * total_arrow_height, j * total_arrow_width))
            real_coords = to_real_coordinates(coords)
            drawArrow(real_coords, color)
            # calculate and draw mirrored arrow
            coords = translate(m_arrow, (arrow_base_height + i * total_arrow_height, j * total_arrow_width))
            real_coords = to_real_coordinates(coords)
            drawArrow(real_coords, color)

    im = im.transpose(Image.ROTATE_270)
    im.show()
    im.save("/tmp/image.png")

input enter image description here

\$\endgroup\$
  • \$\begingroup\$ Looks kind of cool, but might I ask why you want to convert images to "arrow images"? \$\endgroup\$ – holroy May 14 '17 at 13:15
  • 2
    \$\begingroup\$ @holroy: it's an art project I'm doing. It's basically a mix of Escher, Above and Invaders. \$\endgroup\$ – redfast00 May 14 '17 at 13:33
3
\$\begingroup\$

Be consistent and pythonic in naming functions.

draw_arrow instead of drawArrow

Use comprehensions instead of creating empty list and appending to it in a for loop. It's both faster and easier to read.

def translate(coords, vector):
    # Translates a list of coordinate tuples over a vector tuple
    return tuple(tuple(map(operator.add, c, vector)) for c in coords)

def mirror(coords):
    # Takes a list of coordinate tuples and mirrors it across the first element of
    #    the first tuple
    # Formula: 2 * base - original
    base = coords[0]
    double_base = tuple(map(operator.mul, base, len(base) * (2,) ))
    return tuple(tuple(map(operator.sub, double_base, cord)) for c in coords)

Why have a function that returns a constant value. Why creating a variable when you can just return the value.

def get_arrow_coords():
    return [...]
# or if you worry about mutability - use tuples. They actually faster if you wouldn't try to modify them (creating new ones from old ones) a lot.

arrow_coords = ...

You can also use collections.namedtuple('Size', 'height width') instead of using plain tuples for sizes. This would improve readability a little.

And I'm not sure, but maybe you would benefit from using numpy as it seems that you're doing some matrices work.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.