From 8045d3c2ba7927f2d9074ed7f8cf0fc4a03a4920 Mon Sep 17 00:00:00 2001 From: rep Date: Thu, 10 Dec 2015 01:46:48 +0100 Subject: [PATCH] pixelisation v2 --- ghost-diagrams-0.8.py | 1112 +++ haacheuur.daube.py | 268 + haacheuur.py | 350 +- hacheur.py | 271 + test.pickle | 15010 ++++++++++++---------------------------- 5 files changed, 6348 insertions(+), 10663 deletions(-) create mode 100644 ghost-diagrams-0.8.py create mode 100644 haacheuur.daube.py create mode 100644 hacheur.py diff --git a/ghost-diagrams-0.8.py b/ghost-diagrams-0.8.py new file mode 100644 index 0000000..4fe3b9e --- /dev/null +++ b/ghost-diagrams-0.8.py @@ -0,0 +1,1112 @@ +#!/usr/bin/env python + +# Copyright (C) 2004 Paul Harrison +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +""" Ghost Diagrams + + This program takes sets of tiles that connect together in certain + ways, and looks for the patterns these tiles imply. The patterns + are often surprising. + + + This software is currently somewhat alpha. + + Tile set specification: + + A tile set specification is a list of 6- or 4-character strings, + eg 'B Aa ', 'b Aa ' + + Each character represents a tile edge. Letters (abcd, ABCD) + match with their opposite case. Numbers match with themselves. + + A number of extra paramters can also be supplied: + + border : True/False : draw tile borders or not + thickness : the thickness of the border + width : minimum width of diagram + height : minimum height of diagram + colors : a list of colors + [ background color, edge color, tile 1 color, tile 2 color... ] + grid : True/False : draw a grid + labels : True/False : draw labels for each tile under diagram + + eg 'B Aa ', 'b Aa ', width=1000, height=1000, thickness=0.5, colors=['#000','#000','#fff','#f00'] + + Change log: + + 0.1 -- initial release + 0.2 -- don't segfault on empty tiles + 0.3 -- random keeps trying till it finds something that will grow + optimization (options_cache) + 0.4 -- assembly algorithm tweaks + random tile set tweaks + 0.5 -- Patch by Jeff Epler + - allow window resizing + - new connection types (33,44,cC,dD) + - DNA tile set + widget to set size of tiles + no repeated tiles in random + improvements to assembler + 0.6 -- Use Bezier curves + Parameters to set width, height, thickness, color + Save images + 0.7 -- Allow square tiles + Smarter assembler + Animate assembly + 0.8 -- Knotwork + Don't fill all of memory + Use psyco if available + + + TODO: don't backtrack areas outside current locus + (difficulty: accidentally creating disconnected islands) + + TODO: (blue sky) 3D, third dimension == time + TODO: allowances: 3 of this, 2 of that, etc. +""" + +try: + import psyco + psyco.profile() +except: + pass + +__version__ = '0.8' + +import sys, os, random, gtk, pango, gobject, string, math, sets + +class Point: + def __init__(self, x,y): + self.x = x + self.y = y + + def __add__(self, other): return Point(self.x+other.x,self.y+other.y) + def __sub__(self, other): return Point(self.x-other.x,self.y-other.y) + def __mul__(self, factor): return Point(self.x*factor, self.y*factor) + def length(self): return (self.y*self.y + self.x*self.x) ** 0.5 + def int_xy(self): return int(self.x+0.5), int(self.y+0.5) + def left90(self): return Point(-self.y, self.x) + + +# ======================================================================== +# Constants + +# Hexagonal connection pattern: +# +# o o +# o * o +# o o +# +# (all points remain on a square grid, but a regular hexagon pattern +# can be formed by a simple linear transformation) + +connections_6 = [ (-1, 0, 3), (-1, 1, 4), (0, 1, 5), (1, 0, 0), (1, -1, 1), (0, -1, 2) ] +# [ (y, x, index of reverse connection) ] +x_mapper_6 = Point(1.0, 0.0) +y_mapper_6 = Point(0.5, 0.75**0.5) + +connections_4 = [ (-1,0,2), (0,1,3), (1,0,0), (0,-1,1) ] +x_mapper_4 = Point(1.0, 0.0) +y_mapper_4 = Point(0.0, 1.0) + + + +# What edge type connects with what? +# (a tile is represented as a string of 6 characters representing the 6 edges) +compatabilities = { + ' ':' ', + 'A':'a', 'a':'A', 'B':'b', 'b':'B', 'c':'C', 'C':'c', 'd':'D', 'D':'d', + '1':'1', '2':'2', '3':'3', '4':'4', '-':'-' +} + + +# Amount of oversampling in drawing +factor = 3 + + +# Some cool tile sets people have found +catalogue = [ + "' 33Aa', ' 33 Aa'", + "' 33Aa/#000', ' 33 Aa/#000', colors=['#fff','#fff'], border=0, grid=0", + "'ab A ', 'B C ', 'B c ', 'B D ', 'B d '", + "'d D 4 ', 'd D ', '44 '", + "'AaAa '", + "'aA ', 'AaAa '", + "' bB ', 'bAaB ', 'aAaA '", + "'B Aa ', 'b Aa '", + "'44 ', '11 4 '", + "'3 3 3 ', '33 '", + "'1 1 1 ', '2 12 '", + "' a ', 'a AA '", + "' AAaa ', 'a A '", + "' a A ', 'Aaa A'", + "'a a ', ' aAA A'", + "' a A a', ' A '", + "' AA A', 'a a a '", + "'a aa ', ' AA'", + "'A A a ', 'a a '", + "'A A a ', 'a a '", + "' a 4', 'a4 44A', '4A '", + "'a2 A ', ' a2 A2'", + "'a 2a2 ', ' A A', ' 2'", + "'141 ', '4 4 ', '1 1 '", + "' 22 22', '22 '", + "' Aaa ', 'A1A ', 'a 1AAa'", + "'aA a2 ', '2A ', ' 2 A'", + "' bB1 ', ' b B '", + "'BbB 1 ', ' b'", + "'b b b', ' BbB '", + "'aA1 ', ' AA ', 'a 2 '", + "' a 4 ', ' 4 4 ', ' A441'", + "'212111', ' 1 2 '", + "'22222a', '22 A22'", + "'2 222 ', '2 B2', ' b 2'", + "' 21221', ' 221', ' 2 2'", + "' a a a', ' A A'", + "' Dd cA', ' d D', ' a C'", + "' CCCc', ' 3Ca A', ' 3 c', ' c'", + "' C dDc', ' CC C', ' ccC'", + "' Aa Cc', ' c', ' C'", + "' CcDdC', ' cC c', ' C'", + "' CcCc', ' CcC c', ' c C'", + "'A 1 1 ','a1 B','b 1 '", + "'aa aa /#fff', 'AA /#fff', 'A A /#fff', grid=0, border=0, thickness=0.3", + #"'bb bb ', 'BB ', 'B B '", + "' 44B4D', ' dbB4b', ' 44D d', ' 44'", + "' d3 3', ' D D'", + "' cc c', ' C C c'", + "'AaAaaa', ' 1 Aa', ' A'", + "'d D 3 ', 'dD ', '3 '", + "'a 1 A ', 'a A '", + "'cCCcCC', 'cccC ', 'c C C'", + "'A44444', 'a4 4', '4 4 '", + "'acaACA', 'acbBCB', 'bcaBCB', 'bcbACA'", + "'A ab ', 'B ab ', 'A a ', 'B b ', 'ABd D'", #Tree + "'d AD ', ' a A ', 'a A ', 'aa A '", #Counter (?) + "'bBbBBB', 'bb ', 'b B '", + "'a AA A', 'a a '", + "'cC a A', 'a A '", + "'bbB B', 'b BBB ', 'bb '", + "'cCc C ', 'cC c C'", + "'d4 Dd ', 'd D ', 'DD '", + "' 111'", + "'abA ', 'B C ', 'B c ', 'B D ', 'B d '", + "'4A4a', ' a4', ' A B', ' Ab'", + "'acAC', 'adBD', 'bcBD', 'bdAC'", + "'1111', ' 1'", + "' bbb', ' BB'", + "'1B1B', 'a A ', ' bA ', 'ab B'", +] + + +default_colors=['#fff','#000', '#8ff', '#f44', '#aaf','#449', '#ff0088', '#ff4088', '#ff4040', '#ff00ff', '#40c0ff'] + +default_thickness = 3.0/16 + + +# ======================================================================== +# Utility functions + +def bezier(a,b,c,d): + result = [ ] + n = 12 + for i in xrange(1,n): + u = float(i) / n + result.append( + a * ((1-u)*(1-u)*(1-u)) + + b * (3*u*(1-u)*(1-u)) + + c * (3*u*u*(1-u)) + + d * (u*u*u) + ) + return result + + +def normalize(form): + best = form + for i in xrange(len(form)-1): + form = form[1:] + form[0] + if form > best: best = form + return best + + +# ========================================================================= + +class Config: + def __init__(self, *forms, **kwargs): + self.colors = kwargs.get("colors", [ ]) + self.colors += default_colors[len(self.colors):] + + self.border = kwargs.get("border", True) + self.thickness = kwargs.get("thickness", default_thickness) + self.width = kwargs.get("width", -1) + self.height = kwargs.get("height", -1) + + self.grid = kwargs.get("grid", True) + self.labels = kwargs.get("labels", False) + + forms = list(forms) + + if len(forms) < 1: raise "error" + + for item in forms: + if type(item) != type(""): + raise "error" + + for i in xrange(len(forms)): + if "/" in forms[i]: + forms[i], self.colors[i%(len(self.colors)-2)+2] = forms[i].split("/",1) + + if len(forms[0]) == 4: + self.connections = connections_4 + self.x_mapper = x_mapper_4 + self.y_mapper = y_mapper_4 + else: + self.connections = connections_6 + self.x_mapper = x_mapper_6 + self.y_mapper = y_mapper_6 + + for item in forms: + if len(item) != len(self.connections): + raise "error" + for edge in item: + if edge not in compatabilities: + raise "error" + + self.forms = forms + +# ======================================================================== + + +class Assembler: + def __init__(self, connections, compatabilities, forms, point_set): + self.connections = connections # [(y,x,index of reverse connection)] + self.compatabilities = compatabilities # { edge-char -> edge-char } + self.point_set = point_set # (y,x) -> True + + self.basic_forms = forms # ['edge types'] + self.forms = [ ] # ['edge types'] + self.form_id = [ ] # [original form number] + self.rotation = [ ] # [rotation from original] + + for id, form in enumerate(forms): + current = form + for i in xrange(len(self.connections)): + if current not in self.forms: + self.forms.append(current) + self.form_id.append(id) + self.rotation.append(i) + current = current[1:] + current[0] + + self.tiles = { } # (y,x) -> form number + + self.dirty = { } # (y,x) -> True -- Possible sites for adding tiles + + self.options_cache = { } # pattern -> [form_ids] + + self.dead_loci = sets.Set([ ]) # [ {(y,x)->form number} ] + + self.history = [ ] + + self.total_y = 0 + self.total_x = 0 + + self.changes = { } + + def put(self, y,x, value): + if (y,x) in self.changes: + if value == self.changes[(y,x)]: + del self.changes[(y,x)] + else: + self.changes[(y,x)] = self.tiles.get((y,x),None) + + + if (y,x) in self.tiles: + self.total_y -= y + self.total_x -= x + + if value == None: + if (y,x) not in self.tiles: return + del self.tiles[(y,x)] + self.dirty[(y,x)] = True + else: + self.tiles[(y,x)] = value + self.total_y += y + self.total_x += x + + for oy, ox, opposite in self.connections: + y1 = y + oy + x1 = x + ox + if (y1,x1) not in self.tiles and (y1, x1) in self.point_set: + self.dirty[(y1,x1)] = True + + def get_pattern(self, y,x): + result = '' + for oy, ox, opposite in self.connections: + y1 = y + oy + x1 = x + ox + if self.tiles.has_key((y1,x1)): + result += self.compatabilities[self.forms[self.tiles[(y1,x1)]][opposite]] + #elif (y1,x1) not in self.point_set: + # result += ' ' + else: + result += '.' + + return result + + def fit_ok(self, pattern,form_number): + form = self.forms[form_number] + for i in xrange(len(self.connections)): + if pattern[i] != '.' and pattern[i] != form[i]: + return False + + return True + + def options(self, y,x): + pattern = self.get_pattern(y,x) + if pattern in self.options_cache: + result = self.options_cache[pattern] + + result = [ ] + for i in xrange(len(self.forms)): + if self.fit_ok(pattern,i): + result.append(i) + result = tuple(result) + + self.options_cache[pattern] = result + + return result + + def locus(self, y,x, rotation=0): + visited = { } + neighbours = { } + todo = [ ((y,x), (0,0)) ] + result = [ ] + + min_y = 1<<30 + min_x = 1<<30 + + while todo: + current, offset = todo.pop(0) + if current in visited: continue + visited[current] = True + + any = False + new_todo = [ ] + for i, (oy, ox, opposite) in enumerate(self.connections): + neighbour = (current[0]+oy, current[1]+ox) + if neighbour in self.point_set: + if neighbour in self.tiles: + any = True + neighbours[neighbour] = True + min_y = min(min_y, offset[0]) + min_x = min(min_x, offset[1]) + result.append( (offset, opposite, + self.forms[self.tiles[neighbour]][opposite]) ) + else: + temp = self.connections[(i+rotation) % len(self.connections)] + new_offset = (offset[0]+temp[0], offset[1]+temp[1]) + new_todo.append((neighbour, new_offset)) + + if not any and len(self.connections) == 4: + for oy, ox in ((-1,-1), (-1,1), (1,-1), (1,1)): + neighbour = (current[0]+oy, current[1]+ox) + if neighbour in self.tiles: + any = True + break + + if any: + todo.extend(new_todo) + + result = [ (yy-min_y,xx-min_x,a,b) for ((yy,xx),a,b) in result ] + + return sets.ImmutableSet(result), visited, neighbours + + + def filter_options(self, y,x,options): + result = [ ] + for i in options: + self.tiles[(y,x)] = i + visiteds = [ ] + + for oy, ox, oppoiste in self.connections: + y1 = y+oy + x1 = x+ox + + ok = True + if (y1,x1) not in self.tiles and (y1,x1) in self.point_set: + for visited in visiteds: + if (y1,x1) in visited: + ok = False + break + if ok: + locus, visited, _ = self.locus(y1,x1) + visiteds.append(visited) + if locus is not None and locus in self.dead_loci: + break + else: + result.append(i) + + del self.tiles[(y,x)] + + return result + + def any_links_to(self, y,x): + for oy, ox, opposite in self.connections: + y1 = y + oy + x1 = x + ox + if (y1, x1) in self.tiles: + if self.forms[self.tiles[(y1,x1)]][opposite] != ' ': + return True + return False + + def prune_dead_loci(self): + for item in list(self.dead_loci): + if random.randrange(2): + self.dead_loci.remove(item) + + def iterate(self): + if not self.tiles: + self.put(0,0,0) + self.history.append((0,0)) + return True + + mid_y = 0.0 + mid_x = 0.0 + for y, x in self.dirty.keys(): + if (y,x) in self.tiles or not self.any_links_to(y,x): + del self.dirty[(y,x)] + continue + mid_y += y + mid_x += x + + if not self.dirty: + return False + + mid_y /= len(self.dirty) + mid_x /= len(self.dirty) + + point_list = [ ] + for y, x in self.dirty.keys(): + yy = y - mid_y + xx = x - mid_x + sorter = ((yy*2)**2+(xx*2+yy)**2) + point_list.append( (sorter,y,x) ) + + point_list.sort() + + best = None + + for sorter, y, x in point_list: + options = self.options(y,x) + + if len(options) < 2: + score = 0 + else: + score = 1 + + if best == None or score < best_score: + best = options + best_score = score + best_x = x + best_y = y + if score == 0: break + + if best == None: return False + + best = self.filter_options(best_y,best_x,best) + + if len(best) > 0: + self.put(best_y,best_x,random.choice(best)) + self.history.append((best_y,best_x)) + return True + + #otherwise, backtrack: + + for i in xrange(len(self.connections)): + locus, _, relevant = self.locus(best_y,best_x,i) + if locus is None: break + self.dead_loci.add(locus) + if len(locus) > 8: break + + if len(self.dead_loci) > 10000: + self.prune_dead_loci() + + # Shape of distribution + autism = 1.0 # 1.0 == normal, >1.0 == autistic (just a theory :-) ) + + # Overall level + adhd = 2.0 # Lower == more adhd + + n = 1 + while n < len(self.tiles)-1 and random.random() < ( n/(n+autism) )**adhd: + n += 1 + + while self.history and (n > 0 or + self.locus(best_y,best_x)[0] in self.dead_loci): + item = self.history.pop(-1) + self.put(item[0],item[1],None) + n -= 1 + + if not self.tiles: + return False + + return True + + + + +# ======================================================================== + + +class Interface: + def __init__(self): + self.iteration = 0 + self.idle_enabled = False + + self.drawing = gtk.DrawingArea() + self.drawing.unset_flags(gtk.DOUBLE_BUFFERED) + #self.drawing.add_events(gtk.gdk.BUTTON_PRESS_MASK) + self.drawing.connect('expose-event', self.on_expose) + self.drawing.connect('size-allocate', self.on_size) + #self.drawing.connect('button-press-event', self.on_click) + + scroller = gtk.ScrolledWindow() + scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + scroller.add_with_viewport(self.drawing) + + self.combo = gtk.Combo() + for item in catalogue: + item = gtk.ListItem(item) + item.get_children()[0].modify_font(pango.FontDescription("mono")) + item.show() + self.combo.list.append_items([item]) + self.combo.list.connect('select-child', lambda widget, child: self.reset()) + self.combo.entry.modify_font(pango.FontDescription("mono")) + + knot_box = gtk.CheckButton("knotwork") + knot_box.set_active(False) + self.knot = False + knot_box.connect("toggled", self.on_knot_changed) + + scale_label = gtk.Label(' Size: ') + scale = gtk.SpinButton() + scale.set_digits(1) + scale.set_increments(1.0,1.0) + scale.set_range(3.0, 50.0) + scale.set_value(10.0) + scale.connect('value-changed', self.on_set_scale) + + random_button = gtk.Button(' Random ') + random_button.connect('clicked', lambda widget: self.random()) + + reset = gtk.Button(' Restart ') + reset.connect('clicked', lambda widget: self.reset()) + + save = gtk.Button(' Save image... ') + save.connect('clicked', self.on_save) + + hbox = gtk.HBox(False,5) + hbox.set_border_width(3) + hbox.pack_start(knot_box, False, False, 0) + hbox.pack_end(save, False,False,0) + hbox.pack_end(reset, False,False,0) + hbox.pack_end(random_button, False,False,0) + hbox.pack_end(scale, False,False,0) + hbox.pack_end(scale_label, False,False,0) + + vbox = gtk.VBox(False,5) + vbox.pack_start(self.combo, False,False,0) + vbox.pack_start(hbox, False,False,0) + vbox.pack_start(scroller, True,True,0) + + self.window = gtk.Window() + self.window.set_default_size(600,650) + #self.window.set_default_size(200,200) + self.window.set_title('Ghost Diagrams') + self.window.add(vbox) + self.window.connect('destroy', lambda widget: gtk.main_quit()) + + self.drawing.realize() + + self.gc = gtk.gdk.GC(self.drawing.window) + + self.pixbuf_ready = False + self.render_iterator = None + + self.randomizing = False + + self.set_scale(scale.get_value()) + + def on_size(self, widget, event): + self.pixbuf_ready = False + + self.width = event.width + self.height = event.height + self.pixmap = gtk.gdk.Pixmap(self.drawing.window, self.width*factor,self.height*factor) + + self.pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, + self.width*factor,self.height*factor) + self.scaled_pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, + self.width,self.height) + self.reset() + + def on_set_scale(self, widget): + self.set_scale(widget.get_value()) + self.reset() + + def set_scale(self, value): + self.scale = value + + def on_knot_changed(self, widget): + self.knot = widget.get_active() + self.reset() + + def reset(self): + try: + self.config = eval('Config('+self.combo.entry.get_text()+')') + except: + import traceback + traceback.print_exc() + self.config = Config(" ", grid=False, colors=["#f66"]) + + colormap = self.drawing.get_colormap() + self.colors = [ colormap.alloc_color(item) for item in self.config.colors ] + + point_set = { } + yr = int( self.height/self.scale/4 ) + xr = int( self.width/self.scale/4 ) + if self.config.labels: + bound = self.scale * 3 + for y in xrange(-yr,yr): + for x in xrange(-yr,xr): + point = self.pos(x*2,y*2) + if point.x > bound and point.x < self.width-bound and \ + point.y > bound and point.y < self.height-bound-90: + point_set[(y,x)] = True + else: + bound = self.scale * 3 + for y in xrange(-yr,yr): + for x in xrange(-yr,xr): + point = self.pos(x*2,y*2) + if point.x > -bound and point.x < self.width+bound and \ + point.y > -bound and point.y < self.height+bound: + point_set[(y,x)] = True + + + self.pixbuf_ready = False + self.render_iterator = None + self.randomizing = False + self.iteration = 0 + self.drawing.queue_draw() + self.shapes = { } + self.polys = { } + self.assembler = Assembler(self.config.connections, compatabilities, + self.config.forms, point_set) + if not self.idle_enabled: + gobject.idle_add(self.idle) + self.idle_enabled = True + + self.drawing.set_size_request(self.config.width, self.config.height) + self.drawing.queue_draw() + + + def run(self): + self.window.show_all() + gtk.main() + + + def idle(self): + if self.render_iterator: + try: + self.render_iterator.next() + except StopIteration: + self.render_iterator = None + return True + + if not self.idle_enabled: + return False + + self.idle_enabled = self.assembler.iterate() + + self.iteration += 1 + + if self.randomizing and \ + (self.iteration == 100 or not self.idle_enabled): + self.randomizing = False + + forms_present = { } + for item in self.assembler.tiles.values(): + forms_present[self.assembler.form_id[item]] = 1 + + if len(self.assembler.tiles) < 10 \ + or len(forms_present) < len(self.assembler.basic_forms): + self.idle_enabled = False + self.random(True) + return False + + if not self.idle_enabled or len(self.assembler.changes) >= 8: + changes = self.assembler.changes + self.assembler.changes = { } + for y,x in changes: + old = changes[(y,x)] + if old is not None: + self.draw_poly(y,x,old,1, self.drawing.window, True) + for y,x in changes: + new = self.assembler.tiles.get((y,x),None) + if new is not None: + self.draw_poly(y,x,new,1, self.drawing.window, False) + + if not self.idle_enabled and not self.assembler.dirty: + self.render_iterator = self.make_render_iter() + + return True + + + def pos(self, x,y, center=True): + result = (self.config.x_mapper*x + self.config.y_mapper*y) * (self.scale*2) + if center: + return result + Point(self.width/2.0,self.height/2.0) + else: + return result + + def make_shape(self, form_number): + if form_number in self.shapes: + return self.shapes[form_number] + + result = [ ] + connections = { } + + for i in xrange(len(self.assembler.connections)): + yy, xx = self.assembler.connections[i][:2] + symbol = self.assembler.forms[form_number][i] + if symbol in ' -': continue + + edge = self.pos(xx,yy,0) + out = edge + left = out.left90() + + if symbol in 'aA1': + r = 0.4 + #r = 0.6 + elif symbol in 'bB2': + r = 0.3 + elif symbol in 'cC3': + r = 0.225 + else: + r = 0.15 + + if symbol in 'ABCD': + poke = 0.3 #r + elif symbol in 'abcd': + poke = -0.3 #-r + else: + poke = 0.0 + + points = [ + edge + left*-r, + edge + out*poke, + edge + left*r, + ] + + result.append( (out * (1.0/out.length()), points, 0.5)) #0.625)) + connections[i] = points + # Note: set constant to ~0.35 for old-style circular look + + if len(result) == 1: + point = result[0][0]*(self.scale*-0.7) + result.append( (result[0][0].left90()*-1.0, [point], 0.8) ) + result.append( (result[0][0].left90(), [point], 0.8) ) + + poly = [ ] + for i in xrange(len(result)): + a = result[i-1][1][-1] + d = result[i][1][0] + length = (d-a).length() * ((result[i][2]+result[i-1][2])*0.5) + b = a - result[i-1][0]*length + c = d - result[i][0]*length + poly.extend(bezier(a,b,c,d)) + poly.extend(result[i][1]) + + links = [ ] + if self.knot: + form = self.assembler.forms[form_number] + items = connections.keys() + cords = [ ] + + if len(items)%2 != 0: + for item in items[:]: + if (item+len(form)/2) % len(form) not in items and \ + (item+len(form)/2+1) % len(form) not in items and \ + (item+len(form)/2-1) % len(form) not in items: + items.remove(item) + + if len(items)%2 != 0: + for i in xrange(len(form)): + if form[i] not in ' -' and \ + form.count(form[i]) == 1 and \ + (compatabilities[form[i]] == form[i] or \ + form.count(compatabilities[form[i]])%2 == 0): + items.remove(i) + + if len(items)%2 != 0: + for item in items[:]: + if (item+len(form)/2) % len(form) not in items: + items.remove(item) + + if len(items)%2 == 0: + rot = self.assembler.rotation[form_number] + mod = len(self.assembler.connections) + items.sort(lambda a,b: cmp((a+rot)%mod,(b+rot)%mod)) + step = len(items)/2 + + for ii in xrange(len(items)/2): + i = items[ii] + j = items[ii-step] + cords.append((i,j)) + + for i,j in cords: + a = connections[i] + b = connections[j] + a_in = (a[-1]-a[0]).left90() + a_in = a_in*(self.scale*1.25/a_in.length()) + b_in = (b[-1]-b[0]).left90() + b_in = b_in*(self.scale*1.25/b_in.length()) + a = [(a[0]+a[1])*0.5,a[1],(a[-2]+a[-1])*0.5] + b = [(b[0]+b[1])*0.5,b[1],(b[-2]+b[-1])*0.5] + bez1 = bezier(a[-1],a[-1]+a_in,b[0]+b_in,b[0]) + bez2 = bezier(b[-1],b[-1]+b_in,a[0]+a_in,a[0]) + linker = a + bez1 + b + bez2 + links.append((linker,a[-1:]+bez1+b[:1],b[-1:]+bez2+a[:1])) + + self.shapes[form_number] = poly, links + return poly, links + + def draw_poly(self, y,x,form_number,factor, drawable, erase=False): + id = (y,x,form_number,factor) + + if id not in self.polys: + def intify(points): return [ ((middle+point)*factor).int_xy() for point in points ] + + middle = self.pos(x*2,y*2) + + poly, links = self.make_shape(form_number) + poly = intify(poly) + links = [ (intify(link), intify(line1), intify(line2)) for link,line1,line2 in links ] + + self.polys[id] = poly, links + else: + poly, links = self.polys[id] + + if len(poly) > 0: + if erase: + color = 0 + else: + color = self.assembler.form_id[form_number] % (len(self.colors)-2) + 2 + + self.gc.set_rgb_fg_color(self.colors[color]) + drawable.draw_polygon(self.gc, True, poly) + + if self.knot: + self.gc.set_line_attributes(max(factor,int(self.scale*factor * self.config.thickness)), + gtk.gdk.LINE_SOLID, gtk.gdk.CAP_ROUND, gtk.gdk.JOIN_ROUND) + for link, line1, line2 in links: + if not erase: self.gc.set_rgb_fg_color(self.colors[1]) + drawable.draw_polygon(self.gc, True, link) + if not erase: self.gc.set_rgb_fg_color(self.colors[color]) + #drawable.draw_polygon(self.gc, False, link) + drawable.draw_lines(self.gc, line1) + drawable.draw_lines(self.gc, line2) + #drawable.draw_line(self.gc, *(connections[i][-1]+connections[j][0])) + #drawable.draw_line(self.gc, *(connections[j][-1]+connections[i][0])) + + if self.config.border: + self.gc.set_line_attributes(max(factor,int(self.scale*factor * self.config.thickness)), + gtk.gdk.LINE_SOLID, gtk.gdk.CAP_ROUND, gtk.gdk.JOIN_ROUND) + if not erase: self.gc.set_rgb_fg_color(self.colors[1]) + self.pixmap.draw_polygon(self.gc, False, poly) + drawable.draw_polygon(self.gc, False, poly) + + def make_render_iter(self): + yield None + + if not self.assembler.tiles: + self.pixbuf_ready = False + return + + self.gc.set_rgb_fg_color(self.colors[0]) + self.pixmap.draw_rectangle(self.gc, True, 0,0,self.width*factor,self.height*factor) + + if self.config.labels and False: + font = pango.FontDescription("mono bold 36") + self.gc.set_rgb_fg_color(self.colors[1]) + + for i, form in enumerate(self.assembler.basic_forms): + layout = self.drawing.create_pango_layout(" "+form.replace(" ","-")+" ") + layout.set_font_description(font) + x = (i+1)*(len(form)+3)*30 + y = (self.height-70) *factor + width, height = layout.get_pixel_size() + self.pixmap.draw_rectangle(self.gc, True, x-6,y-6, width+12,height+12) + self.pixmap.draw_layout(self.gc, x,y, layout, self.colors[1], self.colors[i+2]) + + if self.config.grid: + colormap = self.drawing.get_colormap() + self.gc.set_rgb_fg_color(colormap.alloc_color("#eee")) + self.gc.set_line_attributes(factor, + gtk.gdk.LINE_SOLID, gtk.gdk.CAP_ROUND, gtk.gdk.JOIN_ROUND) + f = 4.0 / len(self.config.connections) + for (y,x) in self.assembler.point_set.keys(): + poly = [ ] + for i in xrange(len(self.config.connections)): + a = self.config.connections[i-1] + b = self.config.connections[i] + poly.append((self.pos(x*2+(a[0]+b[0])*f,y*2+(a[1]+b[1])*f)*factor).int_xy()) + + self.pixmap.draw_polygon(self.gc, False, poly) + yield None + + for (y,x), form_number in self.assembler.tiles.items(): + self.draw_poly(y,x,form_number,factor, self.pixmap) + yield None + + self.pixbuf.get_from_drawable(self.pixmap, self.pixmap.get_colormap(), + 0,0,0,0, self.width*factor,self.height*factor) + + yield None + + self.pixbuf.scale(self.scaled_pixbuf, 0,0,self.width,self.height, + 0,0,1.0/factor,1.0/factor,gtk.gdk.INTERP_BILINEAR) + + self.pixbuf_ready = True + self.drawing.queue_draw() + + def on_expose(self, widget, event): + self.assembler.changes = dict([ (item, None) for item in self.assembler.tiles ]) + + if self.pixbuf_ready: + self.drawing.window.draw_pixbuf(self.gc, self.scaled_pixbuf, 0,0,0,0,-1,-1) + else: + self.gc.set_rgb_fg_color(self.colors[0]) + self.drawing.window.draw_rectangle(self.gc, True, 0,0,self.width,self.height) + + def random(self, same_form=False): + if same_form: + n = len(self.assembler.basic_forms) + sides = len(self.assembler.basic_forms[0]) + else: + n = random.choice([1,1,2,2,2,3,3,3,4]) + if self.knot: + sides = 6 + else: + sides = random.choice([4,6]) + + + while True: + if self.knot: + edge_counts = [ random.choice(range(2,sides+1,2)) + for i in xrange(n) ] + else: + edge_counts = [ random.choice(range(1,sides+1)) + for i in xrange(n) ] + + edge_counts.sort() + edge_counts.reverse() + if edge_counts[0] != 1: break + + while True: + try: + result = [ ] + previous = '1234' + 'aAbBcCdD' #* 3 + for edge_count in edge_counts: + item = [' ']*(sides-edge_count) + for j in xrange(edge_count): + selection = random.choice(previous) + previous += compatabilities[selection]*6 #12 + item.append(selection) + + random.shuffle(item) + item = normalize(string.join(item,'')) + if item in result: raise "repeat" + result.append(item) + + all = string.join(result,'') + for a, b in compatabilities.items(): + if a in all and b not in all: raise "repeat" + + break + except "repeat": + pass + + self.combo.entry.set_text(repr(result)[1:-1]) + self.reset() + self.randomizing = True + + def on_save(self, widget): + selecter = gtk.FileSelection('Save image') + selecter.set_filename('diagram.png') + + def on_ok(widget): + filename = selecter.get_filename() + selecter.destroy() + + if self.pixbuf_ready: + self.scaled_pixbuf.save(filename, 'png') + else: + pass + # TODO: show error + + def on_cancel(widget): + selecter.destroy() + + selecter.ok_button.connect('clicked', on_ok) + selecter.cancel_button.connect('clicked', on_ok) + + selecter.show() + + + +if __name__ == '__main__': + Interface().run() + sys.exit(0) + + # Just some phd stuff... + interface = Interface() + interface.window.show_all() + + base = 2 + chars = " 1" + n = len(connections) + done = { } + + for i in xrange(1, base ** n): + result = "" + for j in xrange(n): + result += chars[(i / (base ** j)) % base] + if normalize(result) in done or normalize(result.swapcase()) in done: continue + print result + done[normalize(result)] = True + + interface.combo.entry.set_text("'"+result+"', width=350, height=400") + interface.reset() + + while gtk.events_pending(): + gtk.main_iteration() + + if interface.assembler.dirty: + print "--- failed" + continue + + interface.scaled_pixbuf.save("/tmp/T" + result.replace(" ","-") + ".png", "png") diff --git a/haacheuur.daube.py b/haacheuur.daube.py new file mode 100644 index 0000000..ef13645 --- /dev/null +++ b/haacheuur.daube.py @@ -0,0 +1,268 @@ +#!/usr/bin/python +# coding: utf-8 + +# haacheuur 0.24 +# port industriel de port la nouvelle - couleur - 60cm*30cm +# image source : pln.jpg +# image obtenue : pln..20150910-11h59m53s.jpg + +# pln.png 3936x1024 pix + +''' +image input puredata : 16384 width x 768 height +donc par exemple, pour 3 images : +3 x 16384 = 49152 pixels en tout +''' + +import sys +import Image +import random +import os +import ImageDraw +import ImageFont +import ImageFilter +from time import gmtime, strftime +import time + +if not len(sys.argv) > 1: + raise SystemExit("Usage: %s image_source" % sys.argv[0]) + +# modifs du 30/10/2013 +import ImageEnhance + + +#rapport d'allongement de la nouvelle image par rapport à la largeur de l'image originale +allongement = 1 + + +#ouverture de l'image source et conversion en mode couleur 1bit +#1 bit (sys.argv[1])).convert('1') et niveaux de gris : (sys.argv[1])).convert('L') +im1 = Image.open(str(sys.argv[1])) +im2 = im1.copy() +im3 = Image.new("RGBA",(im1.size[0], im1.size[1])) +im5 = Image.new("RGBA",(im1.size[0], im1.size[1])) +im4 = Image.new("RGBA",(im1.size[0]*allongement, im1.size[1])) +#im4 = Image.new("RGBA",(49152, im1.size[1])) + +Larg = im1.size[0] +Haut = im1.size[1] +import pickle + +loadfile = False + +class Sequence: + def __init__(s): + randomCoupeHauteur = [] + s.randomCopyPosi =[] + s.proportions=[] + s.choix=[] + s.sizeOutput=None + s.sizeInput=None + +""" +seq = dict() +seq["randomCoupeHauteur"] = [] +seq["randomCopyPosi"] = [] +seq["proportions"] = [] +seq["choix"] = [] +seq["sizeOutput"]= im4.size +seq["sizeInput"] = im1.size +""" + +if loadfile: + seq=pickle.load(open("test.pickle")) + +else : + seq=Sequence() + +for i in range(1): + # constitution de la liste des tranches horizontales + # genre comme si qu'on avait un 16 pistes :) + # nombre aleatoire compris dans les limites de l'image + def randHaut(): + return random.randint(0, im1.size[1]/8)*8 + + if loadfile: + randomCoupeHauteur = seq.randomCoupeHauteur + + else: + randomCoupeHauteur = [0, \ + randHaut(),randHaut(),randHaut(),randHaut(), \ + randHaut(),randHaut(),randHaut(),randHaut(), \ + randHaut(),randHaut(),randHaut(),randHaut(), \ + randHaut(),randHaut(),randHaut(),randHaut(), \ + randHaut(),randHaut(),randHaut(),randHaut(), \ + randHaut(),randHaut(),randHaut(),randHaut(), \ + randHaut(),randHaut(),randHaut(),randHaut(), \ + randHaut(),randHaut(),randHaut(),randHaut(), \ + im1.size[1]] + # rangement des valeurs des plus petites au plus grandes + randomCoupeHauteur.sort() + seq.randomCoupeHauteur = randomCoupeHauteur + + # les hachures + def Hacheur(haut, bas) : + + n=0 + i=0 + #!!!!!!!!!! + while n 1 : + im5 = im5.resize((im3.size[0]/pixelSize, im3.size[1]/pixelSize), Image.NEAREST) + im5 = im5.resize((im3.size[0]*pixelSize, im3.size[1]*pixelSize), Image.NEAREST) + + im3 = im5.crop((0,0,cropfinal[0],cropfinal[1])) + + while loop 1 : + im5 = im5.resize((im3.size[0]/pixelSize, im3.size[1]/pixelSize), Image.NEAREST) + im5 = im5.resize((im3.size[0]*pixelSize, im3.size[1]*pixelSize), Image.NEAREST) + + im3 = im5.crop((0,0,cropfinal[0],cropfinal[1])) + + while loop