Objectcaching (#3317)

This commit is contained in:
Andrea Bogazzi 2016-11-13 21:00:10 +01:00 committed by GitHub
parent 161a3e8acb
commit 9422fd39be
23 changed files with 377 additions and 187 deletions

View file

@ -198,9 +198,12 @@ fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.protot
}
fabric.copiedText = selectedText;
fabric.copiedTextStyle = this.getSelectionStyles(
this.selectionStart,
this.selectionEnd);
fabric.copiedTextStyle = fabric.util.object.clone(
this.getSelectionStyles(
this.selectionStart,
this.selectionEnd
)
);
e.stopImmediatePropagation();
e.preventDefault();
this._copyDone = true;

View file

@ -113,24 +113,8 @@
*/
_getNonTransformedDimensions: function() {
var strokeWidth = this.strokeWidth,
w = this.width,
h = this.height,
addStrokeToW = true,
addStrokeToH = true;
if (this.type === 'line' && this.strokeLineCap === 'butt') {
addStrokeToH = w;
addStrokeToW = h;
}
if (addStrokeToH) {
h += h < 0 ? -strokeWidth : strokeWidth;
}
if (addStrokeToW) {
w += w < 0 ? -strokeWidth : strokeWidth;
}
w = this.width + strokeWidth,
h = this.height + strokeWidth;
return { x: w, y: h };
},

View file

