diff --git a/CHANGELOG.md b/CHANGELOG.md
index d23f97f1..466afb4f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,7 @@
**Edge**
+- [BACK_INCOMPAT] `fabric.StaticCanvas#backgroundImage` and `fabric.StaticCanvas#overlayImage` are `fabric.Image` instances. `fabric.StaticCanvas#backgroundImageOpacity`, `fabric.StaticCanvas#backgroundImageStretch`, `fabric.StaticCanvas#overlayImageLeft` and `fabric.StaticCanvas#overlayImageTop` were removed.
+
- [BACK_INCOMPAT] `fabric.Text#backgroundColor` is now `fabric.Object#backgroundColor`
- [BACK_INCOMPAT] Remove `fabric.Object#toGrayscale` and `fabric.Object#overlayFill` since they're too specific
diff --git a/src/mixins/canvas_serialization.mixin.js b/src/mixins/canvas_serialization.mixin.js
index ec50af64..86bb3ad8 100644
--- a/src/mixins/canvas_serialization.mixin.js
+++ b/src/mixins/canvas_serialization.mixin.js
@@ -50,72 +50,74 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati
var _this = this;
this._enlivenObjects(serialized.objects, function () {
- _this._setBgOverlayImages(serialized, callback);
+ _this._setBgOverlay(serialized, callback);
}, reviver);
return this;
},
- _setBgOverlayImages: function(serialized, callback) {
-
+ /**
+ * @private
+ * @param {Object} serialized Object with background and overlay information
+ * @param {Function} callback Invoked after all background and overlay images/patterns loaded
+ */
+ _setBgOverlay: function(serialized, callback) {
var _this = this,
- backgroundPatternLoaded,
- backgroundImageLoaded,
- overlayImageLoaded;
+ loaded = {
+ backgroundColor: false,
+ overlayColor: false,
+ backgroundImage: false,
+ overlayImage: false
+ };
+
+ if (!serialized.backgroundImage && !serialized.overlayImage && !serialized.background && !serialized.overlay) {
+ callback && callback();
+ return;
+ }
var cbIfLoaded = function () {
- callback && backgroundImageLoaded && overlayImageLoaded && backgroundPatternLoaded && callback();
+ if (loaded.backgroundImage && loaded.overlayImage && loaded.backgroundColor && loaded.overlayColor) {
+ _this.renderAll();
+ callback && callback();
+ }
};
- if (serialized.backgroundImage) {
- this.setBackgroundImage(serialized.backgroundImage, function() {
+ this.__setBgOverlay('backgroundImage', serialized.backgroundImage, loaded, cbIfLoaded);
+ this.__setBgOverlay('overlayImage', serialized.overlayImage, loaded, cbIfLoaded);
+ this.__setBgOverlay('backgroundColor', serialized.background, loaded, cbIfLoaded);
+ this.__setBgOverlay('overlayColor', serialized.overlay, loaded, cbIfLoaded);
- _this.backgroundImageOpacity = serialized.backgroundImageOpacity;
- _this.backgroundImageStretch = serialized.backgroundImageStretch;
+ cbIfLoaded();
+ },
- _this.renderAll();
+ /**
+ * @private
+ * @param {String} property Property to set (backgroundImage, overlayImage, backgroundColor, overlayColor)
+ * @param {(Object|String)} value Value to set
+ * @param {Object} loaded Set loaded property to true if property is set
+ * @param {Object} callback Callback function to invoke after property is set
+ */
+ __setBgOverlay: function(property, value, loaded, callback) {
+ var _this = this;
- backgroundImageLoaded = true;
+ if (!value) {
+ loaded[property] = true;
+ return;
+ }
- cbIfLoaded();
+ if (property === 'backgroundImage' || property === 'overlayImage') {
+ fabric.Image.fromObject(value, function(img) {
+ _this[property] = img;
+ loaded[property] = true;
+ callback && callback();
});
}
else {
- backgroundImageLoaded = true;
- }
-
- if (serialized.overlayImage) {
- this.setOverlayImage(serialized.overlayImage, function() {
-
- _this.overlayImageLeft = serialized.overlayImageLeft || 0;
- _this.overlayImageTop = serialized.overlayImageTop || 0;
-
- _this.renderAll();
- overlayImageLoaded = true;
-
- cbIfLoaded();
+ this['set' + fabric.util.string.capitalize(property, true)](value, function() {
+ loaded[property] = true;
+ callback && callback();
});
}
- else {
- overlayImageLoaded = true;
- }
-
- if (serialized.background) {
- this.setBackgroundColor(serialized.background, function() {
-
- _this.renderAll();
- backgroundPatternLoaded = true;
-
- cbIfLoaded();
- });
- }
- else {
- backgroundPatternLoaded = true;
- }
-
- if (!serialized.backgroundImage && !serialized.overlayImage && !serialized.background) {
- callback && callback();
- }
},
/**
diff --git a/src/parser.js b/src/parser.js
index 951a100c..e00388ba 100644
--- a/src/parser.js
+++ b/src/parser.js
@@ -805,23 +805,31 @@
* @return {String}
*/
function createSVGRefElementsMarkup(canvas) {
- var markup = '';
+ var markup = [ ];
- if (canvas.backgroundColor && canvas.backgroundColor.source) {
- markup = [
- '',
' '
- ].join('');
+ );
}
-
- return markup;
}
/**
diff --git a/src/shapes/image.class.js b/src/shapes/image.class.js
index 85b13cd9..9d18d9a9 100644
--- a/src/shapes/image.class.js
+++ b/src/shapes/image.class.js
@@ -201,6 +201,7 @@
'" transform="translate(' + (-this.width/2) + ' ' + (-this.height/2) + ')',
'" width="', this.width,
'" height="', this.height,
+ '" preserveAspectRatio="none"',
'">'
);
diff --git a/src/static_canvas.class.js b/src/static_canvas.class.js
index 6a5cbacf..62c67cd9 100644
--- a/src/static_canvas.class.js
+++ b/src/static_canvas.class.js
@@ -44,56 +44,41 @@
},
/**
- * Background color of canvas instance
- * @type String
+ * Background color of canvas instance.
+ * Should be set via {@link fabric.StaticCanvas#setBackgroundColor}.
+ * @type {(String|fabric.Pattern)}
* @default
*/
backgroundColor: '',
/**
- * Background image of canvas instance
- * Should be set via {@link fabric.StaticCanvas#setBackgroundImage}
- * @type String
+ * Background image of canvas instance.
+ * Should be set via {@link fabric.StaticCanvas#setBackgroundImage}.
+ * Backwards incompatibility note: The "backgroundImageOpacity" and "backgroundImageStretch" properties are deprecated since 1.3.9.
+ * Use {@link fabric.Image#opacity}, {@link fabric.Image#width} and {@link fabric.Image#height}.
+ * @type fabric.Image
* @default
*/
- backgroundImage: '',
+ backgroundImage: null,
/**
- * Opacity of the background image of the canvas instance
- * @type Float
+ * Overlay color of canvas instance.
+ * Should be set via {@link fabric.StaticCanvas#setOverlayColor}
+ * @since 1.3.9
+ * @type {(String|fabric.Pattern)}
* @default
*/
- backgroundImageOpacity: 1,
+ overlayColor: '',
/**
- * Indicates whether the background image should be stretched to fit the
- * dimensions of the canvas instance.
- * @type Boolean
+ * Overlay image of canvas instance.
+ * Should be set via {@link fabric.StaticCanvas#setOverlayImage}.
+ * Backwards incompatibility note: The "overlayImageLeft" and "overlayImageTop" properties are deprecated since 1.3.9.
+ * Use {@link fabric.Image#left} and {@link fabric.Image#top}.
+ * @type fabric.Image
* @default
*/
- backgroundImageStretch: true,
-
- /**
- * Overlay image of canvas instance
- * Should be set via {@link fabric.StaticCanvas#setOverlayImage}
- * @type String
- * @default
- */
- overlayImage: '',
-
- /**
- * Left offset of overlay image (if present)
- * @type Number
- * @default
- */
- overlayImageLeft: 0,
-
- /**
- * Top offset of overlay image (if present)
- * @type Number
- * @default
- */
- overlayImageTop: 0,
+ overlayImage: null,
/**
* Indicates whether toObject/toDatalessObject should include default values
@@ -168,6 +153,9 @@
if (options.backgroundColor) {
this.setBackgroundColor(options.backgroundColor, this.renderAll.bind(this));
}
+ if (options.overlayColor) {
+ this.setOverlayColor(options.overlayColor, this.renderAll.bind(this));
+ }
this.calcOffset();
},
@@ -184,74 +172,115 @@
/**
* Sets {@link fabric.StaticCanvas#overlayImage|overlay image} for this canvas
- * @param {String} url url of an image to set overlay to
+ * @param {(fabric.Image|String)} image fabric.Image instance or URL of an image to set overlay to
* @param {Function} callback callback to invoke when image is loaded and set as an overlay
- * @param {Object} [options] Optional options to set for the overlay image
- * @param {Number} [options.overlayImageLeft] {@link fabric.StaticCanvas#overlayImageLeft|Left offset} of overlay image
- * @param {Number} [options.overlayImageTop] {@link fabric.StaticCanvas#overlayImageTop|Top offset} of overlay image
+ * @param {Object} [options] Optional options to set for the {@link fabric.Image|overlay image}.
* @return {fabric.Canvas} thisArg
* @chainable
* @see {@link http://jsfiddle.net/fabricjs/MnzHT/|jsFiddle demo}
- * @example
Normal overlayImage
- * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas));
- * @example Displaced overlayImage (left and top != 0)
+ * @example Normal overlayImage with left/top = 0
* canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), {
- * overlayImageLeft: 100,
- * overlayImageTop: 100
+ * // Needed to position overlayImage at 0/0
+ * originX: 'left',
+ * originY: 'top'
+ * });
+ * @example overlayImage with different properties
+ * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), {
+ * opacity: 0.5,
+ * angle: 45,
+ * left: 400,
+ * top: 400,
+ * originX: 'left',
+ * originY: 'top'
+ * });
+ * @example Stretched overlayImage #1 - width/height correspond to canvas width/height
+ * fabric.Image.fromURL('http://fabricjs.com/assets/jail_cell_bars.png', function(img) {
+ * img.set({width: canvas.width, height: canvas.height, originX: 'left', originY: 'top'});
+ * canvas.setOverlayImage(img, canvas.renderAll.bind(canvas));
+ * });
+ * @example Stretched overlayImage #2 - width/height correspond to canvas width/height
+ * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), {
+ * width: canvas.width,
+ * height: canvas.height,
+ * // Needed to position overlayImage at 0/0
+ * originX: 'left',
+ * originY: 'top'
* });
*/
- setOverlayImage: function (url, callback, options) { // TODO (kangax): test callback
- fabric.util.loadImage(url, function(img) {
- this.overlayImage = img;
- if (options && ('overlayImageLeft' in options)) {
- this.overlayImageLeft = options.overlayImageLeft;
- }
- if (options && ('overlayImageTop' in options)) {
- this.overlayImageTop = options.overlayImageTop;
- }
- callback && callback();
- }, this);
-
- return this;
+ setOverlayImage: function (image, callback, options) {
+ return this.__setBgOverlayImage('overlayImage', image, callback, options);
},
/**
* Sets {@link fabric.StaticCanvas#backgroundImage|background image} for this canvas
- * @param {String} url URL of an image to set background to
- * @param {Function} callback callback to invoke when image is loaded and set as background
- * @param {Object} [options] Optional options to set for the background image
- * @param {Float} [options.backgroundImageOpacity] {@link fabric.StaticCanvas#backgroundImageOpacity|Opacity} of the background image of the canvas instance
- * @param {Boolean} [options.backgroundImageStretch] Indicates whether the background image should be {@link fabric.StaticCanvas#backgroundImageStretch|strechted} to fit the canvas
+ * @param {(fabric.Image|String)} image fabric.Image instance or URL of an image to set background to
+ * @param {Function} callback Callback to invoke when image is loaded and set as background
+ * @param {Object} [options] Optional options to set for the {@link fabric.Image|background image}.
* @return {fabric.Canvas} thisArg
* @chainable
* @see {@link http://jsfiddle.net/fabricjs/YH9yD/|jsFiddle demo}
- * @example Normal backgroundImage
- * canvas.setBackgroundImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas));
- * @example Stretched backgroundImage with opacity
- * canvas.setBackgroundImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), {
- * backgroundImageOpacity: 0.5,
- * backgroundImageStretch: true
+ * @example Normal backgroundImage with left/top = 0
+ * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), {
+ * // Needed to position backgroundImage at 0/0
+ * originX: 'left',
+ * originY: 'top'
+ * });
+ * @example backgroundImage with different properties
+ * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), {
+ * opacity: 0.5,
+ * angle: 45,
+ * left: 400,
+ * top: 400,
+ * originX: 'left',
+ * originY: 'top'
+ * });
+ * @example Stretched backgroundImage #1 - width/height correspond to canvas width/height
+ * fabric.Image.fromURL('http://fabricjs.com/assets/honey_im_subtle.png', function(img) {
+ * img.set({width: canvas.width, height: canvas.height, originX: 'left', originY: 'top'});
+ * canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas));
+ * });
+ * @example Stretched backgroundImage #2 - width/height correspond to canvas width/height
+ * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), {
+ * width: canvas.width,
+ * height: canvas.height,
+ * // Needed to position backgroundImage at 0/0
+ * originX: 'left',
+ * originY: 'top'
* });
*/
- setBackgroundImage: function (url, callback, options) {
- fabric.util.loadImage(url, function(img) {
- this.backgroundImage = img;
- if (options && ('backgroundImageOpacity' in options)) {
- this.backgroundImageOpacity = options.backgroundImageOpacity;
- }
- if (options && ('backgroundImageStretch' in options)) {
- this.backgroundImageStretch = options.backgroundImageStretch;
- }
- callback && callback();
- }, this);
+ setBackgroundImage: function (image, callback, options) {
+ return this.__setBgOverlayImage('backgroundImage', image, callback, options);
+ },
- return this;
+ /**
+ * Sets {@link fabric.StaticCanvas#overlayColor|background color} for this canvas
+ * @param {(String|fabric.Pattern)} overlayColor Color or pattern to set background color to
+ * @param {Function} callback Callback to invoke when background color is set
+ * @return {fabric.Canvas} thisArg
+ * @chainable
+ * @see {@link http://jsfiddle.net/fabricjs/pB55h/|jsFiddle demo}
+ * @example Normal overlayColor - color value
+ * canvas.setOverlayColor('rgba(255, 73, 64, 0.6)', canvas.renderAll.bind(canvas));
+ * @example fabric.Pattern used as overlayColor
+ * canvas.setOverlayColor({
+ * source: 'http://fabricjs.com/assets/escheresque_ste.png'
+ * }, canvas.renderAll.bind(canvas));
+ * @example fabric.Pattern used as overlayColor with repeat and offset
+ * canvas.setOverlayColor({
+ * source: 'http://fabricjs.com/assets/escheresque_ste.png',
+ * repeat: 'repeat',
+ * offsetX: 200,
+ * offsetY: 100
+ * }, canvas.renderAll.bind(canvas));
+ */
+ setOverlayColor: function(overlayColor, callback) {
+ return this.__setBgOverlayColor('overlayColor', overlayColor, callback);
},
/**
* Sets {@link fabric.StaticCanvas#backgroundColor|background color} for this canvas
- * @param {String|fabric.Pattern} backgroundColor Color or pattern to set background color to
- * @param {Function} callback callback to invoke when background color is set
+ * @param {(String|fabric.Pattern)} backgroundColor Color or pattern to set background color to
+ * @param {Function} callback Callback to invoke when background color is set
* @return {fabric.Canvas} thisArg
* @chainable
* @see {@link http://jsfiddle.net/fabricjs/hXzvk/|jsFiddle demo}
@@ -261,20 +290,61 @@
* canvas.setBackgroundColor({
* source: 'http://fabricjs.com/assets/escheresque_ste.png'
* }, canvas.renderAll.bind(canvas));
+ * @example fabric.Pattern used as backgroundColor with repeat and offset
+ * canvas.setBackgroundColor({
+ * source: 'http://fabricjs.com/assets/escheresque_ste.png',
+ * repeat: 'repeat',
+ * offsetX: 200,
+ * offsetY: 100
+ * }, canvas.renderAll.bind(canvas));
*/
setBackgroundColor: function(backgroundColor, callback) {
- if (backgroundColor.source) {
+ return this.__setBgOverlayColor('backgroundColor', backgroundColor, callback);
+ },
+
+ /**
+ * @private
+ * @param {String} property Property to set ({@link fabric.StaticCanvas#backgroundImage|backgroundImage} or {@link fabric.StaticCanvas#overlayImage|overlayImage})
+ * @param {(fabric.Image|String)} image fabric.Image instance or URL of an image to set background or overlay to
+ * @param {Function} callback Callback to invoke when image is loaded and set as background or overlay
+ * @param {Object} [options] Optional options to set for the {@link fabric.Image|image}.
+ */
+ __setBgOverlayImage: function(property, image, callback, options) {
+ if (typeof image === 'string') {
+ fabric.util.loadImage(image, function(img) {
+ this[property] = new fabric.Image(img, options);
+ callback && callback();
+ }, this);
+ }
+ else {
+ this[property] = image;
+ callback && callback();
+ }
+
+ return this;
+ },
+
+ /**
+ * @private
+ * @param {String} property Property to set ({@link fabric.StaticCanvas#backgroundColor|backgroundColor} or {@link fabric.StaticCanvas#overlayColor|overlayColor})
+ * @param {(Object|String)} color Object with pattern information or color value
+ * @param {Function} [callback] Callback is invoked when color is set
+ */
+ __setBgOverlayColor: function(property, color, callback) {
+ if (color.source) {
var _this = this;
- fabric.util.loadImage(backgroundColor.source, function(img) {
- _this.backgroundColor = new fabric.Pattern({
+ fabric.util.loadImage(color.source, function(img) {
+ _this[property] = new fabric.Pattern({
source: img,
- repeat: backgroundColor.repeat
+ repeat: color.repeat,
+ offsetX: color.offsetX,
+ offsetY: color.offsetY
});
callback && callback();
});
}
else {
- this.backgroundColor = backgroundColor;
+ this[property] = color;
callback && callback();
}
@@ -576,9 +646,7 @@
canvasToDrawOn.restore();
}
- if (this.overlayImage) {
- canvasToDrawOn.drawImage(this.overlayImage, this.overlayImageLeft, this.overlayImageTop);
- }
+ this._renderOverlay(canvasToDrawOn);
if (this.controlsAboveOverlay && this.interactive) {
this.drawControls(canvasToDrawOn);
@@ -589,16 +657,26 @@
return this;
},
- _renderObjects: function(canvasToDrawOn, activeGroup) {
+ /**
+ * @private
+ * @param {CanvasRenderingContext2D} ctx Context to render on
+ * @param {fabric.Group} activeGroup
+ */
+ _renderObjects: function(ctx, activeGroup) {
for (var i = 0, length = this._objects.length; i < length; ++i) {
if (!activeGroup ||
(activeGroup && this._objects[i] && !activeGroup.contains(this._objects[i]))) {
- this._draw(canvasToDrawOn, this._objects[i]);
+ this._draw(ctx, this._objects[i]);
}
}
},
- _renderActiveGroup: function(canvasToDrawOn, activeGroup) {
+ /**
+ * @private
+ * @param {CanvasRenderingContext2D} ctx Context to render on
+ * @param {fabric.Group} activeGroup
+ */
+ _renderActiveGroup: function(ctx, activeGroup) {
// delegate rendering to group selection (if one exists)
if (activeGroup) {
@@ -611,42 +689,50 @@
}
});
activeGroup._set('objects', sortedObjects);
- this._draw(canvasToDrawOn, activeGroup);
- }
- },
-
- _renderBackground: function(canvasToDrawOn) {
- if (this.backgroundColor) {
- canvasToDrawOn.fillStyle = this.backgroundColor.toLive
- ? this.backgroundColor.toLive(canvasToDrawOn)
- : this.backgroundColor;
-
- canvasToDrawOn.fillRect(
- this.backgroundColor.offsetX || 0,
- this.backgroundColor.offsetY || 0,
- this.width,
- this.height);
- }
- if (typeof this.backgroundImage === 'object') {
- this._renderBackroundImage(canvasToDrawOn);
+ this._draw(ctx, activeGroup);
}
},
/**
* @private
- * @param {CanvasRenderingContext2D} canvasToDrawOn Context to render on
+ * @param {CanvasRenderingContext2D} ctx Context to render on
*/
- _renderBackroundImage: function(canvasToDrawOn) {
- canvasToDrawOn.save();
- canvasToDrawOn.globalAlpha = this.backgroundImageOpacity;
+ _renderBackground: function(ctx) {
+ if (this.backgroundColor) {
+ ctx.fillStyle = this.backgroundColor.toLive
+ ? this.backgroundColor.toLive(ctx)
+ : this.backgroundColor;
- if (this.backgroundImageStretch) {
- canvasToDrawOn.drawImage(this.backgroundImage, 0, 0, this.width, this.height);
+ ctx.fillRect(
+ this.backgroundColor.offsetX || 0,
+ this.backgroundColor.offsetY || 0,
+ this.width,
+ this.height);
}
- else {
- canvasToDrawOn.drawImage(this.backgroundImage, 0, 0);
+ if (this.backgroundImage) {
+ this.backgroundImage.render(ctx);
+ }
+ },
+
+ /**
+ * @private
+ * @param {CanvasRenderingContext2D} ctx Context to render on
+ */
+ _renderOverlay: function(ctx) {
+ if (this.overlayColor) {
+ ctx.fillStyle = this.overlayColor.toLive
+ ? this.overlayColor.toLive(ctx)
+ : this.overlayColor;
+
+ ctx.fillRect(
+ this.overlayColor.offsetX || 0,
+ this.overlayColor.offsetY || 0,
+ this.width,
+ this.height);
+ }
+ if (this.overlayImage) {
+ this.overlayImage.render(ctx);
}
- canvasToDrawOn.restore();
},
/**
@@ -777,22 +863,13 @@
instance.includeDefaultValues = originalValue;
}
return object;
- }, this),
- background: (this.backgroundColor && this.backgroundColor.toObject)
- ? this.backgroundColor.toObject()
- : this.backgroundColor
+ }, this)
};
- if (this.backgroundImage) {
- data.backgroundImage = this.backgroundImage.src;
- data.backgroundImageOpacity = this.backgroundImageOpacity;
- data.backgroundImageStretch = this.backgroundImageStretch;
- }
- if (this.overlayImage) {
- data.overlayImage = this.overlayImage.src;
- data.overlayImageLeft = this.overlayImageLeft;
- data.overlayImageTop = this.overlayImageTop;
- }
+
+ extend(data, this.__serializeBgOverlay());
+
fabric.util.populateWithProperties(this, data, propertiesToInclude);
+
if (activeGroup) {
this.setActiveGroup(new fabric.Group(activeGroup.getObjects()));
activeGroup.forEachObject(function(o) { o.set('active', true) });
@@ -800,6 +877,31 @@
return data;
},
+ /**
+ * @private
+ */
+ __serializeBgOverlay: function() {
+ var data = {
+ background: (this.backgroundColor && this.backgroundColor.toObject)
+ ? this.backgroundColor.toObject()
+ : this.backgroundColor
+ };
+
+ if (this.overlayColor) {
+ data.overlay = this.overlayColor.toObject
+ ? this.overlayColor.toObject()
+ : this.overlayColor;
+ }
+ if (this.backgroundImage) {
+ data.backgroundImage = this.backgroundImage.toObject();
+ }
+ if (this.overlayImage) {
+ data.overlayImage = this.overlayImage.toObject();
+ }
+
+ return data;
+ },
+
/* _TO_SVG_START_ */
/**
* Returns SVG representation of canvas
@@ -843,14 +945,23 @@
this._setSVGPreamble(markup, options);
this._setSVGHeader(markup, options);
- this._setSVGBackgroundColor(markup);
- this._setSVGBackgroundImage(markup);
- this._setSVGOverlayImage(markup);
+
+ this._setSVGBgOverlayColor(markup, 'backgroundColor');
+ this._setSVGBgOverlayImage(markup, 'backgroundImage');
+
this._setSVGObjects(markup, reviver);
+ this._setSVGBgOverlayColor(markup, 'overlayColor');
+ this._setSVGBgOverlayImage(markup, 'overlayImage');
+
+ markup.push('');
+
return markup.join('');
},
+ /**
+ * @private
+ */
_setSVGPreamble: function(markup, options) {
if (!options.suppressPreamble) {
markup.push(
@@ -861,6 +972,9 @@
}
},
+ /**
+ * @private
+ */
_setSVGHeader: function(markup, options) {
markup.push(
''
- );
- }
- },
-
- _setSVGBackgroundImage: function(markup) {
- if (this.backgroundImage) {
- markup.push(
- ' '
- );
- }
- },
-
- _setSVGOverlayImage: function(markup) {
- if (this.overlayImage) {
- markup.push(
- ' '
- );
- }
- },
-
+ /**
+ * @private
+ */
_setSVGObjects: function(markup, reviver) {
var activeGroup = this.getActiveGroup();
if (activeGroup) {
@@ -941,7 +1019,39 @@
o.set('active', true);
});
}
- markup.push(' ');
+ },
+
+ /**
+ * @private
+ */
+ _setSVGBgOverlayImage: function(markup, property) {
+ if (this[property] && this[property].toSVG) {
+ markup.push(this[property].toSVG());
+ }
+ },
+
+ /**
+ * @private
+ */
+ _setSVGBgOverlayColor: function(markup, property) {
+ if (this[property] && this[property].source) {
+ markup.push(
+ ' '
+ );
+ }
+ else if (this[property] && property === 'overlayColor') {
+ markup.push(
+ ' '
+ );
+ }
},
/* _TO_SVG_END_ */
diff --git a/src/util/lang_string.js b/src/util/lang_string.js
index 86df0664..e5586da5 100644
--- a/src/util/lang_string.js
+++ b/src/util/lang_string.js
@@ -30,10 +30,12 @@ function camelize(string) {
* Capitalizes a string
* @memberOf fabric.util.string
* @param {String} string String to capitalize
+ * @param {Boolean} [firstLetterOnly] If true only first letter is capitalized and other letters stay untouched,
+ * if false first letter is capitalized and other letters are converted to lowercase.
* @return {String} Capitalized version of a string
*/
-function capitalize(string) {
- return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase();
+function capitalize(string, firstLetterOnly) {
+ return string.charAt(0).toUpperCase() + (firstLetterOnly ? string.slice(1) : string.slice(1).toLowerCase());
}
/**
diff --git a/test/unit/canvas.js b/test/unit/canvas.js
index 95f4b272..76e688cb 100644
--- a/test/unit/canvas.js
+++ b/test/unit/canvas.js
@@ -19,7 +19,7 @@
' -3.56, 6.891, -7.481, 8.848], ["c", -4.689, 2.336, -9.084, -0.802, -11.277, -2.868], ["l",'+
' -1.948, 3.104], ["l", -1.628, -1.333], ["l", 3.138, -4.689], ["c", 0.025, 0, 9, 1.932, 9, 1.932], '+
'["c", 0.877, -9.979, 2.893, -12.905, 4.942, -15.621], ["C", 17.878, 21.775, 18.713, 17.397, 18.511, '+
- '13.99], ["z", null]]}], "background": "#ff5555"}';
+ '13.99], ["z", null]]}], "background": "#ff5555","overlay": "rgba(0,0,0,0.2)"}';
var PATH_OBJ_JSON = '{"type": "path", "originX": "center", "originY": "center", "left": 268, "top": 266, "width": 51, "height": 49,'+
' "fill": "rgb(0,0,0)", "stroke": null, "strokeWidth": 1, "scaleX": 1, "scaleY": 1, '+
@@ -46,7 +46,7 @@
var RECT_JSON = '{"objects":[{"type":"rect","originX":"center","originY":"center","left":0,"top":0,"width":10,"height":10,"fill":"rgb(0,0,0)",'+
'"stroke":null,"strokeWidth":1,"strokeDashArray":null,"strokeLineCap":"butt","strokeLineJoin":"miter","strokeMiterLimit":10,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,'+
'"shadow":null,'+
- '"visible":true,"clipTo":null,"backgroundColor":"","rx":0,"ry":0,"x":0,"y":0}],"background":"#ff5555"}';
+ '"visible":true,"clipTo":null,"backgroundColor":"","rx":0,"ry":0,"x":0,"y":0}],"background":"#ff5555","overlay":"rgba(0,0,0,0.2)"}';
var el = fabric.document.createElement('canvas');
el.width = 600; el.height = 600;
@@ -68,7 +68,7 @@
canvas.clear();
canvas.setActiveGroup(null);
canvas.backgroundColor = fabric.Canvas.prototype.backgroundColor;
- canvas.backgroundImage = '';
+ canvas.overlayColor = fabric.Canvas.prototype.overlayColor;
canvas.calcOffset();
upperCanvasEl.style.display = 'none';
}
@@ -248,19 +248,12 @@
ok(typeof canvas.toJSON == 'function');
equal(JSON.stringify(canvas.toJSON()), EMPTY_JSON);
canvas.backgroundColor = '#ff5555';
- equal(JSON.stringify(canvas.toJSON()), '{"objects":[],"background":"#ff5555"}', '`background` value should be reflected in json');
+ canvas.overlayColor = 'rgba(0,0,0,0.2)';
+ equal(JSON.stringify(canvas.toJSON()), '{"objects":[],"background":"#ff5555","overlay":"rgba(0,0,0,0.2)"}', '`background` and `overlayColor` value should be reflected in json');
canvas.add(makeRect());
deepEqual(JSON.stringify(canvas.toJSON()), RECT_JSON);
});
- test('toDatalessJSON', function() {
- var path = new fabric.Path('M 100 100 L 300 100 L 200 300 z', {
- sourcePath: 'http://example.com/'
- });
- canvas.add(path);
- equal(JSON.stringify(canvas.toDatalessJSON()), PATH_DATALESS_JSON);
- });
-
test('toJSON with active group', function() {
var rect = new fabric.Rect({ width: 50, height: 50, left: 100, top: 100 });
var circle = new fabric.Circle({ radius: 50, left: 50, top: 50 });
@@ -273,6 +266,14 @@
equal(json, jsonWithActiveGroup);
});
+ test('toDatalessJSON', function() {
+ var path = new fabric.Path('M 100 100 L 300 100 L 200 300 z', {
+ sourcePath: 'http://example.com/'
+ });
+ canvas.add(path);
+ equal(JSON.stringify(canvas.toDatalessJSON()), PATH_DATALESS_JSON);
+ });
+
test('toObject', function() {
ok(typeof canvas.toObject == 'function');
var expectedObject = {
@@ -318,6 +319,7 @@
ok(!canvas.isEmpty(), 'canvas is not empty');
equal(obj.type, 'path', 'first object is a path object');
equal(canvas.backgroundColor, '#ff5555', 'backgroundColor is populated properly');
+ equal(canvas.overlayColor, 'rgba(0,0,0,0.2)', 'overlayColor is populated properly');
equal(obj.get('left'), 268);
equal(obj.get('top'), 266);
@@ -345,6 +347,7 @@
ok(!canvas.isEmpty(), 'canvas is not empty');
equal(obj.type, 'path', 'first object is a path object');
equal(canvas.backgroundColor, '#ff5555', 'backgroundColor is populated properly');
+ equal(canvas.overlayColor, 'rgba(0,0,0,0.2)', 'overlayColor is populated properly');
equal(obj.get('left'), 268);
equal(obj.get('top'), 266);
@@ -380,6 +383,7 @@
ok(!canvas.isEmpty(), 'canvas is not empty');
equal(obj.type, 'path', 'first object is a path object');
equal(canvas.backgroundColor, '#ff5555', 'backgroundColor is populated properly');
+ equal(canvas.overlayColor, 'rgba(0,0,0,0.2)', 'overlayColor is populated properly');
equal(obj.get('left'), 268);
equal(obj.get('top'), 266);
@@ -400,20 +404,19 @@
});
asyncTest('loadFromJSON with no objects', function() {
- var c1 = new fabric.Canvas('c1', { backgroundColor: 'green' }),
- c2 = new fabric.Canvas('c2', { backgroundColor: 'red' });
+ var c1 = new fabric.Canvas('c1', { backgroundColor: 'green', overlayColor: 'yellow' }),
+ c2 = new fabric.Canvas('c2', { backgroundColor: 'red', overlayColor: 'orange' });
var json = c1.toJSON();
var fired = false;
c2.loadFromJSON(json, function() {
fired = true;
- });
- setTimeout(function() {
ok(fired, 'Callback should be fired even if no objects');
equal(c2.backgroundColor, 'green', 'Color should be set properly');
+ equal(c2.overlayColor, 'yellow', 'Color should be set properly');
start();
- }, 500);
+ });
});
asyncTest('loadFromJSON with empty fabric.Group', function() {
@@ -428,12 +431,10 @@
var fired = false;
c2.loadFromJSON(json, function() {
fired = true;
- });
- setTimeout(function() {
ok(fired, 'Callback should be fired even if empty fabric.Group exists');
start();
- }, 500);
+ });
});
asyncTest('loadFromJSON with async content', function() {
diff --git a/test/unit/canvas_static.js b/test/unit/canvas_static.js
index 34c00fe0..c45e1d7a 100644
--- a/test/unit/canvas_static.js
+++ b/test/unit/canvas_static.js
@@ -23,7 +23,7 @@
' -3.56, 6.891, -7.481, 8.848], ["c", -4.689, 2.336, -9.084, -0.802, -11.277, -2.868], ["l",'+
' -1.948, 3.104], ["l", -1.628, -1.333], ["l", 3.138, -4.689], ["c", 0.025, 0, 9, 1.932, 9, 1.932], '+
'["c", 0.877, -9.979, 2.893, -12.905, 4.942, -15.621], ["C", 17.878, 21.775, 18.713, 17.397, 18.511, '+
- '13.99], ["z", null]]}], "background": "#ff5555"}';
+ '13.99], ["z", null]]}], "background": "#ff5555", "overlay":"rgba(0,0,0,0.2)"}';
var PATH_DATALESS_JSON = '{"objects":[{"type":"path","originX":"center","originY":"center","left":200,"top":200,"width":200,"height":200,"fill":"rgb(0,0,0)",'+
'"stroke":null,"strokeWidth":1,"strokeDashArray":null,"strokeLineCap":"butt","strokeLineJoin":"miter","strokeMiterLimit":10,'+
@@ -33,13 +33,97 @@
var RECT_JSON = '{"objects":[{"type":"rect","originX":"center","originY":"center","left":0,"top":0,"width":10,"height":10,"fill":"rgb(0,0,0)",'+
'"stroke":null,"strokeWidth":1,"strokeDashArray":null,"strokeLineCap":"butt","strokeLineJoin":"miter","strokeMiterLimit":10,'+
'"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,'+
- '"shadow":null,"visible":true,"clipTo":null,"backgroundColor":"","rx":0,"ry":0,"x":0,"y":0}],"background":"#ff5555"}';
+ '"shadow":null,"visible":true,"clipTo":null,"backgroundColor":"","rx":0,"ry":0,"x":0,"y":0}],"background":"#ff5555","overlay":"rgba(0,0,0,0.2)"}';
var RECT_JSON_WITH_PADDING = '{"objects":[{"type":"rect","originX":"center","originY":"center","left":0,"top":0,"width":10,"height":20,"fill":"rgb(0,0,0)",'+
'"stroke":null,"strokeWidth":1,"strokeDashArray":null,"strokeLineCap":"butt","strokeLineJoin":"miter","strokeMiterLimit":10,'+
'"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,'+
'"shadow":null,"visible":true,"clipTo":null,"backgroundColor":"","padding":123,"foo":"bar","rx":0,"ry":0,"x":0,"y":0}],"background":""}';
+ function getAbsolutePath(path) {
+ var isAbsolute = /^https?:/.test(path);
+ if (isAbsolute) return path;
+ var imgEl = _createImageElement();
+ imgEl.src = path;
+ var src = imgEl.src;
+ imgEl = null;
+ return src;
+ }
+
+ var IMG_SRC = fabric.isLikelyNode ? (__dirname + '/../fixtures/test_image.gif') : getAbsolutePath('../fixtures/test_image.gif'),
+ IMG_WIDTH = 276,
+ IMG_HEIGHT = 110;
+
+ var REFERENCE_IMG_OBJECT = {
+ 'type': 'image',
+ 'originX': 'center',
+ 'originY': 'center',
+ 'left': 0,
+ 'top': 0,
+ 'width': IMG_WIDTH, // node-canvas doesn't seem to allow setting width/height on image objects
+ 'height': IMG_HEIGHT, // or does it now?
+ 'fill': 'rgb(0,0,0)',
+ 'stroke': null,
+ 'strokeWidth': 1,
+ 'strokeDashArray': null,
+ 'strokeLineCap': 'butt',
+ 'strokeLineJoin': 'miter',
+ 'strokeMiterLimit': 10,
+ 'scaleX': 1,
+ 'scaleY': 1,
+ 'angle': 0,
+ 'flipX': false,
+ 'flipY': false,
+ 'opacity': 1,
+ 'src': fabric.isLikelyNode ? undefined : IMG_SRC,
+ 'shadow': null,
+ 'visible': true,
+ 'backgroundColor': '',
+ 'clipTo': null,
+ 'filters': []
+ };
+
+ function _createImageElement() {
+ return fabric.isLikelyNode ? new (require('canvas').Image) : fabric.document.createElement('img');
+ }
+
+ function _createImageObject(width, height, callback) {
+ var elImage = _createImageElement();
+ elImage.width = width;
+ elImage.height = height;
+ setSrc(elImage, IMG_SRC, function() {
+ callback(new fabric.Image(elImage));
+ });
+ }
+
+ function createImageObject(callback) {
+ return _createImageObject(IMG_WIDTH, IMG_HEIGHT, callback)
+ }
+
+ function setSrc(img, src, callback) {
+ if (fabric.isLikelyNode) {
+ require('fs').readFile(src, function(err, imgData) {
+ if (err) throw err;
+ img.src = imgData;
+ callback && callback();
+ });
+ }
+ else {
+ img.src = src;
+ callback && callback();
+ }
+ }
+
+ function fixImageDimension(imgObj) {
+ // workaround for node-canvas sometimes producing images with width/height and sometimes not
+ if (imgObj.width === 0) {
+ imgObj.width = IMG_WIDTH;
+ }
+ if (imgObj.height === 0) {
+ imgObj.height = IMG_HEIGHT;
+ }
+ }
+
// force creation of static canvas
// TODO: fix this
var Canvas = fabric.Canvas;
@@ -61,6 +145,7 @@
teardown: function() {
canvas.clear();
canvas.backgroundColor = fabric.StaticCanvas.prototype.backgroundColor;
+ canvas.overlayColor = fabric.StaticCanvas.prototype.overlayColor;
canvas.calcOffset();
}
});
@@ -352,11 +437,62 @@
ok(typeof canvas.toJSON == 'function');
equal(JSON.stringify(canvas.toJSON()), '{"objects":[],"background":""}');
canvas.backgroundColor = '#ff5555';
- equal(JSON.stringify(canvas.toJSON()), '{"objects":[],"background":"#ff5555"}', '`background` value should be reflected in json');
+ canvas.overlayColor = 'rgba(0,0,0,0.2)';
+ equal(JSON.stringify(canvas.toJSON()), '{"objects":[],"background":"#ff5555","overlay":"rgba(0,0,0,0.2)"}', '`background` and `overlay` value should be reflected in json');
canvas.add(makeRect());
deepEqual(JSON.stringify(canvas.toJSON()), RECT_JSON);
});
+ test('toJSON custom properties non-existence check', function() {
+ var rect = new fabric.Rect({ width: 10, height: 20 });
+ rect.padding = 123;
+ canvas.add(rect);
+ rect.foo = 'bar';
+
+ canvas.bar = 456;
+
+ var data = canvas.toJSON(['padding', 'foo', 'bar', 'baz']);
+ ok('padding' in data.objects[0]);
+ ok('foo' in data.objects[0], 'foo shouldn\'t be included if it\'s not in an object');
+ ok(!('bar' in data.objects[0]), 'bar shouldn\'t be included if it\'s not in an object');
+ ok(!('baz' in data.objects[0]), 'bar shouldn\'t be included if it\'s not in an object');
+ ok(!('foo' in data));
+ ok(!('baz' in data));
+ ok('bar' in data);
+ });
+
+ asyncTest('toJSON backgroundImage', function() {
+ createImageObject(function(image) {
+
+ canvas.backgroundImage = image;
+
+ var json = canvas.toJSON();
+
+ fixImageDimension(json.backgroundImage);
+ deepEqual(json.backgroundImage, REFERENCE_IMG_OBJECT);
+
+ canvas.backgroundImage = null;
+
+ start();
+ });
+ });
+
+ asyncTest('toJSON overlayImage', function() {
+ createImageObject(function(image) {
+
+ canvas.overlayImage = image;
+
+ var json = canvas.toJSON();
+
+ fixImageDimension(json.overlayImage);
+ deepEqual(json.overlayImage, REFERENCE_IMG_OBJECT);
+
+ canvas.overlayImage = null;
+
+ start();
+ });
+ });
+
test('toDatalessJSON', function() {
var path = new fabric.Path('M 100 100 L 300 100 L 200 300 z', {
sourcePath: 'http://example.com/'
@@ -458,6 +594,7 @@
ok(!canvas.isEmpty(), 'canvas is not empty');
equal(obj.type, 'path', 'first object is a path object');
equal(canvas.backgroundColor, '#ff5555', 'backgroundColor is populated properly');
+ equal(canvas.overlayColor, 'rgba(0,0,0,0.2)', 'overlayColor is populated properly');
equal(obj.get('left'), 268);
equal(obj.get('top'), 266);
@@ -499,22 +636,19 @@
});
});
- test('toJSON custom properties non-existence check', function() {
- var rect = new fabric.Rect({ width: 10, height: 20 });
- rect.padding = 123;
- canvas.add(rect);
- rect.foo = 'bar';
+ asyncTest('loadFromJSON with text', function() {
+ var json = '{"objects":[{"type":"text","left":150,"top":200,"width":128,"height":64.32,"fill":"#000000","stroke":"","strokeWidth":"","scaleX":0.8,"scaleY":0.8,"angle":0,"flipX":false,"flipY":false,"opacity":1,"text":"NAME HERE","fontSize":24,"fontWeight":"","fontFamily":"Delicious_500","fontStyle":"","lineHeight":"","textDecoration":"","textAlign":"center","path":"","strokeStyle":"","backgroundColor":""}],"background":"#ffffff"}';
+ canvas.loadFromJSON(json, function() {
- canvas.bar = 456;
+ canvas.renderAll();
- var data = canvas.toJSON(['padding', 'foo', 'bar', 'baz']);
- ok('padding' in data.objects[0]);
- ok('foo' in data.objects[0], 'foo shouldn\'t be included if it\'s not in an object');
- ok(!('bar' in data.objects[0]), 'bar shouldn\'t be included if it\'s not in an object');
- ok(!('baz' in data.objects[0]), 'bar shouldn\'t be included if it\'s not in an object');
- ok(!('foo' in data));
- ok(!('baz' in data));
- ok('bar' in data);
+ equal('text', canvas.item(0).type);
+ equal(150, canvas.item(0).left);
+ equal(200, canvas.item(0).top)
+ equal('NAME HERE', canvas.item(0).text);
+
+ start();
+ });
});
test('remove', function() {
@@ -895,19 +1029,4 @@
deepEqual(objectsAdded[3], circle3);
});
- asyncTest('loadFromJSON with text', function() {
- var json = '{"objects":[{"type":"text","left":150,"top":200,"width":128,"height":64.32,"fill":"#000000","stroke":"","strokeWidth":"","scaleX":0.8,"scaleY":0.8,"angle":0,"flipX":false,"flipY":false,"opacity":1,"text":"NAME HERE","fontSize":24,"fontWeight":"","fontFamily":"Delicious_500","fontStyle":"","lineHeight":"","textDecoration":"","textAlign":"center","path":"","strokeStyle":"","backgroundColor":""}],"background":"#ffffff"}';
- canvas.loadFromJSON(json, function() {
-
- canvas.renderAll();
-
- equal('text', canvas.item(0).type);
- equal(150, canvas.item(0).left);
- equal(200, canvas.item(0).top)
- equal('NAME HERE', canvas.item(0).text);
-
- start();
- });
- });
-
})();