Commit d700883b authored by Julian Frosch's avatar Julian Frosch
Browse files

Bounding Box works, fixed some bugs

parent 3896183a
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;
this._heatmapProvided = ((typeof precision) === "object") ? true : false;
// Defaults for scale and offset
this._scaleX = 1;
this._scaleY = 1;
this._offsetX = 0;
this._offsetY = 0;
}
/**
......@@ -64,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._heatmapProvided) ? this._precision.getPrecision(curve[0][0], curve[0][1]) : this._precision;
// 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]]);
......@@ -170,92 +176,30 @@ method.approximatePath = function(pathNo) {
return path;
}
/**
This will calculate a bounding box for all paths in the SVG and store it in this class.
*/
method.calculateBoundingBox = function() {
var xMin = Infinity, xMax = 0, yMin = Infinity, yMax = 0;
var sPoint = [0,0];
for (var i = 0; i < this._parsedData.length; i++) {
// paths
var path = this._parsedData[i];
for (var j = 0; j < path.length; j++) {
// commands
var command = 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]];
// 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]];
// set new startingpoint to last point of second curve
sPoint[0] = p3[0];
sPoint[1] = p3[1];
break;
case "l":
// copy values, set sPoint (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 "L":
// copy values, set sPoint (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;
default:
// TODO: default behaviour
}
}
}
}
/**
Starts the whole approximation-process
*/
method.approximateData = function(callback) {
if (this._heatmapProvided) {
// calculate the bounding box first!
this.calculateBoundingBox();
}
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;
......@@ -19,9 +19,9 @@ function PrecisionMap(data, min, max) {
this._h = shape[1];
this._c = shape[2];
this._min = min;
this._max = max;
this._diff = max-min;
this._min = parseFloat(min);
this._max = parseFloat(max);
this._diff = min-max;
}
/**
......@@ -30,8 +30,8 @@ function PrecisionMap(data, min, max) {
*/
method.getPrecision = function(x, y) {
// 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;
};
......
......@@ -12,6 +12,170 @@ 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.
*/
......@@ -47,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 {
......@@ -74,6 +237,7 @@ method.getSVGPaths = function(path, callback) {
var parsedPath = parse(pathData);
data.push(parsedPath);
}
self._parsedPaths = data;
callback(null, data);
}
});
......
......@@ -11,11 +11,13 @@ $(function(){
var printOut = function(text) {
var d = new Date();
output.insertAdjacentHTML('beforeend', "<p>" + dateformat(d, "HH:MM:ss") + " - " + text + "</p>");
output.scrollTop = output.scrollHeight;
}
var printErr = function(text) {
var d = new Date();
output.insertAdjacentHTML('beforeend', "<p class='error'>" + dateformat(d, "HH:MM:ss") + " - " + text + "</p>");
output.scrollTop = output.scrollHeight;
}
/**
......@@ -92,66 +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;
printOut("Approximating paths without heatmap...");
var approximator = new Approximator(result, pMax);
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);
});
}
}
}
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment