forked from jankovicsandras/imagetracerjava
-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactorized ImageTracer.java into smaller java files, and imported a…
…n external algorithm to replace the existent Quantization algorithm.
- Loading branch information
1 parent
c906e01
commit ba545c9
Showing
9 changed files
with
1,489 additions
and
812 deletions.
There are no files selected for viewing
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
package jankovicsandras.imagetracer; | ||
|
||
import java.util.ArrayList; | ||
import java.util.HashMap; | ||
import java.util.TreeMap; | ||
import java.util.Map.Entry; | ||
|
||
import jankovicsandras.imagetracer.ImageTracer.IndexedImage; | ||
|
||
public class SVGUtils { | ||
|
||
|
||
|
||
|
||
//////////////////////////////////////////////////////////// | ||
// | ||
// 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){ | ||
// 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)+"\" "; | ||
} | ||
|
||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
package jankovicsandras.imagetracer; | ||
|
||
import jankovicsandras.imagetracer.ImageTracer.ImageData; | ||
|
||
public class SelectiveBlur { | ||
|
||
|
||
// 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() | ||
} |
Oops, something went wrong.