# Python image art project

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:
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)
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")


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

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.