diff --git a/programs/formats/gerber/plot png b/programs/formats/gerber/plot png
index f7ecc07ea3b6f423327792123851914d3fd7c716..24a7b3f52e575659f8bac25be795d6202488f832 100644
--- a/programs/formats/gerber/plot png	
+++ b/programs/formats/gerber/plot png	
@@ -1 +1 @@
-{"modules":{"0.07944144280928633":{"definition":"//\n// edge detect\n//    green = interior, blue = exterior, red = boundary\n//    assumes input is thresholded\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'edge detect'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         edge_detect()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:interior, blue:exterior, red:boundary'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// edge detect\n//\nfunction edge_detect() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({worker:worker.toString(),\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var i00,i0m,i0p,im0,ip0,imm,imp,ipm,ipp,row,col\n      //\n      // find edges - interior\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            i00 = (input[(h-1-row)*w*4+col*4+0] \n                      +input[(h-1-row)*w*4+col*4+1] \n                      +input[(h-1-row)*w*4+col*4+2])\n            i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                      +input[(h-1-row)*w*4+(col+1)*4+1] \n                      +input[(h-1-row)*w*4+(col+1)*4+2])\n            ip0 = (input[(h-2-row)*w*4+col*4+0] \n                      +input[(h-2-row)*w*4+col*4+1] \n                      +input[(h-2-row)*w*4+col*4+2])\n            ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                      +input[(h-2-row)*w*4+(col+1)*4+1] \n                      +input[(h-2-row)*w*4+(col+1)*4+2])\n            i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                      +input[(h-1-row)*w*4+(col-1)*4+1] \n                      +input[(h-1-row)*w*4+(col-1)*4+2])\n            im0 = (input[(h-row)*w*4+col*4+0] \n                      +input[(h-row)*w*4+col*4+1] \n                      +input[(h-row)*w*4+col*4+2])\n            imm = (input[(h-row)*w*4+(col-1)*4+0] \n                      +input[(h-row)*w*4+(col-1)*4+1] \n                      +input[(h-row)*w*4+(col-1)*4+2])\n            imp = (input[(h-row)*w*4+(col+1)*4+0] \n                      +input[(h-row)*w*4+(col+1)*4+1] \n                      +input[(h-row)*w*4+(col+1)*4+2])\n            ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                      +input[(h-2-row)*w*4+(col-1)*4+1] \n                      +input[(h-2-row)*w*4+(col-1)*4+2])\n            if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n               || (i00 != i0m) || (i00 != im0) || (i00 != imm)\n               || (i00 != imp) || (i00 != ipm)) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else if (i00 == 0) {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // left and right edges\n      //\n      for (row = 1; row < (h-1); ++row) {\n         col = w-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm) \n           || (i00 != im0) || (i00 != imm)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         col = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // top and bottom edges\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = h-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != imm) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         row = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != ipm) \n           || (i00 != ip0) || (i00 != ipp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // corners\n      //\n      row = 0\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                +input[(h-2-row)*w*4+(col+1)*4+1] \n                +input[(h-2-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = 0\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                +input[(h-2-row)*w*4+(col-1)*4+1] \n                +input[(h-2-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imp = (input[(h-row)*w*4+(col+1)*4+0] \n                +input[(h-row)*w*4+(col+1)*4+1] \n                +input[(h-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != im0) || (i00 != imp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imm = (input[(h-row)*w*4+(col-1)*4+0] \n                +input[(h-row)*w*4+(col-1)*4+1] \n                +input[(h-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != im0) || (i00 != imm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"188.3025579837396","left":"1686.6484625538612","filename":"undefined","inputs":{},"outputs":{}},"0.8903773266711255":{"definition":"//\n// orient edges\n//    input is green:interior, blue:exterior, red:boundary\n//    output is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'orient edges'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         var ctx = mod.display.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         orient_edges()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // off-screen display canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.display = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('red:north, dark red:south'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:east, dark green:west'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('blue:start, dark blue:stop'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.display,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// orient edges\n//\nfunction orient_edges() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      var disp = new Uint8ClampedArray(evt.data.display)\n      var dispdata = new ImageData(disp,w,h)\n      var ctx = mod.display.getContext(\"2d\")\n      ctx.putImageData(dispdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var w = mod.canvas.width\n      var h = mod.canvas.height\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,w,h)\n      ctx.drawImage(mod.display,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var row,col\n      var boundary = 0\n      var interior = 1\n      var exterior = 2\n      var alpha = 3\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      //\n      // orient body states\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            output[(h-1-row)*w*4+col*4+northsouth] = 0\n            output[(h-1-row)*w*4+col*4+eastwest] = 0\n            output[(h-1-row)*w*4+col*4+startstop] = 0\n            output[(h-1-row)*w*4+col*4+alpha] = 255\n            if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n               if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= north\n               if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= south\n               if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n                  && ((input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= east\n               if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n                  && ((input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= west\n               }\n            }\n         }\n      //\n      // orient edge states\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= north\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         row = h-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= south\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      for (row = 1; row < (h-1); ++row) {\n         col = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n               && (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= east\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         col = w-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n               && (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= west\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      //\n      // orient corner states (todo)\n      //\n      row = 0\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = 0\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      //\n      // invert background for display\n      //\n      var display = new Uint8ClampedArray(h*w*4)\n      var r,g,b,i\n      for (row = 0; row < h; ++row) {\n         for (col = 0; col < w; ++col) {\n            r = output[(h-1-row)*w*4+col*4+0]\n            g = output[(h-1-row)*w*4+col*4+1]\n            b = output[(h-1-row)*w*4+col*4+2]\n            i = r+g+b\n            if (i != 0) {            \n               display[(h-1-row)*w*4+col*4+0] = output[(h-1-row)*w*4+col*4+0]\n               display[(h-1-row)*w*4+col*4+1] = output[(h-1-row)*w*4+col*4+1]\n               display[(h-1-row)*w*4+col*4+2] = output[(h-1-row)*w*4+col*4+2]\n               display[(h-1-row)*w*4+col*4+3] = output[(h-1-row)*w*4+col*4+3]\n               }\n            else {\n               display[(h-1-row)*w*4+col*4+0] = 255\n               display[(h-1-row)*w*4+col*4+1] = 255\n               display[(h-1-row)*w*4+col*4+2] = 255\n               display[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // return output\n      //\n      self.postMessage({buffer:output.buffer,display:display.buffer},[output.buffer,display.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"618.3025579837396","left":"1667.6484625538612","filename":"undefined","inputs":{},"outputs":{}},"0.08127540142793499":{"definition":"//\n// read png\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'read png'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   }\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}},\n   imageInfo:{type:'object',\n      event:function(){\n         var obj = {}\n         obj.name = mod.name.nodeValue\n         obj.dpi = parseFloat(mod.dpitext.value)\n         obj.width = mod.img.width\n         obj.height = mod.img.height\n         mods.output(mod,'imageInfo',obj)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // file input control\n   //\n   var file = document.createElement('input')\n      file.setAttribute('type','file')\n      file.setAttribute('id',div.id+'file_input')\n      file.style.position = 'absolute'\n      file.style.left = 0\n      file.style.top = 0\n      file.style.width = 0\n      file.style.height = 0\n      file.style.opacity = 0\n      file.addEventListener('change',function() {\n         png_read_handler()\n         })\n      div.appendChild(file)\n      mod.file = file\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // file select button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('select png file'))\n      btn.addEventListener('click',function(){\n         var file = document.getElementById(div.id+'file_input')\n         file.value = null\n         file.click()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createTextNode(' '))\n   //\n   // invert button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('invert'))\n      btn.addEventListener('click',function(){\n         invert_image()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // info div\n   //\n   var info = document.createElement('div')\n      info.setAttribute('id',div.id+'info')\n      info.appendChild(document.createTextNode('dpi: '))\n      var input = document.createElement('input')\n         input.type = 'text'\n         input.size = 6\n         input.addEventListener('input',function(){\n            mod.dpi = parseFloat(mod.dpitext.value)\n            mod.mmtext.nodeValue = (25.4*mod.img.width/mod.dpi).toFixed(3)\n               +' x '+(25.4*mod.img.height/mod.dpi).toFixed(3)+' mm'\n            mod.intext.nodeValue = (mod.img.width/mod.dpi).toFixed(3)\n               +' x '+(mod.img.height/mod.dpi).toFixed(3)+' in'\n            outputs.imageInfo.event()\n            })\n         info.appendChild(input)\n         mod.dpitext = input\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('px: ')\n         info.appendChild(text)\n         mod.pxtext = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('mm: ')\n         info.appendChild(text)\n         mod.mmtext = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('in: ')\n         info.appendChild(text)\n         mod.intext = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('')\n         info.appendChild(text)\n         mod.name = text\n      div.appendChild(info)\n   }\n//\n// local functions\n//\n// read handler\n//\nfunction png_read_handler(event) {\n   var file_reader = new FileReader()\n   file_reader.onload = png_binary_handler\n   input_file = mod.file.files[0]\n   file_name = input_file.name\n   mod.name.nodeValue = file_name\n   file_reader.readAsArrayBuffer(input_file)\n   }\n//\n// binary load handler\n//\nfunction png_binary_handler(event) {\n   //\n   // get DPI\n   //\n   // 8 header\n   // 4 len, 4 type, data, 4 crc\n   // pHYs 4 ppx, 4 ppy, 1 unit: 0 ?, 1 meter\n   // IEND\n   //\n   var units = ppx = ppy = 0\n   var buf = event.target.result\n   var view = new DataView(buf)\n   var ptr = 8\n   if (!((view.getUint8(1) == 80) && (view.getUint8(2) == 78) && (view.getUint8(3) == 71))) {\n      set_prompt(\"error: PNG header not found\")\n      return\n      }\n   while (1) {\n      var length = view.getUint32(ptr)\n      ptr += 4\n      var type = String.fromCharCode(\n         view.getUint8(ptr),view.getUint8(ptr+1),\n         view.getUint8(ptr+2),view.getUint8(ptr+3))\n      ptr += 4\n      if (type == \"pHYs\") {\n         ppx = view.getUint32(ptr)\n         ppy = view.getUint32(ptr + 4)\n         units = view.getUint8(ptr + 8)\n         }\n      if (type == \"IEND\")\n         break\n      ptr += length + 4\n      }\n   if (units == 0) {\n      set_prompt(\"no PNG units not found, assuming 72 DPI\")\n      ppx = 72*1000/25.4\n      }\n   dpi = ppx*25.4/1000\n   //\n   // read as URL for display\n   //\n   var file_reader = new FileReader()\n   file_reader.onload = png_URL_handler\n   file_reader.readAsDataURL(input_file)\n   }\n//\n// URL load handler\n//\nfunction png_URL_handler(event) {\n   var img = new Image()\n   img.setAttribute(\"src\",event.target.result)\n   img.onload = function() {\n      if (img.width > img.height) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-img.height/img.width)\n         var w = mod.canvas.width\n         var h = mod.canvas.width*img.height/img.width\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-img.width/img.height)\n         var y0 = 0\n         var w = mod.canvas.height*img.width/img.height\n         var h = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n         ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n         ctx.drawImage(img,x0,y0,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = img.width\n         ctx.canvas.height = img.height \n         ctx.drawImage(img,0,0)\n      mod.dpitext.value = dpi.toFixed(3)\n      mod.pxtext.nodeValue = img.width+' x '+img.height+' px'\n      mod.mmtext.nodeValue = (25.4*img.width/dpi).toFixed(3)\n         +' x '+(25.4*img.height/dpi).toFixed(3)+' mm'\n      mod.intext.nodeValue = (img.width/dpi).toFixed(3)\n         +' x '+(img.height/dpi).toFixed(3)+' in'\n      outputs.image.event()\n      outputs.imageInfo.event()\n      }\n   }\n//\n// invert image\n//\nfunction invert_image() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var h = mod.img.height\n   var w = mod.img.width\n   var ctx = mod.img.getContext(\"2d\")\n   var img = ctx.getImageData(0,0,w,h)\n   webworker.postMessage({\n      height:img.height,width:img.width,buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            buf[(h-1-row)*w*4+col*4+0] \n               = 255-buf[(h-1-row)*w*4+col*4+0] \n            buf[(h-1-row)*w*4+col*4+1] \n               = 255-buf[(h-1-row)*w*4+col*4+1] \n            buf[(h-1-row)*w*4+col*4+2] \n               = 255-buf[(h-1-row)*w*4+col*4+2] \n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"227.3025579837396","left":"162.64846255386124","filename":"undefined","inputs":{},"outputs":{}},"0.6488303557466412":{"definition":"//\n// image threshold\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'image threshold'\n//\n// initialization\n//\nvar init = function() {\n   mod.threshold.value = 0.5\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         threshold_image()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // threshold value\n   //\n   div.appendChild(document.createTextNode('threshold (0-1): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         threshold_image()\n         })\n      div.appendChild(input)\n      mod.threshold = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// threshold image\n//\nfunction threshold_image() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var t = parseFloat(mod.threshold.value)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,threshold:t,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var t = evt.data.threshold\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var r,g,b,a,i\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            r = buf[(h-1-row)*w*4+col*4+0] \n            g = buf[(h-1-row)*w*4+col*4+1] \n            b = buf[(h-1-row)*w*4+col*4+2] \n            a = buf[(h-1-row)*w*4+col*4+3] \n            i = (r+g+b)/(3*255)\n            if (a == 0)\n               val = 255\n            else if (i > t)\n               var val = 255\n            else\n               var val = 0\n            buf[(h-1-row)*w*4+col*4+0] = val\n            buf[(h-1-row)*w*4+col*4+1] = val\n            buf[(h-1-row)*w*4+col*4+2] = val\n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"99.3025579837396","left":"1205.6484625538612","filename":"undefined","inputs":{},"outputs":{}},"0.749132408760488":{"definition":"//\n// vectorize\n//    input is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'vectorize'\n//\n// initialization\n//\nvar init = function() {\n   mod.error.value = 1\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         vectorize()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   path:{type:'array',\n      event:function(){\n         mods.output(mod,'path',mod.path)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))   \n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // error value\n   //\n   div.appendChild(document.createTextNode('vector fit (pixels): '))\n   //div.appendChild(document.createElement('br'))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         vectorize()\n         })\n      div.appendChild(input)\n      mod.error = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      input.checked = true\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// vectorize\n//\nfunction vectorize() {\n   //\n   // draw path\n   //\n   function draw_path(path) {\n      window.URL.revokeObjectURL(url)\n      var svg = document.getElementById(mod.div.id+'svg')\n      svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n      var g = document.getElementById(mod.div.id+'g')\n      svg.removeChild(g)\n      var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n      g.setAttribute('id',mod.div.id+'g')\n      var h = mod.img.height\n      var w = mod.img.width\n      var xend = null\n      var yend = null\n      //\n      // loop over segments\n      //\n      for (var segment in path) {\n         if (path[segment].length > 1) {\n            if (xend != null) {\n               //\n               // draw connection from previous segment\n               //\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','red')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = xend\n               var y1 = yend\n               var x2 = path[segment][0][0]\n               var y2 = h-path[segment][0][1]-1\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','red')\n                  g.appendChild(triangle)\n                  }\n               }\n            //\n            // loop over points\n            //\n            for (var point = 1; point < path[segment].length; ++point) {\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','black')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = path[segment][point-1][0]\n               var y1 = h-path[segment][point-1][1]-1\n               var x2 = path[segment][point][0]\n               var y2 = h-path[segment][point][1]-1\n               xend = x2\n               yend = y2\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','black')\n                  g.appendChild(triangle)\n                  }\n               }\n            }\n         }\n      svg.appendChild(g)\n      }\n   //\n   // set up worker\n   //\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      webworker.terminate()\n      mod.path = evt.data.path\n      draw_path(mod.path)\n      outputs.path.event()\n      })\n   //\n   // call worker\n   //\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,sort:mod.sort.checked,\n      error:parseFloat(mod.error.value),\n      buffer:mod.input.data.buffer})\n   }\n//\n// vectorize worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var sort = evt.data.sort\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      var path = []\n      //\n      // edge follower\n      //\n      function follow_edges(row,col) {\n         if ((input[(h-1-row)*w*4+col*4+northsouth] != 0)\n            || (input[(h-1-row)*w*4+col*4+eastwest] != 0)) {\n            path[path.length] = [[col,row]]\n            while (1) {\n               if (input[(h-1-row)*w*4+col*4+northsouth] & north) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~north\n                  row += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+northsouth] & south) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~south\n                  row -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & east) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~east\n                  col += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & west) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~west\n                  col -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else\n                  break\n               }\n            }\n         }\n      //\n      // follow boundary starts\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         col = 0\n         follow_edges(row,col)\n         col = w-1\n         follow_edges(row,col)\n         }\n      for (var col = 1; col < (w-1); ++col) {\n         row = 0\n         follow_edges(row,col)\n         row = h-1      \n         follow_edges(row,col)\n         }\n      //\n      // follow interior paths\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         for (var col = 1; col < (w-1); ++col) {\n            follow_edges(row,col)\n            }\n         }\n      //\n      // vectorize path\n      //\n      var error = evt.data.error\n      var vecpath = []\n      for (var seg = 0; seg < path.length; ++seg) {\n         var x0 = path[seg][0][0]\n         var y0 = path[seg][0][1]\n         vecpath[vecpath.length] = [[x0,y0]]\n         var xsum = x0\n         var ysum = y0\n         var sum = 1\n         for (var pt = 1; pt < path[seg].length; ++pt) {\n            var xold = x\n            var yold = y\n            var x = path[seg][pt][0]\n            var y = path[seg][pt][1]\n            if (sum == 1) {\n               xsum += x\n               ysum += y\n               sum += 1\n               }\n            else {\n               var xmean = xsum/sum\n               var ymean = ysum/sum\n               var dx = xmean-x0\n               var dy = ymean-y0\n               var d = Math.sqrt(dx*dx+dy*dy)\n               var nx = dy/d\n               var ny = -dx/d\n               var l = Math.abs(nx*(x-x0)+ny*(y-y0))\n               if (l < error) {\n                  xsum += x\n                  ysum += y\n                  sum += 1\n                  }\n               else {\n                  vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [xold,yold]\n                  x0 = xold\n                  y0 = yold\n                  xsum = xold\n                  ysum = yold\n                  sum = 1\n                  }\n               }\n            if (pt == (path[seg].length-1)) {\n               vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [x,y]\n               }\n            }\n         }\n      //\n      // sort path\n      //\n      if ((vecpath.length > 0) && (sort == true)) {\n         var dmin = w*w+h*h\n         segmin = null\n         for (var seg = 0; seg < vecpath.length; ++seg) {\n            var x = vecpath[seg][0][0]\n            var y = vecpath[seg][0][0]\n            var d = x*x+y*y\n            if (d < dmin) {\n               dmin = d\n               segmin = seg\n               }\n            }\n         if (segmin != null) {\n            var sortpath = [vecpath[segmin]]\n            vecpath.splice(segmin,1)\n            }\n         while (vecpath.length > 0) {\n            var dmin = w*w+h*h\n            var x0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][0]\n            var y0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][1]\n            segmin = null\n            for (var seg = 0; seg < vecpath.length; ++seg) {\n               var x = vecpath[seg][0][0]\n               var y = vecpath[seg][0][1]\n               var d = (x-x0)*(x-x0)+(y-y0)*(y-y0)\n               if (d < dmin) {\n                  dmin = d\n                  segmin = seg\n                  }\n               }\n            if (segmin != null) {\n               sortpath[sortpath.length] = vecpath[segmin]\n               vecpath.splice(segmin,1)\n               }\n            }\n         }\n      else if ((vecpath.length > 0) && (sort == false))\n         sortpath = vecpath\n      else\n         sortpath = []\n      //\n      // return path\n      //\n      self.postMessage({path:sortpath})\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"532.3025579837396","left":"1262.6484625538612","filename":"undefined","inputs":{},"outputs":{}},"0.9085301680722191":{"definition":"//\n// path to Gerber\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2021\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'path to Gerber'\n//\n// initialization\n//\nvar init = function() {\n   mod.fill.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   imageInfo:{type:'',\n      event:function(evt){\n         mod.imageInfo = evt.detail\n         }},\n   path:{type:'',\n      event:function(evt){\n         mod.path = evt.detail\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   Gerber:{type:'',\n      event:function(evt){\n         mods.output(mod,'Gerber',evt)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('plot'))\n      btn.addEventListener('click',function(){\n         plot()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('format:'))\n   div.appendChild(document.createElement('br'))\n   var formats = document.createElement('div');\n   formats.style.textAlign = 'left'\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'format'\n      input.id = mod.div.id+'fill'\n      formats.appendChild(input)\n   mod.fill = input\n   formats.appendChild(document.createTextNode('fill'))\n   formats.appendChild(document.createElement('br'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'format'\n      input.id = mod.div.id+'outline'\n      input.disabled = true\n      formats.appendChild(input)\n   mod.outline = input\n   formats.appendChild(document.createTextNode('outline'))\n   formats.appendChild(document.createElement('br'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'format'\n      input.id = mod.div.id+'outline'\n      input.disabled = true\n      formats.appendChild(input)\n   mod.drill = input\n   formats.appendChild(document.createTextNode('drill'))\n   div.appendChild(formats)\n   }\n//\n// local functions\n//\n// plot\n//\nfunction plot() {\n   var imgwidth = mod.imageInfo.width/parseFloat(mod.imageInfo.dpi)\n   var imgheight = mod.imageInfo.height/parseFloat(mod.imageInfo.dpi)\n   str = ''\n   str += \"%MOIN*%\\n\" // inch units\n   str += \"%ASAXBY*%\\n\" // axes\n   str += \"%IPPOS*%\\n\" // positive image\n   str += \"%LPD*%\\n\" // dark layer\n   str += \"%FSLAX33Y33*%\\n\" // format absolute 3.3\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n         var x = imgwidth*mod.path[seg][pt][0]/(mod.imageInfo.width-1)\n         var y = imgheight*(1-mod.path[seg][pt][1]/(mod.imageInfo.height-1))\n         }\n      var x = imgwidth*mod.path[seg][0][0]/(mod.imageInfo.width-1)\n      var y = imgheight*(1-mod.path[seg][0][1]/(mod.imageInfo.height-1))\n      console.log(parseFloat(x.toFixed(3)))\n      }\n   }\n/*\n%MOIN*%\n%ASAXBY*%\n%IPPOS*%\n%FSLAX33Y33*%\n%ADD11C,0.1*%\n%LPD*%\nG36*\nX1000Y1000D02*\nX2000Y2000D01*\nX2000Y1000D01*\nX1000Y1000D01*\nG37*\n%LPC*%\nG01*\nD11*\nX1000Y1000D02*\nX1000Y2000D01*\nX2000Y2000D01*\nX1500Y1000D01*\nM02*\n//\n// convert\n//\nfunction convert(path) {\n   //\n   // draw path\n   //\n      if (mod.fill.checked == true) {\n         var polyline = document.createElementNS(svgNS,'polyline')\n            polyline.setAttribute('stroke','none')\n            polyline.setAttribute('fill','black')\n            polyline.setAttribute('fill-rule','evenodd')\n            polyline.setAttribute('points',points)\n            g.appendChild(polyline)\n         }\n      else {\n         var polyline = document.createElementNS(svgNS,'polyline')\n            polyline.setAttribute('stroke','black')\n            polyline.setAttribute('stroke-width','0.01')\n            polyline.setAttribute('stroke-linecap','round')\n            polyline.setAttribute('fill','none')\n            polyline.setAttribute('points',points)\n            g.appendChild(polyline)\n         }\n      }\n   //\n   // save file\n   //\n   var obj = {}\n   obj.type = 'file'\n   obj.name = mod.imageInfo.name+'.svg'\n   var xml = new XMLSerializer().serializeToString(svg)\n   obj.contents = xml\n   outputs.SVG.event(obj)\n   }\n*/\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n\n","top":"377","left":"692","filename":"modules/path/formats/gerber","inputs":{},"outputs":{}}},"links":["{\"source\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.08127540142793499\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.749132408760488\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.08127540142793499\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9085301680722191\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.749132408760488\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"path\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9085301680722191\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"path\\\"}\"}"]}
\ No newline at end of file
+{"modules":{"0.07944144280928633":{"definition":"//\n// edge detect\n//    green = interior, blue = exterior, red = boundary\n//    assumes input is thresholded\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'edge detect'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         edge_detect()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:interior, blue:exterior, red:boundary'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// edge detect\n//\nfunction edge_detect() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({worker:worker.toString(),\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var i00,i0m,i0p,im0,ip0,imm,imp,ipm,ipp,row,col\n      //\n      // find edges - interior\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            i00 = (input[(h-1-row)*w*4+col*4+0] \n                      +input[(h-1-row)*w*4+col*4+1] \n                      +input[(h-1-row)*w*4+col*4+2])\n            i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                      +input[(h-1-row)*w*4+(col+1)*4+1] \n                      +input[(h-1-row)*w*4+(col+1)*4+2])\n            ip0 = (input[(h-2-row)*w*4+col*4+0] \n                      +input[(h-2-row)*w*4+col*4+1] \n                      +input[(h-2-row)*w*4+col*4+2])\n            ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                      +input[(h-2-row)*w*4+(col+1)*4+1] \n                      +input[(h-2-row)*w*4+(col+1)*4+2])\n            i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                      +input[(h-1-row)*w*4+(col-1)*4+1] \n                      +input[(h-1-row)*w*4+(col-1)*4+2])\n            im0 = (input[(h-row)*w*4+col*4+0] \n                      +input[(h-row)*w*4+col*4+1] \n                      +input[(h-row)*w*4+col*4+2])\n            imm = (input[(h-row)*w*4+(col-1)*4+0] \n                      +input[(h-row)*w*4+(col-1)*4+1] \n                      +input[(h-row)*w*4+(col-1)*4+2])\n            imp = (input[(h-row)*w*4+(col+1)*4+0] \n                      +input[(h-row)*w*4+(col+1)*4+1] \n                      +input[(h-row)*w*4+(col+1)*4+2])\n            ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                      +input[(h-2-row)*w*4+(col-1)*4+1] \n                      +input[(h-2-row)*w*4+(col-1)*4+2])\n            if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n               || (i00 != i0m) || (i00 != im0) || (i00 != imm)\n               || (i00 != imp) || (i00 != ipm)) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else if (i00 == 0) {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // left and right edges\n      //\n      for (row = 1; row < (h-1); ++row) {\n         col = w-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm) \n           || (i00 != im0) || (i00 != imm)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         col = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // top and bottom edges\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = h-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != imm) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         row = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != ipm) \n           || (i00 != ip0) || (i00 != ipp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // corners\n      //\n      row = 0\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                +input[(h-2-row)*w*4+(col+1)*4+1] \n                +input[(h-2-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = 0\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                +input[(h-2-row)*w*4+(col-1)*4+1] \n                +input[(h-2-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imp = (input[(h-row)*w*4+(col+1)*4+0] \n                +input[(h-row)*w*4+(col+1)*4+1] \n                +input[(h-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != im0) || (i00 != imp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imm = (input[(h-row)*w*4+(col-1)*4+0] \n                +input[(h-row)*w*4+(col-1)*4+1] \n                +input[(h-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != im0) || (i00 != imm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"262.3025579837396","left":"1815.6484625538612","filename":"undefined","inputs":{},"outputs":{}},"0.8903773266711255":{"definition":"//\n// orient edges\n//    input is green:interior, blue:exterior, red:boundary\n//    output is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'orient edges'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         var ctx = mod.display.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         orient_edges()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // off-screen display canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.display = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('red:north, dark red:south'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:east, dark green:west'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('blue:start, dark blue:stop'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.display,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// orient edges\n//\nfunction orient_edges() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      var disp = new Uint8ClampedArray(evt.data.display)\n      var dispdata = new ImageData(disp,w,h)\n      var ctx = mod.display.getContext(\"2d\")\n      ctx.putImageData(dispdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var w = mod.canvas.width\n      var h = mod.canvas.height\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,w,h)\n      ctx.drawImage(mod.display,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var row,col\n      var boundary = 0\n      var interior = 1\n      var exterior = 2\n      var alpha = 3\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      //\n      // orient body states\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            output[(h-1-row)*w*4+col*4+northsouth] = 0\n            output[(h-1-row)*w*4+col*4+eastwest] = 0\n            output[(h-1-row)*w*4+col*4+startstop] = 0\n            output[(h-1-row)*w*4+col*4+alpha] = 255\n            if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n               if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= north\n               if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= south\n               if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n                  && ((input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= east\n               if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n                  && ((input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= west\n               }\n            }\n         }\n      //\n      // orient edge states\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= north\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         row = h-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= south\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      for (row = 1; row < (h-1); ++row) {\n         col = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n               && (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= east\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         col = w-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n               && (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= west\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      //\n      // orient corner states (todo)\n      //\n      row = 0\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = 0\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      //\n      // invert background for display\n      //\n      var display = new Uint8ClampedArray(h*w*4)\n      var r,g,b,i\n      for (row = 0; row < h; ++row) {\n         for (col = 0; col < w; ++col) {\n            r = output[(h-1-row)*w*4+col*4+0]\n            g = output[(h-1-row)*w*4+col*4+1]\n            b = output[(h-1-row)*w*4+col*4+2]\n            i = r+g+b\n            if (i != 0) {            \n               display[(h-1-row)*w*4+col*4+0] = output[(h-1-row)*w*4+col*4+0]\n               display[(h-1-row)*w*4+col*4+1] = output[(h-1-row)*w*4+col*4+1]\n               display[(h-1-row)*w*4+col*4+2] = output[(h-1-row)*w*4+col*4+2]\n               display[(h-1-row)*w*4+col*4+3] = output[(h-1-row)*w*4+col*4+3]\n               }\n            else {\n               display[(h-1-row)*w*4+col*4+0] = 255\n               display[(h-1-row)*w*4+col*4+1] = 255\n               display[(h-1-row)*w*4+col*4+2] = 255\n               display[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // return output\n      //\n      self.postMessage({buffer:output.buffer,display:display.buffer},[output.buffer,display.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"692.3025579837396","left":"1796.6484625538612","filename":"undefined","inputs":{},"outputs":{}},"0.08127540142793499":{"definition":"//\n// read png\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'read png'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   }\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}},\n   imageInfo:{type:'object',\n      event:function(){\n         var obj = {}\n         obj.name = mod.name.nodeValue\n         obj.dpi = parseFloat(mod.dpitext.value)\n         obj.width = mod.img.width\n         obj.height = mod.img.height\n         mods.output(mod,'imageInfo',obj)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // file input control\n   //\n   var file = document.createElement('input')\n      file.setAttribute('type','file')\n      file.setAttribute('id',div.id+'file_input')\n      file.style.position = 'absolute'\n      file.style.left = 0\n      file.style.top = 0\n      file.style.width = 0\n      file.style.height = 0\n      file.style.opacity = 0\n      file.addEventListener('change',function() {\n         png_read_handler()\n         })\n      div.appendChild(file)\n      mod.file = file\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // file select button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('select png file'))\n      btn.addEventListener('click',function(){\n         var file = document.getElementById(div.id+'file_input')\n         file.value = null\n         file.click()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createTextNode(' '))\n   //\n   // invert button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('invert'))\n      btn.addEventListener('click',function(){\n         invert_image()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // info div\n   //\n   var info = document.createElement('div')\n      info.setAttribute('id',div.id+'info')\n      info.appendChild(document.createTextNode('dpi: '))\n      var input = document.createElement('input')\n         input.type = 'text'\n         input.size = 6\n         input.addEventListener('input',function(){\n            mod.dpi = parseFloat(mod.dpitext.value)\n            mod.mmtext.nodeValue = (25.4*mod.img.width/mod.dpi).toFixed(3)\n               +' x '+(25.4*mod.img.height/mod.dpi).toFixed(3)+' mm'\n            mod.intext.nodeValue = (mod.img.width/mod.dpi).toFixed(3)\n               +' x '+(mod.img.height/mod.dpi).toFixed(3)+' in'\n            outputs.imageInfo.event()\n            })\n         info.appendChild(input)\n         mod.dpitext = input\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('px: ')\n         info.appendChild(text)\n         mod.pxtext = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('mm: ')\n         info.appendChild(text)\n         mod.mmtext = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('in: ')\n         info.appendChild(text)\n         mod.intext = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('')\n         info.appendChild(text)\n         mod.name = text\n      div.appendChild(info)\n   }\n//\n// local functions\n//\n// read handler\n//\nfunction png_read_handler(event) {\n   var file_reader = new FileReader()\n   file_reader.onload = png_binary_handler\n   input_file = mod.file.files[0]\n   file_name = input_file.name\n   mod.name.nodeValue = file_name\n   file_reader.readAsArrayBuffer(input_file)\n   }\n//\n// binary load handler\n//\nfunction png_binary_handler(event) {\n   //\n   // get DPI\n   //\n   // 8 header\n   // 4 len, 4 type, data, 4 crc\n   // pHYs 4 ppx, 4 ppy, 1 unit: 0 ?, 1 meter\n   // IEND\n   //\n   var units = ppx = ppy = 0\n   var buf = event.target.result\n   var view = new DataView(buf)\n   var ptr = 8\n   if (!((view.getUint8(1) == 80) && (view.getUint8(2) == 78) && (view.getUint8(3) == 71))) {\n      set_prompt(\"error: PNG header not found\")\n      return\n      }\n   while (1) {\n      var length = view.getUint32(ptr)\n      ptr += 4\n      var type = String.fromCharCode(\n         view.getUint8(ptr),view.getUint8(ptr+1),\n         view.getUint8(ptr+2),view.getUint8(ptr+3))\n      ptr += 4\n      if (type == \"pHYs\") {\n         ppx = view.getUint32(ptr)\n         ppy = view.getUint32(ptr + 4)\n         units = view.getUint8(ptr + 8)\n         }\n      if (type == \"IEND\")\n         break\n      ptr += length + 4\n      }\n   if (units == 0) {\n      set_prompt(\"no PNG units not found, assuming 72 DPI\")\n      ppx = 72*1000/25.4\n      }\n   dpi = ppx*25.4/1000\n   //\n   // read as URL for display\n   //\n   var file_reader = new FileReader()\n   file_reader.onload = png_URL_handler\n   file_reader.readAsDataURL(input_file)\n   }\n//\n// URL load handler\n//\nfunction png_URL_handler(event) {\n   var img = new Image()\n   img.setAttribute(\"src\",event.target.result)\n   img.onload = function() {\n      if (img.width > img.height) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-img.height/img.width)\n         var w = mod.canvas.width\n         var h = mod.canvas.width*img.height/img.width\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-img.width/img.height)\n         var y0 = 0\n         var w = mod.canvas.height*img.width/img.height\n         var h = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n         ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n         ctx.drawImage(img,x0,y0,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = img.width\n         ctx.canvas.height = img.height \n         ctx.drawImage(img,0,0)\n      mod.dpitext.value = dpi.toFixed(3)\n      mod.pxtext.nodeValue = img.width+' x '+img.height+' px'\n      mod.mmtext.nodeValue = (25.4*img.width/dpi).toFixed(3)\n         +' x '+(25.4*img.height/dpi).toFixed(3)+' mm'\n      mod.intext.nodeValue = (img.width/dpi).toFixed(3)\n         +' x '+(img.height/dpi).toFixed(3)+' in'\n      outputs.image.event()\n      outputs.imageInfo.event()\n      }\n   }\n//\n// invert image\n//\nfunction invert_image() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var h = mod.img.height\n   var w = mod.img.width\n   var ctx = mod.img.getContext(\"2d\")\n   var img = ctx.getImageData(0,0,w,h)\n   webworker.postMessage({\n      height:img.height,width:img.width,buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            buf[(h-1-row)*w*4+col*4+0] \n               = 255-buf[(h-1-row)*w*4+col*4+0] \n            buf[(h-1-row)*w*4+col*4+1] \n               = 255-buf[(h-1-row)*w*4+col*4+1] \n            buf[(h-1-row)*w*4+col*4+2] \n               = 255-buf[(h-1-row)*w*4+col*4+2] \n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"301.3025579837396","left":"291.64846255386124","filename":"undefined","inputs":{},"outputs":{}},"0.6488303557466412":{"definition":"//\n// image threshold\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'image threshold'\n//\n// initialization\n//\nvar init = function() {\n   mod.threshold.value = 0.5\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         threshold_image()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // threshold value\n   //\n   div.appendChild(document.createTextNode('threshold (0-1): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         threshold_image()\n         })\n      div.appendChild(input)\n      mod.threshold = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// threshold image\n//\nfunction threshold_image() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var t = parseFloat(mod.threshold.value)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,threshold:t,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var t = evt.data.threshold\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var r,g,b,a,i\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            r = buf[(h-1-row)*w*4+col*4+0] \n            g = buf[(h-1-row)*w*4+col*4+1] \n            b = buf[(h-1-row)*w*4+col*4+2] \n            a = buf[(h-1-row)*w*4+col*4+3] \n            i = (r+g+b)/(3*255)\n            if (a == 0)\n               val = 255\n            else if (i > t)\n               var val = 255\n            else\n               var val = 0\n            buf[(h-1-row)*w*4+col*4+0] = val\n            buf[(h-1-row)*w*4+col*4+1] = val\n            buf[(h-1-row)*w*4+col*4+2] = val\n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"173.3025579837396","left":"1334.6484625538612","filename":"undefined","inputs":{},"outputs":{}},"0.749132408760488":{"definition":"//\n// vectorize\n//    input is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'vectorize'\n//\n// initialization\n//\nvar init = function() {\n   mod.error.value = 1\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         vectorize()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   path:{type:'array',\n      event:function(){\n         mods.output(mod,'path',mod.path)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))   \n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // error value\n   //\n   div.appendChild(document.createTextNode('vector fit (pixels): '))\n   //div.appendChild(document.createElement('br'))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         vectorize()\n         })\n      div.appendChild(input)\n      mod.error = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      input.checked = true\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// vectorize\n//\nfunction vectorize() {\n   //\n   // draw path\n   //\n   function draw_path(path) {\n      window.URL.revokeObjectURL(url)\n      var svg = document.getElementById(mod.div.id+'svg')\n      svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n      var g = document.getElementById(mod.div.id+'g')\n      svg.removeChild(g)\n      var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n      g.setAttribute('id',mod.div.id+'g')\n      var h = mod.img.height\n      var w = mod.img.width\n      var xend = null\n      var yend = null\n      //\n      // loop over segments\n      //\n      for (var segment in path) {\n         if (path[segment].length > 1) {\n            if (xend != null) {\n               //\n               // draw connection from previous segment\n               //\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','red')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = xend\n               var y1 = yend\n               var x2 = path[segment][0][0]\n               var y2 = h-path[segment][0][1]-1\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','red')\n                  g.appendChild(triangle)\n                  }\n               }\n            //\n            // loop over points\n            //\n            for (var point = 1; point < path[segment].length; ++point) {\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','black')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = path[segment][point-1][0]\n               var y1 = h-path[segment][point-1][1]-1\n               var x2 = path[segment][point][0]\n               var y2 = h-path[segment][point][1]-1\n               xend = x2\n               yend = y2\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','black')\n                  g.appendChild(triangle)\n                  }\n               }\n            }\n         }\n      svg.appendChild(g)\n      }\n   //\n   // set up worker\n   //\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      webworker.terminate()\n      mod.path = evt.data.path\n      draw_path(mod.path)\n      outputs.path.event()\n      })\n   //\n   // call worker\n   //\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,sort:mod.sort.checked,\n      error:parseFloat(mod.error.value),\n      buffer:mod.input.data.buffer})\n   }\n//\n// vectorize worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var sort = evt.data.sort\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      var path = []\n      //\n      // edge follower\n      //\n      function follow_edges(row,col) {\n         if ((input[(h-1-row)*w*4+col*4+northsouth] != 0)\n            || (input[(h-1-row)*w*4+col*4+eastwest] != 0)) {\n            path[path.length] = [[col,row]]\n            while (1) {\n               if (input[(h-1-row)*w*4+col*4+northsouth] & north) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~north\n                  row += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+northsouth] & south) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~south\n                  row -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & east) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~east\n                  col += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & west) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~west\n                  col -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else\n                  break\n               }\n            }\n         }\n      //\n      // follow boundary starts\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         col = 0\n         follow_edges(row,col)\n         col = w-1\n         follow_edges(row,col)\n         }\n      for (var col = 1; col < (w-1); ++col) {\n         row = 0\n         follow_edges(row,col)\n         row = h-1      \n         follow_edges(row,col)\n         }\n      //\n      // follow interior paths\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         for (var col = 1; col < (w-1); ++col) {\n            follow_edges(row,col)\n            }\n         }\n      //\n      // vectorize path\n      //\n      var error = evt.data.error\n      var vecpath = []\n      for (var seg = 0; seg < path.length; ++seg) {\n         var x0 = path[seg][0][0]\n         var y0 = path[seg][0][1]\n         vecpath[vecpath.length] = [[x0,y0]]\n         var xsum = x0\n         var ysum = y0\n         var sum = 1\n         for (var pt = 1; pt < path[seg].length; ++pt) {\n            var xold = x\n            var yold = y\n            var x = path[seg][pt][0]\n            var y = path[seg][pt][1]\n            if (sum == 1) {\n               xsum += x\n               ysum += y\n               sum += 1\n               }\n            else {\n               var xmean = xsum/sum\n               var ymean = ysum/sum\n               var dx = xmean-x0\n               var dy = ymean-y0\n               var d = Math.sqrt(dx*dx+dy*dy)\n               var nx = dy/d\n               var ny = -dx/d\n               var l = Math.abs(nx*(x-x0)+ny*(y-y0))\n               if (l < error) {\n                  xsum += x\n                  ysum += y\n                  sum += 1\n                  }\n               else {\n                  vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [xold,yold]\n                  x0 = xold\n                  y0 = yold\n                  xsum = xold\n                  ysum = yold\n                  sum = 1\n                  }\n               }\n            if (pt == (path[seg].length-1)) {\n               vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [x,y]\n               }\n            }\n         }\n      //\n      // sort path\n      //\n      if ((vecpath.length > 0) && (sort == true)) {\n         var dmin = w*w+h*h\n         segmin = null\n         for (var seg = 0; seg < vecpath.length; ++seg) {\n            var x = vecpath[seg][0][0]\n            var y = vecpath[seg][0][0]\n            var d = x*x+y*y\n            if (d < dmin) {\n               dmin = d\n               segmin = seg\n               }\n            }\n         if (segmin != null) {\n            var sortpath = [vecpath[segmin]]\n            vecpath.splice(segmin,1)\n            }\n         while (vecpath.length > 0) {\n            var dmin = w*w+h*h\n            var x0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][0]\n            var y0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][1]\n            segmin = null\n            for (var seg = 0; seg < vecpath.length; ++seg) {\n               var x = vecpath[seg][0][0]\n               var y = vecpath[seg][0][1]\n               var d = (x-x0)*(x-x0)+(y-y0)*(y-y0)\n               if (d < dmin) {\n                  dmin = d\n                  segmin = seg\n                  }\n               }\n            if (segmin != null) {\n               sortpath[sortpath.length] = vecpath[segmin]\n               vecpath.splice(segmin,1)\n               }\n            }\n         }\n      else if ((vecpath.length > 0) && (sort == false))\n         sortpath = vecpath\n      else\n         sortpath = []\n      //\n      // return path\n      //\n      self.postMessage({path:sortpath})\n      })\n   }\n//\n// return values\n//\nreturn ({\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"606.3025579837396","left":"1391.6484625538612","filename":"undefined","inputs":{},"outputs":{}},"0.9308036593731321":{"definition":"//\n// save file\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'save file'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   file:{type:'object',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.contents = evt.detail.contents\n         save_file()\n         }}}\n//\n// outputs\n//\nvar outputs = {}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // info\n   //\n   var text = document.createTextNode('name:')\n      div.appendChild(text)\n      mod.nametext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('size:')\n      div.appendChild(text)\n      mod.sizetext = text\n   div.appendChild(document.createElement('br'))\n   }\n//\n// local functions\n//\nfunction save_file() {\n   var a = document.createElement('a')\n   a.setAttribute('href','data:text/plain;charset=utf-8,'+ \n      encodeURIComponent(mod.contents))\n   a.setAttribute('download',mod.name)\n   a.style.display = 'none'\n   document.body.appendChild(a)\n   a.click()\n   document.body.removeChild(a)\n   mod.nametext.nodeValue = 'name: '+mod.name\n   mods.fit(mod.div)\n   mod.sizetext.nodeValue = 'size: '+mod.contents.length\n   mods.fit(mod.div)\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"708.2066115702479","left":"847.0661157024794","filename":"modules/file/save","inputs":{},"outputs":{}},"0.3039514232113123":{"definition":"//\n// path to Gerber\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2021\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'path to Gerber'\n//\n// initialization\n//\nvar init = function() {\n   mod.fill.checked = false\n   }\n//\n// inputs\n//\nvar inputs = {\n   imageInfo:{type:'',\n      event:function(evt){\n         mod.imageInfo = evt.detail\n         }},\n   path:{type:'',\n      event:function(evt){\n         mod.path = evt.detail\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   Gerber:{type:'',\n      event:function(evt){\n         mods.output(mod,'Gerber',evt)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('plot'))\n      btn.addEventListener('click',function(){\n         plot()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('format:'))\n   div.appendChild(document.createElement('br'))\n   var formats = document.createElement('div');\n   formats.style.textAlign = 'left'\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'format'\n      input.id = mod.div.id+'fill'\n      formats.appendChild(input)\n   mod.fill = input\n   formats.appendChild(document.createTextNode('fill'))\n   formats.appendChild(document.createElement('br'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'format'\n      input.id = mod.div.id+'outline'\n      formats.appendChild(input)\n   mod.outline = input\n   formats.appendChild(document.createTextNode('outline'))\n   formats.appendChild(document.createElement('br'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'format'\n      input.id = mod.div.id+'outline'\n      input.disabled = true\n      formats.appendChild(input)\n   mod.drill = input\n   formats.appendChild(document.createTextNode('drill'))\n   div.appendChild(formats)\n   }\n//\n// local functions\n//\nfunction format(x) {\n   var s = x.toFixed(6)\n   s = s.substr(0,s.length-7)+s.substr(-6,6)\n   return s\n   }\n//\nfunction plot() {\n   var imgwidth = mod.imageInfo.width/parseFloat(mod.imageInfo.dpi)\n   var imgheight = mod.imageInfo.height/parseFloat(mod.imageInfo.dpi)\n   var x,y\n   //\n   str = ''\n   str += \"%MOIN*%\\n\" // inch units\n   str += \"%LPD*%\\n\" // layer dark\n   str += \"%FSLAX66Y66*%\\n\" // format absolute 6.6\n   str += \"G01*\\n\" // linear interpolation\n   //\n   if (mod.fill.checked == true) {\n      for (var seg = 0; seg < mod.path.length; ++seg) {\n         str += \"G36*\\n\"\n         x = imgwidth*mod.path[seg][0][0]/(mod.imageInfo.width-1)\n         y = imgheight*mod.path[seg][0][1]/(mod.imageInfo.height-1)\n         str += 'X'+format(x)+'Y'+format(y)+'D02*\\n'\n         for (var pt = 1; pt < mod.path[seg].length; ++pt) {\n            var x = imgwidth*mod.path[seg][pt][0]/(mod.imageInfo.width-1)\n            var y = imgheight*mod.path[seg][pt][1]/(mod.imageInfo.height-1)\n            str += 'X'+format(x)+'Y'+format(y)+'D01*\\n'\n            }\n         x = imgwidth*mod.path[seg][0][0]/(mod.imageInfo.width-1)\n         y = imgheight*mod.path[seg][0][1]/(mod.imageInfo.height-1)\n         str += 'X'+format(x)+'Y'+format(y)+'D01*\\n'\n         str += \"G37*\\n\"\n         }\n      str += \"M02*\\n\"\n      var obj = {}\n      obj.type = 'file'\n      obj.name = mod.imageInfo.name+'.gtl'\n      obj.contents = str\n      outputs.Gerber.event(obj)\n      }\n   else if (mod.outline.checked == true) {\n      str += \"%ADD10C,0.001*%\\n\"\n      str += \"D10*\\n\"\n      for (var seg = 0; seg < mod.path.length; ++seg) {\n         x = imgwidth*mod.path[seg][0][0]/(mod.imageInfo.width-1)\n         y = imgheight*mod.path[seg][0][1]/(mod.imageInfo.height-1)\n         str += 'X'+format(x)+'Y'+format(y)+'D02*\\n'\n         for (var pt = 1; pt < mod.path[seg].length; ++pt) {\n            var x = imgwidth*mod.path[seg][pt][0]/(mod.imageInfo.width-1)\n            var y = imgheight*mod.path[seg][pt][1]/(mod.imageInfo.height-1)\n            str += 'X'+format(x)+'Y'+format(y)+'D01*\\n'\n            }\n         x = imgwidth*mod.path[seg][0][0]/(mod.imageInfo.width-1)\n         y = imgheight*mod.path[seg][0][1]/(mod.imageInfo.height-1)\n         str += 'X'+format(x)+'Y'+format(y)+'D01*\\n'\n         }\n      str += \"M02*\\n\"\n      var obj = {}\n      obj.type = 'file'\n      obj.name = mod.imageInfo.name+'.gko'\n      obj.contents = str\n      outputs.Gerber.event(obj)\n      }\n   }\n/*\nD11*\nX1000Y1000D02*\nX1000Y2000D01*\nX2000Y2000D01*\nX1500Y1000D01*\nM02*\n*/\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n\n","top":"429.0133288253729","left":"811.9563594751694","filename":"modules/path/formats/gerber","inputs":{},"outputs":{}}},"links":["{\"source\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.08127540142793499\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.749132408760488\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.08127540142793499\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3039514232113123\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.749132408760488\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"path\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3039514232113123\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"path\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.3039514232113123\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"Gerber\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.9308036593731321\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"file\\\"}\"}"]}
\ No newline at end of file