from tkinter import *
-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")
+class EditorGui:
+ def __init__(self):
+ self.tkroot = Tk()
+
+ self.canvas = Canvas(self.tkroot,
+ width=2 * (XSIZE*pixel) + 3*gutter,
+ height=YSIZE*pixel + 2*gutter,
+ bg='white')
+ self.bitmap = [0] * YSIZE
+ self.oldbitmap = self.bitmap[:]
+ self.pixels = [[None]*XSIZE for y in range(YSIZE)]
+ self.polygons = []
+
+ for x in range(XSIZE+1):
+ self.canvas.create_line(gutter + x*pixel, gutter,
+ gutter + x*pixel, gutter + YSIZE*pixel)
+ for y in range(YSIZE+1):
+ self.canvas.create_line(gutter, gutter + y*pixel,
+ gutter + XSIZE*pixel, gutter + y*pixel)
+
+ self.canvas.bind("<Button-1>", self.click)
+ self.canvas.bind("<B1-Motion>", self.drag)
+ self.canvas.bind("<Button-2>", self.paste)
+ self.tkroot.bind("<Key>", self.key)
+ self.canvas.pack()
+
+ def getpixel(self, x, y):
+ assert x >= 0 and x < XSIZE and y >= 0 and y < YSIZE
+ bit = 1 << (XSIZE-1 - x)
+ return self.bitmap[y] & bit
+
+ def setpixel(self, x, y, state):
+ assert x >= 0 and x < XSIZE and y >= 0 and y < YSIZE
+ bit = 1 << (XSIZE-1 - x)
+ if state and not (self.bitmap[y] & bit):
+ self.bitmap[y] |= bit
+ self.pixels[y][x] = self.canvas.create_rectangle(
+ gutter + x*pixel, gutter + y*pixel,
+ gutter + (x+1)*pixel, gutter + (y+1)*pixel,
+ fill='black')
+ elif not state and (self.bitmap[y] & bit):
+ self.bitmap[y] &= ~bit
+ self.canvas.delete(self.pixels[y][x])
+ self.pixels[y][x] = None
+
+ def regenerate(self):
+ if self.oldbitmap == self.bitmap:
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'):
+
+ self.oldbitmap = self.bitmap[:]
+
+ for pg in self.polygons:
+ self.canvas.delete(pg)
+ self.polygons = []
+
+ data = subprocess.check_output(
+ ["./bedstead"] + list(map(str, self.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 = self.canvas.create_polygon(*args, fill=colour)
+ self.polygons.append(pg)
+
+ def click(self, 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:
+ self.dragstartx = dragstartx
+ self.dragstate = not self.getpixel(x,y)
+ self.setpixel(x, y, self.dragstate)
+ self.regenerate()
+ break
+
+ def paste(self, event):
+ s = self.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, 0)
- regenerate()
- elif event.char in ('q','Q','\x11'):
- sys.exit(0)
-
-cont.canvas.bind("<Button-1>", click)
-cont.canvas.bind("<B1-Motion>", drag)
-cont.canvas.bind("<Button-2>", paste)
-tkroot.bind("<Key>", key)
-cont.canvas.pack()
-
-mainloop()
+ self.setpixel(x, y, 1 & (bitmap[y] >> (XSIZE-1 - x)))
+ self.regenerate()
+
+ def drag(self, event):
+ x = (event.x - self.dragstartx) // pixel
+ y = (event.y - gutter) // pixel
+ if x >= 0 and x < XSIZE and y >= 0 and y < YSIZE:
+ self.setpixel(x, y, self.dragstate)
+ self.regenerate()
+ return
+
+ def key(self, event):
+ if event.char in (' '):
+ bm = ",".join(map(lambda n: "%03o" % n, self.bitmap))
+ print(" {{%s}, 0x }," % bm)
+ elif event.char in ('c','C'):
+ for y in range(YSIZE):
+ for x in range(XSIZE):
+ self.setpixel(x, y, 0)
+ self.regenerate()
+ elif event.char in ('q','Q','\x11'):
+ sys.exit(0)
+
+ def run(self):
+ mainloop()
+
+def main():
+ editor = EditorGui()
+ editor.run()
+
+if __name__ == '__main__':
+ main()