├── .eslintrc.json ├── .project ├── .settings ├── .jsdtscope ├── org.eclipse.wst.jsdt.ui.superType.container └── org.eclipse.wst.jsdt.ui.superType.name ├── LICENSE ├── README.md ├── bower.json ├── docimages ├── cover_problem.png ├── option_presets.png ├── option_presets_small.png ├── s1.png ├── s10.png ├── s11.png ├── s12.png ├── s13.png ├── s14.png ├── s15.png ├── s2.png ├── s3.png ├── s4.png ├── s7.png ├── s8.png ├── s9.png └── transparency_problem.png ├── imagetracer_examples.html ├── imagetracer_options_gallery.html ├── imagetracer_test_automation.html ├── imagetracer_v1.2.6.js ├── nodecli ├── PNG.js ├── PNGReader.js ├── READTHIS.md └── nodecli.js ├── options.md ├── package.json ├── panda.png ├── process_overview.md ├── simplify_interop.html ├── smiley.png ├── smileyRGB.png ├── testimages ├── 1.png ├── 10.png ├── 11.png ├── 12.png ├── 13.png ├── 14.png ├── 15.png ├── 16.png ├── 2.png ├── 3.png ├── 4.png ├── 5.png ├── 6.png ├── 7.png ├── 8.png ├── 9.png └── combined.png └── version_history.md /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true 4 | }, 5 | "extends": "eslint:recommended", 6 | "rules": { 7 | "indent": [ 8 | 2, 9 | "tab" 10 | ], 11 | "linebreak-style": [ 12 | 2, 13 | "windows" 14 | ], 15 | "quotes": [ 16 | 2, 17 | "single" 18 | ], 19 | "semi": [ 20 | 2, 21 | "always" 22 | ] 23 | } 24 | } -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | imagetracerjs 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.wst.jsdt.core.javascriptValidator 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.wst.jsdt.core.jsNature 16 | 17 | 18 | -------------------------------------------------------------------------------- /.settings/.jsdtscope: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.settings/org.eclipse.wst.jsdt.ui.superType.container: -------------------------------------------------------------------------------- 1 | org.eclipse.wst.jsdt.launching.JRE_CONTAINER -------------------------------------------------------------------------------- /.settings/org.eclipse.wst.jsdt.ui.superType.name: -------------------------------------------------------------------------------- 1 | Global -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # imagetracerjs 2 | ![alt Bitmap to Svg](docimages/s1.png) 3 | 4 | Simple raster image tracer and vectorizer written in JavaScript. 5 | 6 | --- 7 | 8 | ## Table of contents 9 | - [Getting started](#getting-started) 10 | - [News](#news) 11 | - [API](#api) 12 | - [Options](#options) 13 | - [Examples](#examples) 14 | - [InkScape extension](#inkscape-extension) 15 | - [Process overview](#process-overview) 16 | - [License](#license) 17 | 18 | --- 19 | 20 | ## Getting started 21 | 22 | ### Using in the Browser 23 | Include the script: 24 | ```javascript 25 | 26 | ``` 27 | Then: 28 | ```javascript 29 | // Loading an image, tracing with the 'posterized2' option preset, and appending the SVG to an element with id="svgcontainer" 30 | ImageTracer.imageToSVG( 31 | 32 | 'panda.png', /* input filename / URL */ 33 | 34 | function(svgstr){ ImageTracer.appendSVGString( svgstr, 'svgcontainer' ); }, /* callback function to run on SVG string result */ 35 | 36 | 'posterized2' /* Option preset */ 37 | 38 | ); 39 | ``` 40 | 41 | ### Using with Node.js 42 | 43 | Node.js Command line interface example: 44 | 45 | ``` 46 | imagetracerjs/nodecli>node nodecli ../panda.png outfilename panda.svg scale 10 47 | ``` 48 | 49 | Expected result: 50 | 51 | ``` 52 | imagetracerjs/nodecli/panda.svg was saved! 53 | ``` 54 | 55 | --- 56 | 57 | ## News 58 | 59 | ### 1.2.6 60 | - NEW: [InkScape extension](#inkscape-extension) 61 | - FIXED: hole shape parent search (Issues #31 #39) 62 | - FIXED: Handle (absolute) paths in CLI correctly Issue #42 63 | 64 | ### 1.2.5 65 | - RGBA ImageData check in colorquantization(), solving Issue #24 and #18 66 | 67 | ### 1.2.4 68 | - ```options.layering``` : default 0 = sequential, new method ; 1 = parallel, old method. (Enhancement Issue #17) 69 | - case insensitive option preset names 70 | - README.md reorganizing 71 | 72 | [Version history](https://github.com/jankovicsandras/imagetracerjs/blob/master/version_history.md) 73 | 74 | --- 75 | 76 | ## API 77 | |Function name|Arguments|Returns|Run type| 78 | |-------------|---------|-------|--------| 79 | |```imageToSVG```|```image_url /*string*/ , callback /*function*/ , options /*optional object or preset name*/```|Nothing, ```callback(svgstring)``` will be executed|Asynchronous, Browser only| 80 | |```imagedataToSVG```|```imagedata /*object*/ , options /*optional object or preset name*/```|```svgstring /*string*/```|Synchronous, Browser & Node.js| 81 | |```imageToTracedata```|```image_url /*string*/ , callback /*function*/ , options /*optional object or preset name*/```|Nothing, ```callback(tracedata)``` will be executed|Asynchronous, Browser only| 82 | |```imagedataToTracedata```|```imagedata /*object*/ , options /*optional object or preset name*/```|```tracedata /*object*/```|Synchronous, Browser & Node.js| 83 | 84 | ```imagedata``` is standard [ImageData](https://developer.mozilla.org/en-US/docs/Web/API/ImageData) here, ```canvas``` is [canvas](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas) . 85 | 86 | ### Helper Functions (Browser only) 87 | |Function name|Arguments|Returns|Run type| 88 | |-------------|---------|-------|--------| 89 | |```appendSVGString```|```svgstring /*string*/, parentid /*string*/```|Nothing, an SVG will be appended to the container DOM element with id=parentid.|Synchronous, Browser only| 90 | |```loadImage```|```url /*string*/, callback /*function*/```|Nothing, loading an image from a URL, then executing ```callback(canvas)```|Asynchronous, Browser only| 91 | |```getImgdata```|```canvas /*object*/```|```imagedata /*object*/```|Synchronous, Browser only| 92 | 93 | ```imagedata``` is standard [ImageData](https://developer.mozilla.org/en-US/docs/Web/API/ImageData) here, ```canvas``` is [canvas](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas) . 94 | There are more functions for advanced users, read the source if you are interested. :) 95 | 96 | "Browser only" means that Node.js doesn't have built-in canvas and DOM support as of 2018, so loading an image to an ImageData object needs an external library. 97 | 98 | --- 99 | 100 | ## Options 101 | You can use an option preset name (string) or an [options object](https://github.com/jankovicsandras/imagetracerjs/blob/master/options.md) to control the tracing and rendering process. 102 | 103 | ![Option presets gallery](docimages/option_presets_small.png) 104 | 105 | These strings can be passed instead of the options object: 106 | ```'default'``` 107 | ```'posterized1'``` 108 | ```'posterized2'``` 109 | ```'posterized3'``` 110 | ```'curvy'``` 111 | ```'sharp'``` 112 | ```'detailed'``` 113 | ```'smoothed'``` 114 | ```'grayscale'``` 115 | ```'fixedpalette'``` 116 | ```'randomsampling1'``` 117 | ```'randomsampling2'``` 118 | ```'artistic1'``` 119 | ```'artistic2'``` 120 | ```'artistic3'``` 121 | ```'artistic4'``` 122 | 123 | [Read more about options.](https://github.com/jankovicsandras/imagetracerjs/blob/master/options.md) 124 | 125 | --- 126 | 127 | ## Examples 128 | 129 | ### Using in the Browser 130 | Include the script: 131 | ```javascript 132 | 133 | ``` 134 | Then 135 | ```javascript 136 | // Loading smiley.png, tracing and calling alert callback on the SVG string result 137 | ImageTracer.imageToSVG( 'smiley.png', alert ); 138 | 139 | 140 | // Almost the same with options, and the ImageTracer.appendSVGString callback will append the SVG 141 | ImageTracer.imageToSVG( 'smiley.png', ImageTracer.appendSVGString, { ltres:0.1, qtres:1, scale:10, strokewidth:5 } ); 142 | 143 | 144 | // This uses the 'posterized2' option preset and appends the SVG to an element with id="svgcontainer" 145 | ImageTracer.imageToSVG( 146 | 'panda.png', 147 | function(svgstr){ ImageTracer.appendSVGString( svgstr, 'svgcontainer' ); }, 148 | 'posterized2' 149 | ); 150 | 151 | 152 | // The helper function loadImage() loads an image to a canvas, then executing callback: 153 | // appending the canvas to a div here. 154 | ImageTracer.loadImage( 155 | 'panda.png', 156 | function(canvas){ (document.getElementById('canvascontainer')).appendChild(canvas); } 157 | ); 158 | 159 | 160 | // ImageData can be traced to an SVG string synchronously. 161 | ImageTracer.loadImage( 162 | 'smiley.png', 163 | function(canvas){ 164 | 165 | // Getting ImageData from canvas with the helper function getImgdata(). 166 | var imgd = ImageTracer.getImgdata( canvas ); 167 | 168 | // Synchronous tracing to SVG string 169 | var svgstr = ImageTracer.imagedataToSVG( imgd, { scale:5 } ); 170 | 171 | // Appending SVG 172 | ImageTracer.appendSVGString( svgstr, 'svgcontainer' ); 173 | 174 | } 175 | ); 176 | 177 | 178 | // This will load an image, trace it when loaded, and execute callback on the tracedata: 179 | // stringifying and alerting it here. 180 | ImageTracer.imageToTracedata( 181 | 'smiley.png', 182 | function(tracedata){ alert( JSON.stringify( tracedata ) ); }, 183 | { ltres:0.1, qtres:1, scale:10 } 184 | ); 185 | 186 | 187 | // imagedataToTracedata() is very similar to the previous functions. This returns tracedata synchronously. 188 | ImageTracer.loadImage( 189 | 'smiley.png', 190 | function(canvas){ 191 | 192 | // Getting ImageData from canvas with the helper function getImgdata(). 193 | var imgd = ImageTracer.getImgdata(canvas); 194 | 195 | // Synchronous tracing to tracedata 196 | var tracedata = ImageTracer.imagedataToTracedata( imgd, { ltres:1, qtres:0.01, scale:10 } ); 197 | 198 | alert( JSON.stringify( tracedata ) ); 199 | } 200 | ); 201 | ``` 202 | 203 | ### Using with Node.js CLI 204 | 205 | Node.js Command line interface example: 206 | 207 | ``` 208 | imagetracerjs/nodecli>node nodecli ../panda.png outfilename panda.svg scale 10 209 | ``` 210 | 211 | Expected result: 212 | 213 | ``` 214 | imagetracerjs/nodecli/panda.svg was saved! 215 | ``` 216 | 217 | CLI parameter names are supported both with and without trailing dash: ```-scale 10``` and ```scale 10``` are both correct. 218 | Almost all options are supported, except ```pal``` and ```layercontainerid```. 219 | 220 | ### Simple Node.js converting example 221 | 222 | ```javascript 223 | "use strict"; 224 | 225 | var fs = require('fs'); 226 | 227 | var ImageTracer = require( __dirname + '/../imagetracer_v1.2.6' ); 228 | 229 | // This example uses https://github.com/arian/pngjs 230 | // , but other libraries can be used to load an image file to an ImageData object. 231 | var PNGReader = require( __dirname + '/PNGReader' ); 232 | 233 | // Input and output filepaths / URLs 234 | var infilepath = __dirname + '/' + 'panda.png'; 235 | var outfilepath = __dirname + '/' + 'panda.svg'; 236 | 237 | 238 | fs.readFile( 239 | 240 | infilepath, 241 | 242 | function( err, bytes ){ // fs.readFile callback 243 | if(err){ console.log(err); throw err; } 244 | 245 | var reader = new PNGReader(bytes); 246 | 247 | reader.parse( function( err, png ){ // PNGReader callback 248 | if(err){ console.log(err); throw err; } 249 | 250 | // creating an ImageData object 251 | var myImageData = { width:png.width, height:png.height, data:png.pixels }; 252 | 253 | // tracing to SVG string 254 | var options = { scale: 5 }; // options object; option preset string can be used also 255 | 256 | var svgstring = ImageTracer.imagedataToSVG( myImageData, options ); 257 | 258 | // writing to file 259 | fs.writeFile( 260 | outfilepath, 261 | svgstring, 262 | function(err){ if(err){ console.log(err); throw err; } console.log( outfilepath + ' was saved!' ); } 263 | ); 264 | 265 | });// End of reader.parse() 266 | 267 | }// End of readFile callback() 268 | 269 | );// End of fs.readFile() 270 | ``` 271 | 272 | ### Tracedata processing / Simplify.js example 273 | It's possible to process the traced geometry and color data before SVG rendering. This example [simplify_interop.html](https://github.com/jankovicsandras/imagetracerjs/blob/master/simplify_interop.html) shows polyline simplification. You need to download simplify.js from https://github.com/mourner/simplify-js . 274 | 275 | --- 276 | 277 | ## InkScape extension 278 | ImageTracer is available as an InkScape extension: [https://inkscape.org/~MarioVoigt/%E2%98%85imagetracerjs-for-inkscape-1x](https://inkscape.org/~MarioVoigt/%E2%98%85imagetracerjs-for-inkscape-1x) 279 | 280 | [Screenshots](https://github.com/jankovicsandras/imagetracerjs/issues/47) 281 | 282 | Thanks Mario Voigt! 283 | 284 | --- 285 | 286 | ## Process overview 287 | See [Process overview and Ideas for improvement](https://github.com/jankovicsandras/imagetracerjs/blob/master/process_overview.md) 288 | 289 | --- 290 | 291 | ## License 292 | ### The Unlicense / PUBLIC DOMAIN 293 | 294 | This is free and unencumbered software released into the public domain. 295 | 296 | Anyone is free to copy, modify, publish, use, compile, sell, or 297 | distribute this software, either in source code form or as a compiled 298 | binary, for any purpose, commercial or non-commercial, and by any 299 | means. 300 | 301 | In jurisdictions that recognize copyright laws, the author or authors 302 | of this software dedicate any and all copyright interest in the 303 | software to the public domain. We make this dedication for the benefit 304 | of the public at large and to the detriment of our heirs and 305 | successors. We intend this dedication to be an overt act of 306 | relinquishment in perpetuity of all present and future rights to this 307 | software under copyright law. 308 | 309 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 310 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 311 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 312 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 313 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 314 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 315 | OTHER DEALINGS IN THE SOFTWARE. 316 | 317 | For more information, please refer to [http://unlicense.org](http://unlicense.org) 318 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "imagetracerjs", 3 | "description": "raster image tracer and vectorizer, bitmap to SVG converter", 4 | "repository": { 5 | "type": "git", 6 | "url": "https://github.com/jankovicsandras/imagetracerjs.git" 7 | }, 8 | "main": "imagetracer_v1.2.6.js", 9 | "keywords": [ 10 | "image", 11 | "tracer", 12 | "tracing", 13 | "vector", 14 | "raster", 15 | "vectorize", 16 | "vectorizing", 17 | "convert", 18 | "conversion", 19 | "converting", 20 | "bitmap", 21 | "svg", 22 | "bmp", 23 | "png", 24 | "jpg", 25 | "jpeg", 26 | "gif" 27 | ], 28 | "authors": [ "András Jankovics" ], 29 | "license": "Unlicense", 30 | "bugs": { 31 | "url": "https://github.com/jankovicsandras/imagetracerjs/issues" 32 | }, 33 | "homepage": "https://github.com/jankovicsandras/imagetracerjs#readme" 34 | } 35 | -------------------------------------------------------------------------------- /docimages/cover_problem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankovicsandras/imagetracerjs/cb0c84a309df5e75614d3b5166cdc77a56f12a98/docimages/cover_problem.png -------------------------------------------------------------------------------- /docimages/option_presets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankovicsandras/imagetracerjs/cb0c84a309df5e75614d3b5166cdc77a56f12a98/docimages/option_presets.png -------------------------------------------------------------------------------- /docimages/option_presets_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankovicsandras/imagetracerjs/cb0c84a309df5e75614d3b5166cdc77a56f12a98/docimages/option_presets_small.png -------------------------------------------------------------------------------- /docimages/s1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankovicsandras/imagetracerjs/cb0c84a309df5e75614d3b5166cdc77a56f12a98/docimages/s1.png -------------------------------------------------------------------------------- /docimages/s10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankovicsandras/imagetracerjs/cb0c84a309df5e75614d3b5166cdc77a56f12a98/docimages/s10.png -------------------------------------------------------------------------------- /docimages/s11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankovicsandras/imagetracerjs/cb0c84a309df5e75614d3b5166cdc77a56f12a98/docimages/s11.png -------------------------------------------------------------------------------- /docimages/s12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankovicsandras/imagetracerjs/cb0c84a309df5e75614d3b5166cdc77a56f12a98/docimages/s12.png -------------------------------------------------------------------------------- /docimages/s13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankovicsandras/imagetracerjs/cb0c84a309df5e75614d3b5166cdc77a56f12a98/docimages/s13.png -------------------------------------------------------------------------------- /docimages/s14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankovicsandras/imagetracerjs/cb0c84a309df5e75614d3b5166cdc77a56f12a98/docimages/s14.png -------------------------------------------------------------------------------- /docimages/s15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankovicsandras/imagetracerjs/cb0c84a309df5e75614d3b5166cdc77a56f12a98/docimages/s15.png -------------------------------------------------------------------------------- /docimages/s2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankovicsandras/imagetracerjs/cb0c84a309df5e75614d3b5166cdc77a56f12a98/docimages/s2.png -------------------------------------------------------------------------------- /docimages/s3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankovicsandras/imagetracerjs/cb0c84a309df5e75614d3b5166cdc77a56f12a98/docimages/s3.png -------------------------------------------------------------------------------- /docimages/s4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankovicsandras/imagetracerjs/cb0c84a309df5e75614d3b5166cdc77a56f12a98/docimages/s4.png -------------------------------------------------------------------------------- /docimages/s7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankovicsandras/imagetracerjs/cb0c84a309df5e75614d3b5166cdc77a56f12a98/docimages/s7.png -------------------------------------------------------------------------------- /docimages/s8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankovicsandras/imagetracerjs/cb0c84a309df5e75614d3b5166cdc77a56f12a98/docimages/s8.png -------------------------------------------------------------------------------- /docimages/s9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankovicsandras/imagetracerjs/cb0c84a309df5e75614d3b5166cdc77a56f12a98/docimages/s9.png -------------------------------------------------------------------------------- /docimages/transparency_problem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankovicsandras/imagetracerjs/cb0c84a309df5e75614d3b5166cdc77a56f12a98/docimages/transparency_problem.png -------------------------------------------------------------------------------- /imagetracer_examples.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 77 | 78 | 79 | 80 |
81 |
82 | -------------------------------------------------------------------------------- /imagetracer_options_gallery.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | imagetracer.js options gallery 8 | 32 | 33 | 55 | 56 | 57 | 58 | 59 |
60 | 61 | 62 | 63 | 64 | 65 |

