mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-07-30 07:39:50 -04:00
Improve performance of Path constructors and handling of { insert: false } Item creation.
This commit is contained in:
parent
737466d15c
commit
ccfd51a65a
9 changed files with 107 additions and 85 deletions
|
@ -39,7 +39,15 @@ var Item = Base.extend(Callback, /** @lends Item# */{
|
||||||
if (name)
|
if (name)
|
||||||
proto._type = Base.hyphenate(name);
|
proto._type = Base.hyphenate(name);
|
||||||
return res;
|
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',
|
_class: 'Item',
|
||||||
|
@ -68,6 +76,16 @@ var Item = Base.extend(Callback, /** @lends Item# */{
|
||||||
// Do nothing, but declare it for named constructors.
|
// 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) {
|
_initialize: function(props, point) {
|
||||||
// Define this Item's unique id. But allow the creation of internally
|
// Define this Item's unique id. But allow the creation of internally
|
||||||
// used paths with no ids.
|
// used paths with no ids.
|
||||||
|
@ -95,7 +113,10 @@ var Item = Base.extend(Callback, /** @lends Item# */{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this._style = new Style(this._project._currentStyle, this);
|
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() {
|
_events: new function() {
|
||||||
|
@ -1443,7 +1464,7 @@ var Item = Base.extend(Callback, /** @lends Item# */{
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
clone: function(insert) {
|
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) {
|
_clone: function(copy, insert) {
|
||||||
|
@ -1538,12 +1559,10 @@ var Item = Base.extend(Callback, /** @lends Item# */{
|
||||||
// See Project#draw() for an explanation of new Base()
|
// See Project#draw() for an explanation of new Base()
|
||||||
this.draw(ctx, new Base({ transforms: [matrix] }));
|
this.draw(ctx, new Base({ transforms: [matrix] }));
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
var raster = new Raster({
|
var raster = new Raster(Item.NO_INSERT);
|
||||||
canvas: canvas,
|
raster.setCanvas(canvas);
|
||||||
insert: false
|
|
||||||
});
|
|
||||||
raster.transform(new Matrix().translate(topLeft.add(size.divide(2)))
|
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));
|
.scale(1 / scale));
|
||||||
raster.insertAbove(this);
|
raster.insertAbove(this);
|
||||||
// NOTE: We don't need to release the canvas since it now belongs to the
|
// NOTE: We don't need to release the canvas since it now belongs to the
|
||||||
|
|
|
@ -99,10 +99,9 @@ var PlacedSymbol = Item.extend(/** @lends PlacedSymbol# */{
|
||||||
},
|
},
|
||||||
|
|
||||||
clone: function(insert) {
|
clone: function(insert) {
|
||||||
return this._clone(new PlacedSymbol({
|
var copy = new PlacedSymbol(Item.NO_INSERT);
|
||||||
symbol: this.symbol,
|
copy.setSymbol(this._symbol);
|
||||||
insert: false
|
return this._clone(copy, insert);
|
||||||
}), insert);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
isEmpty: function() {
|
isEmpty: function() {
|
||||||
|
|
|
@ -97,17 +97,19 @@ var Raster = Item.extend(/** @lends Raster# */{
|
||||||
},
|
},
|
||||||
|
|
||||||
clone: function(insert) {
|
clone: function(insert) {
|
||||||
var param = { insert: false },
|
var copy = new Raster(Item.NO_INSERT),
|
||||||
image = this._image;
|
image = this._image,
|
||||||
|
canvas = this._canvas;
|
||||||
if (image) {
|
if (image) {
|
||||||
param.image = image;
|
copy.setImage(image);
|
||||||
} else if (this._canvas) {
|
} else if (canvas) {
|
||||||
// If the Raster contains a Canvas object, we need to create
|
// If the Raster contains a Canvas object, we need to create a new
|
||||||
// a new one and draw this raster's canvas on it.
|
// one and draw this raster's canvas on it.
|
||||||
var canvas = param.canvas = CanvasProvider.getCanvas(this._size);
|
var copyCanvas = CanvasProvider.getCanvas(this._size);
|
||||||
canvas.getContext('2d').drawImage(this._canvas, 0, 0);
|
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!
|
getSubRaster: function(rect) { // TODO: Fix argument assignment!
|
||||||
var rect = Rectangle.read(arguments),
|
var rect = Rectangle.read(arguments),
|
||||||
raster = new Raster({
|
raster = new Raster(Item.NO_INSERT);
|
||||||
canvas: this.getSubCanvas(rect),
|
raster.setCanvas(this.getSubCanvas(rect));
|
||||||
insert: false
|
|
||||||
});
|
|
||||||
raster.translate(rect.getCenter().subtract(this.getSize().divide(2)));
|
raster.translate(rect.getCenter().subtract(this.getSize().divide(2)));
|
||||||
raster._matrix.preConcatenate(this._matrix);
|
raster._matrix.preConcatenate(this._matrix);
|
||||||
raster.insertAbove(this);
|
raster.insertAbove(this);
|
||||||
|
|
|
@ -39,12 +39,11 @@ var Shape = Item.extend(/** @lends Shape# */{
|
||||||
},
|
},
|
||||||
|
|
||||||
clone: function(insert) {
|
clone: function(insert) {
|
||||||
return this._clone(new Shape({
|
var copy = new Shape(Item.NO_INSERT);
|
||||||
shape: this._shape,
|
copy.setShape(this._shape);
|
||||||
size: this._size,
|
copy.setSize(this._size);
|
||||||
radius: this._radius,
|
copy.setRadius(this._radius);
|
||||||
insert: false
|
return this._clone(copy, insert);
|
||||||
}), insert);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -20,9 +20,19 @@ Path.inject({ statics: new function() {
|
||||||
new Segment([0, 1], [kappa, 0 ], [-kappa, 0])
|
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) {
|
function createEllipse(center, radius, args) {
|
||||||
var path = new Path(),
|
var segments = new Array(4);
|
||||||
segments = new Array(4);
|
|
||||||
for (var i = 0; i < 4; i++) {
|
for (var i = 0; i < 4; i++) {
|
||||||
var segment = ellipseSegments[i];
|
var segment = ellipseSegments[i];
|
||||||
segments[i] = new Segment(
|
segments[i] = new Segment(
|
||||||
|
@ -31,11 +41,7 @@ Path.inject({ statics: new function() {
|
||||||
segment._handleOut.multiply(radius)
|
segment._handleOut.multiply(radius)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
path._add(segments);
|
return createPath(segments, true, args);
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -73,10 +79,10 @@ Path.inject({ statics: new function() {
|
||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
Line: function(/* from, to */) {
|
Line: function(/* from, to */) {
|
||||||
return new Path([
|
return createPath([
|
||||||
Point.readNamed(arguments, 'from'),
|
new Segment(Point.readNamed(arguments, 'from')),
|
||||||
Point.readNamed(arguments, 'to')
|
new Segment(Point.readNamed(arguments, 'to'))
|
||||||
]).set(Base.getNamed(arguments));
|
], false, arguments);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -210,22 +216,22 @@ Path.inject({ statics: new function() {
|
||||||
bl = rect.getBottomLeft(true),
|
bl = rect.getBottomLeft(true),
|
||||||
tl = rect.getTopLeft(true),
|
tl = rect.getTopLeft(true),
|
||||||
tr = rect.getTopRight(true),
|
tr = rect.getTopRight(true),
|
||||||
br = rect.getBottomRight(true);
|
br = rect.getBottomRight(true),
|
||||||
path = new Path();
|
segments;
|
||||||
if (!radius || radius.isZero()) {
|
if (!radius || radius.isZero()) {
|
||||||
path._add([
|
segments = [
|
||||||
new Segment(bl),
|
new Segment(bl),
|
||||||
new Segment(tl),
|
new Segment(tl),
|
||||||
new Segment(tr),
|
new Segment(tr),
|
||||||
new Segment(br)
|
new Segment(br)
|
||||||
]);
|
];
|
||||||
} else {
|
} else {
|
||||||
radius = Size.min(radius, rect.getSize(true).divide(2));
|
radius = Size.min(radius, rect.getSize(true).divide(2));
|
||||||
var rx = radius.width,
|
var rx = radius.width,
|
||||||
ry = radius.height,
|
ry = radius.height,
|
||||||
hx = rx * kappa,
|
hx = rx * kappa,
|
||||||
hy = ry * kappa;
|
hy = ry * kappa;
|
||||||
path._add([
|
segments = [
|
||||||
new Segment(bl.add(rx, 0), null, [-hx, 0]),
|
new Segment(bl.add(rx, 0), null, [-hx, 0]),
|
||||||
new Segment(bl.subtract(0, ry), [0, hy]),
|
new Segment(bl.subtract(0, ry), [0, hy]),
|
||||||
new Segment(tl.add(0, ry), null, [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(tr.add(0, ry), [0, -hy], null),
|
||||||
new Segment(br.subtract(0, ry), null, [0, hy]),
|
new Segment(br.subtract(0, ry), null, [0, hy]),
|
||||||
new Segment(br.subtract(rx, 0), [hx, 0])
|
new Segment(br.subtract(rx, 0), [hx, 0])
|
||||||
]);
|
];
|
||||||
}
|
}
|
||||||
// No need to use setter for _closed since _add() called _changed().
|
return createPath(segments, true, arguments);
|
||||||
path._closed = true;
|
|
||||||
return path.set(Base.getNamed(arguments));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -329,10 +333,13 @@ Path.inject({ statics: new function() {
|
||||||
var from = Point.readNamed(arguments, 'from'),
|
var from = Point.readNamed(arguments, 'from'),
|
||||||
through = Point.readNamed(arguments, 'through'),
|
through = Point.readNamed(arguments, 'through'),
|
||||||
to = Point.readNamed(arguments, 'to'),
|
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.moveTo(from);
|
||||||
path.arcTo(through, to);
|
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'),
|
var center = Point.readNamed(arguments, 'center'),
|
||||||
sides = Base.readNamed(arguments, 'sides'),
|
sides = Base.readNamed(arguments, 'sides'),
|
||||||
radius = Base.readNamed(arguments, 'radius'),
|
radius = Base.readNamed(arguments, 'radius'),
|
||||||
path = new Path(),
|
|
||||||
step = 360 / sides,
|
step = 360 / sides,
|
||||||
three = !(sides % 3),
|
three = !(sides % 3),
|
||||||
vector = new Point(0, three ? -radius : radius),
|
vector = new Point(0, three ? -radius : radius),
|
||||||
offset = three ? -1 : 0.5,
|
offset = three ? -1 : 0.5,
|
||||||
segments = new Array(sides);
|
segments = new Array(sides);
|
||||||
for (var i = 0; i < sides; i++) {
|
for (var i = 0; i < sides; i++)
|
||||||
segments[i] = new Segment(center.add(
|
segments[i] = new Segment(center.add(
|
||||||
vector.rotate((i + offset) * step)));
|
vector.rotate((i + offset) * step)));
|
||||||
}
|
return createPath(segments, true, arguments);
|
||||||
path._add(segments);
|
|
||||||
path._closed = true;
|
|
||||||
return path.set(Base.getNamed(arguments));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -432,17 +435,13 @@ Path.inject({ statics: new function() {
|
||||||
points = Base.readNamed(arguments, 'points') * 2,
|
points = Base.readNamed(arguments, 'points') * 2,
|
||||||
radius1 = Base.readNamed(arguments, 'radius1'),
|
radius1 = Base.readNamed(arguments, 'radius1'),
|
||||||
radius2 = Base.readNamed(arguments, 'radius2'),
|
radius2 = Base.readNamed(arguments, 'radius2'),
|
||||||
path = new Path(),
|
|
||||||
step = 360 / points,
|
step = 360 / points,
|
||||||
vector = new Point(0, -1),
|
vector = new Point(0, -1),
|
||||||
segments = new Array(points);
|
segments = new Array(points);
|
||||||
for (var i = 0; i < points; i++) {
|
for (var i = 0; i < points; i++)
|
||||||
segments[i] = new Segment(center.add(
|
segments[i] = new Segment(center.add(vector.rotate(step * i)
|
||||||
vector.rotate(step * i).multiply(i % 2 ? radius2 : radius1)));
|
.multiply(i % 2 ? radius2 : radius1)));
|
||||||
}
|
return createPath(segments, true, arguments);
|
||||||
path._add(segments);
|
|
||||||
path._closed = true;
|
|
||||||
return path.set(Base.getNamed(arguments));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}});
|
}});
|
||||||
|
|
|
@ -106,11 +106,17 @@ var Path = PathItem.extend(/** @lends Path# */{
|
||||||
? arguments
|
? arguments
|
||||||
: null;
|
: null;
|
||||||
// Always call setSegments() to initialize a few related variables.
|
// Always call setSegments() to initialize a few related variables.
|
||||||
this.setSegments(segments || []);
|
if (segments && segments.length > 0) {
|
||||||
if (!segments && typeof arg === 'string') {
|
// This sets _curves and _selectedSegmentState too!
|
||||||
this.setPathData(arg);
|
this.setSegments(segments);
|
||||||
// Erase for _initialize() call below.
|
} else {
|
||||||
arg = null;
|
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.
|
// Only pass on arg as props if it wasn't consumed for segments already.
|
||||||
this._initialize(!segments && arg);
|
this._initialize(!segments && arg);
|
||||||
|
@ -121,15 +127,12 @@ var Path = PathItem.extend(/** @lends Path# */{
|
||||||
},
|
},
|
||||||
|
|
||||||
clone: function(insert) {
|
clone: function(insert) {
|
||||||
var copy = this._clone(new Path({
|
var copy = new Path(Item.NO_INSERT);
|
||||||
segments: this._segments,
|
copy.setSegments(this._segments);
|
||||||
insert: false
|
|
||||||
}), insert);
|
|
||||||
// Speed up things a little by copy over values that don't need checking
|
|
||||||
copy._closed = this._closed;
|
copy._closed = this._closed;
|
||||||
if (this._clockwise !== undefined)
|
if (this._clockwise !== undefined)
|
||||||
copy._clockwise = this._clockwise;
|
copy._clockwise = this._clockwise;
|
||||||
return copy;
|
return this._clone(copy, insert);
|
||||||
},
|
},
|
||||||
|
|
||||||
_changed: function _changed(flags) {
|
_changed: function _changed(flags) {
|
||||||
|
@ -177,7 +180,10 @@ var Path = PathItem.extend(/** @lends Path# */{
|
||||||
this._selectedSegmentState = 0;
|
this._selectedSegmentState = 0;
|
||||||
// Calculate new curves next time we call getCurves()
|
// Calculate new curves next time we call getCurves()
|
||||||
this._curves = undefined;
|
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)
|
if (fullySelected)
|
||||||
this.setFullySelected(true);
|
this.setFullySelected(true);
|
||||||
},
|
},
|
||||||
|
@ -1785,7 +1791,7 @@ var Path = PathItem.extend(/** @lends Path# */{
|
||||||
|
|
||||||
// Code to check stroke join / cap areas
|
// Code to check stroke join / cap areas
|
||||||
|
|
||||||
function addAreaPoint(point) {
|
function addToArea(point) {
|
||||||
area.add(point);
|
area.add(point);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1803,10 +1809,10 @@ var Path = PathItem.extend(/** @lends Path# */{
|
||||||
if (join !== 'round' && (segment._handleIn.isZero()
|
if (join !== 'round' && (segment._handleIn.isZero()
|
||||||
|| segment._handleOut.isZero()))
|
|| segment._handleOut.isZero()))
|
||||||
Path._addSquareJoin(segment, join, radius, miterLimit,
|
Path._addSquareJoin(segment, join, radius, miterLimit,
|
||||||
addAreaPoint, true);
|
addToArea, true);
|
||||||
} else if (cap !== 'round') {
|
} else if (cap !== 'round') {
|
||||||
// It's a cap
|
// 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
|
// See if the above produced an area to check for
|
||||||
if (!area.isEmpty()) {
|
if (!area.isEmpty()) {
|
||||||
|
|
|
@ -364,7 +364,7 @@ PathItem.inject(new function() {
|
||||||
seg = startSeg = segments[i];
|
seg = startSeg = segments[i];
|
||||||
if (seg._visited || !operator(seg._winding))
|
if (seg._visited || !operator(seg._winding))
|
||||||
continue;
|
continue;
|
||||||
var path = new Path({ insert: false }),
|
var path = new Path(Item.NO_INSERT),
|
||||||
inter = seg._intersection,
|
inter = seg._intersection,
|
||||||
startInterSeg = inter && inter._segment,
|
startInterSeg = inter && inter._segment,
|
||||||
added = false, // Wether a first segment as added already
|
added = false, // Wether a first segment as added already
|
||||||
|
|
|
@ -183,7 +183,7 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{
|
||||||
// NOTE: If there is no layer and this project is not the active
|
// NOTE: If there is no layer and this project is not the active
|
||||||
// one, passing insert: false and calling addChild on the
|
// one, passing insert: false and calling addChild on the
|
||||||
// project will handle it correctly.
|
// project will handle it correctly.
|
||||||
|| this.addChild(new Layer({ insert: false }))).addChild(child);
|
|| this.addChild(new Layer(Item.NO_INSERT))).addChild(child);
|
||||||
} else {
|
} else {
|
||||||
child = null;
|
child = null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,7 +59,7 @@ var PointText = TextItem.extend(/** @lends PointText# */{
|
||||||
},
|
},
|
||||||
|
|
||||||
clone: function(insert) {
|
clone: function(insert) {
|
||||||
return this._clone(new PointText({ insert: false }), insert);
|
return this._clone(new PointText(Item.NO_INSERT), insert);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue