mirror of
https://github.com/Hopiu/fabric.js.git
synced 2026-05-03 19:34:46 +00:00
[BACK_INCOMPAT] Add support for static canvases — fabric.StaticCanvas. fabric.Canvas#loadImageFromURL -> fabric.util.loadImage. Add fabric.Canvas#centerObject.
This commit is contained in:
parent
15c0dc3768
commit
b8ea6b7cec
12 changed files with 2686 additions and 2707 deletions
|
|
@ -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;
|
||||
|
|
|
|||
14
README.md
14
README.md
|
|
@ -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
2484
dist/all.js
vendored
File diff suppressed because it is too large
Load diff
8
dist/all.min.js
vendored
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
BIN
dist/all.min.js.gz
vendored
Normal file
Binary file not shown.
|
|
@ -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;
|
||||
}
|
||||
|
||||
})();
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
fabric.util.object.extend(fabric.Canvas.prototype, {
|
||||
fabric.util.object.extend(fabric.StaticCanvas.prototype, {
|
||||
|
||||
/**
|
||||
* Centers object horizontally with animation.
|
||||
|
|
|
|||
1819
src/canvas.class.js
1819
src/canvas.class.js
File diff suppressed because it is too large
Load diff
|
|
@ -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') {
|
||||
|
|
|
|||
|
|
@ -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
948
src/static_canvas.class.js
Normal 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 <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);
|
||||
|
|
@ -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;
|
||||
})();
|
||||
Loading…
Reference in a new issue