@ -1,6 +1,7 @@
(function() {
var extend = fabric.util.object.extend;
var extend = fabric.util.object.extend,
originalSet = 'stateProperties';
/*
Depends on `stateProperties`
@ -13,7 +14,7 @@
extend(origin[destination], tmpObj, deep);
}
function _isEqual(origValue, currentValue) {
function _isEqual(origValue, currentValue, firstPass) {
if (!fabric.isLikelyNode && origValue instanceof Element) {
// avoid checking deep html elements
return origValue === currentValue;
@ -29,6 +30,9 @@
});
}
else if (origValue instanceof Object) {
if (!firstPass && Object.keys(origValue).length !== Object.keys(currentValue).length) {
return false;
}
for (var key in origValue) {
if (!_isEqual(origValue[key], currentValue[key])) {
return false;
@ -46,10 +50,13 @@
/**
* Returns true if object state (one of its state properties) was changed
* @param {String} [propertySet] optional name for the set of property we want to save
* @return {Boolean} true if instance' state has changed since `{@link fabric.Object#saveState}` was called
*/
hasStateChanged: function() {
return !_isEqual(this.originalState, this);
hasStateChanged: function(propertySet) {
propertySet = propertySet || originalSet;
propertySet = '_' + propertySet;
return !_isEqual(this[propertySet], this, true);
},
/**
@ -58,9 +65,11 @@
* @return {fabric.Object} thisArg
*/
saveState: function(options) {
saveProps(this, 'originalState', this.stateProperties);
var propertySet = options && options.propertySet || originalSet,
destination = '_' + propertySet;
saveProps(this, destination, this[propertySet]);
if (options && options.stateProperties) {
saveProps(this, 'originalState', options.stateProperties);
saveProps(this, destination, options.stateProperties);
}
return this;
},
@ -71,7 +80,10 @@
* @return {fabric.Object} thisArg
*/
setupState: function(options) {
this.originalState = { };
options = options || { };
var propertySet = options.propertySet || originalSet;
options.propertySet = propertySet;
this['_' + propertySet] = { };
this.saveState(options);
return this;
}

View file

@ -69,9 +69,14 @@
value = '';
}
else if (attr === 'strokeDashArray') {
value = value.replace(/,/g, ' ').split(/\s+/).map(function(n) {
return parseFloat(n);
});
if (value === 'none') {
value = null;
}
else {
value = value.replace(/,/g, ' ').split(/\s+/).map(function(n) {
return parseFloat(n);
});
}
}
else if (attr === 'transformMatrix') {
if (parentAttributes && parentAttributes.transformMatrix) {

View file

@ -53,13 +53,8 @@
* @return {fabric.Circle} thisArg
*/
initialize: function(options) {
options = options || { };
this.callSuper('initialize', options);
this.set('radius', options.radius || 0);
this.startAngle = options.startAngle || this.startAngle;
this.endAngle = options.endAngle || this.endAngle;
this.set('radius', options && options.radius || 0);
},
/**

View file

@ -47,12 +47,9 @@
* @return {fabric.Ellipse} thisArg
*/
initialize: function(options) {
options = options || { };
this.callSuper('initialize', options);
this.set('rx', options.rx || 0);
this.set('ry', options.ry || 0);
this.set('rx', options && options.rx || 0);
this.set('ry', options && options.ry || 0);
},
/**

View file

@ -164,6 +164,7 @@
this.forEachObject(this._setObjectActive, this);
this._calcBounds();
this._updateObjectsCoords();
this.dirty = true;
return this;
},
@ -190,7 +191,7 @@
this.remove(object);
this._calcBounds();
this._updateObjectsCoords();
this.dirty = true;
return this;
},
@ -198,6 +199,7 @@
* @private
*/
_onObjectAdded: function(object) {
this.dirty = true;
object.group = this;
object._set('canvas', this.canvas);
},
@ -206,6 +208,7 @@
* @private
*/
_onObjectRemoved: function(object) {
this.dirty = true;
delete object.group;
object.set('active', false);
},
@ -264,27 +267,40 @@
* @param {CanvasRenderingContext2D} ctx context to render instance on
*/
render: function(ctx) {
// do not render if object is not visible
if (!this.visible) {
return;
}
ctx.save();
if (this.transformMatrix) {
ctx.transform.apply(ctx, this.transformMatrix);
}
this.transform(ctx);
this._setShadow(ctx);
this.clipTo && fabric.util.clipContext(this, ctx);
this._transformDone = true;
// the array is now sorted in order of highest first, so start from end
this.callSuper('render', ctx);
this._transformDone = false;
},
/**
* Execute the drawing operation for an object on a specified context
* @param {CanvasRenderingContext2D} ctx Context to render on
* @param {Boolean} [noTransform] When true, context is not transformed
*/
drawObject: function(ctx) {
for (var i = 0, len = this._objects.length; i < len; i++) {
this._renderObject(this._objects[i], ctx);
}
},
this.clipTo && ctx.restore();
ctx.restore();
this._transformDone = false;
/**
* Check if cache is dirty
*/
isCacheDirty: function() {
if (this.callSuper('isCacheDirty')) {
return true
}
if (!this.statefullCache) {
return false;
}
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);
return true
}
}
return false;
},
/**

View file

@ -112,6 +112,15 @@
*/
stateProperties: stateProperties,
/**
* When `true`, object is cached on an additional canvas.
* default to false for images
* since 1.7.0
* @type Boolean
* @default
*/
objectCaching: false,
/**
* Constructor
* @param {HTMLImageElement | String} element Image element

View file

@ -811,7 +811,7 @@
leftOffset = this._getLeftOffset(),
topOffset = this._getTopOffset(),
line, _char, style;
ctx.save();
for (var i = 0, len = this._textLines.length; i < len; i++) {
heightOfLine = this._getHeightOfLine(ctx, i);
line = this._textLines[i];
@ -842,6 +842,7 @@
}
lineTopOffset += heightOfLine;
}
ctx.restore();
},
/**

View file

@ -62,8 +62,6 @@
* @return {fabric.Line} thisArg
*/
initialize: function(points, options) {
options = options || { };
if (!points) {
points = [0, 0, 0, 0];
}
@ -206,6 +204,23 @@
return extend(this.callSuper('toObject', propertiesToInclude), this.calcLinePoints());
},
/*
* Calculate object dimensions from its properties
* @private
*/
_getNonTransformedDimensions: function() {
var dim = this.callSuper('_getNonTransformedDimensions');
if (this.strokeLineCap === 'butt') {
if (dim.x === 0) {
dim.y -= this.strokeWidth;
}
if (dim.y === 0) {
dim.x -= this.strokeWidth;
}
}
return dim;
},
/**
* Recalculates line points given width and height
* @private

View file

@ -7,7 +7,8 @@
toFixed = fabric.util.toFixed,
capitalize = fabric.util.string.capitalize,
degreesToRadians = fabric.util.degreesToRadians,
supportsLineDash = fabric.StaticCanvas.supports('setLineDash');
supportsLineDash = fabric.StaticCanvas.supports('setLineDash'),
objectCaching = !fabric.isLikelyNode;
if (fabric.Object) {
return;
@ -743,7 +744,6 @@
* @type Boolean
* @default
*/
lockScalingFlip: false,
/**
@ -752,8 +752,39 @@
* @type Boolean
* @default
*/
excludeFromExport: false,
excludeFromExport: false,
/**
* When `true`, object is cached on an additional canvas.
* default to true
* since 1.7.0
* @type Boolean
* @default
*/
objectCaching: objectCaching,
/**
* When `true`, object properties are checked for cache invalidation. In some particular
* situation you may want this to be disabled ( spray brush, very big pathgroups, groups)
* or if your application does not allow you to modify properties for groups child you want
* to disable it for groups.
* default to true
* since 1.7.0
* @type Boolean
* @default
*/
statefullCache: false,
/**
* When `true`, cache does not get updated during scaling. The picture will get blocky if scaled
* too much and will be redrawn with correct details at the end of scaling.
* this setting is performance and application dependant.
* default to false
* since 1.7.0
* @type Boolean
* @default
*/
noScaleCache: true,
/**
* List of properties to consider when checking if state
@ -761,21 +792,80 @@
* as well as for history (undo/redo) purposes
* @type Array
*/
stateProperties: (
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'
).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'
).split(' '),
/**
* Constructor
* @param {Object} [options] Options object
*/
initialize: function(options) {
options = options || { };
if (options) {
this.setOptions(options);
}
if (this.objectCaching) {
this._createCacheCanvas();
this.setupState({ propertySet: 'cacheProperties' });
}
},
/**
* Create a the canvas used to keep the cached copy of the object
* @private
*/
_createCacheCanvas: function() {
this._cacheCanvas = fabric.document.createElement('canvas');
this._cacheContext = this._cacheCanvas.getContext('2d');
this._updateCacheCanvas();
},
/**
* Update width and height of the canvas for cache
* returns true or false if canvas needed resize.
* @private
* @return {Boolean} true if the canvas has been resized
*/
_updateCacheCanvas: function() {
if (this.noScaleCache && this.canvas && this.canvas._currentTransform) {
var action = this.canvas._currentTransform.action;
if (action.slice(0, 5) === 'scale') {
return false;
}
}
var zoom = this.getViewportTransform()[0],
objectScale = this.getObjectScaling(),
dim = this._getNonTransformedDimensions(),
retina = this.canvas && this.canvas._isRetinaScaling() ? fabric.devicePixelRatio : 1,
zoomX = objectScale.scaleX * zoom * retina,
zoomY = objectScale.scaleY * zoom * retina;
if (zoomX !== this.zoomX || zoomY !== this.zoomY) {
var width = dim.x * zoomX,
height = dim.y * zoomY;
this._cacheCanvas.width = width;
this._cacheCanvas.height = height;
this._cacheContext.translate(width / 2, height / 2);
this._cacheContext.scale(zoomX, zoomY);
this.cacheWidth = width;
this.cacheHeight = height;
this.zoomX = zoomX;
this.zoomY = zoomY;
return true
}
return false
},
/**
@ -1087,9 +1177,7 @@
if ((this.width === 0 && this.height === 0) || !this.visible) {
return;
}
ctx.save();
//setup fill rule for current object
this._setupCompositeOperation(ctx);
this.drawSelectionBackground(ctx);
@ -1098,21 +1186,71 @@
}
this._setOpacity(ctx);
this._setShadow(ctx);
this._renderBackground(ctx);
this._setStrokeStyles(ctx);
this._setFillStyles(ctx);
if (this.transformMatrix) {
ctx.transform.apply(ctx, this.transformMatrix);
}
this.clipTo && fabric.util.clipContext(this, ctx);
this._render(ctx, noTransform);
if (this.objectCaching && !this.group) {
if (this.isCacheDirty(noTransform)) {
this.saveState({ propertySet: 'cacheProperties' });
this.drawObject(this._cacheContext, noTransform);
this.dirty = false;
}
this.drawCacheOnCanvas(ctx);
}
else {
this.drawObject(ctx, noTransform);
noTransform && this.saveState({ propertySet: 'cacheProperties' });
}
this.clipTo && ctx.restore();
ctx.restore();
},
/**
* Draws a background for the object big as its width and height;
* Execute the drawing operation for an object on a specified context
* @param {CanvasRenderingContext2D} ctx Context to render on
* @param {Boolean} [noTransform] When true, context is not transformed
*/
drawObject: function(ctx, noTransform) {
this._renderBackground(ctx);
this._setStrokeStyles(ctx);
this._setFillStyles(ctx);
this._render(ctx, noTransform);
},
/**
* Paint the cached copy of the object on the target context.
* @param {CanvasRenderingContext2D} ctx Context to render on
*/
drawCacheOnCanvas: function(ctx) {
ctx.scale(1 / this.zoomX, 1 / this.zoomY);
ctx.drawImage(this._cacheCanvas, -this.cacheWidth / 2, -this.cacheHeight / 2);
},
/**
* Check if cache is dirty
* @param {Boolean} skipCanvas skip canvas checks because this object is painted
* on parent canvas.
*/
isCacheDirty: function(skipCanvas) {
if (!skipCanvas && this._updateCacheCanvas()) {
// in this case the context is already cleared.
return true;
}
else {
if (this.dirty || (this.statefullCache && this.hasStateChanged('cacheProperties'))) {
if (!skipCanvas) {
var dim = this._getNonTransformedDimensions();
this._cacheContext.clearRect(-dim.x / 2, -dim.y / 2, dim.x, dim.y);
}
return true;
}
}
return false;
},
/**
* Draws a background for the object big as its untrasformed dimensions
* @private
* @param {CanvasRenderingContext2D} ctx Context to render on
*/
@ -1120,14 +1258,14 @@
if (!this.backgroundColor) {
return;
}
var dim = this._getNonTransformedDimensions();
ctx.fillStyle = this.backgroundColor;
ctx.fillRect(
-this.width / 2,
-this.height / 2,
this.width,
this.height
-dim.x / 2,
-dim.y / 2,
dim.x,
dim.y
);
// if there is background color no other shadows
// should be casted
@ -1139,9 +1277,6 @@
* @param {CanvasRenderingContext2D} ctx Context to render on
*/
_setOpacity: function(ctx) {
if (this.group) {
this.group._setOpacity(ctx);
}
ctx.globalAlpha *= this.opacity;
},

