From 7ddca94275c15581ae358a3db25c29ccb4a12294 Mon Sep 17 00:00:00 2001 From: casperlamboo Date: Wed, 20 Apr 2016 17:37:59 +0200 Subject: [PATCH 01/29] added load image from canvas function --- potrace.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/potrace.js b/potrace.js index e3ccfc3..95912bb 100644 --- a/potrace.js +++ b/potrace.js @@ -138,7 +138,17 @@ var Potrace = (function() { clear(); } imgElement.src = url; - + + } + + function loadImageFromCanvas(canvas) { + if (info.isReady) { + clear(); + } + + imgCanvas = canvas; + + loadBm(); } function setParameter(obj) { @@ -1295,6 +1305,7 @@ var Potrace = (function() { return{ loadImageFromFile: loadImageFromFile, loadImageFromUrl: loadImageFromUrl, + loadImageFromCanvas: loadImageFromCanvas, setParameter: setParameter, process: process, getSVG: getSVG, From 74a2273704a663e65e1a90b87586d5bf212771a8 Mon Sep 17 00:00:00 2001 From: casperlamboo Date: Wed, 20 Apr 2016 17:40:09 +0200 Subject: [PATCH 02/29] updated docs --- potrace.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/potrace.js b/potrace.js index 95912bb..1800a56 100644 --- a/potrace.js +++ b/potrace.js @@ -9,7 +9,9 @@ * because of the same-origin policy, can not load image from another domain. * input color/grayscale image is simply converted to binary image. no pre- * process is performed. - * + * loadImageFromUrl(canvas): load image from html5 canvas element + * when using load from canvas potrace can be used synchronous + * * setParameter({para1: value, ...}) : set parameters * parameters: * turnpolicy ("black" / "white" / "left" / "right" / "minority" / "majority") From 87dc7fcb6c41cbf187983b9f608c5782edd1f196 Mon Sep 17 00:00:00 2001 From: casperlamboo Date: Wed, 20 Apr 2016 17:54:09 +0200 Subject: [PATCH 03/29] fix typo --- potrace.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/potrace.js b/potrace.js index 1800a56..428d940 100644 --- a/potrace.js +++ b/potrace.js @@ -10,7 +10,7 @@ * input color/grayscale image is simply converted to binary image. no pre- * process is performed. * loadImageFromUrl(canvas): load image from html5 canvas element - * when using load from canvas potrace can be used synchronous + * when using load from canvas potrace can be used synchronously * * setParameter({para1: value, ...}) : set parameters * parameters: From 7dd5972c5bf67d5774ef66af13fb62d14df2c5c7 Mon Sep 17 00:00:00 2001 From: casperlamboo Date: Tue, 28 Feb 2017 18:44:31 +0100 Subject: [PATCH 04/29] install jspm --- .gitignore | 2 ++ jspm.config.js | 34 ++++++++++++++++++++++++++++++++++ package.json | 26 ++++++++++++++++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 .gitignore create mode 100644 jspm.config.js create mode 100644 package.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ce4f912 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ + +jspm_packages diff --git a/jspm.config.js b/jspm.config.js new file mode 100644 index 0000000..35b02f7 --- /dev/null +++ b/jspm.config.js @@ -0,0 +1,34 @@ +SystemJS.config({ + paths: { + "npm:": "jspm_packages/npm/", + "potrace/": "src/" + }, + browserConfig: { + "baseURL": "/" + }, + devConfig: { + "map": { + "plugin-babel": "npm:systemjs-plugin-babel@0.0.21" + } + }, + transpiler: "plugin-babel", + packages: { + "potrace": { + "main": "potrace.js", + "meta": { + "*.js": { + "loader": "plugin-babel" + } + } + } + } +}); + +SystemJS.config({ + packageConfigPaths: [ + "npm:@*/*.json", + "npm:*.json" + ], + map: {}, + packages: {} +}); diff --git a/package.json b/package.json new file mode 100644 index 0000000..46dbd21 --- /dev/null +++ b/package.json @@ -0,0 +1,26 @@ +{ + "name": "potrace", + "version": "1.0.0", + "description": "potrace =======", + "main": "src/index.js", + "jspm": { + "name": "potrace", + "main": "potrace.js", + "devDependencies": { + "plugin-babel": "npm:systemjs-plugin-babel@^0.0.21" + } + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/casperlamboo/potrace.git" + }, + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/casperlamboo/potrace/issues" + }, + "homepage": "https://github.com/casperlamboo/potrace#readme" +} From 41be3bfcda2395dbca67867edb94a6b44aedeae8 Mon Sep 17 00:00:00 2001 From: casperlamboo Date: Tue, 28 Feb 2017 18:44:45 +0100 Subject: [PATCH 05/29] separate functions --- src/Bitmap.js | 39 ++ src/Curve.js | 12 + src/Path.js | 12 + src/Point.js | 10 + src/bitmapToPathlist.js | 130 ++++++ src/getSVG.js | 60 +++ src/index.js | 41 ++ src/processPath.js | 924 ++++++++++++++++++++++++++++++++++++++++ src/utils.js | 54 +++ 9 files changed, 1282 insertions(+) create mode 100644 src/Bitmap.js create mode 100644 src/Curve.js create mode 100644 src/Path.js create mode 100644 src/Point.js create mode 100644 src/bitmapToPathlist.js create mode 100644 src/getSVG.js create mode 100644 src/index.js create mode 100644 src/processPath.js create mode 100644 src/utils.js diff --git a/src/Bitmap.js b/src/Bitmap.js new file mode 100644 index 0000000..2e4d322 --- /dev/null +++ b/src/Bitmap.js @@ -0,0 +1,39 @@ +import Point from './Point.js'; + +function Bitmap(w, h) { + this.w = w; + this.h = h; + this.size = w * h; + this.arraybuffer = new ArrayBuffer(this.size); + this.data = new Int8Array(this.arraybuffer); +} + +Bitmap.prototype.at = function (x, y) { + return (x >= 0 && x < this.w && y >=0 && y < this.h) && + this.data[this.w * y + x] === 1; +}; + +Bitmap.prototype.index = function(i) { + var point = new Point(); + point.y = Math.floor(i / this.w); + point.x = i - point.y * this.w; + return point; +}; + +Bitmap.prototype.flip = function(x, y) { + if (this.at(x, y)) { + this.data[this.w * y + x] = 0; + } else { + this.data[this.w * y + x] = 1; + } +}; + +Bitmap.prototype.copy = function() { + var bm = new Bitmap(this.w, this.h), i; + for (i = 0; i < this.size; i++) { + bm.data[i] = this.data[i]; + } + return bm; +}; + +export default Bitmap; diff --git a/src/Curve.js b/src/Curve.js new file mode 100644 index 0000000..29b49d2 --- /dev/null +++ b/src/Curve.js @@ -0,0 +1,12 @@ +function Curve(n) { + this.n = n; + this.tag = new Array(n); + this.c = new Array(n * 3); + this.alphaCurve = 0; + this.vertex = new Array(n); + this.alpha = new Array(n); + this.alpha0 = new Array(n); + this.beta = new Array(n); +} + +export default Curve; diff --git a/src/Path.js b/src/Path.js new file mode 100644 index 0000000..53b2049 --- /dev/null +++ b/src/Path.js @@ -0,0 +1,12 @@ +function Path() { + this.area = 0; + this.len = 0; + this.curve = {}; + this.pt = []; + this.minX = 100000; + this.minY = 100000; + this.maxX= -1; + this.maxY = -1; +} + +export default Path; diff --git a/src/Point.js b/src/Point.js new file mode 100644 index 0000000..5afba82 --- /dev/null +++ b/src/Point.js @@ -0,0 +1,10 @@ +function Point(x, y) { + this.x = x; + this.y = y; +} + +Point.prototype.copy = function(){ + return new Point(this.x, this.y); +}; + +export default Point; diff --git a/src/bitmapToPathlist.js b/src/bitmapToPathlist.js new file mode 100644 index 0000000..2505447 --- /dev/null +++ b/src/bitmapToPathlist.js @@ -0,0 +1,130 @@ +import Path from './Path.js'; +import Point from './Point.js'; + +function bmToPathlist(bm, info) { + + var bm1 = bm.copy(), + currentPoint = new Point(0, 0), + path; + + function findNext(point) { + var i = bm1.w * point.y + point.x; + while (i < bm1.size && bm1.data[i] !== 1) { + i++; + } + return i < bm1.size && bm1.index(i); + } + + function majority(x, y) { + var i, a, ct; + for (i = 2; i < 5; i++) { + ct = 0; + for (a = -i + 1; a <= i - 1; a++) { + ct += bm1.at(x + a, y + i - 1) ? 1 : -1; + ct += bm1.at(x + i - 1, y + a - 1) ? 1 : -1; + ct += bm1.at(x + a - 1, y - i) ? 1 : -1; + ct += bm1.at(x - i, y + a) ? 1 : -1; + } + if (ct > 0) { + return 1; + } else if (ct < 0) { + return 0; + } + } + return 0; + } + + function findPath(point) { + var path = new Path(), + x = point.x, y = point.y, + dirx = 0, diry = 1, tmp; + + path.sign = bm.at(point.x, point.y) ? "+" : "-"; + + while (1) { + path.pt.push(new Point(x, y)); + if (x > path.maxX) + path.maxX = x; + if (x < path.minX) + path.minX = x; + if (y > path.maxY) + path.maxY = y; + if (y < path.minY) + path.minY = y; + path.len++; + + x += dirx; + y += diry; + path.area -= x * diry; + + if (x === point.x && y === point.y) + break; + + var l = bm1.at(x + (dirx + diry - 1 ) / 2, y + (diry - dirx - 1) / 2); + var r = bm1.at(x + (dirx - diry - 1) / 2, y + (diry + dirx - 1) / 2); + + if (r && !l) { + if (info.turnpolicy === "right" || + (info.turnpolicy === "black" && path.sign === '+') || + (info.turnpolicy === "white" && path.sign === '-') || + (info.turnpolicy === "majority" && majority(x, y)) || + (info.turnpolicy === "minority" && !majority(x, y))) { + tmp = dirx; + dirx = -diry; + diry = tmp; + } else { + tmp = dirx; + dirx = diry; + diry = -tmp; + } + } else if (r) { + tmp = dirx; + dirx = -diry; + diry = tmp; + } else if (!l) { + tmp = dirx; + dirx = diry; + diry = -tmp; + } + } + return path; + } + + function xorPath(path){ + var y1 = path.pt[0].y, + len = path.len, + x, y, maxX, minY, i, j; + for (i = 1; i < len; i++) { + x = path.pt[i].x; + y = path.pt[i].y; + + if (y !== y1) { + minY = y1 < y ? y1 : y; + maxX = path.maxX; + for (j = x; j < maxX; j++) { + bm1.flip(j, minY); + } + y1 = y; + } + } + + } + + const pathlist = []; + + while (currentPoint = findNext(currentPoint)) { + + path = findPath(currentPoint); + + xorPath(path); + + if (path.area > info.turdsize) { + pathlist.push(path); + } + } + + return pathlist; + +} + +export default bmToPathlist; diff --git a/src/getSVG.js b/src/getSVG.js new file mode 100644 index 0000000..f4647c0 --- /dev/null +++ b/src/getSVG.js @@ -0,0 +1,60 @@ +function getSVG(pathlist, size, opt_type) { + + function path(curve) { + + function bezier(i) { + var b = 'C ' + (curve.c[i * 3 + 0].x * size).toFixed(3) + ' ' + + (curve.c[i * 3 + 0].y * size).toFixed(3) + ','; + b += (curve.c[i * 3 + 1].x * size).toFixed(3) + ' ' + + (curve.c[i * 3 + 1].y * size).toFixed(3) + ','; + b += (curve.c[i * 3 + 2].x * size).toFixed(3) + ' ' + + (curve.c[i * 3 + 2].y * size).toFixed(3) + ' '; + return b; + } + + function segment(i) { + var s = 'L ' + (curve.c[i * 3 + 1].x * size).toFixed(3) + ' ' + + (curve.c[i * 3 + 1].y * size).toFixed(3) + ' '; + s += (curve.c[i * 3 + 2].x * size).toFixed(3) + ' ' + + (curve.c[i * 3 + 2].y * size).toFixed(3) + ' '; + return s; + } + + var n = curve.n, i; + var p = 'M' + (curve.c[(n - 1) * 3 + 2].x * size).toFixed(3) + + ' ' + (curve.c[(n - 1) * 3 + 2].y * size).toFixed(3) + ' '; + for (i = 0; i < n; i++) { + if (curve.tag[i] === "CURVE") { + p += bezier(i); + } else if (curve.tag[i] === "CORNER") { + p += segment(i); + } + } + //p += + return p; + } + + var w = 846, h = 352, + len = pathlist.length, c, i, strokec, fillc, fillrule; + + var svg = ''; + svg += ''; + return svg; +} + +export default getSVG; diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..08edf7c --- /dev/null +++ b/src/index.js @@ -0,0 +1,41 @@ +import { loadImage, createBitmap } from './utils.js'; +import bitmapToPathlist from './bitmapToPathlist.js'; +import processPath from './processPath.js'; +import _getSVG from './getSVG.js'; + +const OPTIONS = { + turnpolicy: 'minority', + turdsize: 2, + optcurve: true, + alphamax: 1, + opttolerance: 0.2 +}; + +export function traceFile(file, options) { + return traceUrl(URL.createObjectURL(file), options); +} + +export async function traceUrl(url, options) { + const image = await loadImage(url, options); + + const canvas = document.createElement('canvas'); + canvas.width = image.width; + canvas.height = image.height; + + const context = canvas.getContext('2d'); + context.drawImage(image, 0, 0); + + return traceCanvas(canvas, options); +} + +export function traceCanvas(canvas, options = {}) { + options = { ...options, ...OPTIONS }; + + const bitmap = createBitmap(canvas); + const pathList = bitmapToPathlist(bitmap, options); + const path = processPath(pathList, options); + + return path; +} + +export const getSVG = _getSVG; diff --git a/src/processPath.js b/src/processPath.js new file mode 100644 index 0000000..b11b418 --- /dev/null +++ b/src/processPath.js @@ -0,0 +1,924 @@ +import Point from './Point.js'; +import Curve from './Curve.js'; + +function processPath(pathlist, info) { + + function Quad() { + this.data = [0,0,0,0,0,0,0,0,0]; + } + + Quad.prototype.at = function(x, y) { + return this.data[x * 3 + y]; + }; + + function Sum(x, y, xy, x2, y2) { + this.x = x; + this.y = y; + this.xy = xy; + this.x2 = x2; + this.y2 = y2; + } + + function mod(a, n) { + return a >= n ? a % n : a>=0 ? a : n-1-(-1-a) % n; + } + + function xprod(p1, p2) { + return p1.x * p2.y - p1.y * p2.x; + } + + function cyclic(a, b, c) { + if (a <= c) { + return (a <= b && b < c); + } else { + return (a <= b || b < c); + } + } + + function sign(i) { + return i > 0 ? 1 : i < 0 ? -1 : 0; + } + + function quadform(Q, w) { + var v = new Array(3), i, j, sum; + + v[0] = w.x; + v[1] = w.y; + v[2] = 1; + sum = 0.0; + + for (i=0; i<3; i++) { + for (j=0; j<3; j++) { + sum += v[i] * Q.at(i, j) * v[j]; + } + } + return sum; + } + + function interval(lambda, a, b) { + var res = new Point(); + + res.x = a.x + lambda * (b.x - a.x); + res.y = a.y + lambda * (b.y - a.y); + return res; + } + + function dorth_infty(p0, p2) { + var r = new Point(); + + r.y = sign(p2.x - p0.x); + r.x = -sign(p2.y - p0.y); + + return r; + } + + function ddenom(p0, p2) { + var r = dorth_infty(p0, p2); + + return r.y * (p2.x - p0.x) - r.x * (p2.y - p0.y); + } + + function dpara(p0, p1, p2) { + var x1, y1, x2, y2; + + x1 = p1.x - p0.x; + y1 = p1.y - p0.y; + x2 = p2.x - p0.x; + y2 = p2.y - p0.y; + + return x1 * y2 - x2 * y1; + } + + function cprod(p0, p1, p2, p3) { + var x1, y1, x2, y2; + + x1 = p1.x - p0.x; + y1 = p1.y - p0.y; + x2 = p3.x - p2.x; + y2 = p3.y - p2.y; + + return x1 * y2 - x2 * y1; + } + + function iprod(p0, p1, p2) { + var x1, y1, x2, y2; + + x1 = p1.x - p0.x; + y1 = p1.y - p0.y; + x2 = p2.x - p0.x; + y2 = p2.y - p0.y; + + return x1*x2 + y1*y2; + } + + function iprod1(p0, p1, p2, p3) { + var x1, y1, x2, y2; + + x1 = p1.x - p0.x; + y1 = p1.y - p0.y; + x2 = p3.x - p2.x; + y2 = p3.y - p2.y; + + return x1 * x2 + y1 * y2; + } + + function ddist(p, q) { + return Math.sqrt((p.x - q.x) * (p.x - q.x) + (p.y - q.y) * (p.y - q.y)); + } + + function bezier(t, p0, p1, p2, p3) { + var s = 1 - t, res = new Point(); + + res.x = s*s*s*p0.x + 3*(s*s*t)*p1.x + 3*(t*t*s)*p2.x + t*t*t*p3.x; + res.y = s*s*s*p0.y + 3*(s*s*t)*p1.y + 3*(t*t*s)*p2.y + t*t*t*p3.y; + + return res; + } + + function tangent(p0, p1, p2, p3, q0, q1) { + var A, B, C, a, b, c, d, s, r1, r2; + + A = cprod(p0, p1, q0, q1); + B = cprod(p1, p2, q0, q1); + C = cprod(p2, p3, q0, q1); + + a = A - 2 * B + C; + b = -2 * A + 2 * B; + c = A; + + d = b * b - 4 * a * c; + + if (a===0 || d<0) { + return -1.0; + } + + s = Math.sqrt(d); + + r1 = (-b + s) / (2 * a); + r2 = (-b - s) / (2 * a); + + if (r1 >= 0 && r1 <= 1) { + return r1; + } else if (r2 >= 0 && r2 <= 1) { + return r2; + } else { + return -1.0; + } + } + + function calcSums(path) { + var i, x, y; + path.x0 = path.pt[0].x; + path.y0 = path.pt[0].y; + + path.sums = []; + var s = path.sums; + s.push(new Sum(0, 0, 0, 0, 0)); + for(i = 0; i < path.len; i++){ + x = path.pt[i].x - path.x0; + y = path.pt[i].y - path.y0; + s.push(new Sum(s[i].x + x, s[i].y + y, s[i].xy + x * y, + s[i].x2 + x * x, s[i].y2 + y * y)); + } + } + + function calcLon(path) { + + var n = path.len, pt = path.pt, dir, + pivk = new Array(n), + nc = new Array(n), + ct = new Array(4); + path.lon = new Array(n); + + var constraint = [new Point(), new Point()], + cur = new Point(), + off = new Point(), + dk = new Point(), + foundk; + + var i, j, k1, a, b, c, d, k = 0; + for(i = n - 1; i >= 0; i--){ + if (pt[i].x != pt[k].x && pt[i].y != pt[k].y) { + k = i + 1; + } + nc[i] = k; + } + + for (i = n - 1; i >= 0; i--) { + ct[0] = ct[1] = ct[2] = ct[3] = 0; + dir = (3 + 3 * (pt[mod(i + 1, n)].x - pt[i].x) + + (pt[mod(i + 1, n)].y - pt[i].y)) / 2; + ct[dir]++; + + constraint[0].x = 0; + constraint[0].y = 0; + constraint[1].x = 0; + constraint[1].y = 0; + + k = nc[i]; + k1 = i; + while (1) { + foundk = 0; + dir = (3 + 3 * sign(pt[k].x - pt[k1].x) + + sign(pt[k].y - pt[k1].y)) / 2; + ct[dir]++; + + if (ct[0] && ct[1] && ct[2] && ct[3]) { + pivk[i] = k1; + foundk = 1; + break; + } + + cur.x = pt[k].x - pt[i].x; + cur.y = pt[k].y - pt[i].y; + + if (xprod(constraint[0], cur) < 0 || xprod(constraint[1], cur) > 0) { + break; + } + + if (Math.abs(cur.x) <= 1 && Math.abs(cur.y) <= 1) { + + } else { + off.x = cur.x + ((cur.y >= 0 && (cur.y > 0 || cur.x < 0)) ? 1 : -1); + off.y = cur.y + ((cur.x <= 0 && (cur.x < 0 || cur.y < 0)) ? 1 : -1); + if (xprod(constraint[0], off) >= 0) { + constraint[0].x = off.x; + constraint[0].y = off.y; + } + off.x = cur.x + ((cur.y <= 0 && (cur.y < 0 || cur.x < 0)) ? 1 : -1); + off.y = cur.y + ((cur.x >= 0 && (cur.x > 0 || cur.y < 0)) ? 1 : -1); + if (xprod(constraint[1], off) <= 0) { + constraint[1].x = off.x; + constraint[1].y = off.y; + } + } + k1 = k; + k = nc[k1]; + if (!cyclic(k, i, k1)) { + break; + } + } + if (foundk === 0) { + dk.x = sign(pt[k].x-pt[k1].x); + dk.y = sign(pt[k].y-pt[k1].y); + cur.x = pt[k1].x - pt[i].x; + cur.y = pt[k1].y - pt[i].y; + + a = xprod(constraint[0], cur); + b = xprod(constraint[0], dk); + c = xprod(constraint[1], cur); + d = xprod(constraint[1], dk); + + j = 10000000; + if (b < 0) { + j = Math.floor(a / -b); + } + if (d > 0) { + j = Math.min(j, Math.floor(-c / d)); + } + pivk[i] = mod(k1+j,n); + } + } + + j=pivk[n-1]; + path.lon[n-1]=j; + for (i=n-2; i>=0; i--) { + if (cyclic(i+1,pivk[i],j)) { + j=pivk[i]; + } + path.lon[i]=j; + } + + for (i=n-1; cyclic(mod(i+1,n),j,path.lon[i]); i--) { + path.lon[i] = j; + } + } + + function bestPolygon(path) { + + function penalty3(path, i, j) { + + var n = path.len, pt = path.pt, sums = path.sums; + var x, y, xy, x2, y2, + k, a, b, c, s, + px, py, ex, ey, + r = 0; + if (j>=n) { + j -= n; + r = 1; + } + + if (r === 0) { + x = sums[j+1].x - sums[i].x; + y = sums[j+1].y - sums[i].y; + x2 = sums[j+1].x2 - sums[i].x2; + xy = sums[j+1].xy - sums[i].xy; + y2 = sums[j+1].y2 - sums[i].y2; + k = j+1 - i; + } else { + x = sums[j+1].x - sums[i].x + sums[n].x; + y = sums[j+1].y - sums[i].y + sums[n].y; + x2 = sums[j+1].x2 - sums[i].x2 + sums[n].x2; + xy = sums[j+1].xy - sums[i].xy + sums[n].xy; + y2 = sums[j+1].y2 - sums[i].y2 + sums[n].y2; + k = j+1 - i + n; + } + + px = (pt[i].x + pt[j].x) / 2.0 - pt[0].x; + py = (pt[i].y + pt[j].y) / 2.0 - pt[0].y; + ey = (pt[j].x - pt[i].x); + ex = -(pt[j].y - pt[i].y); + + a = ((x2 - 2*x*px) / k + px*px); + b = ((xy - x*py - y*px) / k + px*py); + c = ((y2 - 2*y*py) / k + py*py); + + s = ex*ex*a + 2*ex*ey*b + ey*ey*c; + + return Math.sqrt(s); + } + + var i, j, m, k, + n = path.len, + pen = new Array(n + 1), + prev = new Array(n + 1), + clip0 = new Array(n), + clip1 = new Array(n + 1), + seg0 = new Array (n + 1), + seg1 = new Array(n + 1), + thispen, best, c; + + for (i=0; i0; j--) { + seg1[j] = i; + i = clip1[i]; + } + seg1[0] = 0; + + pen[0]=0; + for (j=1; j<=m; j++) { + for (i=seg1[j]; i<=seg0[j]; i++) { + best = -1; + for (k=seg0[j-1]; k>=clip1[i]; k--) { + thispen = penalty3(path, k, i) + pen[k]; + if (best < 0 || thispen < best) { + prev[i] = k; + best = thispen; + } + } + pen[i] = best; + } + } + path.m = m; + path.po = new Array(m); + + for (i=n, j=m-1; i>0; j--) { + i = prev[i]; + path.po[j] = i; + } + } + + function adjustVertices(path) { + + function pointslope(path, i, j, ctr, dir) { + + var n = path.len, sums = path.sums, + x, y, x2, xy, y2, + k, a, b, c, lambda2, l, r=0; + + while (j>=n) { + j-=n; + r+=1; + } + while (i>=n) { + i-=n; + r-=1; + } + while (j<0) { + j+=n; + r-=1; + } + while (i<0) { + i+=n; + r+=1; + } + + x = sums[j+1].x-sums[i].x+r*sums[n].x; + y = sums[j+1].y-sums[i].y+r*sums[n].y; + x2 = sums[j+1].x2-sums[i].x2+r*sums[n].x2; + xy = sums[j+1].xy-sums[i].xy+r*sums[n].xy; + y2 = sums[j+1].y2-sums[i].y2+r*sums[n].y2; + k = j+1-i+r*n; + + ctr.x = x/k; + ctr.y = y/k; + + a = (x2-x*x/k)/k; + b = (xy-x*y/k)/k; + c = (y2-y*y/k)/k; + + lambda2 = (a+c+Math.sqrt((a-c)*(a-c)+4*b*b))/2; + + a -= lambda2; + c -= lambda2; + + if (Math.abs(a) >= Math.abs(c)) { + l = Math.sqrt(a*a+b*b); + if (l!==0) { + dir.x = -b/l; + dir.y = a/l; + } + } else { + l = Math.sqrt(c*c+b*b); + if (l!==0) { + dir.x = -c/l; + dir.y = b/l; + } + } + if (l===0) { + dir.x = dir.y = 0; + } + } + + var m = path.m, po = path.po, n = path.len, pt = path.pt, + x0 = path.x0, y0 = path.y0, + ctr = new Array(m), dir = new Array(m), + q = new Array(m), + v = new Array(3), d, i, j, k, l, + s = new Point(); + + path.curve = new Curve(m); + + for (i=0; iQ.at(1, 1)) { + v[0] = -Q.at(0, 1); + v[1] = Q.at(0, 0); + } else if (Q.at(1, 1)) { + v[0] = -Q.at(1, 1); + v[1] = Q.at(1, 0); + } else { + v[0] = 1; + v[1] = 0; + } + d = v[0] * v[0] + v[1] * v[1]; + v[2] = - v[1] * s.y - v[0] * s.x; + for (l=0; l<3; l++) { + for (k=0; k<3; k++) { + Q.data[l * 3 + k] += v[l] * v[k] / d; + } + } + } + dx = Math.abs(w.x-s.x); + dy = Math.abs(w.y-s.y); + if (dx <= 0.5 && dy <= 0.5) { + path.curve.vertex[i] = new Point(w.x+x0, w.y+y0); + continue; + } + + min = quadform(Q, s); + xmin = s.x; + ymin = s.y; + + if (Q.at(0, 0) !== 0.0) { + for (z=0; z<2; z++) { + w.y = s.y-0.5+z; + w.x = - (Q.at(0, 1) * w.y + Q.at(0, 2)) / Q.at(0, 0); + dx = Math.abs(w.x-s.x); + cand = quadform(Q, w); + if (dx <= 0.5 && cand < min) { + min = cand; + xmin = w.x; + ymin = w.y; + } + } + } + + if (Q.at(1, 1) !== 0.0) { + for (z=0; z<2; z++) { + w.x = s.x-0.5+z; + w.y = - (Q.at(1, 0) * w.x + Q.at(1, 2)) / Q.at(1, 1); + dy = Math.abs(w.y-s.y); + cand = quadform(Q, w); + if (dy <= 0.5 && cand < min) { + min = cand; + xmin = w.x; + ymin = w.y; + } + } + } + + for (l=0; l<2; l++) { + for (k=0; k<2; k++) { + w.x = s.x-0.5+l; + w.y = s.y-0.5+k; + cand = quadform(Q, w); + if (cand < min) { + min = cand; + xmin = w.x; + ymin = w.y; + } + } + } + + path.curve.vertex[i] = new Point(xmin + x0, ymin + y0); + } + } + + function reverse(path) { + var curve = path.curve, m = curve.n, v = curve.vertex, i, j, tmp; + + for (i=0, j=m-1; i1 ? (1 - 1.0/dd) : 0; + alpha = alpha / 0.75; + } else { + alpha = 4/3.0; + } + curve.alpha0[j] = alpha; + + if (alpha >= info.alphamax) { + curve.tag[j] = "CORNER"; + curve.c[3 * j + 1] = curve.vertex[j]; + curve.c[3 * j + 2] = p4; + } else { + if (alpha < 0.55) { + alpha = 0.55; + } else if (alpha > 1) { + alpha = 1; + } + p2 = interval(0.5+0.5*alpha, curve.vertex[i], curve.vertex[j]); + p3 = interval(0.5+0.5*alpha, curve.vertex[k], curve.vertex[j]); + curve.tag[j] = "CURVE"; + curve.c[3 * j + 0] = p2; + curve.c[3 * j + 1] = p3; + curve.c[3 * j + 2] = p4; + } + curve.alpha[j] = alpha; + curve.beta[j] = 0.5; + } + curve.alphacurve = 1; + } + + function optiCurve(path) { + function Opti(){ + this.pen = 0; + this.c = [new Point(), new Point()]; + this.t = 0; + this.s = 0; + this.alpha = 0; + } + + function opti_penalty(path, i, j, res, opttolerance, convc, areac) { + var m = path.curve.n, curve = path.curve, vertex = curve.vertex, + k, k1, k2, conv, i1, + area, alpha, d, d1, d2, + p0, p1, p2, p3, pt, + A, R, A1, A2, A3, A4, + s, t; + + if (i==j) { + return 1; + } + + k = i; + i1 = mod(i+1, m); + k1 = mod(k+1, m); + conv = convc[k1]; + if (conv === 0) { + return 1; + } + d = ddist(vertex[i], vertex[i1]); + for (k=k1; k!=j; k=k1) { + k1 = mod(k+1, m); + k2 = mod(k+2, m); + if (convc[k1] != conv) { + return 1; + } + if (sign(cprod(vertex[i], vertex[i1], vertex[k1], vertex[k2])) != + conv) { + return 1; + } + if (iprod1(vertex[i], vertex[i1], vertex[k1], vertex[k2]) < + d * ddist(vertex[k1], vertex[k2]) * -0.999847695156) { + return 1; + } + } + + p0 = curve.c[mod(i,m) * 3 + 2].copy(); + p1 = vertex[mod(i+1,m)].copy(); + p2 = vertex[mod(j,m)].copy(); + p3 = curve.c[mod(j,m) * 3 + 2].copy(); + + area = areac[j] - areac[i]; + area -= dpara(vertex[0], curve.c[i * 3 + 2], curve.c[j * 3 + 2])/2; + if (i>=j) { + area += areac[m]; + } + + A1 = dpara(p0, p1, p2); + A2 = dpara(p0, p1, p3); + A3 = dpara(p0, p2, p3); + + A4 = A1+A3-A2; + + if (A2 == A1) { + return 1; + } + + t = A3/(A3-A4); + s = A2/(A2-A1); + A = A2 * t / 2.0; + + if (A === 0.0) { + return 1; + } + + R = area / A; + alpha = 2 - Math.sqrt(4 - R / 0.3); + + res.c[0] = interval(t * alpha, p0, p1); + res.c[1] = interval(s * alpha, p3, p2); + res.alpha = alpha; + res.t = t; + res.s = s; + + p1 = res.c[0].copy(); + p2 = res.c[1].copy(); + + res.pen = 0; + + for (k=mod(i+1,m); k!=j; k=k1) { + k1 = mod(k+1,m); + t = tangent(p0, p1, p2, p3, vertex[k], vertex[k1]); + if (t<-0.5) { + return 1; + } + pt = bezier(t, p0, p1, p2, p3); + d = ddist(vertex[k], vertex[k1]); + if (d === 0.0) { + return 1; + } + d1 = dpara(vertex[k], vertex[k1], pt) / d; + if (Math.abs(d1) > opttolerance) { + return 1; + } + if (iprod(vertex[k], vertex[k1], pt) < 0 || + iprod(vertex[k1], vertex[k], pt) < 0) { + return 1; + } + res.pen += d1 * d1; + } + + for (k=i; k!=j; k=k1) { + k1 = mod(k+1,m); + t = tangent(p0, p1, p2, p3, curve.c[k * 3 + 2], curve.c[k1 * 3 + 2]); + if (t<-0.5) { + return 1; + } + pt = bezier(t, p0, p1, p2, p3); + d = ddist(curve.c[k * 3 + 2], curve.c[k1 * 3 + 2]); + if (d === 0.0) { + return 1; + } + d1 = dpara(curve.c[k * 3 + 2], curve.c[k1 * 3 + 2], pt) / d; + d2 = dpara(curve.c[k * 3 + 2], curve.c[k1 * 3 + 2], vertex[k1]) / d; + d2 *= 0.75 * curve.alpha[k1]; + if (d2 < 0) { + d1 = -d1; + d2 = -d2; + } + if (d1 < d2 - opttolerance) { + return 1; + } + if (d1 < d2) { + res.pen += (d1 - d2) * (d1 - d2); + } + } + + return 0; + } + + var curve = path.curve, m = curve.n, vert = curve.vertex, + pt = new Array(m + 1), + pen = new Array(m + 1), + len = new Array(m + 1), + opt = new Array(m + 1), + om, i,j,r, + o = new Opti(), p0, + i1, area, alpha, ocurve, + s, t; + + var convc = new Array(m), areac = new Array(m + 1); + + for (i=0; i=0; i--) { + r = opti_penalty(path, i, mod(j,m), o, info.opttolerance, convc, + areac); + if (r) { + break; + } + if (len[j] > len[i]+1 || + (len[j] == len[i]+1 && pen[j] > pen[i] + o.pen)) { + pt[j] = i; + pen[j] = pen[i] + o.pen; + len[j] = len[i] + 1; + opt[j] = o; + o = new Opti(); + } + } + } + om = len[m]; + ocurve = new Curve(om); + s = new Array(om); + t = new Array(om); + + j = m; + for (i=om-1; i>=0; i--) { + if (pt[j]==j-1) { + ocurve.tag[i] = curve.tag[mod(j,m)]; + ocurve.c[i * 3 + 0] = curve.c[mod(j,m) * 3 + 0]; + ocurve.c[i * 3 + 1] = curve.c[mod(j,m) * 3 + 1]; + ocurve.c[i * 3 + 2] = curve.c[mod(j,m) * 3 + 2]; + ocurve.vertex[i] = curve.vertex[mod(j,m)]; + ocurve.alpha[i] = curve.alpha[mod(j,m)]; + ocurve.alpha0[i] = curve.alpha0[mod(j,m)]; + ocurve.beta[i] = curve.beta[mod(j,m)]; + s[i] = t[i] = 1.0; + } else { + ocurve.tag[i] = "CURVE"; + ocurve.c[i * 3 + 0] = opt[j].c[0]; + ocurve.c[i * 3 + 1] = opt[j].c[1]; + ocurve.c[i * 3 + 2] = curve.c[mod(j,m) * 3 + 2]; + ocurve.vertex[i] = interval(opt[j].s, curve.c[mod(j,m) * 3 + 2], + vert[mod(j,m)]); + ocurve.alpha[i] = opt[j].alpha; + ocurve.alpha0[i] = opt[j].alpha; + s[i] = opt[j].s; + t[i] = opt[j].t; + } + j = pt[j]; + } + + for (i=0; i { + const image = new Image(); + image.onload = () => resolve(image); + image.onerror = reject; + image.src = url; + }); +} + +export function createBitmap(imgCanvas) { + var ctx = imgCanvas.getContext('2d'); + var bm = new Bitmap(imgCanvas.width, imgCanvas.height); + var imgdataobj = ctx.getImageData(0, 0, bm.w, bm.h); + var l = imgdataobj.data.length, i, j, color; + + let zero = 0; + let one = 0; + + for (i = 0, j = 0; i < l; i += 4, j++) { + color = 0.2126 * imgdataobj.data[i] + 0.7153 * imgdataobj.data[i + 1] + + 0.0721 * imgdataobj.data[i + 2]; + bm.data[j] = (color < 128 ? 1 : 0); + + if (color < 128) { + one ++; + } else { + zero ++; + } + } + + return bm; +} + +export function mod(a, n) { + if (a >= n) { + return a % n; + } else if (a >= 0) { + return a; + } else { + return n - 1 - (-1 - a) % n; + } +} + +export function sign(i) { + if (i > 0) { + return 1; + } else if (i < 0) { + return -1; + } else { + return 0; + } +} From 636383d2f39c79fcc84613fecbac87eba4463bb5 Mon Sep 17 00:00:00 2001 From: casperlamboo Date: Tue, 28 Feb 2017 18:44:48 +0100 Subject: [PATCH 06/29] add test --- example/index.html | 22 ++++++++++++++++++++++ example/index.js | 7 +++++++ example/test.png | Bin 0 -> 114644 bytes 3 files changed, 29 insertions(+) create mode 100644 example/index.html create mode 100644 example/index.js create mode 100644 example/test.png diff --git a/example/index.html b/example/index.html new file mode 100644 index 0000000..a513215 --- /dev/null +++ b/example/index.html @@ -0,0 +1,22 @@ + + + + + + + Potrace + + + + + + + +
+ + + + + diff --git a/example/index.js b/example/index.js new file mode 100644 index 0000000..239af4d --- /dev/null +++ b/example/index.js @@ -0,0 +1,7 @@ +import * as POTRACE from 'src/index.js'; + +POTRACE.traceUrl('test.png') + .then(paths => POTRACE.getSVG(paths, 1.0, 'curve')) + .then(svg => { + document.write(svg); + }); diff --git a/example/test.png b/example/test.png new file mode 100644 index 0000000000000000000000000000000000000000..91a70181cfcccea4ba241902c4cbdee7600a69d2 GIT binary patch literal 114644 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�xp4{@V*tCdP=VArBtKtcM`H^v-NNM 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 UJV9r~g8%>k07*qoM6N<$g4LI8!T Date: Tue, 28 Feb 2017 18:48:35 +0100 Subject: [PATCH 07/29] remove some util functions --- src/utils.js | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/src/utils.js b/src/utils.js index b26f89f..2d29c4d 100644 --- a/src/utils.js +++ b/src/utils.js @@ -32,23 +32,3 @@ export function createBitmap(imgCanvas) { return bm; } - -export function mod(a, n) { - if (a >= n) { - return a % n; - } else if (a >= 0) { - return a; - } else { - return n - 1 - (-1 - a) % n; - } -} - -export function sign(i) { - if (i > 0) { - return 1; - } else if (i < 0) { - return -1; - } else { - return 0; - } -} From 596f693df287c3fba446a1e83558464e67ff2928 Mon Sep 17 00:00:00 2001 From: casperlamboo Date: Tue, 28 Feb 2017 20:26:35 +0100 Subject: [PATCH 08/29] clean up --- src/Bitmap.js | 77 ++++++++++------- src/Path.js | 22 ++--- src/Point.js | 17 ++-- src/bitmapToPathlist.js | 181 +++++++++++++++++----------------------- src/utils.js | 32 ++++--- 5 files changed, 155 insertions(+), 174 deletions(-) diff --git a/src/Bitmap.js b/src/Bitmap.js index 2e4d322..bd1e0ba 100644 --- a/src/Bitmap.js +++ b/src/Bitmap.js @@ -1,39 +1,56 @@ import Point from './Point.js'; -function Bitmap(w, h) { - this.w = w; - this.h = h; - this.size = w * h; - this.arraybuffer = new ArrayBuffer(this.size); - this.data = new Int8Array(this.arraybuffer); -} +export default class Bitmap { + constructor(width, height) { + this.width = width; + this.height = height; + this.size = width * height; + this.data = new Int8Array(this.size); + } -Bitmap.prototype.at = function (x, y) { - return (x >= 0 && x < this.w && y >=0 && y < this.h) && - this.data[this.w * y + x] === 1; -}; + at(x, y) { + return (x >= 0 && x < this.width && y >=0 && y < this.height) && + this.data[this.width * y + x] === 1; + } -Bitmap.prototype.index = function(i) { - var point = new Point(); - point.y = Math.floor(i / this.w); - point.x = i - point.y * this.w; - return point; -}; + flip(x, y) { + if (this.at(x, y)) { + this.data[this.width * y + x] = 0; + } else { + this.data[this.width * y + x] = 1; + } + } -Bitmap.prototype.flip = function(x, y) { - if (this.at(x, y)) { - this.data[this.w * y + x] = 0; - } else { - this.data[this.w * y + x] = 1; + copy() { + var bm = new Bitmap(this.width, this.height), i; + for (i = 0; i < this.size; i++) { + bm.data[i] = this.data[i]; + } + return bm; } -}; -Bitmap.prototype.copy = function() { - var bm = new Bitmap(this.w, this.h), i; - for (i = 0; i < this.size; i++) { - bm.data[i] = this.data[i]; + index(i) { + const x = i % this.width; + const y = Math.floor(i / this.width); + return new Point(x, y); } - return bm; -}; -export default Bitmap; + xOrPath(path) { + var y1 = path.pt[0].y, + len = path.len, + x, y, maxX, minY, i, j; + for (i = 1; i < len; i++) { + x = path.pt[i].x; + y = path.pt[i].y; + + if (y !== y1) { + minY = y1 < y ? y1 : y; + maxX = path.maxX; + for (j = x; j < maxX; j++) { + this.flip(j, minY); + } + y1 = y; + } + } + } +} diff --git a/src/Path.js b/src/Path.js index 53b2049..a1137d2 100644 --- a/src/Path.js +++ b/src/Path.js @@ -1,12 +1,12 @@ -function Path() { - this.area = 0; - this.len = 0; - this.curve = {}; - this.pt = []; - this.minX = 100000; - this.minY = 100000; - this.maxX= -1; - this.maxY = -1; +export default class Path { + constructor() { + this.area = 0; + this.len = 0; + this.curve = {}; + this.pt = []; + this.minX = 100000; + this.minY = 100000; + this.maxX= -1; + this.maxY = -1; + } } - -export default Path; diff --git a/src/Point.js b/src/Point.js index 5afba82..c078490 100644 --- a/src/Point.js +++ b/src/Point.js @@ -1,10 +1,9 @@ -function Point(x, y) { - this.x = x; - this.y = y; +export default class Point { + constructor(x, y) { + this.x = x; + this.y = y; + } + copy() { + return new Point(this.x, this.y); + } } - -Point.prototype.copy = function(){ - return new Point(this.x, this.y); -}; - -export default Point; diff --git a/src/bitmapToPathlist.js b/src/bitmapToPathlist.js index 2505447..7e74802 100644 --- a/src/bitmapToPathlist.js +++ b/src/bitmapToPathlist.js @@ -1,130 +1,99 @@ import Path from './Path.js'; import Point from './Point.js'; -function bmToPathlist(bm, info) { - - var bm1 = bm.copy(), - currentPoint = new Point(0, 0), - path; - - function findNext(point) { - var i = bm1.w * point.y + point.x; - while (i < bm1.size && bm1.data[i] !== 1) { - i++; - } - return i < bm1.size && bm1.index(i); - } - - function majority(x, y) { - var i, a, ct; - for (i = 2; i < 5; i++) { - ct = 0; - for (a = -i + 1; a <= i - 1; a++) { - ct += bm1.at(x + a, y + i - 1) ? 1 : -1; - ct += bm1.at(x + i - 1, y + a - 1) ? 1 : -1; - ct += bm1.at(x + a - 1, y - i) ? 1 : -1; - ct += bm1.at(x - i, y + a) ? 1 : -1; - } - if (ct > 0) { - return 1; - } else if (ct < 0) { - return 0; - } - } - return 0; +function findNext(point, bitmapTarget) { + let i = bitmapTarget.width * point.y + point.x; + while (i < bitmapTarget.size && bitmapTarget.data[i] !== 1) { + i ++; } + return i < bitmapTarget.size && bitmapTarget.index(i); +} - function findPath(point) { - var path = new Path(), - x = point.x, y = point.y, - dirx = 0, diry = 1, tmp; - - path.sign = bm.at(point.x, point.y) ? "+" : "-"; - - while (1) { - path.pt.push(new Point(x, y)); - if (x > path.maxX) - path.maxX = x; - if (x < path.minX) - path.minX = x; - if (y > path.maxY) - path.maxY = y; - if (y < path.minY) - path.minY = y; - path.len++; - - x += dirx; - y += diry; - path.area -= x * diry; - - if (x === point.x && y === point.y) - break; - - var l = bm1.at(x + (dirx + diry - 1 ) / 2, y + (diry - dirx - 1) / 2); - var r = bm1.at(x + (dirx - diry - 1) / 2, y + (diry + dirx - 1) / 2); - - if (r && !l) { - if (info.turnpolicy === "right" || - (info.turnpolicy === "black" && path.sign === '+') || - (info.turnpolicy === "white" && path.sign === '-') || - (info.turnpolicy === "majority" && majority(x, y)) || - (info.turnpolicy === "minority" && !majority(x, y))) { - tmp = dirx; - dirx = -diry; - diry = tmp; - } else { - tmp = dirx; - dirx = diry; - diry = -tmp; - } - } else if (r) { +function findPath(point, bitmap, bitmapTarget, info) { + var path = new Path(), + x = point.x, y = point.y, + dirx = 0, diry = 1, tmp; + + path.sign = bitmap.at(point.x, point.y) ? "+" : "-"; + + while (true) { + path.pt.push(new Point(x, y)); + if (x > path.maxX) + path.maxX = x; + if (x < path.minX) + path.minX = x; + if (y > path.maxY) + path.maxY = y; + if (y < path.minY) + path.minY = y; + path.len++; + + x += dirx; + y += diry; + path.area -= x * diry; + + if (x === point.x && y === point.y) + break; + + var l = bitmapTarget.at(x + (dirx + diry - 1 ) / 2, y + (diry - dirx - 1) / 2); + var r = bitmapTarget.at(x + (dirx - diry - 1) / 2, y + (diry + dirx - 1) / 2); + + if (r && !l) { + if (info.turnpolicy === "right" || + (info.turnpolicy === "black" && path.sign === '+') || + (info.turnpolicy === "white" && path.sign === '-') || + (info.turnpolicy === "majority" && majority(x, y, bitmapTarget)) || + (info.turnpolicy === "minority" && !majority(x, y, bitmapTarget))) { tmp = dirx; dirx = -diry; diry = tmp; - } else if (!l) { + } else { tmp = dirx; dirx = diry; diry = -tmp; } + } else if (r) { + tmp = dirx; + dirx = -diry; + diry = tmp; + } else if (!l) { + tmp = dirx; + dirx = diry; + diry = -tmp; } - return path; } + return path; +} - function xorPath(path){ - var y1 = path.pt[0].y, - len = path.len, - x, y, maxX, minY, i, j; - for (i = 1; i < len; i++) { - x = path.pt[i].x; - y = path.pt[i].y; - - if (y !== y1) { - minY = y1 < y ? y1 : y; - maxX = path.maxX; - for (j = x; j < maxX; j++) { - bm1.flip(j, minY); - } - y1 = y; - } +function majority(x, y, bitmapTarget) { + var i, a, ct; + for (i = 2; i < 5; i++) { + ct = 0; + for (a = -i + 1; a <= i - 1; a++) { + ct += bitmapTarget.at(x + a, y + i - 1) ? 1 : -1; + ct += bitmapTarget.at(x + i - 1, y + a - 1) ? 1 : -1; + ct += bitmapTarget.at(x + a - 1, y - i) ? 1 : -1; + ct += bitmapTarget.at(x - i, y + a) ? 1 : -1; + } + if (ct > 0) { + return 1; + } else if (ct < 0) { + return 0; } - } + return 0; +} +export default function bitmapToPathlist(bitmap, options) { + const bitmapTarget = bitmap.copy(); const pathlist = []; - while (currentPoint = findNext(currentPoint)) { - - path = findPath(currentPoint); - - xorPath(path); - - if (path.area > info.turdsize) { - pathlist.push(path); - } + let currentPoint = new Point(0, 0); + while (currentPoint = findNext(currentPoint, bitmapTarget)) { + const path = findPath(currentPoint, bitmap, bitmapTarget, options); + bitmapTarget.xOrPath(path); + if (path.area > options.turdsize) pathlist.push(path); } return pathlist; - } - -export default bmToPathlist; diff --git a/src/utils.js b/src/utils.js index 2d29c4d..312f27b 100644 --- a/src/utils.js +++ b/src/utils.js @@ -9,26 +9,22 @@ export function loadImage(url) { }); } -export function createBitmap(imgCanvas) { - var ctx = imgCanvas.getContext('2d'); - var bm = new Bitmap(imgCanvas.width, imgCanvas.height); - var imgdataobj = ctx.getImageData(0, 0, bm.w, bm.h); - var l = imgdataobj.data.length, i, j, color; +export function createBitmap(canvas) { + const { width, height } = canvas; + const imageData = canvas.getContext('2d').getImageData(0, 0, width, height); + const bitmap = new Bitmap(width, height); - let zero = 0; - let one = 0; + let imageDataIndex = 0; + const length = width * height; + for (let bitmapIndex = 0; bitmapIndex < length; bitmapIndex ++) { + const r = 0.2126 * imageData.data[imageDataIndex ++]; + const g = 0.7153 * imageData.data[imageDataIndex ++]; + const b = 0.0721 * imageData.data[imageDataIndex ++]; + imageDataIndex ++; // alpha + const color = r + g + b; - for (i = 0, j = 0; i < l; i += 4, j++) { - color = 0.2126 * imgdataobj.data[i] + 0.7153 * imgdataobj.data[i + 1] + - 0.0721 * imgdataobj.data[i + 2]; - bm.data[j] = (color < 128 ? 1 : 0); - - if (color < 128) { - one ++; - } else { - zero ++; - } + bitmap.data[bitmapIndex] = (color < 128 ? 1 : 0); } - return bm; + return bitmap; } From 66dc7b447247ad4af79cc53ff6e2b32f5c2731d2 Mon Sep 17 00:00:00 2001 From: casperlamboo Date: Tue, 28 Feb 2017 20:33:19 +0100 Subject: [PATCH 09/29] clean up --- src/Bitmap.js | 25 ++++++++++++------------- src/Path.js | 4 ++-- src/bitmapToPathlist.js | 4 ++-- src/processPath.js | 20 ++++++++++---------- 4 files changed, 26 insertions(+), 27 deletions(-) diff --git a/src/Bitmap.js b/src/Bitmap.js index bd1e0ba..8432381 100644 --- a/src/Bitmap.js +++ b/src/Bitmap.js @@ -22,11 +22,11 @@ export default class Bitmap { } copy() { - var bm = new Bitmap(this.width, this.height), i; - for (i = 0; i < this.size; i++) { - bm.data[i] = this.data[i]; + const bitmap = new Bitmap(this.width, this.height); + for (let i = 0; i < this.size; i ++) { + bitmap.data[i] = this.data[i]; } - return bm; + return bitmap; } index(i) { @@ -36,17 +36,16 @@ export default class Bitmap { } xOrPath(path) { - var y1 = path.pt[0].y, - len = path.len, - x, y, maxX, minY, i, j; - for (i = 1; i < len; i++) { - x = path.pt[i].x; - y = path.pt[i].y; + let y1 = path.points[0].y; + + for (let i = 1; i < path.length; i ++) { + const x = path.points[i].x; + const y = path.points[i].y; if (y !== y1) { - minY = y1 < y ? y1 : y; - maxX = path.maxX; - for (j = x; j < maxX; j++) { + const minY = Math.min(y1, y); + const maxX = path.maxX; + for (let j = x; j < maxX; j ++) { this.flip(j, minY); } y1 = y; diff --git a/src/Path.js b/src/Path.js index a1137d2..fe3f14a 100644 --- a/src/Path.js +++ b/src/Path.js @@ -1,9 +1,9 @@ export default class Path { constructor() { this.area = 0; - this.len = 0; + this.length = 0; this.curve = {}; - this.pt = []; + this.points = []; this.minX = 100000; this.minY = 100000; this.maxX= -1; diff --git a/src/bitmapToPathlist.js b/src/bitmapToPathlist.js index 7e74802..74611f7 100644 --- a/src/bitmapToPathlist.js +++ b/src/bitmapToPathlist.js @@ -17,7 +17,7 @@ function findPath(point, bitmap, bitmapTarget, info) { path.sign = bitmap.at(point.x, point.y) ? "+" : "-"; while (true) { - path.pt.push(new Point(x, y)); + path.points.push(new Point(x, y)); if (x > path.maxX) path.maxX = x; if (x < path.minX) @@ -26,7 +26,7 @@ function findPath(point, bitmap, bitmapTarget, info) { path.maxY = y; if (y < path.minY) path.minY = y; - path.len++; + path.length++; x += dirx; y += diry; diff --git a/src/processPath.js b/src/processPath.js index b11b418..2cc87af 100644 --- a/src/processPath.js +++ b/src/processPath.js @@ -168,15 +168,15 @@ function processPath(pathlist, info) { function calcSums(path) { var i, x, y; - path.x0 = path.pt[0].x; - path.y0 = path.pt[0].y; + path.x0 = path.points[0].x; + path.y0 = path.points[0].y; path.sums = []; var s = path.sums; s.push(new Sum(0, 0, 0, 0, 0)); - for(i = 0; i < path.len; i++){ - x = path.pt[i].x - path.x0; - y = path.pt[i].y - path.y0; + for(i = 0; i < path.length; i++){ + x = path.points[i].x - path.x0; + y = path.points[i].y - path.y0; s.push(new Sum(s[i].x + x, s[i].y + y, s[i].xy + x * y, s[i].x2 + x * x, s[i].y2 + y * y)); } @@ -184,7 +184,7 @@ function processPath(pathlist, info) { function calcLon(path) { - var n = path.len, pt = path.pt, dir, + var n = path.length, pt = path.points, dir, pivk = new Array(n), nc = new Array(n), ct = new Array(4); @@ -298,7 +298,7 @@ function processPath(pathlist, info) { function penalty3(path, i, j) { - var n = path.len, pt = path.pt, sums = path.sums; + var n = path.length, pt = path.points, sums = path.sums; var x, y, xy, x2, y2, k, a, b, c, s, px, py, ex, ey, @@ -339,7 +339,7 @@ function processPath(pathlist, info) { } var i, j, m, k, - n = path.len, + n = path.length, pen = new Array(n + 1), prev = new Array(n + 1), clip0 = new Array(n), @@ -410,7 +410,7 @@ function processPath(pathlist, info) { function pointslope(path, i, j, ctr, dir) { - var n = path.len, sums = path.sums, + var n = path.length, sums = path.sums, x, y, x2, xy, y2, k, a, b, c, lambda2, l, r=0; @@ -468,7 +468,7 @@ function processPath(pathlist, info) { } } - var m = path.m, po = path.po, n = path.len, pt = path.pt, + var m = path.m, po = path.po, n = path.length, pt = path.points, x0 = path.x0, y0 = path.y0, ctr = new Array(m), dir = new Array(m), q = new Array(m), From b22d7c7404199d2622633456722141d65f585ef2 Mon Sep 17 00:00:00 2001 From: casperlamboo Date: Tue, 28 Feb 2017 23:00:42 +0100 Subject: [PATCH 10/29] clean up --- src/Bitmap.js | 2 +- src/Path.js | 3 +- src/Point.js | 4 ++ src/bitmapToPathlist.js | 110 ++++++++++++++++++++-------------------- src/processPath.js | 12 ++--- 5 files changed, 66 insertions(+), 65 deletions(-) diff --git a/src/Bitmap.js b/src/Bitmap.js index 8432381..7c1959e 100644 --- a/src/Bitmap.js +++ b/src/Bitmap.js @@ -38,7 +38,7 @@ export default class Bitmap { xOrPath(path) { let y1 = path.points[0].y; - for (let i = 1; i < path.length; i ++) { + for (let i = 1; i < path.points.length; i ++) { const x = path.points[i].x; const y = path.points[i].y; diff --git a/src/Path.js b/src/Path.js index fe3f14a..69e12c1 100644 --- a/src/Path.js +++ b/src/Path.js @@ -1,12 +1,11 @@ export default class Path { constructor() { this.area = 0; - this.length = 0; this.curve = {}; this.points = []; this.minX = 100000; this.minY = 100000; - this.maxX= -1; + this.maxX = -1; this.maxY = -1; } } diff --git a/src/Point.js b/src/Point.js index c078490..f05dcac 100644 --- a/src/Point.js +++ b/src/Point.js @@ -6,4 +6,8 @@ export default class Point { copy() { return new Point(this.x, this.y); } + toIndex(width, height) { + if (this.x < 0 || this.y < 0 || this.x >= width || this.y >= height) return null; + return width * this.y + this.x; + } } diff --git a/src/bitmapToPathlist.js b/src/bitmapToPathlist.js index 74611f7..4c5d99e 100644 --- a/src/bitmapToPathlist.js +++ b/src/bitmapToPathlist.js @@ -2,74 +2,72 @@ import Path from './Path.js'; import Point from './Point.js'; function findNext(point, bitmapTarget) { - let i = bitmapTarget.width * point.y + point.x; - while (i < bitmapTarget.size && bitmapTarget.data[i] !== 1) { - i ++; + for (let i = point.toIndex(bitmapTarget.width, bitmapTarget.height); i < bitmapTarget.size; i ++) { + if (bitmapTarget.data[i]) return bitmapTarget.index(i); } - return i < bitmapTarget.size && bitmapTarget.index(i); + return false; } function findPath(point, bitmap, bitmapTarget, info) { - var path = new Path(), - x = point.x, y = point.y, - dirx = 0, diry = 1, tmp; + const path = new Path(); - path.sign = bitmap.at(point.x, point.y) ? "+" : "-"; + let { x, y } = point; + + let dirX = 0; + let dirY = 1; + + path.sign = bitmap.at(x, y) ? '+' : '-'; while (true) { path.points.push(new Point(x, y)); - if (x > path.maxX) - path.maxX = x; - if (x < path.minX) - path.minX = x; - if (y > path.maxY) - path.maxY = y; - if (y < path.minY) - path.minY = y; - path.length++; - - x += dirx; - y += diry; - path.area -= x * diry; - - if (x === point.x && y === point.y) - break; - - var l = bitmapTarget.at(x + (dirx + diry - 1 ) / 2, y + (diry - dirx - 1) / 2); - var r = bitmapTarget.at(x + (dirx - diry - 1) / 2, y + (diry + dirx - 1) / 2); - - if (r && !l) { - if (info.turnpolicy === "right" || - (info.turnpolicy === "black" && path.sign === '+') || - (info.turnpolicy === "white" && path.sign === '-') || - (info.turnpolicy === "majority" && majority(x, y, bitmapTarget)) || - (info.turnpolicy === "minority" && !majority(x, y, bitmapTarget))) { - tmp = dirx; - dirx = -diry; - diry = tmp; + if (x > path.maxX) path.maxX = x; + if (x < path.minX) path.minX = x; + if (y > path.maxY) path.maxY = y; + if (y < path.minY) path.minY = y; + + x += dirX; + y += dirY; + path.area -= x * dirY; + + if (x === point.x && y === point.y) break; + + const left = bitmapTarget.at(x + (dirX + dirY - 1 ) / 2, y + (dirY - dirX - 1) / 2); + const right = bitmapTarget.at(x + (dirX - dirY - 1) / 2, y + (dirY + dirX - 1) / 2); + + if (right && !left) { + if ( + info.turnpolicy === 'right' || + (info.turnpolicy === 'black' && path.sign === '+') || + (info.turnpolicy === 'white' && path.sign === '-') || + (info.turnpolicy === 'majority' && majority(x, y, bitmapTarget)) || + (info.turnpolicy === 'minority' && !majority(x, y, bitmapTarget)) + ) { + const tmp = dirX; + dirX = -dirY; + dirY = tmp; } else { - tmp = dirx; - dirx = diry; - diry = -tmp; + const tmp = dirX; + dirX = dirY; + dirY = -tmp; } - } else if (r) { - tmp = dirx; - dirx = -diry; - diry = tmp; - } else if (!l) { - tmp = dirx; - dirx = diry; - diry = -tmp; + } else if (right) { + const tmp = dirX; + dirX = -dirY; + dirY = tmp; + } else if (!left) { + const tmp = dirX; + dirX = dirY; + dirY = -tmp; } } + return path; } function majority(x, y, bitmapTarget) { - var i, a, ct; - for (i = 2; i < 5; i++) { - ct = 0; - for (a = -i + 1; a <= i - 1; a++) { + for (let i = 2; i < 5; i ++) { + let ct = 0; + for (let a = -i + 1; a <= i - 1; a ++) { ct += bitmapTarget.at(x + a, y + i - 1) ? 1 : -1; ct += bitmapTarget.at(x + i - 1, y + a - 1) ? 1 : -1; ct += bitmapTarget.at(x + a - 1, y - i) ? 1 : -1; @@ -88,11 +86,11 @@ export default function bitmapToPathlist(bitmap, options) { const bitmapTarget = bitmap.copy(); const pathlist = []; - let currentPoint = new Point(0, 0); - while (currentPoint = findNext(currentPoint, bitmapTarget)) { - const path = findPath(currentPoint, bitmap, bitmapTarget, options); - bitmapTarget.xOrPath(path); + for (let point = findNext(new Point(0, 0), bitmapTarget); point; point = findNext(point, bitmapTarget)) { + const path = findPath(point, bitmap, bitmapTarget, options); if (path.area > options.turdsize) pathlist.push(path); + + bitmapTarget.xOrPath(path); } return pathlist; diff --git a/src/processPath.js b/src/processPath.js index 2cc87af..c3c532e 100644 --- a/src/processPath.js +++ b/src/processPath.js @@ -174,7 +174,7 @@ function processPath(pathlist, info) { path.sums = []; var s = path.sums; s.push(new Sum(0, 0, 0, 0, 0)); - for(i = 0; i < path.length; i++){ + for(i = 0; i < path.points.length; i++){ x = path.points[i].x - path.x0; y = path.points[i].y - path.y0; s.push(new Sum(s[i].x + x, s[i].y + y, s[i].xy + x * y, @@ -184,7 +184,7 @@ function processPath(pathlist, info) { function calcLon(path) { - var n = path.length, pt = path.points, dir, + var n = path.points.length, pt = path.points, dir, pivk = new Array(n), nc = new Array(n), ct = new Array(4); @@ -298,7 +298,7 @@ function processPath(pathlist, info) { function penalty3(path, i, j) { - var n = path.length, pt = path.points, sums = path.sums; + var n = path.points.length, pt = path.points, sums = path.sums; var x, y, xy, x2, y2, k, a, b, c, s, px, py, ex, ey, @@ -339,7 +339,7 @@ function processPath(pathlist, info) { } var i, j, m, k, - n = path.length, + n = path.points.length, pen = new Array(n + 1), prev = new Array(n + 1), clip0 = new Array(n), @@ -410,7 +410,7 @@ function processPath(pathlist, info) { function pointslope(path, i, j, ctr, dir) { - var n = path.length, sums = path.sums, + var n = path.points.length, sums = path.sums, x, y, x2, xy, y2, k, a, b, c, lambda2, l, r=0; @@ -468,7 +468,7 @@ function processPath(pathlist, info) { } } - var m = path.m, po = path.po, n = path.length, pt = path.points, + var m = path.m, po = path.po, n = path.points.length, pt = path.points, x0 = path.x0, y0 = path.y0, ctr = new Array(m), dir = new Array(m), q = new Array(m), From b19124221d8bb8de054319ae358c8f5f811425b5 Mon Sep 17 00:00:00 2001 From: casperlamboo Date: Tue, 28 Feb 2017 23:30:11 +0100 Subject: [PATCH 11/29] clean up --- src/bitmapToPathlist.js | 75 +++++++++++++++++++++++++---------------- src/index.js | 6 ++-- 2 files changed, 49 insertions(+), 32 deletions(-) diff --git a/src/bitmapToPathlist.js b/src/bitmapToPathlist.js index 4c5d99e..63dda54 100644 --- a/src/bitmapToPathlist.js +++ b/src/bitmapToPathlist.js @@ -1,6 +1,20 @@ import Path from './Path.js'; import Point from './Point.js'; +export default function bitmapToPathlist(bitmap, options) { + const bitmapTarget = bitmap.copy(); + const pathlist = []; + + for (let point = findNext(new Point(0, 0), bitmapTarget); point; point = findNext(point, bitmapTarget)) { + const path = findPath(point, bitmap, bitmapTarget, options); + if (path.area > options.turdsize) pathlist.push(path); + + bitmapTarget.xOrPath(path); + } + + return pathlist; +} + function findNext(point, bitmapTarget) { for (let i = point.toIndex(bitmapTarget.width, bitmapTarget.height); i < bitmapTarget.size; i ++) { if (bitmapTarget.data[i]) return bitmapTarget.index(i); @@ -35,13 +49,7 @@ function findPath(point, bitmap, bitmapTarget, info) { const right = bitmapTarget.at(x + (dirX - dirY - 1) / 2, y + (dirY + dirX - 1) / 2); if (right && !left) { - if ( - info.turnpolicy === 'right' || - (info.turnpolicy === 'black' && path.sign === '+') || - (info.turnpolicy === 'white' && path.sign === '-') || - (info.turnpolicy === 'majority' && majority(x, y, bitmapTarget)) || - (info.turnpolicy === 'minority' && !majority(x, y, bitmapTarget)) - ) { + if (turn(info.turnpolicy, path, bitmapTarget, x, y)) { const tmp = dirX; dirX = -dirY; dirY = tmp; @@ -64,34 +72,43 @@ function findPath(point, bitmap, bitmapTarget, info) { return path; } -function majority(x, y, bitmapTarget) { +function turn(turnpolicy, path, bitmap, x, y) { + switch (turnpolicy) { + case 'right': + return true; + + case 'black': + return path.sign === '+'; + + case 'white': + return path.sign === '-'; + + case 'majority': + return majority(x, y, bitmap); + + case 'minority': + return !majority(x, y, bitmap); + + default: + return true; + } +} + +function majority(x, y, bitmap) { for (let i = 2; i < 5; i ++) { let ct = 0; for (let a = -i + 1; a <= i - 1; a ++) { - ct += bitmapTarget.at(x + a, y + i - 1) ? 1 : -1; - ct += bitmapTarget.at(x + i - 1, y + a - 1) ? 1 : -1; - ct += bitmapTarget.at(x + a - 1, y - i) ? 1 : -1; - ct += bitmapTarget.at(x - i, y + a) ? 1 : -1; + ct += bitmap.at(x + a, y + i - 1) ? 1 : -1; + ct += bitmap.at(x + i - 1, y + a - 1) ? 1 : -1; + ct += bitmap.at(x + a - 1, y - i) ? 1 : -1; + ct += bitmap.at(x - i, y + a) ? 1 : -1; } + if (ct > 0) { - return 1; + return true; } else if (ct < 0) { - return 0; + return false; } } - return 0; -} - -export default function bitmapToPathlist(bitmap, options) { - const bitmapTarget = bitmap.copy(); - const pathlist = []; - - for (let point = findNext(new Point(0, 0), bitmapTarget); point; point = findNext(point, bitmapTarget)) { - const path = findPath(point, bitmap, bitmapTarget, options); - if (path.area > options.turdsize) pathlist.push(path); - - bitmapTarget.xOrPath(path); - } - - return pathlist; + return false; } diff --git a/src/index.js b/src/index.js index 08edf7c..562579e 100644 --- a/src/index.js +++ b/src/index.js @@ -1,10 +1,10 @@ import { loadImage, createBitmap } from './utils.js'; import bitmapToPathlist from './bitmapToPathlist.js'; import processPath from './processPath.js'; -import _getSVG from './getSVG.js'; +import getSVG from './getSVG.js'; const OPTIONS = { - turnpolicy: 'minority', + turnpolicy: 'right', turdsize: 2, optcurve: true, alphamax: 1, @@ -38,4 +38,4 @@ export function traceCanvas(canvas, options = {}) { return path; } -export const getSVG = _getSVG; +export { getSVG }; From 34c707721526a5b23979915b4a5a737ebd5dbb15 Mon Sep 17 00:00:00 2001 From: casperlamboo Date: Tue, 28 Feb 2017 23:47:43 +0100 Subject: [PATCH 12/29] clean up --- src/Path.js | 20 +++++++++++++------- src/bitmapToPathlist.js | 27 +++++++++++---------------- src/processPath.js | 2 +- 3 files changed, 25 insertions(+), 24 deletions(-) diff --git a/src/Path.js b/src/Path.js index 69e12c1..e7a003e 100644 --- a/src/Path.js +++ b/src/Path.js @@ -1,11 +1,17 @@ export default class Path { - constructor() { - this.area = 0; + constructor(points, area, isHole) { + this.points = points; + this.area = area; + this.isHole = isHole; + + const xValues = this.points.map(({ x }) => x); + const yValues = this.points.map(({ y }) => y); + + this.minX = Math.min(...xValues); + this.minY = Math.min(...yValues); + this.maxX = Math.max(...xValues); + this.maxY = Math.max(...yValues); + this.curve = {}; - this.points = []; - this.minX = 100000; - this.minY = 100000; - this.maxX = -1; - this.maxY = -1; } } diff --git a/src/bitmapToPathlist.js b/src/bitmapToPathlist.js index 63dda54..af41a3b 100644 --- a/src/bitmapToPathlist.js +++ b/src/bitmapToPathlist.js @@ -22,26 +22,21 @@ function findNext(point, bitmapTarget) { return false; } -function findPath(point, bitmap, bitmapTarget, info) { - const path = new Path(); - +function findPath(point, bitmap, bitmapTarget, options) { let { x, y } = point; - let dirX = 0; let dirY = 1; - path.sign = bitmap.at(x, y) ? '+' : '-'; + const points = []; + let area = 0; + const isHole = !bitmap.at(x, y); while (true) { - path.points.push(new Point(x, y)); - if (x > path.maxX) path.maxX = x; - if (x < path.minX) path.minX = x; - if (y > path.maxY) path.maxY = y; - if (y < path.minY) path.minY = y; + points.push(new Point(x, y)); x += dirX; y += dirY; - path.area -= x * dirY; + area -= x * dirY; if (x === point.x && y === point.y) break; @@ -49,7 +44,7 @@ function findPath(point, bitmap, bitmapTarget, info) { const right = bitmapTarget.at(x + (dirX - dirY - 1) / 2, y + (dirY + dirX - 1) / 2); if (right && !left) { - if (turn(info.turnpolicy, path, bitmapTarget, x, y)) { + if (turn(options.turnpolicy, isHole, bitmapTarget, x, y)) { const tmp = dirX; dirX = -dirY; dirY = tmp; @@ -69,19 +64,19 @@ function findPath(point, bitmap, bitmapTarget, info) { } } - return path; + return new Path(points, area, isHole); } -function turn(turnpolicy, path, bitmap, x, y) { +function turn(turnpolicy, isHole, bitmap, x, y) { switch (turnpolicy) { case 'right': return true; case 'black': - return path.sign === '+'; + return !isHole; case 'white': - return path.sign === '-'; + return isHole; case 'majority': return majority(x, y, bitmap); diff --git a/src/processPath.js b/src/processPath.js index c3c532e..1943eb7 100644 --- a/src/processPath.js +++ b/src/processPath.js @@ -906,7 +906,7 @@ function processPath(pathlist, info) { bestPolygon(path); adjustVertices(path); - if (path.sign === "-") { + if (path.isHole) { reverse(path); } From 0f2caa035cf48607708204519f3de0aa5efe49fe Mon Sep 17 00:00:00 2001 From: casperlamboo Date: Wed, 1 Mar 2017 10:51:13 +0100 Subject: [PATCH 13/29] clean up --- src/Path.js | 9 + src/Point.js | 19 + src/Quad.js | 12 + src/Sum.js | 9 + src/bitmapToPathlist.js | 8 +- src/getSVG.js | 6 +- src/index.js | 8 +- src/processPath.js | 1440 +++++++++++++++++++-------------------- src/utils.js | 6 +- 9 files changed, 755 insertions(+), 762 deletions(-) create mode 100644 src/Quad.js create mode 100644 src/Sum.js diff --git a/src/Path.js b/src/Path.js index e7a003e..a9f97e1 100644 --- a/src/Path.js +++ b/src/Path.js @@ -14,4 +14,13 @@ export default class Path { this.curve = {}; } + reverse() { + var curve = this.curve, m = curve.n, v = curve.vertex, i, j, tmp; + + for (i=0, j=m-1; i= width || this.y >= height) return null; return width * this.y + this.x; } + lerp(point, lambda) { + const x = this.x + lambda * (point.x - this.x); + const y = this.y + lambda * (point.y - this.y); + + return new Point(x, y); + } + dorthInfty(point) { + const x = -sign(point.y - this.y); + const y = sign(point.x - this.x); + + return new Point(x, y); + } + ddenom(point) { + const r = this.dorthInfty(point); + + return r.y * (point.x - this.x) - r.x * (point.y - this.y); + } } diff --git a/src/Quad.js b/src/Quad.js new file mode 100644 index 0000000..5e8f35d --- /dev/null +++ b/src/Quad.js @@ -0,0 +1,12 @@ +export default class Quad { + constructor() { + this.data = [ + 0, 0, 0, + 0, 0, 0, + 0, 0, 0 + ]; + } + at(x, y) { + return this.data[x * 3 + y]; + }; +} diff --git a/src/Sum.js b/src/Sum.js new file mode 100644 index 0000000..1710764 --- /dev/null +++ b/src/Sum.js @@ -0,0 +1,9 @@ +export default class Sum { + constructor(x, y, xy, x2, y2) { + this.x = x; + this.y = y; + this.xy = xy; + this.x2 = x2; + this.y2 = y2; + } +} diff --git a/src/bitmapToPathlist.js b/src/bitmapToPathlist.js index af41a3b..ed121b0 100644 --- a/src/bitmapToPathlist.js +++ b/src/bitmapToPathlist.js @@ -1,18 +1,18 @@ import Path from './Path.js'; import Point from './Point.js'; -export default function bitmapToPathlist(bitmap, options) { +export default function bitmapToPathList(bitmap, options) { const bitmapTarget = bitmap.copy(); - const pathlist = []; + const pathList = []; for (let point = findNext(new Point(0, 0), bitmapTarget); point; point = findNext(point, bitmapTarget)) { const path = findPath(point, bitmap, bitmapTarget, options); - if (path.area > options.turdsize) pathlist.push(path); + if (path.area > options.turdsize) pathList.push(path); bitmapTarget.xOrPath(path); } - return pathlist; + return pathList; } function findNext(point, bitmapTarget) { diff --git a/src/getSVG.js b/src/getSVG.js index f4647c0..58135d1 100644 --- a/src/getSVG.js +++ b/src/getSVG.js @@ -1,4 +1,4 @@ -function getSVG(pathlist, size, opt_type) { +function getSVG(pathList, size, opt_type) { function path(curve) { @@ -35,13 +35,13 @@ function getSVG(pathlist, size, opt_type) { } var w = 846, h = 352, - len = pathlist.length, c, i, strokec, fillc, fillrule; + len = pathList.length, c, i, strokec, fillc, fillrule; var svg = ''; svg += '= n ? a % n : a>=0 ? a : n-1-(-1-a) % n; + if (options.optcurve) optiCurve(path, options); } +} - function xprod(p1, p2) { - return p1.x * p2.y - p1.y * p2.x; - } +function mod(a, n) { + return a >= n ? a % n : a >= 0 ? a : n - 1 - (-1 - a) % n; +} - function cyclic(a, b, c) { - if (a <= c) { - return (a <= b && b < c); - } else { - return (a <= b || b < c); - } - } +function xprod(p1, p2) { + return p1.x * p2.y - p1.y * p2.x; +} - function sign(i) { - return i > 0 ? 1 : i < 0 ? -1 : 0; +function cyclic(a, b, c) { + if (a <= c) { + return (a <= b && b < c); + } else { + return (a <= b || b < c); } +} - function quadform(Q, w) { - var v = new Array(3), i, j, sum; +function quadform(Q, w) { + var v = new Array(3), i, j, sum; - v[0] = w.x; - v[1] = w.y; - v[2] = 1; - sum = 0.0; + v[0] = w.x; + v[1] = w.y; + v[2] = 1; + sum = 0.0; - for (i=0; i<3; i++) { - for (j=0; j<3; j++) { - sum += v[i] * Q.at(i, j) * v[j]; - } + for (i=0; i<3; i++) { + for (j=0; j<3; j++) { + sum += v[i] * Q.at(i, j) * v[j]; } - return sum; - } - - function interval(lambda, a, b) { - var res = new Point(); - - res.x = a.x + lambda * (b.x - a.x); - res.y = a.y + lambda * (b.y - a.y); - return res; } + return sum; +} - function dorth_infty(p0, p2) { - var r = new Point(); +function dpara(p0, p1, p2) { + var x1, y1, x2, y2; - r.y = sign(p2.x - p0.x); - r.x = -sign(p2.y - p0.y); + x1 = p1.x - p0.x; + y1 = p1.y - p0.y; + x2 = p2.x - p0.x; + y2 = p2.y - p0.y; - return r; - } + return x1 * y2 - x2 * y1; +} - function ddenom(p0, p2) { - var r = dorth_infty(p0, p2); +function cprod(p0, p1, p2, p3) { + var x1, y1, x2, y2; - return r.y * (p2.x - p0.x) - r.x * (p2.y - p0.y); - } + x1 = p1.x - p0.x; + y1 = p1.y - p0.y; + x2 = p3.x - p2.x; + y2 = p3.y - p2.y; - function dpara(p0, p1, p2) { - var x1, y1, x2, y2; + return x1 * y2 - x2 * y1; +} - x1 = p1.x - p0.x; - y1 = p1.y - p0.y; - x2 = p2.x - p0.x; - y2 = p2.y - p0.y; +function iprod(p0, p1, p2) { + var x1, y1, x2, y2; - return x1 * y2 - x2 * y1; - } + x1 = p1.x - p0.x; + y1 = p1.y - p0.y; + x2 = p2.x - p0.x; + y2 = p2.y - p0.y; - function cprod(p0, p1, p2, p3) { - var x1, y1, x2, y2; + return x1 * x2 + y1 * y2; +} - x1 = p1.x - p0.x; - y1 = p1.y - p0.y; - x2 = p3.x - p2.x; - y2 = p3.y - p2.y; +function iprod1(p0, p1, p2, p3) { + var x1, y1, x2, y2; - return x1 * y2 - x2 * y1; - } + x1 = p1.x - p0.x; + y1 = p1.y - p0.y; + x2 = p3.x - p2.x; + y2 = p3.y - p2.y; - function iprod(p0, p1, p2) { - var x1, y1, x2, y2; + return x1 * x2 + y1 * y2; +} - x1 = p1.x - p0.x; - y1 = p1.y - p0.y; - x2 = p2.x - p0.x; - y2 = p2.y - p0.y; +function ddist(p, q) { + return Math.sqrt((p.x - q.x) * (p.x - q.x) + (p.y - q.y) * (p.y - q.y)); +} - return x1*x2 + y1*y2; - } +function bezier(t, p0, p1, p2, p3) { + var s = 1 - t, res = new Point(); - function iprod1(p0, p1, p2, p3) { - var x1, y1, x2, y2; + res.x = s*s*s*p0.x + 3*(s*s*t)*p1.x + 3*(t*t*s)*p2.x + t*t*t*p3.x; + res.y = s*s*s*p0.y + 3*(s*s*t)*p1.y + 3*(t*t*s)*p2.y + t*t*t*p3.y; - x1 = p1.x - p0.x; - y1 = p1.y - p0.y; - x2 = p3.x - p2.x; - y2 = p3.y - p2.y; + return res; +} - return x1 * x2 + y1 * y2; - } +function tangent(p0, p1, p2, p3, q0, q1) { + var A, B, C, a, b, c, d, s, r1, r2; - function ddist(p, q) { - return Math.sqrt((p.x - q.x) * (p.x - q.x) + (p.y - q.y) * (p.y - q.y)); - } + A = cprod(p0, p1, q0, q1); + B = cprod(p1, p2, q0, q1); + C = cprod(p2, p3, q0, q1); - function bezier(t, p0, p1, p2, p3) { - var s = 1 - t, res = new Point(); + a = A - 2 * B + C; + b = -2 * A + 2 * B; + c = A; - res.x = s*s*s*p0.x + 3*(s*s*t)*p1.x + 3*(t*t*s)*p2.x + t*t*t*p3.x; - res.y = s*s*s*p0.y + 3*(s*s*t)*p1.y + 3*(t*t*s)*p2.y + t*t*t*p3.y; + d = b * b - 4 * a * c; - return res; + if (a===0 || d<0) { + return -1.0; } - function tangent(p0, p1, p2, p3, q0, q1) { - var A, B, C, a, b, c, d, s, r1, r2; + s = Math.sqrt(d); - A = cprod(p0, p1, q0, q1); - B = cprod(p1, p2, q0, q1); - C = cprod(p2, p3, q0, q1); + r1 = (-b + s) / (2 * a); + r2 = (-b - s) / (2 * a); - a = A - 2 * B + C; - b = -2 * A + 2 * B; - c = A; - - d = b * b - 4 * a * c; - - if (a===0 || d<0) { - return -1.0; - } - - s = Math.sqrt(d); - - r1 = (-b + s) / (2 * a); - r2 = (-b - s) / (2 * a); - - if (r1 >= 0 && r1 <= 1) { - return r1; - } else if (r2 >= 0 && r2 <= 1) { - return r2; - } else { - return -1.0; - } + if (r1 >= 0 && r1 <= 1) { + return r1; + } else if (r2 >= 0 && r2 <= 1) { + return r2; + } else { + return -1.0; } +} - function calcSums(path) { - var i, x, y; - path.x0 = path.points[0].x; - path.y0 = path.points[0].y; - - path.sums = []; - var s = path.sums; - s.push(new Sum(0, 0, 0, 0, 0)); - for(i = 0; i < path.points.length; i++){ - x = path.points[i].x - path.x0; - y = path.points[i].y - path.y0; - s.push(new Sum(s[i].x + x, s[i].y + y, s[i].xy + x * y, - s[i].x2 + x * x, s[i].y2 + y * y)); - } +function calcSums(path) { + var i, x, y; + path.x0 = path.points[0].x; + path.y0 = path.points[0].y; + + path.sums = []; + var s = path.sums; + s.push(new Sum(0, 0, 0, 0, 0)); + for(i = 0; i < path.points.length; i++){ + x = path.points[i].x - path.x0; + y = path.points[i].y - path.y0; + s.push(new Sum(s[i].x + x, s[i].y + y, s[i].xy + x * y, + s[i].x2 + x * x, s[i].y2 + y * y)); } +} - function calcLon(path) { +function calcLon(path) { - var n = path.points.length, pt = path.points, dir, - pivk = new Array(n), - nc = new Array(n), - ct = new Array(4); - path.lon = new Array(n); + var n = path.points.length, pt = path.points, dir, + pivk = new Array(n), + nc = new Array(n), + ct = new Array(4); + path.lon = new Array(n); - var constraint = [new Point(), new Point()], - cur = new Point(), - off = new Point(), - dk = new Point(), - foundk; + var constraint = [new Point(), new Point()], + cur = new Point(), + off = new Point(), + dk = new Point(), + foundk; - var i, j, k1, a, b, c, d, k = 0; - for(i = n - 1; i >= 0; i--){ - if (pt[i].x != pt[k].x && pt[i].y != pt[k].y) { - k = i + 1; - } - nc[i] = k; + var i, j, k1, a, b, c, d, k = 0; + for(i = n - 1; i >= 0; i--){ + if (pt[i].x != pt[k].x && pt[i].y != pt[k].y) { + k = i + 1; } + nc[i] = k; + } - for (i = n - 1; i >= 0; i--) { - ct[0] = ct[1] = ct[2] = ct[3] = 0; - dir = (3 + 3 * (pt[mod(i + 1, n)].x - pt[i].x) + - (pt[mod(i + 1, n)].y - pt[i].y)) / 2; + for (i = n - 1; i >= 0; i--) { + ct[0] = ct[1] = ct[2] = ct[3] = 0; + dir = (3 + 3 * (pt[mod(i + 1, n)].x - pt[i].x) + + (pt[mod(i + 1, n)].y - pt[i].y)) / 2; + ct[dir]++; + + constraint[0].x = 0; + constraint[0].y = 0; + constraint[1].x = 0; + constraint[1].y = 0; + + k = nc[i]; + k1 = i; + while (1) { + foundk = 0; + dir = (3 + 3 * sign(pt[k].x - pt[k1].x) + + sign(pt[k].y - pt[k1].y)) / 2; ct[dir]++; - constraint[0].x = 0; - constraint[0].y = 0; - constraint[1].x = 0; - constraint[1].y = 0; - - k = nc[i]; - k1 = i; - while (1) { - foundk = 0; - dir = (3 + 3 * sign(pt[k].x - pt[k1].x) + - sign(pt[k].y - pt[k1].y)) / 2; - ct[dir]++; - - if (ct[0] && ct[1] && ct[2] && ct[3]) { - pivk[i] = k1; - foundk = 1; - break; - } - - cur.x = pt[k].x - pt[i].x; - cur.y = pt[k].y - pt[i].y; + if (ct[0] && ct[1] && ct[2] && ct[3]) { + pivk[i] = k1; + foundk = 1; + break; + } - if (xprod(constraint[0], cur) < 0 || xprod(constraint[1], cur) > 0) { - break; - } + cur.x = pt[k].x - pt[i].x; + cur.y = pt[k].y - pt[i].y; - if (Math.abs(cur.x) <= 1 && Math.abs(cur.y) <= 1) { - - } else { - off.x = cur.x + ((cur.y >= 0 && (cur.y > 0 || cur.x < 0)) ? 1 : -1); - off.y = cur.y + ((cur.x <= 0 && (cur.x < 0 || cur.y < 0)) ? 1 : -1); - if (xprod(constraint[0], off) >= 0) { - constraint[0].x = off.x; - constraint[0].y = off.y; - } - off.x = cur.x + ((cur.y <= 0 && (cur.y < 0 || cur.x < 0)) ? 1 : -1); - off.y = cur.y + ((cur.x >= 0 && (cur.x > 0 || cur.y < 0)) ? 1 : -1); - if (xprod(constraint[1], off) <= 0) { - constraint[1].x = off.x; - constraint[1].y = off.y; - } - } - k1 = k; - k = nc[k1]; - if (!cyclic(k, i, k1)) { - break; - } + if (xprod(constraint[0], cur) < 0 || xprod(constraint[1], cur) > 0) { + break; } - if (foundk === 0) { - dk.x = sign(pt[k].x-pt[k1].x); - dk.y = sign(pt[k].y-pt[k1].y); - cur.x = pt[k1].x - pt[i].x; - cur.y = pt[k1].y - pt[i].y; - a = xprod(constraint[0], cur); - b = xprod(constraint[0], dk); - c = xprod(constraint[1], cur); - d = xprod(constraint[1], dk); + if (Math.abs(cur.x) <= 1 && Math.abs(cur.y) <= 1) { - j = 10000000; - if (b < 0) { - j = Math.floor(a / -b); + } else { + off.x = cur.x + ((cur.y >= 0 && (cur.y > 0 || cur.x < 0)) ? 1 : -1); + off.y = cur.y + ((cur.x <= 0 && (cur.x < 0 || cur.y < 0)) ? 1 : -1); + if (xprod(constraint[0], off) >= 0) { + constraint[0].x = off.x; + constraint[0].y = off.y; } - if (d > 0) { - j = Math.min(j, Math.floor(-c / d)); + off.x = cur.x + ((cur.y <= 0 && (cur.y < 0 || cur.x < 0)) ? 1 : -1); + off.y = cur.y + ((cur.x >= 0 && (cur.x > 0 || cur.y < 0)) ? 1 : -1); + if (xprod(constraint[1], off) <= 0) { + constraint[1].x = off.x; + constraint[1].y = off.y; } - pivk[i] = mod(k1+j,n); + } + k1 = k; + k = nc[k1]; + if (!cyclic(k, i, k1)) { + break; } } + if (foundk === 0) { + dk.x = sign(pt[k].x-pt[k1].x); + dk.y = sign(pt[k].y-pt[k1].y); + cur.x = pt[k1].x - pt[i].x; + cur.y = pt[k1].y - pt[i].y; - j=pivk[n-1]; - path.lon[n-1]=j; - for (i=n-2; i>=0; i--) { - if (cyclic(i+1,pivk[i],j)) { - j=pivk[i]; + a = xprod(constraint[0], cur); + b = xprod(constraint[0], dk); + c = xprod(constraint[1], cur); + d = xprod(constraint[1], dk); + + j = 10000000; + if (b < 0) { + j = Math.floor(a / -b); + } + if (d > 0) { + j = Math.min(j, Math.floor(-c / d)); } - path.lon[i]=j; + pivk[i] = mod(k1+j,n); } + } - for (i=n-1; cyclic(mod(i+1,n),j,path.lon[i]); i--) { - path.lon[i] = j; + j=pivk[n-1]; + path.lon[n-1]=j; + for (i=n-2; i>=0; i--) { + if (cyclic(i+1,pivk[i],j)) { + j=pivk[i]; } + path.lon[i]=j; } - function bestPolygon(path) { + for (i=n-1; cyclic(mod(i+1,n),j,path.lon[i]); i--) { + path.lon[i] = j; + } +} - function penalty3(path, i, j) { +function bestPolygon(path) { - var n = path.points.length, pt = path.points, sums = path.sums; - var x, y, xy, x2, y2, - k, a, b, c, s, - px, py, ex, ey, - r = 0; - if (j>=n) { - j -= n; - r = 1; - } + function penalty3(path, i, j) { - if (r === 0) { - x = sums[j+1].x - sums[i].x; - y = sums[j+1].y - sums[i].y; - x2 = sums[j+1].x2 - sums[i].x2; - xy = sums[j+1].xy - sums[i].xy; - y2 = sums[j+1].y2 - sums[i].y2; - k = j+1 - i; - } else { - x = sums[j+1].x - sums[i].x + sums[n].x; - y = sums[j+1].y - sums[i].y + sums[n].y; - x2 = sums[j+1].x2 - sums[i].x2 + sums[n].x2; - xy = sums[j+1].xy - sums[i].xy + sums[n].xy; - y2 = sums[j+1].y2 - sums[i].y2 + sums[n].y2; - k = j+1 - i + n; - } + var n = path.points.length, pt = path.points, sums = path.sums; + var x, y, xy, x2, y2, + k, a, b, c, s, + px, py, ex, ey, + r = 0; + if (j>=n) { + j -= n; + r = 1; + } - px = (pt[i].x + pt[j].x) / 2.0 - pt[0].x; - py = (pt[i].y + pt[j].y) / 2.0 - pt[0].y; - ey = (pt[j].x - pt[i].x); - ex = -(pt[j].y - pt[i].y); + if (r === 0) { + x = sums[j+1].x - sums[i].x; + y = sums[j+1].y - sums[i].y; + x2 = sums[j+1].x2 - sums[i].x2; + xy = sums[j+1].xy - sums[i].xy; + y2 = sums[j+1].y2 - sums[i].y2; + k = j+1 - i; + } else { + x = sums[j+1].x - sums[i].x + sums[n].x; + y = sums[j+1].y - sums[i].y + sums[n].y; + x2 = sums[j+1].x2 - sums[i].x2 + sums[n].x2; + xy = sums[j+1].xy - sums[i].xy + sums[n].xy; + y2 = sums[j+1].y2 - sums[i].y2 + sums[n].y2; + k = j+1 - i + n; + } - a = ((x2 - 2*x*px) / k + px*px); - b = ((xy - x*py - y*px) / k + px*py); - c = ((y2 - 2*y*py) / k + py*py); + px = (pt[i].x + pt[j].x) / 2.0 - pt[0].x; + py = (pt[i].y + pt[j].y) / 2.0 - pt[0].y; + ey = (pt[j].x - pt[i].x); + ex = -(pt[j].y - pt[i].y); - s = ex*ex*a + 2*ex*ey*b + ey*ey*c; + a = ((x2 - 2*x*px) / k + px*px); + b = ((xy - x*py - y*px) / k + px*py); + c = ((y2 - 2*y*py) / k + py*py); - return Math.sqrt(s); - } + s = ex*ex*a + 2*ex*ey*b + ey*ey*c; - var i, j, m, k, - n = path.points.length, - pen = new Array(n + 1), - prev = new Array(n + 1), - clip0 = new Array(n), - clip1 = new Array(n + 1), - seg0 = new Array (n + 1), - seg1 = new Array(n + 1), - thispen, best, c; + return Math.sqrt(s); + } - for (i=0; i0; j--) { - seg1[j] = i; - i = clip1[i]; - } - seg1[0] = 0; + i = 0; + for (j=0; i0; j--) { + seg1[j] = i; + i = clip1[i]; + } + seg1[0] = 0; - pen[0]=0; - for (j=1; j<=m; j++) { - for (i=seg1[j]; i<=seg0[j]; i++) { - best = -1; - for (k=seg0[j-1]; k>=clip1[i]; k--) { - thispen = penalty3(path, k, i) + pen[k]; - if (best < 0 || thispen < best) { - prev[i] = k; - best = thispen; - } + pen[0]=0; + for (j=1; j<=m; j++) { + for (i=seg1[j]; i<=seg0[j]; i++) { + best = -1; + for (k=seg0[j-1]; k>=clip1[i]; k--) { + thispen = penalty3(path, k, i) + pen[k]; + if (best < 0 || thispen < best) { + prev[i] = k; + best = thispen; } - pen[i] = best; } + pen[i] = best; } - path.m = m; - path.po = new Array(m); + } + path.m = m; + path.po = new Array(m); - for (i=n, j=m-1; i>0; j--) { - i = prev[i]; - path.po[j] = i; - } + for (i=n, j=m-1; i>0; j--) { + i = prev[i]; + path.po[j] = i; } +} - function adjustVertices(path) { +function adjustVertices(path) { - function pointslope(path, i, j, ctr, dir) { + function pointslope(path, i, j, ctr, dir) { - var n = path.points.length, sums = path.sums, - x, y, x2, xy, y2, - k, a, b, c, lambda2, l, r=0; + var n = path.points.length, sums = path.sums, + x, y, x2, xy, y2, + k, a, b, c, lambda2, l, r=0; - while (j>=n) { - j-=n; - r+=1; - } - while (i>=n) { - i-=n; - r-=1; - } - while (j<0) { - j+=n; - r-=1; - } - while (i<0) { - i+=n; - r+=1; - } + while (j>=n) { + j-=n; + r+=1; + } + while (i>=n) { + i-=n; + r-=1; + } + while (j<0) { + j+=n; + r-=1; + } + while (i<0) { + i+=n; + r+=1; + } - x = sums[j+1].x-sums[i].x+r*sums[n].x; - y = sums[j+1].y-sums[i].y+r*sums[n].y; - x2 = sums[j+1].x2-sums[i].x2+r*sums[n].x2; - xy = sums[j+1].xy-sums[i].xy+r*sums[n].xy; - y2 = sums[j+1].y2-sums[i].y2+r*sums[n].y2; - k = j+1-i+r*n; + x = sums[j+1].x-sums[i].x+r*sums[n].x; + y = sums[j+1].y-sums[i].y+r*sums[n].y; + x2 = sums[j+1].x2-sums[i].x2+r*sums[n].x2; + xy = sums[j+1].xy-sums[i].xy+r*sums[n].xy; + y2 = sums[j+1].y2-sums[i].y2+r*sums[n].y2; + k = j+1-i+r*n; - ctr.x = x/k; - ctr.y = y/k; + ctr.x = x/k; + ctr.y = y/k; - a = (x2-x*x/k)/k; - b = (xy-x*y/k)/k; - c = (y2-y*y/k)/k; + a = (x2-x*x/k)/k; + b = (xy-x*y/k)/k; + c = (y2-y*y/k)/k; - lambda2 = (a+c+Math.sqrt((a-c)*(a-c)+4*b*b))/2; + lambda2 = (a+c+Math.sqrt((a-c)*(a-c)+4*b*b))/2; - a -= lambda2; - c -= lambda2; + a -= lambda2; + c -= lambda2; - if (Math.abs(a) >= Math.abs(c)) { - l = Math.sqrt(a*a+b*b); - if (l!==0) { - dir.x = -b/l; - dir.y = a/l; - } - } else { - l = Math.sqrt(c*c+b*b); - if (l!==0) { - dir.x = -c/l; - dir.y = b/l; - } + if (Math.abs(a) >= Math.abs(c)) { + l = Math.sqrt(a*a+b*b); + if (l!==0) { + dir.x = -b/l; + dir.y = a/l; } - if (l===0) { - dir.x = dir.y = 0; + } else { + l = Math.sqrt(c*c+b*b); + if (l!==0) { + dir.x = -c/l; + dir.y = b/l; } } - - var m = path.m, po = path.po, n = path.points.length, pt = path.points, - x0 = path.x0, y0 = path.y0, - ctr = new Array(m), dir = new Array(m), - q = new Array(m), - v = new Array(3), d, i, j, k, l, - s = new Point(); - - path.curve = new Curve(m); - - for (i=0; iQ.at(1, 1)) { - v[0] = -Q.at(0, 1); - v[1] = Q.at(0, 0); - } else if (Q.at(1, 1)) { - v[0] = -Q.at(1, 1); - v[1] = Q.at(1, 0); - } else { - v[0] = 1; - v[1] = 0; - } - d = v[0] * v[0] + v[1] * v[1]; - v[2] = - v[1] * s.y - v[0] * s.x; - for (l=0; l<3; l++) { - for (k=0; k<3; k++) { - Q.data[l * 3 + k] += v[l] * v[k] / d; - } - } + if (Q.at(0, 0)>Q.at(1, 1)) { + v[0] = -Q.at(0, 1); + v[1] = Q.at(0, 0); + } else if (Q.at(1, 1)) { + v[0] = -Q.at(1, 1); + v[1] = Q.at(1, 0); + } else { + v[0] = 1; + v[1] = 0; } - dx = Math.abs(w.x-s.x); - dy = Math.abs(w.y-s.y); - if (dx <= 0.5 && dy <= 0.5) { - path.curve.vertex[i] = new Point(w.x+x0, w.y+y0); - continue; - } - - min = quadform(Q, s); - xmin = s.x; - ymin = s.y; - - if (Q.at(0, 0) !== 0.0) { - for (z=0; z<2; z++) { - w.y = s.y-0.5+z; - w.x = - (Q.at(0, 1) * w.y + Q.at(0, 2)) / Q.at(0, 0); - dx = Math.abs(w.x-s.x); - cand = quadform(Q, w); - if (dx <= 0.5 && cand < min) { - min = cand; - xmin = w.x; - ymin = w.y; - } + d = v[0] * v[0] + v[1] * v[1]; + v[2] = - v[1] * s.y - v[0] * s.x; + for (l=0; l<3; l++) { + for (k=0; k<3; k++) { + Q.data[l * 3 + k] += v[l] * v[k] / d; } } + } + dx = Math.abs(w.x-s.x); + dy = Math.abs(w.y-s.y); + if (dx <= 0.5 && dy <= 0.5) { + path.curve.vertex[i] = new Point(w.x+x0, w.y+y0); + continue; + } - if (Q.at(1, 1) !== 0.0) { - for (z=0; z<2; z++) { - w.x = s.x-0.5+z; - w.y = - (Q.at(1, 0) * w.x + Q.at(1, 2)) / Q.at(1, 1); - dy = Math.abs(w.y-s.y); - cand = quadform(Q, w); - if (dy <= 0.5 && cand < min) { - min = cand; - xmin = w.x; - ymin = w.y; - } + min = quadform(Q, s); + xmin = s.x; + ymin = s.y; + + if (Q.at(0, 0) !== 0.0) { + for (z=0; z<2; z++) { + w.y = s.y-0.5+z; + w.x = - (Q.at(0, 1) * w.y + Q.at(0, 2)) / Q.at(0, 0); + dx = Math.abs(w.x-s.x); + cand = quadform(Q, w); + if (dx <= 0.5 && cand < min) { + min = cand; + xmin = w.x; + ymin = w.y; } } + } - for (l=0; l<2; l++) { - for (k=0; k<2; k++) { - w.x = s.x-0.5+l; - w.y = s.y-0.5+k; - cand = quadform(Q, w); - if (cand < min) { - min = cand; - xmin = w.x; - ymin = w.y; - } + if (Q.at(1, 1) !== 0.0) { + for (z=0; z<2; z++) { + w.x = s.x-0.5+z; + w.y = - (Q.at(1, 0) * w.x + Q.at(1, 2)) / Q.at(1, 1); + dy = Math.abs(w.y-s.y); + cand = quadform(Q, w); + if (dy <= 0.5 && cand < min) { + min = cand; + xmin = w.x; + ymin = w.y; } } - - path.curve.vertex[i] = new Point(xmin + x0, ymin + y0); } - } - - function reverse(path) { - var curve = path.curve, m = curve.n, v = curve.vertex, i, j, tmp; - for (i=0, j=m-1; i1 ? (1 - 1.0/dd) : 0; - alpha = alpha / 0.75; - } else { - alpha = 4/3.0; - } - curve.alpha0[j] = alpha; + denom = curve.vertex[i].ddenom(curve.vertex[k]); + if (denom !== 0.0) { + dd = dpara(curve.vertex[i], curve.vertex[j], curve.vertex[k]) / denom; + dd = Math.abs(dd); + alpha = dd>1 ? (1 - 1.0/dd) : 0; + alpha = alpha / 0.75; + } else { + alpha = 4/3.0; + } + curve.alpha0[j] = alpha; - if (alpha >= info.alphamax) { - curve.tag[j] = "CORNER"; - curve.c[3 * j + 1] = curve.vertex[j]; - curve.c[3 * j + 2] = p4; - } else { - if (alpha < 0.55) { - alpha = 0.55; - } else if (alpha > 1) { - alpha = 1; - } - p2 = interval(0.5+0.5*alpha, curve.vertex[i], curve.vertex[j]); - p3 = interval(0.5+0.5*alpha, curve.vertex[k], curve.vertex[j]); - curve.tag[j] = "CURVE"; - curve.c[3 * j + 0] = p2; - curve.c[3 * j + 1] = p3; - curve.c[3 * j + 2] = p4; - } - curve.alpha[j] = alpha; - curve.beta[j] = 0.5; + if (alpha >= info.alphamax) { + curve.tag[j] = 'CORNER'; + curve.c[3 * j + 1] = curve.vertex[j]; + curve.c[3 * j + 2] = p4; + } else { + if (alpha < 0.55) { + alpha = 0.55; + } else if (alpha > 1) { + alpha = 1; + } + const lambda = 0.5 + 0.5 * alpha; + p2 = curve.vertex[i].lerp(curve.vertex[j], lambda); + p3 = curve.vertex[k].lerp(curve.vertex[j], lambda); + curve.tag[j] = 'CURVE'; + curve.c[3 * j + 0] = p2; + curve.c[3 * j + 1] = p3; + curve.c[3 * j + 2] = p4; } - curve.alphacurve = 1; + curve.alpha[j] = alpha; + curve.beta[j] = 0.5; } + curve.alphacurve = 1; +} - function optiCurve(path) { - function Opti(){ - this.pen = 0; - this.c = [new Point(), new Point()]; - this.t = 0; - this.s = 0; - this.alpha = 0; - } +function optiCurve(path, info) { + function Opti(){ + this.pen = 0; + this.c = [new Point(), new Point()]; + this.t = 0; + this.s = 0; + this.alpha = 0; + } - function opti_penalty(path, i, j, res, opttolerance, convc, areac) { - var m = path.curve.n, curve = path.curve, vertex = curve.vertex, - k, k1, k2, conv, i1, - area, alpha, d, d1, d2, - p0, p1, p2, p3, pt, - A, R, A1, A2, A3, A4, - s, t; + function opti_penalty(path, i, j, res, opttolerance, convc, areac) { + var m = path.curve.n, curve = path.curve, vertex = curve.vertex, + k, k1, k2, conv, i1, + area, alpha, d, d1, d2, + p0, p1, p2, p3, pt, + A, R, A1, A2, A3, A4, + s, t; - if (i==j) { - return 1; - } + if (i==j) { + return 1; + } - k = i; - i1 = mod(i+1, m); + k = i; + i1 = mod(i+1, m); + k1 = mod(k+1, m); + conv = convc[k1]; + if (conv === 0) { + return 1; + } + d = ddist(vertex[i], vertex[i1]); + for (k=k1; k!=j; k=k1) { k1 = mod(k+1, m); - conv = convc[k1]; - if (conv === 0) { + k2 = mod(k+2, m); + if (convc[k1] != conv) { return 1; } - d = ddist(vertex[i], vertex[i1]); - for (k=k1; k!=j; k=k1) { - k1 = mod(k+1, m); - k2 = mod(k+2, m); - if (convc[k1] != conv) { - return 1; - } - if (sign(cprod(vertex[i], vertex[i1], vertex[k1], vertex[k2])) != - conv) { - return 1; - } - if (iprod1(vertex[i], vertex[i1], vertex[k1], vertex[k2]) < - d * ddist(vertex[k1], vertex[k2]) * -0.999847695156) { - return 1; - } - } - - p0 = curve.c[mod(i,m) * 3 + 2].copy(); - p1 = vertex[mod(i+1,m)].copy(); - p2 = vertex[mod(j,m)].copy(); - p3 = curve.c[mod(j,m) * 3 + 2].copy(); - - area = areac[j] - areac[i]; - area -= dpara(vertex[0], curve.c[i * 3 + 2], curve.c[j * 3 + 2])/2; - if (i>=j) { - area += areac[m]; - } - - A1 = dpara(p0, p1, p2); - A2 = dpara(p0, p1, p3); - A3 = dpara(p0, p2, p3); - - A4 = A1+A3-A2; - - if (A2 == A1) { + if (sign(cprod(vertex[i], vertex[i1], vertex[k1], vertex[k2])) != + conv) { return 1; } - - t = A3/(A3-A4); - s = A2/(A2-A1); - A = A2 * t / 2.0; - - if (A === 0.0) { + if (iprod1(vertex[i], vertex[i1], vertex[k1], vertex[k2]) < + d * ddist(vertex[k1], vertex[k2]) * -0.999847695156) { return 1; } + } - R = area / A; - alpha = 2 - Math.sqrt(4 - R / 0.3); + p0 = curve.c[mod(i,m) * 3 + 2].copy(); + p1 = vertex[mod(i+1,m)].copy(); + p2 = vertex[mod(j,m)].copy(); + p3 = curve.c[mod(j,m) * 3 + 2].copy(); - res.c[0] = interval(t * alpha, p0, p1); - res.c[1] = interval(s * alpha, p3, p2); - res.alpha = alpha; - res.t = t; - res.s = s; + area = areac[j] - areac[i]; + area -= dpara(vertex[0], curve.c[i * 3 + 2], curve.c[j * 3 + 2])/2; + if (i>=j) { + area += areac[m]; + } - p1 = res.c[0].copy(); - p2 = res.c[1].copy(); + A1 = dpara(p0, p1, p2); + A2 = dpara(p0, p1, p3); + A3 = dpara(p0, p2, p3); - res.pen = 0; + A4 = A1+A3-A2; - for (k=mod(i+1,m); k!=j; k=k1) { - k1 = mod(k+1,m); - t = tangent(p0, p1, p2, p3, vertex[k], vertex[k1]); - if (t<-0.5) { - return 1; - } - pt = bezier(t, p0, p1, p2, p3); - d = ddist(vertex[k], vertex[k1]); - if (d === 0.0) { - return 1; - } - d1 = dpara(vertex[k], vertex[k1], pt) / d; - if (Math.abs(d1) > opttolerance) { - return 1; - } - if (iprod(vertex[k], vertex[k1], pt) < 0 || - iprod(vertex[k1], vertex[k], pt) < 0) { - return 1; - } - res.pen += d1 * d1; - } + if (A2 == A1) { + return 1; + } - for (k=i; k!=j; k=k1) { - k1 = mod(k+1,m); - t = tangent(p0, p1, p2, p3, curve.c[k * 3 + 2], curve.c[k1 * 3 + 2]); - if (t<-0.5) { - return 1; - } - pt = bezier(t, p0, p1, p2, p3); - d = ddist(curve.c[k * 3 + 2], curve.c[k1 * 3 + 2]); - if (d === 0.0) { - return 1; - } - d1 = dpara(curve.c[k * 3 + 2], curve.c[k1 * 3 + 2], pt) / d; - d2 = dpara(curve.c[k * 3 + 2], curve.c[k1 * 3 + 2], vertex[k1]) / d; - d2 *= 0.75 * curve.alpha[k1]; - if (d2 < 0) { - d1 = -d1; - d2 = -d2; - } - if (d1 < d2 - opttolerance) { - return 1; - } - if (d1 < d2) { - res.pen += (d1 - d2) * (d1 - d2); - } - } + t = A3/(A3-A4); + s = A2/(A2-A1); + A = A2 * t / 2.0; - return 0; + if (A === 0.0) { + return 1; } - var curve = path.curve, m = curve.n, vert = curve.vertex, - pt = new Array(m + 1), - pen = new Array(m + 1), - len = new Array(m + 1), - opt = new Array(m + 1), - om, i,j,r, - o = new Opti(), p0, - i1, area, alpha, ocurve, - s, t; + R = area / A; + alpha = 2 - Math.sqrt(4 - R / 0.3); - var convc = new Array(m), areac = new Array(m + 1); + res.c[0] = p0.lerp(p1, t * alpha); + res.c[1] = p3.lerp(p2, s * alpha); + res.alpha = alpha; + res.t = t; + res.s = s; - for (i=0; i opttolerance) { + return 1; + } + if (iprod(vertex[k], vertex[k1], pt) < 0 || + iprod(vertex[k1], vertex[k], pt) < 0) { + return 1; + } + res.pen += d1 * d1; } - area = 0.0; - areac[0] = 0.0; - p0 = curve.vertex[0]; - for (i=0; i=0; i--) { - r = opti_penalty(path, i, mod(j,m), o, info.opttolerance, convc, - areac); - if (r) { - break; - } - if (len[j] > len[i]+1 || - (len[j] == len[i]+1 && pen[j] > pen[i] + o.pen)) { - pt[j] = i; - pen[j] = pen[i] + o.pen; - len[j] = len[i] + 1; - opt[j] = o; - o = new Opti(); - } - } - } - om = len[m]; - ocurve = new Curve(om); - s = new Array(om); - t = new Array(om); - - j = m; - for (i=om-1; i>=0; i--) { - if (pt[j]==j-1) { - ocurve.tag[i] = curve.tag[mod(j,m)]; - ocurve.c[i * 3 + 0] = curve.c[mod(j,m) * 3 + 0]; - ocurve.c[i * 3 + 1] = curve.c[mod(j,m) * 3 + 1]; - ocurve.c[i * 3 + 2] = curve.c[mod(j,m) * 3 + 2]; - ocurve.vertex[i] = curve.vertex[mod(j,m)]; - ocurve.alpha[i] = curve.alpha[mod(j,m)]; - ocurve.alpha0[i] = curve.alpha0[mod(j,m)]; - ocurve.beta[i] = curve.beta[mod(j,m)]; - s[i] = t[i] = 1.0; - } else { - ocurve.tag[i] = "CURVE"; - ocurve.c[i * 3 + 0] = opt[j].c[0]; - ocurve.c[i * 3 + 1] = opt[j].c[1]; - ocurve.c[i * 3 + 2] = curve.c[mod(j,m) * 3 + 2]; - ocurve.vertex[i] = interval(opt[j].s, curve.c[mod(j,m) * 3 + 2], - vert[mod(j,m)]); - ocurve.alpha[i] = opt[j].alpha; - ocurve.alpha0[i] = opt[j].alpha; - s[i] = opt[j].s; - t[i] = opt[j].t; - } - j = pt[j]; - } - - for (i=0; i=0; i--) { + r = opti_penalty(path, i, mod(j,m), o, info.opttolerance, convc, + areac); + if (r) { + break; + } + if (len[j] > len[i]+1 || + (len[j] == len[i]+1 && pen[j] > pen[i] + o.pen)) { + pt[j] = i; + pen[j] = pen[i] + o.pen; + len[j] = len[i] + 1; + opt[j] = o; + o = new Opti(); + } } } + om = len[m]; + ocurve = new Curve(om); + s = new Array(om); + t = new Array(om); + + j = m; + for (i=om-1; i>=0; i--) { + if (pt[j]==j-1) { + ocurve.tag[i] = curve.tag[mod(j,m)]; + ocurve.c[i * 3 + 0] = curve.c[mod(j,m) * 3 + 0]; + ocurve.c[i * 3 + 1] = curve.c[mod(j,m) * 3 + 1]; + ocurve.c[i * 3 + 2] = curve.c[mod(j,m) * 3 + 2]; + ocurve.vertex[i] = curve.vertex[mod(j,m)]; + ocurve.alpha[i] = curve.alpha[mod(j,m)]; + ocurve.alpha0[i] = curve.alpha0[mod(j,m)]; + ocurve.beta[i] = curve.beta[mod(j,m)]; + s[i] = t[i] = 1.0; + } else { + ocurve.tag[i] = 'CURVE'; + ocurve.c[i * 3 + 0] = opt[j].c[0]; + ocurve.c[i * 3 + 1] = opt[j].c[1]; + ocurve.c[i * 3 + 2] = curve.c[mod(j,m) * 3 + 2]; + + ocurve.vertex[i] = curve.c[mod(j, m) * 3 + 2].lerp(vert[mod(j, m)], opt[j].s); + ocurve.alpha[i] = opt[j].alpha; + ocurve.alpha0[i] = opt[j].alpha; + s[i] = opt[j].s; + t[i] = opt[j].t; + } + j = pt[j]; + } - return pathlist; - + for (i=0; i 0 ? 1 : i < 0 ? -1 : 0; +} From b6fe98c62415df8ffe7f4f4670c700ecd1b55e5b Mon Sep 17 00:00:00 2001 From: casperlamboo Date: Wed, 1 Mar 2017 11:00:46 +0100 Subject: [PATCH 14/29] clean up --- example/index.js | 4 +--- src/Path.js | 8 +------- src/index.js | 4 ++++ 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/example/index.js b/example/index.js index 239af4d..37bcae9 100644 --- a/example/index.js +++ b/example/index.js @@ -2,6 +2,4 @@ import * as POTRACE from 'src/index.js'; POTRACE.traceUrl('test.png') .then(paths => POTRACE.getSVG(paths, 1.0, 'curve')) - .then(svg => { - document.write(svg); - }); + .then(svg => document.write(svg)); diff --git a/src/Path.js b/src/Path.js index a9f97e1..a1ca4da 100644 --- a/src/Path.js +++ b/src/Path.js @@ -15,12 +15,6 @@ export default class Path { this.curve = {}; } reverse() { - var curve = this.curve, m = curve.n, v = curve.vertex, i, j, tmp; - - for (i=0, j=m-1; i Date: Wed, 1 Mar 2017 11:23:26 +0100 Subject: [PATCH 15/29] Add read me --- README.md | 91 +++++++++++++++++++++++++++++++++++++++++++++++++--- src/index.js | 4 --- 2 files changed, 87 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 08267f7..8068aa3 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,91 @@ -potrace -======= +# POTRACE +Based on http://potrace.sourceforge.net and https://github.com/kilobtye/potrace. -A javascript port of [Potrace](http://potrace.sourceforge.net) +Converts bitmap images to vector paths. -[online demo](http://kilobtye.github.io/potrace/) +# Usage +### Using JSPM (ECMAScript / ES6 Module) +Install the library. + +``` +jspm install POTRACE=github:casperlamboo/POTRACE +``` + +Include the library. + +```javascript +import POTRACE from 'POTRACE'; +``` + +### Using NPM (CommonJS module) + +Install the library. + +``` +npm install potrace-js +``` + +Include the library. + +```javascript +var POTRACE = require('potrace-js'); +``` + +# API + +**Options** + +```javascript +{ + turnpolicy: enum('black' | 'white' | 'left' | 'right' | 'minority' | 'majority'), + turdsize: Float, + optcurve: Bool, + alphamax: Float, + opttolerance: Float +} +``` + - turnpolicy: how to resolve ambiguities in path decomposition. (default: "minority") + - turdsize: suppress speckles of up to this size (default: 2) + - optcurve: turn on/off curve optimization (default: true) + - alphamax: corner threshold parameter (default: 1) + - opttolerance: curve optimization tolerance (default: 0.2) + +**POTRACE.traceUrl** + +Traces a given image from url. + +```javascript +[...Path] = async POTRACE.traceUrl(url: String, [ options: Object ]) +``` + - url: path to the image + - options: trace options + +**POTRACE.traceImage** + +Traces a given image. + +```javascript +[...Path] = POTRACE.traceImage(image: HTMLImageElement, [ options: Object ]) +``` + - image: image containing the image + - options: trace options + +**POTRACE.traceCanvas** + +Traces a given canvas. + +```javascript +[...Path] = POTRACE.traceCanvas(canvas: HTMLCanvasElement, [ options: Object ]) +``` + - canvas: canvas containing the image + - options: trace options + +**POTRACE.getSVG** + +Converts trace result to svg. + +```javascript +svg: String = POTRACE.getSVG([...Path]) +``` diff --git a/src/index.js b/src/index.js index a0dc0b4..59fbe4f 100644 --- a/src/index.js +++ b/src/index.js @@ -11,10 +11,6 @@ const OPTIONS = { opttolerance: 0.2 }; -export function traceFile(file, options) { - return traceUrl(URL.createObjectURL(file), options); -} - export async function traceUrl(url, options) { const image = await loadImage(url, options); From 1e5c8f434b65b5392fe511e345507776cb36ee16 Mon Sep 17 00:00:00 2001 From: casperlamboo Date: Wed, 1 Mar 2017 11:23:40 +0100 Subject: [PATCH 16/29] remove potrace --- potrace.js | 1316 ---------------------------------------------------- 1 file changed, 1316 deletions(-) delete mode 100644 potrace.js diff --git a/potrace.js b/potrace.js deleted file mode 100644 index 428d940..0000000 --- a/potrace.js +++ /dev/null @@ -1,1316 +0,0 @@ -/* - * A javascript port of Potrace (http://potrace.sourceforge.net). - * - * Licensed under the GPL - * - * Usage - * loadImageFromFile(file) : load image from File API - * loadImageFromUrl(url): load image from URL - * because of the same-origin policy, can not load image from another domain. - * input color/grayscale image is simply converted to binary image. no pre- - * process is performed. - * loadImageFromUrl(canvas): load image from html5 canvas element - * when using load from canvas potrace can be used synchronously - * - * setParameter({para1: value, ...}) : set parameters - * parameters: - * turnpolicy ("black" / "white" / "left" / "right" / "minority" / "majority") - * how to resolve ambiguities in path decomposition. (default: "minority") - * turdsize - * suppress speckles of up to this size (default: 2) - * optcurve (true / false) - * turn on/off curve optimization (default: true) - * alphamax - * corner threshold parameter (default: 1) - * opttolerance - * curve optimization tolerance (default: 0.2) - * - * process(callback) : wait for the image be loaded, then run potrace algorithm, - * then call callback function. - * - * getSVG(size, opt_type) : return a string of generated SVG image. - * result_image_size = original_image_size * size - * optional parameter opt_type can be "curve" - */ - -var Potrace = (function() { - - function Point(x, y) { - this.x = x; - this.y = y; - } - - Point.prototype.copy = function(){ - return new Point(this.x, this.y); - }; - - function Bitmap(w, h) { - this.w = w; - this.h = h; - this.size = w * h; - this.arraybuffer = new ArrayBuffer(this.size); - this.data = new Int8Array(this.arraybuffer); - } - - Bitmap.prototype.at = function (x, y) { - return (x >= 0 && x < this.w && y >=0 && y < this.h) && - this.data[this.w * y + x] === 1; - }; - - Bitmap.prototype.index = function(i) { - var point = new Point(); - point.y = Math.floor(i / this.w); - point.x = i - point.y * this.w; - return point; - }; - - Bitmap.prototype.flip = function(x, y) { - if (this.at(x, y)) { - this.data[this.w * y + x] = 0; - } else { - this.data[this.w * y + x] = 1; - } - }; - - Bitmap.prototype.copy = function() { - var bm = new Bitmap(this.w, this.h), i; - for (i = 0; i < this.size; i++) { - bm.data[i] = this.data[i]; - } - return bm; - }; - - function Path() { - this.area = 0; - this.len = 0; - this.curve = {}; - this.pt = []; - this.minX = 100000; - this.minY = 100000; - this.maxX= -1; - this.maxY = -1; - } - - function Curve(n) { - this.n = n; - this.tag = new Array(n); - this.c = new Array(n * 3); - this.alphaCurve = 0; - this.vertex = new Array(n); - this.alpha = new Array(n); - this.alpha0 = new Array(n); - this.beta = new Array(n); - } - - var imgElement = document.createElement("img"), - imgCanvas = document.createElement("canvas"), - bm = null, - pathlist = [], - callback, - info = { - isReady: false, - turnpolicy: "minority", - turdsize: 2, - optcurve: true, - alphamax: 1, - opttolerance: 0.2 - }; - - imgElement.onload = function() { - loadCanvas(); - loadBm(); - }; - - function loadImageFromFile(file) { - if (info.isReady) { - clear(); - } - imgElement.file = file; - var reader = new FileReader(); - reader.onload = (function(aImg) { - return function(e) { - aImg.src = e.target.result; - }; - })(imgElement); - reader.readAsDataURL(file); - } - - function loadImageFromUrl(url) { - if (info.isReady) { - clear(); - } - imgElement.src = url; - - } - - function loadImageFromCanvas(canvas) { - if (info.isReady) { - clear(); - } - - imgCanvas = canvas; - - loadBm(); - } - - function setParameter(obj) { - var key; - for (key in obj) { - if (obj.hasOwnProperty(key)) { - info[key] = obj[key]; - } - } - } - - function loadCanvas() { - imgCanvas.width = imgElement.width; - imgCanvas.height = imgElement.height; - var ctx = imgCanvas.getContext('2d'); - ctx.drawImage(imgElement, 0, 0); - } - - function loadBm() { - var ctx = imgCanvas.getContext('2d'); - bm = new Bitmap(imgCanvas.width, imgCanvas.height); - var imgdataobj = ctx.getImageData(0, 0, bm.w, bm.h); - var l = imgdataobj.data.length, i, j, color; - for (i = 0, j = 0; i < l; i += 4, j++) { - color = 0.2126 * imgdataobj.data[i] + 0.7153 * imgdataobj.data[i + 1] + - 0.0721 * imgdataobj.data[i + 2]; - bm.data[j] = (color < 128 ? 1 : 0); - } - info.isReady = true; - } - - - function bmToPathlist() { - - var bm1 = bm.copy(), - currentPoint = new Point(0, 0), - path; - - function findNext(point) { - var i = bm1.w * point.y + point.x; - while (i < bm1.size && bm1.data[i] !== 1) { - i++; - } - return i < bm1.size && bm1.index(i); - } - - function majority(x, y) { - var i, a, ct; - for (i = 2; i < 5; i++) { - ct = 0; - for (a = -i + 1; a <= i - 1; a++) { - ct += bm1.at(x + a, y + i - 1) ? 1 : -1; - ct += bm1.at(x + i - 1, y + a - 1) ? 1 : -1; - ct += bm1.at(x + a - 1, y - i) ? 1 : -1; - ct += bm1.at(x - i, y + a) ? 1 : -1; - } - if (ct > 0) { - return 1; - } else if (ct < 0) { - return 0; - } - } - return 0; - } - - function findPath(point) { - var path = new Path(), - x = point.x, y = point.y, - dirx = 0, diry = 1, tmp; - - path.sign = bm.at(point.x, point.y) ? "+" : "-"; - - while (1) { - path.pt.push(new Point(x, y)); - if (x > path.maxX) - path.maxX = x; - if (x < path.minX) - path.minX = x; - if (y > path.maxY) - path.maxY = y; - if (y < path.minY) - path.minY = y; - path.len++; - - x += dirx; - y += diry; - path.area -= x * diry; - - if (x === point.x && y === point.y) - break; - - var l = bm1.at(x + (dirx + diry - 1 ) / 2, y + (diry - dirx - 1) / 2); - var r = bm1.at(x + (dirx - diry - 1) / 2, y + (diry + dirx - 1) / 2); - - if (r && !l) { - if (info.turnpolicy === "right" || - (info.turnpolicy === "black" && path.sign === '+') || - (info.turnpolicy === "white" && path.sign === '-') || - (info.turnpolicy === "majority" && majority(x, y)) || - (info.turnpolicy === "minority" && !majority(x, y))) { - tmp = dirx; - dirx = -diry; - diry = tmp; - } else { - tmp = dirx; - dirx = diry; - diry = -tmp; - } - } else if (r) { - tmp = dirx; - dirx = -diry; - diry = tmp; - } else if (!l) { - tmp = dirx; - dirx = diry; - diry = -tmp; - } - } - return path; - } - - function xorPath(path){ - var y1 = path.pt[0].y, - len = path.len, - x, y, maxX, minY, i, j; - for (i = 1; i < len; i++) { - x = path.pt[i].x; - y = path.pt[i].y; - - if (y !== y1) { - minY = y1 < y ? y1 : y; - maxX = path.maxX; - for (j = x; j < maxX; j++) { - bm1.flip(j, minY); - } - y1 = y; - } - } - - } - - while (currentPoint = findNext(currentPoint)) { - - path = findPath(currentPoint); - - xorPath(path); - - if (path.area > info.turdsize) { - pathlist.push(path); - } - } - - } - - - function processPath() { - - function Quad() { - this.data = [0,0,0,0,0,0,0,0,0]; - } - - Quad.prototype.at = function(x, y) { - return this.data[x * 3 + y]; - }; - - function Sum(x, y, xy, x2, y2) { - this.x = x; - this.y = y; - this.xy = xy; - this.x2 = x2; - this.y2 = y2; - } - - function mod(a, n) { - return a >= n ? a % n : a>=0 ? a : n-1-(-1-a) % n; - } - - function xprod(p1, p2) { - return p1.x * p2.y - p1.y * p2.x; - } - - function cyclic(a, b, c) { - if (a <= c) { - return (a <= b && b < c); - } else { - return (a <= b || b < c); - } - } - - function sign(i) { - return i > 0 ? 1 : i < 0 ? -1 : 0; - } - - function quadform(Q, w) { - var v = new Array(3), i, j, sum; - - v[0] = w.x; - v[1] = w.y; - v[2] = 1; - sum = 0.0; - - for (i=0; i<3; i++) { - for (j=0; j<3; j++) { - sum += v[i] * Q.at(i, j) * v[j]; - } - } - return sum; - } - - function interval(lambda, a, b) { - var res = new Point(); - - res.x = a.x + lambda * (b.x - a.x); - res.y = a.y + lambda * (b.y - a.y); - return res; - } - - function dorth_infty(p0, p2) { - var r = new Point(); - - r.y = sign(p2.x - p0.x); - r.x = -sign(p2.y - p0.y); - - return r; - } - - function ddenom(p0, p2) { - var r = dorth_infty(p0, p2); - - return r.y * (p2.x - p0.x) - r.x * (p2.y - p0.y); - } - - function dpara(p0, p1, p2) { - var x1, y1, x2, y2; - - x1 = p1.x - p0.x; - y1 = p1.y - p0.y; - x2 = p2.x - p0.x; - y2 = p2.y - p0.y; - - return x1 * y2 - x2 * y1; - } - - function cprod(p0, p1, p2, p3) { - var x1, y1, x2, y2; - - x1 = p1.x - p0.x; - y1 = p1.y - p0.y; - x2 = p3.x - p2.x; - y2 = p3.y - p2.y; - - return x1 * y2 - x2 * y1; - } - - function iprod(p0, p1, p2) { - var x1, y1, x2, y2; - - x1 = p1.x - p0.x; - y1 = p1.y - p0.y; - x2 = p2.x - p0.x; - y2 = p2.y - p0.y; - - return x1*x2 + y1*y2; - } - - function iprod1(p0, p1, p2, p3) { - var x1, y1, x2, y2; - - x1 = p1.x - p0.x; - y1 = p1.y - p0.y; - x2 = p3.x - p2.x; - y2 = p3.y - p2.y; - - return x1 * x2 + y1 * y2; - } - - function ddist(p, q) { - return Math.sqrt((p.x - q.x) * (p.x - q.x) + (p.y - q.y) * (p.y - q.y)); - } - - function bezier(t, p0, p1, p2, p3) { - var s = 1 - t, res = new Point(); - - res.x = s*s*s*p0.x + 3*(s*s*t)*p1.x + 3*(t*t*s)*p2.x + t*t*t*p3.x; - res.y = s*s*s*p0.y + 3*(s*s*t)*p1.y + 3*(t*t*s)*p2.y + t*t*t*p3.y; - - return res; - } - - function tangent(p0, p1, p2, p3, q0, q1) { - var A, B, C, a, b, c, d, s, r1, r2; - - A = cprod(p0, p1, q0, q1); - B = cprod(p1, p2, q0, q1); - C = cprod(p2, p3, q0, q1); - - a = A - 2 * B + C; - b = -2 * A + 2 * B; - c = A; - - d = b * b - 4 * a * c; - - if (a===0 || d<0) { - return -1.0; - } - - s = Math.sqrt(d); - - r1 = (-b + s) / (2 * a); - r2 = (-b - s) / (2 * a); - - if (r1 >= 0 && r1 <= 1) { - return r1; - } else if (r2 >= 0 && r2 <= 1) { - return r2; - } else { - return -1.0; - } - } - - function calcSums(path) { - var i, x, y; - path.x0 = path.pt[0].x; - path.y0 = path.pt[0].y; - - path.sums = []; - var s = path.sums; - s.push(new Sum(0, 0, 0, 0, 0)); - for(i = 0; i < path.len; i++){ - x = path.pt[i].x - path.x0; - y = path.pt[i].y - path.y0; - s.push(new Sum(s[i].x + x, s[i].y + y, s[i].xy + x * y, - s[i].x2 + x * x, s[i].y2 + y * y)); - } - } - - function calcLon(path) { - - var n = path.len, pt = path.pt, dir, - pivk = new Array(n), - nc = new Array(n), - ct = new Array(4); - path.lon = new Array(n); - - var constraint = [new Point(), new Point()], - cur = new Point(), - off = new Point(), - dk = new Point(), - foundk; - - var i, j, k1, a, b, c, d, k = 0; - for(i = n - 1; i >= 0; i--){ - if (pt[i].x != pt[k].x && pt[i].y != pt[k].y) { - k = i + 1; - } - nc[i] = k; - } - - for (i = n - 1; i >= 0; i--) { - ct[0] = ct[1] = ct[2] = ct[3] = 0; - dir = (3 + 3 * (pt[mod(i + 1, n)].x - pt[i].x) + - (pt[mod(i + 1, n)].y - pt[i].y)) / 2; - ct[dir]++; - - constraint[0].x = 0; - constraint[0].y = 0; - constraint[1].x = 0; - constraint[1].y = 0; - - k = nc[i]; - k1 = i; - while (1) { - foundk = 0; - dir = (3 + 3 * sign(pt[k].x - pt[k1].x) + - sign(pt[k].y - pt[k1].y)) / 2; - ct[dir]++; - - if (ct[0] && ct[1] && ct[2] && ct[3]) { - pivk[i] = k1; - foundk = 1; - break; - } - - cur.x = pt[k].x - pt[i].x; - cur.y = pt[k].y - pt[i].y; - - if (xprod(constraint[0], cur) < 0 || xprod(constraint[1], cur) > 0) { - break; - } - - if (Math.abs(cur.x) <= 1 && Math.abs(cur.y) <= 1) { - - } else { - off.x = cur.x + ((cur.y >= 0 && (cur.y > 0 || cur.x < 0)) ? 1 : -1); - off.y = cur.y + ((cur.x <= 0 && (cur.x < 0 || cur.y < 0)) ? 1 : -1); - if (xprod(constraint[0], off) >= 0) { - constraint[0].x = off.x; - constraint[0].y = off.y; - } - off.x = cur.x + ((cur.y <= 0 && (cur.y < 0 || cur.x < 0)) ? 1 : -1); - off.y = cur.y + ((cur.x >= 0 && (cur.x > 0 || cur.y < 0)) ? 1 : -1); - if (xprod(constraint[1], off) <= 0) { - constraint[1].x = off.x; - constraint[1].y = off.y; - } - } - k1 = k; - k = nc[k1]; - if (!cyclic(k, i, k1)) { - break; - } - } - if (foundk === 0) { - dk.x = sign(pt[k].x-pt[k1].x); - dk.y = sign(pt[k].y-pt[k1].y); - cur.x = pt[k1].x - pt[i].x; - cur.y = pt[k1].y - pt[i].y; - - a = xprod(constraint[0], cur); - b = xprod(constraint[0], dk); - c = xprod(constraint[1], cur); - d = xprod(constraint[1], dk); - - j = 10000000; - if (b < 0) { - j = Math.floor(a / -b); - } - if (d > 0) { - j = Math.min(j, Math.floor(-c / d)); - } - pivk[i] = mod(k1+j,n); - } - } - - j=pivk[n-1]; - path.lon[n-1]=j; - for (i=n-2; i>=0; i--) { - if (cyclic(i+1,pivk[i],j)) { - j=pivk[i]; - } - path.lon[i]=j; - } - - for (i=n-1; cyclic(mod(i+1,n),j,path.lon[i]); i--) { - path.lon[i] = j; - } - } - - function bestPolygon(path) { - - function penalty3(path, i, j) { - - var n = path.len, pt = path.pt, sums = path.sums; - var x, y, xy, x2, y2, - k, a, b, c, s, - px, py, ex, ey, - r = 0; - if (j>=n) { - j -= n; - r = 1; - } - - if (r === 0) { - x = sums[j+1].x - sums[i].x; - y = sums[j+1].y - sums[i].y; - x2 = sums[j+1].x2 - sums[i].x2; - xy = sums[j+1].xy - sums[i].xy; - y2 = sums[j+1].y2 - sums[i].y2; - k = j+1 - i; - } else { - x = sums[j+1].x - sums[i].x + sums[n].x; - y = sums[j+1].y - sums[i].y + sums[n].y; - x2 = sums[j+1].x2 - sums[i].x2 + sums[n].x2; - xy = sums[j+1].xy - sums[i].xy + sums[n].xy; - y2 = sums[j+1].y2 - sums[i].y2 + sums[n].y2; - k = j+1 - i + n; - } - - px = (pt[i].x + pt[j].x) / 2.0 - pt[0].x; - py = (pt[i].y + pt[j].y) / 2.0 - pt[0].y; - ey = (pt[j].x - pt[i].x); - ex = -(pt[j].y - pt[i].y); - - a = ((x2 - 2*x*px) / k + px*px); - b = ((xy - x*py - y*px) / k + px*py); - c = ((y2 - 2*y*py) / k + py*py); - - s = ex*ex*a + 2*ex*ey*b + ey*ey*c; - - return Math.sqrt(s); - } - - var i, j, m, k, - n = path.len, - pen = new Array(n + 1), - prev = new Array(n + 1), - clip0 = new Array(n), - clip1 = new Array(n + 1), - seg0 = new Array (n + 1), - seg1 = new Array(n + 1), - thispen, best, c; - - for (i=0; i0; j--) { - seg1[j] = i; - i = clip1[i]; - } - seg1[0] = 0; - - pen[0]=0; - for (j=1; j<=m; j++) { - for (i=seg1[j]; i<=seg0[j]; i++) { - best = -1; - for (k=seg0[j-1]; k>=clip1[i]; k--) { - thispen = penalty3(path, k, i) + pen[k]; - if (best < 0 || thispen < best) { - prev[i] = k; - best = thispen; - } - } - pen[i] = best; - } - } - path.m = m; - path.po = new Array(m); - - for (i=n, j=m-1; i>0; j--) { - i = prev[i]; - path.po[j] = i; - } - } - - function adjustVertices(path) { - - function pointslope(path, i, j, ctr, dir) { - - var n = path.len, sums = path.sums, - x, y, x2, xy, y2, - k, a, b, c, lambda2, l, r=0; - - while (j>=n) { - j-=n; - r+=1; - } - while (i>=n) { - i-=n; - r-=1; - } - while (j<0) { - j+=n; - r-=1; - } - while (i<0) { - i+=n; - r+=1; - } - - x = sums[j+1].x-sums[i].x+r*sums[n].x; - y = sums[j+1].y-sums[i].y+r*sums[n].y; - x2 = sums[j+1].x2-sums[i].x2+r*sums[n].x2; - xy = sums[j+1].xy-sums[i].xy+r*sums[n].xy; - y2 = sums[j+1].y2-sums[i].y2+r*sums[n].y2; - k = j+1-i+r*n; - - ctr.x = x/k; - ctr.y = y/k; - - a = (x2-x*x/k)/k; - b = (xy-x*y/k)/k; - c = (y2-y*y/k)/k; - - lambda2 = (a+c+Math.sqrt((a-c)*(a-c)+4*b*b))/2; - - a -= lambda2; - c -= lambda2; - - if (Math.abs(a) >= Math.abs(c)) { - l = Math.sqrt(a*a+b*b); - if (l!==0) { - dir.x = -b/l; - dir.y = a/l; - } - } else { - l = Math.sqrt(c*c+b*b); - if (l!==0) { - dir.x = -c/l; - dir.y = b/l; - } - } - if (l===0) { - dir.x = dir.y = 0; - } - } - - var m = path.m, po = path.po, n = path.len, pt = path.pt, - x0 = path.x0, y0 = path.y0, - ctr = new Array(m), dir = new Array(m), - q = new Array(m), - v = new Array(3), d, i, j, k, l, - s = new Point(); - - path.curve = new Curve(m); - - for (i=0; iQ.at(1, 1)) { - v[0] = -Q.at(0, 1); - v[1] = Q.at(0, 0); - } else if (Q.at(1, 1)) { - v[0] = -Q.at(1, 1); - v[1] = Q.at(1, 0); - } else { - v[0] = 1; - v[1] = 0; - } - d = v[0] * v[0] + v[1] * v[1]; - v[2] = - v[1] * s.y - v[0] * s.x; - for (l=0; l<3; l++) { - for (k=0; k<3; k++) { - Q.data[l * 3 + k] += v[l] * v[k] / d; - } - } - } - dx = Math.abs(w.x-s.x); - dy = Math.abs(w.y-s.y); - if (dx <= 0.5 && dy <= 0.5) { - path.curve.vertex[i] = new Point(w.x+x0, w.y+y0); - continue; - } - - min = quadform(Q, s); - xmin = s.x; - ymin = s.y; - - if (Q.at(0, 0) !== 0.0) { - for (z=0; z<2; z++) { - w.y = s.y-0.5+z; - w.x = - (Q.at(0, 1) * w.y + Q.at(0, 2)) / Q.at(0, 0); - dx = Math.abs(w.x-s.x); - cand = quadform(Q, w); - if (dx <= 0.5 && cand < min) { - min = cand; - xmin = w.x; - ymin = w.y; - } - } - } - - if (Q.at(1, 1) !== 0.0) { - for (z=0; z<2; z++) { - w.x = s.x-0.5+z; - w.y = - (Q.at(1, 0) * w.x + Q.at(1, 2)) / Q.at(1, 1); - dy = Math.abs(w.y-s.y); - cand = quadform(Q, w); - if (dy <= 0.5 && cand < min) { - min = cand; - xmin = w.x; - ymin = w.y; - } - } - } - - for (l=0; l<2; l++) { - for (k=0; k<2; k++) { - w.x = s.x-0.5+l; - w.y = s.y-0.5+k; - cand = quadform(Q, w); - if (cand < min) { - min = cand; - xmin = w.x; - ymin = w.y; - } - } - } - - path.curve.vertex[i] = new Point(xmin + x0, ymin + y0); - } - } - - function reverse(path) { - var curve = path.curve, m = curve.n, v = curve.vertex, i, j, tmp; - - for (i=0, j=m-1; i1 ? (1 - 1.0/dd) : 0; - alpha = alpha / 0.75; - } else { - alpha = 4/3.0; - } - curve.alpha0[j] = alpha; - - if (alpha >= info.alphamax) { - curve.tag[j] = "CORNER"; - curve.c[3 * j + 1] = curve.vertex[j]; - curve.c[3 * j + 2] = p4; - } else { - if (alpha < 0.55) { - alpha = 0.55; - } else if (alpha > 1) { - alpha = 1; - } - p2 = interval(0.5+0.5*alpha, curve.vertex[i], curve.vertex[j]); - p3 = interval(0.5+0.5*alpha, curve.vertex[k], curve.vertex[j]); - curve.tag[j] = "CURVE"; - curve.c[3 * j + 0] = p2; - curve.c[3 * j + 1] = p3; - curve.c[3 * j + 2] = p4; - } - curve.alpha[j] = alpha; - curve.beta[j] = 0.5; - } - curve.alphacurve = 1; - } - - function optiCurve(path) { - function Opti(){ - this.pen = 0; - this.c = [new Point(), new Point()]; - this.t = 0; - this.s = 0; - this.alpha = 0; - } - - function opti_penalty(path, i, j, res, opttolerance, convc, areac) { - var m = path.curve.n, curve = path.curve, vertex = curve.vertex, - k, k1, k2, conv, i1, - area, alpha, d, d1, d2, - p0, p1, p2, p3, pt, - A, R, A1, A2, A3, A4, - s, t; - - if (i==j) { - return 1; - } - - k = i; - i1 = mod(i+1, m); - k1 = mod(k+1, m); - conv = convc[k1]; - if (conv === 0) { - return 1; - } - d = ddist(vertex[i], vertex[i1]); - for (k=k1; k!=j; k=k1) { - k1 = mod(k+1, m); - k2 = mod(k+2, m); - if (convc[k1] != conv) { - return 1; - } - if (sign(cprod(vertex[i], vertex[i1], vertex[k1], vertex[k2])) != - conv) { - return 1; - } - if (iprod1(vertex[i], vertex[i1], vertex[k1], vertex[k2]) < - d * ddist(vertex[k1], vertex[k2]) * -0.999847695156) { - return 1; - } - } - - p0 = curve.c[mod(i,m) * 3 + 2].copy(); - p1 = vertex[mod(i+1,m)].copy(); - p2 = vertex[mod(j,m)].copy(); - p3 = curve.c[mod(j,m) * 3 + 2].copy(); - - area = areac[j] - areac[i]; - area -= dpara(vertex[0], curve.c[i * 3 + 2], curve.c[j * 3 + 2])/2; - if (i>=j) { - area += areac[m]; - } - - A1 = dpara(p0, p1, p2); - A2 = dpara(p0, p1, p3); - A3 = dpara(p0, p2, p3); - - A4 = A1+A3-A2; - - if (A2 == A1) { - return 1; - } - - t = A3/(A3-A4); - s = A2/(A2-A1); - A = A2 * t / 2.0; - - if (A === 0.0) { - return 1; - } - - R = area / A; - alpha = 2 - Math.sqrt(4 - R / 0.3); - - res.c[0] = interval(t * alpha, p0, p1); - res.c[1] = interval(s * alpha, p3, p2); - res.alpha = alpha; - res.t = t; - res.s = s; - - p1 = res.c[0].copy(); - p2 = res.c[1].copy(); - - res.pen = 0; - - for (k=mod(i+1,m); k!=j; k=k1) { - k1 = mod(k+1,m); - t = tangent(p0, p1, p2, p3, vertex[k], vertex[k1]); - if (t<-0.5) { - return 1; - } - pt = bezier(t, p0, p1, p2, p3); - d = ddist(vertex[k], vertex[k1]); - if (d === 0.0) { - return 1; - } - d1 = dpara(vertex[k], vertex[k1], pt) / d; - if (Math.abs(d1) > opttolerance) { - return 1; - } - if (iprod(vertex[k], vertex[k1], pt) < 0 || - iprod(vertex[k1], vertex[k], pt) < 0) { - return 1; - } - res.pen += d1 * d1; - } - - for (k=i; k!=j; k=k1) { - k1 = mod(k+1,m); - t = tangent(p0, p1, p2, p3, curve.c[k * 3 + 2], curve.c[k1 * 3 + 2]); - if (t<-0.5) { - return 1; - } - pt = bezier(t, p0, p1, p2, p3); - d = ddist(curve.c[k * 3 + 2], curve.c[k1 * 3 + 2]); - if (d === 0.0) { - return 1; - } - d1 = dpara(curve.c[k * 3 + 2], curve.c[k1 * 3 + 2], pt) / d; - d2 = dpara(curve.c[k * 3 + 2], curve.c[k1 * 3 + 2], vertex[k1]) / d; - d2 *= 0.75 * curve.alpha[k1]; - if (d2 < 0) { - d1 = -d1; - d2 = -d2; - } - if (d1 < d2 - opttolerance) { - return 1; - } - if (d1 < d2) { - res.pen += (d1 - d2) * (d1 - d2); - } - } - - return 0; - } - - var curve = path.curve, m = curve.n, vert = curve.vertex, - pt = new Array(m + 1), - pen = new Array(m + 1), - len = new Array(m + 1), - opt = new Array(m + 1), - om, i,j,r, - o = new Opti(), p0, - i1, area, alpha, ocurve, - s, t; - - var convc = new Array(m), areac = new Array(m + 1); - - for (i=0; i=0; i--) { - r = opti_penalty(path, i, mod(j,m), o, info.opttolerance, convc, - areac); - if (r) { - break; - } - if (len[j] > len[i]+1 || - (len[j] == len[i]+1 && pen[j] > pen[i] + o.pen)) { - pt[j] = i; - pen[j] = pen[i] + o.pen; - len[j] = len[i] + 1; - opt[j] = o; - o = new Opti(); - } - } - } - om = len[m]; - ocurve = new Curve(om); - s = new Array(om); - t = new Array(om); - - j = m; - for (i=om-1; i>=0; i--) { - if (pt[j]==j-1) { - ocurve.tag[i] = curve.tag[mod(j,m)]; - ocurve.c[i * 3 + 0] = curve.c[mod(j,m) * 3 + 0]; - ocurve.c[i * 3 + 1] = curve.c[mod(j,m) * 3 + 1]; - ocurve.c[i * 3 + 2] = curve.c[mod(j,m) * 3 + 2]; - ocurve.vertex[i] = curve.vertex[mod(j,m)]; - ocurve.alpha[i] = curve.alpha[mod(j,m)]; - ocurve.alpha0[i] = curve.alpha0[mod(j,m)]; - ocurve.beta[i] = curve.beta[mod(j,m)]; - s[i] = t[i] = 1.0; - } else { - ocurve.tag[i] = "CURVE"; - ocurve.c[i * 3 + 0] = opt[j].c[0]; - ocurve.c[i * 3 + 1] = opt[j].c[1]; - ocurve.c[i * 3 + 2] = curve.c[mod(j,m) * 3 + 2]; - ocurve.vertex[i] = interval(opt[j].s, curve.c[mod(j,m) * 3 + 2], - vert[mod(j,m)]); - ocurve.alpha[i] = opt[j].alpha; - ocurve.alpha0[i] = opt[j].alpha; - s[i] = opt[j].s; - t[i] = opt[j].t; - } - j = pt[j]; - } - - for (i=0; i'; - svg += ''; - return svg; - } - - return{ - loadImageFromFile: loadImageFromFile, - loadImageFromUrl: loadImageFromUrl, - loadImageFromCanvas: loadImageFromCanvas, - setParameter: setParameter, - process: process, - getSVG: getSVG, - img: imgElement - }; -})(); From c43dcb8813c308db2f5d64c0136e94b8ebfa222a Mon Sep 17 00:00:00 2001 From: casperlamboo Date: Wed, 1 Mar 2017 11:32:06 +0100 Subject: [PATCH 17/29] update package.json --- .gitignore | 2 ++ package.json | 30 ++++++++++++++++++++++++------ src/index.js | 4 +--- 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index ce4f912..8c0236d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ jspm_packages +node_modules +lib diff --git a/package.json b/package.json index 46dbd21..279cb40 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { - "name": "potrace", - "version": "1.0.0", - "description": "potrace =======", - "main": "src/index.js", + "name": "potrace-js", + "version": "0.0.1", + "description": "Traces bitmap images to vector paths", + "main": "lib/index.js", "jspm": { "name": "potrace", "main": "potrace.js", @@ -11,16 +11,34 @@ } }, "scripts": { + "prepublish": "npm run build", + "build": "babel src --out-dir lib", "test": "echo \"Error: no test specified\" && exit 1" }, + "babel": { + "presets": [ + "es2017" + ], + "plugins": [ + "add-module-exports" + ] + }, + "format": "esm", "repository": { "type": "git", "url": "git+https://github.com/casperlamboo/potrace.git" }, - "author": "", + "author": "Casper Lamboo", "license": "ISC", "bugs": { "url": "https://github.com/casperlamboo/potrace/issues" }, - "homepage": "https://github.com/casperlamboo/potrace#readme" + "homepage": "https://github.com/casperlamboo/potrace#readme", + "devDependencies": { + "babel-cli": "^6.23.0", + "babel-core": "^6.23.1", + "babel-plugin-add-module-exports": "^0.2.1", + "babel-preset-es2017": "^6.22.0", + "jspm": "^0.17.0-beta.40" + } } diff --git a/src/index.js b/src/index.js index 59fbe4f..0da1498 100644 --- a/src/index.js +++ b/src/index.js @@ -28,9 +28,7 @@ export function traceImage(image, options) { return traceCanvas(canvas, options); } -export function traceCanvas(canvas, options = {}) { - options = { ...options, ...OPTIONS }; - +export function traceCanvas(canvas, options = OPTIONS) { const bitmap = createBitmap(canvas); const pathList = bitmapToPathList(bitmap, options); processPath(pathList, options); From e2a87aaaaf6f5105e4613bfa91e6299995b01801 Mon Sep 17 00:00:00 2001 From: casperlamboo Date: Wed, 1 Mar 2017 11:41:56 +0100 Subject: [PATCH 18/29] implement getPaths --- README.md | 8 ++++++ src/getPaths.js | 75 +++++++++++++++++++++++++++++++++++++++++++++++++ src/index.js | 3 +- 3 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 src/getPaths.js diff --git a/README.md b/README.md index 8068aa3..6cdb4b9 100644 --- a/README.md +++ b/README.md @@ -89,3 +89,11 @@ Converts trace result to svg. ```javascript svg: String = POTRACE.getSVG([...Path]) ``` + +**POTRACE.getPaths** + +Converts trace result to readable paths. + +```javascript +svg: String = POTRACE.getPaths([...Path]) +``` diff --git a/src/getPaths.js b/src/getPaths.js new file mode 100644 index 0000000..4d1283c --- /dev/null +++ b/src/getPaths.js @@ -0,0 +1,75 @@ +export default function getPaths(pathlist, size) { + + function path(curve) { + function bezier(i) { + var x1 = curve.c[i * 3 + 0].x * size; + var y1 = curve.c[i * 3 + 0].x * size; + var x2 = curve.c[i * 3 + 1].x * size; + var y2 = curve.c[i * 3 + 1].y * size; + var x = curve.c[i * 3 + 2].x * size; + var y = curve.c[i * 3 + 2].y * size; + + return { + type: "CURVE", + x1: x1, + y1: y1, + x2: x2, + y2: y2, + x: x, + y: y, + } + } + + function segment(i) { + var x1 = curve.c[i * 3 + 1].x * size; + var y1 = curve.c[i * 3 + 1].y * size; + var x2 = curve.c[i * 3 + 2].x * size; + var y2 = curve.c[i * 3 + 2].y * size; + + return [ + { + type: "POINT", + x: x1, + y: y1 + }, { + type: "POINT", + x: x2, + y: y2 + } + ]; + } + var p = []; + + var n = curve.n, i, s; + + var x = curve.c[(n - 1) * 3 + 2].x * size; + var y = curve.c[(n - 1) * 3 + 2].y * size; + + p.push({ + type: "POINT", + x: x, + y: y + }); + + for (i = 0; i < n; i++) { + if (curve.tag[i] === "CURVE") { + p.push(bezier(i)); + } else if (curve.tag[i] === "CORNER") { + s = segment(i); + p.push(s[0], s[1]); + } + } + //p += + return p; + } + + var len = pathlist.length, c, i; + + var paths = []; + for (i = 0; i < len; i++) { + c = pathlist[i].curve; + paths.push(path(c)); + } + + return paths; +} diff --git a/src/index.js b/src/index.js index 0da1498..42cff84 100644 --- a/src/index.js +++ b/src/index.js @@ -2,6 +2,7 @@ import { loadImage, createBitmap } from './utils.js'; import bitmapToPathList from './bitmapToPathList.js'; import processPath from './processPath.js'; import getSVG from './getSVG.js'; +import getPaths from './getPaths.js'; const OPTIONS = { turnpolicy: 'right', @@ -36,4 +37,4 @@ export function traceCanvas(canvas, options = OPTIONS) { return pathList; } -export { getSVG }; +export { getSVG, getPaths }; From 73d78351c19b78299483180b3fa9ad123422648e Mon Sep 17 00:00:00 2001 From: casperlamboo Date: Wed, 1 Mar 2017 11:42:33 +0100 Subject: [PATCH 19/29] remove size argument --- src/getPaths.js | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/getPaths.js b/src/getPaths.js index 4d1283c..19f6f9b 100644 --- a/src/getPaths.js +++ b/src/getPaths.js @@ -1,13 +1,13 @@ -export default function getPaths(pathlist, size) { +export default function getPaths(pathlist) { function path(curve) { function bezier(i) { - var x1 = curve.c[i * 3 + 0].x * size; - var y1 = curve.c[i * 3 + 0].x * size; - var x2 = curve.c[i * 3 + 1].x * size; - var y2 = curve.c[i * 3 + 1].y * size; - var x = curve.c[i * 3 + 2].x * size; - var y = curve.c[i * 3 + 2].y * size; + var x1 = curve.c[i * 3 + 0].x; + var y1 = curve.c[i * 3 + 0].x; + var x2 = curve.c[i * 3 + 1].x; + var y2 = curve.c[i * 3 + 1].y; + var x = curve.c[i * 3 + 2].x; + var y = curve.c[i * 3 + 2].y; return { type: "CURVE", @@ -21,10 +21,10 @@ export default function getPaths(pathlist, size) { } function segment(i) { - var x1 = curve.c[i * 3 + 1].x * size; - var y1 = curve.c[i * 3 + 1].y * size; - var x2 = curve.c[i * 3 + 2].x * size; - var y2 = curve.c[i * 3 + 2].y * size; + var x1 = curve.c[i * 3 + 1].x; + var y1 = curve.c[i * 3 + 1].y; + var x2 = curve.c[i * 3 + 2].x; + var y2 = curve.c[i * 3 + 2].y; return [ { @@ -42,8 +42,8 @@ export default function getPaths(pathlist, size) { var n = curve.n, i, s; - var x = curve.c[(n - 1) * 3 + 2].x * size; - var y = curve.c[(n - 1) * 3 + 2].y * size; + var x = curve.c[(n - 1) * 3 + 2].x; + var y = curve.c[(n - 1) * 3 + 2].y; p.push({ type: "POINT", From 485ad8f71e15a70163b19894c1fb5f0edbd64a4b Mon Sep 17 00:00:00 2001 From: casperlamboo Date: Wed, 1 Mar 2017 11:44:10 +0100 Subject: [PATCH 20/29] added size --- src/getPaths.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/getPaths.js b/src/getPaths.js index 19f6f9b..e287386 100644 --- a/src/getPaths.js +++ b/src/getPaths.js @@ -63,7 +63,7 @@ export default function getPaths(pathlist) { return p; } - var len = pathlist.length, c, i; + var w = bm.w, h = bm.h, len = pathlist.length, c, i; var paths = []; for (i = 0; i < len; i++) { @@ -71,5 +71,9 @@ export default function getPaths(pathlist) { paths.push(path(c)); } - return paths; + return { + width: w, + height: h, + paths: paths + } } From 3ad5c680bf2854f74d4e313990af6d71810154d6 Mon Sep 17 00:00:00 2001 From: casperlamboo Date: Wed, 1 Mar 2017 11:44:49 +0100 Subject: [PATCH 21/29] fix y-property in bezier path --- src/getPaths.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/getPaths.js b/src/getPaths.js index e287386..326d8f5 100644 --- a/src/getPaths.js +++ b/src/getPaths.js @@ -3,7 +3,7 @@ export default function getPaths(pathlist) { function path(curve) { function bezier(i) { var x1 = curve.c[i * 3 + 0].x; - var y1 = curve.c[i * 3 + 0].x; + var y1 = curve.c[i * 3 + 0].y; var x2 = curve.c[i * 3 + 1].x; var y2 = curve.c[i * 3 + 1].y; var x = curve.c[i * 3 + 2].x; From f5c76751df66b3e9e771fca1a41adc830b5f608b Mon Sep 17 00:00:00 2001 From: casperlamboo Date: Wed, 1 Mar 2017 11:45:08 +0100 Subject: [PATCH 22/29] version bumb --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 279cb40..f509611 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "potrace-js", - "version": "0.0.1", + "version": "0.0.2", "description": "Traces bitmap images to vector paths", "main": "lib/index.js", "jspm": { From 21e22594682a7c1cdf5e0d2069d765dc50b186fb Mon Sep 17 00:00:00 2001 From: casperlamboo Date: Wed, 1 Mar 2017 11:58:37 +0100 Subject: [PATCH 23/29] update package.json --- package.json | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index f509611..f8acfb3 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,14 @@ { "name": "potrace-js", - "version": "0.0.2", + "version": "0.0.3", "description": "Traces bitmap images to vector paths", "main": "lib/index.js", "jspm": { "name": "potrace", - "main": "potrace.js", + "main": "index.js", + "directories": { + "lib": "src" + }, "devDependencies": { "plugin-babel": "npm:systemjs-plugin-babel@^0.0.21" } @@ -17,7 +20,7 @@ }, "babel": { "presets": [ - "es2017" + "es2015" ], "plugins": [ "add-module-exports" @@ -38,7 +41,7 @@ "babel-cli": "^6.23.0", "babel-core": "^6.23.1", "babel-plugin-add-module-exports": "^0.2.1", - "babel-preset-es2017": "^6.22.0", + "babel-preset-es2015": "^6.22.0", "jspm": "^0.17.0-beta.40" } } From ba839fbc0042060fc2550055a06c4590a8715a5e Mon Sep 17 00:00:00 2001 From: casperlamboo Date: Wed, 1 Mar 2017 12:16:56 +0100 Subject: [PATCH 24/29] fix getPaths --- src/getPaths.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/getPaths.js b/src/getPaths.js index 326d8f5..a24ea60 100644 --- a/src/getPaths.js +++ b/src/getPaths.js @@ -63,7 +63,7 @@ export default function getPaths(pathlist) { return p; } - var w = bm.w, h = bm.h, len = pathlist.length, c, i; + var len = pathlist.length, c, i; var paths = []; for (i = 0; i < len; i++) { @@ -71,9 +71,5 @@ export default function getPaths(pathlist) { paths.push(path(c)); } - return { - width: w, - height: h, - paths: paths - } + return paths; } From dbe23dbe15665309a0fc44fcc6ce13f96036d918 Mon Sep 17 00:00:00 2001 From: casperlamboo Date: Thu, 2 Mar 2017 14:37:21 +0100 Subject: [PATCH 25/29] Fix capital character in file name --- src/{bitmapToPathlist.js => _bitmapToPathList.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{bitmapToPathlist.js => _bitmapToPathList.js} (100%) diff --git a/src/bitmapToPathlist.js b/src/_bitmapToPathList.js similarity index 100% rename from src/bitmapToPathlist.js rename to src/_bitmapToPathList.js From 1dcf54d5eec85995a163d22f5b2ab0d00c02f724 Mon Sep 17 00:00:00 2001 From: casperlamboo Date: Thu, 2 Mar 2017 14:37:28 +0100 Subject: [PATCH 26/29] Fix capital character in file name --- src/{_bitmapToPathList.js => bitmapToPathList.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{_bitmapToPathList.js => bitmapToPathList.js} (100%) diff --git a/src/_bitmapToPathList.js b/src/bitmapToPathList.js similarity index 100% rename from src/_bitmapToPathList.js rename to src/bitmapToPathList.js From 680f206233683615c5820bc6f4d5ac8919d4c552 Mon Sep 17 00:00:00 2001 From: casperlamboo Date: Thu, 2 Mar 2017 14:37:44 +0100 Subject: [PATCH 27/29] version bumb --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f8acfb3..4c293ac 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "potrace-js", - "version": "0.0.3", + "version": "0.0.4", "description": "Traces bitmap images to vector paths", "main": "lib/index.js", "jspm": { From 69a9b2e9e86e865b5d83388232b205898725f4ee Mon Sep 17 00:00:00 2001 From: casperlamboo Date: Fri, 17 Mar 2017 15:49:53 +0100 Subject: [PATCH 28/29] add trace bitmap --- README.md | 11 +++++++++++ src/index.js | 10 ++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6cdb4b9..0d4dfde 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,17 @@ Traces a given canvas. - canvas: canvas containing the image - options: trace options +**POTRACE.bitmap** + +Traces a given bitmap. + + ```javascript + [...Path] = POTRACE.traceBitmap(bitmap: POTRACE.Bitmap, [ options: Object ]) + ``` + - bitmap: bitmap containing image info (1 and 0 values) + - options: trace options + + **POTRACE.getSVG** Converts trace result to svg. diff --git a/src/index.js b/src/index.js index 42cff84..f4cb83a 100644 --- a/src/index.js +++ b/src/index.js @@ -3,6 +3,7 @@ import bitmapToPathList from './bitmapToPathList.js'; import processPath from './processPath.js'; import getSVG from './getSVG.js'; import getPaths from './getPaths.js'; +import Bitmap from './Bitmap.js'; const OPTIONS = { turnpolicy: 'right', @@ -29,12 +30,17 @@ export function traceImage(image, options) { return traceCanvas(canvas, options); } -export function traceCanvas(canvas, options = OPTIONS) { +export function traceCanvas(canvas, options) { const bitmap = createBitmap(canvas); + + return traceBitmap(bitmap, options); +} + +export function traceBitmap(bitmap, options = OPTIONS) { const pathList = bitmapToPathList(bitmap, options); processPath(pathList, options); return pathList; } -export { getSVG, getPaths }; +export { getSVG, getPaths, Bitmap }; From adfc3ee55504f1e014464716d7155d98eaf5616b Mon Sep 17 00:00:00 2001 From: casperlamboo Date: Fri, 17 Mar 2017 15:50:02 +0100 Subject: [PATCH 29/29] bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4c293ac..5a3537f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "potrace-js", - "version": "0.0.4", + "version": "0.0.5", "description": "Traces bitmap images to vector paths", "main": "lib/index.js", "jspm": {