Implement caching of drawn Canvas Path objects for better performance.

Already supported on recent Chrome and Safari.
This commit is contained in:
Jürg Lehni 2013-11-04 11:46:20 +01:00
parent f07f4ac977
commit 5630b7e415
2 changed files with 42 additions and 17 deletions

View file

@ -57,6 +57,13 @@ var CompoundPath = PathItem.extend(/** @lends CompoundPath# */{
this.addChildren(Array.isArray(arg) ? arg : arguments); this.addChildren(Array.isArray(arg) ? arg : arguments);
}, },
_changed: function _changed(flags) {
_changed.base.call(this, flags);
// Delete cached native Path
if (flags & (/*#=*/ ChangeFlag.HIERARCHY | /*#=*/ ChangeFlag.GEOMETRY))
delete this._currentPath;
},
insertChildren: function insertChildren(index, items, _preserve) { insertChildren: function insertChildren(index, items, _preserve) {
// Pass on 'path' for _type, to make sure that only paths are added as // Pass on 'path' for _type, to make sure that only paths are added as
// children. // children.
@ -218,10 +225,17 @@ var CompoundPath = PathItem.extend(/** @lends CompoundPath# */{
// Return early if the compound path doesn't have any children: // Return early if the compound path doesn't have any children:
if (children.length === 0) if (children.length === 0)
return; return;
if (this._currentPath) {
ctx.currentPath = this._currentPath;
} else {
ctx.beginPath(); ctx.beginPath();
param = param.extend({ compound: true }); param = param.extend({ compound: true });
for (var i = 0, l = children.length; i < l; i++) for (var i = 0, l = children.length; i < l; i++)
children[i].draw(ctx, param); children[i].draw(ctx, param);
this._currentPath = ctx.currentPath;
}
if (!param.clip) { if (!param.clip) {
this._setStyles(ctx); this._setStyles(ctx);
var style = this._style; var style = this._style;

View file

@ -115,15 +115,16 @@ var Path = PathItem.extend(/** @lends Path# */{
_changed: function _changed(flags) { _changed: function _changed(flags) {
_changed.base.call(this, flags); _changed.base.call(this, flags);
if (flags & /*#=*/ ChangeFlag.GEOMETRY) { if (flags & /*#=*/ ChangeFlag.GEOMETRY) {
// Delete cached native Path
delete (this._compound ? this._parent : this)._currentPath;
delete this._length; delete this._length;
// Clockwise state becomes undefined as soon as geometry changes. // Clockwise state becomes undefined as soon as geometry changes.
delete this._clockwise; delete this._clockwise;
// Curves are no longer valid // Curves are no longer valid
if (this._curves) { if (this._curves) {
for (var i = 0, l = this._curves.length; i < l; i++) { for (var i = 0, l = this._curves.length; i < l; i++)
this._curves[i]._changed(/*#=*/ Change.GEOMETRY); this._curves[i]._changed(/*#=*/ Change.GEOMETRY);
} }
}
} else if (flags & /*#=*/ ChangeFlag.STROKE) { } else if (flags & /*#=*/ ChangeFlag.STROKE) {
// TODO: We could preserve the purely geometric bounds that are not // TODO: We could preserve the purely geometric bounds that are not
// affected by stroke: _bounds.bounds and _bounds.handleBounds // affected by stroke: _bounds.bounds and _bounds.handleBounds
@ -1997,7 +1998,8 @@ var Path = PathItem.extend(/** @lends Path# */{
return { return {
_draw: function(ctx, param) { _draw: function(ctx, param) {
var clip = param.clip, var clip = param.clip,
compound = param.compound; // Also mark this Path as _compound so _changed() knows about it
compound = this._compound = param.compound;
if (!compound) if (!compound)
ctx.beginPath(); ctx.beginPath();
@ -2015,29 +2017,38 @@ var Path = PathItem.extend(/** @lends Path# */{
return dashArray[((i % dashLength) + dashLength) % dashLength]; return dashArray[((i % dashLength) + dashLength) % dashLength];
} }
// Prepare the canvas path if we have any situation that requires it // CompoundPath collects its own _currentPath
// to be defined. if (!compound && this._currentPath) {
ctx.currentPath = this._currentPath;
} else {
// Prepare the canvas path if we have any situation that
// requires it to be defined.
if (hasFill || hasStroke && !dashLength || compound || clip) if (hasFill || hasStroke && !dashLength || compound || clip)
drawSegments(ctx, this); drawSegments(ctx, this);
if (this._closed) if (this._closed)
ctx.closePath(); ctx.closePath();
if (!compound)
this._currentPath = ctx.currentPath;
}
if (!clip && !compound && (hasFill || hasStroke)) { if (!clip && !compound && (hasFill || hasStroke)) {
// If the path is part of a compound path or doesn't have a fill // If the path is part of a compound path or doesn't have a fill
// or stroke, there is no need to continue. // or stroke, there is no need to continue.
this._setStyles(ctx); this._setStyles(ctx);
// If shadowColor is defined, clear it after fill, so it won't
// be applied to both fill and stroke. If the path is only
// stroked, we don't have to clear it.
if (hasFill) { if (hasFill) {
ctx.fill(style.getWindingRule()); ctx.fill(style.getWindingRule());
// If shadowColor is defined, clear it after fill, so it
// won't be applied to both fill and stroke. If the path is
// only stroked, we don't have to clear it.
ctx.shadowColor = 'rgba(0,0,0,0)'; ctx.shadowColor = 'rgba(0,0,0,0)';
} }
if (hasStroke) { if (hasStroke) {
if (dashLength) { if (dashLength) {
// We cannot use the path created by drawSegments above // We cannot use the path created by drawSegments above
// Use CurveFlatteners to draw dashed paths: // Use CurveFlatteners to draw dashed paths:
// NOTE: We don't cache this path in another currentPath
// since browsers that support currentPath also support
// native dashes.
ctx.beginPath(); ctx.beginPath();
var flattener = new PathFlattener(this), var flattener = new PathFlattener(this),
length = flattener.length, length = flattener.length,