View file

@ -75,7 +75,9 @@
initialize: function(path, options) {
options = options || { };
this.setOptions(options);
if (options) {
this.setOptions(options);
}
if (!path) {
path = [];
@ -101,6 +103,10 @@
if (options.sourcePath) {
this.setSourcePath(options.sourcePath);
}
if (this.objectCaching) {
this._createCacheCanvas();
this.setupState({ propertySet: 'cacheProperties' });
}
},
/**

View file

@ -19,7 +19,7 @@
* @tutorial {@link http://fabricjs.com/fabric-intro-part-1#path_and_pathgroup}
* @see {@link fabric.PathGroup#initialize} for constructor definition
*/
fabric.PathGroup = fabric.util.createClass(fabric.Path, /** @lends fabric.PathGroup.prototype */ {
fabric.PathGroup = fabric.util.createClass(fabric.Object, /** @lends fabric.PathGroup.prototype */ {
/**
* Type of an object
@ -56,10 +56,13 @@
}
this.setOptions(options);
this.setCoords();
if (options.sourcePath) {
this.setSourcePath(options.sourcePath);
}
if (this.objectCaching) {
this._createCacheCanvas();
this.setupState({ propertySet: 'cacheProperties' });
}
},
/**
@ -93,32 +96,39 @@
},
/**
* Renders this group on a specified context
* @param {CanvasRenderingContext2D} ctx Context to render this instance on
* Execute the drawing operation for an object on a specified context
* @param {CanvasRenderingContext2D} ctx Context to render on
* @param {Boolean} [noTransform] When true, context is not transformed
*/
render: function(ctx) {
// do not render if object is not visible
if (!this.visible) {
return;
}
drawObject: function(ctx) {
ctx.save();
if (this.transformMatrix) {
ctx.transform.apply(ctx, this.transformMatrix);
}
this.transform(ctx);
this._setShadow(ctx);
this.clipTo && fabric.util.clipContext(this, ctx);
ctx.translate(-this.width / 2, -this.height / 2);
for (var i = 0, l = this.paths.length; i < l; ++i) {
this.paths[i].render(ctx, true);
}
this.clipTo && ctx.restore();
ctx.restore();
},
/**
* Check if cache is dirty
*/
isCacheDirty: function() {
if (this.callSuper('isCacheDirty')) {
return true
}
if (!this.statefullCache) {
return false;
}
for (var i = 0, len = this.paths.length; i < len; i++) {
if (this.paths[i].isCacheDirty(true)) {
var dim = this._getNonTransformedDimensions();
this._cacheContext.clearRect(-dim.x / 2, -dim.y / 2, dim.x, dim.y);
return true
}
}
return false;
},
/**
* Sets certain property to a certain value
* @param {String} prop

View file

@ -56,7 +56,7 @@
* @return {fabric.Polygon} thisArg
*/
initialize: function(points, options) {
options = options || { };
options = options || {};
this.points = points || [];
this.callSuper('initialize', options);
this._calcDimensions();

View file

@ -62,8 +62,6 @@
* @return {Object} thisArg
*/
initialize: function(options) {
options = options || { };
this.callSuper('initialize', options);
this._initRxRy();

View file

@ -23,9 +23,24 @@
'textAlign',
'fontStyle',
'lineHeight',
'textBackgroundColor'
'textBackgroundColor',
'charSpacing'
);
var cacheProperties = fabric.Object.prototype.cacheProperties.concat();
cacheProperties.push(
'fontFamily',
'fontWeight',
'fontSize',
'text',
'textDecoration',
'textAlign',
'fontStyle',
'lineHeight',
'textBackgroundColor',
'charSpacing',
'styles'
);
/**
* Text class
* @class fabric.Text
@ -41,17 +56,16 @@
* @type Object
* @private
*/
_dimensionAffectingProps: {
fontSize: true,
fontWeight: true,
fontFamily: true,
fontStyle: true,
lineHeight: true,
text: true,
charSpacing: true,
textAlign: true,
strokeWidth: false,
},
_dimensionAffectingProps: [
'fontSize',
'fontWeight',
'fontFamily',
'fontStyle',
'lineHeight',
'text',
'charSpacing',
'textAlign'
],
/**
* @private
@ -290,6 +304,12 @@
*/
stateProperties: stateProperties,
/**
* List of properties to consider when checking if cache needs refresh
* @type Array
*/
cacheProperties: cacheProperties,
/**
* When defined, an object is rendered via stroke and this property specifies its color.
* <b>Backwards incompatibility note:</b> This property was named "strokeStyle" until v1.1.6
@ -336,9 +356,10 @@
options = options || { };
this.text = text;
this.__skipDimension = true;
this.setOptions(options);
this.callSuper('initialize', options);
this.__skipDimension = false;
this._initDimensions();
this.setupState({ propertySet: '_dimensionAffectingProps' });
},
/**
@ -377,16 +398,13 @@
* @param {CanvasRenderingContext2D} ctx Context to render on
*/
_render: function(ctx) {
this.clipTo && fabric.util.clipContext(this, ctx);
this._setOpacity(ctx);
this._setShadow(ctx);
this._setupCompositeOperation(ctx);
this._renderTextBackground(ctx);
this._setStrokeStyles(ctx);
this._setFillStyles(ctx);
this._setTextStyles(ctx);
if (this.group && this.group.type === 'path-group') {
ctx.translate(this.left, this.top);
}
this._renderTextLinesBackground(ctx);
this._renderText(ctx);
this._renderTextDecoration(ctx);
this.clipTo && ctx.restore();
},
/**
@ -629,15 +647,6 @@
return this.fontSize * this._fontSizeMult;
},
/**
* @private
* @param {CanvasRenderingContext2D} ctx Context to render on
*/
_renderTextBackground: function(ctx) {
this._renderBackground(ctx);
this._renderTextLinesBackground(ctx);
},
/**
* @private
* @param {CanvasRenderingContext2D} ctx Context to render on
@ -647,7 +656,7 @@
return;
}
var lineTopOffset = 0, heightOfLine,
lineWidth, lineLeftOffset;
lineWidth, lineLeftOffset, originalFill = ctx.fillStye;
ctx.fillStyle = this.textBackgroundColor;
for (var i = 0, len = this._textLines.length; i < len; i++) {
@ -664,6 +673,7 @@
}
lineTopOffset += heightOfLine;
}
ctx.fillStyle = originalFill;
// if there is text background color no
// other shadows should be casted
this._removeShadow(ctx);
@ -695,17 +705,15 @@
/**
* @private
*/
_shouldClearCache: function() {
_shouldClearDimensionCache: function() {
var shouldClear = false;
if (this._forceClearCache) {
this._forceClearCache = false;
return true;
}
for (var prop in this._dimensionAffectingProps) {
if (this['__' + prop] !== this[prop]) {
this['__' + prop] = this[prop];
shouldClear = true;
}
shouldClear = this.hasStateChanged('_dimensionAffectingProps');
if (shouldClear) {
this.saveState({ propertySet: '_dimensionAffectingProps' });
}
return shouldClear;
},
@ -836,25 +844,10 @@
if (!this.visible) {
return;
}
ctx.save();
this._setTextStyles(ctx);
if (this._shouldClearCache()) {
if (this._shouldClearDimensionCache()) {
this._initDimensions(ctx);
}
this.drawSelectionBackground(ctx);
if (!noTransform) {
this.transform(ctx);
}
if (this.transformMatrix) {
ctx.transform.apply(ctx, this.transformMatrix);
}
if (this.group && this.group.type === 'path-group') {
ctx.translate(this.left, this.top);
}
this._render(ctx);
ctx.restore();
this.callSuper('render', ctx, noTransform);
},
/**
@ -1086,7 +1079,7 @@
_set: function(key, value) {
this.callSuper('_set', key, value);
if (key in this._dimensionAffectingProps) {
if (this._dimensionAffectingProps.indexOf(key) > -1) {
this._initDimensions();
this.setCoords();
}

View file

@ -66,12 +66,12 @@
* @return {fabric.Textbox} thisArg
*/
initialize: function(text, options) {
this.ctx = fabric.util.createCanvasElement().getContext('2d');
this.callSuper('initialize', text, options);
this.setControlsVisibility(fabric.Textbox.getTextboxControlVisibility());
this.ctx = this.objectCaching ? this._cacheContext : fabric.util.createCanvasElement().getContext('2d');
// add width to this list of props that effect line wrapping.
this._dimensionAffectingProps.width = true;
this._dimensionAffectingProps.push('width');
},
/**

View file

@ -31,12 +31,9 @@
* @return {Object} thisArg
*/
initialize: function(options) {
options = options || { };
this.callSuper('initialize', options);
this.set('width', options.width || 100)
.set('height', options.height || 100);
this.set('width', options && options.width || 100)
.set('height', options && options.height || 100);
},
/**

View file

@ -93,7 +93,7 @@
* @type Boolean
* @default
*/
stateful: true,
stateful: false,
/**
* Indicates whether {@link fabric.Collection.add}, {@link fabric.Collection.insertAt} and {@link fabric.Collection.remove} should also re-render canvas.
@ -645,7 +645,7 @@
* @return {Number}
*/
getZoom: function () {
return Math.sqrt(this.viewportTransform[0] * this.viewportTransform[3]);
return this.viewportTransform[0];
},
/**

View file

@ -23,7 +23,9 @@
}
else if (source instanceof Object) {
for (var property in source) {
destination[property] = clone(source[property], deep)
if (source.hasOwnProperty(property)) {
destination[property] = clone(source[property], deep)
}
}
}
else {

View file

@ -140,7 +140,8 @@
var Canvas = fabric.Canvas;
fabric.Canvas = null;
var el = fabric.document.createElement('canvas');
el.width = 600; el.height = 600;
el.width = 600;
el.height = 600;
var canvas = this.canvas = fabric.isLikelyNode ? fabric.createCanvasForNode() : new fabric.StaticCanvas(el),
canvas2 = this.canvas2 = fabric.isLikelyNode ? fabric.createCanvasForNode() : new fabric.StaticCanvas(el);
@ -180,7 +181,7 @@
ok('overlayVpt' in canvas);
equal(canvas.includeDefaultValues, true);
equal(canvas.stateful, true);
equal(canvas.stateful, false);
equal(canvas.renderOnAddRemove, true);
equal(canvas.controlsAboveOverlay, false);
equal(canvas.allowTouchScrolling, false);

View file

@ -516,7 +516,7 @@
test('test group transformMatrix', function() {
var rect1 = new fabric.Rect({ top: 1, left: 1, width: 2, height: 2, strokeWidth: 0, fill: 'red', opacity: 1}),
rect2 = new fabric.Rect({ top: 4, left: 4, width: 2, height: 2, strokeWidth: 0, fill: 'red', opacity: 1}),
rect2 = new fabric.Rect({ top: 5, left: 5, width: 2, height: 2, strokeWidth: 0, fill: 'red', opacity: 1}),
group = new fabric.Group([rect1, rect2], {opacity: 1, fill: 'blue', strokeWidth: 0}),
isTransparent = fabric.util.isTransparent,
ctx = canvas.contextContainer;
@ -526,14 +526,25 @@
equal(isTransparent(ctx, 1, 1, 0), false, '1,1 is opaque');
equal(isTransparent(ctx, 2, 2, 0), false, '2,2 is opaque');
equal(isTransparent(ctx, 3, 3, 0), true, '3,3 is transparent');
equal(isTransparent(ctx, 4, 4, 0), false, '4,4 is opaque');
group.transformMatrix = [2, 0, 0, 2, 1, 1];
equal(isTransparent(ctx, 4, 4, 0), true, '4,4 is transparent');
equal(isTransparent(ctx, 5, 5, 0), false, '5,5 is opaque');
equal(isTransparent(ctx, 6, 6, 0), false, '6,6 is opaque');
equal(isTransparent(ctx, 7, 7, 0), true, '7,7 is transparent');
group.transformMatrix = [2, 0, 0, 2, 2, 2];
canvas.renderAll();
equal(isTransparent(ctx, 0, 0, 0), true, '0,0 is transparent');
equal(isTransparent(ctx, 1, 1, 0), true, '1,1 is transparent');
equal(isTransparent(ctx, 2, 2, 0), true, '2,2 is transparent');
equal(isTransparent(ctx, 0, 0, 0), false, '0,0 is opaque');
equal(isTransparent(ctx, 1, 1, 0), false, '1,1 is opaque');
equal(isTransparent(ctx, 2, 2, 0), false, '2,2 is opaque');
equal(isTransparent(ctx, 3, 3, 0), false, '3,3 is opaque');
equal(isTransparent(ctx, 4, 4, 0), false, '4,4 is opaque');
equal(isTransparent(ctx, 4, 4, 0), true, '4,4 is transparent');
equal(isTransparent(ctx, 5, 5, 0), true, '5,5 is transparent');
equal(isTransparent(ctx, 6, 6, 0), true, '6,6 is transparent');
equal(isTransparent(ctx, 7, 7, 0), true, '7,7 is transparent');
equal(isTransparent(ctx, 8, 8, 0), false, '8,8 is opaque');
equal(isTransparent(ctx, 9, 9, 0), false, '9,9 is opaque');
equal(isTransparent(ctx, 10, 10, 0), false, '10,10 is opaque');
equal(isTransparent(ctx, 11, 11, 0), false, '11,11 is opaque');
equal(isTransparent(ctx, 12, 12, 0), true, '12,12 is transparent');
});
// asyncTest('cloning group with image', function() {
// var rect = new fabric.Rect({ top: 100, left: 100, width: 30, height: 10 }),

View file

@ -20,8 +20,8 @@
cObj.set('left', 123).set('top', 456);
cObj.saveState();
cObj.set('left', 223).set('top', 556);
equal(cObj.originalState.left, 123);
equal(cObj.originalState.top, 456);
equal(cObj._stateProperties.left, 123);
equal(cObj._stateProperties.top, 456);
});
test('saveState with extra props', function() {
@ -32,19 +32,19 @@
var extraProps = ['prop1', 'prop2'];
var options = { stateProperties: extraProps };
cObj.setupState(options);
equal(cObj.originalState.prop1, 'a', 'it saves the extra props');
equal(cObj.originalState.prop2, 'b', 'it saves the extra props');
equal(cObj._stateProperties.prop1, 'a', 'it saves the extra props');
equal(cObj._stateProperties.prop2, 'b', 'it saves the extra props');
cObj.prop1 = 'c';
ok(cObj.hasStateChanged(), 'it detects changes in extra props');
equal(cObj.originalState.left, 123, 'normal props are still there');
equal(cObj._stateProperties.left, 123, 'normal props are still there');
});
test('saveState with array', function() {
var cObj = new fabric.Text('Hello');
cObj.set('textDecoration', ['underline']);
cObj.setupState();
deepEqual(cObj.textDecoration, cObj.originalState.textDecoration, 'textDecoration in state is deepEqual');
notEqual(cObj.textDecoration, cObj.originalState.textDecoration, 'textDecoration in not same Object');
deepEqual(cObj.textDecoration, cObj._stateProperties.textDecoration, 'textDecoration in state is deepEqual');
notEqual(cObj.textDecoration, cObj._stateProperties.textDecoration, 'textDecoration in not same Object');
cObj.textDecoration[0] = 'overline';
ok(cObj.hasStateChanged(), 'hasStateChanged detects changes in nested props');