############################################################################ # # File: heddle.icn # # Subject: Program to find thread colors for weaving # # Author: Will Evans # # Date: April 19, 1999 # ############################################################################ # # Contributor: Gregg Townsend # ############################################################################ # # This file is in the public domain. # ############################################################################ # # Heddle solves a coloring problem inspired by weaving. Given a # multicolored rectangular pattern, assign colors to warp and weft # threads that will allow the pattern to be woven on a loom. # We ignore questions of structural integrity and insist only # that each cell's color be matched by either the corresponding # warp thread (column color) or weft thread (row color). # ############################################################################ # # Usage: heddle filename # # Input is an image file (GIF, XBM) to be mapped to the c1 palette, # or an image string acceptable to readims(). The maximum size is # 256 x 256. # # ############################################################################ # # Requires: Version 9 graphics # ############################################################################ # # Links: graphics, imscolor, imsutils # ############################################################################ link graphics link imscolor link imsutils global opts # command options global fname # input file name global imstring # image string from input file global nrows # number of rows in input image global ncols # number of columns in input image global palette # palette type (e.g. "c1") global data # image data ############################## MAIN ############################## procedure main(args) local g *args >= 1 | stop("usage: ", &progname, " imsfile *") every (fname := !args) do { if not readWeaving(fname) then { write(&errout,fname," : Can't load file") } else { g := implicationGraph() # writeGraph(g) scc(g) # writes("finishOrder ") # writeList(finishOrder) # writes("visited ") # writeForest(visited) if not assignColors() then { write(&errout,fname," : Can't assign colors") # writeForest(visited) } else { dpygrid(fname) } } } return end ############################## INPUT ############################## # readWeaving(fname) -- load image from file, convert to imstring # if necessary procedure readWeaving(fname) local f, s if f := WOpen("canvas=hidden", "image=" || fname) then { if WAttrib(f, "width" | "height") > 256 then write("image exceeds 256 x 256") & fail imstring := Capture(f, "c1") | (write("can't init captured image") & fail) WClose(f) } else { f := open(fname) | fail imstring := readims(f) | fail close(f) } ncols := imswidth(imstring) | fail nrows := imsheight(imstring) | fail palette := imspalette(imstring) | fail data := (imstring ? 3(tab(upto(',')+1), tab(upto(',')+1), tab(0))) | fail if *data ~= nrows * ncols then write("malformed image string: wrong data length") & fail if nrows > 256 || ncols > 256 then write("pattern exceeds 256 x 256") & fail return end ######################### Graph Structure ########################### # # Consists of a table of lists of strings. # The strings are vertex names. # The table is indexed by vertex names. # T["x1==c"] is a list of neighbors of vertex "x1==c" # The naming convention of vertices used in loom is: # # <==|~=> # # "x1==c" is a vertex that says "the first warp thread is color c" # "y3~=c" means the third weft thread is NOT color c" # ####################################################################### ######################### Depth First Search ######################## global visited # keep track of visited vtcs global finishOrder # vertex list: rev. finish order global treeNumber # DFS tree number $define RECURSIVE_DFS $ifdef RECURSIVE_DFS procedure dfs(g,visitOrder) local v finishOrder := [] # vertex list: rev. finish order visited := table() # table of visited vtcs (holds their treeNumber := 1 # DFS tree number) if /visitOrder then { visitOrder := [] every put(visitOrder,key(g)) } every /visited[v := !visitOrder] do { # loop over unvisited vertices dfsFrom(g,v) treeNumber +:= 1 } return end procedure dfsFrom(g,v) local w visited[v] := treeNumber # mark vertex with its DFStree number every /visited[w := !g[v]] do { # loop over unvisited nbrs dfsFrom(g,w) # push dfs from nbr onto tree } push(finishOrder,v) # store as finished return end $else procedure dfs(g,visitOrder) local v, w, stack stack := [] # stack for DFS finishOrder := [] # vertex list: rev. finish order visited := table() # table of visited vtcs (holds their treeNumber := 0 # DFS tree number) if /visitOrder then { # arbitrary visitOrder if not given visitOrder := [] every put(visitOrder,key(g)) } every /visited[v := !visitOrder] do { # loop over unvisited vertices treeNumber +:= 1 visited[v] := treeNumber # assign treeNumber put(g[v],"*") # add mark to end of adj list push(stack,v) # push vertex onto stack while (v := stack[1]) do { w := get(g[v]) # get next nbr of v if w == "*" then { # exhausted nbrs so pop v push(finishOrder,pop(stack)) } else { put(g[v],w) # put nbr at end of v's adj list if /visited[w] then { # if w not visited then visit... visited[w] := treeNumber put(g[w],"*") push(stack,w) # ...and stack } } } } end $endif ######################### Strongly Connected Components ############# # Sets "visited" to be SCC number of vertices in g: # If visited[v] = visited[w] then v and w in same SCC. # Sets "finishOrder" to be SCC-topoorder of vertices: # If (v,w) \in g then v and w in same SCC or v after w # in "finishOrder". procedure scc(g) dfs(g) dfs(transpose(g),copy(finishOrder)) return end ######################### Transpose ################################# procedure transpose(g) local h, v, w h := table() # table of lists every v := key(g) do { /h[v] := [] # create empty adj list if needed every w := !g[v] do { /h[w] := [] put(h[w],v) } } return h end ######################### Graph from Image ########################## procedure implicationGraph() local colors, i, j, c, d, g, x, y, notx, noty colors := set() # set of colors in image # Form an implication graph from the given data g := table() # graph = table of lists # Put in edges caused by the color matrix data ? { every j := 1 to nrows do { every i := 1 to ncols do { c := move(1) notx := "x"||i||"~="||c noty := "y"||j||"~="||c x := "x"||i||"=="||c y := "y"||j||"=="||c /g[notx] := [] # create empty adj lists if needed /g[noty] := [] /g[x] := [] /g[y] := [] put(g[notx],y) # xi~=c --> yj==c put(g[noty],x) # yj~=c --> xi==c insert(colors,c) # add color to set of seen colors } } } # Put in edges that say color for a thread must be unique every c := !colors do { every i := 1 to ncols do { every d := (c ~== !colors) do { x := "x"||i||"=="||c notx := "x"||i||"~="||d /g[x] := [] # create empty adj lists if needed /g[notx] := [] put(g[x],notx) # xi==c --> xi~=d } } every i := 1 to nrows do { every d := (c ~== !colors) do { y := "y"||i||"=="||c noty := "y"||i||"~="||d /g[y] := [] # create empty adj lists if needed /g[noty] := [] put(g[y],noty) # yi==c --> yi~=d } } } return g end ######################### Assign Colors ############################# # If "xi==c" and "xi~=c" (or "yj==c" and "yj~=c") both occur in the same # strongly connected component, for some character c and 1<=i<=nrows # (1<=j<=nrows), then there is no solution. # # If "xi==c" is first occurrence of "xi==*" (or "yi==c" is first of "yi==*") # in SCC-topoorder then the warp thread i (weft thread i) can be colored c. global colColor global rowColor procedure assignColors() local v, xy, i, op, c colColor := list(ncols) rowColor := list(nrows) every v := !finishOrder do { v ? { # parse vertex name xy := move(1) i := tab(many(&digits)) op := move(2) c := move(1) } if (op == "==") then { if (xy == "x") & (/colColor[i]) then { if (visited[v] == visited[xy||i||"~="||c]) then fail colColor[i] := c } else if (xy == "y") & (/rowColor[i]) then { if (visited[v] == visited[xy||i||"~="||c]) then fail rowColor[i] := c } } } return end ######################### OUTPUT ############################# # dpygrid(label) -- display grid in window $define BACKGROUND "pale-weak-yellow" $define PREFSZ 800 # preferred size after scaling $define MAXMAG 10 # maximum magnification $define STRIPE 6 # space for thread color(s) $define GAP 1 # margin around image procedure dpygrid(label) local s, x, y, c static w, h, z, p, v p := imspalette(imstring) w := STRIPE + GAP + ncols + GAP + STRIPE h := STRIPE + GAP + nrows + GAP + STRIPE z := PREFSZ / w z >:= PREFSZ / h z <:= 1 z >:= MAXMAG WOpen("width=" || (z * w), "height=" || (z * h), "bg=" || BACKGROUND) | (write("can't open window") & fail) EraseArea() DrawImage(STRIPE + GAP, STRIPE + GAP, imstring) y := 0 every c := !rowColor do { Fg(PaletteColor(palette,c)) DrawPoint(STRIPE - 1, STRIPE + GAP + y) DrawPoint(w - STRIPE, STRIPE + GAP + y) y +:= 1 } x := 0 every c := !colColor do { Fg(PaletteColor(palette,c)) DrawPoint(STRIPE + GAP + x, STRIPE - 1) DrawPoint(STRIPE + GAP + x, h - STRIPE) x +:= 1 } Zoom(0, 0, w, h, 0, 0, z * w, z * h) if nrows <= z * STRIPE & ncols <= z * STRIPE then every DrawImage(1 | z * w - ncols - 1, 1 | z * h - nrows - 1, imstring) WAttrib("label=" || fname || ": " || label) until Event() === QuitEvents() WClose() return end ############################## DEBUG ############################# procedure writeGraph(g) local v every v := key(g) do { writes(v,":") writeList(g[v]) } return end procedure writeList(L) writes("[") every writes(!L,",") write("]") return end procedure writeForest(F) local pair, index index := 0 every pair := !sort(F,2) do { if (index ~== pair[2]) then { write() writes(index +:= 1,": ") } writes(pair[1]," ") } write() return end