jankovicsandras / imagetracerjs

Simple raster image tracer and vectorizer written in JavaScript.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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:

  1. ImageTracer is meant to be minimalistic, path simplification is probably not in the scope (yet).
  2. 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. )
  3. 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.