Added imagetracerjs extension (by Mario Voigt)
65
extensions/fablabchemnitz_imagetracerjs.inx
Normal 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>
|
@ -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()
|
@ -0,0 +1,24 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"rules": {
|
||||
"indent": [
|
||||
2,
|
||||
"tab"
|
||||
],
|
||||
"linebreak-style": [
|
||||
2,
|
||||
"windows"
|
||||
],
|
||||
"quotes": [
|
||||
2,
|
||||
"single"
|
||||
],
|
||||
"semi": [
|
||||
2,
|
||||
"always"
|
||||
]
|
||||
}
|
||||
}
|
@ -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>
|
@ -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>
|
@ -0,0 +1 @@
|
||||
org.eclipse.wst.jsdt.launching.JRE_CONTAINER
|
@ -0,0 +1 @@
|
||||
Global
|
@ -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>
|
||||
|
@ -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)
|
@ -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"
|
||||
}
|
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 1.6 MiB |
After Width: | Height: | Size: 512 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 6.5 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 4.5 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 5.6 KiB |
After Width: | Height: | Size: 7.2 KiB |
After Width: | Height: | Size: 81 KiB |
@ -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>
|
@ -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>
|
@ -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>
|
@ -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;
|
@ -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;
|
@ -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
|
@ -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()
|
@ -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
|
||||
```
|
@ -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"
|
||||
}
|
After Width: | Height: | Size: 45 KiB |
@ -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
|
@ -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>
|
After Width: | Height: | Size: 167 B |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 177 B |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 6.3 KiB |
After Width: | Height: | Size: 77 KiB |
After Width: | Height: | Size: 27 KiB |
After Width: | Height: | Size: 38 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 74 KiB |
After Width: | Height: | Size: 41 KiB |
After Width: | Height: | Size: 33 KiB |
After Width: | Height: | Size: 77 KiB |
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 49 KiB |
After Width: | Height: | Size: 84 KiB |
@ -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
|