[
  {
    "path": ".classpath",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<classpath>\n\t<classpathentry kind=\"src\" path=\"\"/>\n\t<classpathentry kind=\"con\" path=\"org.eclipse.jdt.launching.JRE_CONTAINER\"/>\n\t<classpathentry kind=\"output\" path=\"bin\"/>\n</classpath>\n"
  },
  {
    "path": ".gitattributes",
    "content": "# Auto detect text files and perform LF normalization\n* text=auto\n\n# Custom for Visual Studio\n*.cs     diff=csharp\n\n# Standard to msysgit\n*.doc\t diff=astextplain\n*.DOC\t diff=astextplain\n*.docx diff=astextplain\n*.DOCX diff=astextplain\n*.dot  diff=astextplain\n*.DOT  diff=astextplain\n*.pdf  diff=astextplain\n*.PDF\t diff=astextplain\n*.rtf\t diff=astextplain\n*.RTF\t diff=astextplain\n"
  },
  {
    "path": ".gitignore",
    "content": "# Windows image file caches\nThumbs.db\nehthumbs.db\n\n# Folder config file\nDesktop.ini\n\n# Recycle Bin used on file shares\n$RECYCLE.BIN/\n\n# Windows Installer files\n*.cab\n*.msi\n*.msm\n*.msp\n\n# Windows shortcuts\n*.lnk\n\n# =========================\n# Operating System Files\n# =========================\n\n# OSX\n# =========================\n\n.DS_Store\n.AppleDouble\n.LSOverride\n\n# Thumbnails\n._*\n\n# Files that might appear on external disk\n.Spotlight-V100\n.Trashes\n\n# Directories potentially created on remote AFP share\n.AppleDB\n.AppleDesktop\nNetwork Trash Folder\nTemporary Items\n.apdisk\n/bin"
  },
  {
    "path": ".project",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<projectDescription>\n\t<name>jankovicsandras.imagetracer-java</name>\n\t<comment></comment>\n\t<projects>\n\t</projects>\n\t<buildSpec>\n\t\t<buildCommand>\n\t\t\t<name>org.eclipse.jdt.core.javabuilder</name>\n\t\t\t<arguments>\n\t\t\t</arguments>\n\t\t</buildCommand>\n\t</buildSpec>\n\t<natures>\n\t\t<nature>org.eclipse.jdt.core.javanature</nature>\n\t</natures>\n</projectDescription>\n"
  },
  {
    "path": ".settings/org.eclipse.core.resources.prefs",
    "content": "eclipse.preferences.version=1\nencoding//jankovicsandras/imagetracer/ImageTracer.java=UTF-8\n"
  },
  {
    "path": ".settings/org.eclipse.jdt.core.prefs",
    "content": "eclipse.preferences.version=1\norg.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled\norg.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8\norg.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve\norg.eclipse.jdt.core.compiler.compliance=1.8\norg.eclipse.jdt.core.compiler.debug.lineNumber=generate\norg.eclipse.jdt.core.compiler.debug.localVariable=generate\norg.eclipse.jdt.core.compiler.debug.sourceFile=generate\norg.eclipse.jdt.core.compiler.problem.assertIdentifier=error\norg.eclipse.jdt.core.compiler.problem.enumIdentifier=error\norg.eclipse.jdt.core.compiler.source=1.8\n"
  },
  {
    "path": "LICENSE",
    "content": "This is free and unencumbered software released into the public domain.\n\nAnyone is free to copy, modify, publish, use, compile, sell, or\ndistribute this software, either in source code form or as a compiled\nbinary, for any purpose, commercial or non-commercial, and by any\nmeans.\n\nIn jurisdictions that recognize copyright laws, the author or authors\nof this software dedicate any and all copyright interest in the\nsoftware to the public domain. We make this dedication for the benefit\nof the public at large and to the detriment of our heirs and\nsuccessors. We intend this dedication to be an overt act of\nrelinquishment in perpetuity of all present and future rights to this\nsoftware under copyright law.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR\nOTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\nOTHER DEALINGS IN THE SOFTWARE.\n\nFor more information, please refer to <http://unlicense.org>\n\n"
  },
  {
    "path": "README.md",
    "content": "# imagetracerjava\n![alt Bitmap to Svg](docimages/s1.png)\n\nSimple raster image tracer and vectorizer written in Java for desktop. See https://github.com/jankovicsandras/imagetracerandroid for the Android version.\n\nby András Jankovics\n\nThis is a port of imagetracer.js: https://github.com/jankovicsandras/imagetracerjs\n\n### Check this out for a refactored version with better color quantization algorithm: https://github.com/miguelemosreverte/imagetracerjava\n\n### Check this out for a C++ port: https://github.com/dov/ImageTracerCpp\n\n### 1.1.2\n\n- minor bugfixes\n- lookup based ```pathscan()```\n\n### 1.1.1\n\n- Bugfix: CSS3 RGBA output in SVG was technically incorrect (however supported by major browsers), so this is changed. [More info](https://stackoverflow.com/questions/6042550/svg-fill-color-transparency-alpha)\n- transparency support: alpha is not discarded now, it is given more weight in color quantization\n- new ```options.roundcoords``` : rounding coordinates to a given decimal place. This can reduce SVG length significantly (>20%) with minor loss of precision.\n- new ```options.desc``` : setting this to false will turn off path descriptions, reducing SVG length.\n- new ```options.viewbox``` : setting this to true will use viewBox instead of exact width and height\n- new ```options.colorsampling``` : color quantization will sample the colors now by default, can be turned off.\n- new ```options.blurradius``` : setting this to 1..5 will preprocess the image with a selective Gaussian blur with ```options.blurdelta``` treshold. This can filter noise and improve quality.\n- ```IndexedImage``` has width and height\n- ```getsvgstring()``` needs now only ```IndexedImage``` (tracedata) and ```options``` as parameters\n- ```colorquantization()``` needs now only ```imgd```, ```palette``` and ```options``` as parameters\n- background field is removed from the results of color quantization \n\n### Running as a standalone program \n\nWarning: if the outfilename parameter is not specified, then this will overwrite <filename>.svg .\n\nBasic usage: \n```bash\njava -jar ImageTracer.jar smiley.png\n```\n\nWith options:\n```bash\njava -jar ImageTracer.jar smiley.png outfilename output.svg ltres 1 qtres 1 pathomit 8 colorsampling 1 numberofcolors 16 mincolorratio 0.02 colorquantcycles 3 scale 1 simplifytolerance 0 roundcoords 1 lcpr 0 qcpr 0 desc 1 viewbox 0 blurradius 0 blurdelta 20\n```\n\n### Including in Java projects\nAdd ImageTracer.jar to your build path, import, then use the static methods:\n```java\nimport jankovicsandras.imagetracer.ImageTracer;\n\n...\n\nImageTracer.saveString(\n\t\t\t\t\"output.svg\" ,\n\t\t\t\tImageTracer.imageToSVG(\"input.jpg\",null,null)\n);\n```\n\nWith options and palette\n```java\n// Options\nHashMap<String,Float> options = new HashMap<String,Float>();\n\n// Tracing\noptions.put(\"ltres\",1f);\noptions.put(\"qtres\",1f);\noptions.put(\"pathomit\",8f);\n\n// Color quantization\noptions.put(\"colorsampling\",1f); // 1f means true ; 0f means false: starting with generated palette\noptions.put(\"numberofcolors\",16f);\noptions.put(\"mincolorratio\",0.02f);\noptions.put(\"colorquantcycles\",3f);\n\n// SVG rendering\noptions.put(\"scale\",1f);\noptions.put(\"roundcoords\",1f); // 1f means rounded to 1 decimal places, like 7.3 ; 3f means rounded to 3 places, like 7.356 ; etc.\noptions.put(\"lcpr\",0f);\noptions.put(\"qcpr\",0f);\noptions.put(\"desc\",1f); // 1f means true ; 0f means false: SVG descriptions deactivated\noptions.put(\"viewbox\",0f); // 1f means true ; 0f means false: fixed width and height\n\n// Selective Gauss Blur\noptions.put(\"blurradius\",0f); // 0f means deactivated; 1f .. 5f : blur with this radius\noptions.put(\"blurdelta\",20f); // smaller than this RGB difference will be blurred\n\n// Palette\n// This is an example of a grayscale palette\n// please note that signed byte values [ -128 .. 127 ] will be converted to [ 0 .. 255 ] in the getsvgstring function\nbyte[][] palette = new byte[8][4];\nfor(int colorcnt=0; colorcnt < 8; colorcnt++){\n\tpalette[colorcnt][0] = (byte)( -128 + colorcnt * 32); // R\n\tpalette[colorcnt][1] = (byte)( -128 + colorcnt * 32); // G\n\tpalette[colorcnt][2] = (byte)( -128 + colorcnt * 32); // B\n\tpalette[colorcnt][3] = (byte)127; \t\t      // A\n}\n\nImageTracer.saveString(\n\t\t\t\t\"output.svg\" ,\n\t\t\t\tImageTracer.imageToSVG(\"input.jpg\",options,palette)\n);\n```\n\n### Deterministic output\nSee [options for deterministic tracing](https://github.com/jankovicsandras/imagetracerjava/blob/master/deterministic.md)\n\n\n### Main Functions\n|Function name|Arguments|Returns|\n|-------------|---------|-------|\n|```imageToSVG```|```String filename, HashMap<String,Float> options /*can be null*/, byte [][] palette /*can be null*/```|```String /*SVG content*/```|\n|```imageToSVG```|```BufferedImage image, HashMap<String,Float> options /*can be null*/, byte [][] palette /*can be null*/```|```String /*SVG content*/```|\n|```imagedataToSVG```|```ImageData imgd, HashMap<String,Float> options /*can be null*/, byte [][] palette /*can be null*/```|```String /*SVG content*/```|\n|```imageToTracedata```|```String filename, HashMap<String,Float> options /*can be null*/, byte [][] palette /*can be null*/```|```IndexedImage /*read the source for details*/```|\n|```imageToTracedata```|```BufferedImage image, HashMap<String,Float> options /*can be null*/, byte [][] palette /*can be null*/```|```IndexedImage /*read the source for details*/```|\n|```imagedataToTracedata```|```ImageData imgd, HashMap<String,Float> options /*can be null*/, byte [][] palette /*can be null*/```|```IndexedImage /*read the source for details*/```|\n\n\t\n#### Helper Functions\n|Function name|Arguments|Returns|\n|-------------|---------|-------|\n|```saveString```|```String filename, String str```|```void```|\n|```loadImageData```|```String filename```|```ImageData /*read the source for details*/```|\n|```loadImageData```|```BufferedImage image```|```ImageData /*read the source for details*/```|\n\n```ImageData``` is similar to [ImageData](https://developer.mozilla.org/en-US/docs/Web/API/ImageData) here.\n\nThere are more functions for advanced users, read the source if you are interested. :)\n\t\n### Options\n|Option name|Default value|Meaning|\n|-----------|-------------|-------|\n|```ltres```|```1f```|Error treshold for straight lines.|\n|```qtres```|```1f```|Error treshold for quadratic splines.|\n|```pathomit```|```8f```|Edge node paths shorter than this will be discarded for noise reduction.|\n|```colorsampling```|```1f```|Enable or disable color sampling. 1f is on, 0f is off.|\n|```numberofcolors```|```16f```|Number of colors to use on palette if pal object is not defined.|\n|```mincolorratio```|```0.02f```|Color quantization will randomize a color if fewer pixels than (total pixels*mincolorratio) has it.|\n|```colorquantcycles```|```3f```|Color quantization will be repeated this many times.|\n|```blurradius```|```0f```|Set this to 1f..5f for selective Gaussian blur preprocessing.|\n|```blurdelta```|```20f```|RGBA delta treshold for selective Gaussian blur preprocessing.|\n|```scale```|```1f```|Every coordinate will be multiplied with this, to scale the SVG.|\n|```roundcoords```|```1f```|rounding coordinates to a given decimal place. 1f means rounded to 1 decimal place like 7.3 ; 3f means rounded to 3 places, like 7.356|\n|```viewbox```|```0f```|Enable or disable SVG viewBox. 1f is on, 0f is off.|\n|```desc```|```1f```|Enable or disable SVG descriptions. 1f is on, 0f is off.|\n|```lcpr```|```0f```|Straight line control point radius, if this is greater than zero, small circles will be drawn in the SVG. Do not use this for big/complex images.|\n|```qcpr```|```0f```|Quadratic spline control point radius, if this is greater than zero, small circles and lines will be drawn in the SVG. Do not use this for big/complex images.|\n\n### Process overview\nSee [Process overview and Ideas for improvement](https://github.com/jankovicsandras/imagetracerjava/blob/master/process_overview.md)\n\n### License\n#### The Unlicense / PUBLIC DOMAIN\n\nThis is free and unencumbered software released into the public domain.\n\nAnyone is free to copy, modify, publish, use, compile, sell, or\ndistribute this software, either in source code form or as a compiled\nbinary, for any purpose, commercial or non-commercial, and by any\nmeans.\n\nIn jurisdictions that recognize copyright laws, the author or authors\nof this software dedicate any and all copyright interest in the\nsoftware to the public domain. We make this dedication for the benefit\nof the public at large and to the detriment of our heirs and\nsuccessors. We intend this dedication to be an overt act of\nrelinquishment in perpetuity of all present and future rights to this\nsoftware under copyright law.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR\nOTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\nOTHER DEALINGS IN THE SOFTWARE.\n\nFor more information, please refer to [http://unlicense.org](http://unlicense.org)\n"
  },
  {
    "path": "deterministic.md",
    "content": "﻿## TLDR; options for deterministic tracing:\n\ncustom palette, `colorquantcycles`:1\n\ncustom palette, `mincolorratio`:0\n\n`colorsampling`:0 (false), `mincolorratio`:0, `numberofcolors`<8\n\n`colorsampling`:0 (false), `mincolorratio`:0, `numberofcolors`:n^3 eg. 8, 27...\n\n`colorsampling`:0 (false), `colorquantcycles`:1, `numberofcolors`<8\n\n`colorsampling`:0 (false), `colorquantcycles`:1, `numberofcolors`:n^3 eg. 8, 27...\n\n---\n\n## The long story: ☺\n\nOnly color quantization uses randomization, all the other processing steps are deterministic.\n\nThere are two \"sources of random\" which make the `colorquantization()` non-deterministic by default, but these can be turned off. `colorquantization()` is based on [K-means clustering](https://en.wikipedia.org/wiki/K-means_clustering) , the initial palette contains the initial means. It makes often sense to use randomization creating the initial palette (see below). Some clusters may have very few members, so they should be \"recycled\": the new cluster center (palette color) is generated randomly. These non-deterministic defaults can be changed:\n\n### 1. There are 3 ways to create the initial palette before color clustering, listed by priority:\n- use a custom palette (deterministic) IF it's defined ELSE\n- sample the input image randomly (non-deterministic) IF `colorsampling` is 1 (true, the default) ELSE \n- generate a palette\n  - grayscale (deterministic) IF `numberofcolors`<8 ELSE\n  - RGB cubic grid (deterministic) \"from the cubic part of\" `numberofcolors` AND\n  - random colors (non-deterministic) \"from the rest of\" `numberofcolors`\n\nSo to create a deterministic initial palette: \n- use custom palette OR \n- set `colorsampling`:0 (false) AND \n  - use less than 8 colors eg. `numberofcolors`:7 OR\n  - set `numberofcolors` to a cubic number eg. 8, 27, 64, 125...\n\n### 2. Clusters which have very few members, can be \"recycled\" to improve clustering: \nthe new cluster center (palette color) is generated randomly. This depends on `mincolorratio` : if the ratio of pixels that belong to this color (cluster) is less than `mincolorratio` , then this color will be randomized. The default 0.02 means that if fewer than 2% of all pixels are similar to this color, then this is probably a \"bad\" color and will be \"recycled\".\n\nIF the clustering is not repeated ( `colorquantcycles`:1 ) OR no color will be recycled ( `mincolorratio`:0 ) THEN this will be deterministic.\n\nThese design choices were made so that the color quantization would be:\n- flexible : the user can use a custom palette or tweak many parameters\n- heuristic: sometimes it's bad but sometimes it's good, instead of being deterministic and mediocre. It's recommended to run tracing multiple times and keep the best result.\n- simple to implement.\n\n"
  },
  {
    "path": "jankovicsandras/imagetracer/ImageTracer.java",
    "content": "/*\n\tImageTracer.java\n\t(Desktop version with javax.imageio. See ImageTracerAndroid.java for the Android version.)\n\tSimple raster image tracer and vectorizer written in Java. This is a port of imagetracer.js.\n\tby András Jankovics 2015, 2016\n\tandras@jankovics.net\n\n */\n\n/*\n\nThe Unlicense / PUBLIC DOMAIN\n\nThis is free and unencumbered software released into the public domain.\n\nAnyone is free to copy, modify, publish, use, compile, sell, or\ndistribute this software, either in source code form or as a compiled\nbinary, for any purpose, commercial or non-commercial, and by any\nmeans.\n\nIn jurisdictions that recognize copyright laws, the author or authors\nof this software dedicate any and all copyright interest in the\nsoftware to the public domain. We make this dedication for the benefit\nof the public at large and to the detriment of our heirs and\nsuccessors. We intend this dedication to be an overt act of\nrelinquishment in perpetuity of all present and future rights to this\nsoftware under copyright law.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR\nOTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\nOTHER DEALINGS IN THE SOFTWARE.\n\nFor more information, please refer to http://unlicense.org/\n\n */\npackage jankovicsandras.imagetracer;\n\nimport java.awt.image.BufferedImage;\nimport java.io.BufferedWriter;\nimport java.io.File;\nimport java.io.FileWriter;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.Map.Entry;\nimport java.util.TreeMap;\n\nimport javax.imageio.ImageIO;\n\npublic class ImageTracer{\n\n\tpublic static String versionnumber = \"1.1.2\";\n\n\tpublic ImageTracer(){}\n\n\tpublic static void main (String[] args){\n\t\ttry{\n\n\t\t\tif(args.length<1){\n\t\t\t\tSystem.out.println(\"ERROR: there's no input filename. Basic usage: \\r\\n\\r\\njava -jar ImageTracer.jar <filename>\"+\n\t\t\t\t\t\t\"\\r\\n\\r\\nor\\r\\n\\r\\njava -jar ImageTracer.jar help\");\n\t\t\t} else if(arraycontains(args,\"help\")>-1){\n\t\t\t\tSystem.out.println(\"Example usage:\\r\\n\\r\\njava -jar ImageTracer.jar <filename> outfilename test.svg \"+\n\t\t\t\t\t\t\"ltres 1 qtres 1 pathomit 8 colorsampling 1 numberofcolors 16 mincolorratio 0.02 colorquantcycles 3 \"+\n\t\t\t\t\t\t\"scale 1 simplifytolerance 0 roundcoords 1 lcpr 0 qcpr 0 desc 1 viewbox 0 blurradius 0 blurdelta 20 \\r\\n\"+\n\t\t\t\t\t\t\"\\r\\nOnly <filename> is mandatory, if some of the other optional parameters are missing, they will be set to these defaults. \"+\n\t\t\t\t\t\t\"\\r\\nWarning: if outfilename is not specified, then <filename>.svg will be overwritten.\"+\n\t\t\t\t\t\t\"\\r\\nSee https://github.com/jankovicsandras/imagetracerjava for details. \\r\\nThis is version \"+versionnumber);\n\t\t\t} else {\n\n\t\t\t\t// Parameter parsing\n\t\t\t\tString outfilename = args[0]+\".svg\";\n\t\t\t\tHashMap<String,Float> options = new HashMap<String,Float>();\n\t\t\t\tString[] parameternames = {\"ltres\",\"qtres\",\"pathomit\",\"colorsampling\",\"numberofcolors\",\"mincolorratio\",\"colorquantcycles\",\"scale\",\"simplifytolerance\",\"roundcoords\",\"lcpr\",\"qcpr\",\"desc\",\"viewbox\",\"blurradius\",\"blurdelta\",\"outfilename\"};\n\t\t\t\tint j = -1; float f = -1;\n\t\t\t\tfor (String parametername : parameternames) {\n\t\t\t\t\tj = arraycontains(args,parametername);\n\t\t\t\t\tif(j>-1){\n\t\t\t\t\t\tif(parametername==\"outfilename\"){\n\t\t\t\t\t\t\tif( j < (args.length-1)){ outfilename = args[j+1]; }\n\t\t\t\t\t\t}else{\n\t\t\t\t\t\t\tf = parsenext(args,j); if(f>-1){ options.put(parametername, new Float(f)); }\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}// End of parameternames loop\n\n\t\t\t\t// Loading image, tracing, rendering SVG, saving SVG file\n\t\t\t\tsaveString(outfilename,imageToSVG(args[0],options,null));\n\n\t\t\t}// End of parameter parsing and processing\n\n\t\t}catch(Exception e){ e.printStackTrace(); }\n\t}// End of main()\n\n\n\tpublic static int arraycontains (String [] arr, String str){\n\t\tfor(int j=0; j<arr.length; j++ ){ if(arr[j].toLowerCase().equals(str)){ return j; } } return -1;\n\t}\n\n\n\tpublic static float parsenext (String [] arr, int i){\n\t\tif(i<(arr.length-1)){ try{ return Float.parseFloat(arr[i+1]); }catch(Exception e){} } return -1;\n\t}\n\n\n\t// Container for the color-indexed image before and tracedata after vectorizing\n\tpublic static class IndexedImage{\n\t\tpublic int width, height;\n\t\tpublic int [][] array; // array[x][y] of palette colors\n\t\tpublic byte [][] palette;// array[palettelength][4] RGBA color palette\n\t\tpublic ArrayList<ArrayList<ArrayList<Double[]>>> layers;// tracedata\n\n\t\tpublic IndexedImage(int [][] marray, byte [][] mpalette){\n\t\t\tarray = marray; palette = mpalette;\n\t\t\twidth = marray[0].length-2; height = marray.length-2;// Color quantization adds +2 to the original width and height\n\t\t}\n\t}\n\n\n\t// https://developer.mozilla.org/en-US/docs/Web/API/ImageData\n\tpublic static class ImageData{\n\t\tpublic int width, height;\n\t\tpublic byte[] data; // raw byte data: R G B A R G B A ...\n\t\tpublic ImageData(int mwidth, int mheight, byte[] mdata){\n\t\t\twidth = mwidth; height = mheight; data = mdata;\n\t\t}\n\t}\n\n\n\t// Saving a String as a file\n\tpublic static void saveString (String filename, String str) throws Exception {\n\t\tFile file = new File(filename);\n\t\t// if file doesnt exists, then create it\n\t\tif(!file.exists()){ file.createNewFile(); }\n\t\tFileWriter fw = new FileWriter(file.getAbsoluteFile());\n\t\tBufferedWriter bw = new BufferedWriter(fw);\n\t\tbw.write(str);\n\t\tbw.close();\n\t}\n\n\n\t// Loading a file to ImageData, ARGB byte order\n\tpublic static ImageData loadImageData (String filename) throws Exception {\n\t\tBufferedImage image = ImageIO.read(new File(filename));\n\t\treturn loadImageData(image);\n\t}\n\tpublic static ImageData loadImageData (BufferedImage image) throws Exception {\n\t\tint width = image.getWidth(); int height = image.getHeight();\n\t\tint[] rawdata = image.getRGB(0, 0, width, height, null, 0, width);\n\t\tbyte[] data = new byte[rawdata.length*4];\n\t\tfor(int i=0; i<rawdata.length; i++){\n\t\t\tdata[(i*4)+3] = bytetrans((byte)(rawdata[i] >>> 24));\n\t\t\tdata[i*4  ] = bytetrans((byte)(rawdata[i] >>> 16));\n\t\t\tdata[(i*4)+1] = bytetrans((byte)(rawdata[i] >>> 8));\n\t\t\tdata[(i*4)+2] = bytetrans((byte)(rawdata[i]));\n\t\t}\n\t\treturn new ImageData(width,height,data);\n\t}\n\n\n\t// The bitshift method in loadImageData creates signed bytes where -1 -> 255 unsigned ; -128 -> 128 unsigned ;\n\t// 127 -> 127 unsigned ; 0 -> 0 unsigned ; These will be converted to -128 (representing 0 unsigned) ...\n\t// 127 (representing 255 unsigned) and tosvgcolorstr will add +128 to create RGB values 0..255\n\tpublic static byte bytetrans (byte b){\n\t\tif(b<0){ return (byte)(b+128); }else{ return (byte)(b-128); }\n\t}\n\n\n\t////////////////////////////////////////////////////////////\n\t//\n\t//  User friendly functions\n\t//\n\t////////////////////////////////////////////////////////////\n\n\t// Loading an image from a file, tracing when loaded, then returning the SVG String\n\tpublic static String imageToSVG (String filename, HashMap<String,Float> options, byte [][] palette) throws Exception{\n\t\toptions = checkoptions(options);\n\t\tImageData imgd = loadImageData(filename);\n\t\treturn imagedataToSVG(imgd,options,palette);\n\t}// End of imageToSVG()\n\tpublic static String imageToSVG (BufferedImage image, HashMap<String,Float> options, byte [][] palette) throws Exception{\n\t\toptions = checkoptions(options);\n\t\tImageData imgd = loadImageData(image);\n\t\treturn imagedataToSVG(imgd,options,palette);\n\t}// End of imageToSVG()\n\n\n\t// Tracing ImageData, then returning the SVG String\n\tpublic static String imagedataToSVG (ImageData imgd, HashMap<String,Float> options, byte [][] palette){\n\t\toptions = checkoptions(options);\n\t\tIndexedImage ii = imagedataToTracedata(imgd,options,palette);\n\t\treturn getsvgstring(ii, options);\n\t}// End of imagedataToSVG()\n\n\n\t// Loading an image from a file, tracing when loaded, then returning IndexedImage with tracedata in layers\n\tpublic IndexedImage imageToTracedata (String filename, HashMap<String,Float> options, byte [][] palette) throws Exception{\n\t\toptions = checkoptions(options);\n\t\tImageData imgd = loadImageData(filename);\n\t\treturn imagedataToTracedata(imgd,options,palette);\n\t}// End of imageToTracedata()\n\tpublic IndexedImage imageToTracedata (BufferedImage image, HashMap<String,Float> options, byte [][] palette) throws Exception{\n\t\toptions = checkoptions(options);\n\t\tImageData imgd = loadImageData(image);\n\t\treturn imagedataToTracedata(imgd,options,palette);\n\t}// End of imageToTracedata()\n\n\n\t// Tracing ImageData, then returning IndexedImage with tracedata in layers\n\tpublic static IndexedImage imagedataToTracedata (ImageData imgd, HashMap<String,Float> options, byte [][] palette){\n\t\t// 1. Color quantization\n\t\tIndexedImage ii = colorquantization(imgd, palette, options);\n\t\t// 2. Layer separation and edge detection\n\t\tint[][][] rawlayers = layering(ii);\n\t\t// 3. Batch pathscan\n\t\tArrayList<ArrayList<ArrayList<Integer[]>>> bps = batchpathscan(rawlayers,(int)(Math.floor(options.get(\"pathomit\"))));\n\t\t// 4. Batch interpollation\n\t\tArrayList<ArrayList<ArrayList<Double[]>>> bis = batchinternodes(bps);\n\t\t// 5. Batch tracing\n\t\tii.layers = batchtracelayers(bis,options.get(\"ltres\"),options.get(\"qtres\"));\n\t\treturn ii;\n\t}// End of imagedataToTracedata()\n\n\n\t// creating options object, setting defaults for missing values\n\tpublic static HashMap<String,Float> checkoptions (HashMap<String,Float> options){\n\t\tif(options==null){ options = new HashMap<String,Float>(); }\n\t\t// Tracing\n\t\tif(!options.containsKey(\"ltres\")){ options.put(\"ltres\",1f); }\n\t\tif(!options.containsKey(\"qtres\")){ options.put(\"qtres\",1f); }\n\t\tif(!options.containsKey(\"pathomit\")){ options.put(\"pathomit\",8f); }\n\t\t// Color quantization\n\t\tif(!options.containsKey(\"colorsampling\")){ options.put(\"colorsampling\",1f); }\n\t\tif(!options.containsKey(\"numberofcolors\")){ options.put(\"numberofcolors\",16f); }\n\t\tif(!options.containsKey(\"mincolorratio\")){ options.put(\"mincolorratio\",0.02f); }\n\t\tif(!options.containsKey(\"colorquantcycles\")){ options.put(\"colorquantcycles\",3f); }\n\t\t// SVG rendering\n\t\tif(!options.containsKey(\"scale\")){ options.put(\"scale\",1f); }\n\t\tif(!options.containsKey(\"simplifytolerance\")){ options.put(\"simplifytolerance\",0f); }\n\t\tif(!options.containsKey(\"roundcoords\")){ options.put(\"roundcoords\",1f); }\n\t\tif(!options.containsKey(\"lcpr\")){ options.put(\"lcpr\",0f); }\n\t\tif(!options.containsKey(\"qcpr\")){ options.put(\"qcpr\",0f); }\n\t\tif(!options.containsKey(\"desc\")){ options.put(\"desc\",1f); }\n\t\tif(!options.containsKey(\"viewbox\")){ options.put(\"viewbox\",0f); }\n\t\t// Blur\n\t\tif(!options.containsKey(\"blurradius\")){ options.put(\"blurradius\",0f); }\n\t\tif(!options.containsKey(\"blurdelta\")){ options.put(\"blurdelta\",20f); }\n\n\t\treturn options;\n\t}// End of checkoptions()\n\n\n\t////////////////////////////////////////////////////////////\n\t//\n\t//  Vectorizing functions\n\t//\n\t////////////////////////////////////////////////////////////\n\n\t// 1. Color quantization repeated \"cycles\" times, based on K-means clustering\n\t// https://en.wikipedia.org/wiki/Color_quantization    https://en.wikipedia.org/wiki/K-means_clustering\n\tpublic static IndexedImage colorquantization (ImageData imgd, byte [][] palette, HashMap<String,Float> options){\n\t\tint numberofcolors = (int)Math.floor(options.get(\"numberofcolors\")); float minratio = options.get(\"mincolorratio\"); int cycles = (int)Math.floor(options.get(\"colorquantcycles\"));\n\t\t// Creating indexed color array arr which has a boundary filled with -1 in every direction\n\t\tint [][] arr = new int[imgd.height+2][imgd.width+2];\n\t\tfor(int j=0; j<(imgd.height+2); j++){ arr[j][0] = -1; arr[j][imgd.width+1 ] = -1; }\n\t\tfor(int i=0; i<(imgd.width+2) ; i++){ arr[0][i] = -1; arr[imgd.height+1][i] = -1; }\n\n\t\tint idx=0, cd,cdl,ci,c1,c2,c3,c4;\n\n\t\t// Use custom palette if pal is defined or sample or generate custom length palette\n\t\tif(palette==null){\n\t\t\tif(options.get(\"colorsampling\")!=0){\n\t\t\t\tpalette = samplepalette(numberofcolors,imgd);\n\t\t\t}else{\n\t\t\t\tpalette = generatepalette(numberofcolors);\n\t\t\t}\n\t\t}\n\n\t\t// Selective Gaussian blur preprocessing\n\t\tif( options.get(\"blurradius\") > 0 ){ imgd = blur( imgd, options.get(\"blurradius\"), options.get(\"blurdelta\") ); }\n\n\t\tlong [][] paletteacc = new long[palette.length][5];\n\n\t\t// Repeat clustering step \"cycles\" times\n\t\tfor(int cnt=0;cnt<cycles;cnt++){\n\n\t\t\t// Average colors from the second iteration\n\t\t\tif(cnt>0){\n\t\t\t\t// averaging paletteacc for palette\n\t\t\t\tfloat ratio;\n\t\t\t\tfor(int k=0;k<palette.length;k++){\n\t\t\t\t\t// averaging\n\t\t\t\t\tif(paletteacc[k][3]>0){\n\t\t\t\t\t\tpalette[k][0] = (byte) (-128 + (paletteacc[k][0] / paletteacc[k][4]));\n\t\t\t\t\t\tpalette[k][1] = (byte) (-128 + (paletteacc[k][1] / paletteacc[k][4]));\n\t\t\t\t\t\tpalette[k][2] = (byte) (-128 + (paletteacc[k][2] / paletteacc[k][4]));\n\t\t\t\t\t\tpalette[k][3] = (byte) (-128 + (paletteacc[k][3] / paletteacc[k][4]));\n\t\t\t\t\t}\n\t\t\t\t\tratio = (float)( (double)(paletteacc[k][4]) / (double)(imgd.width*imgd.height) );\n\n\t\t\t\t\t// Randomizing a color, if there are too few pixels and there will be a new cycle\n\t\t\t\t\tif( (ratio<minratio) && (cnt<(cycles-1)) ){\n\t\t\t\t\t\tpalette[k][0] = (byte) (-128+Math.floor(Math.random()*255));\n\t\t\t\t\t\tpalette[k][1] = (byte) (-128+Math.floor(Math.random()*255));\n\t\t\t\t\t\tpalette[k][2] = (byte) (-128+Math.floor(Math.random()*255));\n\t\t\t\t\t\tpalette[k][3] = (byte) (-128+Math.floor(Math.random()*255));\n\t\t\t\t\t}\n\n\t\t\t\t}// End of palette loop\n\t\t\t}// End of Average colors from the second iteration\n\n\t\t\t// Reseting palette accumulator for averaging\n\t\t\tfor(int i=0;i<palette.length;i++){\n\t\t\t\tpaletteacc[i][0]=0;\n\t\t\t\tpaletteacc[i][1]=0;\n\t\t\t\tpaletteacc[i][2]=0;\n\t\t\t\tpaletteacc[i][3]=0;\n\t\t\t\tpaletteacc[i][4]=0;\n\t\t\t}\n\n\t\t\t// loop through all pixels\n\t\t\tfor(int j=0;j<imgd.height;j++){\n\t\t\t\tfor(int i=0;i<imgd.width;i++){\n\n\t\t\t\t\tidx = ((j*imgd.width)+i)*4;\n\n\t\t\t\t\t// find closest color from palette by measuring (rectilinear) color distance between this pixel and all palette colors\n\t\t\t\t\tcdl = 256+256+256+256; ci=0;\n\t\t\t\t\tfor(int k=0;k<palette.length;k++){\n\n\t\t\t\t\t\t// In my experience, https://en.wikipedia.org/wiki/Rectilinear_distance works better than https://en.wikipedia.org/wiki/Euclidean_distance\n\t\t\t\t\t\tc1 = Math.abs(palette[k][0]-imgd.data[idx]);\n\t\t\t\t\t\tc2 = Math.abs(palette[k][1]-imgd.data[idx+1]);\n\t\t\t\t\t\tc3 = Math.abs(palette[k][2]-imgd.data[idx+2]);\n\t\t\t\t\t\tc4 = Math.abs(palette[k][3]-imgd.data[idx+3]);\n\t\t\t\t\t\tcd = c1+c2+c3+(c4*4); // weighted alpha seems to help images with transparency\n\n\t\t\t\t\t\t// Remember this color if this is the closest yet\n\t\t\t\t\t\tif(cd<cdl){ cdl = cd; ci = k; }\n\n\t\t\t\t\t}// End of palette loop\n\n\t\t\t\t\t// add to palettacc\n\t\t\t\t\tpaletteacc[ci][0] += 128+imgd.data[idx];\n\t\t\t\t\tpaletteacc[ci][1] += 128+imgd.data[idx+1];\n\t\t\t\t\tpaletteacc[ci][2] += 128+imgd.data[idx+2];\n\t\t\t\t\tpaletteacc[ci][3] += 128+imgd.data[idx+3];\n\t\t\t\t\tpaletteacc[ci][4]++;\n\n\t\t\t\t\tarr[j+1][i+1] = ci;\n\t\t\t\t}// End of i loop\n\t\t\t}// End of j loop\n\n\t\t}// End of Repeat clustering step \"cycles\" times\n\n\t\treturn new IndexedImage(arr, palette);\n\t}// End of colorquantization\n\n\n\t// Generating a palette with numberofcolors, array[numberofcolors][4] where [i][0] = R ; [i][1] = G ; [i][2] = B ; [i][3] = A\n\tpublic static byte[][] generatepalette (int numberofcolors){\n\t\tbyte [][] palette = new byte[numberofcolors][4];\n\t\tif(numberofcolors<8){\n\n\t\t\t// Grayscale\n\t\t\tdouble graystep = 255.0/(double)(numberofcolors-1);\n\t\t\tfor(byte ccnt=0;ccnt<numberofcolors;ccnt++){\n\t\t\t\tpalette[ccnt][0] = (byte)(-128+Math.round(ccnt*graystep));\n\t\t\t\tpalette[ccnt][1] = (byte)(-128+Math.round(ccnt*graystep));\n\t\t\t\tpalette[ccnt][2] = (byte)(-128+Math.round(ccnt*graystep));\n\t\t\t\tpalette[ccnt][3] = (byte)127;\n\t\t\t}\n\n\t\t}else{\n\n\t\t\t// RGB color cube\n\t\t\tint colorqnum = (int) Math.floor(Math.pow(numberofcolors, 1.0/3.0)); // Number of points on each edge on the RGB color cube\n\t\t\tint colorstep = (int) Math.floor(255/(colorqnum-1)); // distance between points\n\t\t\tint ccnt = 0;\n\t\t\tfor(int rcnt=0;rcnt<colorqnum;rcnt++){\n\t\t\t\tfor(int gcnt=0;gcnt<colorqnum;gcnt++){\n\t\t\t\t\tfor(int bcnt=0;bcnt<colorqnum;bcnt++){\n\t\t\t\t\t\tpalette[ccnt][0] = (byte)(-128+(rcnt*colorstep));\n\t\t\t\t\t\tpalette[ccnt][1] = (byte)(-128+(gcnt*colorstep));\n\t\t\t\t\t\tpalette[ccnt][2] = (byte)(-128+(bcnt*colorstep));\n\t\t\t\t\t\tpalette[ccnt][3] = (byte)127;\n\t\t\t\t\t\tccnt++;\n\t\t\t\t\t}// End of blue loop\n\t\t\t\t}// End of green loop\n\t\t\t}// End of red loop\n\n\t\t\t// Rest is random\n\t\t\tfor(int rcnt=ccnt;rcnt<numberofcolors;rcnt++){\n\t\t\t\tpalette[ccnt][0] = (byte)(-128+Math.floor(Math.random()*255));\n\t\t\t\tpalette[ccnt][1] = (byte)(-128+Math.floor(Math.random()*255));\n\t\t\t\tpalette[ccnt][2] = (byte)(-128+Math.floor(Math.random()*255));\n\t\t\t\tpalette[ccnt][3] = (byte)(-128+Math.floor(Math.random()*255));\n\t\t\t}\n\n\t\t}// End of numberofcolors check\n\n\t\treturn palette;\n\t};// End of generatepalette()\n\n\n\tpublic static byte[][] samplepalette (int numberofcolors, ImageData imgd){\n\t\tint idx=0; byte [][] palette = new byte[numberofcolors][4];\n\t\tfor(int i=0; i<numberofcolors; i++){\n\t\t\tidx = (int) (Math.floor( (Math.random() * imgd.data.length) / 4 ) * 4);\n\t\t\tpalette[i][0] = imgd.data[idx  ];\n\t\t\tpalette[i][1] = imgd.data[idx+1];\n\t\t\tpalette[i][2] = imgd.data[idx+2];\n\t\t\tpalette[i][3] = imgd.data[idx+3];\n\t\t}\n\t\treturn palette;\n\t}// End of samplepalette()\n\n\n\t// 2. Layer separation and edge detection\n\t// Edge node types ( ▓:light or 1; ░:dark or 0 )\n\t// 12  ░░  ▓░  ░▓  ▓▓  ░░  ▓░  ░▓  ▓▓  ░░  ▓░  ░▓  ▓▓  ░░  ▓░  ░▓  ▓▓\n\t// 48  ░░  ░░  ░░  ░░  ░▓  ░▓  ░▓  ░▓  ▓░  ▓░  ▓░  ▓░  ▓▓  ▓▓  ▓▓  ▓▓\n\t//     0   1   2   3   4   5   6   7   8   9   10  11  12  13  14  15\n\t//\n\tpublic static int[][][] layering (IndexedImage ii){\n\t\t// Creating layers for each indexed color in arr\n\t\tint val=0, aw = ii.array[0].length, ah = ii.array.length, n1,n2,n3,n4,n5,n6,n7,n8;\n\t\tint[][][] layers = new int[ii.palette.length][ah][aw];\n\n\t\t// Looping through all pixels and calculating edge node type\n\t\tfor(int j=1; j<(ah-1); j++){\n\t\t\tfor(int i=1; i<(aw-1); i++){\n\n\t\t\t\t// This pixel's indexed color\n\t\t\t\tval = ii.array[j][i];\n\n\t\t\t\t// Are neighbor pixel colors the same?\n\t\t\t\tn1 = ii.array[j-1][i-1]==val ? 1 : 0;\n\t\t\t\tn2 = ii.array[j-1][i  ]==val ? 1 : 0;\n\t\t\t\tn3 = ii.array[j-1][i+1]==val ? 1 : 0;\n\t\t\t\tn4 = ii.array[j  ][i-1]==val ? 1 : 0;\n\t\t\t\tn5 = ii.array[j  ][i+1]==val ? 1 : 0;\n\t\t\t\tn6 = ii.array[j+1][i-1]==val ? 1 : 0;\n\t\t\t\tn7 = ii.array[j+1][i  ]==val ? 1 : 0;\n\t\t\t\tn8 = ii.array[j+1][i+1]==val ? 1 : 0;\n\n\t\t\t\t// this pixel\"s type and looking back on previous pixels\n\t\t\t\tlayers[val][j+1][i+1] = 1 + (n5 * 2) + (n8 * 4) + (n7 * 8) ;\n\t\t\t\tif(n4==0){ layers[val][j+1][i  ] = 0 + 2 + (n7 * 4) + (n6 * 8) ; }\n\t\t\t\tif(n2==0){ layers[val][j  ][i+1] = 0 + (n3*2) + (n5 * 4) + 8 ; }\n\t\t\t\tif(n1==0){ layers[val][j  ][i  ] = 0 + (n2*2) + 4 + (n4 * 8) ; }\n\n\t\t\t}// End of i loop\n\t\t}// End of j loop\n\n\t\treturn layers;\n\t}// End of layering()\n\n\n\t// Lookup tables for pathscan\n\tstatic byte [] pathscan_dir_lookup = {0,0,3,0, 1,0,3,0, 0,3,3,1, 0,3,0,0};\n\tstatic boolean [] pathscan_holepath_lookup = {false,false,false,false, false,false,false,true, false,false,false,true, false,true,true,false };\n\t// pathscan_combined_lookup[ arr[py][px] ][ dir ] = [nextarrpypx, nextdir, deltapx, deltapy];\n\tstatic byte [][][] pathscan_combined_lookup = {\n\t\t\t{{-1,-1,-1,-1}, {-1,-1,-1,-1}, {-1,-1,-1,-1}, {-1,-1,-1,-1}},// arr[py][px]==0 is invalid\n\t\t\t{{ 0, 1, 0,-1}, {-1,-1,-1,-1}, {-1,-1,-1,-1}, { 0, 2,-1, 0}},\n\t\t\t{{-1,-1,-1,-1}, {-1,-1,-1,-1}, { 0, 1, 0,-1}, { 0, 0, 1, 0}},\n\t\t\t{{ 0, 0, 1, 0}, {-1,-1,-1,-1}, { 0, 2,-1, 0}, {-1,-1,-1,-1}},\n\n\t\t\t{{-1,-1,-1,-1}, { 0, 0, 1, 0}, { 0, 3, 0, 1}, {-1,-1,-1,-1}},\n\t\t\t{{13, 3, 0, 1}, {13, 2,-1, 0}, { 7, 1, 0,-1}, { 7, 0, 1, 0}},\n\t\t\t{{-1,-1,-1,-1}, { 0, 1, 0,-1}, {-1,-1,-1,-1}, { 0, 3, 0, 1}},\n\t\t\t{{ 0, 3, 0, 1}, { 0, 2,-1, 0}, {-1,-1,-1,-1}, {-1,-1,-1,-1}},\n\n\t\t\t{{ 0, 3, 0, 1}, { 0, 2,-1, 0}, {-1,-1,-1,-1}, {-1,-1,-1,-1}},\n\t\t\t{{-1,-1,-1,-1}, { 0, 1, 0,-1}, {-1,-1,-1,-1}, { 0, 3, 0, 1}},\n\t\t\t{{11, 1, 0,-1}, {14, 0, 1, 0}, {14, 3, 0, 1}, {11, 2,-1, 0}},\n\t\t\t{{-1,-1,-1,-1}, { 0, 0, 1, 0}, { 0, 3, 0, 1}, {-1,-1,-1,-1}},\n\n\t\t\t{{ 0, 0, 1, 0}, {-1,-1,-1,-1}, { 0, 2,-1, 0}, {-1,-1,-1,-1}},\n\t\t\t{{-1,-1,-1,-1}, {-1,-1,-1,-1}, { 0, 1, 0,-1}, { 0, 0, 1, 0}},\n\t\t\t{{ 0, 1, 0,-1}, {-1,-1,-1,-1}, {-1,-1,-1,-1}, { 0, 2,-1, 0}},\n\t\t\t{{-1,-1,-1,-1}, {-1,-1,-1,-1}, {-1,-1,-1,-1}, {-1,-1,-1,-1}}// arr[py][px]==15 is invalid\n\t};\n\n\n\t// 3. Walking through an edge node array, discarding edge node types 0 and 15 and creating paths from the rest.\n\t// Walk directions (dir): 0 > ; 1 ^ ; 2 < ; 3 v\n\t// Edge node types ( ▓:light or 1; ░:dark or 0 )\n\t// ░░  ▓░  ░▓  ▓▓  ░░  ▓░  ░▓  ▓▓  ░░  ▓░  ░▓  ▓▓  ░░  ▓░  ░▓  ▓▓\n\t// ░░  ░░  ░░  ░░  ░▓  ░▓  ░▓  ░▓  ▓░  ▓░  ▓░  ▓░  ▓▓  ▓▓  ▓▓  ▓▓\n\t// 0   1   2   3   4   5   6   7   8   9   10  11  12  13  14  15\n\t//\n\tpublic static ArrayList<ArrayList<Integer[]>> pathscan (int [][] arr,float pathomit){\n\t\tArrayList<ArrayList<Integer[]>> paths = new ArrayList<ArrayList<Integer[]>>();\n\t\tArrayList<Integer[]> thispath;\n\t\tint px=0,py=0,w=arr[0].length,h=arr.length,dir=0;\n\t\tboolean pathfinished=true, holepath = false;\n\t\tbyte[] lookuprow;\n\n\t\tfor(int j=0;j<h;j++){\n\t\t\tfor(int i=0;i<w;i++){\n\t\t\t\tif((arr[j][i]!=0)&&(arr[j][i]!=15)){\n\n\t\t\t\t\t// Init\n\t\t\t\t\tpx = i; py = j;\n\t\t\t\t\tpaths.add(new ArrayList<Integer[]>());\n\t\t\t\t\tthispath = paths.get(paths.size()-1);\n\t\t\t\t\tpathfinished = false;\n\n\t\t\t\t\t// fill paths will be drawn, but hole paths are also required to remove unnecessary edge nodes\n\t\t\t\t\tdir = pathscan_dir_lookup[ arr[py][px] ]; holepath = pathscan_holepath_lookup[ arr[py][px] ];\n\n\t\t\t\t\t// Path points loop\n\t\t\t\t\twhile(!pathfinished){\n\n\t\t\t\t\t\t// New path point\n\t\t\t\t\t\tthispath.add(new Integer[3]);\n\t\t\t\t\t\tthispath.get(thispath.size()-1)[0] = px-1;\n\t\t\t\t\t\tthispath.get(thispath.size()-1)[1] = py-1;\n\t\t\t\t\t\tthispath.get(thispath.size()-1)[2] = arr[py][px];\n\n\t\t\t\t\t\t// Next: look up the replacement, direction and coordinate changes = clear this cell, turn if required, walk forward\n\t\t\t\t\t\tlookuprow = pathscan_combined_lookup[ arr[py][px] ][ dir ];\n\t\t\t\t\t\tarr[py][px] = lookuprow[0]; dir = lookuprow[1]; px += lookuprow[2]; py += lookuprow[3];\n\n\t\t\t\t\t\t// Close path\n\t\t\t\t\t\tif(((px-1)==thispath.get(0)[0])&&((py-1)==thispath.get(0)[1])){\n\t\t\t\t\t\t\tpathfinished = true;\n\t\t\t\t\t\t\t// Discarding 'hole' type paths and paths shorter than pathomit\n\t\t\t\t\t\t\tif( (holepath) || (thispath.size()<pathomit) ){\n\t\t\t\t\t\t\t\tpaths.remove(thispath);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t}// End of Path points loop\n\n\t\t\t\t}// End of Follow path\n\n\t\t\t}// End of i loop\n\t\t}// End of j loop\n\n\t\treturn paths;\n\t}// End of pathscan()\n\n\n\t// 3. Batch pathscan\n\tpublic static ArrayList<ArrayList<ArrayList<Integer[]>>> batchpathscan (int [][][] layers, float pathomit){\n\t\tArrayList<ArrayList<ArrayList<Integer[]>>> bpaths = new ArrayList<ArrayList<ArrayList<Integer[]>>>();\n\t\tfor (int[][] layer : layers) {\n\t\t\tbpaths.add(pathscan(layer,pathomit));\n\t\t}\n\t\treturn bpaths;\n\t}\n\n\n\t// 4. interpolating between path points for nodes with 8 directions ( East, SouthEast, S, SW, W, NW, N, NE )\n\tpublic static ArrayList<ArrayList<Double[]>> internodes (ArrayList<ArrayList<Integer[]>> paths){\n\t\tArrayList<ArrayList<Double[]>> ins = new ArrayList<ArrayList<Double[]>>();\n\t\tArrayList<Double[]> thisinp;\n\t\tDouble[] thispoint, nextpoint = new Double[2];\n\t\tInteger[] pp1, pp2, pp3;\n\t\tint palen=0,nextidx=0,nextidx2=0;\n\n\t\t// paths loop\n\t\tfor(int pacnt=0; pacnt<paths.size(); pacnt++){\n\t\t\tins.add(new ArrayList<Double[]>());\n\t\t\tthisinp = ins.get(ins.size()-1);\n\t\t\tpalen = paths.get(pacnt).size();\n\t\t\t// pathpoints loop\n\t\t\tfor(int pcnt=0;pcnt<palen;pcnt++){\n\n\t\t\t\t// interpolate between two path points\n\t\t\t\tnextidx = (pcnt+1)%palen; nextidx2 = (pcnt+2)%palen;\n\t\t\t\tthisinp.add(new Double[3]);\n\t\t\t\tthispoint = thisinp.get(thisinp.size()-1);\n\t\t\t\tpp1 = paths.get(pacnt).get(pcnt);\n\t\t\t\tpp2 = paths.get(pacnt).get(nextidx);\n\t\t\t\tpp3 = paths.get(pacnt).get(nextidx2);\n\t\t\t\tthispoint[0] = (pp1[0]+pp2[0]) / 2.0;\n\t\t\t\tthispoint[1] = (pp1[1]+pp2[1]) / 2.0;\n\t\t\t\tnextpoint[0] = (pp2[0]+pp3[0]) / 2.0;\n\t\t\t\tnextpoint[1] = (pp2[1]+pp3[1]) / 2.0;\n\n\t\t\t\t// line segment direction to the next point\n\t\t\t\tif(thispoint[0] < nextpoint[0]){\n\t\t\t\t\tif     (thispoint[1] < nextpoint[1]){ thispoint[2] = 1.0; }// SouthEast\n\t\t\t\t\telse if(thispoint[1] > nextpoint[1]){ thispoint[2] = 7.0; }// NE\n\t\t\t\t\telse                                { thispoint[2] = 0.0; } // E\n\t\t\t\t}else if(thispoint[0] > nextpoint[0]){\n\t\t\t\t\tif     (thispoint[1] < nextpoint[1]){ thispoint[2] = 3.0; }// SW\n\t\t\t\t\telse if(thispoint[1] > nextpoint[1]){ thispoint[2] = 5.0; }// NW\n\t\t\t\t\telse                                { thispoint[2] = 4.0; }// W\n\t\t\t\t}else{\n\t\t\t\t\tif     (thispoint[1] < nextpoint[1]){ thispoint[2] = 2.0; }// S\n\t\t\t\t\telse if(thispoint[1] > nextpoint[1]){ thispoint[2] = 6.0; }// N\n\t\t\t\t\telse                                { thispoint[2] = 8.0; }// center, this should not happen\n\t\t\t\t}\n\n\t\t\t}// End of pathpoints loop\n\t\t}// End of paths loop\n\t\treturn ins;\n\t}// End of internodes()\n\n\n\t// 4. Batch interpollation\n\tstatic ArrayList<ArrayList<ArrayList<Double[]>>> batchinternodes (ArrayList<ArrayList<ArrayList<Integer[]>>> bpaths){\n\t\tArrayList<ArrayList<ArrayList<Double[]>>> binternodes = new ArrayList<ArrayList<ArrayList<Double[]>>>();\n\t\tfor(int k=0; k<bpaths.size(); k++) {\n\t\t\tbinternodes.add(internodes(bpaths.get(k)));\n\t\t}\n\t\treturn binternodes;\n\t}\n\n\n\t// 5. tracepath() : recursively trying to fit straight and quadratic spline segments on the 8 direction internode path\n\n\t// 5.1. Find sequences of points with only 2 segment types\n\t// 5.2. Fit a straight line on the sequence\n\t// 5.3. If the straight line fails (an error>ltreshold), find the point with the biggest error\n\t// 5.4. Fit a quadratic spline through errorpoint (project this to get controlpoint), then measure errors on every point in the sequence\n\t// 5.5. If the spline fails (an error>qtreshold), find the point with the biggest error, set splitpoint = (fitting point + errorpoint)/2\n\t// 5.6. Split sequence and recursively apply 5.2. - 5.7. to startpoint-splitpoint and splitpoint-endpoint sequences\n\t// 5.7. TODO? If splitpoint-endpoint is a spline, try to add new points from the next sequence\n\n\t// This returns an SVG Path segment as a double[7] where\n\t// segment[0] ==1.0 linear  ==2.0 quadratic interpolation\n\t// segment[1] , segment[2] : x1 , y1\n\t// segment[3] , segment[4] : x2 , y2 ; middle point of Q curve, endpoint of L line\n\t// segment[5] , segment[6] : x3 , y3 for Q curve, should be 0.0 , 0.0 for L line\n\t//\n\t// path type is discarded, no check for path.size < 3 , which should not happen\n\n\tpublic static ArrayList<Double[]> tracepath (ArrayList<Double[]> path, float ltreshold, float qtreshold){\n\t\tint pcnt=0, seqend=0; double segtype1, segtype2;\n\t\tArrayList<Double[]> smp = new ArrayList<Double[]>();\n\t\t//Double [] thissegment;\n\t\tint pathlength = path.size();\n\n\t\twhile(pcnt<pathlength){\n\t\t\t// 5.1. Find sequences of points with only 2 segment types\n\t\t\tsegtype1 = path.get(pcnt)[2]; segtype2 = -1; seqend=pcnt+1;\n\t\t\twhile(\n\t\t\t\t\t((path.get(seqend)[2]==segtype1) || (path.get(seqend)[2]==segtype2) || (segtype2==-1))\n\t\t\t\t\t&& (seqend<(pathlength-1))){\n\t\t\t\tif((path.get(seqend)[2]!=segtype1) && (segtype2==-1)){ segtype2 = path.get(seqend)[2];}\n\t\t\t\tseqend++;\n\t\t\t}\n\t\t\tif(seqend==(pathlength-1)){ seqend = 0; }\n\n\t\t\t// 5.2. - 5.6. Split sequence and recursively apply 5.2. - 5.6. to startpoint-splitpoint and splitpoint-endpoint sequences\n\t\t\tsmp.addAll(fitseq(path,ltreshold,qtreshold,pcnt,seqend));\n\t\t\t// 5.7. TODO? If splitpoint-endpoint is a spline, try to add new points from the next sequence\n\n\t\t\t// forward pcnt;\n\t\t\tif(seqend>0){ pcnt = seqend; }else{ pcnt = pathlength; }\n\n\t\t}// End of pcnt loop\n\n\t\treturn smp;\n\n\t}// End of tracepath()\n\n\n\t// 5.2. - 5.6. recursively fitting a straight or quadratic line segment on this sequence of path nodes,\n\t// called from tracepath()\n\tpublic static ArrayList<Double[]> fitseq (ArrayList<Double[]> path, float ltreshold, float qtreshold, int seqstart, int seqend){\n\t\tArrayList<Double[]> segment = new ArrayList<Double[]>();\n\t\tDouble [] thissegment;\n\t\tint pathlength = path.size();\n\n\t\t// return if invalid seqend\n\t\tif((seqend>pathlength)||(seqend<0)){return segment;}\n\n\t\tint errorpoint=seqstart;\n\t\tboolean curvepass=true;\n\t\tdouble px, py, dist2, errorval=0;\n\t\tdouble tl = (seqend-seqstart); if(tl<0){ tl += pathlength; }\n\t\tdouble vx = (path.get(seqend)[0]-path.get(seqstart)[0]) / tl,\n\t\t\t\tvy = (path.get(seqend)[1]-path.get(seqstart)[1]) / tl;\n\n\t\t// 5.2. Fit a straight line on the sequence\n\t\tint pcnt = (seqstart+1)%pathlength;\n\t\tdouble pl;\n\t\twhile(pcnt != seqend){\n\t\t\tpl = pcnt-seqstart; if(pl<0){ pl += pathlength; }\n\t\t\tpx = path.get(seqstart)[0] + (vx * pl); py = path.get(seqstart)[1] + (vy * pl);\n\t\t\tdist2 = ((path.get(pcnt)[0]-px)*(path.get(pcnt)[0]-px)) + ((path.get(pcnt)[1]-py)*(path.get(pcnt)[1]-py));\n\t\t\tif(dist2>ltreshold){curvepass=false;}\n\t\t\tif(dist2>errorval){ errorpoint=pcnt; errorval=dist2; }\n\t\t\tpcnt = (pcnt+1)%pathlength;\n\t\t}\n\n\t\t// return straight line if fits\n\t\tif(curvepass){\n\t\t\tsegment.add(new Double[7]);\n\t\t\tthissegment = segment.get(segment.size()-1);\n\t\t\tthissegment[0] = 1.0;\n\t\t\tthissegment[1] = path.get(seqstart)[0];\n\t\t\tthissegment[2] = path.get(seqstart)[1];\n\t\t\tthissegment[3] = path.get(seqend)[0];\n\t\t\tthissegment[4] = path.get(seqend)[1];\n\t\t\tthissegment[5] = 0.0;\n\t\t\tthissegment[6] = 0.0;\n\t\t\treturn segment;\n\t\t}\n\n\t\t// 5.3. If the straight line fails (an error>ltreshold), find the point with the biggest error\n\t\tint fitpoint = errorpoint; curvepass = true; errorval = 0;\n\n\t\t// 5.4. Fit a quadratic spline through this point, measure errors on every point in the sequence\n\t\t// helpers and projecting to get control point\n\t\tdouble t=(fitpoint-seqstart)/tl, t1=(1.0-t)*(1.0-t), t2=2.0*(1.0-t)*t, t3=t*t;\n\t\tdouble cpx = (((t1*path.get(seqstart)[0]) + (t3*path.get(seqend)[0])) - path.get(fitpoint)[0])/-t2 ,\n\t\t\t\tcpy = (((t1*path.get(seqstart)[1]) + (t3*path.get(seqend)[1])) - path.get(fitpoint)[1])/-t2 ;\n\n\t\t// Check every point\n\t\tpcnt = seqstart+1;\n\t\twhile(pcnt != seqend){\n\n\t\t\tt=(pcnt-seqstart)/tl; t1=(1.0-t)*(1.0-t); t2=2.0*(1.0-t)*t; t3=t*t;\n\t\t\tpx = (t1 * path.get(seqstart)[0]) + (t2 * cpx) + (t3 * path.get(seqend)[0]);\n\t\t\tpy = (t1 * path.get(seqstart)[1]) + (t2 * cpy) + (t3 * path.get(seqend)[1]);\n\n\t\t\tdist2 = ((path.get(pcnt)[0]-px)*(path.get(pcnt)[0]-px)) + ((path.get(pcnt)[1]-py)*(path.get(pcnt)[1]-py));\n\n\t\t\tif(dist2>qtreshold){curvepass=false;}\n\t\t\tif(dist2>errorval){ errorpoint=pcnt; errorval=dist2; }\n\t\t\tpcnt = (pcnt+1)%pathlength;\n\t\t}\n\n\t\t// return spline if fits\n\t\tif(curvepass){\n\t\t\tsegment.add(new Double[7]);\n\t\t\tthissegment = segment.get(segment.size()-1);\n\t\t\tthissegment[0] = 2.0;\n\t\t\tthissegment[1] = path.get(seqstart)[0];\n\t\t\tthissegment[2] = path.get(seqstart)[1];\n\t\t\tthissegment[3] = cpx;\n\t\t\tthissegment[4] = cpy;\n\t\t\tthissegment[5] = path.get(seqend)[0];\n\t\t\tthissegment[6] = path.get(seqend)[1];\n\t\t\treturn segment;\n\t\t}\n\n\t\t// 5.5. If the spline fails (an error>qtreshold), find the point with the biggest error,\n\t\t// set splitpoint = (fitting point + errorpoint)/2\n\t\tint splitpoint = (fitpoint + errorpoint)/2;\n\n\t\t// 5.6. Split sequence and recursively apply 5.2. - 5.6. to startpoint-splitpoint and splitpoint-endpoint sequences\n\t\tsegment = fitseq(path,ltreshold,qtreshold,seqstart,splitpoint);\n\t\tsegment.addAll(fitseq(path,ltreshold,qtreshold,splitpoint,seqend));\n\t\treturn segment;\n\n\t}// End of fitseq()\n\n\n\t// 5. Batch tracing paths\n\tpublic static ArrayList<ArrayList<Double[]>> batchtracepaths (ArrayList<ArrayList<Double[]>> internodepaths, float ltres,float qtres){\n\t\tArrayList<ArrayList<Double[]>> btracedpaths = new ArrayList<ArrayList<Double[]>>();\n\t\tfor(int k=0; k<internodepaths.size(); k++){\n\t\t\tbtracedpaths.add(tracepath(internodepaths.get(k),ltres,qtres) );\n\t\t}\n\t\treturn btracedpaths;\n\t}\n\n\n\t// 5. Batch tracing layers\n\tpublic static ArrayList<ArrayList<ArrayList<Double[]>>> batchtracelayers (ArrayList<ArrayList<ArrayList<Double[]>>> binternodes, float ltres, float qtres){\n\t\tArrayList<ArrayList<ArrayList<Double[]>>> btbis = new ArrayList<ArrayList<ArrayList<Double[]>>>();\n\t\tfor(int k=0; k<binternodes.size(); k++){\n\t\t\tbtbis.add( batchtracepaths( binternodes.get(k),ltres,qtres) );\n\t\t}\n\t\treturn btbis;\n\t}\n\n\n\t////////////////////////////////////////////////////////////\n\t//\n\t//  SVG Drawing functions\n\t//\n\t////////////////////////////////////////////////////////////\n\n\tpublic static float roundtodec (float val, float places){\n\t\treturn (float)(Math.round(val*Math.pow(10,places))/Math.pow(10,places));\n\t}\n\n\n\t// Getting SVG path element string from a traced path\n\tpublic static void svgpathstring (StringBuilder sb, String desc, ArrayList<Double[]> segments, String colorstr, HashMap<String,Float> options){\n\t\tfloat scale = options.get(\"scale\"), lcpr = options.get(\"lcpr\"), qcpr = options.get(\"qcpr\"), roundcoords = (float) Math.floor(options.get(\"roundcoords\"));\n\t\t// Path\n\t\tsb.append(\"<path \").append(desc).append(colorstr).append(\"d=\\\"\" ).append(\"M \").append(segments.get(0)[1]*scale).append(\" \").append(segments.get(0)[2]*scale).append(\" \");\n\n\t\tif( roundcoords == -1 ){\n\t\t\tfor(int pcnt=0;pcnt<segments.size();pcnt++){\n\t\t\t\tif(segments.get(pcnt)[0]==1.0){\n\t\t\t\t\tsb.append(\"L \").append(segments.get(pcnt)[3]*scale).append(\" \").append(segments.get(pcnt)[4]*scale).append(\" \");\n\t\t\t\t}else{\n\t\t\t\t\tsb.append(\"Q \").append(segments.get(pcnt)[3]*scale).append(\" \").append(segments.get(pcnt)[4]*scale).append(\" \").append(segments.get(pcnt)[5]*scale).append(\" \").append(segments.get(pcnt)[6]*scale).append(\" \");\n\t\t\t\t}\n\t\t\t}\n\t\t}else{\n\t\t\tfor(int pcnt=0;pcnt<segments.size();pcnt++){\n\t\t\t\tif(segments.get(pcnt)[0]==1.0){\n\t\t\t\t\tsb.append(\"L \").append(roundtodec((float)(segments.get(pcnt)[3]*scale),roundcoords)).append(\" \")\n\t\t\t\t\t.append(roundtodec((float)(segments.get(pcnt)[4]*scale),roundcoords)).append(\" \");\n\t\t\t\t}else{\n\t\t\t\t\tsb.append(\"Q \").append(roundtodec((float)(segments.get(pcnt)[3]*scale),roundcoords)).append(\" \")\n\t\t\t\t\t.append(roundtodec((float)(segments.get(pcnt)[4]*scale),roundcoords)).append(\" \")\n\t\t\t\t\t.append(roundtodec((float)(segments.get(pcnt)[5]*scale),roundcoords)).append(\" \")\n\t\t\t\t\t.append(roundtodec((float)(segments.get(pcnt)[6]*scale),roundcoords)).append(\" \");\n\t\t\t\t}\n\t\t\t}\n\t\t}// End of roundcoords check\n\n\t\tsb.append(\"Z\\\" />\");\n\n\t\t// Rendering control points\n\t\tfor(int pcnt=0;pcnt<segments.size();pcnt++){\n\t\t\tif((lcpr>0)&&(segments.get(pcnt)[0]==1.0)){\n\t\t\t\tsb.append( \"<circle cx=\\\"\").append(segments.get(pcnt)[3]*scale).append(\"\\\" cy=\\\"\").append(segments.get(pcnt)[4]*scale).append(\"\\\" r=\\\"\").append(lcpr).append(\"\\\" fill=\\\"white\\\" stroke-width=\\\"\").append(lcpr*0.2).append(\"\\\" stroke=\\\"black\\\" />\");\n\t\t\t}\n\t\t\tif((qcpr>0)&&(segments.get(pcnt)[0]==2.0)){\n\t\t\t\tsb.append( \"<circle cx=\\\"\").append(segments.get(pcnt)[3]*scale).append(\"\\\" cy=\\\"\").append(segments.get(pcnt)[4]*scale).append(\"\\\" r=\\\"\").append(qcpr).append(\"\\\" fill=\\\"cyan\\\" stroke-width=\\\"\").append(qcpr*0.2).append(\"\\\" stroke=\\\"black\\\" />\");\n\t\t\t\tsb.append( \"<circle cx=\\\"\").append(segments.get(pcnt)[5]*scale).append(\"\\\" cy=\\\"\").append(segments.get(pcnt)[6]*scale).append(\"\\\" r=\\\"\").append(qcpr).append(\"\\\" fill=\\\"white\\\" stroke-width=\\\"\").append(qcpr*0.2).append(\"\\\" stroke=\\\"black\\\" />\");\n\t\t\t\tsb.append( \"<line x1=\\\"\").append(segments.get(pcnt)[1]*scale).append(\"\\\" y1=\\\"\").append(segments.get(pcnt)[2]*scale).append(\"\\\" x2=\\\"\").append(segments.get(pcnt)[3]*scale).append(\"\\\" y2=\\\"\").append(segments.get(pcnt)[4]*scale).append(\"\\\" stroke-width=\\\"\").append(qcpr*0.2).append(\"\\\" stroke=\\\"cyan\\\" />\");\n\t\t\t\tsb.append( \"<line x1=\\\"\").append(segments.get(pcnt)[3]*scale).append(\"\\\" y1=\\\"\").append(segments.get(pcnt)[4]*scale).append(\"\\\" x2=\\\"\").append(segments.get(pcnt)[5]*scale).append(\"\\\" y2=\\\"\").append(segments.get(pcnt)[6]*scale).append(\"\\\" stroke-width=\\\"\").append(qcpr*0.2).append(\"\\\" stroke=\\\"cyan\\\" />\");\n\t\t\t}// End of quadratic control points\n\t\t}\n\n\t}// End of svgpathstring()\n\n\n\t// Converting tracedata to an SVG string, paths are drawn according to a Z-index\n\t// the optional lcpr and qcpr are linear and quadratic control point radiuses\n\tpublic static String getsvgstring (IndexedImage ii, HashMap<String,Float> options){\n\t\toptions = checkoptions(options);\n\t\t// SVG start\n\t\tint w = (int) (ii.width * options.get(\"scale\")), h = (int) (ii.height * options.get(\"scale\"));\n\t\tString viewboxorviewport = options.get(\"viewbox\")!=0 ? \"viewBox=\\\"0 0 \"+w+\" \"+h+\"\\\" \" : \"width=\\\"\"+w+\"\\\" height=\\\"\"+h+\"\\\" \";\n\t\tStringBuilder svgstr = new StringBuilder(\"<svg \"+viewboxorviewport+\"version=\\\"1.1\\\" xmlns=\\\"http://www.w3.org/2000/svg\\\" \");\n\t\tif(options.get(\"desc\")!=0){ svgstr.append(\"desc=\\\"Created with ImageTracer.java version \"+ImageTracer.versionnumber+\"\\\" \"); }\n\t\tsvgstr.append(\">\");\n\n\t\t// creating Z-index\n\t\tTreeMap <Double,Integer[]> zindex = new TreeMap <Double,Integer[]>();\n\t\tdouble label;\n\t\t// Layer loop\n\t\tfor(int k=0; k<ii.layers.size(); k++) {\n\n\t\t\t// Path loop\n\t\t\tfor(int pcnt=0; pcnt<ii.layers.get(k).size(); pcnt++){\n\n\t\t\t\t// Label (Z-index key) is the startpoint of the path, linearized\n\t\t\t\tlabel = (ii.layers.get(k).get(pcnt).get(0)[2] * w) + ii.layers.get(k).get(pcnt).get(0)[1];\n\t\t\t\t// Creating new list if required\n\t\t\t\tif(!zindex.containsKey(label)){ zindex.put(label,new Integer[2]); }\n\t\t\t\t// Adding layer and path number to list\n\t\t\t\tzindex.get(label)[0] = new Integer(k);\n\t\t\t\tzindex.get(label)[1] = new Integer(pcnt);\n\t\t\t}// End of path loop\n\n\t\t}// End of layer loop\n\n\t\t// Sorting Z-index is not required, TreeMap is sorted automatically\n\n\t\t// Drawing\n\t\t// Z-index loop\n\t\tString thisdesc = \"\";\n\t\tfor(Entry<Double, Integer[]> entry : zindex.entrySet()) {\n\t\t\tif(options.get(\"desc\")!=0){ thisdesc = \"desc=\\\"l \"+entry.getValue()[0]+\" p \"+entry.getValue()[1]+\"\\\" \"; }else{ thisdesc = \"\"; }\n\t\t\tsvgpathstring(svgstr,\n\t\t\t\t\tthisdesc,\n\t\t\t\t\tii.layers.get(entry.getValue()[0]).get(entry.getValue()[1]),\n\t\t\t\t\ttosvgcolorstr(ii.palette[entry.getValue()[0]]),\n\t\t\t\t\toptions);\n\t\t}\n\n\t\t// SVG End\n\t\tsvgstr.append(\"</svg>\");\n\n\t\treturn svgstr.toString();\n\n\t}// End of getsvgstring()\n\n\n\tstatic String tosvgcolorstr (byte[] c){\n\t\treturn \"fill=\\\"rgb(\"+(c[0]+128)+\",\"+(c[1]+128)+\",\"+(c[2]+128)+\")\\\" stroke=\\\"rgb(\"+(c[0]+128)+\",\"+(c[1]+128)+\",\"+(c[2]+128)+\")\\\" stroke-width=\\\"1\\\" opacity=\\\"\"+((c[3]+128)/255.0)+\"\\\" \";\n\t}\n\n\n\t// Gaussian kernels for blur\n\tstatic double[][] gks = { {0.27901,0.44198,0.27901}, {0.135336,0.228569,0.272192,0.228569,0.135336}, {0.086776,0.136394,0.178908,0.195843,0.178908,0.136394,0.086776},\n\t\t\t{0.063327,0.093095,0.122589,0.144599,0.152781,0.144599,0.122589,0.093095,0.063327}, {0.049692,0.069304,0.089767,0.107988,0.120651,0.125194,0.120651,0.107988,0.089767,0.069304,0.049692} };\n\n\n\t// Selective Gaussian blur for preprocessing\n\tstatic ImageData blur (ImageData imgd, float rad, float del){\n\t\tint i,j,k,d,idx;\n\t\tdouble racc,gacc,bacc,aacc,wacc;\n\t\tImageData imgd2 = new ImageData(imgd.width,imgd.height,new byte[imgd.width*imgd.height*4]);\n\n\t\t// radius and delta limits, this kernel\n\t\tint radius = (int)Math.floor(rad); if(radius<1){ return imgd; } if(radius>5){ radius = 5; }\n\t\tint delta = (int)Math.abs(del); if(delta>1024){ delta = 1024; }\n\t\tdouble[] thisgk = gks[radius-1];\n\n\t\t// loop through all pixels, horizontal blur\n\t\tfor( j=0; j < imgd.height; j++ ){\n\t\t\tfor( i=0; i < imgd.width; i++ ){\n\n\t\t\t\tracc = 0; gacc = 0; bacc = 0; aacc = 0; wacc = 0;\n\t\t\t\t// gauss kernel loop\n\t\t\t\tfor( k = -radius; k < (radius+1); k++){\n\t\t\t\t\t// add weighted color values\n\t\t\t\t\tif( ((i+k) > 0) && ((i+k) < imgd.width) ){\n\t\t\t\t\t\tidx = ((j*imgd.width)+i+k)*4;\n\t\t\t\t\t\tracc += imgd.data[idx  ] * thisgk[k+radius];\n\t\t\t\t\t\tgacc += imgd.data[idx+1] * thisgk[k+radius];\n\t\t\t\t\t\tbacc += imgd.data[idx+2] * thisgk[k+radius];\n\t\t\t\t\t\taacc += imgd.data[idx+3] * thisgk[k+radius];\n\t\t\t\t\t\twacc += thisgk[k+radius];\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// The new pixel\n\t\t\t\tidx = ((j*imgd.width)+i)*4;\n\t\t\t\timgd2.data[idx  ] = (byte) Math.floor(racc / wacc);\n\t\t\t\timgd2.data[idx+1] = (byte) Math.floor(gacc / wacc);\n\t\t\t\timgd2.data[idx+2] = (byte) Math.floor(bacc / wacc);\n\t\t\t\timgd2.data[idx+3] = (byte) Math.floor(aacc / wacc);\n\n\t\t\t}// End of width loop\n\t\t}// End of horizontal blur\n\n\t\t// copying the half blurred imgd2\n\t\tbyte[] himgd = imgd2.data.clone();\n\n\t\t// loop through all pixels, vertical blur\n\t\tfor( j=0; j < imgd.height; j++ ){\n\t\t\tfor( i=0; i < imgd.width; i++ ){\n\n\t\t\t\tracc = 0; gacc = 0; bacc = 0; aacc = 0; wacc = 0;\n\t\t\t\t// gauss kernel loop\n\t\t\t\tfor( k = -radius; k < (radius+1); k++){\n\t\t\t\t\t// add weighted color values\n\t\t\t\t\tif( ((j+k) > 0) && ((j+k) < imgd.height) ){\n\t\t\t\t\t\tidx = (((j+k)*imgd.width)+i)*4;\n\t\t\t\t\t\tracc += himgd[idx  ] * thisgk[k+radius];\n\t\t\t\t\t\tgacc += himgd[idx+1] * thisgk[k+radius];\n\t\t\t\t\t\tbacc += himgd[idx+2] * thisgk[k+radius];\n\t\t\t\t\t\taacc += himgd[idx+3] * thisgk[k+radius];\n\t\t\t\t\t\twacc += thisgk[k+radius];\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// The new pixel\n\t\t\t\tidx = ((j*imgd.width)+i)*4;\n\t\t\t\timgd2.data[idx  ] = (byte) Math.floor(racc / wacc);\n\t\t\t\timgd2.data[idx+1] = (byte) Math.floor(gacc / wacc);\n\t\t\t\timgd2.data[idx+2] = (byte) Math.floor(bacc / wacc);\n\t\t\t\timgd2.data[idx+3] = (byte) Math.floor(aacc / wacc);\n\n\t\t\t}// End of width loop\n\t\t}// End of vertical blur\n\n\t\t// Selective blur: loop through all pixels\n\t\tfor( j=0; j < imgd.height; j++ ){\n\t\t\tfor( i=0; i < imgd.width; i++ ){\n\n\t\t\t\tidx = ((j*imgd.width)+i)*4;\n\t\t\t\t// d is the difference between the blurred and the original pixel\n\t\t\t\td = Math.abs(imgd2.data[idx  ] - imgd.data[idx  ]) + Math.abs(imgd2.data[idx+1] - imgd.data[idx+1]) +\n\t\t\t\t\t\tMath.abs(imgd2.data[idx+2] - imgd.data[idx+2]) + Math.abs(imgd2.data[idx+3] - imgd.data[idx+3]);\n\t\t\t\t// selective blur: if d>delta, put the original pixel back\n\t\t\t\tif(d>delta){\n\t\t\t\t\timgd2.data[idx  ] = imgd.data[idx  ];\n\t\t\t\t\timgd2.data[idx+1] = imgd.data[idx+1];\n\t\t\t\t\timgd2.data[idx+2] = imgd.data[idx+2];\n\t\t\t\t\timgd2.data[idx+3] = imgd.data[idx+3];\n\t\t\t\t}\n\t\t\t}\n\t\t}// End of Selective blur\n\n\t\treturn imgd2;\n\n\t}// End of blur()\n\n\n}// End of ImageTracer class"
  },
  {
    "path": "process_overview.md",
    "content": "### Process overview\n#### 1. Color quantization\nThe **colorquantization** function creates an indexed image (https://en.wikipedia.org/wiki/Indexed_color)\n\n![alt Original image (20x scale)](docimages/s2.png)\n\n#### 2. Layer separation and edge detection\nThe **layering** function creates arrays for every color, and calculates edge node types. These are at the center of every 4 pixels, shown here as dots.\n\n![alt layer 0: black](docimages/s3.png)\n![alt layer 1: yellow](docimages/s4.png)\n![alt edge node examples](docimages/s7.png)\n\n#### 3. Pathscan\nThe **pathscan** function finds chains of edge nodes, example: the cyan dots and lines.\n\n![alt an edge node path](docimages/s8.png)\n\n#### 4. Interpolation\nThe **internodes** function interpolates the coordinates of the edge node paths. Every line segment in the new path has one of the 8 directions (East, North East, N, NW, W, SW, S, SE).\n\n![alt interpolating](docimages/s9.png)\n![alt interpolation result](docimages/s10.png)\n\n#### 5. Tracing\nThe **tracepath** function splits the interpolated paths into sequences with two directions.\n\n![alt a sequence](docimages/s11.png)\n\nThe **fitseq** function tries to fit a straight line on the start- and endpoint of the sequence (black line). If the distance error between the calculated points (black line) and actual sequence points (blue dots) is greater than the treshold, the point with the greatest error is selected (red line).\n\n![alt fitting a straight line](docimages/s12.png)\n\nThe **fitseq** function tries to fit a quadratic spline through the error point.\n\n![alt fitting a quadratic spline](docimages/s13.png)\n![alt fitting line segments](docimages/s14.png) \n![alt result with control points](docimages/s15.png)\n\nIf the **fitseq** function can not fit a straight line or a quadratic spline to the sequence with the given error tresholds, then it will split the sequence in two and recursively call **fitseq** on each part.\n\n#### 6. SVG rendering\nThe coordinates are rendered to [SVG Paths](https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths) in the **getsvgstring** function.\n\n### Ideas for improvement\n- Error handling: there's very little error handling now, Out of memory can happen easily with big images or many layers.\n- Color quantization: other algorithms?\n- Color quantization: colors with few pixels are randomized, but probably the most distant colors should be found instead.\n- Tracing: 5.1. finding more suitable sequences.\n- Tracing: 5.5. Set splitpoint = (fitting point + errorpoint)/2 ; this is just a guess, there might be a better splitpoint.\n- Tracing: 5.7. If splitpoint-endpoint is a spline, try to add new points from the next sequence; this is not implemented.\n- Tracing: cubic splines or other curves?\n- Default values: they are chosen because they seemed OK, not based on calculations.\n- Output: [PDF](https://en.wikipedia.org/wiki/Portable_Document_Format), [DXF](https://en.wikipedia.org/wiki/AutoCAD_DXF),   [G-code](https://en.wikipedia.org/wiki/G-code) or other output?\n"
  }
]