#!/usr/bin/env python from __future__ import division, print_function, unicode_literals import re import sys import string try: from tkinter import * except ImportError: # Fall back to Python 2 name for module from Tkinter import * import subprocess tkroot = Tk() class Container: pass cont = Container() gutter = 20 pixel = 32 XSIZE, YSIZE = 5, 9 LEFT, TOP = 100, 700 # for transforming coordinates returned from bedstead cont.canvas = Canvas(tkroot, width=2 * (XSIZE*pixel) + 3*gutter, height=YSIZE*pixel + 2*gutter, bg='white') cont.bitmap = [0] * YSIZE cont.oldbitmap = cont.bitmap[:] cont.pixels = [[None]*XSIZE for y in range(YSIZE)] cont.polygons = [] for x in range(XSIZE+1): cont.canvas.create_line(gutter + x*pixel, gutter, gutter + x*pixel, gutter + YSIZE*pixel) for y in range(YSIZE+1): cont.canvas.create_line(gutter, gutter + y*pixel, gutter + XSIZE*pixel, gutter + y*pixel) dragging = None def getpixel(x, y): assert x >= 0 and x < XSIZE and y >= 0 and y < YSIZE bit = 1 << (XSIZE-1 - x) return cont.bitmap[y] & bit def setpixel(x, y, state): assert x >= 0 and x < XSIZE and y >= 0 and y < YSIZE bit = 1 << (XSIZE-1 - x) if state and not (cont.bitmap[y] & bit): cont.bitmap[y] |= bit cont.pixels[y][x] = cont.canvas.create_rectangle( gutter + x*pixel, gutter + y*pixel, gutter + (x+1)*pixel, gutter + (y+1)*pixel, fill='black') elif not state and (cont.bitmap[y] & bit): cont.bitmap[y] &= ~bit cont.canvas.delete(cont.pixels[y][x]) cont.pixels[y][x] = None def regenerate(): if cont.oldbitmap == cont.bitmap: return cont.oldbitmap = cont.bitmap[:] for pg in cont.polygons: cont.canvas.delete(pg) cont.polygons = [] data = subprocess.check_output(["./bedstead"] + list(map(str, cont.bitmap))) paths = [] path = None for line in data.splitlines(): words = line.split() if len(words) >= 3 and words[2] in [b"m",b"l"]: x = int((float(words[0])-LEFT)*pixel*0.01 + 2*gutter + XSIZE*pixel) y = int((TOP - float(words[1]))*pixel*0.01 + gutter) if words[2] == b"m": path = [] paths.append(path) path.append([x,y]) # The output from 'bedstead' will be a set of disjoint paths, # in the Postscript style (going one way around the outside of # filled areas, and the other way around internal holes in # those areas). Python/Tk doesn't know how to fill an # arbitrary path in that representation, so instead we must # convert into a set of individual Tk polygons (convex shapes # with a single closed outline) and display them in the right # order with the right colour. # # A neat way to arrange this is to compute the area enclosed # by each polygon, essentially by integration: for each line # segment (x0,y0)-(x1,y1), sum the y difference (y1-y0) times # the average x value, which gives the area between that line # segment and the corresponding segment of the x-axis. After # we go all the way round an outline in this way, we'll have # precisely the area enclosed by the outline, no matter how # many times it doubles back on itself (because every piece of # x-axis has been cancelled out by an outline going back the # other way). Furthermore, the sign of the integral we've # computed tells us whether the outline goes one way or the # other around the area. # # So then we sort our paths into descending order of the # absolute value of its computed area (guaranteeing that any # path contained inside another appears after it, since it # must enclose a strictly smaller area) and fill each one with # a colour based on the area's sign. # # This strategy depends critically on 'bedstead' having given # us sensible paths in the first place: it wouldn't handle an # _arbitrary_ PostScript path, with loops allowed to overlap # and intersect rather than being neatly nested. pathswithmetadata = [] for path in paths: area = 0 for i in range(len(path)): x0, y0 = path[i-1] x1, y1 = path[i] area += (y1-y0) * (x0+x1)/2 pathswithmetadata.append([abs(area), ('black' if area>0 else 'white'), path]) pathswithmetadata.sort(reverse=True) for _, colour, path in pathswithmetadata: if len(path) > 1 and path[0] == path[-1]: del path[-1] args = sum(path, []) # x,y,x,y,...,x.y pg = cont.canvas.create_polygon(*args, fill=colour) cont.polygons.append(pg) def click(event): for dragstartx in gutter, 2*gutter + XSIZE*pixel: x = (event.x - dragstartx) // pixel y = (event.y - gutter) // pixel if x >= 0 and x < XSIZE and y >= 0 and y < YSIZE: cont.dragstartx = dragstartx cont.dragstate = not getpixel(x,y) setpixel(x, y, cont.dragstate) regenerate() break def paste(event): s = tkroot.selection_get() pat = re.compile("[0-7]+") bitmap = [] for i in range(YSIZE): m = pat.search(s) if m is None: print("gronk") return bitmap.append(int(m.group(0), 8) & ((1 << XSIZE) - 1)) s = s[m.end(0):] for y in range(YSIZE): for x in range(XSIZE): setpixel(x, y, 1 & (bitmap[y] >> (XSIZE-1 - x))) regenerate() def drag(event): x = (event.x - cont.dragstartx) // pixel y = (event.y - gutter) // pixel if x >= 0 and x < XSIZE and y >= 0 and y < YSIZE: setpixel(x, y, cont.dragstate) regenerate() return def key(event): if event.char in (' '): bm = ",".join(map(lambda n: "%03o" % n, cont.bitmap)) print(" {{%s}, 0x }," % bm) elif event.char in ('c','C'): for y in range(YSIZE): for x in range(XSIZE): setpixel(x, y, 0) regenerate() elif event.char in ('q','Q','\x11'): sys.exit(0) cont.canvas.bind("", click) cont.canvas.bind("", drag) cont.canvas.bind("", paste) tkroot.bind("", key) cont.canvas.pack() mainloop()