diff --git a/files.html b/files.html
index 716b240713126780a554429a673877baceed711e..9e7b86c26bbe930f10b03aaf41a93629e28ee221 100644
--- a/files.html
+++ b/files.html
@@ -19,7 +19,6 @@
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./js/files.js'>files.js</a><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./js/load.js'>load.js</a><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./js/mods.js'>mods.js</a><br>
-<i>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;node_modules</i><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./js/printserver.js'>printserver.js</a><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./js/serialserver.js'>serialserver.js</a><br>
 <i>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;three.js</i><br>
@@ -134,6 +133,7 @@
 <i>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;programs</i><br>
 <i>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;image</i><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./programs/image/function'>function</a><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./programs/image/palette%20mask%20raster'>palette mask raster</a><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./programs/image/palette%20mask%20vector'>palette mask vector</a><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href='./programs/index.html'>index.html</a><br>
 <i>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;iterate</i><br>
diff --git a/modules/image/raster mask b/modules/image/raster mask
index 14803e72f452854d6d1b05992953bbdcc4ae0e21..dc26f1d76221c492f93be777a7cb06ca8f5557cb 100644
--- a/modules/image/raster mask	
+++ b/modules/image/raster mask	
@@ -25,7 +25,6 @@ var name = 'raster mask'
 // initialization
 //
 var init = function() {
-   mod.fill.checked = true
    }
 //
 // inputs
@@ -49,7 +48,9 @@ var inputs = {
          }},
    mask:{type:'RGBA',
       event:function(evt){
-         make_mask(evt.detail)
+         var ctx = mod.convert.getContext("2d")
+         ctx.putImageData(evt.detail,0,0)
+         make_mask()
          }}}
 //
 // outputs
@@ -96,16 +97,6 @@ var interface = function(div){
          })
       div.appendChild(btn)
    div.appendChild(document.createElement('br'))
-   //
-   // fill
-   //
-   div.appendChild(document.createTextNode('fill masks: '))
-   var input = document.createElement('input')
-      input.type = 'checkbox'
-      input.id = mod.div.id+'fill'
-      div.appendChild(input)
-      mod.fill = input
-   div.appendChild(document.createElement('br'))
    }
 //
 // local functions
