#!/usr/bin/python # -*- coding: utf-8 -*- """Draw a pentagon, inefficiently. I wrote this code for file convolution-bokeh in Dercuano because I wanted a larger example bokeh kernel than the ones I’d drawn by hand. Not too bad for half a page of code. It takes about 60 ms on my laptop to draw the pentagon, suggesting that it could do about 15 polygons per second — about four orders of magnitude slower than the machine is capable of. But it only took me half an hour to write. Then another half hour (and five more lines of code) to debug the next day. Then another half hour to clean it up. It never fucking ends. """ from __future__ import division, print_function import sys def draw(image): "Yield one line of text for each list of numbers in list of lists `image`." for line in image: yield ''.join({1: ' 1', -1: '-1'}.get(pixel, ' .') for pixel in line) def eofill(p, size): """Even-odd fill polygon p (a list of complex numbers) by finding the number of crossings of line segments to the left of the cursor. Disregards horizontal lines to avoid division by zero. Counts crossings of topmost endpoints but not bottommost endpoints. """ t = [(q.real, q.imag) for q in p] # more convenient for later iteration all_segments = [(t[i], t[(i+1) % len(t)]) for i in range(len(t))] # Drop horizontal lines and reorganize all lines to point downward: segments = [(start, end) if start[1] < end[1] else (end, start) for start, end in all_segments if start[1] != end[1]] for y in range(int(size.imag)): yield [sum((sy <= y < ey and x > sx + (y - sy) * (ex - sx) / (ey - sy)) for (sx, sy), (ex, ey) in segments) % 2 for x in range(int(size.real))] def polygon(n, size, skip=1): "Regular polygon. Size and the returned list items are a complex numbers." c = size/2 return [c + min(c.real, c.imag) * 1j**(-1 + i*skip*4.0/n) for i in range(n)] def deltas(image): """Compute the weights you would need to convolve with a horizontal prefix sum to produce a convolution with the input (for example, a filled polygon.) """ for line in image: yield [-line[0]] + [line[i-1] - line[i] for i in range(1, len(line))] if __name__ == '__main__': sides = 5 if len(sys.argv) < 2 else int(sys.argv[1]) skip = 1 if len(sys.argv) < 3 else int(sys.argv[2]) filled = list(eofill(list(polygon(sides, 32 + 32j, skip)), 33 + 32j)) for line in draw(filled): print(line) print() for line in draw(deltas(filled)): print(line)