#!/usr/bin/python 'generate TANSTAAFL flag' from __future__ import division import sys, os, random, math, Image, ImageDraw, ImageFont MAGIC = [7, 11, 13] # see docstring for 'field' BLOODRED = '#660000' # '#660000' at www.december.com/html/spec/color1.html FLAG = Image.new('RGB', (900, 600), 'black') CANVAS = ImageDraw.Draw(FLAG) DEBUGGING = True def flag(image = FLAG, draw = CANVAS): 'compose the TANSTAAFL flag and show its image' field(image, draw) barsinister(image, draw) cannon(image) # overlay cannon tanstaafl(image, draw) # add lettering image.show() image.save(os.path.join(os.getenv('HOME'), 'tanstaafl.png'), 'PNG') def field(image = FLAG, draw = CANVAS): '''the field is the background, i.e., the whole area of the flag or shield the "magic" number is used both for seeding the pseudorandom number generator, so the script produces the same output each time, and for the total number of stars. there is no need to have these numbers identical, nor a product of primes, but Wye Knott? ''' size = image.size magic = reduce(int.__mul__, MAGIC) random.seed(magic) for index in range(magic): star = [random.randint(0, size[0]), random.randint(0, size[1])] intensity = 1 + (index % MAGIC[1] == 0) + ( index % (MAGIC[0] * MAGIC[1]) == 0) star.extend([star[0] + intensity, star[1] + intensity]) draw.ellipse(star, fill = 'white') def bendsinister(image = FLAG, draw = CANVAS): '''a bend sinister covers 1/3 of the field, sinister chief to dexter base (some sources on the web say 1/5 of the field, but we'll use 1/3) the "field" in this case being the area of the flag, so we need to remove triangles covering 2/3 of the area, leaving two joined trapezoids. we need to return only the width of the diagonal, so that the barsinister routine can finish the calculations and draw the baton. ''' FRACTION = 1 / 3 x, y = image.size b = math.sqrt((x ** 2) + (y ** 2)) A = x * y H = triangle_height(A / 2, b) # height of triangular half of flag h = math.sqrt(2 / 3) * H # height of smaller triangle width = (H - h) * 2 # width of two remaining trapezoids debug(('H', H, 'h', h, 'width', width)) if command == 'bendsinister': show_bendsinister(x, y, width, image, draw) return width def show_bendsinister(x, y, width, image = FLAG, draw = CANVAS): 'for debugging formula' dexter_base, sinister_chief = (0, y), (x, 0) draw.line((dexter_base, sinister_chief), 'blue', int(width)) image.show() debug(image.getcolors(2)) # should be twice as many black pixels as blue def triangle_height(a, b): 'a=bh/2' h = a / (b / 2) debug('triangle height: %.2f' % h) return h def barsinister(image, draw): '''a bar sinister is properly a baton sinister... a diminutive of the bend sinister, and couped at the ends a baton is 1/4 the breadth of the bend. and a baton does not extend to the corners. ''' bendwidth = bendsinister(image, draw) width = bendwidth / 4 x, y = image.size hypotenuse = math.sqrt((x ** 2) + (y ** 2)) coup = MAGIC[1] / MAGIC[2] # more silliness to determine length dexter_base = (x * coup, (y - (y * coup))) sinister_chief = ((x - (x * coup)), y * coup) draw.line((dexter_base, sinister_chief), BLOODRED, int(width)) def cannon(image): FRACTION = MAGIC[0] / MAGIC[2] debug(('FRACTION', FRACTION)) cannon = Image.open('cannon_transparent_posterized.png') debug(('cannon', cannon.size, cannon.mode)) cannon = cannon.resize(tuple(map(lambda s: int(s * FRACTION), image.size))) debug(('cannon', cannon.size, cannon.mode)) offset = (int((image.size[0] - cannon.size[0]) / 2), int((image.size[1] - cannon.size[1]) / 2)) image.paste(cannon, offset, cannon) def tanstaafl(image, draw): FONTPATH = os.path.join(os.sep, 'usr', 'share', 'fonts', 'truetype', 'freefont', 'FreeSansBold.ttf') FONTSIZE = MAGIC[0] * MAGIC[1] font = ImageFont.truetype(FONTPATH, FONTSIZE) text = 'TANSTAAFL!' imagesize, textsize = image.size, draw.textsize(text, font) offset = (imagesize[0] - textsize[0]) / 2 draw.text((offset, imagesize[1] - (textsize[1])), text, font = font, fill = brighten(list(reversed(MAGIC)), MAGIC[1] * MAGIC[2])) def brighten(color, max_brightness): 'scale color tuple to highest value, keeping same relative strengths' brightest = max(color) brighter = tuple(map(lambda c: int((c / brightest) * max_brightness), color)) debug(('color', color, 'brighter', brighter)) return brighter def debug(message): if DEBUGGING: print >>sys.stderr, message if __name__ == '__main__': command = os.path.splitext(os.path.basename(sys.argv[0]))[0] print eval(command)(*sys.argv[1:]) or ''