Improve performance of Path constructors and handling of { insert: false } Item creation.

This commit is contained in:
Jürg Lehni 2014-02-26 16:15:51 +01:00
parent 737466d15c
commit ccfd51a65a
9 changed files with 107 additions and 85 deletions

View file

@ -39,7 +39,15 @@ var Item = Base.extend(Callback, /** @lends Item# */{
if (name)
proto._type = Base.hyphenate(name);
return res;
}
},
/**
* An object constant that can be passed to Item#initialize() to avoid
* insertion into the DOM.
*
* @private
*/
NO_INSERT: { insert: false }
},
_class: 'Item',
@ -68,6 +76,16 @@ var Item = Base.extend(Callback, /** @lends Item# */{
// Do nothing, but declare it for named constructors.
},
/**
* Private helper for #initialize() that tries setting properties from the
* passed props object, and apply the point translation to the internal
* matrix.
*
* @param {Object} props the properties to be applied to the item
* @param {Point} point the point by which to transform the internal matrix
* @returns {Boolean} {@true if the properties were successfully be applied,
* or if none were provided}
*/
_initialize: function(props, point) {
// Define this Item's unique id. But allow the creation of internally
// used paths with no ids.
@ -95,7 +113,10 @@ var Item = Base.extend(Callback, /** @lends Item# */{
}
}
this._style = new Style(this._project._currentStyle, this);
return props ? this._set(props, { insert: true }) : true;
// Filter out Item.NO_INSERT before _set(), for performance reasons
return props && props !== Item.NO_INSERT
? this._set(props, { insert: true }) // Filter out insert prop.
: true;
},
_events: new function() {
@ -1443,7 +1464,7 @@ var Item = Base.extend(Callback, /** @lends Item# */{
* }
*/
clone: function(insert) {
return this._clone(new this.constructor({ insert: false }), insert);
return this._clone(new this.constructor(Item.NO_INSERT), insert);
},
_clone: function(copy, insert) {
@ -1538,12 +1559,10 @@ var Item = Base.extend(Callback, /** @lends Item# */{
// See Project#draw() for an explanation of new Base()
this.draw(ctx, new Base({ transforms: [matrix] }));
ctx.restore();
var raster = new Raster({
canvas: canvas,
insert: false
});
var raster = new Raster(Item.NO_INSERT);
raster.setCanvas(canvas);
raster.transform(new Matrix().translate(topLeft.add(size.divide(2)))
// Take resolution into acocunt and scale back to original size.
// Take resolution into account and scale back to original size.
.scale(1 / scale));
raster.insertAbove(this);
// NOTE: We don't need to release the canvas since it now belongs to the

View file

@ -99,10 +99,9 @@ var PlacedSymbol = Item.extend(/** @lends PlacedSymbol# */{
},
clone: function(insert) {
return this._clone(new PlacedSymbol({
symbol: this.symbol,
insert: false
}), insert);
var copy = new PlacedSymbol(Item.NO_INSERT);
copy.setSymbol(this._symbol);
return this._clone(copy, insert);
},
isEmpty: function() {

View file

@ -97,17 +97,19 @@ var Raster = Item.extend(/** @lends Raster# */{
},
clone: function(insert) {
var param = { insert: false },
image = this._image;
var copy = new Raster(Item.NO_INSERT),
image = this._image,
canvas = this._canvas;
if (image) {
param.image = image;
} else if (this._canvas) {
// If the Raster contains a Canvas object, we need to create
// a new one and draw this raster's canvas on it.
var canvas = param.canvas = CanvasProvider.getCanvas(this._size);
canvas.getContext('2d').drawImage(this._canvas, 0, 0);
copy.setImage(image);
} else if (canvas) {
// If the Raster contains a Canvas object, we need to create a new
// one and draw this raster's canvas on it.
var copyCanvas = CanvasProvider.getCanvas(this._size);
copyCanvas.getContext('2d').drawImage(canvas, 0, 0);
copy.setCanvas(copyCanvas);
}
return this._clone(new Raster(param), insert);
return this._clone(copy, insert);
},
/**
@ -394,10 +396,8 @@ var Raster = Item.extend(/** @lends Raster# */{
*/
getSubRaster: function(rect) { // TODO: Fix argument assignment!
var rect = Rectangle.read(arguments),
raster = new Raster({
canvas: this.getSubCanvas(rect),
insert: false
});
raster = new Raster(Item.NO_INSERT);
raster.setCanvas(this.getSubCanvas(rect));
raster.translate(rect.getCenter().subtract(this.getSize().divide(2)));
raster._matrix.preConcatenate(this._matrix);
raster.insertAbove(this);

View file

@ -39,12 +39,11 @@ var Shape = Item.extend(/** @lends Shape# */{
},
clone: function(insert) {
return this._clone(new Shape({
shape: this._shape,
size: this._size,
radius: this._radius,
insert: false
}), insert);
var copy = new Shape(Item.NO_INSERT);
copy.setShape(this._shape);
copy.setSize(this._size);
copy.setRadius(this._radius);
return this._clone(copy, insert);
},
/**

View file

@ -20,9 +20,19 @@ Path.inject({ statics: new function() {
new Segment([0, 1], [kappa, 0 ], [-kappa, 0])
];
function createPath(segments, closed, args) {
var props = Base.getNamed(args),
path = new Path(props && props.insert === false && Item.NO_INSERT);
path._add(segments);
// No need to use setter for _closed since _add() called _changed().
path._closed = true;
// Set named arguments at the end, since some depend on geometry to be
// defined (e.g. #clockwise)
return path.set(props);
}
function createEllipse(center, radius, args) {
var path = new Path(),
segments = new Array(4);
var segments = new Array(4);
for (var i = 0; i < 4; i++) {
var segment = ellipseSegments[i];
segments[i] = new Segment(
@ -31,11 +41,7 @@ Path.inject({ statics: new function() {
segment._handleOut.multiply(radius)
);
}
path._add(segments);
path._closed = true;
// Set named arguments at the end, since some depend on geometry to be
// defined (e.g. #clockwise)
return path.set(Base.getNamed(args));
return createPath(segments, true, args);
}
@ -73,10 +79,10 @@ Path.inject({ statics: new function() {
* });
*/
Line: function(/* from, to */) {
return new Path([
Point.readNamed(arguments, 'from'),
Point.readNamed(arguments, 'to')
]).set(Base.getNamed(arguments));
return createPath([
new Segment(Point.readNamed(arguments, 'from')),
new Segment(Point.readNamed(arguments, 'to'))
], false, arguments);
},
/**
@ -210,22 +216,22 @@ Path.inject({ statics: new function() {
bl = rect.getBottomLeft(true),
tl = rect.getTopLeft(true),
tr = rect.getTopRight(true),
br = rect.getBottomRight(true);
path = new Path();
br = rect.getBottomRight(true),
segments;
if (!radius || radius.isZero()) {
path._add([
segments = [
new Segment(bl),
new Segment(tl),
new Segment(tr),
new Segment(br)
]);
];
} else {
radius = Size.min(radius, rect.getSize(true).divide(2));
var rx = radius.width,
ry = radius.height,
hx = rx * kappa,
hy = ry * kappa;
path._add([
segments = [
new Segment(bl.add(rx, 0), null, [-hx, 0]),
new Segment(bl.subtract(0, ry), [0, hy]),
new Segment(tl.add(0, ry), null, [0, -hy]),
@ -234,11 +240,9 @@ Path.inject({ statics: new function() {
new Segment(tr.add(0, ry), [0, -hy], null),
new Segment(br.subtract(0, ry), null, [0, hy]),
new Segment(br.subtract(rx, 0), [hx, 0])
]);
];
}
// No need to use setter for _closed since _add() called _changed().
path._closed = true;
return path.set(Base.getNamed(arguments));
return createPath(segments, true, arguments);
},
/**
@ -329,10 +333,13 @@ Path.inject({ statics: new function() {
var from = Point.readNamed(arguments, 'from'),
through = Point.readNamed(arguments, 'through'),
to = Point.readNamed(arguments, 'to'),
path = new Path();
props = Base.getNamed(arguments),
// See createPath() for an explanation of the following sequence
path = new Path(props && props.insert === false
&& Item.NO_INSERT);
path.moveTo(from);
path.arcTo(through, to);
return path.set(Base.getNamed(arguments));
return path.set(props);
},
/**
@ -372,19 +379,15 @@ Path.inject({ statics: new function() {
var center = Point.readNamed(arguments, 'center'),
sides = Base.readNamed(arguments, 'sides'),
radius = Base.readNamed(arguments, 'radius'),
path = new Path(),
step = 360 / sides,
three = !(sides % 3),
vector = new Point(0, three ? -radius : radius),
offset = three ? -1 : 0.5,
segments = new Array(sides);
for (var i = 0; i < sides; i++) {
for (var i = 0; i < sides; i++)
segments[i] = new Segment(center.add(
vector.rotate((i + offset) * step)));
}
path._add(segments);
path._closed = true;
return path.set(Base.getNamed(arguments));
return createPath(segments, true, arguments);
},
/**
@ -432,17 +435,13 @@ Path.inject({ statics: new function() {
points = Base.readNamed(arguments, 'points') * 2,
radius1 = Base.readNamed(arguments, 'radius1'),
radius2 = Base.readNamed(arguments, 'radius2'),
path = new Path(),
step = 360 / points,
vector = new Point(0, -1),
segments = new Array(points);
for (var i = 0; i < points; i++) {
segments[i] = new Segment(center.add(
vector.rotate(step * i).multiply(i % 2 ? radius2 : radius1)));
}
path._add(segments);
path._closed = true;
return path.set(Base.getNamed(arguments));
for (var i = 0; i < points; i++)
segments[i] = new Segment(center.add(vector.rotate(step * i)
.multiply(i % 2 ? radius2 : radius1)));
return createPath(segments, true, arguments);
}
};
}});

View file

@ -106,11 +106,17 @@ var Path = PathItem.extend(/** @lends Path# */{
? arguments
: null;
// Always call setSegments() to initialize a few related variables.
this.setSegments(segments || []);
if (!segments && typeof arg === 'string') {
this.setPathData(arg);
// Erase for _initialize() call below.
arg = null;
if (segments && segments.length > 0) {
// This sets _curves and _selectedSegmentState too!
this.setSegments(segments);
} else {
this._curves = undefined; // For hidden class optimization
this._selectedSegmentState = 0;
if (!segments && typeof arg === 'string') {
this.setPathData(arg);
// Erase for _initialize() call below.
arg = null;
}
}
// Only pass on arg as props if it wasn't consumed for segments already.
this._initialize(!segments && arg);
@ -121,15 +127,12 @@ var Path = PathItem.extend(/** @lends Path# */{
},
clone: function(insert) {
var copy = this._clone(new Path({
segments: this._segments,
insert: false
}), insert);
// Speed up things a little by copy over values that don't need checking
var copy = new Path(Item.NO_INSERT);
copy.setSegments(this._segments);
copy._closed = this._closed;
if (this._clockwise !== undefined)
copy._clockwise = this._clockwise;
return copy;
return this._clone(copy, insert);
},
_changed: function _changed(flags) {
@ -177,7 +180,10 @@ var Path = PathItem.extend(/** @lends Path# */{
this._selectedSegmentState = 0;
// Calculate new curves next time we call getCurves()
this._curves = undefined;
this._add(Segment.readAll(segments));
if (segments && segments.length > 0)
this._add(Segment.readAll(segments));
// Preserve fullySelected state.
// TODO: Do we still need this?
if (fullySelected)
this.setFullySelected(true);
},
@ -1785,7 +1791,7 @@ var Path = PathItem.extend(/** @lends Path# */{
// Code to check stroke join / cap areas
function addAreaPoint(point) {
function addToArea(point) {
area.add(point);
}
@ -1803,10 +1809,10 @@ var Path = PathItem.extend(/** @lends Path# */{
if (join !== 'round' && (segment._handleIn.isZero()
|| segment._handleOut.isZero()))
Path._addSquareJoin(segment, join, radius, miterLimit,
addAreaPoint, true);
addToArea, true);
} else if (cap !== 'round') {
// It's a cap
Path._addSquareCap(segment, cap, radius, addAreaPoint, true);
Path._addSquareCap(segment, cap, radius, addToArea, true);
}
// See if the above produced an area to check for
if (!area.isEmpty()) {

View file

@ -364,7 +364,7 @@ PathItem.inject(new function() {
seg = startSeg = segments[i];
if (seg._visited || !operator(seg._winding))
continue;
var path = new Path({ insert: false }),
var path = new Path(Item.NO_INSERT),
inter = seg._intersection,
startInterSeg = inter && inter._segment,
added = false, // Wether a first segment as added already

View file

@ -183,7 +183,7 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{
// NOTE: If there is no layer and this project is not the active
// one, passing insert: false and calling addChild on the
// project will handle it correctly.
|| this.addChild(new Layer({ insert: false }))).addChild(child);
|| this.addChild(new Layer(Item.NO_INSERT))).addChild(child);
} else {
child = null;
}

View file

@ -59,7 +59,7 @@ var PointText = TextItem.extend(/** @lends PointText# */{
},
clone: function(insert) {
return this._clone(new PointText({ insert: false }), insert);
return this._clone(new PointText(Item.NO_INSERT), insert);
},
/**