From fe8c2a2ef602b5518d135ca370c926d2a2baef9b Mon Sep 17 00:00:00 2001 From: Brandon Rozek Date: Fri, 5 Jun 2020 21:09:51 -0400 Subject: [PATCH] Wrote the pillow code utilizing trebuchets to emulate an image. --- .gitignore | 2 ++ treimage/__init__.py | 49 ++++++++++++++++++++++++++++++++++++++++++++ treimage/__main__.py | 30 +++++++++++++++++++++++++++ 3 files changed, 81 insertions(+) create mode 100644 .gitignore create mode 100644 treimage/__main__.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0307de1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*__pycache__* +*.egg-info* \ No newline at end of file diff --git a/treimage/__init__.py b/treimage/__init__.py index a16ded5..14452bb 100644 --- a/treimage/__init__.py +++ b/treimage/__init__.py @@ -4,4 +4,53 @@ image and replicate it with flexible trebuchets. Concept was inspired by OptArt by Robert Bosch. """ +from typing import Optional, Tuple +from PIL import Image, ImageDraw +import numpy as np from . import trebuchet + +__all__ = ['trebuchet', 'create'] + +WHITE = (255, 255, 255) + +def create(image_path: str, shape: trebuchet.FlexibleTrebuchet = trebuchet.D, + color: Optional[Tuple[int, int, int]] = (26, 95, 138), scale: int = 2): + """ + Creates an image out of trebuchet tiles as close to the original image as possible. + Inspired by the OptArt book by Robert Bosch. + + Parameters + ========== + image_path: str + The path of the image that we wish to emulate + color: Optional[Tuple[int, int, int]] + A RGB tuple containing the color of the Trebuchet tile. + If None, then we average the color of the surrounding area. + scale: int + The multiplicative scale of the width and height of a Trebuchet tile. + """ + if shape is None: + shape = trebuchet.D + if scale is None: + scale = 2 + + with Image.open(image_path) as im: + with Image.new('RGB', im.size, WHITE) as new_im: + grayscale_im = im.convert(mode='L') + draw = ImageDraw.Draw(new_im) + width, height = im.size + length = 4 * scale + for x in range(0, width, length): + for y in range(0, height, length): + gray_tile = grayscale_im.crop((x, y, x + length, y + length)) + brightness = np.asarray(gray_tile).mean() / 255 + p = shape(x, y, scale, brightness) + + tile_color = color + if color is None: + tile = im.crop((x, y, x + length, y + length)) + avg_color = tuple(np.asarray(tile).mean((0, 1))) + tile_color = tuple(int(c) for c in tuple(avg_color)) + + draw.polygon(p.points, fill=tile_color) + return new_im diff --git a/treimage/__main__.py b/treimage/__main__.py new file mode 100644 index 0000000..72b2c84 --- /dev/null +++ b/treimage/__main__.py @@ -0,0 +1,30 @@ +from argparse import ArgumentParser +from . import create, trebuchet + +parser = ArgumentParser(description="Create an image out of trebuchet tiles similar to image specified") +parser.add_argument("image_path", type=str, help="The image to emulate.") +parser.add_argument("--shape", type=str, help="The trebuchet shape (A, B, C, D)") +parser.add_argument("--color", type=str, help="The RGB value specified as \"r,g,b\"") +parser.add_argument("--scale", type=int, help="The multiplicative scale for the trebuchet height and width.") +args = vars(parser.parse_args()) + +image_path = args['image_path'] + +shape = args['shape'] +if shape is not None: + shape = dict( + a=trebuchet.A, + b=trebuchet.B, + c=trebuchet.C, + d=trebuchet.D + )[args['shape'].lower()] + +color = args['color'] +if color is not None: + color = tuple(int(c) for c in args['color'].split(",")) + assert len(color) == 3 + +scale = args['scale'] + +im = create(image_path, shape, color, scale) +im.show()