diff --git a/index.html b/index.html index 6411f12c874a51f49cb70ea670153b9f94e2cda1..c6b8a1ed90c7882239c58953f98722bc57922d0c 100644 --- a/index.html +++ b/index.html @@ -2,7 +2,7 @@ - BezLinApp + Hello World! diff --git a/js/approximation/approximator.js b/js/approximation/approximator.js index 0fd97d1631dfc4fb9a193a6c3e7d916fd0895b2b..76de7fcd957cc20c83732f8c567c08de13546cc4 100644 --- a/js/approximation/approximator.js +++ b/js/approximation/approximator.js @@ -1,9 +1,16 @@ var method = Approximator.prototype; // constructor -function Approximator(paths, precision) { - this._paths = paths; +function Approximator(svgParser, precision) { + this._parser = svgParser; this._precision = precision; + // precision can either be the precisionMap or the overall precision-value (if no heatmap was provided) + this._heatmapProvided = ((typeof precision) === "object") ? true : false; + // Defaults for scale and offset + this._scaleX = 1; + this._scaleY = 1; + this._offsetX = 0; + this._offsetY = 0; } /** @@ -62,7 +69,8 @@ method.buildCurveCommands = function(curves) { var curve = curves[i]; var d = this.getDistance(curve[0][0], curve[0][1], curve[3][0], curve[3][1]); - var p = this._precision.getPrecision(curve[0][0], curve[0][1]); + // get the precision, mind the offset and the scale for the heatmap + var p = (this._heatmapProvided) ? this._precision.getPrecision((curve[0][0]-this._offsetX)/this._scaleX, (curve[0][1]-this._offsetY)/this._scaleY) : this._precision; if (d <= p) { // transform into "L"-command from p0 -> p3 commands.push(["L", curve[3][0], curve[3][1]]); @@ -172,11 +180,26 @@ method.approximatePath = function(pathNo) { Starts the whole approximation-process */ method.approximateData = function(callback) { - for (var i = 0; i < this._paths.length; i++) { - var retPath = this.approximatePath(i); - this._paths[i] = retPath; - } - callback(null, this._paths); + var self = this; + + this._parser.getSVGPaths(function(err, result) { + self._paths = result; + if (self._heatmapProvided) { + // calculate the bounding box first! + self._parser.calculateBoundingBox(); + var scale = self._parser.getScale(self._precision.getWidth(), self._precision.getHeight()); + self._scaleX = scale[0]; + self._scaleY = scale[1]; + var offset = self._parser.getOffset(); + self._offsetX = offset[0]; + self._offsetY = offset[1]; + } + for (var i = 0; i < self._paths.length; i++) { + var retPath = self.approximatePath(i); + self._paths[i] = retPath; + } + callback(null, self._paths); + }) }; module.exports = Approximator; diff --git a/js/approximation/precisionMap.js b/js/approximation/precisionMap.js index 668d3318b40ae0cf16093360b3f70520972c13d2..cbf4cb7d2cb42a8ffd398ef57401bfd1fd09044e 100644 --- a/js/approximation/precisionMap.js +++ b/js/approximation/precisionMap.js @@ -2,32 +2,26 @@ var method = PrecisionMap.prototype; // constructor function PrecisionMap(data, min, max) { - if ((typeof data) == 'object') { - this._data = data; - - /* - FYI - NDArray Format: ((x),(y),(r,g,b,a)) - Access pixel with ....get(x,y,c) - e.g.: - console.log("Red", pixels.get(0,0,0), pixels.get(0,0,1), pixels.get(0,0,2), pixels.get(0,0,3)); - console.log("Blue", pixels.get(1,0,0), pixels.get(1,0,1), pixels.get(1,0,2), pixels.get(1,0,3)); - console.log("Black", pixels.get(2,0,0), pixels.get(2,0,1), pixels.get(2,0,2), pixels.get(2,0,3)); - */ - - // get the dimensions - var shape = this._data.shape.slice(); - this._w = shape[0]; - this._h = shape[1]; - this._c = shape[2]; - - this._min = min; - this._max = max; - this._diff = max-min; - } else { - // no heatmap provided, we only have the "max"-value - this._data = null; - this._max = data; - } + this._data = data; + + /* + FYI - NDArray Format: ((x),(y),(r,g,b,a)) + Access pixel with ....get(x,y,c) + e.g.: + console.log("Red", pixels.get(0,0,0), pixels.get(0,0,1), pixels.get(0,0,2), pixels.get(0,0,3)); + console.log("Blue", pixels.get(1,0,0), pixels.get(1,0,1), pixels.get(1,0,2), pixels.get(1,0,3)); + console.log("Black", pixels.get(2,0,0), pixels.get(2,0,1), pixels.get(2,0,2), pixels.get(2,0,3)); + */ + + // get the dimensions + var shape = this._data.shape.slice(); + this._w = shape[0]; + this._h = shape[1]; + this._c = shape[2]; + + this._min = parseFloat(min); + this._max = parseFloat(max); + this._diff = min-max; } /** @@ -35,13 +29,18 @@ function PrecisionMap(data, min, max) { x and y are floored in the method. */ method.getPrecision = function(x, y) { - if (this._data == null) { - return this._max; - } // Note: (0,0) in SVG is "bottom left" and (0,0) in PNG is "top left" - var value = this._data.get(Math.floor(x), Math.floor(this._h - y), 0); - var p = this._max + this._diff * (x/255); + var value = this._data.get(Math.floor(x), Math.floor(y), 0); + var p = this._max + this._diff * (value/255); return p; }; +method.getHeight = function() { + return this._h; +} + +method.getWidth = function() { + return this._w; +} + module.exports = PrecisionMap; diff --git a/js/parser/svg-parser.js b/js/parser/svg-parser.js index 5a52457e715cbcc22145bb0e835f5be3ded59620..82f01955c36b1e8f169ef60a70ab282e35378dc4 100644 --- a/js/parser/svg-parser.js +++ b/js/parser/svg-parser.js @@ -1,12 +1,181 @@ var method = SVGParser.prototype; // empty constructor -function SVGParser() { +function SVGParser(path) { + this._path = path; this._fs = require('fs'); this._xml2js = require('xml2js'); this._parsedData = null; } +method.getHeight = function() { + return this._parsedData.svg.$.height; +} + +method.getBezierFunctionValue = function(t, p0, p1, p2, p3) { + return (p0*Math.pow(1-t,3) + 3 * p1 * t * Math.pow(1-t,2) + 3 * p2 * (1-t) * Math.pow(t,2) + p3 * Math.pow(t, 3)); +} + +method.getMaxOfArray = function(numArray) { + return Math.max.apply(null, numArray); +} + +method.getMinOfArray = function(numArray) { + return Math.min.apply(null, numArray); +} + +method.getMinMaxOfCurve = function(p0, p1, p2, p3) { + // for x + var a_x = 3*(-p0[0]+3*p1[0]-3*p2[0]+p3[0]); + var b_x = 6*(p0[0]-2*p1[0]+p2[0]); + var c_x = 3*(p1[0]-p0[0]); + + var t_x1 = (-b_x + Math.sqrt(Math.pow(b_x,2)-4*a_x*c_x))/(2*a_x); + var t_x2 = (-b_x - Math.sqrt(Math.pow(b_x,2)-4*a_x*c_x))/(2*a_x); + + var comps_x = []; + // value for t=0 + comps_x.push(this.getBezierFunctionValue(0, p0[0], p1[0], p2[0], p3[0])); + // value for t=1 + comps_x.push(this.getBezierFunctionValue(1, p0[0], p1[0], p2[0], p3[0])); + + if (t_x1 >= 0 && t_x1 <= 1) { + comps_x.push(this.getBezierFunctionValue(t_x1, p0[0], p1[0], p2[0], p3[0])); + } + if (t_x2 >= 0 && t_x2 <= 1) { + comps_x.push(this.getBezierFunctionValue(t_x2, p0[0], p1[0], p2[0], p3[0])); + } + + var xMin = this.getMinOfArray(comps_x); + var xMax = this.getMaxOfArray(comps_x); + + // for y + var y_p0 = this.getHeight()-p0[1], y_p1 = this.getHeight()-p1[1], y_p2 = this.getHeight()-p2[1], y_p3 = this.getHeight()-p3[1]; + + var a_y = 3*(-y_p0+3*y_p1-3*y_p2+y_p3); + var b_y = 6*(y_p0-2*y_p1+y_p2); + var c_y = 3*(y_p1-y_p0); + + var t_y1 = (-b_y + Math.sqrt(Math.pow(b_y,2)-4*a_y*c_y))/(2*a_y); + var t_y2 = (-b_y - Math.sqrt(Math.pow(b_y,2)-4*a_y*c_y))/(2*a_y); + + var comps_y = []; + // value for t=0 + comps_y.push(this.getBezierFunctionValue(0, y_p0, y_p1, y_p2, y_p3)); + // value for t=1 + comps_y.push(this.getBezierFunctionValue(1, y_p0, y_p1, y_p2, y_p3)); + + if (t_y1 >= 0 && t_y1 <= 1) { + comps_y.push(this.getBezierFunctionValue(t_y1, y_p0, y_p1, y_p2, y_p3)); + } + if (t_y2 >= 0 && t_y2 <= 1) { + comps_y.push(this.getBezierFunctionValue(t_y2, y_p0, y_p1, y_p2, y_p3)); + } + + var yMin = this.getMinOfArray(comps_y); + var yMax = this.getMaxOfArray(comps_y); + + return [xMin, xMax, yMin, yMax]; +} + +method.calculateBoundingBox = function() { + var xMin = Infinity, xMax = 0, yMin = Infinity, yMax = 0; + var paths = this._parsedPaths; + for (var i = 0; i < paths.length; i++) { + // paths + var path = paths[i]; + var sPoint = [0,0]; + for (var j = 0; j < path.length; j++) { + // commands + var cmd = path[j]; + switch (cmd[0]) { + case "m": + // adjust sPoint to values (relative) + sPoint[0] += cmd[1]; + sPoint[1] += cmd[2]; + xMin = Math.min(xMin, sPoint[0]); + xMax = Math.max(xMax, sPoint[0]); + yMin = Math.min(yMin, sPoint[1]); + yMax = Math.max(yMax, sPoint[1]); + break; + case "M": + // adjust sPoint to values (absolute) + sPoint[0] = cmd[1]; + sPoint[1] = cmd[2]; + xMin = Math.min(xMin, sPoint[0]); + xMax = Math.max(xMax, sPoint[0]); + yMin = Math.min(yMin, sPoint[1]); + yMax = Math.max(yMax, sPoint[1]); + break; + case "c": + + var p1 = [sPoint[0]+cmd[1],sPoint[1]+cmd[2]]; + var p2 = [sPoint[0]+cmd[3],sPoint[1]+cmd[4]]; + var p3 = [sPoint[0]+cmd[5],sPoint[1]+cmd[6]]; + + // calculate the x and y peaks of this curve, compare with current min/max + var curveMinMax = this.getMinMaxOfCurve(sPoint, p1, p2, p3); + xMin = Math.min(xMin, curveMinMax[0]); + xMax = Math.max(xMax, curveMinMax[1]); + yMin = Math.min(yMin, curveMinMax[2]); + yMax = Math.max(yMax, curveMinMax[3]); + + // set new startingpoint to last point of second curve + sPoint[0] = p3[0]; + sPoint[1] = p3[1]; + break; + case "C": + var p1 = [cmd[1],cmd[2]]; + var p2 = [cmd[3],cmd[4]]; + var p3 = [cmd[5],cmd[6]]; + + // calculate the x and y peaks of this curve, compare with current min/max + var curveMinMax = this.getMinMaxOfCurve(sPoint, p1, p2, p3); + xMin = Math.min(xMin, curveMinMax[0]); + xMax = Math.max(xMax, curveMinMax[1]); + yMin = Math.min(yMin, curveMinMax[2]); + yMax = Math.max(yMax, curveMinMax[3]); + + // set new startingpoint to last point of second curve + sPoint[0] = p3[0]; + sPoint[1] = p3[1]; + break; + case "l": + // set sPoint (relative) + var px = sPoint[0] + cmd[1]; + var py = sPoint[1] + cmd[2]; + xMin = Math.min(xMin, px, sPoint[0]); + xMax = Math.max(xMax, px, sPoint[0]); + yMin = Math.min(yMin, py, sPoint[1]); + yMax = Math.max(yMax, py, sPoint[1]); + sPoint[0] = px; + sPoint[1] = py; + break; + case "L": + // set sPoint (absolute) + xMin = Math.min(xMin, cmd[1], sPoint[0]); + xMax = Math.max(xMax, cmd[1], sPoint[0]); + yMin = Math.min(yMin, cmd[2], sPoint[1]); + yMax = Math.max(yMax, cmd[2], sPoint[1]); + sPoint[0] = cmd[1]; + sPoint[1] = cmd[2]; + break; + default: + // TODO: default behaviour + } + } + } + this._boundingBox = [xMin, xMax, yMin, yMax]; +} + +method.getScale = function(w, h) { + return [(this._boundingBox[1] - this._boundingBox[0])/w, (this._boundingBox[3] - this._boundingBox[2])/h]; +} + +method.getOffset = function() { + return [this._boundingBox[0], this.getHeight() - this._boundingBox[3]]; +} + /** Takes the SVG-file that was approximated, substitutes it's path's d-attributes and writes it to a new file. */ @@ -42,14 +211,13 @@ method.writeSVG = function(filename, nPaths, callback) { An example could look like this: [[["m", 96, 363], ["C", 407, 189, 587, 328, 587, 328]], [...], ...] */ -method.getSVGPaths = function(path, callback) { - this._path = path; +method.getSVGPaths = function(callback) { var parse = require('parse-svg-path'); // for getting the correct context in anonymous function var self = this; var parser = new this._xml2js.Parser(); - this._fs.readFile(path, function(err, data) { + this._fs.readFile(self._path, function(err, data) { if (err) { callback(err, null); } else { @@ -69,6 +237,7 @@ method.getSVGPaths = function(path, callback) { var parsedPath = parse(pathData); data.push(parsedPath); } + self._parsedPaths = data; callback(null, data); } }); diff --git a/js/script.js b/js/script.js index b01df3e5247c02d144b5603ee4dbeee4dacc87cf..b2d44bb8727aa4116d7c988750ff08191d50beb8 100644 --- a/js/script.js +++ b/js/script.js @@ -11,11 +11,13 @@ $(function(){ var printOut = function(text) { var d = new Date(); output.insertAdjacentHTML('beforeend', "

