[BACK_INCOMPAT] Add support for static canvases — fabric.StaticCanvas. fabric.Canvas#loadImageFromURL -> fabric.util.loadImage. Add fabric.Canvas#centerObject.

This commit is contained in:
kangax 2011-11-27 23:57:28 -05:00
parent 15c0dc3768
commit b8ea6b7cec
12 changed files with 2686 additions and 2707 deletions

View file

@ -1,6 +1,6 @@
/*! Fabric.js Copyright 2008-2011, Bitsonnet (Juriy Zaytsev, Maxim Chernyak) */
var fabric = fabric || { version: "0.6.13" };
var fabric = fabric || { version: "0.7" };
if (typeof exports != 'undefined') {
exports.fabric = fabric;

View file

@ -55,6 +55,11 @@ Fabric.js started as a foundation for design editor on [printio.ru](http://print
By default (when none of the modules are specified) only basic functionality is included.
See the list of modules below for more information on each one of them.
Note that default distribution has support for **static canvases** only.
To get minimal distribution with interactivity, make sure to include corresponding module:
$ node build.js modules=interaction
- You can also include all modules like so:
@ -62,15 +67,15 @@ Fabric.js started as a foundation for design editor on [printio.ru](http://print
3. Create a minified distribution file
# Using YUICompressor
$ java -jar lib/yuicompressor-2.4.2.jar dist/all.js -o dist/all.min.js
# Using YUICompressor (default option)
$ node build.js modules=... minifier=yui
# or Google Closure Compiler
$ java -jar lib/google_closure_compiler.jar --js dist/all.js --js_output_file dist/all.min.js
$ node build.js modules=... minifier=closure
4. Optionally, you can build documentation
$ java -jar lib/jsdoc-toolkit/jsrun.jar lib/jsdoc-toolkit/app/run.js -a -t=lib/jsdoc-toolkit/templates/jsdoc -d=site/docs HEADER.js src/ src/util/
$ node build_docs.js
### Demos
@ -90,6 +95,7 @@ These are the optional modules that could be specified for inclusion, when build
- **text** — Adds support for `fabric.Text`
- **serialization** — Adds support for `loadFromJSON`, `loadFromDatalessJSON`, and `clone` methods on `fabric.Canvas`
- **interaction** — Adds support for interactive features of fabric — selecting/transforming objects/groups via mouse/touch devices.
- **parser** — Adds support for `fabric.parseSVGDocument`, `fabric.loadSVGFromURL`, and `fabric.loadSVGFromString`
- **image_filters** — Adds support for image filters, such as grayscale of white removal.
- **node** — Adds support for running fabric under node.js, with help of [jsdom](https://github.com/tmpvar/jsdom) and [node-canvas](https://github.com/learnboost/node-canvas) libraries.

2484
dist/all.js vendored

File diff suppressed because it is too large Load diff

8
dist/all.min.js vendored

File diff suppressed because one or more lines are too long

BIN
dist/all.min.js.gz vendored Normal file

Binary file not shown.

View file

@ -41,8 +41,8 @@
});
});
}
fabric.Canvas.prototype.loadImageFromURL = function(url, callback) {
fabric.util.loadImage = function(url, callback) {
request(url, 'binary', function(body) {
var img = new Image();
img.src = new Buffer(body, 'binary');
@ -68,7 +68,9 @@
};
fabric.Image.fromObject = function(object, callback) {
fabric.Canvas.prototype.loadImageFromURL(object.src, function(oImg) {
fabric.util.loadImage(object.src, function(img) {
var oImg = new fabric.Image(img);
oImg._initConfig(object);
oImg._initFilters(object);
callback(oImg);
@ -86,29 +88,39 @@
canvasEl.width = nodeCanvas.width;
canvasEl.height = nodeCanvas.height;
var fabricCanvas = new fabric.Canvas(canvasEl);
var Canvas = fabric.Canvas || fabric.StaticCanvas;
var fabricCanvas = new Canvas(canvasEl);
fabricCanvas.contextContainer = nodeCanvas.getContext('2d');
fabricCanvas.nodeCanvas = nodeCanvas;
return fabricCanvas;
};
fabric.Canvas.prototype.createPNGStream = function() {
fabric.StaticCanvas.prototype.createPNGStream = function() {
return this.nodeCanvas.createPNGStream();
};
if (fabric.Canvas) {
fabric.Canvas.prototype.createPNGStream
}
var origSetWidth = fabric.Canvas.prototype.setWidth;
fabric.Canvas.prototype.setWidth = function(width) {
var origSetWidth = fabric.StaticCanvas.prototype.setWidth;
fabric.StaticCanvas.prototype.setWidth = function(width) {
origSetWidth.call(this);
this.nodeCanvas.width = width;
return this;
};
if (fabric.Canvas) {
fabric.Canvas.prototype.setWidth = fabric.StaticCanvas.prototype.setWidth;
}
var origSetHeight = fabric.Canvas.prototype.setHeight;
fabric.Canvas.prototype.setHeight = function(height) {
var origSetHeight = fabric.StaticCanvas.prototype.setHeight;
fabric.StaticCanvas.prototype.setHeight = function(height) {
origSetHeight.call(this);
this.nodeCanvas.height = height;
return this;
};
if (fabric.Canvas) {
fabric.Canvas.prototype.setHeight = fabric.StaticCanvas.prototype.setHeight;
}
})();

View file

@ -1,4 +1,4 @@
fabric.util.object.extend(fabric.Canvas.prototype, {
fabric.util.object.extend(fabric.StaticCanvas.prototype, {
/**
* Centers object horizontally with animation.

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,4 @@
fabric.util.object.extend(fabric.Canvas.prototype, {
fabric.util.object.extend(fabric.StaticCanvas.prototype, {
/**
* Populates canvas with data from the specified dataless JSON
@ -85,13 +85,15 @@ fabric.util.object.extend(fabric.Canvas.prototype, {
}
else {
if (obj.type === 'image') {
_this.loadImageFromURL(path, function (image) {
image.setSourcePath(path);
fabric.util.loadImage(path, function (image) {
var oImg = new fabric.Image(image);
oImg.setSourcePath(path);
fabric.util.object.extend(image, obj);
image.setAngle(obj.angle);
fabric.util.object.extend(oImg, obj);
oImg.setAngle(obj.angle);
onObjectLoaded(image, index);
onObjectLoaded(oImg, index);
});
}
else if (obj.type === 'text') {

View file

@ -361,7 +361,7 @@
* @return {fabric.Group} thisArg
* @chainable
*/
forEachObject: fabric.Canvas.prototype.forEachObject,
forEachObject: fabric.StaticCanvas.prototype.forEachObject,
/**
* @private

948
src/static_canvas.class.js Normal file
View file

@ -0,0 +1,948 @@
(function (global) {
"use strict";
if (fabric.StaticCanvas) {
fabric.warn('fabric.StaticCanvas is already defined.');
return;
}
// aliases for faster resolution
var extend = fabric.util.object.extend,
getElementOffset = fabric.util.getElementOffset,
removeFromArray = fabric.util.removeFromArray,
removeListener = fabric.util.removeListener,
CANVAS_INIT_ERROR = new Error('Could not initialize `canvas` element');
/**
* @class fabric.StaticCanvas
* @constructor
* @param {HTMLElement | String} el <canvas> element to initialize instance on
* @param {Object} [options] Options object
*/
fabric.StaticCanvas = function (el, options) {
options || (options = { });
this._initStatic(el, options);
fabric.StaticCanvas.activeInstance = this;
};
extend(fabric.StaticCanvas.prototype, fabric.Observable);
extend(fabric.StaticCanvas.prototype, /** @scope fabric.StaticCanvas.prototype */ {
/**
* Background color of this canvas instance
* @property
* @type String
*/
backgroundColor: 'rgba(0, 0, 0, 0)',
/**
* Background image of this canvas instance
* Should be set via `setBackgroundImage`
* @property
* @type String
*/
backgroundImage: '',
/**
* @property
* @type Boolean
*/
includeDefaultValues: true,
/**
* Indicates whether objects' state should be saved
* @property
* @type Boolean
*/
stateful: true,
/**
* Indicates whether fabric.Canvas#add should also re-render canvas.
* Disabling this option could give a great performance boost when adding a lot of objects to canvas at once
* (followed by a manual rendering after addition)
*/
renderOnAddition: true,
/**
* @constant
* @type Number
*/
CANVAS_WIDTH: 600,
/**
* @constant
* @type Number
*/
CANVAS_HEIGHT: 600,
/**
* @constant
* @type String
*/
CONTAINER_CLASS: 'canvas-container',
/**
* Callback; invoked right before object is about to be scaled/rotated
* @method onBeforeScaleRotate
* @param {fabric.Object} target Object that's about to be scaled/rotated
*/
onBeforeScaleRotate: function (target) {
/* NOOP */
},
/**
* Callback; invoked on every redraw of canvas and is being passed a number indicating current fps
* @method onFpsUpdate
* @param {Number} fps
*/
onFpsUpdate: null,
_initStatic: function(el, options) {
this._objects = [];
this._createLowerCanvas(el);
this._initOptions(options);
if (options.overlayImage) {
this.setOverlayImage(options.overlayImage, this.renderAll.bind(this));
}
if (options.backgroundImage) {
this.setBackgroundImage(options.backgroundImage, this.renderAll.bind(this));
}
this.calcOffset();
},
/**
* Calculates canvas element offset relative to the document
* This method is also attached as "resize" event handler of window
* @method calcOffset
* @return {fabric.Canvas} instance
* @chainable
*/
calcOffset: function () {
this._offset = getElementOffset(this.lowerCanvasEl);
return this;
},
/**
* Sets overlay image for this canvas
* @method setOverlayImage
* @param {String} url url of an image to set overlay to
* @param {Function} callback callback to invoke when image is loaded and set as an overlay
* @return {fabric.Canvas} thisArg
* @chainable
*/
setOverlayImage: function (url, callback) { // TODO (kangax): test callback
return fabric.util.loadImage(url, function(img) {
this.overlayImage = img;
callback && callback();
}, this);
},
/**
* Sets background image for this canvas
* @method setBackgroundImage
* @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
* @return {fabric.Canvas} thisArg
* @chainable
*/
setBackgroundImage: function (url, callback) {
return fabric.util.loadImage(url, function(img) {
this.backgroundImage = img;
callback && callback();
}, this);
},
/**
* @private
* @method _createCanvasElement
* @param {Element} element
*/
_createCanvasElement: function() {
var element = fabric.document.createElement('canvas');
if (!element.style) {
element.style = { };
}
if (!element) {
throw CANVAS_INIT_ERROR;
}
this._initCanvasElement(element);
return element;
},
_initCanvasElement: function(element) {
if (typeof element.getContext === 'undefined' &&
typeof G_vmlCanvasManager !== 'undefined' &&
G_vmlCanvasManager.initElement) {
G_vmlCanvasManager.initElement(element);
}
if (typeof element.getContext === 'undefined') {
throw CANVAS_INIT_ERROR;
}
},
/**
* @method _initOptions
* @param {Object} options
*/
_initOptions: function (options) {
for (var prop in options) {
this[prop] = options[prop];
}
this.width = parseInt(this.lowerCanvasEl.width, 10) || 0;
this.height = parseInt(this.lowerCanvasEl.height, 10) || 0;
this.lowerCanvasEl.style.width = this.width + 'px';
this.lowerCanvasEl.style.height = this.height + 'px';
},
/**
* Creates a secondary canvas
* @method _createLowerCanvas
*/
_createLowerCanvas: function (canvasEl) {
this.lowerCanvasEl = fabric.util.getById(canvasEl) || this._createCanvasElement();
this._initCanvasElement(this.lowerCanvasEl);
fabric.util.addClass(this.lowerCanvasEl, 'lower-canvas');
if (this.interactive) {
this._applyCanvasStyle(this.lowerCanvasEl);
}
this.contextContainer = this.lowerCanvasEl.getContext('2d');
},
/**
* Returns canvas width
* @method getWidth
* @return {Number}
*/
getWidth: function () {
return this.width;
},
/**
* Returns canvas height
* @method getHeight
* @return {Number}
*/
getHeight: function () {
return this.height;
},
/**
* Sets width of this canvas instance
* @method setWidth
* @param {Number} width value to set width to
* @return {fabric.Canvas} instance
* @chainable true
*/
setWidth: function (value) {
return this._setDimension('width', value);
},
/**
* Sets height of this canvas instance
* @method setHeight
* @param {Number} height value to set height to
* @return {fabric.Canvas} instance
* @chainable true
*/
setHeight: function (value) {
return this._setDimension('height', value);
},
/**
* Sets dimensions (width, height) of this canvas instance
* @method setDimensions
* @param {Object} dimensions
* @return {fabric.Canvas} thisArg
* @chainable
*/
setDimensions: function(dimensions) {
for (var prop in dimensions) {
this._setDimension(prop, dimensions[prop]);
}
return this;
},
/**
* Helper for setting width/height
* @private
* @method _setDimensions
* @param {String} prop property (width|height)
* @param {Number} value value to set property to
* @return {fabric.Canvas} instance
* @chainable true
*/
_setDimension: function (prop, value) {
this.lowerCanvasEl[prop] = value;
this.lowerCanvasEl.style[prop] = value + 'px';
if (this.upperCanvasEl) {
this.upperCanvasEl[prop] = value;
this.upperCanvasEl.style[prop] = value + 'px';
}
if (this.wrapperEl) {
this.wrapperEl.style[prop] = value + 'px';
}
this[prop] = value;
this.calcOffset();
this.renderAll();
return this;
},
/**
* Returns <canvas> element corresponding to this instance
* @method getElement
* @return {HTMLCanvasElement}
*/
getElement: function () {
return this.lowerCanvasEl;
},
// placeholder
getActiveObject: function() {
return null;
},
// placeholder
getActiveGroup: function() {
return null;
},
/**
* Given a context, renders an object on that context
* @param ctx {Object} context to render object on
* @param object {Object} object to render
* @private
*/
_draw: function (ctx, object) {
object && object.render(ctx);
},
/**
* Adds objects to canvas, then renders canvas;
* Objects should be instances of (or inherit from) fabric.Object
* @method add
* @return {fabric.Canvas} thisArg
* @chainable
*/
add: function () {
this._objects.push.apply(this._objects, arguments);
for (var i = arguments.length; i--; ) {
this.stateful && arguments[i].setupState();
arguments[i].setCoords();
}
this.renderOnAddition && this.renderAll();
return this;
},
/**
* Inserts an object to canvas at specified index and renders canvas.
* An object should be an instance of (or inherit from) fabric.Object
* @method insertAt
* @param object {Object} Object to insert
* @param index {Number} index to insert object at
* @param nonSplicing {Boolean} when `true`, no splicing (shifting) of objects occurs
* @return {fabric.Canvas} instance
*/
insertAt: function (object, index, nonSplicing) {
if (nonSplicing) {
this._objects[index] = object;
}
else {
this._objects.splice(index, 0, object);
}
this.stateful && object.setupState();
object.setCoords();
this.renderAll();
return this;
},
/**
* Returns an array of objects this instance has
* @method getObjects
* @return {Array}
*/
getObjects: function () {
return this._objects;
},
/**
* Clears specified context of canvas element
* @method clearContext
* @param context {Object} ctx context to clear
* @return {fabric.Canvas} thisArg
* @chainable
*/
clearContext: function(ctx) {
ctx.clearRect(0, 0, this.width, this.height);
return this;
},
/**
* Clears all contexts (background, main, top) of an instance
* @method clear
* @return {fabric.Canvas} thisArg
* @chainable
*/
clear: function () {
this._objects.length = 0;
this.clearContext(this.contextContainer);
if (this.contextTop) {
this.clearContext(this.contextTop);
}
this.renderAll();
return this;
},
/**
* Renders both the top canvas and the secondary container canvas.
* @method renderAll
* @param allOnTop {Boolean} optional Whether we want to force all images to be rendered on the top canvas
* @return {fabric.Canvas} instance
* @chainable
*/
renderAll: function (allOnTop) {
var canvasToDrawOn = this[(allOnTop && this.interactive) ? 'contextTop' : 'contextContainer'];
if (this.contextTop) {
this.clearContext(this.contextTop);
}
if (!allOnTop) {
this.clearContext(canvasToDrawOn);
}
var length = this._objects.length,
activeGroup = this.getActiveGroup(),
startTime = new Date();
if (this.clipTo) {
canvasToDrawOn.save();
canvasToDrawOn.beginPath();
this.clipTo(canvasToDrawOn);
canvasToDrawOn.clip();
}
canvasToDrawOn.fillStyle = this.backgroundColor;
canvasToDrawOn.fillRect(0, 0, this.width, this.height);
if (typeof this.backgroundImage == 'object') {
canvasToDrawOn.drawImage(this.backgroundImage, 0, 0, this.width, this.height);
}
if (length) {
for (var i = 0; i < length; ++i) {
if (!activeGroup ||
(activeGroup && this._objects[i] && !activeGroup.contains(this._objects[i]))) {
this._draw(canvasToDrawOn, this._objects[i]);
}
}
}
if (this.clipTo) {
canvasToDrawOn.restore();
}
// delegate rendering to group selection (if one exists)
if (activeGroup) {
this._draw(this.contextTop, activeGroup);
}
if (this.overlayImage) {
this.contextTop.drawImage(this.overlayImage, 0, 0);
}
if (this.onFpsUpdate) {
var elapsedTime = new Date() - startTime;
this.onFpsUpdate(~~(1000 / elapsedTime));
}
this.fire('after:render');
return this;
},
/**
* Method to render only the top canvas.
* Also used to render the group selection box.
* @method renderTop
* @return {fabric.Canvas} thisArg
* @chainable
*/
renderTop: function () {
this.clearContext(this.contextTop || this.contextContainer);
if (this.overlayImage) {
(this.contextTop || this.contextContainer).drawImage(this.overlayImage, 0, 0);
}
// we render the top context - last object
if (this.selection && this._groupSelector) {
this._drawSelection();
}
// delegate rendering to group selection if one exists
// used for drawing selection borders/corners
var activeGroup = this.getActiveGroup();
if (activeGroup) {
activeGroup.render(this.contextTop);
}
this.fire('after:render');
return this;
},
/**
* Exports canvas element to a dataurl image.
* @method toDataURL
* @param {String} format the format of the output image. Either "jpeg" or "png".
* @return {String}
*/
toDataURL: function (format) {
this.renderAll(true);
var data = (this.upperCanvasEl || this.lowerCanvasEl).toDataURL('image/' + format);
this.renderAll();
return data;
},
/**
* Exports canvas element to a dataurl image (allowing to change image size via multiplier).
* @method toDataURLWithMultiplier
* @param {String} format (png|jpeg)
* @param {Number} multiplier
* @return {String}
*/
toDataURLWithMultiplier: function (format, multiplier) {
var origWidth = this.getWidth(),
origHeight = this.getHeight(),
scaledWidth = origWidth * multiplier,
scaledHeight = origHeight * multiplier,
activeObject = this.getActiveObject();
this.setWidth(scaledWidth).setHeight(scaledHeight);
this.contextTop.scale(multiplier, multiplier);
if (activeObject) {
this.deactivateAll();
}
// restoring width, height for `renderAll` to draw
// background properly (while context is scaled)
this.width = origWidth;
this.height = origHeight;
this.renderAll(true);
var dataURL = this.toDataURL(format);
this.contextTop.scale(1 / multiplier, 1 / multiplier);
this.setWidth(origWidth).setHeight(origHeight);
if (activeObject) {
this.setActiveObject(activeObject);
}
this.renderAll();
return dataURL;
},
/**
* Returns coordinates of a center of canvas.
* Returned value is an object with top and left properties
* @method getCenter
* @return {Object} object with "top" and "left" number values
*/
getCenter: function () {
return {
top: this.getHeight() / 2,
left: this.getWidth() / 2
};
},
/**
* Centers object horizontally.
* @method centerObjectH
* @param {fabric.Object} object Object to center
* @return {fabric.Canvas} thisArg
*/
centerObjectH: function (object) {
object.set('left', this.getCenter().left);
this.renderAll();
return this;
},
/**
* Centers object vertically.
* @method centerObjectH
* @param {fabric.Object} object Object to center
* @return {fabric.Canvas} thisArg
* @chainable
*/
centerObjectV: function (object) {
object.set('top', this.getCenter().top);
this.renderAll();
return this;
},
/**
* Centers object vertically and horizontally.
* @method centerObject
* @param {fabric.Object} object Object to center
* @return {fabric.Canvas} thisArg
* @chainable
*/
centerObject: function (object) {
return this.centerObjectH(object).centerObjectV(object);
},
/**
* Straightens object, then rerenders canvas
* @method straightenObject
* @param {fabric.Object} object Object to straighten
* @return {fabric.Canvas} thisArg
* @chainable
*/
straightenObject: function (object) {
object.straighten();
this.renderAll();
return this;
},
/**
* Returs dataless JSON representation of canvas
* @method toDatalessJSON
* @return {String} json string
*/
toDatalessJSON: function () {
return this.toDatalessObject();
},
/**
* Returns object representation of canvas
* @method toObject
* @return {Object}
*/
toObject: function () {
return this._toObjectMethod('toObject');
},
/**
* Returns dataless object representation of canvas
* @method toDatalessObject
* @return {Object}
*/
toDatalessObject: function () {
return this._toObjectMethod('toDatalessObject');
},
/**
* @private
* @method _toObjectMethod
*/
_toObjectMethod: function (methodName) {
return {
objects: this._objects.map(function (instance){
// TODO (kangax): figure out how to clean this up
if (!this.includeDefaultValues) {
var originalValue = instance.includeDefaultValues;
instance.includeDefaultValues = false;
}
var object = instance[methodName]();
if (!this.includeDefaultValues) {
instance.includeDefaultValues = originalValue;
}
return object;
}, this),
background: this.backgroundColor
}
},
/**
* Returns true if canvas contains no objects
* @method isEmpty
* @return {Boolean} true if canvas is empty
*/
isEmpty: function () {
return this._objects.length === 0;
},
/**
* Removes an object from canvas and returns it
* @method remove
* @param object {Object} Object to remove
* @return {Object} removed object
*/
remove: function (object) {
removeFromArray(this._objects, object);
if (this.getActiveObject() === object) {
this.discardActiveObject();
}
this.renderAll();
return object;
},
/**
* Moves an object to the bottom of the stack of drawn objects
* @method sendToBack
* @param object {fabric.Object} Object to send to back
* @return {fabric.Canvas} thisArg
* @chainable
*/
sendToBack: function (object) {
removeFromArray(this._objects, object);
this._objects.unshift(object);
return this.renderAll();
},
/**
* Moves an object to the top of the stack of drawn objects
* @method bringToFront
* @param object {fabric.Object} Object to send
* @return {fabric.Canvas} thisArg
* @chainable
*/
bringToFront: function (object) {
removeFromArray(this._objects, object);
this._objects.push(object);
return this.renderAll();
},
/**
* Moves an object one level down in stack of drawn objects
* @method sendBackwards
* @param object {fabric.Object} Object to send
* @return {fabric.Canvas} thisArg
* @chainable
*/
sendBackwards: function (object) {
var idx = this._objects.indexOf(object),
nextIntersectingIdx = idx;
// if object is not on the bottom of stack
if (idx !== 0) {
// traverse down the stack looking for the nearest intersecting object
for (var i=idx-1; i>=0; --i) {
if (object.intersectsWithObject(this._objects[i]) || object.isContainedWithinObject(this._objects[i])) {
nextIntersectingIdx = i;
break;
}
}
removeFromArray(this._objects, object);
this._objects.splice(nextIntersectingIdx, 0, object);
}
return this.renderAll();
},
/**
* Moves an object one level up in stack of drawn objects
* @method sendForward
* @param object {fabric.Object} Object to send
* @return {fabric.Canvas} thisArg
* @chainable
*/
bringForward: function (object) {
var objects = this.getObjects(),
idx = objects.indexOf(object),
nextIntersectingIdx = idx;
// if object is not on top of stack (last item in an array)
if (idx !== objects.length-1) {
// traverse up the stack looking for the nearest intersecting object
for (var i = idx + 1, l = this._objects.length; i < l; ++i) {
if (object.intersectsWithObject(objects[i]) || object.isContainedWithinObject(this._objects[i])) {
nextIntersectingIdx = i;
break;
}
}
removeFromArray(objects, object);
objects.splice(nextIntersectingIdx, 0, object);
}
this.renderAll();
},
/**
* Returns object at specified index
* @method item
* @param {Number} index
* @return {fabric.Object}
*/
item: function (index) {
return this.getObjects()[index];
},
/**
* Returns number representation of an instance complexity
* @method complexity
* @return {Number} complexity
*/
complexity: function () {
return this.getObjects().reduce(function (memo, current) {
memo += current.complexity ? current.complexity() : 0;
return memo;
}, 0);
},
/**
* Iterates over all objects, invoking callback for each one of them
* @method forEachObject
* @return {fabric.Canvas} thisArg
*/
forEachObject: function(callback, context) {
var objects = this.getObjects(),
i = objects.length;
while (i--) {
callback.call(context, objects[i], i, objects);
}
return this;
},
/**
* Clears a canvas element and removes all event handlers.
* @method dispose
* @return {fabric.Canvas} thisArg
* @chainable
*/
dispose: function () {
this.clear();
if (this.interactive) {
removeListener(this.upperCanvasEl, 'mousedown', this._onMouseDown);
removeListener(this.upperCanvasEl, 'mousemove', this._onMouseMove);
removeListener(fabric.window, 'resize', this._onResize);
}
return this;
},
/**
* @private
* @method _resizeImageToFit
* @param {HTMLImageElement} imgEl
*/
_resizeImageToFit: function (imgEl) {
var imageWidth = imgEl.width || imgEl.offsetWidth,
widthScaleFactor = this.getWidth() / imageWidth;
// scale image down so that it has original dimensions when printed in large resolution
if (imageWidth) {
imgEl.width = imageWidth * widthScaleFactor;
}
}
});
/**
* Returns a string representation of an instance
* @method toString
* @return {String} string representation of an instance
*/
fabric.StaticCanvas.prototype.toString = function () { // Assign explicitly since `extend` doesn't take care of DontEnum bug yet
return '#<fabric.Canvas (' + this.complexity() + '): '+
'{ objects: ' + this.getObjects().length + ' }>';
};
extend(fabric.StaticCanvas, /** @scope fabric.StaticCanvas */ {
/**
* @static
* @property EMPTY_JSON
* @type String
*/
EMPTY_JSON: '{"objects": [], "background": "white"}',
/**
* Takes &lt;canvas> element and transforms its data in such way that it becomes grayscale
* @static
* @method toGrayscale
* @param {HTMLCanvasElement} canvasEl
*/
toGrayscale: function (canvasEl) {
var context = canvasEl.getContext('2d'),
imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
data = imageData.data,
iLen = imageData.width,
jLen = imageData.height,
index, average, i, j;
for (i = 0; i < iLen; i++) {
for (j = 0; j < jLen; j++) {
index = (i * 4) * jLen + (j * 4);
average = (data[index] + data[index + 1] + data[index + 2]) / 3;
data[index] = average;
data[index + 1] = average;
data[index + 2] = average;
}
}
context.putImageData(imageData, 0, 0);
},
/**
* Provides a way to check support of some of the canvas methods
* (either those of HTMLCanvasElement itself, or rendering context)
*
* @method supports
* @param methodName {String} Method to check support for;
* Could be one of "getImageData" or "toDataURL"
* @return {Boolean | null} `true` if method is supported (or at least exists),
* `null` if canvas element or context can not be initialized
*/
supports: function (methodName) {
var el = fabric.document.createElement('canvas');
if (typeof G_vmlCanvasManager !== 'undefined') {
G_vmlCanvasManager.initElement(el);
}
if (!el || !el.getContext) {
return null;
}
var ctx = el.getContext('2d');
if (!ctx) {
return null;
}
switch (methodName) {
case 'getImageData':
return typeof ctx.getImageData !== 'undefined';
case 'toDataURL':
return typeof el.toDataURL !== 'undefined';
default:
return null;
}
}
});
/**
* Returs JSON representation of canvas
* @function
* @method toJSON
* @return {String} json string
*/
fabric.StaticCanvas.prototype.toJSON = fabric.StaticCanvas.prototype.toObject;
})(typeof exports != 'undefined' ? exports : this);

View file

@ -86,39 +86,59 @@
* @param {Function} [options.easing] Easing function
* @param {Number} [options.duration=500] Duration of change
*/
function animate(options) {
function animate(options) {
options || (options = { });
options || (options = { });
var start = +new Date(),
duration = options.duration || 500,
finish = start + duration, time, pos,
onChange = options.onChange || function() { },
abort = options.abort || function() { return false; },
easing = options.easing || function(pos) { return (-Math.cos(pos * Math.PI) / 2) + 0.5; },
startValue = 'startValue' in options ? options.startValue : 0,
endValue = 'endValue' in options ? options.endValue : 100;
var start = +new Date(),
duration = options.duration || 500,
finish = start + duration, time, pos,
onChange = options.onChange || function() { },
abort = options.abort || function() { return false; },
easing = options.easing || function(pos) { return (-Math.cos(pos * Math.PI) / 2) + 0.5; },
startValue = 'startValue' in options ? options.startValue : 0,
endValue = 'endValue' in options ? options.endValue : 100;
options.onStart && options.onStart();
options.onStart && options.onStart();
var interval = setInterval(function() {
time = +new Date();
pos = time > finish ? 1 : (time - start) / duration;
onChange(startValue + (endValue - startValue) * easing(pos));
if (time > finish || abort()) {
clearInterval(interval);
options.onComplete && options.onComplete();
}
}, 10);
var interval = setInterval(function() {
time = +new Date();
pos = time > finish ? 1 : (time - start) / duration;
onChange(startValue + (endValue - startValue) * easing(pos));
if (time > finish || abort()) {
clearInterval(interval);
options.onComplete && options.onComplete();
}
}, 10);
return interval;
}
/**
* Loads image element from given url and passes it to a callback
* @method loadImage
* @memberOf fabric.util
* @param {String} url URL representing an image
* @param {Function} callback Callback; invoked with loaded image
* @param {Any} context optional Context to invoke callback in
*/
function loadImage(url, callback, context) {
if (url) {
var img = new Image();
/** @ignore */
img.onload = function () {
callback && callback.call(context, img);
img = img.onload = null;
};
img.src = url;
}
}
return interval;
}
fabric.util.removeFromArray = removeFromArray;
fabric.util.degreesToRadians = degreesToRadians;
fabric.util.toFixed = toFixed;
fabric.util.getRandomInt = getRandomInt;
fabric.util.falseFunction = falseFunction;
fabric.util.animate = animate;
})();
fabric.util.loadImage = loadImage;
})();