[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.

`backgroundImage` and `overlayImage` are now `fabric.Image` instances
New property `overlayColor` (analog to `backgroundColor`) - should be set with `canvas.setOverlayColor`
`backgroundImageOpacity` was removed => use `fabric.Image#opacity`
`overlayImageLeft ` was removed => use `fabric.Image#left`
`overlayImageTop ` was removed => use `fabric.Image#top`
`backgroundImageStretch` was removed => use `fabric.Image#width` and `fabric.Image#height`. If you scale your canvas you have to adjust the backgroundImage width/height manually.
Update SVG output.
Add 2nd parameter `firstLetterOnly` to `fabric.util.string.capitalize` => only first letter is transformed to uppercase (other letters stay untouched)
Add `preserveAspectRatio="none"` to `fabric.Image#toSVG` (otherwise streched backgroundImage and overlayImage didn't work as expected)
Update examples - TODO: Update jsfiddles for `setBackgroundImage` and `setOverlayImage`
Add unit test

Closes issue #270
This commit is contained in:
Kienz 2013-11-03 13:09:49 +01:00
parent 3c30eb041a
commit 5017bc6626
8 changed files with 536 additions and 291 deletions

View file

@ -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

View file

@ -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();
}
},
/**

View file

@ -805,23 +805,31 @@
* @return {String}
*/
function createSVGRefElementsMarkup(canvas) {
var markup = '';
var markup = [ ];
if (canvas.backgroundColor && canvas.backgroundColor.source) {
markup = [
'<pattern x="0" y="0" id="backgroundColorPattern" ',
'width="', canvas.backgroundColor.source.width,
'" height="', canvas.backgroundColor.source.height,
_createSVGPattern(markup, canvas, 'backgroundColor');
_createSVGPattern(markup, canvas, 'overlayColor');
return markup.join('');
}
/**
* @private
*/
function _createSVGPattern(markup, canvas, property) {
if (canvas[property] && canvas[property].toSVG) {
markup.push(
'<pattern x="0" y="0" id="', property, 'Pattern" ',
'width="', canvas[property].source.width,
'" height="', canvas[property].source.height,
'" patternUnits="userSpaceOnUse">',
'<image x="0" y="0" ',
'width="', canvas.backgroundColor.source.width,
'" height="', canvas.backgroundColor.source.height,
'" xlink:href="', canvas.backgroundColor.source.src,
'width="', canvas[property].source.width,
'" height="', canvas[property].source.height,
'" xlink:href="', canvas[property].source.src,
'"></image></pattern>'
].join('');
);
}
return markup;
}
/**

View file

@ -201,6 +201,7 @@
'" transform="translate(' + (-this.width/2) + ' ' + (-this.height/2) + ')',
'" width="', this.width,
'" height="', this.height,
'" preserveAspectRatio="none"',
'"></image>'
);

View file

@ -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}.
* <b>Backwards incompatibility note:</b> 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}.
* <b>Backwards incompatibility note:</b> 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 <caption>Normal overlayImage</caption>
* canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas));
* @example <caption>Displaced overlayImage (left and top != 0)</caption>
* @example <caption>Normal overlayImage with left/top = 0</caption>
* 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 <caption>overlayImage with different properties</caption>
* 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 <caption>Stretched overlayImage #1 - width/height correspond to canvas width/height</caption>
* 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 <caption>Stretched overlayImage #2 - width/height correspond to canvas width/height</caption>
* 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 <caption>Normal backgroundImage</caption>
* canvas.setBackgroundImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas));
* @example <caption>Stretched backgroundImage with opacity</caption>
* canvas.setBackgroundImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), {
* backgroundImageOpacity: 0.5,
* backgroundImageStretch: true
* @example <caption>Normal backgroundImage with left/top = 0</caption>
* 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 <caption>backgroundImage with different properties</caption>
* 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 <caption>Stretched backgroundImage #1 - width/height correspond to canvas width/height</caption>
* 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 <caption>Stretched backgroundImage #2 - width/height correspond to canvas width/height</caption>
* 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 <caption>Normal overlayColor - color value</caption>
* canvas.setOverlayColor('rgba(255, 73, 64, 0.6)', canvas.renderAll.bind(canvas));
* @example <caption>fabric.Pattern used as overlayColor</caption>
* canvas.setOverlayColor({
* source: 'http://fabricjs.com/assets/escheresque_ste.png'
* }, canvas.renderAll.bind(canvas));
* @example <caption>fabric.Pattern used as overlayColor with repeat and offset</caption>
* 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 <caption>fabric.Pattern used as backgroundColor with repeat and offset</caption>
* 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('</svg>');
return markup.join('');
},
/**
* @private
*/
_setSVGPreamble: function(markup, options) {
if (!options.suppressPreamble) {
markup.push(
@ -861,6 +972,9 @@
}
},
/**
* @private
*/
_setSVGHeader: function(markup, options) {
markup.push(
'<svg ',
@ -869,7 +983,7 @@
'version="1.1" ',
'width="', (options.viewBox ? options.viewBox.width : this.width), '" ',
'height="', (options.viewBox ? options.viewBox.height : this.height), '" ',
(this.backgroundColor && !this.backgroundColor.source
(this.backgroundColor && !this.backgroundColor.toLive
? 'style="background-color: ' + this.backgroundColor +'" '
: null),
(options.viewBox
@ -888,45 +1002,9 @@
);
},
_setSVGBackgroundColor: function(markup) {
if (this.backgroundColor && this.backgroundColor.source) {
markup.push(
'<rect x="0" y="0" ',
'width="', (this.backgroundColor.repeat === 'repeat-y' || this.backgroundColor.repeat === 'no-repeat' ? this.backgroundColor.source.width : this.width),
'" height="', (this.backgroundColor.repeat === 'repeat-x' || this.backgroundColor.repeat === 'no-repeat' ? this.backgroundColor.source.height : this.height),
'" fill="url(#backgroundColorPattern)"',
'></rect>'
);
}
},
_setSVGBackgroundImage: function(markup) {
if (this.backgroundImage) {
markup.push(
'<image x="0" y="0" ',
'width="', (this.backgroundImageStretch ? this.width : this.backgroundImage.width),
'" height="', (this.backgroundImageStretch ? this.height : this.backgroundImage.height),
'" preserveAspectRatio="', (this.backgroundImageStretch ? 'none' : 'defer'),
'" xlink:href="', this.backgroundImage.src,
'" style="opacity:', this.backgroundImageOpacity,
'"></image>'
);
}
},
_setSVGOverlayImage: function(markup) {
if (this.overlayImage) {
markup.push(
'<image x="', this.overlayImageLeft,
'" y="', this.overlayImageTop,
'" width="', this.overlayImage.width,
'" height="', this.overlayImage.height,
'" xlink:href="', this.overlayImage.src,
'"></image>'
);
}
},
/**
* @private
*/
_setSVGObjects: function(markup, reviver) {
var activeGroup = this.getActiveGroup();
if (activeGroup) {
@ -941,7 +1019,39 @@
o.set('active', true);
});
}
markup.push('</svg>');
},
/**
* @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(
'<rect x="', this[property].offsetX, '" y="', this[property].offsetY, '" ',
'width="', (this[property].repeat === 'repeat-y' || this[property].repeat === 'no-repeat' ? this[property].source.width : this.width),
'" height="', (this[property].repeat === 'repeat-x' || this[property].repeat === 'no-repeat' ? this[property].source.height : this.height),
'" fill="url(#' + property + 'Pattern)"',
'></rect>'
);
}
else if (this[property] && property === 'overlayColor') {
markup.push(
'<rect x="0" y="0" ',
'width="', this.width,
'" height="', this.height,
'" fill="', this[property], '"',
'></rect>'
);
}
},
/* _TO_SVG_END_ */

View file

@ -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());
}
/**

View file

@ -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() {

View file

@ -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();
});
});
})();