master 135f00e5e638 cached
12 files
58.4 KB
18.5k tokens
37 symbols
1 requests
Download .txt
Repository: jankovicsandras/imagetracerjava
Branch: master
Commit: 135f00e5e638
Files: 12
Total size: 58.4 KB

Directory structure:
gitextract_s987juft/

├── .classpath
├── .gitattributes
├── .gitignore
├── .project
├── .settings/
│   ├── org.eclipse.core.resources.prefs
│   └── org.eclipse.jdt.core.prefs
├── ImageTracer.jar
├── LICENSE
├── README.md
├── deterministic.md
├── jankovicsandras/
│   └── imagetracer/
│       └── ImageTracer.java
└── process_overview.md

================================================
FILE CONTENTS
================================================

================================================
FILE: .classpath
================================================
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
	<classpathentry kind="src" path=""/>
	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
	<classpathentry kind="output" path="bin"/>
</classpath>


================================================
FILE: .gitattributes
================================================
# Auto detect text files and perform LF normalization
* text=auto

# Custom for Visual Studio
*.cs     diff=csharp

# Standard to msysgit
*.doc	 diff=astextplain
*.DOC	 diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot  diff=astextplain
*.DOT  diff=astextplain
*.pdf  diff=astextplain
*.PDF	 diff=astextplain
*.rtf	 diff=astextplain
*.RTF	 diff=astextplain


================================================
FILE: .gitignore
================================================
# Windows image file caches
Thumbs.db
ehthumbs.db

# Folder config file
Desktop.ini

# Recycle Bin used on file shares
$RECYCLE.BIN/

# Windows Installer files
*.cab
*.msi
*.msm
*.msp

# Windows shortcuts
*.lnk

# =========================
# Operating System Files
# =========================

# OSX
# =========================

.DS_Store
.AppleDouble
.LSOverride

# Thumbnails
._*

# Files that might appear on external disk
.Spotlight-V100
.Trashes

# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
/bin

================================================
FILE: .project
================================================
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
	<name>jankovicsandras.imagetracer-java</name>
	<comment></comment>
	<projects>
	</projects>
	<buildSpec>
		<buildCommand>
			<name>org.eclipse.jdt.core.javabuilder</name>
			<arguments>
			</arguments>
		</buildCommand>
	</buildSpec>
	<natures>
		<nature>org.eclipse.jdt.core.javanature</nature>
	</natures>
</projectDescription>


================================================
FILE: .settings/org.eclipse.core.resources.prefs
================================================
eclipse.preferences.version=1
encoding//jankovicsandras/imagetracer/ImageTracer.java=UTF-8


================================================
FILE: .settings/org.eclipse.jdt.core.prefs
================================================
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.source=1.8


================================================
FILE: LICENSE
================================================
This is free and unencumbered software released into the public domain.

Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.

In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

For more information, please refer to <http://unlicense.org>



================================================
FILE: README.md
================================================
# imagetracerjava
![alt Bitmap to Svg](docimages/s1.png)

Simple raster image tracer and vectorizer written in Java for desktop. See https://github.com/jankovicsandras/imagetracerandroid for the Android version.

by András Jankovics

This is a port of imagetracer.js: https://github.com/jankovicsandras/imagetracerjs

### Check this out for a refactored version with better color quantization algorithm: https://github.com/miguelemosreverte/imagetracerjava

### Check this out for a C++ port: https://github.com/dov/ImageTracerCpp

### 1.1.2

- minor bugfixes
- lookup based ```pathscan()```

### 1.1.1

- 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)
- transparency support: alpha is not discarded now, it is given more weight in color quantization
- new ```options.roundcoords``` : rounding coordinates to a given decimal place. This can reduce SVG length significantly (>20%) with minor loss of precision.
- new ```options.desc``` : setting this to false will turn off path descriptions, reducing SVG length.
- new ```options.viewbox``` : setting this to true will use viewBox instead of exact width and height
- new ```options.colorsampling``` : color quantization will sample the colors now by default, can be turned off.
- 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.
- ```IndexedImage``` has width and height
- ```getsvgstring()``` needs now only ```IndexedImage``` (tracedata) and ```options``` as parameters
- ```colorquantization()``` needs now only ```imgd```, ```palette``` and ```options``` as parameters
- background field is removed from the results of color quantization 

### Running as a standalone program 

Warning: if the outfilename parameter is not specified, then this will overwrite <filename>.svg .

Basic usage: 
```bash
java -jar ImageTracer.jar smiley.png
```

With options:
```bash
java -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
```

### Including in Java projects
Add ImageTracer.jar to your build path, import, then use the static methods:
```java
import jankovicsandras.imagetracer.ImageTracer;

...

ImageTracer.saveString(
				"output.svg" ,
				ImageTracer.imageToSVG("input.jpg",null,null)
);
```

With options and palette
```java
// Options
HashMap<String,Float> options = new HashMap<String,Float>();

// Tracing
options.put("ltres",1f);
options.put("qtres",1f);
options.put("pathomit",8f);

// Color quantization
options.put("colorsampling",1f); // 1f means true ; 0f means false: starting with generated palette
options.put("numberofcolors",16f);
options.put("mincolorratio",0.02f);
options.put("colorquantcycles",3f);

// SVG rendering
options.put("scale",1f);
options.put("roundcoords",1f); // 1f means rounded to 1 decimal places, like 7.3 ; 3f means rounded to 3 places, like 7.356 ; etc.
options.put("lcpr",0f);
options.put("qcpr",0f);
options.put("desc",1f); // 1f means true ; 0f means false: SVG descriptions deactivated
options.put("viewbox",0f); // 1f means true ; 0f means false: fixed width and height

// Selective Gauss Blur
options.put("blurradius",0f); // 0f means deactivated; 1f .. 5f : blur with this radius
options.put("blurdelta",20f); // smaller than this RGB difference will be blurred

// Palette
// This is an example of a grayscale palette
// please note that signed byte values [ -128 .. 127 ] will be converted to [ 0 .. 255 ] in the getsvgstring function
byte[][] palette = new byte[8][4];
for(int colorcnt=0; colorcnt < 8; colorcnt++){
	palette[colorcnt][0] = (byte)( -128 + colorcnt * 32); // R
	palette[colorcnt][1] = (byte)( -128 + colorcnt * 32); // G
	palette[colorcnt][2] = (byte)( -128 + colorcnt * 32); // B
	palette[colorcnt][3] = (byte)127; 		      // A
}

ImageTracer.saveString(
				"output.svg" ,
				ImageTracer.imageToSVG("input.jpg",options,palette)
);
```

