Tracing path simplification
fallenartist opened this issue · comments
I'd like to optimise (simplify) tracing result much further than blurradius
maximum value allows for. Would it be possible to use either Simplify.js or other algorithms on tracedata
?
Actually, looking at the process, should the simplification step be added after step. 4 or 5?
Hi,
Yes, it's possible, see example below if TLDR. ☺
I thought about built-in path optimization and specifically about Simplify.js earlier, but decided not yet (!) to include this in the project. I have 3 reasons for this, the third is the most important:
- ImageTracer is meant to be minimalistic, path simplification is probably not in the scope (yet).
- Possible licensing issue ( I don't really care about licenses, that's why my stuff is usually Unlicensed / Public domain, but Simplify.js is BSD licensed, and I respect this. )
- Technical challenges (problems?): As I understand, Simplify.js processes only linear line segments, while ImageTracer uses a mix of linear and quadratic spline segments (and maybe cubic in the future), and it's not trivial what would happen if a controlpoint gets deleted or a Q-endpoint-delete merges a quadratic spline with the next linear segment. I chose to treat every segment as linear in the example.
Thanks for your question, and it's a good idea to think about solutions. I'm thinking about making a guide (this example) in the next version, and doing more research.
Example:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="imagetracer_v1.2.5.js"></script>
<script src="simplify.js"></script>
<script>
function onload_init(){
var options = { ltres:0.1, qtres:1, scale:4, strokewidth:4 };
// This will load an image, trace it when loaded, and execute callback on the tracedata
ImageTracer.imageToTracedata(
'panda.png',
function(tracedata){
// Counting total original points; copying and simplifying points in newpointscontainer
var pointcnt = 0, newpointscontainer = [];
// layers loop
for(var i=0; i<tracedata.layers.length; i++){
newpointscontainer[i] = [];
// paths loop
for(var j=0; j<tracedata.layers[i].length; j++){
newpointscontainer[i][j] = [];
// register first point
newpointscontainer[i][j].push({x: tracedata.layers[i][j].segments[0].x1, y: tracedata.layers[i][j].segments[0].y1 });
// segments loop
for(var k=0; k<tracedata.layers[i][j].segments.length; k++){
// register next point
newpointscontainer[i][j].push({x: tracedata.layers[i][j].segments[k].x2, y: tracedata.layers[i][j].segments[k].y2 });
pointcnt++;
if(tracedata.layers[i][j].segments[k].hasOwnProperty('x3')){
// register next point
newpointscontainer[i][j].push({x: tracedata.layers[i][j].segments[k].x3, y: tracedata.layers[i][j].segments[k].y3 });
pointcnt++;
}// End of x3 check
}// End of segments loop
// simplify with tolerance = 2 ; highQuality = true
newpointscontainer[i][j] = simplify(newpointscontainer[i][j], 2, true);
}// End of paths loop
}// End of layers loop
// log original SVG stats
document.getElementById('log').innerHTML += 'Original point count: '+pointcnt+'<br/>';
// display original SVG
ImageTracer.appendSVGString( ImageTracer.getsvgstring(tracedata,options), 'svgcontainer' );
// Copying the new, simplified points back to tracedata
// As we don't know how and which segment is affected,
// whether quadratic spline controlpoints or endpoints were deleted,
// we will just assume everything is linear, no quadratic splines will be used
pointcnt = 0;
// layers loop
for(var i=0; i<tracedata.layers.length; i++){
// paths loop
for(var j=0; j<tracedata.layers[i].length; j++){
// reset this path segments
tracedata.layers[i][j].segments = [];
// count new points
pointcnt += newpointscontainer[i][j].length;
// segments loop
for(var k=0; k<newpointscontainer[i][j].length; k++){
// create new segments from new points
tracedata.layers[i][j].segments.push(
{
type:'L',
x1: newpointscontainer[i][j][k].x,
y1: newpointscontainer[i][j][k].y,
x2: newpointscontainer[i][j][(k+1)%newpointscontainer[i][j].length].x,
y2: newpointscontainer[i][j][(k+1)%newpointscontainer[i][j].length].y
}
);
}// End of segments loop
if(j===0){ console.log(JSON.stringify(tracedata.layers[i][j].segments)); }
}// End of paths loop
}// End of layers loop
// log new SVG stats
document.getElementById('log').innerHTML += 'New point count: '+pointcnt+'<br/>';
// display new SVG
ImageTracer.appendSVGString( ImageTracer.getsvgstring(tracedata,options), 'svgcontainer' );
},// End of imageToTracedata() callback
options // for tracing
);// End of imageToTracedata()
}// End of onload_init()
</script>
</head>
<body style="background-color:rgb(32,32,32);color:rgb(255,255,255);font-family:monospace;" onload="onload_init()" >
<noscript style="background-color:rgb(255,0,0);color:rgb(255,255,255);font-size: 250%;">Please enable JavaScript!</noscript>
<div id="svgcontainer"></div> <div id="log"></div>
</body>
</html>
I referenced this example in README.md, but should make more detailed documentation.