Created
September 26, 2024 18:02
-
-
Save bbengfort/19ce3cd70c1fc7181866cdcbc4e4590b to your computer and use it in GitHub Desktop.
A utility to draw Rotational colored hexagonal tiles. Adapted from https://variable-scope.com/posts/hexagon-tilings-with-python
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
import math | |
import random | |
import argparse | |
from PIL import Image | |
from aggdraw import Draw, Brush | |
COLORS = { | |
# dark to light | |
"space_cadet": (25, 46, 91), | |
"lapis_lazuli": (29, 101, 166), | |
"air_superiority": (114, 162, 192), | |
"sky_blue": (128, 218, 236), | |
"water": (205, 247, 247), | |
# dark to light | |
"dark_slate": (47, 72, 88), | |
"philippine_gray": (140, 140, 140), | |
"metallic_silver": (161, 169, 180), | |
"platinum": (227, 227, 225), | |
"ghost_white": (248, 248, 255), | |
# just white | |
"white": (255, 255, 255), | |
} | |
ALL_COLORS = list(COLORS.values()) | |
class HexagonGenerator(object): | |
""" | |
Generates hexagon paths for the specified size | |
""" | |
def __init__(self, edge_length): | |
self.edge = edge_length | |
@property | |
def pattern_size(self): | |
return self.width, self.height*2 | |
@property | |
def width(self): | |
return self.edge * 3 | |
@property | |
def height(self): | |
return math.sin(math.pi / 3) * self.edge | |
def rows(self, height): | |
""" | |
Returns the number of rows required to fill the canvas height. | |
""" | |
return int(math.ceil(height/self.height)) | |
def __call__(self, row, col): | |
x = (col + 0.5 * (row % 2)) * self.width | |
y = row * self.height | |
for angle in range(0, 360, 60): | |
x += math.cos(math.radians(angle)) * self.edge | |
y += math.sin(math.radians(angle)) * self.edge | |
yield x | |
yield y | |
def create_canvas(pattern_size, repetitions): | |
""" | |
Returns an Image that fits the given number of pattern repetitions. | |
""" | |
width = int(repetitions*pattern_size[0]) | |
height = int(round(width / pattern_size[1]) * pattern_size[1]) | |
return Image.new('RGB', (width, height), 'white') | |
def tiled_canvas(width, height): | |
if width is None and height is None: | |
return None | |
if width is None: | |
width = height | |
if height is None: | |
height = width | |
return Image.new('RGB', (width, height), 'white') | |
def random_color(): | |
""" | |
Returns a random RGB color from the color options. | |
""" | |
return random.choice(ALL_COLORS) | |
def main(args): | |
# Generate hexagon tiles | |
hexagon_generator = HexagonGenerator(args.size) | |
tiles = create_canvas(hexagon_generator.pattern_size, args.repetitions) | |
draw = Draw(tiles) | |
for row in range(hexagon_generator.rows(tiles.size[1])): | |
colors = [random_color() for _ in range(args.repetitions)] | |
for col in range(args.repetitions+1): | |
color = colors[col % args.repetitions] | |
draw.polygon(list(hexagon_generator(row, col)), Brush(color)) | |
for col, color in enumerate(colors): | |
draw.polygon(list(hexagon_generator(-1, col)), Brush(color)) | |
draw.flush() | |
# Perform tiling, if necessary | |
image = tiled_canvas(args.width, args.height) | |
if image is not None: | |
tw, th = tiles.size | |
iw, ih = image.size | |
for i in range(0, iw, tw): | |
for j in range(0, ih, th): | |
image.paste(tiles, (i, j)) | |
else: | |
image = tiles | |
# Show the results to the user | |
if args.out: | |
image.save(args.out) | |
print(f"image saved to {args.out}") | |
else: | |
image.show() | |
if __name__ == "__main__": | |
opts = { | |
('-s', '--size'): { | |
'type': int, 'default': 40, 'metavar': 'PX', | |
'help': 'size in pixels of the hexagon edge length', | |
}, | |
('-r', '--repetitions'): { | |
'type': int, 'default': 2, 'metavar': 'N', | |
'help': 'specify the number of repetitions or rows of the hexagons', # noqa | |
}, | |
('-o', '--out'): { | |
'default': None, 'metavar': 'PATH', | |
'help': 'write the image out to disk instead of showing it', | |
}, | |
('-W', '--width'): { | |
'default': None, 'type': int, 'metavar': 'PX', | |
'help': 'the width of the final canvas to export', | |
}, | |
('-H', '--height'): { | |
'default': None, 'type': int, 'metavar': 'PX', | |
'help': 'the height of the final canvas to export', | |
}, | |
} | |
parser = argparse.ArgumentParser( | |
description="generate rotational colored hexagonal tiles", | |
epilog="from the blog post https://variable-scope.com/posts/hexagon-tilings-with-python" # noqa | |
) | |
for pargs, kwargs in opts.items(): | |
if isinstance(pargs, str): | |
pargs = (pargs,) | |
parser.add_argument(*pargs, **kwargs) | |
args = parser.parse_args() | |
main(args) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment