In part one, we covered the drawing of smooth, anti-aliased hexagons in a grid-like fashion. In this post, we will extend that beginning into a program to draw hexagon fills that can be used for a tiled background. The key improvements that were identified in the previous post:
- Canvas sizing and shape wrapping
- Color wrapping around the edges
- Configurable, mostly random coloring
Canvas sizing and shape wrapping
To be able to use the generated image as a tiled background, the shapes must wrap from left to right. That is, if a row contains a hexagon that is cut off on the left edge, the portion that was cut off should be the end of that row on the right edge, and vice-versa.
This is a complex way of saying that the width of the image must be an exact multiple of the pattern size. The pattern in this case is the smallest shape from which a repeating pattern can be constructed. For the hexagons generator we have, this pattern is one column wide and two rows tall.
Because the pattern size is very closely coupled to the dimensions of the hexagon, we make this information available as a property of
class HexagonGenerator. We then modify the existing script to draw only outlines so we can see how things are constructed:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
class HexagonGenerator(object): # Rest of class as previously defined @property def pattern_size(self): return self.col_width, self.row_height * 2 def main(): hexagon_generator = HexagonGenerator(40) width, height = hexagon_generator.pattern_size image = Image.new('RGB', (int(width * 2), int(height * 3)), 'white') draw = Draw(image) draw.rectangle((0, 0, width, height), Brush('black', opacity=64)) colors = (('red', 'blue'), ('green', 'purple')) for row in range(5): for column in range(3): hexagon = hexagon_generator(row, column) color = colors[row % 2][column % 2] draw.polygon(list(hexagon), Pen(color, width=3)) draw.flush() image.show()
A few days ago I ended up on a website (I forget where) which featured a very nice and subtle square tiling on the background of the page. Now, this is in itself is not astonishing, plenty of folks and businesses out there will present you with all sorts of tessellated backgrounds. However, it got me thinking about doing some tiling background myself. Not because I particularly need one (it certainly wouldn’t look right on this blog), but just to experiment. To make things slightly more interesting I opted for another shape: the hexagon.
A hexagon is a polygon with six edges and six vertices. The regular hexagon is equilateral and all internal angles are 120°. It is also one of three polygons with which you can create a regular tiling. That is, using only hexagons you can fill a plane without any gaps or overlaps. The other two regular shapes with which this can be done are the square and the equilateral triangle.
Drawing a hexagon with PIL
The first step towards tiling hexagons is to create a single hexagon. The first stop when it comes to images in Python is the Python Imaging Library (better known as PIL). Using this library and knowledge of basic math, the following code will generate a single hexagon:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
import math from PIL import Image, ImageDraw def hexagon_generator(edge_length, offset): """Generator for coordinates in a hexagon.""" x, y = offset for angle in range(0, 360, 60): x += math.cos(math.radians(angle)) * edge_length y += math.sin(math.radians(angle)) * edge_length yield x, y def main(): image = Image.new('RGB', (100, 100), 'white') draw = ImageDraw.Draw(image) hexagon = hexagon_generator(40, offset=(30, 15)) draw.polygon(list(hexagon), outline='black', fill='red') image.show()