eu.mihosoft.vrl.v3d.svg.ImageTracer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of JavaCad Show documentation
Show all versions of JavaCad Show documentation
A Java based CSG Cad library
/*
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("");
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