@@ -113,11 +104,11 @@ var interface = function(div){
 //
 // make_mask
 //
-function make_mask(path) {
+function make_mask() {
    //
    // save mask
    //
-   save_mask(path)
+   save_mask()
    //
    // check for next mask
    //
@@ -139,7 +130,7 @@ function make_mask(path) {
 //
 // save_mask
 //
-function save_mask(path) {
+function save_mask(mask) {
    //
    // create SVG
    //
@@ -231,12 +222,9 @@ function save_mask(path) {
       text.setAttribute('dy','.2')
       text.textContent = name
       svg.appendChild(text)
-      
    //
    // raster mask
    //
-   var ctx = mod.convert.getContext("2d")
-   ctx.putImageData(imgdata,0,0)
    var href = mod.convert.toDataURL()
    var img = document.createElementNS(svgNS,'image')
       img.setAttribute('id',mod.div.id+'svgimg')
@@ -246,41 +234,6 @@ function save_mask(path) {
       img.setAttribute('height',imgheight)
       img.setAttributeNS('http://www.w3.org/1999/xlink','href',href)
       svg.appendChild(img)
-
-   //
-   // vector mask
-   //
-   var g = document.createElementNS(svgNS,'g')
-      svg.appendChild(g)
-   for (var seg = 0; seg < path.length; ++seg) {
-      var points = ''
-      for (var pt = 0; pt < path[seg].length; ++ pt) {
-         var x = 1.5+imgwidth*path[seg][pt][0]/(mod.image.width-1)
-         var y = 1.5+imgheight*(1-path[seg][pt][1]/(mod.image.height-1))
-         points += x+','+y+' '
-         }
-      var x = 1.5+imgwidth*path[seg][0][0]/(mod.image.width-1)
-      var y = 1.5+imgheight*(1-path[seg][0][1]/(mod.image.height-1))
-      points += x+','+y+' '
-      if (mod.fill.checked == true) {
-         var polyline = document.createElementNS(svgNS,'polyline')
-            polyline.setAttribute('stroke','none')
-            polyline.setAttribute('fill','black')
-            polyline.setAttribute('fill-rule','evenodd')
-            polyline.setAttribute('points',points)
-            g.appendChild(polyline)
-         }
-      else {
-         var polyline = document.createElementNS(svgNS,'polyline')
-            polyline.setAttribute('stroke','black')
-            polyline.setAttribute('stroke-width','0.01')
-            polyline.setAttribute('stroke-linecap','round')
-            polyline.setAttribute('fill','none')
-            polyline.setAttribute('points',points)
-            g.appendChild(polyline)
-         }
-      }
-
    //
    // file
    //
diff --git a/programs/image/palette mask raster b/programs/image/palette mask raster
new file mode 100644
index 0000000000000000000000000000000000000000..9590f9252524f007f40a840556d5365f9a1f1646
--- /dev/null
+++ b/programs/image/palette mask raster	
@@ -0,0 +1 @@
+{"modules":{"0.5648025145147616":{"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 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 = '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":"102","left":"204","inputs":{},"outputs":{}},"0.3959513260329336":{"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   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"97","left":"1630","inputs":{},"outputs":{}},"0.3603827322636355":{"definition":"//\n// image palette\n//    todo: linear time palette search\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2017\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 = 'image palette'\n//\n// initialization\n//\nvar init = function() {\n   mod.palette.value = '[[255,255,255],\\n[0,0,0],\\n[255,0,0],\\n[0,255,0],\\n[0,0,255]]'\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         var ctx = mod.convert.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         show_palette()\n         }\n      },\n   palette:{type:'text',\n      event:function(evt){\n         mod.palette.value = evt.detail\n         show_palette()\n         }\n      }\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   palette:{type:'text',\n      event:function(){\n         mods.output(mod,'palette',mod.palette.value)\n         }}}\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 conversion canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.convert = canvas\n   //\n   // view\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.createElement('br'))\n   //\n   // image \n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('original')\n            span.appendChild(text)\n         span.style.fontWeight = 'normal'\n         btn.appendChild(span)\n         mod.originalspan = span\n      btn.addEventListener('click',function(){\n         mod.originalspan.style.fontWeight = 'bold'\n         mod.palettespan.style.fontWeight = 'normal'\n         show_original()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createTextNode(' image '))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('palette')\n            span.appendChild(text)\n         span.style.fontWeight = 'bold'\n         btn.appendChild(span)\n         mod.palettespan = span\n      btn.addEventListener('click',function(){\n         mod.originalspan.style.fontWeight = 'normal'\n         mod.palettespan.style.fontWeight = 'bold'\n         show_palette()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // palette\n   //\n   div.appendChild(document.createTextNode('palette'))\n   div.appendChild(document.createElement('br'))\n   var text = document.createElement('textarea')\n      text.setAttribute('rows',mods.ui.rows)\n      text.setAttribute('cols',mods.ui.cols)\n      div.appendChild(text)\n      mod.palette = text\n   div.appendChild(document.createElement('br'))\n   }\n//\n// local functions\n//\n// show_original\n//\nfunction show_original() {\n   var h = mod.img.height\n   var w = mod.img.width\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,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   }\n//\n// show palette\n//\nfunction show_palette() {\n   var blob = new Blob(['('+palette_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.palette.event()\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 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   var palette = JSON.parse(mod.palette.value)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,palette:palette,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\n//\n// palette worker\n//    todo: sort palette\n//\nfunction palette_worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var palette = evt.data.palette\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var r,g,b,a,rc,gc,bc\n      var cmin,dmin,d\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            dmin = Number.MAX_VALUE\n            for (color = 0; color < palette.length; ++color) {\n               rc = palette[color][0]\n               gc = palette[color][1]\n               bc = palette[color][2]\n               d = Math.sqrt(\n                  (rc-r)*(rc-r)+\n                  (gc-g)*(gc-g)+\n                  (bc-b)*(bc-b))\n               if (d < dmin) {\n                  dmin = d\n                  cmin = color\n                  }\n               }\n            buf[(h-1-row)*w*4+col*4+0] = palette[cmin][0]\n            buf[(h-1-row)*w*4+col*4+1] = palette[cmin][1]\n            buf[(h-1-row)*w*4+col*4+2] = palette[cmin][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":"235","left":"614","inputs":{},"outputs":{}},"0.2403743422209378":{"definition":"//\n// read text\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2017\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 = 'read text'\n//\n// initialization\n//\nvar init = function() {\n   mod.text.value = \"load palette\"\n   }\n//\n// inputs\n//\nvar inputs = {\n   text:{type:'',\n      event:function(evt) {\n         mod.text.value = evt.detail\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   text:{type:'',\n      event:function(){\n         mods.output(mod,'text',mod.text.value)}}}\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         text_read_handler()\n         })\n      div.appendChild(file)\n      mod.file = file\n   //\n   // text\n   //\n   var text = document.createElement('textarea')\n      text.setAttribute('rows',mods.ui.rows)\n      text.setAttribute('cols',mods.ui.cols)\n      text.addEventListener('input',function() {\n         outputs.text.event()\n         })\n      div.appendChild(text)\n      mod.text = text\n   div.appendChild(document.createElement('br'))\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 text 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//\n// local functions\n//\n// read handler\n//\nfunction text_read_handler(event) {\n   //\n   // read as text\n   //\n   var file_reader = new FileReader()\n   file_reader.onload = text_load_handler\n   var input_file = mod.file.files[0]\n   file_reader.readAsText(input_file)\n   }\n//\n// load handler\n//\nfunction text_load_handler(event) {\n   mod.text.value = event.target.result\n   outputs.text.event()\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":"681","left":"212","inputs":{},"outputs":{}},"0.10571271239124669":{"definition":"//\n// image color separation\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2017\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 = 'color separation'\n//\n// initialization\n//\nvar init = function() {\n   mod.input = null\n   mod.color.value = '[255,0,0]'\n   mod.fill.checked = true\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         var ctx = mod.convert.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         show_separation()\n         }\n      },\n   color:{type:'RGB',\n      event:function(evt){\n         mod.color.value = JSON.stringify(evt.detail)\n         }\n      }\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 conversion canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.convert = canvas\n   //\n   // view\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.createElement('br'))\n   //\n   // image \n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('original')\n            span.appendChild(text)\n         span.style.fontWeight = 'normal'\n         btn.appendChild(span)\n         mod.originalspan = span\n      btn.addEventListener('click',function(){\n         mod.originalspan.style.fontWeight = 'bold'\n         mod.palettespan.style.fontWeight = 'normal'\n         show_original()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createTextNode(' image '))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('separation')\n            span.appendChild(text)\n         span.style.fontWeight = 'bold'\n         btn.appendChild(span)\n         mod.palettespan = span\n      btn.addEventListener('click',function(){\n         mod.originalspan.style.fontWeight = 'normal'\n         mod.palettespan.style.fontWeight = 'bold'\n         show_separation()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // color\n   //\n   div.appendChild(document.createTextNode('color: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 10\n      div.appendChild(input)\n      mod.color = input\n   div.appendChild(document.createElement('br'))\n   //\n   // edges\n   //\n   div.appendChild(document.createTextNode('fill edges: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'fill'\n      div.appendChild(input)\n      mod.fill = input\n   div.appendChild(document.createElement('br'))\n   }\n//\n// local functions\n//\n// show_original\n//\nfunction show_original() {\n   var h = mod.img.height\n   var w = mod.img.width\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,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   }\n//\n// show separation\n//\nfunction show_separation() {\n   var blob = new Blob(['('+separation_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 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   var color = JSON.parse(mod.color.value)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,color:color,\n      fill:mod.fill.checked,buffer:img.data.buffer},\n      [img.data.buffer])\n   }\n//\n// separation worker\n//\nfunction separation_worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var color = evt.data.color\n      var fill = evt.data.fill\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var r,g,b,a,rc,gc,bc\n      var cmin,dmin,d\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            rc = color[0]\n            gc = color[1]\n            bc = color[2]\n            if ((rc == r) && (gc == g) && (bc == b)) {\n               buf[(h-1-row)*w*4+col*4+0] = 255\n               buf[(h-1-row)*w*4+col*4+1] = 255\n               buf[(h-1-row)*w*4+col*4+2] = 255\n               buf[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               buf[(h-1-row)*w*4+col*4+0] = 0\n               buf[(h-1-row)*w*4+col*4+1] = 0\n               buf[(h-1-row)*w*4+col*4+2] = 0\n               buf[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      if (fill == true) {\n         for (var row = 0; row < h; ++row) {\n            col = 0\n            buf[(h-1-row)*w*4+col*4+0] = 0\n            buf[(h-1-row)*w*4+col*4+1] = 0\n            buf[(h-1-row)*w*4+col*4+2] = 0\n            buf[(h-1-row)*w*4+col*4+3] = 255\n            col = w-1\n            buf[(h-1-row)*w*4+col*4+0] = 0\n            buf[(h-1-row)*w*4+col*4+1] = 0\n            buf[(h-1-row)*w*4+col*4+2] = 0\n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         for (var col = 0; col < w; ++col) {\n            row = 0\n            buf[(h-1-row)*w*4+col*4+0] = 0\n            buf[(h-1-row)*w*4+col*4+1] = 0\n            buf[(h-1-row)*w*4+col*4+2] = 0\n            buf[(h-1-row)*w*4+col*4+3] = 255\n            row = h-1\n            buf[(h-1-row)*w*4+col*4+0] = 0\n            buf[(h-1-row)*w*4+col*4+1] = 0\n            buf[(h-1-row)*w*4+col*4+2] = 0\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":"349","left":"1131","inputs":{},"outputs":{}},"0.5509389792243394":{"definition":"//\n// raster mask\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2017\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 = 'raster mask'\n//\n// initialization\n//\nvar init = function() {\n   mod.fill.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   imageInfo:{type:'object',\n      event:function(evt){\n         mod.imageInfo = evt.detail\n         }},\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.image = evt.detail\n         mod.labelspan.style.fontWeight = 'bold'         \n         var ctx = mod.convert.getContext(\"2d\")\n         ctx.canvas.width = mod.image.width\n         ctx.canvas.height = mod.image.height \n         }},\n   palette:{type:'text',\n      event:function(evt){\n         mod.palette = JSON.parse(evt.detail)\n         }},\n   mask:{type:'RGBA',\n      event:function(evt){\n         var ctx = mod.convert.getContext(\"2d\")\n         ctx.putImageData(evt.detail,0,0)\n         make_mask()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         mods.output(mod,'image',mod.image)\n         }},\n   color:{type:'RGB',\n      event:function(evt){\n         mods.output(mod,'color',evt)\n         }},\n   SVG:{type:'file',\n      event:function(evt){\n         mods.output(mod,'SVG',evt)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // off-screen conversion canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.convert = canvas\n   //\n   // button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('calculate masks')\n            mod.label = text\n            span.appendChild(text)\n         mod.labelspan = span\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         mod.index = 0\n         outputs.color.event(mod.palette[mod.index])\n         outputs.image.event()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // fill\n   //\n   div.appendChild(document.createTextNode('fill masks: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'fill'\n      div.appendChild(input)\n      mod.fill = input\n   div.appendChild(document.createElement('br'))\n   }\n//\n// local functions\n//\n//\n// make_mask\n//\nfunction make_mask() {\n   //\n   // save mask\n   //\n   save_mask()\n   //\n   // check for next mask\n   //\n   if (mod.index < (mod.palette.length-1)) {\n      //\n      // yes, output\n      //\n      mod.index += 1\n      outputs.color.event(mod.palette[mod.index])\n      outputs.image.event()\n      }\n   else {\n      //\n      // no, done\n      //\n      mod.labelspan.style.fontWeight = 'normal' \n      }        \n   }\n//\n// save_mask\n//\nfunction save_mask(mask) {\n   //\n   // create SVG\n   //\n   var imgwidth = mod.image.width/parseFloat(mod.imageInfo.dpi)\n   var imgheight = mod.image.height/parseFloat(mod.imageInfo.dpi)\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n      svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n         \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n      svg.setAttribute('width',(3+imgwidth)+'in')\n      svg.setAttribute('height',(3+imgheight)+'in')\n      svg.style.backgroundColor = 'rgb(255,255,255)'\n      svg.setAttribute('viewBox','0 0 '+(3+imgwidth)+' '+(3+imgheight))\n   //\n   // background\n   //\n   var rect = document.createElementNS(svgNS,'rect')\n      rect.setAttribute('x','0')\n      rect.setAttribute('y','0')\n      rect.setAttribute('width',3+imgwidth)\n      rect.setAttribute('height',3+imgheight)\n      rect.setAttribute('stroke','none')\n      rect.setAttribute('fill','white')\n      svg.appendChild(rect)\n   //\n   // registration\n   //\n   var g = document.createElementNS(svgNS,'g')\n      svg.appendChild(g)\n   var polyline = document.createElementNS(svgNS,'polyline')\n      polyline.setAttribute('stroke','red')\n      polyline.setAttribute('stroke-width','0.01')\n      polyline.setAttribute('stroke-linecap','round')\n      polyline.setAttribute('fill','none')\n      polyline.setAttribute('points','0.5,0.5 '+\n         '0.5,'+(imgheight+2.5)+' '+\n         (imgwidth+2.5)+','+(imgheight+2.5)+' '+\n         (imgwidth+2.5)+',0.9 '+\n         (imgwidth+2.1)+',0.5 '+\n         '0.5,0.5')\n      g.appendChild(polyline)\n   var circle = document.createElementNS(svgNS,'circle')\n      circle.setAttribute('cx','1')\n      circle.setAttribute('cy','1')\n      circle.setAttribute('r','0.125')\n      circle.setAttribute('stroke','red')\n      circle.setAttribute('stroke-width','0.01')\n      circle.setAttribute('fill','none')\n      g.appendChild(circle)\n   var circle = document.createElementNS(svgNS,'circle')\n      circle.setAttribute('cx','1')\n      circle.setAttribute('cy',(2+imgheight))\n      circle.setAttribute('r','0.125')\n      circle.setAttribute('stroke','red')\n      circle.setAttribute('stroke-width','0.01')\n      circle.setAttribute('fill','none')\n      g.appendChild(circle)\n   var circle = document.createElementNS(svgNS,'circle')\n      circle.setAttribute('cx',(2+imgwidth))\n      circle.setAttribute('cy',(2+imgheight))\n      circle.setAttribute('r','0.125')\n      circle.setAttribute('stroke','red')\n      circle.setAttribute('stroke-width','0.01')\n      circle.setAttribute('fill','none')\n      g.appendChild(circle)\n   var circle = document.createElementNS(svgNS,'circle')\n      circle.setAttribute('cx',(2+imgwidth))\n      circle.setAttribute('cy','1')\n      circle.setAttribute('r','0.125')\n      circle.setAttribute('stroke','red')\n      circle.setAttribute('stroke-width','0.01')\n      circle.setAttribute('fill','none')\n      g.appendChild(circle)\n   //\n   // name\n   //\n   var name = mod.imageInfo.name+'.'+mod.palette[mod.index][0]\n   name += '.'+mod.palette[mod.index][1]\n   name += '.'+mod.palette[mod.index][2]\n   if (mod.palette[mod.index][3] != undefined)\n      name += '.'+mod.palette[mod.index][3]\n   var text = document.createElementNS(svgNS,'text')\n      text.setAttribute('id',mod.div.id+'svgtext')\n      text.setAttribute('x',(3+imgwidth)/2)\n      text.setAttribute('y','1')\n      text.setAttribute('fill','red')\n      text.setAttribute('font-size','.5')\n      text.setAttribute('text-anchor','middle')\n      text.setAttribute('dy','.2')\n      text.textContent = name\n      svg.appendChild(text)\n   //\n   // raster mask\n   //\n   var href = mod.convert.toDataURL()\n   var img = document.createElementNS(svgNS,'image')\n      img.setAttribute('id',mod.div.id+'svgimg')\n      img.setAttribute('x','1.5')\n      img.setAttribute('y','1.5')\n      img.setAttribute('width',imgwidth)\n      img.setAttribute('height',imgheight)\n      img.setAttributeNS('http://www.w3.org/1999/xlink','href',href)\n      svg.appendChild(img)\n   //\n   // file\n   //\n   var obj = {}\n   obj.type = 'file'\n   obj.name = name+'.svg'\n   var xml = new XMLSerializer().serializeToString(svg)\n   obj.contents = xml\n   outputs.SVG.event(obj)\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\n","top":"94","left":"1160","inputs":{},"outputs":{}}},"links":["{\"source\":\"{\\\"id\\\":\\\"0.5648025145147616\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3603827322636355\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.2403743422209378\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"text\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3603827322636355\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"palette\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.3603827322636355\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.5509389792243394\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.3603827322636355\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"palette\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.5509389792243394\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"palette\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.5648025145147616\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.5509389792243394\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.5509389792243394\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.10571271239124669\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.5509389792243394\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"color\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.10571271239124669\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"color\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.10571271239124669\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.5509389792243394\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"mask\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.5509389792243394\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"SVG\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3959513260329336\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"file\\\"}\"}"]}
\ No newline at end of file
diff --git a/programs/index.html b/programs/index.html
index 724e7497533806888b36156d73a5137da235ea66..934ea5fcbf22bf5fa8ab189923cdbe1fcdd08493 100644
--- a/programs/index.html
+++ b/programs/index.html
@@ -11,6 +11,7 @@
    </script>
    <i>image</i><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="javascript:handler('programs/image/function')">function</a><br>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="javascript:handler('programs/image/palette%20mask%20raster')">palette mask raster</a><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="javascript:handler('programs/image/palette%20mask%20vector')">palette mask vector</a><br>
 <i>iterate</i><br>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="javascript:handler('programs/iterate/z%20theta%20scan')">z theta scan</a><br>