Original
66 |
67 | 68 | 69 | -------------------------------------------------------------------------------- /imagetracer_test_automation.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | imagetracer.js test automation 6 | 15 | 16 | 251 | 252 | 253 | 254 |
255 |

Test images

256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 |
















284 |

Settings

285 |
286 |
287 |
288 |
289 | Example Custom Options:
{"ltres":1,"qtres":1,"pathomit":8,"colorsampling":true,"numberofcolors":16,"mincolorratio":0.02,"colorquantcycles":3,"scale":1,"simplifytolerance":0,"roundcoords":1,"lcpr":0,"qcpr":0,"desc":true,"viewbox":false,"blurradius":0,"blurdelta":20}
290 |
291 | 292 | 293 |
294 | 295 |

Result (if Draw SVG is active)

296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 |
Traced SVGOriginal rasterSVG rendered as rasterDifference
309 | 310 |

Measurements

311 |
312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 |
RowIDFilenamewidth (pixels)height (pixels)area (pixels)SVG string length (bytes)Tracing time (ms)Rendering time (ms)Nr. of pathsRGBA difference (cummulative RGBA difference / (area*4))Different pixels (%)Options
332 | 333 |
334 | -------------------------------------------------------------------------------- /imagetracer_v1.2.6.js: -------------------------------------------------------------------------------- 1 | /* 2 | imagetracer.js version 1.2.6 3 | Simple raster image tracer and vectorizer written in JavaScript. 4 | andras@jankovics.net 5 | */ 6 | 7 | /* 8 | 9 | The Unlicense / PUBLIC DOMAIN 10 | 11 | This is free and unencumbered software released into the public domain. 12 | 13 | Anyone is free to copy, modify, publish, use, compile, sell, or 14 | distribute this software, either in source code form or as a compiled 15 | binary, for any purpose, commercial or non-commercial, and by any 16 | means. 17 | 18 | In jurisdictions that recognize copyright laws, the author or authors 19 | of this software dedicate any and all copyright interest in the 20 | software to the public domain. We make this dedication for the benefit 21 | of the public at large and to the detriment of our heirs and 22 | successors. We intend this dedication to be an overt act of 23 | relinquishment in perpetuity of all present and future rights to this 24 | software under copyright law. 25 | 26 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 27 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 28 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 29 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 30 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 31 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 32 | OTHER DEALINGS IN THE SOFTWARE. 33 | 34 | For more information, please refer to http://unlicense.org/ 35 | 36 | */ 37 | 38 | (function(){ 'use strict'; 39 | 40 | function ImageTracer(){ 41 | var _this = this; 42 | 43 | this.versionnumber = '1.2.6', 44 | 45 | //////////////////////////////////////////////////////////// 46 | // 47 | // API 48 | // 49 | //////////////////////////////////////////////////////////// 50 | 51 | // Loading an image from a URL, tracing when loaded, 52 | // then executing callback with the scaled svg string as argument 53 | this.imageToSVG = function( url, callback, options ){ 54 | options = _this.checkoptions(options); 55 | // loading image, tracing and callback 56 | _this.loadImage( 57 | url, 58 | function(canvas){ 59 | callback( 60 | _this.imagedataToSVG( _this.getImgdata(canvas), options ) 61 | ); 62 | }, 63 | options 64 | ); 65 | },// End of imageToSVG() 66 | 67 | // Tracing imagedata, then returning the scaled svg string 68 | this.imagedataToSVG = function( imgd, options ){ 69 | options = _this.checkoptions(options); 70 | // tracing imagedata 71 | var td = _this.imagedataToTracedata( imgd, options ); 72 | // returning SVG string 73 | return _this.getsvgstring(td, options); 74 | },// End of imagedataToSVG() 75 | 76 | // Loading an image from a URL, tracing when loaded, 77 | // then executing callback with tracedata as argument 78 | this.imageToTracedata = function( url, callback, options ){ 79 | options = _this.checkoptions(options); 80 | // loading image, tracing and callback 81 | _this.loadImage( 82 | url, 83 | function(canvas){ 84 | callback( 85 | _this.imagedataToTracedata( _this.getImgdata(canvas), options ) 86 | ); 87 | }, 88 | options 89 | ); 90 | },// End of imageToTracedata() 91 | 92 | // Tracing imagedata, then returning tracedata (layers with paths, palette, image size) 93 | this.imagedataToTracedata = function( imgd, options ){ 94 | options = _this.checkoptions(options); 95 | 96 | // 1. Color quantization 97 | var ii = _this.colorquantization( imgd, options ); 98 | 99 | if(options.layering === 0){// Sequential layering 100 | 101 | // create tracedata object 102 | var tracedata = { 103 | layers : [], 104 | palette : ii.palette, 105 | width : ii.array[0].length-2, 106 | height : ii.array.length-2 107 | }; 108 | 109 | // Loop to trace each color layer 110 | for(var colornum=0; colornum pathscan -> internodes -> batchtracepaths 113 | var tracedlayer = 114 | _this.batchtracepaths( 115 | 116 | _this.internodes( 117 | 118 | _this.pathscan( 119 | _this.layeringstep( ii, colornum ), 120 | options.pathomit 121 | ), 122 | 123 | options 124 | 125 | ), 126 | 127 | options.ltres, 128 | options.qtres 129 | 130 | ); 131 | 132 | // adding traced layer 133 | tracedata.layers.push(tracedlayer); 134 | 135 | }// End of color loop 136 | 137 | }else{// Parallel layering 138 | // 2. Layer separation and edge detection 139 | var ls = _this.layering( ii ); 140 | 141 | // Optional edge node visualization 142 | if(options.layercontainerid){ _this.drawLayers( ls, _this.specpalette, options.scale, options.layercontainerid ); } 143 | 144 | // 3. Batch pathscan 145 | var bps = _this.batchpathscan( ls, options.pathomit ); 146 | 147 | // 4. Batch interpollation 148 | var bis = _this.batchinternodes( bps, options ); 149 | 150 | // 5. Batch tracing and creating tracedata object 151 | var tracedata = { 152 | layers : _this.batchtracelayers( bis, options.ltres, options.qtres ), 153 | palette : ii.palette, 154 | width : imgd.width, 155 | height : imgd.height 156 | }; 157 | 158 | }// End of parallel layering 159 | 160 | // return tracedata 161 | return tracedata; 162 | 163 | },// End of imagedataToTracedata() 164 | 165 | this.optionpresets = { 166 | 'default': { 167 | 168 | // Tracing 169 | corsenabled : false, 170 | ltres : 1, 171 | qtres : 1, 172 | pathomit : 8, 173 | rightangleenhance : true, 174 | 175 | // Color quantization 176 | colorsampling : 2, 177 | numberofcolors : 16, 178 | mincolorratio : 0, 179 | colorquantcycles : 3, 180 | 181 | // Layering method 182 | layering : 0, 183 | 184 | // SVG rendering 185 | strokewidth : 1, 186 | linefilter : false, 187 | scale : 1, 188 | roundcoords : 1, 189 | viewbox : false, 190 | desc : false, 191 | lcpr : 0, 192 | qcpr : 0, 193 | 194 | // Blur 195 | blurradius : 0, 196 | blurdelta : 20 197 | 198 | }, 199 | 'posterized1': { colorsampling:0, numberofcolors:2 }, 200 | 'posterized2': { numberofcolors:4, blurradius:5 }, 201 | 'curvy': { ltres:0.01, linefilter:true, rightangleenhance:false }, 202 | 'sharp': { qtres:0.01, linefilter:false }, 203 | 'detailed': { pathomit:0, roundcoords:2, ltres:0.5, qtres:0.5, numberofcolors:64 }, 204 | 'smoothed': { blurradius:5, blurdelta: 64 }, 205 | 'grayscale': { colorsampling:0, colorquantcycles:1, numberofcolors:7 }, 206 | 'fixedpalette': { colorsampling:0, colorquantcycles:1, numberofcolors:27 }, 207 | 'randomsampling1': { colorsampling:1, numberofcolors:8 }, 208 | 'randomsampling2': { colorsampling:1, numberofcolors:64 }, 209 | 'artistic1': { colorsampling:0, colorquantcycles:1, pathomit:0, blurradius:5, blurdelta: 64, ltres:0.01, linefilter:true, numberofcolors:16, strokewidth:2 }, 210 | 'artistic2': { qtres:0.01, colorsampling:0, colorquantcycles:1, numberofcolors:4, strokewidth:0 }, 211 | 'artistic3': { qtres:10, ltres:10, numberofcolors:8 }, 212 | 'artistic4': { qtres:10, ltres:10, numberofcolors:64, blurradius:5, blurdelta: 256, strokewidth:2 }, 213 | 'posterized3': { ltres: 1, qtres: 1, pathomit: 20, rightangleenhance: true, colorsampling: 0, numberofcolors: 3, 214 | mincolorratio: 0, colorquantcycles: 3, blurradius: 3, blurdelta: 20, strokewidth: 0, linefilter: false, 215 | roundcoords: 1, pal: [ { r: 0, g: 0, b: 100, a: 255 }, { r: 255, g: 255, b: 255, a: 255 } ] } 216 | },// End of optionpresets 217 | 218 | // creating options object, setting defaults for missing values 219 | this.checkoptions = function(options){ 220 | options = options || {}; 221 | // Option preset 222 | if(typeof options === 'string'){ 223 | options = options.toLowerCase(); 224 | if( _this.optionpresets[options] ){ options = _this.optionpresets[options]; }else{ options = {}; } 225 | } 226 | // Defaults 227 | var ok = Object.keys(_this.optionpresets['default']); 228 | for(var k=0; k 233 | return options; 234 | },// End of checkoptions() 235 | 236 | //////////////////////////////////////////////////////////// 237 | // 238 | // Vectorizing functions 239 | // 240 | //////////////////////////////////////////////////////////// 241 | 242 | // 1. Color quantization 243 | // Using a form of k-means clustering repeatead options.colorquantcycles times. http://en.wikipedia.org/wiki/Color_quantization 244 | this.colorquantization = function( imgd, options ){ 245 | var arr = [], idx=0, cd,cdl,ci, paletteacc = [], pixelnum = imgd.width * imgd.height, i, j, k, cnt, palette; 246 | 247 | // imgd.data must be RGBA, not just RGB 248 | if( imgd.data.length < pixelnum * 4 ){ 249 | var newimgddata = new Uint8ClampedArray(pixelnum * 4); 250 | for(var pxcnt = 0; pxcnt < pixelnum ; pxcnt++){ 251 | newimgddata[pxcnt*4 ] = imgd.data[pxcnt*3 ]; 252 | newimgddata[pxcnt*4+1] = imgd.data[pxcnt*3+1]; 253 | newimgddata[pxcnt*4+2] = imgd.data[pxcnt*3+2]; 254 | newimgddata[pxcnt*4+3] = 255; 255 | } 256 | imgd.data = newimgddata; 257 | }// End of RGBA imgd.data check 258 | 259 | // Filling arr (color index array) with -1 260 | for( j=0; j 0 ){ imgd = _this.blur( imgd, options.blurradius, options.blurdelta ); } 275 | 276 | // Repeat clustering step options.colorquantcycles times 277 | for( cnt=0; cnt < options.colorquantcycles; cnt++ ){ 278 | 279 | // Average colors from the second iteration 280 | if(cnt>0){ 281 | // averaging paletteacc for palette 282 | for( k=0; k < palette.length; k++ ){ 283 | 284 | // averaging 285 | if( paletteacc[k].n > 0 ){ 286 | palette[k] = { r: Math.floor( paletteacc[k].r / paletteacc[k].n ), 287 | g: Math.floor( paletteacc[k].g / paletteacc[k].n ), 288 | b: Math.floor( paletteacc[k].b / paletteacc[k].n ), 289 | a: Math.floor( paletteacc[k].a / paletteacc[k].n ) }; 290 | } 291 | 292 | // Randomizing a color, if there are too few pixels and there will be a new cycle 293 | if( ( paletteacc[k].n/pixelnum < options.mincolorratio ) && ( cnt < options.colorquantcycles-1 ) ){ 294 | palette[k] = { r: Math.floor(Math.random()*255), 295 | g: Math.floor(Math.random()*255), 296 | b: Math.floor(Math.random()*255), 297 | a: Math.floor(Math.random()*255) }; 298 | } 299 | 300 | }// End of palette loop 301 | }// End of Average colors from the second iteration 302 | 303 | // Reseting palette accumulator for averaging 304 | for( i=0; i < palette.length; i++ ){ paletteacc[i] = { r:0, g:0, b:0, a:0, n:0 }; } 305 | 306 | // loop through all pixels 307 | for( j=0; j < imgd.height; j++ ){ 308 | for( i=0; i < imgd.width; i++ ){ 309 | 310 | // pixel index 311 | idx = (j*imgd.width+i)*4; 312 | 313 | // find closest color from palette by measuring (rectilinear) color distance between this pixel and all palette colors 314 | ci=0; cdl = 1024; // 4 * 256 is the maximum RGBA distance 315 | for( k=0; k imgd.data[idx ] ? palette[k].r - imgd.data[idx ] : imgd.data[idx ] - palette[k].r ) + 320 | ( palette[k].g > imgd.data[idx+1] ? palette[k].g - imgd.data[idx+1] : imgd.data[idx+1] - palette[k].g ) + 321 | ( palette[k].b > imgd.data[idx+2] ? palette[k].b - imgd.data[idx+2] : imgd.data[idx+2] - palette[k].b ) + 322 | ( palette[k].a > imgd.data[idx+3] ? palette[k].a - imgd.data[idx+3] : imgd.data[idx+3] - palette[k].a ); 323 | 324 | // Remember this color if this is the closest yet 325 | if(cd p.y) !== (pa[j].y > p.y)) && (p.x < (pa[j].x - pa[i].x) * (p.y - pa[i].y) / (pa[j].y - pa[i].y) + pa[i].x) ) 495 | ? !isin : isin; 496 | } 497 | 498 | return isin; 499 | }, 500 | 501 | // Lookup tables for pathscan 502 | // pathscan_combined_lookup[ arr[py][px] ][ dir ] = [nextarrpypx, nextdir, deltapx, deltapy]; 503 | this.pathscan_combined_lookup = [ 504 | [[-1,-1,-1,-1], [-1,-1,-1,-1], [-1,-1,-1,-1], [-1,-1,-1,-1]],// arr[py][px]===0 is invalid 505 | [[ 0, 1, 0,-1], [-1,-1,-1,-1], [-1,-1,-1,-1], [ 0, 2,-1, 0]], 506 | [[-1,-1,-1,-1], [-1,-1,-1,-1], [ 0, 1, 0,-1], [ 0, 0, 1, 0]], 507 | [[ 0, 0, 1, 0], [-1,-1,-1,-1], [ 0, 2,-1, 0], [-1,-1,-1,-1]], 508 | 509 | [[-1,-1,-1,-1], [ 0, 0, 1, 0], [ 0, 3, 0, 1], [-1,-1,-1,-1]], 510 | [[13, 3, 0, 1], [13, 2,-1, 0], [ 7, 1, 0,-1], [ 7, 0, 1, 0]], 511 | [[-1,-1,-1,-1], [ 0, 1, 0,-1], [-1,-1,-1,-1], [ 0, 3, 0, 1]], 512 | [[ 0, 3, 0, 1], [ 0, 2,-1, 0], [-1,-1,-1,-1], [-1,-1,-1,-1]], 513 | 514 | [[ 0, 3, 0, 1], [ 0, 2,-1, 0], [-1,-1,-1,-1], [-1,-1,-1,-1]], 515 | [[-1,-1,-1,-1], [ 0, 1, 0,-1], [-1,-1,-1,-1], [ 0, 3, 0, 1]], 516 | [[11, 1, 0,-1], [14, 0, 1, 0], [14, 3, 0, 1], [11, 2,-1, 0]], 517 | [[-1,-1,-1,-1], [ 0, 0, 1, 0], [ 0, 3, 0, 1], [-1,-1,-1,-1]], 518 | 519 | [[ 0, 0, 1, 0], [-1,-1,-1,-1], [ 0, 2,-1, 0], [-1,-1,-1,-1]], 520 | [[-1,-1,-1,-1], [-1,-1,-1,-1], [ 0, 1, 0,-1], [ 0, 0, 1, 0]], 521 | [[ 0, 1, 0,-1], [-1,-1,-1,-1], [-1,-1,-1,-1], [ 0, 2,-1, 0]], 522 | [[-1,-1,-1,-1], [-1,-1,-1,-1], [-1,-1,-1,-1], [-1,-1,-1,-1]]// arr[py][px]===15 is invalid 523 | ], 524 | 525 | // 3. Walking through an edge node array, discarding edge node types 0 and 15 and creating paths from the rest. 526 | // Walk directions (dir): 0 > ; 1 ^ ; 2 < ; 3 v 527 | this.pathscan = function( arr, pathomit ){ 528 | var paths=[], pacnt=0, pcnt=0, px=0, py=0, w = arr[0].length, h = arr.length, 529 | dir=0, pathfinished=true, holepath=false, lookuprow; 530 | 531 | for(var j=0; j paths[pacnt].boundingbox[2] ){ paths[pacnt].boundingbox[2] = px-1; } 558 | if( (py-1) < paths[pacnt].boundingbox[1] ){ paths[pacnt].boundingbox[1] = py-1; } 559 | if( (py-1) > paths[pacnt].boundingbox[3] ){ paths[pacnt].boundingbox[3] = py-1; } 560 | 561 | // Next: look up the replacement, direction and coordinate changes = clear this cell, turn if required, walk forward 562 | lookuprow = _this.pathscan_combined_lookup[ arr[py][px] ][ dir ]; 563 | arr[py][px] = lookuprow[0]; dir = lookuprow[1]; px += lookuprow[2]; py += lookuprow[3]; 564 | 565 | // Close path 566 | if( (px-1 === paths[pacnt].points[0].x ) && ( py-1 === paths[pacnt].points[0].y ) ){ 567 | pathfinished = true; 568 | 569 | // Discarding paths shorter than pathomit 570 | if( paths[pacnt].points.length < pathomit ){ 571 | paths.pop(); 572 | }else{ 573 | 574 | paths[pacnt].isholepath = holepath ? true : false; 575 | 576 | // Finding the parent shape for this hole 577 | if(holepath){ 578 | 579 | var parentidx = 0, parentbbox = [-1,-1,w+1,h+1]; 580 | for(var parentcnt=0; parentcnt < pacnt; parentcnt++){ 581 | if( (!paths[parentcnt].isholepath) && 582 | _this.boundingboxincludes( paths[parentcnt].boundingbox , paths[pacnt].boundingbox ) && 583 | _this.boundingboxincludes( parentbbox , paths[parentcnt].boundingbox ) && 584 | _this.pointinpoly( paths[pacnt].points[0], paths[parentcnt].points ) 585 | ){ 586 | parentidx = parentcnt; 587 | parentbbox = paths[parentcnt].boundingbox; 588 | } 589 | } 590 | 591 | paths[parentidx].holechildren.push( pacnt ); 592 | 593 | }// End of holepath parent finding 594 | 595 | pacnt++; 596 | 597 | } 598 | 599 | }// End of Close path 600 | 601 | pcnt++; 602 | 603 | }// End of Path points loop 604 | 605 | }// End of Follow path 606 | 607 | }// End of i loop 608 | }// End of j loop 609 | 610 | return paths; 611 | },// End of pathscan() 612 | 613 | this.boundingboxincludes = function( parentbbox, childbbox ){ 614 | return ( ( parentbbox[0] < childbbox[0] ) && ( parentbbox[1] < childbbox[1] ) && ( parentbbox[2] > childbbox[2] ) && ( parentbbox[3] > childbbox[3] ) ); 615 | },// End of boundingboxincludes() 616 | 617 | // 3. Batch pathscan 618 | this.batchpathscan = function( layers, pathomit ){ 619 | var bpaths = []; 620 | for(var k in layers){ 621 | if(!layers.hasOwnProperty(k)){ continue; } 622 | bpaths[k] = _this.pathscan( layers[k], pathomit ); 623 | } 624 | return bpaths; 625 | }, 626 | 627 | // 4. interpollating between path points for nodes with 8 directions ( East, SouthEast, S, SW, W, NW, N, NE ) 628 | this.internodes = function( paths, options ){ 629 | var ins = [], palen=0, nextidx=0, nextidx2=0, previdx=0, previdx2=0, pacnt, pcnt; 630 | 631 | // paths loop 632 | for(pacnt=0; pacnt 0){ 652 | ins[pacnt].points[ ins[pacnt].points.length-1 ].linesegment = _this.getdirection( 653 | ins[pacnt].points[ ins[pacnt].points.length-1 ].x, 654 | ins[pacnt].points[ ins[pacnt].points.length-1 ].y, 655 | paths[pacnt].points[pcnt].x, 656 | paths[pacnt].points[pcnt].y 657 | ); 658 | } 659 | 660 | // This corner point 661 | ins[pacnt].points.push({ 662 | x : paths[pacnt].points[pcnt].x, 663 | y : paths[pacnt].points[pcnt].y, 664 | linesegment : _this.getdirection( 665 | paths[pacnt].points[pcnt].x, 666 | paths[pacnt].points[pcnt].y, 667 | (( paths[pacnt].points[pcnt].x + paths[pacnt].points[nextidx].x ) /2), 668 | (( paths[pacnt].points[pcnt].y + paths[pacnt].points[nextidx].y ) /2) 669 | ) 670 | }); 671 | 672 | }// End of right angle enhance 673 | 674 | // interpolate between two path points 675 | ins[pacnt].points.push({ 676 | x : (( paths[pacnt].points[pcnt].x + paths[pacnt].points[nextidx].x ) /2), 677 | y : (( paths[pacnt].points[pcnt].y + paths[pacnt].points[nextidx].y ) /2), 678 | linesegment : _this.getdirection( 679 | (( paths[pacnt].points[pcnt].x + paths[pacnt].points[nextidx].x ) /2), 680 | (( paths[pacnt].points[pcnt].y + paths[pacnt].points[nextidx].y ) /2), 681 | (( paths[pacnt].points[nextidx].x + paths[pacnt].points[nextidx2].x ) /2), 682 | (( paths[pacnt].points[nextidx].y + paths[pacnt].points[nextidx2].y ) /2) 683 | ) 684 | }); 685 | 686 | }// End of pathpoints loop 687 | 688 | }// End of paths loop 689 | 690 | return ins; 691 | },// End of internodes() 692 | 693 | this.testrightangle = function( path, idx1, idx2, idx3, idx4, idx5 ){ 694 | return ( (( path.points[idx3].x === path.points[idx1].x) && 695 | ( path.points[idx3].x === path.points[idx2].x) && 696 | ( path.points[idx3].y === path.points[idx4].y) && 697 | ( path.points[idx3].y === path.points[idx5].y) 698 | ) || 699 | (( path.points[idx3].y === path.points[idx1].y) && 700 | ( path.points[idx3].y === path.points[idx2].y) && 701 | ( path.points[idx3].x === path.points[idx4].x) && 702 | ( path.points[idx3].x === path.points[idx5].x) 703 | ) 704 | ); 705 | },// End of testrightangle() 706 | 707 | this.getdirection = function( x1, y1, x2, y2 ){ 708 | var val = 8; 709 | if(x1 < x2){ 710 | if (y1 < y2){ val = 1; }// SouthEast 711 | else if(y1 > y2){ val = 7; }// NE 712 | else { val = 0; }// E 713 | }else if(x1 > x2){ 714 | if (y1 < y2){ val = 3; }// SW 715 | else if(y1 > y2){ val = 5; }// NW 716 | else { val = 4; }// W 717 | }else{ 718 | if (y1 < y2){ val = 2; }// S 719 | else if(y1 > y2){ val = 6; }// N 720 | else { val = 8; }// center, this should not happen 721 | } 722 | return val; 723 | },// End of getdirection() 724 | 725 | // 4. Batch interpollation 726 | this.batchinternodes = function( bpaths, options ){ 727 | var binternodes = []; 728 | for (var k in bpaths) { 729 | if(!bpaths.hasOwnProperty(k)){ continue; } 730 | binternodes[k] = _this.internodes(bpaths[k], options); 731 | } 732 | return binternodes; 733 | }, 734 | 735 | // 5. tracepath() : recursively trying to fit straight and quadratic spline segments on the 8 direction internode path 736 | 737 | // 5.1. Find sequences of points with only 2 segment types 738 | // 5.2. Fit a straight line on the sequence 739 | // 5.3. If the straight line fails (distance error > ltres), find the point with the biggest error 740 | // 5.4. Fit a quadratic spline through errorpoint (project this to get controlpoint), then measure errors on every point in the sequence 741 | // 5.5. If the spline fails (distance error > qtres), find the point with the biggest error, set splitpoint = fitting point 742 | // 5.6. Split sequence and recursively apply 5.2. - 5.6. to startpoint-splitpoint and splitpoint-endpoint sequences 743 | 744 | this.tracepath = function( path, ltres, qtres ){ 745 | var pcnt=0, segtype1, segtype2, seqend, smp = {}; 746 | smp.segments = []; 747 | smp.boundingbox = path.boundingbox; 748 | smp.holechildren = path.holechildren; 749 | smp.isholepath = path.isholepath; 750 | 751 | while(pcnt < path.points.length){ 752 | // 5.1. Find sequences of points with only 2 segment types 753 | segtype1 = path.points[pcnt].linesegment; segtype2 = -1; seqend=pcnt+1; 754 | while( 755 | ((path.points[seqend].linesegment === segtype1) || (path.points[seqend].linesegment === segtype2) || (segtype2 === -1)) 756 | && (seqend < path.points.length-1) ){ 757 | 758 | if((path.points[seqend].linesegment!==segtype1) && (segtype2===-1)){ segtype2 = path.points[seqend].linesegment; } 759 | seqend++; 760 | 761 | } 762 | if(seqend === path.points.length-1){ seqend = 0; } 763 | 764 | // 5.2. - 5.6. Split sequence and recursively apply 5.2. - 5.6. to startpoint-splitpoint and splitpoint-endpoint sequences 765 | smp.segments = smp.segments.concat( _this.fitseq(path, ltres, qtres, pcnt, seqend) ); 766 | 767 | // forward pcnt; 768 | if(seqend>0){ pcnt = seqend; }else{ pcnt = path.points.length; } 769 | 770 | }// End of pcnt loop 771 | 772 | return smp; 773 | },// End of tracepath() 774 | 775 | // 5.2. - 5.6. recursively fitting a straight or quadratic line segment on this sequence of path nodes, 776 | // called from tracepath() 777 | this.fitseq = function( path, ltres, qtres, seqstart, seqend ){ 778 | // return if invalid seqend 779 | if( (seqend>path.points.length) || (seqend<0) ){ return []; } 780 | // variables 781 | var errorpoint=seqstart, errorval=0, curvepass=true, px, py, dist2; 782 | var tl = (seqend-seqstart); if(tl<0){ tl += path.points.length; } 783 | var vx = (path.points[seqend].x-path.points[seqstart].x) / tl, 784 | vy = (path.points[seqend].y-path.points[seqstart].y) / tl; 785 | 786 | // 5.2. Fit a straight line on the sequence 787 | var pcnt = (seqstart+1) % path.points.length, pl; 788 | while(pcnt != seqend){ 789 | pl = pcnt-seqstart; if(pl<0){ pl += path.points.length; } 790 | px = path.points[seqstart].x + vx * pl; py = path.points[seqstart].y + vy * pl; 791 | dist2 = (path.points[pcnt].x-px)*(path.points[pcnt].x-px) + (path.points[pcnt].y-py)*(path.points[pcnt].y-py); 792 | if(dist2>ltres){curvepass=false;} 793 | if(dist2>errorval){ errorpoint=pcnt; errorval=dist2; } 794 | pcnt = (pcnt+1)%path.points.length; 795 | } 796 | // return straight line if fits 797 | if(curvepass){ return [{ type:'L', x1:path.points[seqstart].x, y1:path.points[seqstart].y, x2:path.points[seqend].x, y2:path.points[seqend].y }]; } 798 | 799 | // 5.3. If the straight line fails (distance error>ltres), find the point with the biggest error 800 | var fitpoint = errorpoint; curvepass = true; errorval = 0; 801 | 802 | // 5.4. Fit a quadratic spline through this point, measure errors on every point in the sequence 803 | // helpers and projecting to get control point 804 | var t=(fitpoint-seqstart)/tl, t1=(1-t)*(1-t), t2=2*(1-t)*t, t3=t*t; 805 | var cpx = (t1*path.points[seqstart].x + t3*path.points[seqend].x - path.points[fitpoint].x)/-t2 , 806 | cpy = (t1*path.points[seqstart].y + t3*path.points[seqend].y - path.points[fitpoint].y)/-t2 ; 807 | 808 | // Check every point 809 | pcnt = seqstart+1; 810 | while(pcnt != seqend){ 811 | t=(pcnt-seqstart)/tl; t1=(1-t)*(1-t); t2=2*(1-t)*t; t3=t*t; 812 | px = t1 * path.points[seqstart].x + t2 * cpx + t3 * path.points[seqend].x; 813 | py = t1 * path.points[seqstart].y + t2 * cpy + t3 * path.points[seqend].y; 814 | 815 | dist2 = (path.points[pcnt].x-px)*(path.points[pcnt].x-px) + (path.points[pcnt].y-py)*(path.points[pcnt].y-py); 816 | 817 | if(dist2>qtres){curvepass=false;} 818 | if(dist2>errorval){ errorpoint=pcnt; errorval=dist2; } 819 | pcnt = (pcnt+1)%path.points.length; 820 | } 821 | // return spline if fits 822 | if(curvepass){ return [{ type:'Q', x1:path.points[seqstart].x, y1:path.points[seqstart].y, x2:cpx, y2:cpy, x3:path.points[seqend].x, y3:path.points[seqend].y }]; } 823 | // 5.5. If the spline fails (distance error>qtres), find the point with the biggest error 824 | var splitpoint = fitpoint; // Earlier: Math.floor((fitpoint + errorpoint)/2); 825 | 826 | // 5.6. Split sequence and recursively apply 5.2. - 5.6. to startpoint-splitpoint and splitpoint-endpoint sequences 827 | return _this.fitseq( path, ltres, qtres, seqstart, splitpoint ).concat( 828 | _this.fitseq( path, ltres, qtres, splitpoint, seqend ) ); 829 | 830 | },// End of fitseq() 831 | 832 | // 5. Batch tracing paths 833 | this.batchtracepaths = function(internodepaths,ltres,qtres){ 834 | var btracedpaths = []; 835 | for(var k in internodepaths){ 836 | if(!internodepaths.hasOwnProperty(k)){ continue; } 837 | btracedpaths.push( _this.tracepath(internodepaths[k],ltres,qtres) ); 838 | } 839 | return btracedpaths; 840 | }, 841 | 842 | // 5. Batch tracing layers 843 | this.batchtracelayers = function(binternodes, ltres, qtres){ 844 | var btbis = []; 845 | for(var k in binternodes){ 846 | if(!binternodes.hasOwnProperty(k)){ continue; } 847 | btbis[k] = _this.batchtracepaths(binternodes[k], ltres, qtres); 848 | } 849 | return btbis; 850 | }, 851 | 852 | //////////////////////////////////////////////////////////// 853 | // 854 | // SVG Drawing functions 855 | // 856 | //////////////////////////////////////////////////////////// 857 | 858 | // Rounding to given decimals https://stackoverflow.com/questions/11832914/round-to-at-most-2-decimal-places-in-javascript 859 | this.roundtodec = function(val,places){ return +val.toFixed(places); }, 860 | 861 | // Getting SVG path element string from a traced path 862 | this.svgpathstring = function( tracedata, lnum, pathnum, options ){ 863 | 864 | var layer = tracedata.layers[lnum], smp = layer[pathnum], str='', pcnt; 865 | 866 | // Line filter 867 | if(options.linefilter && (smp.segments.length < 3)){ return str; } 868 | 869 | // Starting path element, desc contains layer and path number 870 | str = ''; 942 | 943 | // Rendering control points 944 | if(options.lcpr || options.qcpr){ 945 | for(pcnt=0; pcnt'; 948 | str += ''; 949 | str += ''; 950 | str += ''; 951 | } 952 | if( (!smp.segments[pcnt].hasOwnProperty('x3')) && options.lcpr){ 953 | str += ''; 954 | } 955 | } 956 | 957 | // Hole children control points 958 | for( var hcnt=0; hcnt < smp.holechildren.length; hcnt++){ 959 | var hsmp = layer[ smp.holechildren[hcnt] ]; 960 | for(pcnt=0; pcnt'; 963 | str += ''; 964 | str += ''; 965 | str += ''; 966 | } 967 | if( (!hsmp.segments[pcnt].hasOwnProperty('x3')) && options.lcpr){ 968 | str += ''; 969 | } 970 | } 971 | } 972 | }// End of Rendering control points 973 | 974 | return str; 975 | 976 | },// End of svgpathstring() 977 | 978 | // Converting tracedata to an SVG string 979 | this.getsvgstring = function( tracedata, options ){ 980 | 981 | options = _this.checkoptions(options); 982 | 983 | var w = tracedata.width * options.scale, h = tracedata.height * options.scale; 984 | 985 | // SVG start 986 | var svgstr = ''; 988 | 989 | // Drawing: Layers and Paths loops 990 | for(var lcnt=0; lcnt < tracedata.layers.length; lcnt++){ 991 | for(var pcnt=0; pcnt < tracedata.layers[lcnt].length; pcnt++){ 992 | 993 | // Adding SVG string 994 | if( !tracedata.layers[lcnt][pcnt].isholepath ){ 995 | svgstr += _this.svgpathstring( tracedata, lcnt, pcnt, options ); 996 | } 997 | 998 | }// End of paths loop 999 | }// End of layers loop 1000 | 1001 | // SVG End 1002 | svgstr+=''; 1003 | 1004 | return svgstr; 1005 | 1006 | },// End of getsvgstring() 1007 | 1008 | // Comparator for numeric Array.sort 1009 | this.compareNumbers = function(a,b){ return a - b; }, 1010 | 1011 | // Convert color object to rgba string 1012 | this.torgbastr = function(c){ return 'rgba('+c.r+','+c.g+','+c.b+','+c.a+')'; }, 1013 | 1014 | // Convert color object to SVG color string 1015 | this.tosvgcolorstr = function(c, options){ 1016 | return 'fill="rgb('+c.r+','+c.g+','+c.b+')" stroke="rgb('+c.r+','+c.g+','+c.b+')" stroke-width="'+options.strokewidth+'" opacity="'+c.a/255.0+'" '; 1017 | }, 1018 | 1019 | // Helper function: Appending an element to a container from an svgstring 1020 | this.appendSVGString = function(svgstr,parentid){ 1021 | var div; 1022 | if(parentid){ 1023 | div = document.getElementById(parentid); 1024 | if(!div){ 1025 | div = document.createElement('div'); 1026 | div.id = parentid; 1027 | document.body.appendChild(div); 1028 | } 1029 | }else{ 1030 | div = document.createElement('div'); 1031 | document.body.appendChild(div); 1032 | } 1033 | div.innerHTML += svgstr; 1034 | }, 1035 | 1036 | //////////////////////////////////////////////////////////// 1037 | // 1038 | // Canvas functions 1039 | // 1040 | //////////////////////////////////////////////////////////// 1041 | 1042 | // Gaussian kernels for blur 1043 | this.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], 1044 | [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] ], 1045 | 1046 | // Selective Gaussian blur for preprocessing 1047 | this.blur = function(imgd,radius,delta){ 1048 | var i,j,k,d,idx,racc,gacc,bacc,aacc,wacc; 1049 | 1050 | // new ImageData 1051 | var imgd2 = { width:imgd.width, height:imgd.height, data:[] }; 1052 | 1053 | // radius and delta limits, this kernel 1054 | radius = Math.floor(radius); if(radius<1){ return imgd; } if(radius>5){ radius = 5; } delta = Math.abs( delta ); if(delta>1024){ delta = 1024; } 1055 | var thisgk = _this.gks[radius-1]; 1056 | 1057 | // loop through all pixels, horizontal blur 1058 | for( j=0; j < imgd.height; j++ ){ 1059 | for( i=0; i < imgd.width; i++ ){ 1060 | 1061 | racc = 0; gacc = 0; bacc = 0; aacc = 0; wacc = 0; 1062 | // gauss kernel loop 1063 | for( k = -radius; k < radius+1; k++){ 1064 | // add weighted color values 1065 | if( (i+k > 0) && (i+k < imgd.width) ){ 1066 | idx = (j*imgd.width+i+k)*4; 1067 | racc += imgd.data[idx ] * thisgk[k+radius]; 1068 | gacc += imgd.data[idx+1] * thisgk[k+radius]; 1069 | bacc += imgd.data[idx+2] * thisgk[k+radius]; 1070 | aacc += imgd.data[idx+3] * thisgk[k+radius]; 1071 | wacc += thisgk[k+radius]; 1072 | } 1073 | } 1074 | // The new pixel 1075 | idx = (j*imgd.width+i)*4; 1076 | imgd2.data[idx ] = Math.floor(racc / wacc); 1077 | imgd2.data[idx+1] = Math.floor(gacc / wacc); 1078 | imgd2.data[idx+2] = Math.floor(bacc / wacc); 1079 | imgd2.data[idx+3] = Math.floor(aacc / wacc); 1080 | 1081 | }// End of width loop 1082 | }// End of horizontal blur 1083 | 1084 | // copying the half blurred imgd2 1085 | var himgd = new Uint8ClampedArray(imgd2.data); 1086 | 1087 | // loop through all pixels, vertical blur 1088 | for( j=0; j < imgd.height; j++ ){ 1089 | for( i=0; i < imgd.width; i++ ){ 1090 | 1091 | racc = 0; gacc = 0; bacc = 0; aacc = 0; wacc = 0; 1092 | // gauss kernel loop 1093 | for( k = -radius; k < radius+1; k++){ 1094 | // add weighted color values 1095 | if( (j+k > 0) && (j+k < imgd.height) ){ 1096 | idx = ((j+k)*imgd.width+i)*4; 1097 | racc += himgd[idx ] * thisgk[k+radius]; 1098 | gacc += himgd[idx+1] * thisgk[k+radius]; 1099 | bacc += himgd[idx+2] * thisgk[k+radius]; 1100 | aacc += himgd[idx+3] * thisgk[k+radius]; 1101 | wacc += thisgk[k+radius]; 1102 | } 1103 | } 1104 | // The new pixel 1105 | idx = (j*imgd.width+i)*4; 1106 | imgd2.data[idx ] = Math.floor(racc / wacc); 1107 | imgd2.data[idx+1] = Math.floor(gacc / wacc); 1108 | imgd2.data[idx+2] = Math.floor(bacc / wacc); 1109 | imgd2.data[idx+3] = Math.floor(aacc / wacc); 1110 | 1111 | }// End of width loop 1112 | }// End of vertical blur 1113 | 1114 | // Selective blur: loop through all pixels 1115 | for( j=0; j < imgd.height; j++ ){ 1116 | for( i=0; i < imgd.width; i++ ){ 1117 | 1118 | idx = (j*imgd.width+i)*4; 1119 | // d is the difference between the blurred and the original pixel 1120 | d = Math.abs(imgd2.data[idx ] - imgd.data[idx ]) + Math.abs(imgd2.data[idx+1] - imgd.data[idx+1]) + 1121 | Math.abs(imgd2.data[idx+2] - imgd.data[idx+2]) + Math.abs(imgd2.data[idx+3] - imgd.data[idx+3]); 1122 | // selective blur: if d>delta, put the original pixel back 1123 | if(d>delta){ 1124 | imgd2.data[idx ] = imgd.data[idx ]; 1125 | imgd2.data[idx+1] = imgd.data[idx+1]; 1126 | imgd2.data[idx+2] = imgd.data[idx+2]; 1127 | imgd2.data[idx+3] = imgd.data[idx+3]; 1128 | } 1129 | } 1130 | }// End of Selective blur 1131 | 1132 | return imgd2; 1133 | 1134 | },// End of blur() 1135 | 1136 | // Helper function: loading an image from a URL, then executing callback with canvas as argument 1137 | this.loadImage = function(url,callback,options){ 1138 | var img = new Image(); 1139 | if(options && options.corsenabled){ img.crossOrigin = 'Anonymous'; } 1140 | img.onload = function(){ 1141 | var canvas = document.createElement('canvas'); 1142 | canvas.width = img.width; 1143 | canvas.height = img.height; 1144 | var context = canvas.getContext('2d'); 1145 | context.drawImage(img,0,0); 1146 | callback(canvas); 1147 | }; 1148 | img.src = url; 1149 | }, 1150 | 1151 | // Helper function: getting ImageData from a canvas 1152 | this.getImgdata = function(canvas){ 1153 | var context = canvas.getContext('2d'); 1154 | return context.getImageData(0,0,canvas.width,canvas.height); 1155 | }, 1156 | 1157 | // Special palette to use with drawlayers() 1158 | this.specpalette = [ 1159 | {r:0,g:0,b:0,a:255}, {r:128,g:128,b:128,a:255}, {r:0,g:0,b:128,a:255}, {r:64,g:64,b:128,a:255}, 1160 | {r:192,g:192,b:192,a:255}, {r:255,g:255,b:255,a:255}, {r:128,g:128,b:192,a:255}, {r:0,g:0,b:192,a:255}, 1161 | {r:128,g:0,b:0,a:255}, {r:128,g:64,b:64,a:255}, {r:128,g:0,b:128,a:255}, {r:168,g:168,b:168,a:255}, 1162 | {r:192,g:128,b:128,a:255}, {r:192,g:0,b:0,a:255}, {r:255,g:255,b:255,a:255}, {r:0,g:128,b:0,a:255} 1163 | ], 1164 | 1165 | // Helper function: Drawing all edge node layers into a container 1166 | this.drawLayers = function(layers,palette,scale,parentid){ 1167 | scale = scale||1; 1168 | var w,h,i,j,k; 1169 | 1170 | // Preparing container 1171 | var div; 1172 | if(parentid){ 1173 | div = document.getElementById(parentid); 1174 | if(!div){ 1175 | div = document.createElement('div'); 1176 | div.id = parentid; 1177 | document.body.appendChild(div); 1178 | } 1179 | }else{ 1180 | div = document.createElement('div'); 1181 | document.body.appendChild(div); 1182 | } 1183 | 1184 | // Layers loop 1185 | for (k in layers) { 1186 | if(!layers.hasOwnProperty(k)){ continue; } 1187 | 1188 | // width, height 1189 | w=layers[k][0].length; h=layers[k].length; 1190 | 1191 | // Creating new canvas for every layer 1192 | var canvas = document.createElement('canvas'); canvas.width=w*scale; canvas.height=h*scale; 1193 | var context = canvas.getContext('2d'); 1194 | 1195 | // Drawing 1196 | for(j=0; j 4 | 5 | 6 | 7 | 121 | 122 | 123 | 124 |
125 | 126 | 127 | -------------------------------------------------------------------------------- /smiley.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankovicsandras/imagetracerjs/cb0c84a309df5e75614d3b5166cdc77a56f12a98/smiley.png -------------------------------------------------------------------------------- /smileyRGB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankovicsandras/imagetracerjs/cb0c84a309df5e75614d3b5166cdc77a56f12a98/smileyRGB.png -------------------------------------------------------------------------------- /testimages/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankovicsandras/imagetracerjs/cb0c84a309df5e75614d3b5166cdc77a56f12a98/testimages/1.png -------------------------------------------------------------------------------- /testimages/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankovicsandras/imagetracerjs/cb0c84a309df5e75614d3b5166cdc77a56f12a98/testimages/10.png -------------------------------------------------------------------------------- /testimages/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankovicsandras/imagetracerjs/cb0c84a309df5e75614d3b5166cdc77a56f12a98/testimages/11.png -------------------------------------------------------------------------------- /testimages/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankovicsandras/imagetracerjs/cb0c84a309df5e75614d3b5166cdc77a56f12a98/testimages/12.png -------------------------------------------------------------------------------- /testimages/13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankovicsandras/imagetracerjs/cb0c84a309df5e75614d3b5166cdc77a56f12a98/testimages/13.png -------------------------------------------------------------------------------- /testimages/14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankovicsandras/imagetracerjs/cb0c84a309df5e75614d3b5166cdc77a56f12a98/testimages/14.png -------------------------------------------------------------------------------- /testimages/15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankovicsandras/imagetracerjs/cb0c84a309df5e75614d3b5166cdc77a56f12a98/testimages/15.png -------------------------------------------------------------------------------- /testimages/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankovicsandras/imagetracerjs/cb0c84a309df5e75614d3b5166cdc77a56f12a98/testimages/16.png -------------------------------------------------------------------------------- /testimages/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankovicsandras/imagetracerjs/cb0c84a309df5e75614d3b5166cdc77a56f12a98/testimages/2.png -------------------------------------------------------------------------------- /testimages/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankovicsandras/imagetracerjs/cb0c84a309df5e75614d3b5166cdc77a56f12a98/testimages/3.png -------------------------------------------------------------------------------- /testimages/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankovicsandras/imagetracerjs/cb0c84a309df5e75614d3b5166cdc77a56f12a98/testimages/4.png -------------------------------------------------------------------------------- /testimages/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankovicsandras/imagetracerjs/cb0c84a309df5e75614d3b5166cdc77a56f12a98/testimages/5.png -------------------------------------------------------------------------------- /testimages/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankovicsandras/imagetracerjs/cb0c84a309df5e75614d3b5166cdc77a56f12a98/testimages/6.png -------------------------------------------------------------------------------- /testimages/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankovicsandras/imagetracerjs/cb0c84a309df5e75614d3b5166cdc77a56f12a98/testimages/7.png -------------------------------------------------------------------------------- /testimages/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankovicsandras/imagetracerjs/cb0c84a309df5e75614d3b5166cdc77a56f12a98/testimages/8.png -------------------------------------------------------------------------------- /testimages/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankovicsandras/imagetracerjs/cb0c84a309df5e75614d3b5166cdc77a56f12a98/testimages/9.png -------------------------------------------------------------------------------- /testimages/combined.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jankovicsandras/imagetracerjs/cb0c84a309df5e75614d3b5166cdc77a56f12a98/testimages/combined.png -------------------------------------------------------------------------------- /version_history.md: -------------------------------------------------------------------------------- 1 | ## Version history 2 | 3 | ### 1.2.6 4 | - FIXED: hole shape parent search (Issues #31 #39) 5 | - FIXED: Handle (absolute) paths in CLI correctly Issue #42 6 | 7 | ### 1.2.5 8 | - RGBA ImageData check in colorquantization(), solving Issue #24 and #18 9 | 10 | ### 1.2.4 11 | - ```options.layering``` : default 0 = sequential, new method ; 1 = parallel, old method. (Enhancement Issue #17) 12 | - case insensitive option preset names 13 | - README.md reorganizing 14 | 15 | ### 1.2.3 16 | 17 | - Node.js Command line interface (Enhancement Issue #13) 18 | - FIXED: Pathomit problem thanks to EusthEnoptEron (Issue #14) 19 | - options.corsenabled for [CORS Image loading](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image) thanks to neel-radica (Issue #12) 20 | 21 | ### 1.2.2 22 | 23 | - FIXED: missing hole in path because of incorrect bounding box (Issue #11) 24 | - Posterized3 option preset 25 | - Changed svgpathstring() arguments to simplify getsvgstring() 26 | 27 | ### 1.2.1 28 | 29 | - FIXED: Gaussian blur preprocessing is now independent of DOM and canvas, thus working directly with Node.js (Issue #9) 30 | 31 | ### 1.2.0 32 | 33 | This is a major update, changing some internal logic and option default values. The API is compatible, so it should work out of the box. 34 | 35 | - FIXED: transparent holes are now possible. ( Issue #7 and #8 ) 36 | - Deterministic output by default: ```options.colorsampling = 2``` ; ```options.mincolorratio = 0``` are deterministic and the defaults now. 37 | - Right angle enhancing: ```options.rightangleenhance``` ( default : true ) 38 | - Option presets (see below) 39 | - Custom strokewidth with ```options.strokewidth``` ( default : 1 ) 40 | - Line filter with ```options.linefilter``` ( default : false ) 41 | - Simplified ```getsvgstring()```; ```options.desc = false``` by default; splitpoint = fitpoint in fitseq(); small bugfixes and optimizations 42 | 43 | Version history and README for the old 1.1.2 version is [here.](https://github.com/jankovicsandras/imagetracerjs/blob/master/README_v1.1.2.md) 44 | 45 | ### 1.1.2 46 | 47 | - minor bugfixes 48 | - lookup based ```pathscan()``` 49 | 50 | ### 1.1.1 51 | 52 | - Bugfix: CSS3 RGBA output in SVG was technically incorrect (however supported by major browsers), so this is changed. [More info](https://stackoverflow.com/questions/6042550/svg-fill-color-transparency-alpha) 53 | 54 | ### 1.1.0 55 | 56 | - it works with Node.js (external library required to load image into an ImageData object) 57 | - export as AMD module / Node module / browser or worker variable 58 | - new syntax: ```ImageTracer112.imageToTracedata()```, no need to initialize 59 | - fixed ```options``` with hasOwnProperty: 0 values are not replaced with defaults, fixed polygons with coordinates x=0 or y=0 60 | - transparency support: alpha is not discarded now, it is given more weight in color quantization 61 | - new ```options.roundcoords``` : rounding coordinates to a given decimal place. This can reduce SVG length significantly (>20%) with minor loss of precision. 62 | - new ```options.desc``` : setting this to false will turn off path descriptions, reducing SVG length. 63 | - new ```options.viewbox``` : setting this to true will use viewBox instead of exact width and height 64 | - new ```options.colorsampling``` : color quantization will sample the colors now by default, can be turned off. 65 | - new ```options.blurradius``` : setting this to 1..5 will preprocess the image with a selective Gaussian blur with ```options.blurdelta``` treshold. This can filter noise and improve quality. 66 | - ```imagedataToTracedata()``` returns image width and height in tracedata 67 | - ```getsvgstring()``` needs now only ```tracedata``` and ```options``` as parameters 68 | - ```colorquantization()``` needs now only ```imgd``` and ```options``` as parameters 69 | - background field is removed from the results of color quantization 70 | - ESLint passed 71 | - test automation and simple statistics in imagetracer_test_automation.html 72 | 73 | ### 1.0.0 - 1.0.4 74 | 75 | - first published version + bugfixes --------------------------------------------------------------------------------