" + dateformat(d, "HH:MM:ss") + " - " + text + "

"); + output.scrollTop = output.scrollHeight; } var printErr = function(text) { var d = new Date(); output.insertAdjacentHTML('beforeend', "

" + dateformat(d, "HH:MM:ss") + " - " + text + "

"); + output.scrollTop = output.scrollHeight; } /** @@ -92,67 +94,55 @@ $(function(){ alert("No SVG submitted!"); } else { var SVGParser = require("./js/parser/svg-parser.js"); + svgParser = new SVGParser(svg); + + if (heatmap != null) { + var PrecisionMap = require('./js/approximation/precisionMap.js'); + printOut("Reading PNG..."); + // the user has provided a heatmap, so we need to consider the precision-values from it: + // first, parse the file: + var getPixels = require("get-pixels"); + getPixels(heatmap, function(err, pixels) { + if (err) { + printErr(err); + return; + } - printOut("Reading SVG..."); - svgParser = new SVGParser(); - var data = svgParser.getSVGPaths(svg, function(err, result) { - if (err) { - printErr(err); - } else { - printOut("Parsed SVG-Paths: " + result.length + " paths.") - console.dir(result); - - var PrecisionMap = require('./js/approximation/precisionMap.js'); - - if (heatmap != null) { - printOut("Reading PNG..."); - // the user has provided a heatmap, so we need to consider the precision-values from it: - // first, parse the file: - var getPixels = require("get-pixels"); - getPixels(heatmap, function(err, pixels) { - if (err) { - printErr(err); - return; - } - - // now, get the specified min/max values and create the precisionMap! - var pMax = document.getElementById('pMax').value; - var pMin = document.getElementById('pMin').value; - - printOut("Creating precisionmap..."); - var map = new PrecisionMap(pixels, pMin, pMax); - - // start the approximator with SVG-data and the precisionMap - printOut("Approximating paths with heatmap..."); - var approximator = new Approximator(result, map); - approximator.approximateData(function(err, nPaths) { - if (err) { - printErr(err); - return; - } - writePathsToFile(nPaths); - console.dir(nPaths); - }); - }); - } else { - // the user did not provide a heatmap, so just start the approximator - // get the pMax-value, this will be the precision for all positions - var pMax = document.getElementById('pMax').value; - var map = new PrecisionMap(pMax); - - printOut("Approximating paths without heatmap..."); - var approximator = new Approximator(result, map); - approximator.approximateData(function(err, nPaths) { - if (err) { - printErr(err); - return; - } - writePathsToFile(nPaths); - console.dir(nPaths); - }); + // now, get the specified min/max values and create the precisionMap! + var pMax = document.getElementById('pMax').value; + var pMin = document.getElementById('pMin').value; + + printOut("Creating precisionmap..."); + var map = new PrecisionMap(pixels, pMin, pMax); + + // start the approximator with SVG-data and the precisionMap + printOut("Approximating paths with heatmap..."); + var approximator = new Approximator(svgParser, map); + approximator.approximateData(function(err, nPaths) { + if (err) { + printErr(err); + return; + } + writePathsToFile(nPaths); + console.dir(nPaths); + }); + }); + } else { + // the user did not provide a heatmap, so just start the approximator + // get the pMax-value, this will be the precision for all positions + var pMax = document.getElementById('pMax').value; + + printOut("Approximating paths without heatmap..."); + var approximator = new Approximator(svgParser, pMax); + approximator.approximateData(function(err, nPaths) { + if (err) { + printErr(err); + return; } - } - }); + writePathsToFile(nPaths); + console.dir(nPaths); + }); + } } } diff --git a/main.js b/main.js index f9d492b4e333cc231ebc2f8c383d8155662f280c..ca2d1a0f260208d9ca27def7810f939a0290a81d 100644 --- a/main.js +++ b/main.js @@ -21,10 +21,8 @@ app.on('window-all-closed', function() { // This method will be called when Electron has finished // initialization and is ready to create browser windows. app.on('ready', function() { - // Create the browser window. - // Note: icon won't work for OSX, only for Linux and Windows - mainWindow = new BrowserWindow({icon:'./res/icon.png', width: 800, height: 600}); + mainWindow = new BrowserWindow({width: 800, height: 600}); // and load the index.html of the app. mainWindow.loadURL('file://' + __dirname + '/index.html'); diff --git a/res/icon.png b/res/icon.png deleted file mode 100644 index 4b46f46be3ace9821e540ea1b1f847619dbfe417..0000000000000000000000000000000000000000 Binary files a/res/icon.png and /dev/null differ