Added imagetracerjs extension (by Mario Voigt)

This commit is contained in:
Mario Voigt 2020-08-18 20:17:36 +02:00
parent ab5c2e54fc
commit 9c1f85d3a8
61 changed files with 3474 additions and 0 deletions

View File

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Imagetracer.js</name>
<id>fablabchemnitz.de.imagetracerjs</id>
<hbox>
<vbox>
<label appearance="header">Tracing</label>
<separator/>
<param name="keeporiginal" type="bool" gui-text="Keep original image on canvas">false</param>
<param name="ltres" type="float" min="0.0" gui-text="Error treshold straight lines">1.0</param>
<param name="qtres" type="float" min="0.0" gui-text="Error treshold quadratic splines">1.0</param>
<param name="pathomit" type="int" min="0" max="9999" gui-text="Noise reduction - discard edge node paths shorter than">8</param>
<param name="rightangleenhance" type="bool" gui-text="Enhance right angle corners">true</param>
<spacer/>
<label appearance="header">Color Quantization and Layering</label>
<separator/>
<param name="colorsampling" appearance="combo" type="optiongroup" default="2" gui-text="Color sampling">
<option value="0">disabled, generating a palette</option>
<option value="1">random sampling</option>
<option value="2">deterministic sampling</option>
</param>
<param name="numberofcolors" type="int" min="1" max="9999" gui-text="Number of colors to use on palette">16</param>
<param name="mincolorratio" type="int" gui-text="Color randomization ratio">0</param>
<param name="colorquantcycles" type="int" min="1" max="20" gui-text="Color quantization will be repeated this many times">3</param>
<param name="layering" appearance="combo" type="optiongroup" default="0" gui-text="Layering">
<option value="0">sequential</option>
<option value="1">parallel</option>
</param>
</vbox>
<spacer/>
<separator/>
<spacer/>
<vbox>
<label appearance="header">SVG Rendering</label>
<separator/>
<param name="strokewidth" type="float" min="0.0" max="9999" gui-text="SVG stroke-width">1.0</param>
<param name="linefilter" type="bool" gui-text="Noise reduction line filter">false</param>
<!--<param name="scale" type="float" min="0.0" max="9999" gui-text="Coordinate scale factor">1.0</param> disabled because we resize to the size of the original image-->
<param name="roundcoords" type="int" min="0" max="10" gui-text="Decimal places for rounding">1</param>
<param name="viewbox" type="bool" gui-text="Enable or disable SVG viewBox">false</param>
<param name="desc" type="bool" gui-text="SVG descriptions">false</param>
<spacer/>
<label appearance="header">Blur Preprocessing</label>
<separator/>
<param name="blurradius" type="int" min="1" max="5" gui-text="Selective Gaussian blur preprocessing">0</param>
<param name="blurdelta" type="float" min="0.0" max="9999" gui-text="RGBA delta treshold for selective Gaussian blur preprocessing">20.0</param>
</vbox>
</hbox>
<spacer/>
<label appearance="header">About</label>
<separator/>
<label>imagetracerjs - Simple raster image tracer and vectorizer written in JavaScript. Ported to InkScape 1.X by Mario Voigt / Stadtfabrikanten e.V. (2020)</label>
<label>It will vectorize your beautiful image into a more beautiful SVG trace with separated infills(break apart into single surfaces like a puzzle)</label>
<label appearance="url">https://fablabchemnitz.de</label>
<label>License: GNU GPL v3</label>
<effect needs-live-preview="true">
<object-type>image</object-type>
<effects-menu>
<submenu name="FabLab Chemnitz Dev"/>
</effects-menu>
</effect>
<script>
<command location="inx" interpreter="python">./fablabchemnitz_imagetracerjs/fablabchemnitz_imagetracerjs.py</command>
</script>
</inkscape-extension>

View File

@ -0,0 +1,160 @@
#!/usr/bin/env python3
import sys
import inkex
import os
import base64
import urllib.request as urllib
from PIL import Image
from io import BytesIO
from lxml import etree
"""
Extension for InkScape 1.X
Features
- will vectorize your beautiful image into a more beautiful SVG trace with separated infills(break apart into single surfaces like a puzzle)
Author: Mario Voigt / FabLab Chemnitz
Mail: mario.voigt@stadtfabrikanten.org
Date: 18.08.2020
Last patch: 18.08.2020
License: GNU GPL v3
Used version of imagetracerjs: https://github.com/jankovicsandras/imagetracerjs/commit/4d0f429efbb936db1a43db80815007a2cb113b34
"""
class Imagetracerjs (inkex.Effect):
def checkImagePath(self, node):
xlink = node.get('xlink:href')
if xlink and xlink[:5] == 'data:':
# No need, data alread embedded
return
url = urllib.urlparse(xlink)
href = urllib.url2pathname(url.path)
# Primary location always the filename itself.
path = self.absolute_href(href or '')
# Backup directory where we can find the image
if not os.path.isfile(path):
path = node.get('sodipodi:absref', path)
if not os.path.isfile(path):
inkex.errormsg('File not found "{}". Unable to embed image.').format(path)
return
if (os.path.isfile(path)):
return path
def __init__(self):
inkex.Effect.__init__(self)
self.arg_parser.add_argument("--tabs")
self.arg_parser.add_argument("--keeporiginal", type=inkex.Boolean, default=False, help="Keep original image on canvas")
self.arg_parser.add_argument("--ltres", type=float, default=1.0, help="Error treshold straight lines")
self.arg_parser.add_argument("--qtres", type=float, default=1.0, help="Error treshold quadratic splines")
self.arg_parser.add_argument("--pathomit", type=int, default=8, help="Noise reduction - discard edge node paths shorter than")
self.arg_parser.add_argument("--rightangleenhance", type=inkex.Boolean, default=True, help="Enhance right angle corners")
self.arg_parser.add_argument("--colorsampling", default="2",help="Color sampling")
self.arg_parser.add_argument("--numberofcolors", type=int, default=16, help="Number of colors to use on palette")
self.arg_parser.add_argument("--mincolorratio", type=int, default=0, help="Color randomization ratio")
self.arg_parser.add_argument("--colorquantcycles", type=int, default=3, help="Color quantization will be repeated this many times")
self.arg_parser.add_argument("--layering", default="0",help="Layering")
self.arg_parser.add_argument("--strokewidth", type=float, default=1.0, help="SVG stroke-width")
self.arg_parser.add_argument("--linefilter", type=inkex.Boolean, default=False, help="Noise reduction line filter")
#self.arg_parser.add_argument("--scale", type=float, default=1.0, help="Coordinate scale factor")
self.arg_parser.add_argument("--roundcoords", type=int, default=1, help="Decimal places for rounding")
self.arg_parser.add_argument("--viewbox", type=inkex.Boolean, default=False, help="Enable or disable SVG viewBox")
self.arg_parser.add_argument("--desc", type=inkex.Boolean, default=False, help="SVG descriptions")
self.arg_parser.add_argument("--blurradius", type=int, default=0, help="Selective Gaussian blur preprocessing")
self.arg_parser.add_argument("--blurdelta", type=float, default=20.0, help="RGBA delta treshold for selective Gaussian blur preprocessing")
def effect(self):
# internal overwrite for scale:
self.options.scale = 1.0
if (self.options.ids):
for node in self.svg.selected.values():
if node.tag == inkex.addNS('image', 'svg'):
self.path = self.checkImagePath(node) # This also ensures the file exists
if self.path is None: # check if image is embedded or linked
image_string = node.get('{http://www.w3.org/1999/xlink}href')
# find comma position
i = 0
while i < 40:
if image_string[i] == ',':
break
i = i + 1
image = Image.open(BytesIO(base64.b64decode(image_string[i + 1:len(image_string)])))
else:
image = Image.open(self.path)
# Write the embedded or linked image to temporary directory
exportfile = "imagetracerjs.png"
image.save(exportfile, "png")
nodeclipath = os.path.join("imagetracerjs-master", "nodecli", "nodecli.js")
## Build up imagetracerjs command according to your settings from extension GUI
command = "node " # "node.exe" or "node" on Windows or just "node" on Linux
if os.name=="nt": # your OS is Windows. We handle path separator as "\\" instead of unix-like "/"
command += str(nodeclipath).replace("\\", "\\\\")
else:
command += str(nodeclipath)
command += " " + exportfile
command += " ltres " + str(self.options.ltres)
command += " qtres " + str(self.options.qtres)
command += " pathomit " + str(self.options.pathomit)
command += " rightangleenhance " + str(self.options.rightangleenhance).lower()
command += " colorsampling " + str(self.options.colorsampling)
command += " numberofcolors " + str(self.options.numberofcolors)
command += " mincolorratio " + str(self.options.mincolorratio)
command += " numberofcolors " + str(self.options.numberofcolors)
command += " colorquantcycles " + str(self.options.colorquantcycles)
command += " layering " + str(self.options.layering)
command += " strokewidth " + str(self.options.strokewidth)
command += " linefilter " + str(self.options.linefilter).lower()
command += " scale " + str(self.options.scale)
command += " roundcoords " + str(self.options.roundcoords)
command += " viewbox " + str(self.options.viewbox).lower()
command += " desc " + str(self.options.desc).lower()
command += " blurradius " + str(self.options.blurradius)
command += " blurdelta " + str(self.options.blurdelta)
#inkex.utils.debug(command)
# Create the vector traced SVG file
with os.popen(command, "r") as tracerprocess:
result = tracerprocess.read()
#inkex.utils.debug(result)
# proceed if traced SVG file was successfully created
if os.path.exists(exportfile + ".svg"):
# Delete the temporary png file again because we do not need it anymore
if os.path.exists(exportfile):
os.remove(exportfile)
# new parse the SVG file and insert it as new group into the current document tree
doc = etree.parse(exportfile + ".svg").getroot()
newGroup = self.document.getroot().add(inkex.Group())
newGroup.attrib['transform'] = "matrix(" + \
str(float(node.get('width')) / float(doc.get('width'))) + \
", 0, 0 , " + \
str(float(node.get('height')) / float(doc.get('height'))) + \
"," + node.get('x') + \
"," + node.get('y') + ")"
newGroup.append(doc)
# Delet the temporary svg file
if os.path.exists(exportfile + ".svg"):
os.remove(exportfile + ".svg")
#remove the old image or not
if self.options.keeporiginal is not True:
node.getparent().remove(node)
else:
inkex.utils.debug("No image found for tracing. Please select an image first.")
Imagetracerjs().run()

