All Downloads are FREE. Search and download functionalities are using the official Maven repository.

eu.mihosoft.vrl.v3d.svg.ImageTracer Maven / Gradle / Ivy

There is a newer version: 2.0.0
Show newest version
/*
	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
	[email protected]

 */

/*

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 eu.mihosoft.vrl.v3d.svg;

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.1";

	public static int arraycontains(String [] arr, String str){
		for(int j=0; j>> 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>> 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 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 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 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 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 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 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>> bps = batchpathscan(rawlayers,(int)(Math.floor(options.get("pathomit"))));
		// 4. Batch interpollation
		ArrayList>> 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 checkoptions(HashMap options){
		if(options==null){ options = new HashMap(); }
		// 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 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;cnt0){
				// averaging paletteacc for palette
				float ratio;
				for(int k=0;k0){
						palette[k][0] = (byte) (-128+Math.floor(paletteacc[k][0]/paletteacc[k][4]));
						palette[k][1] = (byte) (-128+Math.floor(paletteacc[k][1]/paletteacc[k][4]));
						palette[k][2] = (byte) (-128+Math.floor(paletteacc[k][2]/paletteacc[k][4]));
						palette[k][3] = (byte) (-128+Math.floor(paletteacc[k][3]/paletteacc[k][4]));
					}
					ratio = paletteacc[k][4]/(imgd.width*imgd.height);

					// Randomizing a color, if there are too few pixels and there will be a new cycle
					if((ratio0)    && (i>0))   { n1 = ii.array[j-1][i-1]==val?1:0; }else{ n1 = 0; }
				if (j>0)                { n2 = ii.array[j-1][i  ]==val?1:0; }else{ n2 = 0; }
				if((j>0)    && (i<(aw-1))){ n3 = ii.array[j-1][i+1]==val?1:0; }else{ n3 = 0; }
				if (i>0)                { n4 = ii.array[j  ][i-1]==val?1:0; }else{ n4 = 0; }
				if (i<(aw-1))             { n5 = ii.array[j  ][i+1]==val?1:0; }else{ n5 = 0; }
				if((j<(ah-1)) && (i>0))   { n6 = ii.array[j+1][i-1]==val?1:0; }else{ n6 = 0; }
				if (j<(ah-1))             { n7 = ii.array[j+1][i  ]==val?1:0; }else{ n7 = 0; }
				if((j<(ah-1)) && (i<(aw-1))){ n8 = ii.array[j+1][i+1]==val?1:0; }else{ n8 = 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()

	// 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> pathscan (int [][] arr,float pathomit){
		ArrayList> paths = new ArrayList>();
		ArrayList thispath;
		int px=0,py=0,w=arr[0].length,h=arr.length,dir=0;
		boolean pathfinished=true, holepath = false;

		for(int j=0;j());
					thispath = paths.get(paths.size()-1);
					pathfinished = false;
					// fill paths will be drawn, but hole paths are also required to remove unnecessary edge nodes
					if(arr[py][px]==1){dir = 0;}
					if(arr[py][px]==2){dir = 3;}
					if(arr[py][px]==3){dir = 0;}
					if(arr[py][px]==4){dir = 1; holepath = false;}
					if(arr[py][px]==5){dir = 0;}
					if(arr[py][px]==6){dir = 3;}
					if(arr[py][px]==7){dir = 0; holepath = true;}
					if(arr[py][px]==8){dir = 0;}
					if(arr[py][px]==9){dir = 3;}
					if(arr[py][px]==10){dir = 3;}
					if(arr[py][px]==11){dir = 1; holepath = true;}
					if(arr[py][px]==12){dir = 0;}
					if(arr[py][px]==13){dir = 3; holepath = true;}
					if(arr[py][px]==14){dir = 0; holepath = true;}
					// 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];

						// Node types
						if(arr[py][px]==1){
							arr[py][px] = 0;
							if(dir==0){
								py--;dir=1;
							}else if(dir==3){
								px--;dir=2;
							}else{pathfinished=true;paths.remove(thispath);}
						}

						else if(arr[py][px]==2){
							arr[py][px] = 0;
							if(dir==3){
								px++;dir=0;
							}else if(dir==2){
								py--;dir=1;
							}else{pathfinished=true;paths.remove(thispath);}
						}

						else if(arr[py][px]==3){
							arr[py][px] = 0;
							if(dir==0){
								px++;
							}else if(dir==2){
								px--;
							}else{pathfinished=true;paths.remove(thispath);}
						}

						else if(arr[py][px]==4){
							arr[py][px] = 0;
							if(dir==1){
								px++;dir=0;
							}else if(dir==2){
								py++;dir=3;
							}else{pathfinished=true;paths.remove(thispath);}
						}

						else if(arr[py][px]==5){
							if(dir==0){
								arr[py][px] = 13;py++;dir=3;
							}else if(dir==1){
								arr[py][px] = 13;px--;dir=2;
							}else if(dir==2){
								arr[py][px] = 7;py--;dir=1;
							}else if(dir==3){
								arr[py][px] = 7;px++;dir=0;
							}
						}

						else if(arr[py][px]==6){
							arr[py][px] = 0;
							if(dir==1){
								py--;
							}else if(dir==3){
								py++;
							}else{pathfinished=true;paths.remove(thispath);}
						}

						else if(arr[py][px]==7){
							arr[py][px] = 0;
							if(dir==0){
								py++;dir=3;
							}else if(dir==1){
								px--;dir=2;
							}else{pathfinished=true;paths.remove(thispath);}
						}

						else if(arr[py][px]==8){
							arr[py][px] = 0;
							if(dir==0){
								py++;dir=3;
							}else if(dir==1){
								px--;dir=2;
							}else{pathfinished=true;paths.remove(thispath);}
						}

						else if(arr[py][px]==9){
							arr[py][px] = 0;
							if(dir==1){
								py--;
							}else if(dir==3){
								py++;
							}else{pathfinished=true;paths.remove(thispath);}
						}

						else if(arr[py][px]==10){
							if(dir==0){
								arr[py][px] = 11;py--;dir=1;
							}else if(dir==1){
								arr[py][px] = 14;px++;dir=0;
							}else if(dir==2){
								arr[py][px] = 14;py++;dir=3;
							}else if(dir==3){
								arr[py][px] = 11;px--;dir=2;
							}
						}

						else if(arr[py][px]==11){
							arr[py][px] = 0;
							if(dir==1){
								px++;dir=0;
							}else if(dir==2){
								py++;dir=3;
							}else{pathfinished=true;paths.remove(thispath);}
						}

						else if(arr[py][px]==12){
							arr[py][px] = 0;
							if(dir==0){
								px++;
							}else if(dir==2){
								px--;
							}else{pathfinished=true;paths.remove(thispath);}
						}

						else if(arr[py][px]==13){
							arr[py][px] = 0;
							if(dir==2){
								py--;dir=1;
							}else if(dir==3){
								px++;dir=0;
							}else{pathfinished=true;paths.remove(thispath);}
						}

						else if(arr[py][px]==14){
							arr[py][px] = 0;
							if(dir==0){
								py--;dir=1;
							}else if(dir==3){
								px--;dir=2;
							}else{pathfinished=true;paths.remove(thispath);}
						}

						// 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()>> batchpathscan (int [][][] layers, float pathomit){
		ArrayList>> bpaths = new ArrayList>>();
		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> internodes (ArrayList> paths){
		ArrayList> ins = new ArrayList>();
		ArrayList 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());
			thisinp = ins.get(ins.size()-1);
			palen = paths.get(pacnt).size();
			// pathpoints loop
			for(int pcnt=0;pcnt 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; }// N
				}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>> batchinternodes (ArrayList>> bpaths){
		ArrayList>> binternodes = new ArrayList>>();
		for(int k=0; kltreshold), 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 tracepath (ArrayList path, float ltreshold, float qtreshold){
		int pcnt=0, seqend=0; double segtype1, segtype2;
		ArrayList smp = new ArrayList();
		//Double [] thissegment;
		int pathlength = path.size();

		while(pcnt0){ 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 fitseq (ArrayList path, float ltreshold, float qtreshold, int seqstart, int seqend){
		ArrayList segment = new ArrayList();
		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> batchtracepaths(ArrayList> internodepaths, float ltres,float qtres){
		ArrayList> btracedpaths = new ArrayList>();
		for(int k=0;k>> batchtracelayers(ArrayList>> binternodes, float ltres, float qtres){
		ArrayList>> btbis = new ArrayList>>();
		for(int k=0; k segments, String colorstr, HashMap options){
		float scale = options.get("scale");
		float lcpr = options.get("lcpr");
		float qcpr = options.get("qcpr");
		float roundcoords = (float) Math.floor(options.get("roundcoords"));
		// Path
		sb
		.append("");

		// Rendering control points
		for(int pcnt=0;pcnt0)&&(segments.get(pcnt)[0]==1.0)){
				sb
				.append( "");
			}
			if((qcpr>0)&&(segments.get(pcnt)[0]==2.0)){
				sb
				.append( "");
				sb
				.append( "");
				sb
				.append( "");
				sb
				.append( "");
			}// 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 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("");
		svgstr.append("\n");
		// creating Z-index
		TreeMap  zindex = new TreeMap ();
		double label;
		// Layer loop
		for(int k=0; k 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);
		}
		svgstr.append("\n");
		// SVG End
		svgstr.append("");

		return svgstr.toString();

	}// End of getsvgstring()

	static String tosvgcolorstr(byte[] c){
		//hack this to export cuttable files
		//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)+"\" ";

		return "fill=\"none\" stroke=\"rgb("+(0)+","+(0)+","+(0)+")\" stroke-width=\"0.354\" opacity=\""+1+"\" ";
	}

	// 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




© 2015 - 2024 Weber Informatics LLC | Privacy Policy