### Deterministic output
See [options for deterministic tracing](https://github.com/jankovicsandras/imagetracerjava/blob/master/deterministic.md)


### Main Functions
|Function name|Arguments|Returns|
|-------------|---------|-------|
|```imageToSVG```|```String filename, HashMap<String,Float> options /*can be null*/, byte [][] palette /*can be null*/```|```String /*SVG content*/```|
|```imageToSVG```|```BufferedImage image, HashMap<String,Float> options /*can be null*/, byte [][] palette /*can be null*/```|```String /*SVG content*/```|
|```imagedataToSVG```|```ImageData imgd, HashMap<String,Float> options /*can be null*/, byte [][] palette /*can be null*/```|```String /*SVG content*/```|
|```imageToTracedata```|```String filename, HashMap<String,Float> options /*can be null*/, byte [][] palette /*can be null*/```|```IndexedImage /*read the source for details*/```|
|```imageToTracedata```|```BufferedImage image, HashMap<String,Float> options /*can be null*/, byte [][] palette /*can be null*/```|```IndexedImage /*read the source for details*/```|
|```imagedataToTracedata```|```ImageData imgd, HashMap<String,Float> options /*can be null*/, byte [][] palette /*can be null*/```|```IndexedImage /*read the source for details*/```|

	
#### Helper Functions
|Function name|Arguments|Returns|
|-------------|---------|-------|
|```saveString```|```String filename, String str```|```void```|
|```loadImageData```|```String filename```|```ImageData /*read the source for details*/```|
|```loadImageData```|```BufferedImage image```|```ImageData /*read the source for details*/```|

```ImageData``` is similar to [ImageData](https://developer.mozilla.org/en-US/docs/Web/API/ImageData) here.

There are more functions for advanced users, read the source if you are interested. :)
	
### Options
|Option name|Default value|Meaning|
|-----------|-------------|-------|
|```ltres```|```1f```|Error treshold for straight lines.|
|```qtres```|```1f```|Error treshold for quadratic splines.|
|```pathomit```|```8f```|Edge node paths shorter than this will be discarded for noise reduction.|
|```colorsampling```|```1f```|Enable or disable color sampling. 1f is on, 0f is off.|
|```numberofcolors```|```16f```|Number of colors to use on palette if pal object is not defined.|
|```mincolorratio```|```0.02f```|Color quantization will randomize a color if fewer pixels than (total pixels*mincolorratio) has it.|
|```colorquantcycles```|```3f```|Color quantization will be repeated this many times.|
|```blurradius```|```0f```|Set this to 1f..5f for selective Gaussian blur preprocessing.|
|```blurdelta```|```20f```|RGBA delta treshold for selective Gaussian blur preprocessing.|
|```scale```|```1f```|Every coordinate will be multiplied with this, to scale the SVG.|
|```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|
|```viewbox```|```0f```|Enable or disable SVG viewBox. 1f is on, 0f is off.|
|```desc```|```1f```|Enable or disable SVG descriptions. 1f is on, 0f is off.|
|```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.|
|```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.|

### Process overview
See [Process overview and Ideas for improvement](https://github.com/jankovicsandras/imagetracerjava/blob/master/process_overview.md)

### License
#### The Unlicense / PUBLIC DOMAIN

This is free and unencumbered software released into the public domain.

Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.

In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

For more information, please refer to [http://unlicense.org](http://unlicense.org)


================================================
FILE: deterministic.md
================================================
## TLDR; options for deterministic tracing:

custom palette, `colorquantcycles`:1

custom palette, `mincolorratio`:0

`colorsampling`:0 (false), `mincolorratio`:0, `numberofcolors`<8

`colorsampling`:0 (false), `mincolorratio`:0, `numberofcolors`:n^3 eg. 8, 27...

`colorsampling`:0 (false), `colorquantcycles`:1, `numberofcolors`<8

`colorsampling`:0 (false), `colorquantcycles`:1, `numberofcolors`:n^3 eg. 8, 27...

---

## The long story: ☺

Only color quantization uses randomization, all the other processing steps are deterministic.

There 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:

### 1. There are 3 ways to create the initial palette before color clustering, listed by priority:
- use a custom palette (deterministic) IF it's defined ELSE
- sample the input image randomly (non-deterministic) IF `colorsampling` is 1 (true, the default) ELSE 
- generate a palette
  - grayscale (deterministic) IF `numberofcolors`<8 ELSE
  - RGB cubic grid (deterministic) "from the cubic part of" `numberofcolors` AND
  - random colors (non-deterministic) "from the rest of" `numberofcolors`

So to create a deterministic initial palette: 
- use custom palette OR 
- set `colorsampling`:0 (false) AND 
  - use less than 8 colors eg. `numberofcolors`:7 OR
  - set `numberofcolors` to a cubic number eg. 8, 27, 64, 125...

### 2. Clusters which have very few members, can be "recycled" to improve clustering: 
the 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".

IF the clustering is not repeated ( `colorquantcycles`:1 ) OR no color will be recycled ( `mincolorratio`:0 ) THEN this will be deterministic.

These design choices were made so that the color quantization would be:
- flexible : the user can use a custom palette or tweak many parameters
- 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.
- simple to implement.



================================================
FILE: jankovicsandras/imagetracer/ImageTracer.java
================================================
/*
	ImageTracer.java
	(Desktop version with javax.imageio. See ImageTracerAndroid.java for the Android version.)
	Simple raster image tracer and vectorizer written in Java. This is a port of imagetracer.js.
	by András Jankovics 2015, 2016
	andras@jankovics.net

 */

/*

The Unlicense / PUBLIC DOMAIN

This is free and unencumbered software released into the public domain.

Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.

In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

For more information, please refer to http://unlicense.org/

 */
package jankovicsandras.imagetracer;

import java.awt.image.BufferedImage;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.TreeMap;

import javax.imageio.ImageIO;

public class ImageTracer{

	public static String versionnumber = "1.1.2";

	public ImageTracer(){}

	public static void main (String[] args){
		try{

			if(args.length<1){
				System.out.println("ERROR: there's no input filename. Basic usage: \r\n\r\njava -jar ImageTracer.jar <filename>"+
						"\r\n\r\nor\r\n\r\njava -jar ImageTracer.jar help");
			} else if(arraycontains(args,"help")>-1){
				System.out.println("Example usage:\r\n\r\njava -jar ImageTracer.jar <filename> outfilename test.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 \r\n"+
						"\r\nOnly <filename> is mandatory, if some of the other optional parameters are missing, they will be set to these defaults. "+
						"\r\nWarning: if outfilename is not specified, then <filename>.svg will be overwritten."+
						"\r\nSee https://github.com/jankovicsandras/imagetracerjava for details. \r\nThis is version "+versionnumber);
			} else {

				// Parameter parsing
				String outfilename = args[0]+".svg";
				HashMap<String,Float> options = new HashMap<String,Float>();
				String[] parameternames = {"ltres","qtres","pathomit","colorsampling","numberofcolors","mincolorratio","colorquantcycles","scale","simplifytolerance","roundcoords","lcpr","qcpr","desc","viewbox","blurradius","blurdelta","outfilename"};
				int j = -1; float f = -1;
				for (String parametername : parameternames) {
					j = arraycontains(args,parametername);
					if(j>-1){
						if(parametername=="outfilename"){
							if( j < (args.length-1)){ outfilename = args[j+1]; }
						}else{
							f = parsenext(args,j); if(f>-1){ options.put(parametername, new Float(f)); }
						}
					}
				}// End of parameternames loop

				// Loading image, tracing, rendering SVG, saving SVG file
				saveString(outfilename,imageToSVG(args[0],options,null));

			}// End of parameter parsing and processing

		}catch(Exception e){ e.printStackTrace(); }
	}// End of main()


	public static int arraycontains (String [] arr, String str){
		for(int j=0; j<arr.length; j++ ){ if(arr[j].toLowerCase().equals(str)){ return j; } } return -1;
	}


	public static float parsenext (String [] arr, int i){
		if(i<(arr.length-1)){ try{ return Float.parseFloat(arr[i+1]); }catch(Exception e){} } return -1;
	}


	// Container for the color-indexed image before and tracedata after vectorizing
	public static class IndexedImage{
		public int width, height;
		public int [][] array; // array[x][y] of palette colors
		public byte [][] palette;// array[palettelength][4] RGBA color palette
		public ArrayList<ArrayList<ArrayList<Double[]>>> layers;// tracedata

		public IndexedImage(int [][] marray, byte [][] mpalette){
			array = marray; palette = mpalette;
			width = marray[0].length-2; height = marray.length-2;// Color quantization adds +2 to the original width and height
		}
	}


	// https://developer.mozilla.org/en-US/docs/Web/API/ImageData
	public static class ImageData{
		public int width, height;
		public byte[] data; // raw byte data: R G B A R G B A ...
		public ImageData(int mwidth, int mheight, byte[] mdata){
			width = mwidth; height = mheight; data = mdata;
		}
	}


	// Saving a String as a file
	public static void saveString (String filename, String str) throws Exception {
		File file = new File(filename);
		// if file doesnt exists, then create it
		if(!file.exists()){ file.createNewFile(); }
		FileWriter fw = new FileWriter(file.getAbsoluteFile());
		BufferedWriter bw = new BufferedWriter(fw);
		bw.write(str);
		bw.close();
	}


	// Loading a file to ImageData, ARGB byte order
	public static ImageData loadImageData (String filename) throws Exception {
		BufferedImage image = ImageIO.read(new File(filename));
		return loadImageData(image);
	}
	public static ImageData loadImageData (BufferedImage image) throws Exception {
		int width = image.getWidth(); int height = image.getHeight();
		int[] rawdata = image.getRGB(0, 0, width, height, null, 0, width);
		byte[] data = new byte[rawdata.length*4];
		for(int i=0; i<rawdata.length; i++){
			data[(i*4)+3] = bytetrans((byte)(rawdata[i] >>> 24));
			data[i*4  ] = bytetrans((byte)(rawdata[i] >>> 16));
			data[(i*4)+1] = bytetrans((byte)(rawdata[i] >>> 8));
			data[(i*4)+2] = bytetrans((byte)(rawdata[i]));
		}
		return new ImageData(width,height,data);
	}


	// The bitshift method in loadImageData creates signed bytes where -1 -> 255 unsigned ; -128 -> 128 unsigned ;
	// 127 -> 127 unsigned ; 0 -> 0 unsigned ; These will be converted to -128 (representing 0 unsigned) ...
	// 127 (representing 255 unsigned) and tosvgcolorstr will add +128 to create RGB values 0..255
	public static byte bytetrans (byte b){
		if(b<0){ return (byte)(b+128); }else{ return (byte)(b-128); }
	}


	////////////////////////////////////////////////////////////
	//
	//  User friendly functions
	//
	////////////////////////////////////////////////////////////

	// Loading an image from a file, tracing when loaded, then returning the SVG String
	public static String imageToSVG (String filename, HashMap<String,Float> options, byte [][] palette) throws Exception{
		options = checkoptions(options);
		ImageData imgd = loadImageData(filename);
		return imagedataToSVG(imgd,options,palette);
	}// End of imageToSVG()
	public static String imageToSVG (BufferedImage image, HashMap<String,Float> options, byte [][] palette) throws Exception{
		options = checkoptions(options);
		ImageData imgd = loadImageData(image);
		return imagedataToSVG(imgd,options,palette);
	}// End of imageToSVG()


	// Tracing ImageData, then returning the SVG String
	public static String imagedataToSVG (ImageData imgd, HashMap<String,Float> options, byte [][] palette){
		options = checkoptions(options);
		IndexedImage ii = imagedataToTracedata(imgd,options,palette);
		return getsvgstring(ii, options);
	}// End of imagedataToSVG()


	// Loading an image from a file, tracing when loaded, then returning IndexedImage with tracedata in layers
	public IndexedImage imageToTracedata (String filename, HashMap<String,Float> options, byte [][] palette) throws Exception{
		options = checkoptions(options);
		ImageData imgd = loadImageData(filename);
		return imagedataToTracedata(imgd,options,palette);
	}// End of imageToTracedata()
	public IndexedImage imageToTracedata (BufferedImage image, HashMap<String,Float> options, byte [][] palette) throws Exception{
		options = checkoptions(options);
		ImageData imgd = loadImageData(image);
		return imagedataToTracedata(imgd,options,palette);
	}// End of imageToTracedata()


	// Tracing ImageData, then returning IndexedImage with tracedata in layers
	public static IndexedImage imagedataToTracedata (ImageData imgd, HashMap<String,Float> options, byte [][] palette){
		// 1. Color quantization
		IndexedImage ii = colorquantization(imgd, palette, options);
		// 2. Layer separation and edge detection
		int[][][] rawlayers = layering(ii);
		// 3. Batch pathscan
		ArrayList<ArrayList<ArrayList<Integer[]>>> bps = batchpathscan(rawlayers,(int)(Math.floor(options.get("pathomit"))));
		// 4. Batch interpollation
		ArrayList<ArrayList<ArrayList<Double[]>>> bis = batchinternodes(bps);
		// 5. Batch tracing
		ii.layers = batchtracelayers(bis,options.get("ltres"),options.get("qtres"));
		return ii;
	}// End of imagedataToTracedata()


	// creating options object, setting defaults for missing values
	public static HashMap<String,Float> checkoptions (HashMap<String,Float> options){
		if(options==null){ options = new HashMap<String,Float>(); }
		// Tracing
		if(!options.containsKey("ltres")){ options.put("ltres",1f); }
		if(!options.containsKey("qtres")){ options.put("qtres",1f); }
		if(!options.containsKey("pathomit")){ options.put("pathomit",8f); }
		// Color quantization
		if(!options.containsKey("colorsampling")){ options.put("colorsampling",1f); }
		if(!options.containsKey("numberofcolors")){ options.put("numberofcolors",16f); }
		if(!options.containsKey("mincolorratio")){ options.put("mincolorratio",0.02f); }
		if(!options.containsKey("colorquantcycles")){ options.put("colorquantcycles",3f); }
		// SVG rendering
		if(!options.containsKey("scale")){ options.put("scale",1f); }
		if(!options.containsKey("simplifytolerance")){ options.put("simplifytolerance",0f); }
		if(!options.containsKey("roundcoords")){ options.put("roundcoords",1f); }
		if(!options.containsKey("lcpr")){ options.put("lcpr",0f); }
		if(!options.containsKey("qcpr")){ options.put("qcpr",0f); }
		if(!options.containsKey("desc")){ options.put("desc",1f); }
		if(!options.containsKey("viewbox")){ options.put("viewbox",0f); }
		// Blur
		if(!options.containsKey("blurradius")){ options.put("blurradius",0f); }
		if(!options.containsKey("blurdelta")){ options.put("blurdelta",20f); }

		return options;
	}// End of checkoptions()


	////////////////////////////////////////////////////////////
	//
	//  Vectorizing functions
	//
	////////////////////////////////////////////////////////////

	// 1. Color quantization repeated "cycles" times, based on K-means clustering
	// https://en.wikipedia.org/wiki/Color_quantization    https://en.wikipedia.org/wiki/K-means_clustering
	public static IndexedImage colorquantization (ImageData imgd, byte [][] palette, HashMap<String,Float> options){
		int numberofcolors = (int)Math.floor(options.get("numberofcolors")); float minratio = options.get("mincolorratio"); int cycles = (int)Math.floor(options.get("colorquantcycles"));
		// Creating indexed color array arr which has a boundary filled with -1 in every direction
		int [][] arr = new int[imgd.height+2][imgd.width+2];
		for(int j=0; j<(imgd.height+2); j++){ arr[j][0] = -1; arr[j][imgd.width+1 ] = -1; }
		for(int i=0; i<(imgd.width+2) ; i++){ arr[0][i] = -1; arr[imgd.height+1][i] = -1; }

		int idx=0, cd,cdl,ci,c1,c2,c3,c4;

		// Use custom palette if pal is defined or sample or generate custom length palette
		if(palette==null){
			if(options.get("colorsampling")!=0){
				palette = samplepalette(numberofcolors,imgd);
			}else{
				palette = generatepalette(numberofcolors);
			}
		}

		// Selective Gaussian blur preprocessing
		if( options.get("blurradius") > 0 ){ imgd = blur( imgd, options.get("blurradius"), options.get("blurdelta") ); }

		long [][] paletteacc = new long[palette.length][5];

		// Repeat clustering step "cycles" times
		for(int cnt=0;cnt<cycles;cnt++){

			// Average colors from the second iteration
			if(cnt>0){
				// averaging paletteacc for palette
				float ratio;
				for(int k=0;k<palette.length;k++){
					// averaging
					if(paletteacc[k][3]>0){
						palette[k][0] = (byte) (-128 + (paletteacc[k][0] / paletteacc[k][4]));
						palette[k][1] = (byte) (-128 + (paletteacc[k][1] / paletteacc[k][4]));
						palette[k][2] = (byte) (-128 + (paletteacc[k][2] / paletteacc[k][4]));
						palette[k][3] = (byte) (-128 + (paletteacc[k][3] / paletteacc[k][4]));
					}
					ratio = (float)( (double)(paletteacc[k][4]) / (double)(imgd.width*imgd.height) );

					// Randomizing a color, if there are too few pixels and there will be a new cycle
					if( (ratio<minratio) && (cnt<(cycles-1)) ){
						palette[k][0] = (byte) (-128+Math.floor(Math.random()*255));
						palette[k][1] = (byte) (-128+Math.floor(Math.random()*255));
						palette[k][2] = (byte) (-128+Math.floor(Math.random()*255));
						palette[k][3] = (byte) (-128+Math.floor(Math.random()*255));
					}

				}// End of palette loop
			}// End of Average colors from the second iteration

			// Reseting palette accumulator for averaging
			for(int i=0;i<palette.length;i++){
				paletteacc[i][0]=0;
				paletteacc[i][1]=0;
				paletteacc[i][2]=0;
				paletteacc[i][3]=0;
				paletteacc[i][4]=0;
			}

			// loop through all pixels
			for(int j=0;j<imgd.height;j++){
				for(int i=0;i<imgd.width;i++){

					idx = ((j*imgd.width)+i)*4;

					// find closest color from palette by measuring (rectilinear) color distance between this pixel and all palette colors
					cdl = 256+256+256+256; ci=0;
					for(int k=0;k<palette.length;k++){

						// In my experience, https://en.wikipedia.org/wiki/Rectilinear_distance works better than https://en.wikipedia.org/wiki/Euclidean_distance
						c1 = Math.abs(palette[k][0]-imgd.data[idx]);
						c2 = Math.abs(palette[k][1]-imgd.data[idx+1]);
						c3 = Math.abs(palette[k][2]-imgd.data[idx+2]);
						c4 = Math.abs(palette[k][3]-imgd.data[idx+3]);
						cd = c1+c2+c3+(c4*4); // weighted alpha seems to help images with transparency

						// Remember this color if this is the closest yet
						if(cd<cdl){ cdl = cd; ci = k; }

					}// End of palette loop

					// add to palettacc
					paletteacc[ci][0] += 128+imgd.data[idx];
					paletteacc[ci][1] += 128+imgd.data[idx+1];
					paletteacc[ci][2] += 128+imgd.data[idx+2];
					paletteacc[ci][3] += 128+imgd.data[idx+3];
					paletteacc[ci][4]++;

					arr[j+1][i+1] = ci;
				}// End of i loop
			}// End of j loop

		}// End of Repeat clustering step "cycles" times

		return new IndexedImage(arr, palette);
	}// End of colorquantization


	// Generating a palette with numberofcolors, array[numberofcolors][4] where [i][0] = R ; [i][1] = G ; [i][2] = B ; [i][3] = A
	public static byte[][] generatepalette (int numberofcolors){
		byte [][] palette = new byte[numberofcolors][4];
		if(numberofcolors<8){

			// Grayscale
			double graystep = 255.0/(double)(numberofcolors-1);
			for(byte ccnt=0;ccnt<numberofcolors;ccnt++){
				palette[ccnt][0] = (byte)(-128+Math.round(ccnt*graystep));
				palette[ccnt][1] = (byte)(-128+Math.round(ccnt*graystep));
				palette[ccnt][2] = (byte)(-128+Math.round(ccnt*graystep));
				palette[ccnt][3] = (byte)127;
			}

		}else{

			// RGB color cube
			int colorqnum = (int) Math.floor(Math.pow(numberofcolors, 1.0/3.0)); // Number of points on each edge on the RGB color cube
			int colorstep = (int) Math.floor(255/(colorqnum-1)); // distance between points
			int ccnt = 0;
			for(int rcnt=0;rcnt<colorqnum;rcnt++){
				for(int gcnt=0;gcnt<colorqnum;gcnt++){
					for(int bcnt=0;bcnt<colorqnum;bcnt++){
						palette[ccnt][0] = (byte)(-128+(rcnt*colorstep));
						palette[ccnt][1] = (byte)(-128+(gcnt*colorstep));
						palette[ccnt][2] = (byte)(-128+(bcnt*colorstep));
						palette[ccnt][3] = (byte)127;
						ccnt++;
					}// End of blue loop
				}// End of green loop
			}// End of red loop

			// Rest is random
			for(int rcnt=ccnt;rcnt<numberofcolors;rcnt++){
				palette[ccnt][0] = (byte)(-128+Math.floor(Math.random()*255));
				palette[ccnt][1] = (byte)(-128+Math.floor(Math.random()*255));
				palette[ccnt][2] = (byte)(-128+Math.floor(Math.random()*255));
				palette[ccnt][3] = (byte)(-128+Math.floor(Math.random()*255));
			}

		}// End of numberofcolors check

		return palette;
	};// End of generatepalette()


	public static byte[][] samplepalette (int numberofcolors, ImageData imgd){
		int idx=0; byte [][] palette = new byte[numberofcolors][4];
		for(int i=0; i<numberofcolors; i++){
			idx = (int) (Math.floor( (Math.random() * imgd.data.length) / 4 ) * 4);
			palette[i][0] = imgd.data[idx  ];
			palette[i][1] = imgd.data[idx+1];
			palette[i][2] = imgd.data[idx+2];
			palette[i][3] = imgd.data[idx+3];
		}
		return palette;
	}// End of samplepalette()


	// 2. Layer separation and edge detection
	// Edge node types ( ▓:light or 1; ░:dark or 0 )
	// 12  ░░  ▓░  ░▓  ▓▓  ░░  ▓░  ░▓  ▓▓  ░░  ▓░  ░▓  ▓▓  ░░  ▓░  ░▓  ▓▓
	// 48  ░░  ░░  ░░  ░░  ░▓  ░▓  ░▓  ░▓  ▓░  ▓░  ▓░  ▓░  ▓▓  ▓▓  ▓▓  ▓▓
	//     0   1   2   3   4   5   6   7   8   9   10  11  12  13  14  15
	//
	public static int[][][] layering (IndexedImage ii){
		// Creating layers for each indexed color in arr
		int val=0, aw = ii.array[0].length, ah = ii.array.length, n1,n2,n3,n4,n5,n6,n7,n8;
		int[][][] layers = new int[ii.palette.length][ah][aw];

		// Looping through all pixels and calculating edge node type
		for(int j=1; j<(ah-1); j++){
			for(int i=1; i<(aw-1); i++){

				// This pixel's indexed color
				val = ii.array[j][i];

				// Are neighbor pixel colors the same?
				n1 = ii.array[j-1][i-1]==val ? 1 : 0;
				n2 = ii.array[j-1][i  ]==val ? 1 : 0;
				n3 = ii.array[j-1][i+1]==val ? 1 : 0;
				n4 = ii.array[j  ][i-1]==val ? 1 : 0;
				n5 = ii.array[j  ][i+1]==val ? 1 : 0;
				n6 = ii.array[j+1][i-1]==val ? 1 : 0;
				n7 = ii.array[j+1][i  ]==val ? 1 : 0;
				n8 = ii.array[j+1][i+1]==val ? 1 : 0;

				// this pixel"s type and looking back on previous pixels
				layers[val][j+1][i+1] = 1 + (n5 * 2) + (n8 * 4) + (n7 * 8) ;
				if(n4==0){ layers[val][j+1][i  ] = 0 + 2 + (n7 * 4) + (n6 * 8) ; }
				if(n2==0){ layers[val][j  ][i+1] = 0 + (n3*2) + (n5 * 4) + 8 ; }
				if(n1==0){ layers[val][j  ][i  ] = 0 + (n2*2) + 4 + (n4 * 8) ; }

			}// End of i loop
		}// End of j loop

		return layers;
	}// End of layering()


	// Lookup tables for pathscan
	static byte [] pathscan_dir_lookup = {0,0,3,0, 1,0,3,0, 0,3,3,1, 0,3,0,0};
	static boolean [] pathscan_holepath_lookup = {false,false,false,false, false,false,false,true, false,false,false,true, false,true,true,false };
	// pathscan_combined_lookup[ arr[py][px] ][ dir ] = [nextarrpypx, nextdir, deltapx, deltapy];
	static byte [][][] pathscan_combined_lookup = {
			{{-1,-1,-1,-1}, {-1,-1,-1,-1}, {-1,-1,-1,-1}, {-1,-1,-1,-1}},// arr[py][px]==0 is invalid
			{{ 0, 1, 0,-1}, {-1,-1,-1,-1}, {-1,-1,-1,-1}, { 0, 2,-1, 0}},
			{{-1,-1,-1,-1}, {-1,-1,-1,-1}, { 0, 1, 0,-1}, { 0, 0, 1, 0}},
			{{ 0, 0, 1, 0}, {-1,-1,-1,-1}, { 0, 2,-1, 0}, {-1,-1,-1,-1}},

			{{-1,-1,-1,-1}, { 0, 0, 1, 0}, { 0, 3, 0, 1}, {-1,-1,-1,-1}},
			{{13, 3, 0, 1}, {13, 2,-1, 0}, { 7, 1, 0,-1}, { 7, 0, 1, 0}},
			{{-1,-1,-1,-1}, { 0, 1, 0,-1}, {-1,-1,-1,-1}, { 0, 3, 0, 1}},
			{{ 0, 3, 0, 1}, { 0, 2,-1, 0}, {-1,-1,-1,-1}, {-1,-1,-1,-1}},

			{{ 0, 3, 0, 1}, { 0, 2,-1, 0}, {-1,-1,-1,-1}, {-1,-1,-1,-1}},
			{{-1,-1,-1,-1}, { 0, 1, 0,-1}, {-1,-1,-1,-1}, { 0, 3, 0, 1}},
			{{11, 1, 0,-1}, {14, 0, 1, 0}, {14, 3, 0, 1}, {11, 2,-1, 0}},
			{{-1,-1,-1,-1}, { 0, 0, 1, 0}, { 0, 3, 0, 1}, {-1,-1,-1,-1}},

			{{ 0, 0, 1, 0}, {-1,-1,-1,-1}, { 0, 2,-1, 0}, {-1,-1,-1,-1}},
			{{-1,-1,-1,-1}, {-1,-1,-1,-1}, { 0, 1, 0,-1}, { 0, 0, 1, 0}},
			{{ 0, 1, 0,-1}, {-1,-1,-1,-1}, {-1,-1,-1,-1}, { 0, 2,-1, 0}},
			{{-1,-1,-1,-1}, {-1,-1,-1,-1}, {-1,-1,-1,-1}, {-1,-1,-1,-1}}// arr[py][px]==15 is invalid
	};


	// 3. Walking through an edge node array, discarding edge node types 0 and 15 and creating paths from the rest.
	// Walk directions (dir): 0 > ; 1 ^ ; 2 < ; 3 v
	// Edge node types ( ▓:light or 1; ░:dark or 0 )
	// ░░  ▓░  ░▓  ▓▓  ░░  ▓░  ░▓  ▓▓  ░░  ▓░  ░▓  ▓▓  ░░  ▓░  ░▓  ▓▓
	// ░░  ░░  ░░  ░░  ░▓  ░▓  ░▓  ░▓  ▓░  ▓░  ▓░  ▓░  ▓▓  ▓▓  ▓▓  ▓▓
	// 0   1   2   3   4   5   6   7   8   9   10  11  12  13  14  15
	//
	public static ArrayList<ArrayList<Integer[]>> pathscan (int [][] arr,float pathomit){
		ArrayList<ArrayList<Integer[]>> paths = new ArrayList<ArrayList<Integer[]>>();
		ArrayList<Integer[]> thispath;
		int px=0,py=0,w=arr[0].length,h=arr.length,dir=0;
		boolean pathfinished=true, holepath = false;
		byte[] lookuprow;

		for(int j=0;j<h;j++){
			for(int i=0;i<w;i++){
				if((arr[j][i]!=0)&&(arr[j][i]!=15)){

					// Init
					px = i; py = j;
					paths.add(new ArrayList<Integer[]>());
					thispath = paths.get(paths.size()-1);
					pathfinished = false;

					// fill paths will be drawn, but hole paths are also required to remove unnecessary edge nodes
					dir = pathscan_dir_lookup[ arr[py][px] ]; holepath = pathscan_holepath_lookup[ arr[py][px] ];

					// Path points loop
					while(!pathfinished){

						// New path point
						thispath.add(new Integer[3]);
						thispath.get(thispath.size()-1)[0] = px-1;
						thispath.get(thispath.size()-1)[1] = py-1;
						thispath.get(thispath.size()-1)[2] = arr[py][px];

						// Next: look up the replacement, direction and coordinate changes = clear this cell, turn if required, walk forward
						lookuprow = pathscan_combined_lookup[ arr[py][px] ][ dir ];
						arr[py][px] = lookuprow[0]; dir = lookuprow[1]; px += lookuprow[2]; py += lookuprow[3];

						// Close path
						if(((px-1)==thispath.get(0)[0])&&((py-1)==thispath.get(0)[1])){
							pathfinished = true;
							// Discarding 'hole' type paths and paths shorter than pathomit
							if( (holepath) || (thispath.size()<pathomit) ){
								paths.remove(thispath);
							}
						}

					}// End of Path points loop

				}// End of Follow path

			}// End of i loop
		}// End of j loop

		return paths;
	}// End of pathscan()


	// 3. Batch pathscan
	public static ArrayList<ArrayList<ArrayList<Integer[]>>> batchpathscan (int [][][] layers, float pathomit){
		ArrayList<ArrayList<ArrayList<Integer[]>>> bpaths = new ArrayList<ArrayList<ArrayList<Integer[]>>>();
		for (int[][] layer : layers) {
			bpaths.add(pathscan(layer,pathomit));
		}
		return bpaths;
	}


	// 4. interpolating between path points for nodes with 8 directions ( East, SouthEast, S, SW, W, NW, N, NE )
	public static ArrayList<ArrayList<Double[]>> internodes (ArrayList<ArrayList<Integer[]>> paths){
		ArrayList<ArrayList<Double[]>> ins = new ArrayList<ArrayList<Double[]>>();
		ArrayList<Double[]> thisinp;
		Double[] thispoint, nextpoint = new Double[2];
		Integer[] pp1, pp2, pp3;
		int palen=0,nextidx=0,nextidx2=0;

		// paths loop
		for(int pacnt=0; pacnt<paths.size(); pacnt++){
			ins.add(new ArrayList<Double[]>());
			thisinp = ins.get(ins.size()-1);
			palen = paths.get(pacnt).size();
			// pathpoints loop
			for(int pcnt=0;pcnt<palen;pcnt++){

				// interpolate between two path points
				nextidx = (pcnt+1)%palen; nextidx2 = (pcnt+2)%palen;
				thisinp.add(new Double[3]);
				thispoint = thisinp.get(thisinp.size()-1);
				pp1 = paths.get(pacnt).get(pcnt);
				pp2 = paths.get(pacnt).get(nextidx);
				pp3 = paths.get(pacnt).get(nextidx2);
				thispoint[0] = (pp1[0]+pp2[0]) / 2.0;
				thispoint[1] = (pp1[1]+pp2[1]) / 2.0;
				nextpoint[0] = (pp2[0]+pp3[0]) / 2.0;
				nextpoint[1] = (pp2[1]+pp3[1]) / 2.0;

				// line segment direction to the next point
				if(thispoint[0] < nextpoint[0]){
					if     (thispoint[1] < nextpoint[1]){ thispoint[2] = 1.0; }// SouthEast
					else if(thispoint[1] > nextpoint[1]){ thispoint[2] = 7.0; }// NE
					else                                { thispoint[2] = 0.0; } // E
				}else if(thispoint[0] > nextpoint[0]){
					if     (thispoint[1] < nextpoint[1]){ thispoint[2] = 3.0; }// SW
					else if(thispoint[1] > nextpoint[1]){ thispoint[2] = 5.0; }// NW
					else                                { thispoint[2] = 4.0; }// W
				}else{
					if     (thispoint[1] < nextpoint[1]){ thispoint[2] = 2.0; }// S
					else if(thispoint[1] > nextpoint[1]){ thispoint[2] = 6.0; }// N
					else                                { thispoint[2] = 8.0; }// center, this should not happen
				}

			}// End of pathpoints loop
		}// End of paths loop
		return ins;
	}// End of internodes()


	// 4. Batch interpollation
	static ArrayList<ArrayList<ArrayList<Double[]>>> batchinternodes (ArrayList<ArrayList<ArrayList<Integer[]>>> bpaths){
		ArrayList<ArrayList<ArrayList<Double[]>>> binternodes = new ArrayList<ArrayList<ArrayList<Double[]>>>();
		for(int k=0; k<bpaths.size(); k++) {
			binternodes.add(internodes(bpaths.get(k)));
		}
		return binternodes;
	}


	// 5. tracepath() : recursively trying to fit straight and quadratic spline segments on the 8 direction internode path

	// 5.1. Find sequences of points with only 2 segment types
	// 5.2. Fit a straight line on the sequence
	// 5.3. If the straight line fails (an error>ltreshold), find the point with the biggest error
	// 5.4. Fit a quadratic spline through errorpoint (project this to get controlpoint), then measure errors on every point in the sequence
	// 5.5. If the spline fails (an error>qtreshold), find the point with the biggest error, set splitpoint = (fitting point + errorpoint)/2
	// 5.6. Split sequence and recursively apply 5.2. - 5.7. to startpoint-splitpoint and splitpoint-endpoint sequences
	// 5.7. TODO? If splitpoint-endpoint is a spline, try to add new points from the next sequence

	// This returns an SVG Path segment as a double[7] where
	// segment[0] ==1.0 linear  ==2.0 quadratic interpolation
	// segment[1] , segment[2] : x1 , y1
	// segment[3] , segment[4] : x2 , y2 ; middle point of Q curve, endpoint of L line
	// segment[5] , segment[6] : x3 , y3 for Q curve, should be 0.0 , 0.0 for L line
	//
	// path type is discarded, no check for path.size < 3 , which should not happen

	public static ArrayList<Double[]> tracepath (ArrayList<Double[]> path, float ltreshold, float qtreshold){
		int pcnt=0, seqend=0; double segtype1, segtype2;
		ArrayList<Double[]> smp = new ArrayList<Double[]>();
		//Double [] thissegment;
		int pathlength = path.size();

		while(pcnt<pathlength){
			// 5.1. Find sequences of points with only 2 segment types
			segtype1 = path.get(pcnt)[2]; segtype2 = -1; seqend=pcnt+1;
			while(
					((path.get(seqend)[2]==segtype1) || (path.get(seqend)[2]==segtype2) || (segtype2==-1))
					&& (seqend<(pathlength-1))){
				if((path.get(seqend)[2]!=segtype1) && (segtype2==-1)){ segtype2 = path.get(seqend)[2];}
				seqend++;
			}
			if(seqend==(pathlength-1)){ seqend = 0; }

			// 5.2. - 5.6. Split sequence and recursively apply 5.2. - 5.6. to startpoint-splitpoint and splitpoint-endpoint sequences
			smp.addAll(fitseq(path,ltreshold,qtreshold,pcnt,seqend));
			// 5.7. TODO? If splitpoint-endpoint is a spline, try to add new points from the next sequence

			// forward pcnt;
			if(seqend>0){ pcnt = seqend; }else{ pcnt = pathlength; }

		}// End of pcnt loop

		return smp;

	}// End of tracepath()


	// 5.2. - 5.6. recursively fitting a straight or quadratic line segment on this sequence of path nodes,
	// called from tracepath()
	public static ArrayList<Double[]> fitseq (ArrayList<Double[]> path, float ltreshold, float qtreshold, int seqstart, int seqend){
		ArrayList<Double[]> segment = new ArrayList<Double[]>();
		Double [] thissegment;
		int pathlength = path.size();

		// return if invalid seqend
		if((seqend>pathlength)||(seqend<0)){return segment;}

		int errorpoint=seqstart;
		boolean curvepass=true;
		double px, py, dist2, errorval=0;
		double tl = (seqend-seqstart); if(tl<0){ tl += pathlength; }
		double vx = (path.get(seqend)[0]-path.get(seqstart)[0]) / tl,
				vy = (path.get(seqend)[1]-path.get(seqstart)[1]) / tl;

		// 5.2. Fit a straight line on the sequence
		int pcnt = (seqstart+1)%pathlength;
		double pl;
		while(pcnt != seqend){
			pl = pcnt-seqstart; if(pl<0){ pl += pathlength; }
			px = path.get(seqstart)[0] + (vx * pl); py = path.get(seqstart)[1] + (vy * pl);
			dist2 = ((path.get(pcnt)[0]-px)*(path.get(pcnt)[0]-px)) + ((path.get(pcnt)[1]-py)*(path.get(pcnt)[1]-py));
			if(dist2>ltreshold){curvepass=false;}
			if(dist2>errorval){ errorpoint=pcnt; errorval=dist2; }
			pcnt = (pcnt+1)%pathlength;
		}

		// return straight line if fits
		if(curvepass){
			segment.add(new Double[7]);
			thissegment = segment.get(segment.size()-1);
			thissegment[0] = 1.0;
			thissegment[1] = path.get(seqstart)[0];
			thissegment[2] = path.get(seqstart)[1];
			thissegment[3] = path.get(seqend)[0];
			thissegment[4] = path.get(seqend)[1];
			thissegment[5] = 0.0;
			thissegment[6] = 0.0;
			return segment;
		}

		// 5.3. If the straight line fails (an error>ltreshold), find the point with the biggest error
		int fitpoint = errorpoint; curvepass = true; errorval = 0;

		// 5.4. Fit a quadratic spline through this point, measure errors on every point in the sequence
		// helpers and projecting to get control point
		double t=(fitpoint-seqstart)/tl, t1=(1.0-t)*(1.0-t), t2=2.0*(1.0-t)*t, t3=t*t;
		double cpx = (((t1*path.get(seqstart)[0]) + (t3*path.get(seqend)[0])) - path.get(fitpoint)[0])/-t2 ,
				cpy = (((t1*path.get(seqstart)[1]) + (t3*path.get(seqend)[1])) - path.get(fitpoint)[1])/-t2 ;

		// Check every point
		pcnt = seqstart+1;
		while(pcnt != seqend){

			t=(pcnt-seqstart)/tl; t1=(1.0-t)*(1.0-t); t2=2.0*(1.0-t)*t; t3=t*t;
			px = (t1 * path.get(seqstart)[0]) + (t2 * cpx) + (t3 * path.get(seqend)[0]);
			py = (t1 * path.get(seqstart)[1]) + (t2 * cpy) + (t3 * path.get(seqend)[1]);

			dist2 = ((path.get(pcnt)[0]-px)*(path.get(pcnt)[0]-px)) + ((path.get(pcnt)[1]-py)*(path.get(pcnt)[1]-py));

			if(dist2>qtreshold){curvepass=false;}
			if(dist2>errorval){ errorpoint=pcnt; errorval=dist2; }
			pcnt = (pcnt+1)%pathlength;
		}

		// return spline if fits
		if(curvepass){
			segment.add(new Double[7]);
			thissegment = segment.get(segment.size()-1);
			thissegment[0] = 2.0;
			thissegment[1] = path.get(seqstart)[0];
			thissegment[2] = path.get(seqstart)[1];
			thissegment[3] = cpx;
			thissegment[4] = cpy;
			thissegment[5] = path.get(seqend)[0];
			thissegment[6] = path.get(seqend)[1];
			return segment;
		}

		// 5.5. If the spline fails (an error>qtreshold), find the point with the biggest error,
		// set splitpoint = (fitting point + errorpoint)/2
		int splitpoint = (fitpoint + errorpoint)/2;

		// 5.6. Split sequence and recursively apply 5.2. - 5.6. to startpoint-splitpoint and splitpoint-endpoint sequences
		segment = fitseq(path,ltreshold,qtreshold,seqstart,splitpoint);
		segment.addAll(fitseq(path,ltreshold,qtreshold,splitpoint,seqend));
		return segment;

	}// End of fitseq()


	// 5. Batch tracing paths
	public static ArrayList<ArrayList<Double[]>> batchtracepaths (ArrayList<ArrayList<Double[]>> internodepaths, float ltres,float qtres){
		ArrayList<ArrayList<Double[]>> btracedpaths = new ArrayList<ArrayList<Double[]>>();
		for(int k=0; k<internodepaths.size(); k++){
			btracedpaths.add(tracepath(internodepaths.get(k),ltres,qtres) );
		}
		return btracedpaths;
	}


	// 5. Batch tracing layers
	public static ArrayList<ArrayList<ArrayList<Double[]>>> batchtracelayers (ArrayList<ArrayList<ArrayList<Double[]>>> binternodes, float ltres, float qtres){
		ArrayList<ArrayList<ArrayList<Double[]>>> btbis = new ArrayList<ArrayList<ArrayList<Double[]>>>();
		for(int k=0; k<binternodes.size(); k++){
			btbis.add( batchtracepaths( binternodes.get(k),ltres,qtres) );
		}
		return btbis;
	}


	////////////////////////////////////////////////////////////
	//
	//  SVG Drawing functions
	//
	////////////////////////////////////////////////////////////

	public static float roundtodec (float val, float places){
		return (float)(Math.round(val*Math.pow(10,places))/Math.pow(10,places));
	}


	// Getting SVG path element string from a traced path
	public static void svgpathstring (StringBuilder sb, String desc, ArrayList<Double[]> segments, String colorstr, HashMap<String,Float> options){
		float scale = options.get("scale"), lcpr = options.get("lcpr"), qcpr = options.get("qcpr"), roundcoords = (float) Math.floor(options.get("roundcoords"));
		// Path
		sb.append("<path ").append(desc).append(colorstr).append("d=\"" ).append("M ").append(segments.get(0)[1]*scale).append(" ").append(segments.get(0)[2]*scale).append(" ");

		if( roundcoords == -1 ){
			for(int pcnt=0;pcnt<segments.size();pcnt++){
				if(segments.get(pcnt)[0]==1.0){
					sb.append("L ").append(segments.get(pcnt)[3]*scale).append(" ").append(segments.get(pcnt)[4]*scale).append(" ");
				}else{
					sb.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(" ");
				}
			}
		}else{
			for(int pcnt=0;pcnt<segments.size();pcnt++){
				if(segments.get(pcnt)[0]==1.0){
					sb.append("L ").append(roundtodec((float)(segments.get(pcnt)[3]*scale),roundcoords)).append(" ")
					.append(roundtodec((float)(segments.get(pcnt)[4]*scale),roundcoords)).append(" ");
				}else{
					sb.append("Q ").append(roundtodec((float)(segments.get(pcnt)[3]*scale),roundcoords)).append(" ")
					.append(roundtodec((float)(segments.get(pcnt)[4]*scale),roundcoords)).append(" ")
					.append(roundtodec((float)(segments.get(pcnt)[5]*scale),roundcoords)).append(" ")
					.append(roundtodec((float)(segments.get(pcnt)[6]*scale),roundcoords)).append(" ");
				}
			}
		}// End of roundcoords check

		sb.append("Z\" />");

		// Rendering control points
		for(int pcnt=0;pcnt<segments.size();pcnt++){
			if((lcpr>0)&&(segments.get(pcnt)[0]==1.0)){
				sb.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\" />");
			}
			if((qcpr>0)&&(segments.get(pcnt)[0]==2.0)){
				sb.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\" />");
				sb.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\" />");
				sb.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\" />");
				sb.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\" />");
			}// End of quadratic control points
		}

	}// End of svgpathstring()


	// Converting tracedata to an SVG string, paths are drawn according to a Z-index
	// the optional lcpr and qcpr are linear and quadratic control point radiuses
	public static String getsvgstring (IndexedImage ii, HashMap<String,Float> options){
		options = checkoptions(options);
		// SVG start
		int w = (int) (ii.width * options.get("scale")), h = (int) (ii.height * options.get("scale"));
		String viewboxorviewport = options.get("viewbox")!=0 ? "viewBox=\"0 0 "+w+" "+h+"\" " : "width=\""+w+"\" height=\""+h+"\" ";
		StringBuilder svgstr = new StringBuilder("<svg "+viewboxorviewport+"version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" ");
		if(options.get("desc")!=0){ svgstr.append("desc=\"Created with ImageTracer.java version "+ImageTracer.versionnumber+"\" "); }
		svgstr.append(">");

		// creating Z-index
		TreeMap <Double,Integer[]> zindex = new TreeMap <Double,Integer[]>();
		double label;
		// Layer loop
		for(int k=0; k<ii.layers.size(); k++) {

			// Path loop
			for(int pcnt=0; pcnt<ii.layers.get(k).size(); pcnt++){

				// Label (Z-index key) is the startpoint of the path, linearized
				label = (ii.layers.get(k).get(pcnt).get(0)[2] * w) + ii.layers.get(k).get(pcnt).get(0)[1];
				// Creating new list if required
				if(!zindex.containsKey(label)){ zindex.put(label,new Integer[2]); }
				// Adding layer and path number to list
				zindex.get(label)[0] = new Integer(k);
				zindex.get(label)[1] = new Integer(pcnt);
			}// End of path loop

		}// End of layer loop

		// Sorting Z-index is not required, TreeMap is sorted automatically

		// Drawing
		// Z-index loop
		String thisdesc = "";
		for(Entry<Double, Integer[]> entry : zindex.entrySet()) {
			if(options.get("desc")!=0){ thisdesc = "desc=\"l "+entry.getValue()[0]+" p "+entry.getValue()[1]+"\" "; }else{ thisdesc = ""; }
			svgpathstring(svgstr,
					thisdesc,
					ii.layers.get(entry.getValue()[0]).get(entry.getValue()[1]),
					tosvgcolorstr(ii.palette[entry.getValue()[0]]),
					options);
		}

		// SVG End
		svgstr.append("</svg>");

		return svgstr.toString();

	}// End of getsvgstring()


	static String tosvgcolorstr (byte[] c){
		return "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)+"\" ";
	}


	// Gaussian kernels for blur
	static 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},
			{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} };


	// Selective Gaussian blur for preprocessing
	static ImageData blur (ImageData imgd, float rad, float del){
		int i,j,k,d,idx;
		double racc,gacc,bacc,aacc,wacc;
		ImageData imgd2 = new ImageData(imgd.width,imgd.height,new byte[imgd.width*imgd.height*4]);

		// radius and delta limits, this kernel
		int radius = (int)Math.floor(rad); if(radius<1){ return imgd; } if(radius>5){ radius = 5; }
		int delta = (int)Math.abs(del); if(delta>1024){ delta = 1024; }
		double[] thisgk = gks[radius-1];

		// loop through all pixels, horizontal blur
		for( j=0; j < imgd.height; j++ ){
			for( i=0; i < imgd.width; i++ ){

				racc = 0; gacc = 0; bacc = 0; aacc = 0; wacc = 0;
				// gauss kernel loop
				for( k = -radius; k < (radius+1); k++){
					// add weighted color values
					if( ((i+k) > 0) && ((i+k) < imgd.width) ){
						idx = ((j*imgd.width)+i+k)*4;
						racc += imgd.data[idx  ] * thisgk[k+radius];
						gacc += imgd.data[idx+1] * thisgk[k+radius];
						bacc += imgd.data[idx+2] * thisgk[k+radius];
						aacc += imgd.data[idx+3] * thisgk[k+radius];
						wacc += thisgk[k+radius];
					}
				}
				// The new pixel
				idx = ((j*imgd.width)+i)*4;
				imgd2.data[idx  ] = (byte) Math.floor(racc / wacc);
				imgd2.data[idx+1] = (byte) Math.floor(gacc / wacc);
				imgd2.data[idx+2] = (byte) Math.floor(bacc / wacc);
				imgd2.data[idx+3] = (byte) Math.floor(aacc / wacc);

			}// End of width loop
		}// End of horizontal blur

		// copying the half blurred imgd2
		byte[] himgd = imgd2.data.clone();

		// loop through all pixels, vertical blur
		for( j=0; j < imgd.height; j++ ){
			for( i=0; i < imgd.width; i++ ){

				racc = 0; gacc = 0; bacc = 0; aacc = 0; wacc = 0;
				// gauss kernel loop
				for( k = -radius; k < (radius+1); k++){
					// add weighted color values
					if( ((j+k) > 0) && ((j+k) < imgd.height) ){
						idx = (((j+k)*imgd.width)+i)*4;
						racc += himgd[idx  ] * thisgk[k+radius];
						gacc += himgd[idx+1] * thisgk[k+radius];
						bacc += himgd[idx+2] * thisgk[k+radius];
						aacc += himgd[idx+3] * thisgk[k+radius];
						wacc += thisgk[k+radius];
					}
				}
				// The new pixel
				idx = ((j*imgd.width)+i)*4;
				imgd2.data[idx  ] = (byte) Math.floor(racc / wacc);
				imgd2.data[idx+1] = (byte) Math.floor(gacc / wacc);
				imgd2.data[idx+2] = (byte) Math.floor(bacc / wacc);
				imgd2.data[idx+3] = (byte) Math.floor(aacc / wacc);

			}// End of width loop
		}// End of vertical blur

		// Selective blur: loop through all pixels
		for( j=0; j < imgd.height; j++ ){
			for( i=0; i < imgd.width; i++ ){

				idx = ((j*imgd.width)+i)*4;
				// d is the difference between the blurred and the original pixel
				d = Math.abs(imgd2.data[idx  ] - imgd.data[idx  ]) + Math.abs(imgd2.data[idx+1] - imgd.data[idx+1]) +
						Math.abs(imgd2.data[idx+2] - imgd.data[idx+2]) + Math.abs(imgd2.data[idx+3] - imgd.data[idx+3]);
				// selective blur: if d>delta, put the original pixel back
				if(d>delta){
					imgd2.data[idx  ] = imgd.data[idx  ];
					imgd2.data[idx+1] = imgd.data[idx+1];
					imgd2.data[idx+2] = imgd.data[idx+2];
					imgd2.data[idx+3] = imgd.data[idx+3];
				}
			}
		}// End of Selective blur

		return imgd2;

	}// End of blur()


}// End of ImageTracer class

================================================
FILE: process_overview.md
================================================
### Process overview
#### 1. Color quantization
The **colorquantization** function creates an indexed image (https://en.wikipedia.org/wiki/Indexed_color)

![alt Original image (20x scale)](docimages/s2.png)

#### 2. Layer separation and edge detection
The **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.

![alt layer 0: black](docimages/s3.png)
![alt layer 1: yellow](docimages/s4.png)
![alt edge node examples](docimages/s7.png)

#### 3. Pathscan
The **pathscan** function finds chains of edge nodes, example: the cyan dots and lines.

![alt an edge node path](docimages/s8.png)

#### 4. Interpolation
The **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).

![alt interpolating](docimages/s9.png)
![alt interpolation result](docimages/s10.png)

#### 5. Tracing
The **tracepath** function splits the interpolated paths into sequences with two directions.

![alt a sequence](docimages/s11.png)

The **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).

![alt fitting a straight line](docimages/s12.png)

The **fitseq** function tries to fit a quadratic spline through the error point.

![alt fitting a quadratic spline](docimages/s13.png)
![alt fitting line segments](docimages/s14.png) 
![alt result with control points](docimages/s15.png)

If 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.

#### 6. SVG rendering
The coordinates are rendered to [SVG Paths](https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths) in the **getsvgstring** function.

### Ideas for improvement
- Error handling: there's very little error handling now, Out of memory can happen easily with big images or many layers.
- Color quantization: other algorithms?
- Color quantization: colors with few pixels are randomized, but probably the most distant colors should be found instead.
- Tracing: 5.1. finding more suitable sequences.
- Tracing: 5.5. Set splitpoint = (fitting point + errorpoint)/2 ; this is just a guess, there might be a better splitpoint.
- Tracing: 5.7. If splitpoint-endpoint is a spline, try to add new points from the next sequence; this is not implemented.
- Tracing: cubic splines or other curves?
- Default values: they are chosen because they seemed OK, not based on calculations.
- 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?
Download .txt
gitextract_s987juft/

├── .classpath
├── .gitattributes
├── .gitignore
├── .project
├── .settings/
│   ├── org.eclipse.core.resources.prefs
│   └── org.eclipse.jdt.core.prefs
├── ImageTracer.jar
├── LICENSE
├── README.md
├── deterministic.md
├── jankovicsandras/
│   └── imagetracer/
│       └── ImageTracer.java
└── process_overview.md
Download .txt
SYMBOL INDEX (37 symbols across 1 files)

FILE: jankovicsandras/imagetracer/ImageTracer.java
  class ImageTracer (line 53) | public class ImageTracer{
    method ImageTracer (line 57) | public ImageTracer(){}
    method main (line 59) | public static void main (String[] args){
    method arraycontains (line 99) | public static int arraycontains (String [] arr, String str){
    method parsenext (line 104) | public static float parsenext (String [] arr, int i){
    class IndexedImage (line 110) | public static class IndexedImage{
      method IndexedImage (line 116) | public IndexedImage(int [][] marray, byte [][] mpalette){
    class ImageData (line 124) | public static class ImageData{
      method ImageData (line 127) | public ImageData(int mwidth, int mheight, byte[] mdata){
    method saveString (line 134) | public static void saveString (String filename, String str) throws Exc...
    method loadImageData (line 146) | public static ImageData loadImageData (String filename) throws Excepti...
    method loadImageData (line 150) | public static ImageData loadImageData (BufferedImage image) throws Exc...
    method bytetrans (line 167) | public static byte bytetrans (byte b){
    method imageToSVG (line 179) | public static String imageToSVG (String filename, HashMap<String,Float...
    method imageToSVG (line 184) | public static String imageToSVG (BufferedImage image, HashMap<String,F...
    method imagedataToSVG (line 192) | public static String imagedataToSVG (ImageData imgd, HashMap<String,Fl...
    method imageToTracedata (line 200) | public IndexedImage imageToTracedata (String filename, HashMap<String,...
    method imageToTracedata (line 205) | public IndexedImage imageToTracedata (BufferedImage image, HashMap<Str...
    method imagedataToTracedata (line 213) | public static IndexedImage imagedataToTracedata (ImageData imgd, HashM...
    method checkoptions (line 229) | public static HashMap<String,Float> checkoptions (HashMap<String,Float...
    method colorquantization (line 264) | public static IndexedImage colorquantization (ImageData imgd, byte [][...
    method generatepalette (line 364) | public static byte[][] generatepalette (int numberofcolors){
    method samplepalette (line 409) | public static byte[][] samplepalette (int numberofcolors, ImageData im...
    method layering (line 428) | public static int[][][] layering (IndexedImage ii){
    method pathscan (line 497) | public static ArrayList<ArrayList<Integer[]>> pathscan (int [][] arr,f...
    method batchpathscan (line 551) | public static ArrayList<ArrayList<ArrayList<Integer[]>>> batchpathscan...
    method internodes (line 561) | public static ArrayList<ArrayList<Double[]>> internodes (ArrayList<Arr...
    method batchinternodes (line 610) | static ArrayList<ArrayList<ArrayList<Double[]>>> batchinternodes (Arra...
    method tracepath (line 637) | public static ArrayList<Double[]> tracepath (ArrayList<Double[]> path,...
    method fitseq (line 670) | public static ArrayList<Double[]> fitseq (ArrayList<Double[]> path, fl...
    method batchtracepaths (line 762) | public static ArrayList<ArrayList<Double[]>> batchtracepaths (ArrayLis...
    method batchtracelayers (line 772) | public static ArrayList<ArrayList<ArrayList<Double[]>>> batchtracelaye...
    method roundtodec (line 787) | public static float roundtodec (float val, float places){
    method svgpathstring (line 793) | public static void svgpathstring (StringBuilder sb, String desc, Array...
    method getsvgstring (line 840) | public static String getsvgstring (IndexedImage ii, HashMap<String,Flo...
    method tosvgcolorstr (line 891) | static String tosvgcolorstr (byte[] c){
    method blur (line 902) | static ImageData blur (ImageData imgd, float rad, float del){
Condensed preview — 12 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (65K chars).
[
  {
    "path": ".classpath",
    "chars": 223,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<classpath>\n\t<classpathentry kind=\"src\" path=\"\"/>\n\t<classpathentry kind=\"con\" pat"
  },
  {
    "path": ".gitattributes",
    "chars": 378,
    "preview": "# Auto detect text files and perform LF normalization\n* text=auto\n\n# Custom for Visual Studio\n*.cs     diff=csharp\n\n# St"
  },
  {
    "path": ".gitignore",
    "chars": 578,
    "preview": "# Windows image file caches\nThumbs.db\nehthumbs.db\n\n# Folder config file\nDesktop.ini\n\n# Recycle Bin used on file shares\n$"
  },
  {
    "path": ".project",
    "chars": 391,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<projectDescription>\n\t<name>jankovicsandras.imagetracer-java</name>\n\t<comment></c"
  },
  {
    "path": ".settings/org.eclipse.core.resources.prefs",
    "chars": 91,
    "preview": "eclipse.preferences.version=1\nencoding//jankovicsandras/imagetracer/ImageTracer.java=UTF-8\n"
  },
  {
    "path": ".settings/org.eclipse.jdt.core.prefs",
    "chars": 587,
    "preview": "eclipse.preferences.version=1\norg.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled\norg.eclipse.jdt.core.compi"
  },
  {
    "path": "LICENSE",
    "chars": 1211,
    "preview": "This is free and unencumbered software released into the public domain.\n\nAnyone is free to copy, modify, publish, use, c"
  },
  {
    "path": "README.md",
    "chars": 9134,
    "preview": "# imagetracerjava\n![alt Bitmap to Svg](docimages/s1.png)\n\nSimple raster image tracer and vectorizer written in Java for "
  },
  {
    "path": "deterministic.md",
    "chars": 2761,
    "preview": "## TLDR; options for deterministic tracing:\n\ncustom palette, `colorquantcycles`:1\n\ncustom palette, `mincolorratio`:0\n\n`"
  },
  {
    "path": "jankovicsandras/imagetracer/ImageTracer.java",
    "chars": 41480,
    "preview": "/*\n\tImageTracer.java\n\t(Desktop version with javax.imageio. See ImageTracerAndroid.java for the Android version.)\n\tSimple"
  },
  {
    "path": "process_overview.md",
    "chars": 3017,
    "preview": "### Process overview\n#### 1. Color quantization\nThe **colorquantization** function creates an indexed image (https://en."
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the jankovicsandras/imagetracerjava GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 12 files (58.4 KB), approximately 18.5k tokens, and a symbol index with 37 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!