Skip to content

Commit

Permalink
Fix some caching in group issues related to deep checks (#4032)
Browse files Browse the repository at this point in the history
* backport
* better fix
  • Loading branch information
asturur authored Jun 24, 2017
1 parent efc2a21 commit 7a88afb
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 26 deletions.
25 changes: 12 additions & 13 deletions src/mixins/stateful.mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,35 +15,34 @@
}

function _isEqual(origValue, currentValue, firstPass) {
if (!fabric.isLikelyNode && origValue instanceof Element) {
// avoid checking deep html elements
return origValue === currentValue;
if (origValue === currentValue) {
// if the objects are identical, return
return true;
}
else if (origValue instanceof Array) {
else if (Array.isArray(origValue)) {
if (origValue.length !== currentValue.length) {
return false;
}
for (var i = 0, len = origValue.length; i < len; i++) {
if (origValue[i] !== currentValue[i]) {
if (!_isEqual(origValue[i], currentValue[i])) {
return false;
}
}
return true;
}
else if (origValue && typeof origValue === 'object') {
if (!firstPass && Object.keys(origValue).length !== Object.keys(currentValue).length) {
var keys = Object.keys(origValue), key;
if (!firstPass && keys.length !== Object.keys(currentValue).length) {
return false;
}
for (var key in origValue) {
for (var i = 0, len = keys.length; i < len; i++) {
key = keys[i];
if (!_isEqual(origValue[key], currentValue[key])) {
return false;
}
}
return true;
}
else {
return origValue === currentValue;
}
}


Expand All @@ -56,11 +55,11 @@
*/
hasStateChanged: function(propertySet) {
propertySet = propertySet || originalSet;
propertySet = '_' + propertySet;
if (!Object.keys(this[propertySet]).length) {
var dashedPropertySet = '_' + propertySet;
if (Object.keys(this[dashedPropertySet]).length < this[propertySet].length) {
return true;
}
return !_isEqual(this[propertySet], this, true);
return !_isEqual(this[dashedPropertySet], this, true);
},

/**
Expand Down
14 changes: 12 additions & 2 deletions src/shapes/group.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@
*/
subTargetCheck: false,

/**
* Groups are container, do not render anything on theyr own, ence no cache properties
* @type Boolean
* @default
*/
cacheProperties: [],

/**
* Constructor
* @param {Object} objects Group objects
Expand Down Expand Up @@ -373,8 +380,11 @@
}
for (var i = 0, len = this._objects.length; i < len; i++) {
if (this._objects[i].isCacheDirty(true)) {
var dim = this._getNonTransformedDimensions();
this._cacheContext.clearRect(-dim.x / 2, -dim.y / 2, dim.x, dim.y);
if (this._cacheCanvas) {
// if this group has not a cache canvas there is nothing to clean
var x = this.cacheWidth / this.zoomX, y = this.cacheHeight / this.zoomY;
this._cacheContext.clearRect(-x / 2, -y / 2, x, y);
}
return true;
}
}
Expand Down
30 changes: 22 additions & 8 deletions src/shapes/object.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -806,17 +806,17 @@
stateProperties: (
'top left width height scaleX scaleY flipX flipY originX originY transformMatrix ' +
'stroke strokeWidth strokeDashArray strokeLineCap strokeLineJoin strokeMiterLimit ' +
'angle opacity fill fillRule globalCompositeOperation shadow clipTo visible backgroundColor ' +
'skewX skewY'
'angle opacity fill globalCompositeOperation shadow clipTo visible backgroundColor ' +
'skewX skewY fillRule'
).split(' '),

/**
* List of properties to consider when checking if cache needs refresh
* @type Array
*/
cacheProperties: (
'fill stroke strokeWidth strokeDashArray width height stroke strokeWidth strokeDashArray' +
' strokeLineCap strokeLineJoin strokeMiterLimit fillRule backgroundColor'
'fill stroke strokeWidth strokeDashArray width height' +
' strokeLineCap strokeLineJoin strokeMiterLimit backgroundColor'
).split(' '),

/**
Expand Down Expand Up @@ -1108,7 +1108,7 @@
* Retrieves viewportTransform from Object's canvas if possible
* @method getViewportTransform
* @memberOf fabric.Object.prototype
* @return {Boolean} flipY value // TODO
* @return {Boolean}
*/
getViewportTransform: function() {
if (this.canvas && this.canvas.viewportTransform) {
Expand All @@ -1117,13 +1117,23 @@
return fabric.iMatrix.concat();
},

/*
* @private
* return if the object would be visible in rendering
* @memberOf fabric.Object.prototype
* @return {Boolean}
*/
isNotVisible: function() {
return this.opacity === 0 || (this.width === 0 && this.height === 0) || !this.visible;
},

/**
* Renders an object on a specified context
* @param {CanvasRenderingContext2D} ctx Context to render on
*/
render: function(ctx) {
// do not render if width/height are zeros or object is not visible
if ((this.width === 0 && this.height === 0) || !this.visible) {
if (this.isNotVisible()) {
return;
}
if (this.canvas && this.canvas.skipOffscreen && !this.group && !this.isOnScreen()) {
Expand Down Expand Up @@ -1152,6 +1162,7 @@
this.drawCacheOnCanvas(ctx);
}
else {
this.dirty = false;
this.drawObject(ctx);
if (this.objectCaching && this.statefullCache) {
this.saveState({ propertySet: 'cacheProperties' });
Expand Down Expand Up @@ -1221,13 +1232,16 @@
* on parent canvas.
*/
isCacheDirty: function(skipCanvas) {
if (!skipCanvas && this._updateCacheCanvas()) {
if (this.isNotVisible()) {
return false;
}
if (this._cacheCanvas && !skipCanvas && this._updateCacheCanvas()) {
// in this case the context is already cleared.
return true;
}
else {
if (this.dirty || (this.statefullCache && this.hasStateChanged('cacheProperties'))) {
if (!skipCanvas) {
if (this._cacheCanvas && !skipCanvas) {
var width = this.cacheWidth / this.zoomX;
var height = this.cacheHeight / this.zoomY;
this._cacheContext.clearRect(-width / 2, -height / 2, width, height);
Expand Down
7 changes: 6 additions & 1 deletion src/shapes/path.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,11 @@
return;
}

var stateProperties = fabric.Object.prototype.stateProperties.concat();
stateProperties.push('path');

var cacheProperties = fabric.Object.prototype.cacheProperties.concat();
cacheProperties.push('path');
cacheProperties.push('path', 'fillRule');

/**
* Path class
Expand Down Expand Up @@ -71,6 +74,8 @@

cacheProperties: cacheProperties,

stateProperties: stateProperties,

/**
* Constructor
* @param {Array|String} path Path data (sequence of coordinates and corresponding "command" tokens)
Expand Down
4 changes: 2 additions & 2 deletions test/unit/object.js
Original file line number Diff line number Diff line change
Expand Up @@ -1156,7 +1156,7 @@
});

test('isCacheDirty statefullCache disabled', function() {
var object = new fabric.Object({ scaleX: 3, scaleY: 2});
var object = new fabric.Object({ scaleX: 3, scaleY: 2, width: 1, height: 2});
equal(object.dirty, true, 'object is dirty after creation');
object.cacheProperties = ['propA', 'propB'];
object.dirty = false;
Expand All @@ -1168,7 +1168,7 @@
});

test('isCacheDirty statefullCache enabled', function() {
var object = new fabric.Object({ scaleX: 3, scaleY: 2});
var object = new fabric.Object({ scaleX: 3, scaleY: 2, width: 1, height: 2});
object.cacheProperties = ['propA', 'propB'];
object.dirty = false;
object.statefullCache = true;
Expand Down

0 comments on commit 7a88afb

Please sign in to comment.