View File

@ -0,0 +1,24 @@
{
"env": {
"browser": true
},
"extends": "eslint:recommended",
"rules": {
"indent": [
2,
"tab"
],
"linebreak-style": [
2,
"windows"
],
"quotes": [
2,
"single"
],
"semi": [
2,
"always"
]
}
}

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>imagetracerjs</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.wst.jsdt.core.javascriptValidator</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.wst.jsdt.core.jsNature</nature>
</natures>
</projectDescription>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry excluding="**/bower_components/*|**/node_modules/*|**/*.min.js" kind="src" path=""/>
<classpathentry kind="con" path="org.eclipse.wst.jsdt.launching.JRE_CONTAINER"/>
<classpathentry kind="output" path=""/>
</classpath>

View File

@ -0,0 +1 @@
org.eclipse.wst.jsdt.launching.JRE_CONTAINER

View File

@ -0,0 +1,25 @@
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>

View File

@ -0,0 +1,306 @@
# imagetracerjs
![alt Bitmap to Svg](docimages/s1.png)
Simple raster image tracer and vectorizer written in JavaScript.
---
## Table of contents
- [Getting started](#getting-started)
- [News](#news)
- [API](#api)
- [Options](#options)
- [Examples](#examples)
- [Process overview](#process-overview)
- [License](#license)
---
## Getting started
### Using in the Browser
Include the script:
```javascript
<script src="imagetracer_v1.2.6.js"></script>
```
Then:
```javascript
// Loading an image, tracing with the 'posterized2' option preset, and appending the SVG to an element with id="svgcontainer"
ImageTracer.imageToSVG(
'panda.png', /* input filename / URL */
function(svgstr){ ImageTracer.appendSVGString( svgstr, 'svgcontainer' ); }, /* callback function to run on SVG string result */
'posterized2' /* Option preset */
);
```
### Using with Node.js
Node.js Command line interface example:
```
imagetracerjs/nodecli>node nodecli ../panda.png outfilename panda.svg scale 10
```
Expected result:
```
imagetracerjs/nodecli/panda.svg was saved!
```
---
## News
### 1.2.6
- FIXED: hole shape parent search (Issues #31 #39)
- FIXED: Handle (absolute) paths in CLI correctly Issue #42
### 1.2.5
- RGBA ImageData check in colorquantization(), solving Issue #24 and #18
### 1.2.4
- ```options.layering``` : default 0 = sequential, new method ; 1 = parallel, old method. (Enhancement Issue #17)
- case insensitive option preset names
- README.md reorganizing
[Version history](https://github.com/jankovicsandras/imagetracerjs/blob/master/version_history.md)
---
## API
|Function name|Arguments|Returns|Run type|
|-------------|---------|-------|--------|
|```imageToSVG```|```image_url /*string*/ , callback /*function*/ , options /*optional object or preset name*/```|Nothing, ```callback(svgstring)``` will be executed|Asynchronous, Browser only|
|```imagedataToSVG```|```imagedata /*object*/ , options /*optional object or preset name*/```|```svgstring /*string*/```|Synchronous, Browser & Node.js|
|```imageToTracedata```|```image_url /*string*/ , callback /*function*/ , options /*optional object or preset name*/```|Nothing, ```callback(tracedata)``` will be executed|Asynchronous, Browser only|
|```imagedataToTracedata```|```imagedata /*object*/ , options /*optional object or preset name*/```|```tracedata /*object*/```|Synchronous, Browser & Node.js|
```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) .
### Helper Functions (Browser only)
|Function name|Arguments|Returns|Run type|
|-------------|---------|-------|--------|
|```appendSVGString```|```svgstring /*string*/, parentid /*string*/```|Nothing, an SVG will be appended to the container DOM element with id=parentid.|Synchronous, Browser only|
|```loadImage```|```url /*string*/, callback /*function*/```|Nothing, loading an image from a URL, then executing ```callback(canvas)```|Asynchronous, Browser only|
|```getImgdata```|```canvas /*object*/```|```imagedata /*object*/```|Synchronous, Browser only|
```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) .
There are more functions for advanced users, read the source if you are interested. :)
"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.
---
## Options
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.
![Option presets gallery](docimages/option_presets_small.png)
These strings can be passed instead of the options object:
```'default'```
```'posterized1'```
```'posterized2'```
```'posterized3'```
```'curvy'```
```'sharp'```
```'detailed'```
```'smoothed'```
```'grayscale'```
```'fixedpalette'```
```'randomsampling1'```
```'randomsampling2'```
```'artistic1'```
```'artistic2'```
```'artistic3'```
```'artistic4'```
[Read more about options.](https://github.com/jankovicsandras/imagetracerjs/blob/master/options.md)
---
## Examples
### Using in the Browser
Include the script:
```javascript
<script src="imagetracer_v1.2.6.js"></script>
```
Then
```javascript
// Loading smiley.png, tracing and calling alert callback on the SVG string result
ImageTracer.imageToSVG( 'smiley.png', alert );
// Almost the same with options, and the ImageTracer.appendSVGString callback will append the SVG
ImageTracer.imageToSVG( 'smiley.png', ImageTracer.appendSVGString, { ltres:0.1, qtres:1, scale:10, strokewidth:5 } );
// This uses the 'posterized2' option preset and appends the SVG to an element with id="svgcontainer"
ImageTracer.imageToSVG(
'panda.png',
function(svgstr){ ImageTracer.appendSVGString( svgstr, 'svgcontainer' ); },
'posterized2'
);
// The helper function loadImage() loads an image to a canvas, then executing callback:
// appending the canvas to a div here.
ImageTracer.loadImage(
'panda.png',
function(canvas){ (document.getElementById('canvascontainer')).appendChild(canvas); }
);
// ImageData can be traced to an SVG string synchronously.
ImageTracer.loadImage(
'smiley.png',
function(canvas){
// Getting ImageData from canvas with the helper function getImgdata().
var imgd = ImageTracer.getImgdata( canvas );
// Synchronous tracing to SVG string
var svgstr = ImageTracer.imagedataToSVG( imgd, { scale:5 } );
// Appending SVG
ImageTracer.appendSVGString( svgstr, 'svgcontainer' );
}
);
// This will load an image, trace it when loaded, and execute callback on the tracedata:
// stringifying and alerting it here.
ImageTracer.imageToTracedata(
'smiley.png',
function(tracedata){ alert( JSON.stringify( tracedata ) ); },
{ ltres:0.1, qtres:1, scale:10 }
);
// imagedataToTracedata() is very similar to the previous functions. This returns tracedata synchronously.
ImageTracer.loadImage(
'smiley.png',
function(canvas){
// Getting ImageData from canvas with the helper function getImgdata().
var imgd = ImageTracer.getImgdata(canvas);
// Synchronous tracing to tracedata
var tracedata = ImageTracer.imagedataToTracedata( imgd, { ltres:1, qtres:0.01, scale:10 } );
alert( JSON.stringify( tracedata ) );
}
);
```
### Using with Node.js CLI
Node.js Command line interface example:
```
imagetracerjs/nodecli>node nodecli ../panda.png outfilename panda.svg scale 10
```
Expected result:
```
imagetracerjs/nodecli/panda.svg was saved!
```
CLI parameter names are supported both with and without trailing dash: ```-scale 10``` and ```scale 10``` are both correct.
Almost all options are supported, except ```pal``` and ```layercontainerid```.
### Simple Node.js converting example
```javascript
"use strict";
var fs = require('fs');
var ImageTracer = require( __dirname + '/../imagetracer_v1.2.6' );
// This example uses https://github.com/arian/pngjs
// , but other libraries can be used to load an image file to an ImageData object.
var PNGReader = require( __dirname + '/PNGReader' );
// Input and output filepaths / URLs
var infilepath = __dirname + '/' + 'panda.png';
var outfilepath = __dirname + '/' + 'panda.svg';
fs.readFile(
infilepath,
function( err, bytes ){ // fs.readFile callback
if(err){ console.log(err); throw err; }
var reader = new PNGReader(bytes);
reader.parse( function( err, png ){ // PNGReader callback
if(err){ console.log(err); throw err; }
// creating an ImageData object
var myImageData = { width:png.width, height:png.height, data:png.pixels };
// tracing to SVG string
var options = { scale: 5 }; // options object; option preset string can be used also
var svgstring = ImageTracer.imagedataToSVG( myImageData, options );
// writing to file
fs.writeFile(
outfilepath,
svgstring,
function(err){ if(err){ console.log(err); throw err; } console.log( outfilepath + ' was saved!' ); }
);
});// End of reader.parse()
}// End of readFile callback()
);// End of fs.readFile()
```
### Tracedata processing / Simplify.js example
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 .
---
## Process overview
See [Process overview and Ideas for improvement](https://github.com/jankovicsandras/imagetracerjs/blob/master/process_overview.md)
---
## License
### 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](http://unlicense.org)

View File

@ -0,0 +1,34 @@
{
"name": "imagetracerjs",
"description": "raster image tracer and vectorizer, bitmap to SVG converter",
"repository": {
"type": "git",
"url": "https://github.com/jankovicsandras/imagetracerjs.git"
},
"main": "imagetracer_v1.2.6.js",
"keywords": [
"image",
"tracer",
"tracing",
"vector",
"raster",
"vectorize",
"vectorizing",
"convert",
"conversion",
"converting",
"bitmap",
"svg",
"bmp",
"png",
"jpg",
"jpeg",
"gif"
],
"authors": [ "András Jankovics" ],
"license": "Unlicense",
"bugs": {
"url": "https://github.com/jankovicsandras/imagetracerjs/issues"
},
"homepage": "https://github.com/jankovicsandras/imagetracerjs#readme"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 512 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

View File

@ -0,0 +1,82 @@
<!DOCTYPE html>
<html><head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta charset="utf-8">
<script src="imagetracer_v1.2.6.js"></script>
<script>
function onload_init(){
// Loading smiley.png, tracing and calling alert callback on the SVG string result
ImageTracer.imageToSVG( 'smiley.png', alert );
// Almost the same with options, and the ImageTracer.appendSVGString callback will append the SVG
ImageTracer.imageToSVG( 'smiley.png', ImageTracer.appendSVGString, { ltres:0.1, qtres:1, scale:10, strokewidth:5 } );
// This uses the 'Posterized2' option preset and appends the SVG to an element with id="svgcontainer"
ImageTracer.imageToSVG(
'panda.png',
function(svgstr){ ImageTracer.appendSVGString( svgstr, 'svgcontainer' ); },
'Posterized2'
);
// The helper function loadImage() loads an image to a canvas, then executing callback: appending the canvas to a div here.
ImageTracer.loadImage(
'panda.png',
function(canvas){ (document.getElementById('canvascontainer')).appendChild(canvas); }
);
// ImageData can be traced to an SVG string synchronously.
ImageTracer.loadImage(
'smiley.png',
function(canvas){
// Getting ImageData from canvas with the helper function getImgdata().
var imgd = ImageTracer.getImgdata( canvas );
// Synchronous tracing to SVG string
var svgstr = ImageTracer.imagedataToSVG( imgd, { scale:5 } );
// Appending SVG
ImageTracer.appendSVGString( svgstr, 'svgcontainer' );
}
);
// This will load an image, trace it when loaded, and execute callback on the tracedata: stringifying and alerting it here.
ImageTracer.imageToTracedata(
'smiley.png',
function(tracedata){ alert( JSON.stringify( tracedata ) ); },
{ ltres:0.1, qtres:1, scale:10 }
);
// imagedataToTracedata() is very similar to the previous functions. This returns tracedata synchronously.
ImageTracer.loadImage(
'smiley.png',
function(canvas){
// Getting ImageData from canvas with the helper function getImgdata().
var imgd = ImageTracer.getImgdata(canvas);
// Synchronous tracing to tracedata
var tracedata = ImageTracer.imagedataToTracedata( imgd, { scale:10 } );
alert( JSON.stringify( tracedata ) );
}
);
}// 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="canvascontainer"></div>
</body></html>

View File

@ -0,0 +1,69 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta charset="utf-8">
<title>imagetracer.js options gallery</title>
<style>
body {
zoom: 3;
-moz-transform: scale(3);
-moz-transform-origin: 0 0;
}
img, svg, canvas {
background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAIklEQVQImWPQ1tb+r62t/d/f3/+/v7//fwYMARgDJoEhAACRARthAfQS8AAAAABJRU5ErkJggg==) repeat;
border: solid 1px cyan;
image-rendering: optimizeSpeed; /* Older versions of FF */
image-rendering: -moz-crisp-edges; /* FF 6.0+ */
image-rendering: -webkit-optimize-contrast; /* Safari */
image-rendering: -o-crisp-edges; /* OS X & Windows Opera (12.02+) */
image-rendering: pixelated; /* Awesome future-browsers */
-ms-interpolation-mode: nearest-neighbor; /* IE */
}
table{ border: solid 1px gray; border-collapse:collapse; }
td{ border: solid 1px gray; vertical-align:top; }
th{ border: solid 1px gray; }
</style>
<script src="imagetracer_v1.2.6.js"></script>
<script>
var cnt = 0;
function onload_init(){
tracenext();
}
function tracenext(){
if(cnt<16){
ImageTracer.imageToSVG('testimages/combined.png',
function(svgstr){
document.getElementById('td'+(cnt+1)).innerHTML = svgstr +'<br/>'+Object.keys(ImageTracer.optionpresets)[cnt]+' '+svgstr.length+' bytes';
cnt++;
tracenext();
},
Object.keys(ImageTracer.optionpresets)[cnt]
);
}
}
</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="content">
<table><tbody>
<tr id="tr0" ><td id="td0"><img src="testimages/combined.png" /><br>Original</td><td id="td1"></td><td id="td2"></td><td id="td3"></td><td id="td16"></td></tr>
<tr id="tr1" ><td id="td4"></td><td id="td5"></td><td id="td6"></td><td id="td7"></td><td id="td17"></td></tr>
<tr id="tr2" ><td id="td8"></td><td id="td9"></td><td id="td10"></td><td id="td11"></td><td id="td18"></td></tr>
<tr id="tr3" ><td id="td12"></td><td id="td13"></td><td id="td14"></td><td id="td15"></td><td id="td19"></td></tr>
</tbody></table>
</div>
</body>
</html>

View File

@ -0,0 +1,334 @@
<!DOCTYPE html>
<html><head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta charset="utf-8">
<title>imagetracer.js test automation</title>
<style>
.imgcontainer{
background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAIklEQVQImWPQ1tb+r62t/d/f3/+/v7//fwYMARgDJoEhAACRARthAfQS8AAAAABJRU5ErkJggg==) repeat;
border: solid 1px cyan;
}
table{ border: solid 1px gray; border-collapse:collapse; }
td{ border: solid 1px gray; vertical-align:top; }
th{ border: solid 1px gray; }
</style>
<script src="imagetracer_v1.2.6.js"></script>
<script>
var queue;
var stats = [], tt, rt;
var timeoutid = 0, timeoutwait = 500, drawmode = false, rowid = 1, repeatnum = 1, totaltime;
function log(msg){ console.log(msg); }
function startprocess(){
if(!timeoutid){
log('starting');
var filenames = [];
// Drawmode
if(document.getElementById('cbox0').checked){ drawmode = true; }else{ drawmode = false; }
// Selected images
for(var i=1; i<17; i++){ if(document.getElementById('icb'+i).checked){ filenames.push("testimages/"+i+".png"); } }
// Repeats
repeatnum = 1;
try{ repeatnum = parseInt(email = document.getElementById('repeatinput').value); }catch(e){log(e);}
// Options
var optionses = [], ks = Object.keys(ImageTracer.optionpresets);
for(var i=0; i<ks.length; i++){
if(document.getElementById('ocb'+i).checked){ optionses.push(ImageTracer.optionpresets[ks[i]]); }
}
/*if(document.getElementById('ocb1').checked){ optionses.push({}); }
if(document.getElementById('ocb2').checked){ optionses.push({ blurradius:2, ltres:0.1, numberofcolors:64 }); }
if(document.getElementById('ocb3').checked){ optionses.push({ strokewidth:4, blurradius:5, roundcoords:0, desc:false, scale:4, }); }
*/
if(document.getElementById('ocbx').checked){
try{
optionses.push( JSON.parse( document.getElementById('custom1input').value ) );
}catch(e){ log(e); }
}
if(document.getElementById('ocby').checked){
try{
optionses.push( JSON.parse( document.getElementById('custom2input').value ) );
}catch(e){ log(e); }
}
// Queue
queue = [];
for(var k=0; k<repeatnum; k++){
for(var i=0; i<filenames.length; i++){
for(var j=0; j<optionses.length; j++){
queue.push([filenames[i],optionses[j]]);
}
}
}
// Processing
totaltime = Date.now();
processqueue();
}
}// End of startprocess()
function processqueue(){
var item = queue.shift();
if(item){
log('file '+item[0]);
ImageTracer.loadImage(item[0],
function(canvas){
// Start tracing timer
tt = Date.now();
var tracedata = ImageTracer.imagedataToTracedata( ImageTracer.getImgdata(canvas), item[1] );
// Stop tracing timer
tt = Date.now() - tt;
// Start rendering timer
rt = Date.now();
// Render
var svgstr = ImageTracer.getsvgstring(tracedata, item[1]);
// Stop rendering timer
rt = Date.now() - rt;
// Counting paths
var pcont = 0;
for(k in tracedata.layers) {
if(!tracedata.layers.hasOwnProperty(k)){ continue; }
pcont += tracedata.layers[k].length;
}
////////////////
// Draw SVG
if(drawmode){
document.getElementById('svgcontainer').innerHTML = '';
ImageTracer.appendSVGString(svgstr,'svgcontainer');
// Draw diff
// draw original
var oc = document.getElementById('originalcanvas'); oc.innerHTML = '';
oc.width = canvas.width;
oc.height = canvas.height;
var ctx = oc.getContext('2d');
var oimgd = ImageTracer.getImgdata(canvas);
ctx.putImageData(oimgd,0,0);
// draw new
backtopng(svgstr,
function(imgd){
// draw diff
var dc = document.getElementById('diffcanvas'); dc.innerHTML = '';
dc.width = imgd.width;
dc.height = imgd.height;
var dctx = dc.getContext('2d');
var idx = 0, d = 0;
var rgbadiff=0, pixeldiff=0, thisdiff=0;
// Calculating RGBA diff
var dimgd = dctx.createImageData(imgd.width,imgd.height);
for(var j=0; j<imgd.height; j++){
for(var i=0; i<imgd.width; i++){
idx = (j*imgd.width + i) * 4;
dimgd.data[idx ] = Math.abs( oimgd.data[idx ] - imgd.data[idx ] );
dimgd.data[idx+1] = Math.abs( oimgd.data[idx+1] - imgd.data[idx+1] );
dimgd.data[idx+2] = Math.abs( oimgd.data[idx+2] - imgd.data[idx+2] );
dimgd.data[idx+3] = Math.abs( oimgd.data[idx+3] - imgd.data[idx+3] );
thisdiff = dimgd.data[idx ] + dimgd.data[idx+1] + dimgd.data[idx+2] + dimgd.data[idx+3];
if(thisdiff === 0 ){ dimgd.data[idx+3] = 0; }else{ dimgd.data[idx+3] = 255; pixeldiff++; }
rgbadiff += thisdiff;
}
}
dctx.putImageData( dimgd, 0, 0 );
// Registerstats
registerstats(
item[0],
tracedata.width,
tracedata.height,
(tracedata.width*tracedata.height),
svgstr.length,
tt,
rt,
pcont,
rgbadiff / ( tracedata.width * tracedata.height * 4 ),
Math.floor(pixeldiff*100 / ( tracedata.width * tracedata.height )),
JSON.stringify(item[1])
);
// Next item
timeoutid = setTimeout( processqueue, timeoutwait );
}
);
}else{// No drawmode
// Registerstats
registerstats(
item[0],
tracedata.width,
tracedata.height,
(tracedata.width*tracedata.height),
svgstr.length,
tt,
rt,
pcont,
'n/a',
'n/a',
JSON.stringify(item[1])
);
// Next item
timeoutid = setTimeout( processqueue, timeoutwait );
}// End of drawmode check
}// End of loadImage() callback
);// End of loadImage()
}else{// No more images to process
stopprocessing();
//document.getElementById('logdiv').innerHTML += JSON.stringify(stats);
}// End of item check
}// End of processqueue()
function stopprocessing(){
clearTimeout(timeoutid); timeoutid = 0;
totaltime = Date.now() - totaltime;
alert('Time elapsed: '+totaltime+' ms.');
}
function registerstats(filename,w,h,area,svglength,tracetime,rendertime,pathcnt,rgbadiff,pixeldiff,options){
var tableref = document.getElementById('logtable');
var newrow = tableref.insertRow(-1);
newrow.innerHTML =
'<td>'+rowid+'</td>'+
'<td>'+filename+'</td>'+
'<td>'+w+'</td>'+
'<td>'+h+'</td>'+
'<td>'+area+'</td>'+
'<td>'+svglength+'</td>'+
'<td>'+tracetime+'</td>'+
'<td>'+rendertime+'</td>'+
'<td>'+pathcnt+'</td>'+
'<td>'+rgbadiff+'</td>'+
'<td>'+pixeldiff+'</td>'+
'<td>'+options+'</td>';
rowid++;
}
function backtopng(svgstr,callback){
var nc = document.getElementById('newcanvas'); nc.innerHTML = '';
var img = document.createElement('img');
img.onload = function(){
nc.width=img.width;
nc.height=img.height;
var ctx = nc.getContext('2d');
ctx.drawImage( img, 0, 0 );
callback(ImageTracer.getImgdata(nc));
};
img.setAttribute('src','data:image/svg+xml;base64,'+btoa(svgstr));
}
function onload_init(){
var k = Object.keys(ImageTracer.optionpresets), s='';
for(var i=0; i<k.length; i++){
s += '<label><input type="checkbox" id="ocb'+i+'" value="1" '+(i<6?'checked="true"':'')+' ><b>'+k[i]+'</b> : '+JSON.stringify(ImageTracer.optionpresets[k[i]])+'</label><br/>';
}
document.getElementById('ocbcontainer').innerHTML = s;
}
</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>
<form id="testcontroller">
<h1>Test images</h1>
<table id="itable">
<tbody>
<tr>
<td><label><input type="checkbox" id="icb1" value="1" checked="true">1.png</label><br><img src="testimages/1.png"></td>
<td><label><input type="checkbox" id="icb2" value="1" checked="true" >2.png</label><br><img src="testimages/2.png"></td>
<td><label><input type="checkbox" id="icb3" value="1" checked="true" >3.png</label><br><img src="testimages/3.png"></td>
<td><label><input type="checkbox" id="icb4" value="1" checked="true" >4.png</label><br><img src="testimages/4.png"></td>
</tr>
<tr>
<td><label><input type="checkbox" id="icb5" value="1" checked="true" >5.png</label><br><img src="testimages/5.png"></td>
<td><label><input type="checkbox" id="icb6" value="1" checked="true" >6.png</label><br><img src="testimages/6.png"></td>
<td><label><input type="checkbox" id="icb7" value="1" checked="true" >7.png</label><br><img src="testimages/7.png"></td>
<td><label><input type="checkbox" id="icb8" value="1" checked="true" >8.png</label><br><img src="testimages/8.png"></td>
</tr>
<tr>
<td><label><input type="checkbox" id="icb9" value="1" checked="true" >9.png</label><br><img src="testimages/9.png"></td>
<td><label><input type="checkbox" id="icb10" value="1" checked="true" >10.png</label><br><img src="testimages/10.png"></td>
<td><label><input type="checkbox" id="icb11" value="1" checked="true" >11.png</label><br><img src="testimages/11.png"></td>
<td><label><input type="checkbox" id="icb12" value="1" checked="true" >12.png</label><br><img src="testimages/12.png"></td>
</tr>
<tr>
<td><label><input type="checkbox" id="icb13" value="1" checked="true" >13.png</label><br><img src="testimages/13.png"></td>
<td><label><input type="checkbox" id="icb14" value="1" checked="true" >14.png</label><br><img src="testimages/14.png"></td>
<td><label><input type="checkbox" id="icb15" value="1" checked="true" >15.png</label><br><img src="testimages/15.png"></td>
<td><label><input type="checkbox" id="icb16" value="1" checked="true" >16.png</label><br><img src="testimages/16.png"></td>
</tr>
</tbody>
</table>
<h1>Settings</h1>
<label><input type="checkbox" id="cbox0" value="1" checked="true">Draw SVG</label><br>
<div id="ocbcontainer"></div>
<label><input type="checkbox" id="ocbx" value="1" >Custom Options 1:</label><input type="text" id="custom1input" value="" style="width:40em" ><br>
<label><input type="checkbox" id="ocby" value="1" >Custom Options 2:</label><input type="text" id="custom2input" value="" style="width:40em" ><br>
Example Custom Options:<br>{"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}<br>
<label><input type="text" id="repeatinput" value="1">Repeats</label><br>
<button id="startbutton" type="button" onclick="startprocess()" >START</button>
<button id="stopbutton" type="button" onclick="stopprocessing()" >STOP</button>
</form>
<h1>Result (if Draw SVG is active)</h1>
<table id="itable">
<thead>
<tr><th>Traced SVG</th><th>Original raster</th><th>SVG rendered as raster</th><th>Difference</th></tr>
</thead>
<tbody>
<tr>
<td><div id="svgcontainer" class="imgcontainer"></div></td>
<td><canvas id="originalcanvas" class="imgcontainer"></canvas></td>
<td><canvas id="newcanvas" class="imgcontainer"></canvas></td>
<td><canvas id="diffcanvas" class="imgcontainer"></canvas></td>
</tr>
</tbody>
</table>
<h1>Measurements</h1>
<div id="logdiv">
<table id="logtable">
<thead>
<tr>
<th>RowID</th>
<th>Filename</th>
<th>width (pixels)</th>
<th>height (pixels)</th>
<th>area (pixels)</th>
<th>SVG string length (bytes)</th>
<th>Tracing time (ms)</th>
<th>Rendering time (ms)</th>
<th>Nr. of paths</th>
<th>RGBA difference (cummulative RGBA difference / (area*4))</th>
<th>Different pixels (%)</th>
<th>Options</th>
</tr>
</thead>
<tbody id="logtbody">
</tbody>
</table>
</div>
</body></html>

View File

@ -0,0 +1,160 @@
"use strict";
var PNG = function(){
// initialize all members to keep the same hidden class
this.width = 0;
this.height = 0;
this.bitDepth = 0;
this.colorType = 0;
this.compressionMethod = 0;
this.filterMethod = 0;
this.interlaceMethod = 0;
this.colors = 0;
this.alpha = false;
this.pixelBits = 0;
this.palette = null;
this.pixels = null;
};
PNG.prototype.getWidth = function(){
return this.width;
};
PNG.prototype.setWidth = function(width){
this.width = width;
};
PNG.prototype.getHeight = function(){
return this.height;
};
PNG.prototype.setHeight = function(height){
this.height = height;
};
PNG.prototype.getBitDepth = function(){
return this.bitDepth;
};
PNG.prototype.setBitDepth = function(bitDepth){
if ([2, 4, 8, 16].indexOf(bitDepth) === -1){
throw new Error("invalid bith depth " + bitDepth);
}
this.bitDepth = bitDepth;
};
PNG.prototype.getColorType = function(){
return this.colorType;
};
PNG.prototype.setColorType = function(colorType){
// Color Allowed Interpretation
// Type Bit Depths
//
// 0 1,2,4,8,16 Each pixel is a grayscale sample.
//
// 2 8,16 Each pixel is an R,G,B triple.
//
// 3 1,2,4,8 Each pixel is a palette index;
// a PLTE chunk must appear.
//
// 4 8,16 Each pixel is a grayscale sample,
// followed by an alpha sample.
//
// 6 8,16 Each pixel is an R,G,B triple,
// followed by an alpha sample.
var colors = 0, alpha = false;
switch (colorType){
case 0: colors = 1; break;
case 2: colors = 3; break;
case 3: colors = 1; break;
case 4: colors = 2; alpha = true; break;
case 6: colors = 4; alpha = true; break;
default: throw new Error("invalid color type");
}
this.colors = colors;
this.alpha = alpha;
this.colorType = colorType;
};
PNG.prototype.getCompressionMethod = function(){
return this.compressionMethod;
};
PNG.prototype.setCompressionMethod = function(compressionMethod){
if (compressionMethod !== 0){
throw new Error("invalid compression method " + compressionMethod);
}
this.compressionMethod = compressionMethod;
};
PNG.prototype.getFilterMethod = function(){
return this.filterMethod;
};
PNG.prototype.setFilterMethod = function(filterMethod){
if (filterMethod !== 0){
throw new Error("invalid filter method " + filterMethod);
}
this.filterMethod = filterMethod;
};
PNG.prototype.getInterlaceMethod = function(){
return this.interlaceMethod;
};
PNG.prototype.setInterlaceMethod = function(interlaceMethod){
if (interlaceMethod !== 0 && interlaceMethod !== 1){
throw new Error("invalid interlace method " + interlaceMethod);
}
this.interlaceMethod = interlaceMethod;
};
PNG.prototype.setPalette = function(palette){
if (palette.length % 3 !== 0){
throw new Error("incorrect PLTE chunk length");
}
if (palette.length > (Math.pow(2, this.bitDepth) * 3)){
throw new Error("palette has more colors than 2^bitdepth");
}
this.palette = palette;
};
PNG.prototype.getPalette = function(){
return this.palette;
};
/**
* get the pixel color on a certain location in a normalized way
* result is an array: [red, green, blue, alpha]
*/
PNG.prototype.getPixel = function(x, y){
if (!this.pixels) throw new Error("pixel data is empty");
if (x >= this.width || y >= this.height){
throw new Error("x,y position out of bound");
}
var i = this.colors * this.bitDepth / 8 * (y * this.width + x);
var pixels = this.pixels;
switch (this.colorType){
case 0: return [pixels[i], pixels[i], pixels[i], 255];
case 2: return [pixels[i], pixels[i + 1], pixels[i + 2], 255];
case 3: return [
this.palette[pixels[i] * 3 + 0],
this.palette[pixels[i] * 3 + 1],
this.palette[pixels[i] * 3 + 2],
255];
case 4: return [pixels[i], pixels[i], pixels[i], pixels[i + 1]];
case 6: return [pixels[i], pixels[i + 1], pixels[i + 2], pixels[i + 3]];
}
};
module.exports = PNG;

View File

@ -0,0 +1,418 @@
/*global Uint8Array:true ArrayBuffer:true */
"use strict";
var zlib = require('zlib');
var PNG = require('./PNG');
var inflate = function(data, callback){
return zlib.inflate(new Buffer(data), callback);
};
var slice = Array.prototype.slice;
var toString = Object.prototype.toString;
function equalBytes(a, b){
if (a.length != b.length) return false;
for (var l = a.length; l--;) if (a[l] != b[l]) return false;
return true;
}
function readUInt32(buffer, offset){
return (buffer[offset] << 24) +
(buffer[offset + 1] << 16) +
(buffer[offset + 2] << 8) +
(buffer[offset + 3] << 0);
}
function readUInt16(buffer, offset){
return (buffer[offset + 1] << 8) + (buffer[offset] << 0);
}
function readUInt8(buffer, offset){
return buffer[offset] << 0;
}
function bufferToString(buffer){
var str = '';
for (var i = 0; i < buffer.length; i++){
str += String.fromCharCode(buffer[i]);
}
return str;
}
var PNGReader = function(bytes){
if (typeof bytes == 'string'){
var bts = bytes;
bytes = new Array(bts.length);
for (var i = 0, l = bts.length; i < l; i++){
bytes[i] = bts[i].charCodeAt(0);
}
} else {
var type = toString.call(bytes).slice(8, -1);
if (type == 'ArrayBuffer') bytes = new Uint8Array(bytes);
}
// current pointer
this.i = 0;
// bytes buffer
this.bytes = bytes;
// Output object
this.png = new PNG();
this.dataChunks = [];
};
PNGReader.prototype.readBytes = function(length){
var end = this.i + length;
if (end > this.bytes.length){
throw new Error('Unexpectedly reached end of file');
}
var bytes = slice.call(this.bytes, this.i, end);
this.i = end;
return bytes;
};
/**
* http://www.w3.org/TR/2003/REC-PNG-20031110/#5PNG-file-signature
*/
PNGReader.prototype.decodeHeader = function(){
if (this.i !== 0){
throw new Error('file pointer should be at 0 to read the header');
}
var header = this.readBytes(8);
if (!equalBytes(header, [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A])){
throw new Error('invalid PNGReader file (bad signature)');
}
this.header = header;
};
/**
* http://www.w3.org/TR/2003/REC-PNG-20031110/#5Chunk-layout
*
* length = 4 bytes
* type = 4 bytes (IHDR, PLTE, IDAT, IEND or others)
* chunk = length bytes
* crc = 4 bytes
*/
PNGReader.prototype.decodeChunk = function(){
var length = readUInt32(this.readBytes(4), 0);
if (length < 0){
throw new Error('Bad chunk length ' + (0xFFFFFFFF & length));
}
var type = bufferToString(this.readBytes(4));
var chunk = this.readBytes(length);
var crc = this.readBytes(4);
switch (type){
case 'IHDR': this.decodeIHDR(chunk); break;
case 'PLTE': this.decodePLTE(chunk); break;
case 'IDAT': this.decodeIDAT(chunk); break;
case 'IEND': this.decodeIEND(chunk); break;
}
return type;
};
/**
* http://www.w3.org/TR/2003/REC-PNG-20031110/#11IHDR
* http://www.libpng.org/pub/png/spec/1.2/png-1.2-pdg.html#C.IHDR
*
* Width 4 bytes
* Height 4 bytes
* Bit depth 1 byte
* Colour type 1 byte
* Compression method 1 byte
* Filter method 1 byte
* Interlace method 1 byte
*/
PNGReader.prototype.decodeIHDR = function(chunk){
var png = this.png;
png.setWidth( readUInt32(chunk, 0));
png.setHeight( readUInt32(chunk, 4));
png.setBitDepth( readUInt8(chunk, 8));
png.setColorType( readUInt8(chunk, 9));
png.setCompressionMethod( readUInt8(chunk, 10));
png.setFilterMethod( readUInt8(chunk, 11));
png.setInterlaceMethod( readUInt8(chunk, 12));
};
/**
*
* http://www.w3.org/TR/PNG/#11PLTE
*/
PNGReader.prototype.decodePLTE = function(chunk){
this.png.setPalette(chunk);
};
/**
* http://www.w3.org/TR/2003/REC-PNG-20031110/#11IDAT
*/
PNGReader.prototype.decodeIDAT = function(chunk){
// multiple IDAT chunks will concatenated
this.dataChunks.push(chunk);
};
/**
* http://www.w3.org/TR/2003/REC-PNG-20031110/#11IEND
*/
PNGReader.prototype.decodeIEND = function(){
};
/**
* Uncompress IDAT chunks
*/
PNGReader.prototype.decodePixels = function(callback){
var png = this.png;
var reader = this;
var length = 0;
var i, j, k, l;
for (l = this.dataChunks.length; l--;) length += this.dataChunks[l].length;
var data = new Buffer(length);
for (i = 0, k = 0, l = this.dataChunks.length; i < l; i++){
var chunk = this.dataChunks[i];
for (j = 0; j < chunk.length; j++) data[k++] = chunk[j];
}
inflate(data, function(err, data){
if (err) return callback(err);
try {
if (png.getInterlaceMethod() === 0){
reader.interlaceNone(data);
} else {
reader.interlaceAdam7(data);
}
} catch (e){
return callback(e);
}
callback();
});
};
// Different interlace methods
PNGReader.prototype.interlaceNone = function(data){
var png = this.png;
// bytes per pixel
var bpp = Math.max(1, png.colors * png.bitDepth / 8);
// color bytes per row
var cpr = bpp * png.width;
var pixels = new Buffer(bpp * png.width * png.height);
var scanline;
var offset = 0;
for (var i = 0; i < data.length; i += cpr + 1){
scanline = slice.call(data, i + 1, i + cpr + 1);
switch (readUInt8(data, i)){
case 0: this.unFilterNone( scanline, pixels, bpp, offset, cpr); break;
case 1: this.unFilterSub( scanline, pixels, bpp, offset, cpr); break;
case 2: this.unFilterUp( scanline, pixels, bpp, offset, cpr); break;
case 3: this.unFilterAverage(scanline, pixels, bpp, offset, cpr); break;
case 4: this.unFilterPaeth( scanline, pixels, bpp, offset, cpr); break;
default: throw new Error("unkown filtered scanline");
}
offset += cpr;
}
png.pixels = pixels;
};
PNGReader.prototype.interlaceAdam7 = function(data){
throw new Error("Adam7 interlacing is not implemented yet");
};
// Unfiltering
/**
* No filtering, direct copy
*/
PNGReader.prototype.unFilterNone = function(scanline, pixels, bpp, of, length){
for (var i = 0, to = length; i < to; i++){
pixels[of + i] = scanline[i];
}
};
/**
* The Sub() filter transmits the difference between each byte and the value
* of the corresponding byte of the prior pixel.
* Sub(x) = Raw(x) + Raw(x - bpp)
*/
PNGReader.prototype.unFilterSub = function(scanline, pixels, bpp, of, length){
var i = 0;
for (; i < bpp; i++) pixels[of + i] = scanline[i];
for (; i < length; i++){
// Raw(x) + Raw(x - bpp)
pixels[of + i] = (scanline[i] + pixels[of + i - bpp]) & 0xFF;
}
};
/**
* The Up() filter is just like the Sub() filter except that the pixel
* immediately above the current pixel, rather than just to its left, is used
* as the predictor.
* Up(x) = Raw(x) + Prior(x)
*/
PNGReader.prototype.unFilterUp = function(scanline, pixels, bpp, of, length){
var i = 0, byte, prev;
// Prior(x) is 0 for all x on the first scanline
if ((of - length) < 0) for (; i < length; i++){
pixels[of + i] = scanline[i];
} else for (; i < length; i++){
// Raw(x)
byte = scanline[i];
// Prior(x)
prev = pixels[of + i - length];
pixels[of + i] = (byte + prev) & 0xFF;
}
};
/**
* The Average() filter uses the average of the two neighboring pixels (left
* and above) to predict the value of a pixel.
* Average(x) = Raw(x) + floor((Raw(x-bpp)+Prior(x))/2)
*/
PNGReader.prototype.unFilterAverage = function(scanline, pixels, bpp, of, length){
var i = 0, byte, prev, prior;
if ((of - length) < 0){
// Prior(x) == 0 && Raw(x - bpp) == 0
for (; i < bpp; i++){
pixels[of + i] = scanline[i];
}
// Prior(x) == 0 && Raw(x - bpp) != 0 (right shift, prevent doubles)
for (; i < length; i++){
pixels[of + i] = (scanline[i] + (pixels[of + i - bpp] >> 1)) & 0xFF;
}
} else {
// Prior(x) != 0 && Raw(x - bpp) == 0
for (; i < bpp; i++){
pixels[of + i] = (scanline[i] + (pixels[of - length + i] >> 1)) & 0xFF;
}
// Prior(x) != 0 && Raw(x - bpp) != 0
for (; i < length; i++){
byte = scanline[i];
prev = pixels[of + i - bpp];
prior = pixels[of + i - length];
pixels[of + i] = (byte + (prev + prior >> 1)) & 0xFF;
}
}
};
/**
* The Paeth() filter computes a simple linear function of the three
* neighboring pixels (left, above, upper left), then chooses as predictor
* the neighboring pixel closest to the computed value. This technique is due
* to Alan W. Paeth.
* Paeth(x) = Raw(x) +
* PaethPredictor(Raw(x-bpp), Prior(x), Prior(x-bpp))
* function PaethPredictor (a, b, c)
* begin
* ; a = left, b = above, c = upper left
* p := a + b - c ; initial estimate
* pa := abs(p - a) ; distances to a, b, c
* pb := abs(p - b)
* pc := abs(p - c)
* ; return nearest of a,b,c,
* ; breaking ties in order a,b,c.
* if pa <= pb AND pa <= pc then return a
* else if pb <= pc then return b
* else return c
* end
*/
PNGReader.prototype.unFilterPaeth = function(scanline, pixels, bpp, of, length){
var i = 0, raw, a, b, c, p, pa, pb, pc, pr;
if ((of - length) < 0){
// Prior(x) == 0 && Raw(x - bpp) == 0
for (; i < bpp; i++){
pixels[of + i] = scanline[i];
}
// Prior(x) == 0 && Raw(x - bpp) != 0
// paethPredictor(x, 0, 0) is always x
for (; i < length; i++){
pixels[of + i] = (scanline[i] + pixels[of + i - bpp]) & 0xFF;
}
} else {
// Prior(x) != 0 && Raw(x - bpp) == 0
// paethPredictor(x, 0, 0) is always x
for (; i < bpp; i++){
pixels[of + i] = (scanline[i] + pixels[of + i - length]) & 0xFF;
}
// Prior(x) != 0 && Raw(x - bpp) != 0
for (; i < length; i++){
raw = scanline[i];
a = pixels[of + i - bpp];
b = pixels[of + i - length];
c = pixels[of + i - length - bpp];
p = a + b - c;
pa = Math.abs(p - a);
pb = Math.abs(p - b);
pc = Math.abs(p - c);
if (pa <= pb && pa <= pc) pr = a;
else if (pb <= pc) pr = b;
else pr = c;
pixels[of + i] = (raw + pr) & 0xFF;
}
}
};
/**
* Parse the PNG file
*
* reader.parse(options, callback)
* OR
* reader.parse(callback)
*
* OPTIONS:
* option | type | default
* ----------------------------
* data boolean true should it read the pixel data
*/
PNGReader.prototype.parse = function(options, callback){
if (typeof options == 'function') callback = options;
if (typeof options != 'object') options = {};
try {
this.decodeHeader();
while (this.i < this.bytes.length){
var type = this.decodeChunk();
// stop after IHDR chunk, or after IEND
if (type == 'IHDR' && options.data === false || type == 'IEND') break;
}
var png = this.png;
this.decodePixels(function(err){
callback(err, png);
});
} catch (e){
callback(e);
}
};
module.exports = PNGReader;

View File

@ -0,0 +1,21 @@
Run like this:
>node nodecli ../panda.png -outfilename outpanda.svg numberofcolors 4 -pathomit 16 ltres 0.1
Expected result:
.../outpanda.svg was saved!
Please note:
- You can use option names with or without - e.g. ltres 0.1 or -ltres 0.1
- Any option can be defined in any order or left out to get the defaults
- option values are cast to required types e.g. with parseFloat, but no error checking here
- options.pal and option presets are not supported right now
PNG.js and PNGReader.js are saved on 2016-03-04 from
https://github.com/arian/pngjs
They have MIT license: https://github.com/arian/pngjs/blob/master/LICENSE.md

View File

@ -0,0 +1,85 @@
"use strict";
var fs = require('fs');
var ImageTracer = require( __dirname + '/../imagetracer_v1.2.6' );
// This example uses https://github.com/arian/pngjs
// , but other libraries can be used to load an image file to an ImageData object.
var PNGReader = require( __dirname + '/PNGReader' );
// CLI arguments to options
var infilename = process.argv[2], outfilename = infilename+'.svg', options = {}, thisargname = '';
if(process.argv.length>3){
for(var i=3; i<process.argv.length; i+=2 ){
thisargname = process.argv[i].toLowerCase();
// Output file name
if(thisargname === 'outfilename' || thisargname === '-outfilename'){ outfilename = process.argv[i+1]; }
// Tracing
if(thisargname === 'corsenabled' || thisargname === '-corsenabled'){ options.corsenabled = (process.argv[i+1].toLowerCase() === 'true'); }
if(thisargname === 'ltres' || thisargname === '-ltres'){ options.ltres = parseFloat(process.argv[i+1]); }
if(thisargname === 'qtres' || thisargname === '-qtres'){ options.qtres = parseFloat(process.argv[i+1]); }
if(thisargname === 'pathomit' || thisargname === '-pathomit'){ options.pathomit = parseInt(process.argv[i+1]); }
if(thisargname === 'rightangleenhance' || thisargname === '-rightangleenhance'){ options.rightangleenhance = (process.argv[i+1].toLowerCase() === 'true'); }
// Color quantization
if(thisargname === 'colorsampling' || thisargname === '-colorsampling'){ options.colorsampling = parseInt(process.argv[i+1]); }
if(thisargname === 'numberofcolors' || thisargname === '-numberofcolors'){ options.numberofcolors = parseInt(process.argv[i+1]); }
if(thisargname === 'mincolorratio' || thisargname === '-mincolorratio'){ options.mincolorratio = parseFloat(process.argv[i+1]); }
if(thisargname === 'colorquantcycles' || thisargname === '-colorquantcycles'){ options.colorquantcycles = parseInt(process.argv[i+1]); }
// Layering method
if(thisargname === 'layering' || thisargname === '-layering'){ options.layering = process.argv[i+1]; }
// SVG rendering
if(thisargname === 'strokewidth' || thisargname === '-strokewidth'){ options.strokewidth = parseFloat(process.argv[i+1]); }
if(thisargname === 'linefilter' || thisargname === '-linefilter'){ options.linefilter = (process.argv[i+1].toLowerCase() === 'true'); }
if(thisargname === 'scale' || thisargname === '-scale'){ options.scale = parseFloat(process.argv[i+1]); }
if(thisargname === 'roundcoords' || thisargname === '-roundcoords'){ options.roundcoords = parseInt(process.argv[i+1]); }
if(thisargname === 'viewbox' || thisargname === '-viewbox'){ options.viewbox = (process.argv[i+1].toLowerCase() === 'true'); }
if(thisargname === 'desc' || thisargname === '-desc'){ options.desc = (process.argv[i+1].toLowerCase() === 'true'); }
if(thisargname === 'lcpr' || thisargname === '-lcpr'){ options.lcpr = parseFloat(process.argv[i+1]); }
if(thisargname === 'qcpr' || thisargname === '-qcpr'){ options.qcpr = parseFloat(process.argv[i+1]); }
// Blur
if(thisargname === 'blurradius' || thisargname === '-blurradius'){ options.blurradius = parseInt(process.argv[i+1]); }
if(thisargname === 'blurdelta' || thisargname === '-blurdelta'){ options.blurdelta = parseInt(process.argv[i+1]); }
}// End of argv loop
}// End of command line argument list length check
fs.readFile(
infilename, // Input file path
function( err, bytes ){
if(err){ console.log(err); throw err; }
var reader = new PNGReader(bytes);
reader.parse( function( err, png ){
if(err){ console.log(err); throw err; }
// creating an ImageData object
var myImageData = { width:png.width, height:png.height, data:png.pixels };
// tracing to SVG string
var svgstring = ImageTracer.imagedataToSVG( myImageData, options );
// writing to file
fs.writeFile(
outfilename, // Output file path
svgstring,
function(err){ if(err){ console.log(err); throw err; } console.log( outfilename+' was saved!' ); }
);
});// End of reader.parse()
}// End of readFile callback()
);// End of fs.readFile()

View File

@ -0,0 +1,152 @@
## Options
Using a custom options object:
```javascript
var options = { ltres:2, qtres:1, pathomit:8 }; // Any option can be omitted which will be set to the default
// Adding custom palette. This will override numberofcolors.
options.pal = [{r:0,g:0,b:0,a:255}, {r:0,g:0,b:255,a:255}, {r:255,g:255,b:0,a:255}];
// Using these options and appending the SVG to an element with id="svgcontainer"
ImageTracer.imageToSVG(
'panda.png', /* input filename / URL */
function(svgstr){ ImageTracer.appendSVGString( svgstr, 'svgcontainer' ); }, /* callback function on SVG string result */
options /* custom options object */
);
```
#### Tracing
|Option name|Default value|Meaning|
|-----------|-------------|-------|
|```corsenabled```|```false```|Enable or disable [CORS Image loading](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image)|
|```ltres```|```1```|Error treshold for straight lines.|
|```qtres```|```1```|Error treshold for quadratic splines.|
|```pathomit```|```8```|Edge node paths shorter than this will be discarded for noise reduction.|
|```rightangleenhance```|```true```|Enhance right angle corners.|
#### Color quantization
|Option name|Default value|Meaning|
|-----------|-------------|-------|
|```colorsampling```|```2```|0: disabled, generating a palette; 1: random sampling; 2: deterministic sampling|
|```numberofcolors```|```16```|Number of colors to use on palette if pal object is not defined.|
|```mincolorratio```|```0```|Color quantization will randomize a color if fewer pixels than (total pixels*mincolorratio) has it.|
|```colorquantcycles```|```3```|Color quantization will be repeated this many times.|
#### Layering
|Option name|Default value|Meaning|
|-----------|-------------|-------|
|```layering```|```0```|0: sequential ; 1: parallel|
#### SVG rendering
|Option name|Default value|Meaning|
|-----------|-------------|-------|
|```strokewidth```|```1```|[SVG stroke-width](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-width)|
|```linefilter```|```false```|Enable or disable line filter for noise reduction.|
|```scale```|```1```|Every coordinate will be multiplied with this, to scale the SVG.|
|```roundcoords```|```1```|rounding coordinates to a given decimal place. 1 means rounded to 1 decimal place like 7.3 ; 3 means rounded to 3 places, like 7.356|
|```viewbox```|```false```|Enable or disable SVG viewBox.|
|```desc```|```false```|Enable or disable SVG descriptions.|
#### Blur preprocessing
|Option name|Default value|Meaning|
|-----------|-------------|-------|
|```blurradius```|```0```|Set this to 1..5 for selective Gaussian blur preprocessing.|
|```blurdelta```|```20```|RGBA delta treshold for selective Gaussian blur preprocessing.|
#### Debug
|Option name|Default value|Meaning|
|-----------|-------------|-------|
|```layercontainerid```|No default value|Edge node layers can be visualized if a container div's id is defined.|
|```lcpr```|```0```|Straight line control point radius, if this is greater than zero, small circles will be drawn in the SVG. Do not use this for big/complex images.|
|```qcpr```|```0```|Quadratic spline control point radius, if this is greater than zero, small circles and lines will be drawn in the SVG. Do not use this for big/complex images.|
#### Initial palette
|Option name|Default value|Meaning|
|-----------|-------------|-------|
|```pal```|No default value|Custom palette, an array of color objects: ```[ {r:0,g:0,b:0,a:255}, ... ]```|
---
## Option presets
![Option presets gallery](docimages/option_presets.png)
```javascript
this.optionpresets = {
'default': {
// Tracing
corsenabled : false,
ltres : 1,
qtres : 1,
pathomit : 8,
rightangleenhance : true,
// Color quantization
colorsampling : 2,
numberofcolors : 16,
mincolorratio : 0,
colorquantcycles : 3,
// Layering method
layering : 0,
// SVG rendering
strokewidth : 1,
linefilter : false,
scale : 1,
roundcoords : 1,
viewbox : false,
desc : false,
lcpr : 0,
qcpr : 0,
// Blur
blurradius : 0,
blurdelta : 20
},
'posterized1': { colorsampling:0, numberofcolors:2 },
'posterized2': { numberofcolors:4, blurradius:5 },
'curvy': { ltres:0.01, linefilter:true, rightangleenhance:false },
'sharp': { qtres:0.01, linefilter:false },
'detailed': { pathomit:0, roundcoords:2, ltres:0.5, qtres:0.5, numberofcolors:64 },
'smoothed': { blurradius:5, blurdelta: 64 },
'grayscale': { colorsampling:0, colorquantcycles:1, numberofcolors:7 },
'fixedpalette': { colorsampling:0, colorquantcycles:1, numberofcolors:27 },
'randomsampling1': { colorsampling:1, numberofcolors:8 },
'randomsampling2': { colorsampling:1, numberofcolors:64 },
'artistic1': { colorsampling:0, colorquantcycles:1, pathomit:0, blurradius:5, blurdelta: 64, ltres:0.01, linefilter:true, numberofcolors:16, strokewidth:2 },
'artistic2': { qtres:0.01, colorsampling:0, colorquantcycles:1, numberofcolors:4, strokewidth:0 },
'artistic3': { qtres:10, ltres:10, numberofcolors:8 },
'artistic4': { qtres:10, ltres:10, numberofcolors:64, blurradius:5, blurdelta: 256, strokewidth:2 },
'posterized3': { ltres: 1, qtres: 1, pathomit: 20, rightangleenhance: true, colorsampling: 0, numberofcolors: 3,
mincolorratio: 0, colorquantcycles: 3, blurradius: 3, blurdelta: 20, strokewidth: 0, linefilter: false,
roundcoords: 1, pal: [ { r: 0, g: 0, b: 100, a: 255 }, { r: 255, g: 255, b: 255, a: 255 } ] }
},// End of optionpresets
```

View File

@ -0,0 +1,39 @@
{
"name": "imagetracerjs",
"version": "1.2.6",
"description": "raster image tracer and vectorizer, bitmap to SVG converter",
"main": "imagetracer_v1.2.6.js",
"scripts": {
"test": "node ./nodecli/nodecli.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/jankovicsandras/imagetracerjs.git"
},
"keywords": [
"image",
"tracer",
"tracing",
"vector",
"raster",
"vectorize",
"vectorizing",
"convert",
"conversion",
"converting",
"tracing",
"bitmap",
"svg",
"bmp",
"png",
"jpg",
"jpeg",
"gif"
],
"author": "András Jankovics",
"license": "Unlicense",
"bugs": {
"url": "https://github.com/jankovicsandras/imagetracerjs/issues"
},
"homepage": "https://github.com/jankovicsandras/imagetracerjs#readme"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View File

@ -0,0 +1,57 @@
### Process overview
#### 1. Color quantization
The **colorquantization** function creates an indexed image (https://en.wikipedia.org/wiki/Indexed_color) using a variant of the https://en.wikipedia.org/wiki/K-means_clustering or https://en.wikipedia.org/wiki/K-medians_clustering algorithm.
![alt Original image (20x scale)](docimages/s2.png)
#### 2. Layer separation and edge detection
The **layering** function creates arrays for every color, and calculates edge node types. These are at the center of every 4 pixels, shown here as dots. This, **pathscan** and **interpolation** are a reinvented variant of the https://en.wikipedia.org/wiki/Marching_squares algorithm.
![alt layer 0: black](docimages/s3.png)
![alt layer 1: yellow](docimages/s4.png)
![alt edge node examples](docimages/s7.png)
#### 3. Pathscan
The **pathscan** function finds chains of edge nodes, example: the cyan dots and lines.
![alt an edge node path](docimages/s8.png)
#### 4. Interpolation
The **internodes** function interpolates the coordinates of the edge node paths. Every line segment in the new path has one of the 8 directions (East, North East, N, NW, W, SW, S, SE).
![alt interpolating](docimages/s9.png)
![alt interpolation result](docimages/s10.png)
#### 5. Tracing
The **tracepath** function splits the interpolated paths into sequences with two directions.
![alt a sequence](docimages/s11.png)
The **fitseq** function tries to fit a straight line on the start- and endpoint of the sequence (black line). If the distance error between the calculated points (black line) and actual sequence points (blue dots) is greater than the treshold, the point with the greatest error is selected (red line).
![alt fitting a straight line](docimages/s12.png)
The **fitseq** function tries to fit a quadratic spline through the error point.
![alt fitting a quadratic spline](docimages/s13.png)
![alt fitting line segments](docimages/s14.png)
![alt result with control points](docimages/s15.png)
If the **fitseq** function can not fit a straight line or a quadratic spline to the sequence with the given error tresholds, then it will split the sequence in two and recursively call **fitseq** on each part (https://en.wikipedia.org/wiki/Divide_and_conquer_algorithm).
#### 6. SVG rendering
The coordinates are rendered to [SVG Paths](https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths) in the **getsvgstring** function.
### Ideas for improvement
- Error handling: there's very little error handling now, Out of memory can happen easily with big images or many layers.
- Color quantization: other algorithms e.g. https://en.wikipedia.org/wiki/Octree ?
- Color quantization: colors with few pixels are randomized, but probably the most distant colors should be found instead.
- Tracing: 5.1. finding more suitable sequences.
- Tracing: 5.5. splitpoint = fitpoint ; this is just a guess, there might be a better splitpoint.
- Tracing: 5.7. If splitpoint-endpoint is a spline, try to add new points from the next sequence; this is not implemented.
- Tracing: cubic splines or other curves?
- Default values: they are chosen because they seemed OK, not based on calculations.
- Output: [PDF](https://en.wikipedia.org/wiki/Portable_Document_Format), [DXF](https://en.wikipedia.org/wiki/AutoCAD_DXF), [G-code](https://en.wikipedia.org/wiki/G-code) or other output?
- comparing and integrating ideas from https://en.wikipedia.org/wiki/Potrace
- Pathomit with background hole path shrinking

View File

@ -0,0 +1,126 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="imagetracer_v1.2.6.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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

View File

@ -0,0 +1,75 @@
## Version history
### 1.2.6
- FIXED: hole shape parent search (Issues #31 #39)
- FIXED: Handle (absolute) paths in CLI correctly Issue #42
### 1.2.5
- RGBA ImageData check in colorquantization(), solving Issue #24 and #18
### 1.2.4
- ```options.layering``` : default 0 = sequential, new method ; 1 = parallel, old method. (Enhancement Issue #17)
- case insensitive option preset names
- README.md reorganizing
### 1.2.3
- Node.js Command line interface (Enhancement Issue #13)
- FIXED: Pathomit problem thanks to EusthEnoptEron (Issue #14)
- options.corsenabled for [CORS Image loading](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image) thanks to neel-radica (Issue #12)
### 1.2.2
- FIXED: missing hole in path because of incorrect bounding box (Issue #11)
- Posterized3 option preset
- Changed svgpathstring() arguments to simplify getsvgstring()
### 1.2.1
- FIXED: Gaussian blur preprocessing is now independent of DOM and canvas, thus working directly with Node.js (Issue #9)
### 1.2.0
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.
- FIXED: transparent holes are now possible. ( Issue #7 and #8 )
- Deterministic output by default: ```options.colorsampling = 2``` ; ```options.mincolorratio = 0``` are deterministic and the defaults now.
- Right angle enhancing: ```options.rightangleenhance``` ( default : true )
- Option presets (see below)
- Custom strokewidth with ```options.strokewidth``` ( default : 1 )
- Line filter with ```options.linefilter``` ( default : false )
- Simplified ```getsvgstring()```; ```options.desc = false``` by default; splitpoint = fitpoint in fitseq(); small bugfixes and optimizations
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)
### 1.1.2
- minor bugfixes
- lookup based ```pathscan()```
### 1.1.1
- 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)
### 1.1.0
- it works with Node.js (external library required to load image into an ImageData object)
- export as AMD module / Node module / browser or worker variable
- new syntax: ```ImageTracer112.imageToTracedata()```, no need to initialize
- fixed ```options``` with hasOwnProperty: 0 values are not replaced with defaults, fixed polygons with coordinates x=0 or y=0
- transparency support: alpha is not discarded now, it is given more weight in color quantization
- new ```options.roundcoords``` : rounding coordinates to a given decimal place. This can reduce SVG length significantly (>20%) with minor loss of precision.
- new ```options.desc``` : setting this to false will turn off path descriptions, reducing SVG length.
- new ```options.viewbox``` : setting this to true will use viewBox instead of exact width and height
- new ```options.colorsampling``` : color quantization will sample the colors now by default, can be turned off.
- 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.
- ```imagedataToTracedata()``` returns image width and height in tracedata
- ```getsvgstring()``` needs now only ```tracedata``` and ```options``` as parameters
- ```colorquantization()``` needs now only ```imgd``` and ```options``` as parameters
- background field is removed from the results of color quantization
- ESLint passed
- test automation and simple statistics in imagetracer_test_automation.html
### 1.0.0 - 1.0.4
- first published version + bugfixes

Binary file not shown.