1 (function (global) {
  2   
  3   "use strict";
  4   
  5   if (fabric.Canvas) {
  6     fabric.warn('fabric.Canvas is already defined.');
  7     return;
  8   }
  9   
 10   var window = global.window,
 11       document = window.document,
 12       
 13       // aliases for faster resolution
 14       extend = fabric.util.object.extend,
 15       capitalize = fabric.util.string.capitalize,
 16       camelize = fabric.util.string.camelize,
 17       getPointer = fabric.util.getPointer,
 18       getElementOffset = fabric.util.getElementOffset,
 19       removeFromArray = fabric.util.removeFromArray,
 20       addListener = fabric.util.addListener,
 21       removeListener = fabric.util.removeListener,
 22       
 23       utilMin = fabric.util.array.min,
 24       utilMax = fabric.util.array.max,
 25       
 26       sqrt = Math.sqrt,
 27       pow = Math.pow,
 28       atan2 = Math.atan2,
 29       abs = Math.abs,
 30       min = Math.min,
 31       max = Math.max,
 32       
 33       CANVAS_INIT_ERROR = new Error('Could not initialize `canvas` element'),
 34       FX_DURATION = 500,
 35       STROKE_OFFSET = 0.5,
 36       FX_TRANSITION = 'decel',
 37       
 38       cursorMap = {
 39         'tr': 'ne-resize',
 40         'br': 'se-resize',
 41         'bl': 'sw-resize',
 42         'tl': 'nw-resize',
 43         'ml': 'w-resize',
 44         'mt': 'n-resize',
 45         'mr': 'e-resize',
 46         'mb': 's-resize'
 47       };
 48   
 49   /**
 50    * @class fabric.Canvas
 51    * @constructor
 52    * @param {HTMLElement | String} el <canvas> element to initialize instance on
 53    * @param {Object} [options] Options object
 54    */
 55   fabric.Canvas = function (el, options) {
 56     
 57     options || (options = { });
 58     
 59     /**
 60      * The object literal containing mouse position if clicked in an empty area (no image)
 61      * @property _groupSelector
 62      * @type object
 63      */
 64     this._groupSelector = null;
 65 
 66     /**
 67      * The array literal containing all objects on canvas
 68      * @property _objects
 69      * @type array
 70      */
 71     this._objects = [];
 72 
 73     /**
 74      * The element that references the canvas interface implementation
 75      * @property _context
 76      * @type object
 77      */
 78     this._context = null;
 79 
 80     /**
 81      * The object literal containing the current x,y params of the transformation
 82      * @property _currentTransform
 83      * @type object
 84      */
 85     this._currentTransform = null;
 86     
 87     /**
 88      * References instance of fabric.Group - when multiple objects are selected
 89      * @property _activeGroup
 90      * @type object
 91      */
 92     this._activeGroup = null;
 93     
 94     /**
 95      * X coordinates of a path, captured during free drawing
 96      */
 97     this._freeDrawingXPoints = [ ];
 98     
 99     /**
100      * Y coordinates of a path, captured during free drawing
101      */
102     this._freeDrawingYPoints = [ ];
103     
104     this._createUpperCanvas(el);
105     this._initOptions(options);
106     this._initWrapperElement();
107     this._createLowerCanvas();
108 
109     this._initEvents();
110     
111     if (options.overlayImage) {
112       this.setOverlayImage(options.overlayImage);
113     }
114     
115     this.calcOffset();
116     
117     fabric.Canvas.activeInstance = this;
118   };
119   
120   extend(fabric.Canvas.prototype, fabric.Observable);
121   
122   extend(fabric.Canvas.prototype, /** @scope fabric.Canvas.prototype */ {
123     
124     /**
125      * Background color of this canvas instance
126      * @property
127      * @type String
128      */
129     backgroundColor:        'rgba(0, 0, 0, 0)',
130     
131     /**
132      * Indicates whether object selection should be enabled
133      * @property
134      * @type Boolean
135      */
136     selection:              true,
137     
138     /**
139      * Color of selection
140      * @property
141      * @type String
142      */
143     selectionColor:         'rgba(100, 100, 255, 0.3)', // blue
144     
145     /**
146      * Color of the border of selection (usually slightly darker than color of selection itself)
147      * @property
148      * @type String
149      */
150     selectionBorderColor:   'rgba(255, 255, 255, 0.3)',
151     
152     /**
153      * Width of a line used in selection
154      * @property
155      * @type Number
156      */
157     selectionLineWidth:     1,
158     
159     /**
160      * Color of the line used in free drawing mode
161      * @property
162      * @type String
163      */
164     freeDrawingColor:       'rgb(0, 0, 0)',
165     
166     /**
167      * Width of a line used in free drawing mode
168      * @property
169      * @type Number
170      */
171     freeDrawingLineWidth:   1,
172     
173     /**
174      * @property
175      * @type Boolean
176      */
177     includeDefaultValues:   true,
178     
179     /**
180      * Indicates whether images loaded via `fabric.Canvas#loadImageFromUrl` should be cached
181      * @property
182      * @type Boolean
183      */
184     shouldCacheImages:      false,
185     
186     /**
187      * Indicates whether objects' state should be saved
188      * @property
189      * @type Boolean
190      */
191     stateful:               true,
192     
193     /**
194      * Indicates whether fabric.Canvas#add should also re-render canvas. 
195      * Disabling this option could give a great performance boost when adding a lot of objects to canvas at once 
196      * (followed by a manual rendering after addition)
197      */
198     renderOnAddition:       true,
199     
200     /**
201      * @constant
202      * @type Number
203      */
204     CANVAS_WIDTH:           600,
205     
206     /**
207      * @constant
208      * @type Number
209      */
210     CANVAS_HEIGHT:          600,
211     
212     /**
213      * @constant
214      * @type String
215      */
216     CONTAINER_CLASS:        'canvas-container',
217     
218     /**
219      * Callback; invoked right before object is about to be scaled/rotated
220      * @method onBeforeScaleRotate
221      * @param {fabric.Object} target Object that's about to be scaled/rotated
222      */
223     onBeforeScaleRotate: function (target) {
224       /* NOOP */
225     },
226     
227     /**
228      * Callback; invoked on every redraw of canvas and is being passed a number indicating current fps
229      * @method onFpsUpdate
230      * @param {Number} fps
231      */
232     onFpsUpdate: null,
233     
234     /**
235      * Calculates canvas element offset relative to the document
236      * This method is also attached as "resize" event handler of window
237      * @method calcOffset
238      * @return {fabric.Canvas} instance
239      * @chainable
240      */
241     calcOffset: function () {
242       this._offset = getElementOffset(this.upperCanvasEl);
243       return this;
244     },
245     
246     /**
247      * Sets overlay image for this canvas
248      * @method setOverlayImage
249      * @param {String} url url of an image to set background to
250      * @param {Function} callback callback to invoke when image is loaded and set as an overlay one
251      * @return {fabric.Canvas} thisArg
252      * @chainable
253      */
254     setOverlayImage: function (url, callback) { // TODO (kangax): test callback
255       if (url) {
256         var _this = this, img = new Image();
257         
258         /** @ignore */
259         img.onload = function () { 
260           _this.overlayImage = img;
261           if (callback) {
262             callback();
263           }
264           img = img.onload = null;
265         };
266         img.src = url;
267       }
268       return this;
269     },
270     
271     /**
272      * @private
273      * @method _initWrapperElement
274      * @param {Number} width
275      * @param {Number} height
276      */
277     _initWrapperElement: function () {
278       this.wrapperEl = fabric.util.wrapElement(this.upperCanvasEl, 'div', { 
279         'class': this.CONTAINER_CLASS
280       });
281       fabric.util.setStyle(this.wrapperEl, {
282         width: this.getWidth() + 'px',
283         height: this.getHeight() + 'px',
284         position: 'relative'
285       });
286       fabric.util.makeElementUnselectable(this.wrapperEl);
287     },
288     
289     /**
290      * @private
291      * @method _applyCanvasStyle
292      * @param {Element} element
293      */
294     _applyCanvasStyle: function (element) {
295       var width = this.getWidth() || element.width,
296           height = this.getHeight() || element.height;
297           
298       fabric.util.setStyle(element, {
299         position: 'absolute',
300         width: width + 'px',
301         height: height + 'px',
302         left: 0,
303         top: 0
304       });
305       element.width = width;
306       element.height = height;
307       fabric.util.makeElementUnselectable(element);
308     },
309     
310     /**
311      * @private
312      * @method _createCanvasElement
313      * @param {Element} element
314      */
315     _createCanvasElement: function() {
316       var element = document.createElement('canvas');
317       if (!element) {
318         throw CANVAS_INIT_ERROR;
319       }
320       this._initCanvasElement(element);
321       return element;
322     },
323     
324     _initCanvasElement: function(element) {
325       if (typeof element.getContext === 'undefined' && 
326           typeof G_vmlCanvasManager !== 'undefined' && 
327           G_vmlCanvasManager.initElement) {
328             
329         G_vmlCanvasManager.initElement(element);
330       }
331       if (typeof element.getContext === 'undefined') {
332         throw CANVAS_INIT_ERROR;
333       }
334     },
335 
336     /**
337      * @method _initOptions
338      * @param {Object} options
339      */
340     _initOptions: function (options) {
341       for (var prop in options) {
342         this[prop] = options[prop];
343       }
344       
345       this.width = parseInt(this.upperCanvasEl.width, 10) || 0;
346       this.height = parseInt(this.upperCanvasEl.height, 10) || 0;
347 
348       this.upperCanvasEl.style.width = this.width + 'px';
349       this.upperCanvasEl.style.height = this.height + 'px';
350     },
351 
352     /**
353      * Adds mouse listeners to  canvas
354      * @method _initEvents
355      * @private
356      * See configuration documentation for more details.
357      */
358     _initEvents: function () {
359       
360       var _this = this;
361       
362       this._onMouseDown = function (e) { 
363         _this.__onMouseDown(e);
364         addListener(document, 'mouseup', _this._onMouseUp);
365       };
366       this._onMouseUp = function (e) { 
367         _this.__onMouseUp(e);
368         removeListener(document, 'mouseup', _this._onMouseUp);
369       };
370       this._onMouseMove = function (e) { _this.__onMouseMove(e); };
371       this._onResize = function (e) { _this.calcOffset() };
372       
373       addListener(this.upperCanvasEl, 'mousedown', this._onMouseDown);
374       addListener(document, 'mousemove', this._onMouseMove);
375       addListener(window, 'resize', this._onResize);
376     },
377     
378     /**
379      * @method _createUpperCanvas
380      * @param {HTMLElement|String} canvasEl Canvas element
381      * @throws {CANVAS_INIT_ERROR} If canvas can not be initialized
382      */
383     _createUpperCanvas: function (canvasEl) {
384       this.upperCanvasEl = fabric.util.getById(canvasEl) || this._createCanvasElement();
385       this._initCanvasElement(this.upperCanvasEl);
386       
387       fabric.util.addClass(this.upperCanvasEl, 'upper-canvas');
388       this._applyCanvasStyle(this.upperCanvasEl);
389       
390       this.contextTop = this.upperCanvasEl.getContext('2d');
391     },
392     
393     /**
394      * Creates a secondary canvas
395      * @method _createLowerCanvas
396      */
397     _createLowerCanvas: function () {
398       this.lowerCanvasEl = this._createCanvasElement();
399       this.lowerCanvasEl.className = 'lower-canvas';
400       
401       this.wrapperEl.insertBefore(this.lowerCanvasEl, this.upperCanvasEl);
402       
403       this._applyCanvasStyle(this.lowerCanvasEl);
404       this.contextContainer = this.lowerCanvasEl.getContext('2d');
405     },
406     
407     /**
408      * Returns canvas width
409      * @method getWidth
410      * @return {Number}
411      */
412     getWidth: function () {
413       return this.width;
414     },
415     
416     /**
417      * Returns canvas height
418      * @method getHeight
419      * @return {Number}
420      */
421     getHeight: function () {
422       return this.height;
423     },
424     
425     /**
426      * Sets width of this canvas instance
427      * @method setWidth
428      * @param {Number} width value to set width to
429      * @return {fabric.Canvas} instance
430      * @chainable true
431      */
432     setWidth: function (value) {
433       return this._setDimension('width', value);
434     },
435     
436     /**
437      * Sets height of this canvas instance
438      * @method setHeight
439      * @param {Number} height value to set height to
440      * @return {fabric.Canvas} instance
441      * @chainable true
442      */
443     setHeight: function (value) {
444       return this._setDimension('height', value);
445     },
446     
447     /**
448      * Sets dimensions (width, height) of this canvas instance
449      * @method setDimensions
450      * @param {Object} dimensions
451      * @return {fabric.Canvas} thisArg
452      * @chainable
453      */
454     setDimensions: function(dimensions) {
455       for (var prop in dimensions) {
456         this._setDimension(prop, dimensions[prop]);
457       }
458       return this;
459     },
460     
461     /**
462      * Helper for setting width/height
463      * @private
464      * @method _setDimensions
465      * @param {String} prop property (width|height)
466      * @param {Number} value value to set property to
467      * @return {fabric.Canvas} instance
468      * @chainable true
469      */
470     _setDimension: function (prop, value) {
471       this.lowerCanvasEl[prop] = value;
472       this.lowerCanvasEl.style[prop] = value + 'px';
473       
474       this.upperCanvasEl[prop] = value;
475       this.upperCanvasEl.style[prop] = value + 'px';
476       
477       this.wrapperEl.style[prop] = value + 'px';
478       
479       this[prop] = value;
480       
481       this.calcOffset();
482       this.renderAll();
483       
484       return this;
485     },
486     
487     /**
488      * Method that defines the actions when mouse is released on canvas.
489      * The method resets the currentTransform parameters, store the image corner
490      * position in the image object and render the canvas on top.
491      * @method __onMouseUp
492      * @param {Event} e Event object fired on mouseup
493      *
494      */
495     __onMouseUp: function (e) {
496       
497       if (this.isDrawingMode && this._isCurrentlyDrawing) {
498         this._finalizeDrawingPath();
499         return;
500       }
501       
502       if (this._currentTransform) {
503         
504         var transform = this._currentTransform,
505             target = transform.target;
506             
507         if (target._scaling) {
508           this.fire('object:scaled', { target: target });
509           target._scaling = false;
510         }
511         
512         // determine the new coords everytime the image changes its position
513         var i = this._objects.length;
514         while (i--) {
515           this._objects[i].setCoords();
516         }
517         
518         // only fire :modified event if target coordinates were changed during mousedown-mouseup
519         if (this.stateful && target.hasStateChanged()) {
520           target.isMoving = false;
521           this.fire('object:modified', { target: target });
522         }
523       }
524       
525       this._currentTransform = null;
526       
527       if (this._groupSelector) {
528         // group selection was completed, determine its bounds
529         this._findSelectedObjects(e);
530       }
531       var activeGroup = this.getActiveGroup();
532       if (activeGroup) {
533         if (this.stateful && activeGroup.hasStateChanged() && 
534             activeGroup.containsPoint(this.getPointer(e))) {
535           this.fire('group:modified', { target: activeGroup });
536         }
537         activeGroup.setObjectsCoords();
538         activeGroup.set('isMoving', false);
539         this._setCursor('default');
540       }
541       
542       // clear selection
543       this._groupSelector = null;
544       this.renderAll();
545       
546       this._setCursorFromEvent(e, target);
547       
548       // fix for FF
549       this._setCursor('');
550       
551       var _this = this;
552       setTimeout(function () {
553         _this._setCursorFromEvent(e, target);
554       }, 50);
555       
556       this.fire('mouse:up');
557     },
558     
559     _shouldClearSelection: function (e) {
560       var target = this.findTarget(e),
561           activeGroup = this.getActiveGroup();
562       return (
563         !target || (
564           target && 
565           activeGroup && 
566           !activeGroup.contains(target) && 
567           activeGroup !== target && 
568           !e.shiftKey
569         )
570       );
571     },
572 
573     /**
574      * Method that defines the actions when mouse is clic ked on canvas.
575      * The method inits the currentTransform parameters and renders all the
576      * canvas so the current image can be placed on the top canvas and the rest
577      * in on the container one.
578      * @method __onMouseDown
579      * @param e {Event} Event object fired on mousedown
580      *
581      */
582     __onMouseDown: function (e) {
583       
584       // accept only left clicks
585       if (e.which !== 1) return;
586       
587       if (this.isDrawingMode) {
588         this._prepareForDrawing(e);
589         
590         // capture coordinates immediately; this allows to draw dots (when movement never occurs)
591         this._captureDrawingPath(e);
592         
593         return;
594       }
595       
596       // ignore if some object is being transformed at this moment
597       if (this._currentTransform) return;
598       
599       var target = this.findTarget(e),
600           pointer = this.getPointer(e),
601           activeGroup = this.getActiveGroup(), 
602           corner;
603       
604       if (this._shouldClearSelection(e)) {
605         
606         this._groupSelector = {
607           ex: pointer.x,
608           ey: pointer.y,
609           top: 0,
610           left: 0
611         };
612         
613         this.deactivateAllWithDispatch();
614       }
615       else {
616         // determine if it's a drag or rotate case
617         // rotate and scale will happen at the same time
618         this.stateful && target.saveState();
619         
620         if (corner = target._findTargetCorner(e, this._offset)) {
621           this.onBeforeScaleRotate(target);
622         }
623         
624         this._setupCurrentTransform(e, target);
625         
626         var shouldHandleGroupLogic = e.shiftKey && (activeGroup || this.getActiveObject());
627         if (shouldHandleGroupLogic) {
628           this._handleGroupLogic(e, target);
629         }
630         else {
631           if (target !== this.getActiveGroup()) {
632             this.deactivateAll();
633           }
634           this.setActiveObject(target);
635         }
636       }
637       // we must renderAll so that active image is placed on the top canvas
638       this.renderAll();
639       
640       this.fire('mouse:down');
641     },
642     
643     /**
644      * Returns <canvas> element corresponding to this instance
645      * @method getElement
646      * @return {HTMLCanvasElement}
647      */
648     getElement: function () {
649       return this.upperCanvasEl;
650     },
651     
652     /**
653      * Deactivates all objects and dispatches appropriate events
654      * @method deactivateAllWithDispatch
655      * @return {fabric.Canvas} thisArg
656      */
657     deactivateAllWithDispatch: function () {
658       var activeGroup = this.getActiveGroup();
659       if (activeGroup) {
660         this.fire('before:group:destroyed', {
661           target: activeGroup
662         });
663       }
664       this.deactivateAll();
665       if (activeGroup) {
666         this.fire('after:group:destroyed');
667       }
668       this.fire('selection:cleared');
669       return this;
670     },
671     
672     /**
673      * @private
674      * @method _setupCurrentTransform
675      */
676     _setupCurrentTransform: function (e, target) {
677       var action = 'drag', 
678           corner,
679           pointer = getPointer(e);
680       
681       if (corner = target._findTargetCorner(e, this._offset)) {
682         action = (corner === 'ml' || corner === 'mr') 
683           ? 'scaleX' 
684           : (corner === 'mt' || corner === 'mb') 
685             ? 'scaleY' 
686             : 'rotate';
687       }
688       
689       this._currentTransform = {
690         target: target,
691         action: action,
692         scaleX: target.scaleX,
693         scaleY: target.scaleY,
694         offsetX: pointer.x - target.left,
695         offsetY: pointer.y - target.top,
696         ex: pointer.x,
697         ey: pointer.y,
698         left: target.left, 
699         top: target.top,
700         theta: target.theta,
701         width: target.width * target.scaleX
702       };
703       
704       this._currentTransform.original = {
705         left: target.left,
706         top: target.top
707       };
708     },
709     
710     _handleGroupLogic: function (e, target) {
711       if (target.isType('group')) {
712         // if it's a group, find target again, this time skipping group
713         target = this.findTarget(e, true);
714         // if even object is not found, bail out
715         if (!target || target.isType('group')) {
716           return;
717         }
718       }
719       var activeGroup = this.getActiveGroup();
720       if (activeGroup) {
721         if (activeGroup.contains(target)) {
722           activeGroup.remove(target);
723           target.setActive(false);
724           if (activeGroup.size() === 1) {
725             // remove group alltogether if after removal it only contains 1 object
726             this.removeActiveGroup();
727           }
728         }
729         else {
730           activeGroup.add(target);
731         }
732         this.fire('group:selected', { target: activeGroup });
733         activeGroup.setActive(true);
734       }
735       else {
736         // group does not exist
737         if (this._activeObject) {
738           // only if there's an active object
739           if (target !== this._activeObject) {
740             // and that object is not the actual target
741             var group = new fabric.Group([ this._activeObject,target ]);
742             this.setActiveGroup(group);
743             activeGroup = this.getActiveGroup();
744           }
745         }
746         // activate target object in any case
747         target.setActive(true);
748       }
749       
750       if (activeGroup) {
751         activeGroup.saveCoords();
752       }
753     },
754     
755     /**
756      * @private
757      * @method _prepareForDrawing
758      */
759     _prepareForDrawing: function(e) {
760       
761       this._isCurrentlyDrawing = true;
762       
763       this.removeActiveObject().renderAll();
764       
765       var pointer = this.getPointer(e);
766       
767       this._freeDrawingXPoints.length = this._freeDrawingYPoints.length = 0;
768       
769       this._freeDrawingXPoints.push(pointer.x);
770       this._freeDrawingYPoints.push(pointer.y);
771       
772       this.contextTop.beginPath();
773       this.contextTop.moveTo(pointer.x, pointer.y);
774       this.contextTop.strokeStyle = this.freeDrawingColor;
775       this.contextTop.lineWidth = this.freeDrawingLineWidth;
776       this.contextTop.lineCap = this.contextTop.lineJoin = 'round';
777     },
778     
779     /**
780      * @private
781      * @method _captureDrawingPath
782      */
783     _captureDrawingPath: function(e) {
784       var pointer = this.getPointer(e);
785       
786       this._freeDrawingXPoints.push(pointer.x);
787       this._freeDrawingYPoints.push(pointer.y);
788       
789       this.contextTop.lineTo(pointer.x, pointer.y);
790       this.contextTop.stroke();
791     },
792     
793     /**
794      * @private
795      * @method _finalizeDrawingPath
796      */
797     _finalizeDrawingPath: function() {
798       
799       this.contextTop.closePath();
800       
801       this._isCurrentlyDrawing = false;
802       
803       var minX = utilMin(this._freeDrawingXPoints),
804           minY = utilMin(this._freeDrawingYPoints),
805           maxX = utilMax(this._freeDrawingXPoints),
806           maxY = utilMax(this._freeDrawingYPoints),
807           ctx = this.contextTop,
808           path = [ ],
809           xPoint,
810           yPoint,
811           xPoints = this._freeDrawingXPoints,
812           yPoints = this._freeDrawingYPoints;
813       
814       path.push('M ', xPoints[0] - minX, ' ', yPoints[0] - minY, ' ');
815       
816       for (var i = 1; xPoint = xPoints[i], yPoint = yPoints[i]; i++) {
817         path.push('L ', xPoint - minX, ' ', yPoint - minY, ' ');
818       }
819       
820       // TODO (kangax): maybe remove Path creation from here, to decouple fabric.Canvas from fabric.Path, 
821       // and instead fire something like "drawing:completed" event with path string
822       
823       path = path.join('');
824       
825       if (path === "M 0 0 L 0 0 ") {
826         // do not create 0 width/height paths, as they are rendered inconsistently across browsers
827         // Firefox 4, for example, renders a dot, whereas Chrome 10 renders nothing
828         return;
829       }
830 
831       var p = new fabric.Path(path);
832        
833       p.fill = null;
834       p.stroke = this.freeDrawingColor;
835       p.strokeWidth = this.freeDrawingLineWidth;
836       this.add(p);
837       p.set("left", minX + (maxX - minX) / 2).set("top", minY + (maxY - minY) / 2).setCoords();
838       this.renderAll();
839       this.fire('path:created', { path: p });
840     },
841 
842    /**
843     * Method that defines the actions when mouse is hovering the canvas.
844     * The currentTransform parameter will definde whether the user is rotating/scaling/translating
845     * an image or neither of them (only hovering). A group selection is also possible and would cancel
846     * all any other type of action.
847     * In case of an image transformation only the top canvas will be rendered.
848     * @method __onMouseMove
849     * @param e {Event} Event object fired on mousemove
850     *
851     */
852     __onMouseMove: function (e) {
853       
854       if (this.isDrawingMode) {
855         if (this._isCurrentlyDrawing) {
856           this._captureDrawingPath(e);
857         }
858         return;
859       }
860       
861       var groupSelector = this._groupSelector;
862       
863       // We initially clicked in an empty area, so we draw a box for multiple selection.
864       if (groupSelector !== null) {
865         var pointer = getPointer(e);
866         groupSelector.left = pointer.x - this._offset.left - groupSelector.ex;
867         groupSelector.top = pointer.y - this._offset.top - groupSelector.ey;
868         this.renderTop();
869       }
870       else if (!this._currentTransform) {
871         
872         // alias style to elimintate unnecessary lookup
873         var style = this.upperCanvasEl.style;
874         
875         // Here we are hovering the canvas then we will determine
876         // what part of the pictures we are hovering to change the caret symbol.
877         // We won't do that while dragging or rotating in order to improve the
878         // performance.
879         var target = this.findTarget(e);
880         
881         if (!target) {  
882           // image/text was hovered-out from, we remove its borders
883           for (var i = this._objects.length; i--; ) {
884             if (!this._objects[i].active) {
885               this._objects[i].setActive(false);
886             }
887           }
888           style.cursor = 'default';
889         }
890         else {
891           // set proper cursor 
892           this._setCursorFromEvent(e, target);
893           if (target.isActive()) {
894             // display corners when hovering over an image
895             target.setCornersVisibility && target.setCornersVisibility(true);
896           }
897         }
898       }
899       else {
900         // object is being transformed (scaled/rotated/moved/etc.)
901         var pointer = getPointer(e), 
902             x = pointer.x, 
903             y = pointer.y;
904         
905         this._currentTransform.target.isMoving = true;
906         
907         if (this._currentTransform.action === 'rotate') {  
908           // rotate object only if shift key is not pressed 
909           // and if it is not a group we are transforming
910           
911           if (!e.shiftKey) {
912             this._rotateObject(x, y);
913           }
914           this._scaleObject(x, y);
915         }
916         else if (this._currentTransform.action === 'scaleX') {
917           this._scaleObject(x, y, 'x');
918         }
919         else if (this._currentTransform.action === 'scaleY') {
920           this._scaleObject(x, y, 'y');
921         }
922         else {
923           this._translateObject(x, y);
924           
925           this.fire('object:moved', {
926             target: this._currentTransform.target
927           });
928         }
929         // only commit here. when we are actually moving the pictures
930         this.renderAll();
931       }
932     },
933 
934     /**
935      * Translates object by "setting" its left/top
936      * @method _translateObject
937      * @param x {Number} pointer's x coordinate
938      * @param y {Number} pointer's y coordinate
939      */
940     _translateObject: function (x, y) {
941       var target = this._currentTransform.target;
942       target.lockMovementX || target.set('left', x - this._currentTransform.offsetX);
943       target.lockMovementY || target.set('top', y - this._currentTransform.offsetY);
944     },
945 
946     /**
947      * Scales object by invoking its scaleX/scaleY methods
948      * @method _scaleObject
949      * @param x {Number} pointer's x coordinate
950      * @param y {Number} pointer's y coordinate
951      * @param by {String} Either 'x' or 'y' - specifies dimension constraint by which to scale an object. 
952      *                    When not provided, an object is scaled by both dimensions equally
953      */ 
954     _scaleObject: function (x, y, by) {
955       var t = this._currentTransform,
956           offset = this._offset,
957           target = t.target;
958       
959       if (target.lockScalingX && target.lockScalingY) return;
960       
961       var lastLen = sqrt(pow(t.ey - t.top - offset.top, 2) + pow(t.ex - t.left - offset.left, 2)),
962           curLen = sqrt(pow(y - t.top - offset.top, 2) + pow(x - t.left - offset.left, 2));
963       
964       target._scaling = true;
965       
966       if (!by) {
967         target.lockScalingX || target.set('scaleX', t.scaleX * curLen/lastLen);
968         target.lockScalingY || target.set('scaleY', t.scaleY * curLen/lastLen);
969       }
970       else if (by === 'x' && !target.lockUniScaling) {
971         target.lockScalingX || target.set('scaleX', t.scaleX * curLen/lastLen);
972       }
973       else if (by === 'y' && !target.lockUniScaling) {
974         target.lockScalingY || target.set('scaleY', t.scaleY * curLen/lastLen);
975       }
976     },
977 
978     /**
979      * Rotates object by invoking its rotate method
980      * @method _rotateObject
981      * @param x {Number} pointer's x coordinate
982      * @param y {Number} pointer's y coordinate
983      */ 
984     _rotateObject: function (x, y) {
985       
986       var t = this._currentTransform, 
987           o = this._offset;
988       
989       if (t.target.lockRotation) return;
990       
991       var lastAngle = atan2(t.ey - t.top - o.top, t.ex - t.left - o.left),
992           curAngle = atan2(y - t.top - o.top, x - t.left - o.left);
993           
994       t.target.set('theta', (curAngle - lastAngle) + t.theta);
995     },
996     
997     /**
998      * @method _setCursor
999      */
1000     _setCursor: function (value) {
1001       this.upperCanvasEl.style.cursor = value;
1002     },
1003     
1004     /**
1005      * Sets the cursor depending on where the canvas is being hovered.
1006      * Note: very buggy in Opera
1007      * @method _setCursorFromEvent
1008      * @param e {Event} Event object
1009      * @param target {Object} Object that the mouse is hovering, if so.
1010      */
1011     _setCursorFromEvent: function (e, target) {
1012       var s = this.upperCanvasEl.style;
1013       if (!target) {
1014         s.cursor = 'default';
1015         return false;
1016       }
1017       else {
1018         var activeGroup = this.getActiveGroup();
1019         // only show proper corner when group selection is not active
1020         var corner = !!target._findTargetCorner 
1021                       && (!activeGroup || !activeGroup.contains(target)) 
1022                       && target._findTargetCorner(e, this._offset);
1023         
1024         if (!corner) {
1025           s.cursor = 'move';
1026         }
1027         else {
1028           if (corner in cursorMap) {
1029             s.cursor = cursorMap[corner];
1030           }
1031           else {
1032             s.cursor = 'default';
1033             return false;
1034           }
1035         }
1036       }
1037       return true;
1038     },
1039     
1040     /**
1041      * Given a context, renders an object on that context 
1042      * @param ctx {Object} context to render object on
1043      * @param object {Object} object to render
1044      * @private
1045      */
1046     _draw: function (ctx, object) {
1047       object && object.render(ctx);
1048     },
1049     
1050     /**
1051      * @method _drawSelection
1052      * @private
1053      */
1054     _drawSelection: function () {
1055       var groupSelector = this._groupSelector,
1056           left = groupSelector.left,
1057           top = groupSelector.top,
1058           aleft = abs(left),
1059           atop = abs(top);
1060 
1061       this.contextTop.fillStyle = this.selectionColor;
1062 
1063       this.contextTop.fillRect(
1064         groupSelector.ex - ((left > 0) ? 0 : -left),
1065         groupSelector.ey - ((top > 0) ? 0 : -top),
1066         aleft, 
1067         atop
1068       );
1069 
1070       this.contextTop.lineWidth = this.selectionLineWidth;
1071       this.contextTop.strokeStyle = this.selectionBorderColor;
1072       
1073       this.contextTop.strokeRect(
1074         groupSelector.ex + STROKE_OFFSET - ((left > 0) ? 0 : aleft), 
1075         groupSelector.ey + STROKE_OFFSET - ((top > 0) ? 0 : atop),
1076         aleft,
1077         atop
1078       );
1079     },
1080     
1081     _findSelectedObjects: function (e) {
1082       var target, 
1083           targetRegion,
1084           group = [ ],
1085           x1 = this._groupSelector.ex,
1086           y1 = this._groupSelector.ey,
1087           x2 = x1 + this._groupSelector.left,
1088           y2 = y1 + this._groupSelector.top,
1089           currentObject,
1090           selectionX1Y1 = new fabric.Point(min(x1, x2), min(y1, y2)),
1091           selectionX2Y2 = new fabric.Point(max(x1, x2), max(y1, y2));
1092       
1093       for (var i = 0, len = this._objects.length; i < len; ++i) {
1094         currentObject = this._objects[i];
1095         
1096         if (currentObject.intersectsWithRect(selectionX1Y1, selectionX2Y2) || 
1097             currentObject.isContainedWithinRect(selectionX1Y1, selectionX2Y2)) {
1098           
1099           if (this.selection && currentObject.selectable) {
1100             currentObject.setActive(true);
1101             group.push(currentObject);
1102           }
1103         }
1104       }
1105       
1106       // do not create group for 1 element only
1107       if (group.length === 1) {
1108         this.setActiveObject(group[0]);
1109         this.fire('object:selected', {
1110           target: group[0]
1111         });
1112       } 
1113       else if (group.length > 1) {
1114         var group = new fabric.Group(group);
1115         this.setActiveGroup(group);
1116         group.saveCoords();
1117         this.fire('group:selected', { target: group });
1118       }
1119       
1120       this.renderAll();
1121     },
1122     
1123     /**
1124      * Adds objects to canvas, then renders canvas;
1125      * Objects should be instances of (or inherit from) fabric.Object
1126      * @method add
1127      * @return {fabric.Canvas} thisArg
1128      * @chainable
1129      */
1130     add: function () {
1131       this._objects.push.apply(this._objects, arguments);
1132       for (var i = arguments.length; i--; ) {
1133         this.stateful && arguments[i].setupState();
1134         arguments[i].setCoords();
1135       }
1136       this.renderOnAddition && this.renderAll();
1137       return this;
1138     },
1139     
1140     /**
1141      * Inserts an object to canvas at specified index and renders canvas. 
1142      * An object should be an instance of (or inherit from) fabric.Object
1143      * @method insertAt
1144      * @param object {Object} Object to insert
1145      * @param index {Number} index to insert object at
1146      * @return {fabric.Canvas} instance
1147      */
1148     insertAt: function (object, index) {
1149       this._objects.splice(index, 0, object);
1150       this.stateful && object.setupState();
1151       object.setCoords();
1152       this.renderAll();
1153       return this;
1154     },
1155     
1156     /**
1157      * Returns an array of objects this instance has
1158      * @method getObjects
1159      * @return {Array}
1160      */
1161     getObjects: function () {
1162       return this._objects;
1163     },
1164     
1165     /**
1166      * Returns topmost canvas context
1167      * @method getContext
1168      * @return {CanvasRenderingContext2D}
1169      */
1170     getContext: function () {
1171       return this.contextTop;
1172     },
1173     
1174     /**
1175      * Clears specified context of canvas element
1176      * @method clearContext
1177      * @param context {Object} ctx context to clear
1178      * @return {fabric.Canvas} thisArg
1179      * @chainable
1180      */
1181     clearContext: function(ctx) {
1182       ctx.clearRect(0, 0, this.width, this.height);
1183       return this;
1184     },
1185     
1186     /**
1187      * Clears all contexts (background, main, top) of an instance
1188      * @method clear
1189      * @return {fabric.Canvas} thisArg
1190      * @chainable
1191      */
1192     clear: function () {
1193       this._objects.length = 0;
1194       this.clearContext(this.contextTop);
1195       this.clearContext(this.contextContainer);
1196       this.renderAll();
1197       return this;
1198     },
1199 
1200     /**
1201      * Renders both the top canvas and the secondary container canvas.
1202      * @method renderAll
1203      * @param allOnTop {Boolean} optional Whether we want to force all images to be rendered on the top canvas
1204      * @return {fabric.Canvas} instance
1205      * @chainable
1206      */ 
1207     renderAll: function (allOnTop) {
1208       
1209       var containerCanvas = this[allOnTop ? 'contextTop' : 'contextContainer'];
1210 
1211       this.clearContext(this.contextTop);
1212 
1213       if (!allOnTop) {
1214         this.clearContext(containerCanvas);
1215       }
1216       
1217       var length = this._objects.length,
1218           activeGroup = this.getActiveGroup(),
1219           startTime = new Date();
1220       
1221       if (this.clipTo) {
1222         containerCanvas.save();
1223         containerCanvas.beginPath();
1224         this.clipTo(containerCanvas);
1225         containerCanvas.clip();
1226       }
1227       
1228       containerCanvas.fillStyle = this.backgroundColor;
1229       containerCanvas.fillRect(0, 0, this.width, this.height);
1230       
1231       if (length) {
1232         for (var i = 0; i < length; ++i) {
1233           if (!activeGroup ||
1234               (activeGroup &&
1235               !activeGroup.contains(this._objects[i]))) {
1236             this._draw(containerCanvas, this._objects[i]);
1237           }
1238         }
1239       }
1240       
1241       if (this.clipTo) {
1242         containerCanvas.restore();
1243       }
1244       
1245       // delegate rendering to group selection (if one exists)
1246       if (activeGroup) {
1247         this._draw(this.contextTop, activeGroup);
1248       }
1249       
1250       if (this.overlayImage) {
1251         this.contextTop.drawImage(this.overlayImage, 0, 0);
1252       }
1253       
1254       if (this.onFpsUpdate) {
1255         var elapsedTime = new Date() - startTime;
1256         this.onFpsUpdate(~~(1000 / elapsedTime));
1257       }
1258       
1259       this.fire('after:render');
1260       
1261       return this;
1262     },
1263 
1264     /**
1265      * Method to render only the top canvas.
1266      * Also used to render the group selection box.
1267      * @method renderTop
1268      * @return {fabric.Canvas} thisArg
1269      * @chainable
1270      */
1271     renderTop: function () {
1272       
1273       this.clearContext(this.contextTop);
1274       if (this.overlayImage) {
1275         this.contextTop.drawImage(this.overlayImage, 0, 0);
1276       }
1277       
1278       // we render the top context - last object
1279       if (this.selection && this._groupSelector) {
1280         this._drawSelection();
1281       }
1282       
1283       // delegate rendering to group selection if one exists
1284       // used for drawing selection borders/corners
1285       var activeGroup = this.getActiveGroup();
1286       if (activeGroup) {
1287         activeGroup.render(this.contextTop);
1288       }
1289       
1290       this.fire('after:render');
1291       
1292       return this;
1293     },
1294     
1295     /**
1296      * Applies one implementation of 'point inside polygon' algorithm
1297      * @method containsPoint
1298      * @param e { Event } event object
1299      * @param target { fabric.Object } object to test against
1300      * @return {Boolean} true if point contains within area of given object
1301      */
1302     containsPoint: function (e, target) {
1303       var pointer = this.getPointer(e),
1304           xy = this._normalizePointer(target, pointer),
1305           x = xy.x, 
1306           y = xy.y;
1307       
1308       // http://www.geog.ubc.ca/courses/klink/gis.notes/ncgia/u32.html
1309       // http://idav.ucdavis.edu/~okreylos/TAship/Spring2000/PointInPolygon.html
1310       
1311       // we iterate through each object. If target found, return it.
1312       var iLines = target._getImageLines(target.oCoords),
1313           xpoints = target._findCrossPoints(x, y, iLines);
1314       
1315       // if xcount is odd then we clicked inside the object
1316       // For the specific case of square images xcount === 1 in all true cases
1317       if ((xpoints && xpoints % 2 === 1) || target._findTargetCorner(e, this._offset)) {
1318         return true;
1319       }
1320       return false;
1321     },
1322     
1323     /**
1324      * @private
1325      * @method _normalizePointer
1326      */
1327     _normalizePointer: function (object, pointer) {
1328       
1329       var activeGroup = this.getActiveGroup(), 
1330           x = pointer.x, 
1331           y = pointer.y;
1332       
1333       var isObjectInGroup = (
1334         activeGroup && 
1335         object.type !== 'group' && 
1336         activeGroup.contains(object)
1337       );
1338       
1339       if (isObjectInGroup) {
1340         x -= activeGroup.left;
1341         y -= activeGroup.top;
1342       }
1343       return { x: x, y: y };
1344     },
1345 
1346     /**
1347      * Method that determines what object we are clicking on
1348      * @method findTarget
1349      * @param {Event} e mouse event
1350      * @param {Boolean} skipGroup when true, group is skipped and only objects are traversed through
1351      */ 
1352     findTarget: function (e, skipGroup) {
1353       
1354       var target,
1355           pointer = this.getPointer(e);
1356       
1357       // first check current group (if one exists)
1358       var activeGroup = this.getActiveGroup();
1359       
1360       if (activeGroup && !skipGroup && this.containsPoint(e, activeGroup)) {
1361         target = activeGroup;
1362         return target;
1363       }
1364       
1365       // then check all of the objects on canvas
1366       for (var i = this._objects.length; i--; ) {
1367         if (this.containsPoint(e, this._objects[i])) {
1368           target = this._objects[i];
1369           this.relatedTarget = target;
1370           break;
1371         }
1372       }
1373       if (this.selection && target && target.selectable) {
1374         return target;
1375       }
1376     },
1377 
1378     /**
1379      * Exports canvas element to a dataurl image.
1380      * @method toDataURL
1381      * @param {String} format the format of the output image. Either "jpeg" or "png".
1382      * @return {String}
1383      */
1384     toDataURL: function (format) {
1385       var data;
1386       if (!format) {
1387         format = 'png';
1388       }
1389       if (format === 'jpeg' || format === 'png') {
1390         this.renderAll(true);
1391         data = this.upperCanvasEl.toDataURL('image/' + format);
1392         this.renderAll();
1393       }
1394       return data;
1395     },
1396     
1397     /**
1398      * Exports canvas element to a dataurl image (allowing to change image size via multiplier).
1399      * @method toDataURLWithMultiplier
1400      * @param {String} format (png|jpeg)
1401      * @param {Number} multiplier
1402      * @return {String}
1403      */
1404     toDataURLWithMultiplier: function (format, multiplier) {
1405       
1406       var origWidth = this.getWidth(),
1407           origHeight = this.getHeight(),
1408           scaledWidth = origWidth * multiplier,
1409           scaledHeight = origHeight * multiplier,
1410           activeObject = this.getActiveObject();
1411       
1412       this.setWidth(scaledWidth).setHeight(scaledHeight);
1413       this.contextTop.scale(multiplier, multiplier);
1414       
1415       if (activeObject) {
1416         this.deactivateAll().renderAll();
1417       }
1418       var dataURL = this.toDataURL(format);
1419 
1420       this.contextTop.scale( 1 / multiplier,  1 / multiplier);
1421       this.setWidth(origWidth).setHeight(origHeight);
1422       
1423       if (activeObject) {
1424         this.setActiveObject(activeObject);
1425       }
1426       this.renderAll();
1427       
1428       return dataURL;
1429     },
1430     
1431     /**
1432      * Returns pointer coordinates relative to canvas.
1433      * @method getPointer
1434      * @return {Object} object with "x" and "y" number values
1435      */
1436     getPointer: function (e) {
1437       var pointer = getPointer(e);
1438       return {
1439         x: pointer.x - this._offset.left,
1440         y: pointer.y - this._offset.top
1441       };
1442     },
1443     
1444     /**
1445      * Returns coordinates of a center of canvas.
1446      * Returned value is an object with top and left properties
1447      * @method getCenter
1448      * @return {Object} object with "top" and "left" number values
1449      */
1450     getCenter: function () {
1451       return {
1452         top: this.getHeight() / 2,
1453         left: this.getWidth() / 2
1454       };
1455     },
1456     
1457     /**
1458      * Centers object horizontally.
1459      * @method centerObjectH
1460      * @param {fabric.Object} object Object to center
1461      * @return {fabric.Canvas} thisArg
1462      */
1463     centerObjectH: function (object) {
1464       object.set('left', this.getCenter().left);
1465       this.renderAll();
1466       return this;
1467     },
1468     
1469     /**
1470      * Centers object horizontally with animation.
1471      * @method fxCenterObjectH
1472      * @param {fabric.Object} object Object to center
1473      * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties
1474      * @return {fabric.Canvas} thisArg
1475      * @chainable
1476      */
1477     fxCenterObjectH: function (object, callbacks) {
1478       callbacks = callbacks || { };
1479 
1480       var empty = function() { },
1481           onComplete = callbacks.onComplete || empty,
1482           onChange = callbacks.onChange || empty,
1483           _this = this;
1484 
1485       fabric.util.animate({
1486         startValue: object.get('left'),
1487         endValue: this.getCenter().left,
1488         duration: this.FX_DURATION,
1489         onChange: function(value) {
1490           object.set('left', value);
1491           _this.renderAll();
1492           onChange();
1493         },
1494         onComplete: function() {
1495           object.setCoords();
1496           onComplete();
1497         }
1498       });
1499 
1500       return this;
1501     },
1502     
1503     /**
1504      * Centers object vertically.
1505      * @method centerObjectH
1506      * @param {fabric.Object} object Object to center
1507      * @return {fabric.Canvas} thisArg
1508      * @chainable
1509      */
1510     centerObjectV: function (object) {
1511       object.set('top', this.getCenter().top);
1512       this.renderAll();
1513       return this;
1514     },
1515     
1516     /**
1517      * Centers object vertically with animation.
1518      * @method fxCenterObjectV
1519      * @param {fabric.Object} object Object to center
1520      * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties
1521      * @return {fabric.Canvas} thisArg
1522      * @chainable
1523      */
1524     fxCenterObjectV: function (object, callbacks) {
1525       callbacks = callbacks || { };
1526 
1527       var empty = function() { },
1528           onComplete = callbacks.onComplete || empty,
1529           onChange = callbacks.onChange || empty,
1530           _this = this;
1531 
1532       fabric.util.animate({
1533         startValue: object.get('top'),
1534         endValue: this.getCenter().top,
1535         duration: this.FX_DURATION,
1536         onChange: function(value) {
1537           object.set('top', value);
1538           _this.renderAll();
1539           onChange();
1540         },
1541         onComplete: function() {
1542           object.setCoords();
1543           onComplete();
1544         }
1545       });
1546 
1547       return this;
1548     },
1549     
1550     /**
1551      * Straightens object, then rerenders canvas
1552      * @method straightenObject
1553      * @param {fabric.Object} object Object to straighten
1554      * @return {fabric.Canvas} thisArg
1555      * @chainable
1556      */
1557     straightenObject: function (object) {
1558       object.straighten();
1559       this.renderAll();
1560       return this;
1561     },
1562     
1563     /**
1564      * Same as `fabric.Canvas#straightenObject`, but animated
1565      * @method fxStraightenObject
1566      * @param {fabric.Object} object Object to straighten
1567      * @return {fabric.Canvas} thisArg
1568      * @chainable
1569      */
1570     fxStraightenObject: function (object) {
1571       object.fxStraighten({
1572         onChange: this.renderAll.bind(this)
1573       });
1574       return this;
1575     },
1576     
1577     /**
1578      * Returs dataless JSON representation of canvas
1579      * @method toDatalessJSON
1580      * @return {String} json string
1581      */
1582     toDatalessJSON: function () {
1583       return this.toDatalessObject();
1584     },
1585     
1586     /**
1587      * Returns object representation of canvas
1588      * @method toObject
1589      * @return {Object}
1590      */
1591     toObject: function () {
1592       return this._toObjectMethod('toObject');
1593     },
1594     
1595     /**
1596      * Returns dataless object representation of canvas
1597      * @method toDatalessObject
1598      * @return {Object}
1599      */
1600     toDatalessObject: function () {
1601       return this._toObjectMethod('toDatalessObject');
1602     },
1603     
1604     /**
1605      * @private
1606      * @method _toObjectMethod
1607      */
1608     _toObjectMethod: function (methodName) {
1609       return { 
1610         objects: this._objects.map(function (instance){
1611           // TODO (kangax): figure out how to clean this up
1612           if (!this.includeDefaultValues) {
1613             var originalValue = instance.includeDefaultValues;
1614             instance.includeDefaultValues = false;
1615           }
1616           var object = instance[methodName]();
1617           if (!this.includeDefaultValues) {
1618             instance.includeDefaultValues = originalValue;
1619           }
1620           return object;
1621         }, this),
1622         background: this.backgroundColor
1623       }
1624     },
1625 
1626     /**
1627      * Returns true if canvas contains no objects
1628      * @method isEmpty
1629      * @return {Boolean} true if canvas is empty
1630      */
1631     isEmpty: function () {
1632       return this._objects.length === 0;
1633     },
1634     
1635     /**
1636      * Populates canvas with data from the specified JSON
1637      * JSON format must conform to the one of `fabric.Canvas#toJSON`
1638      * @method loadFromJSON
1639      * @param {String} json JSON string
1640      * @param {Function} callback Callback, invoked when json is parsed 
1641      *                            and corresponding objects (e.g: fabric.Image) 
1642      *                            are initialized
1643      * @return {fabric.Canvas} instance
1644      * @chainable
1645      */
1646     loadFromJSON: function (json, callback) {
1647       if (!json) return;
1648       
1649       var serialized = JSON.parse(json);
1650       if (!serialized || (serialized && !serialized.objects)) return;
1651       
1652       this.clear();
1653       var _this = this;
1654       this._enlivenObjects(serialized.objects, function () {
1655         _this.backgroundColor = serialized.background;
1656         if (callback) {
1657           callback();
1658         }
1659       });
1660       
1661       return this;
1662     },
1663     
1664     /**
1665      * @method _enlivenObjects
1666      * @param {Array} objects
1667      * @param {Function} callback
1668      */
1669     _enlivenObjects: function (objects, callback) {
1670       var numLoadedImages = 0,
1671           // get length of all images 
1672           numTotalImages = objects.filter(function (o) {
1673             return o.type === 'image';
1674           }).length;
1675       
1676       var _this = this;
1677       
1678       objects.forEach(function (o, index) {
1679         if (!o.type) {
1680           return;
1681         }
1682         switch (o.type) {
1683           case 'image':
1684           case 'font':
1685             fabric[capitalize(o.type)].fromObject(o, function (o) {
1686               _this.insertAt(o, index);
1687               if (++numLoadedImages === numTotalImages) {
1688                 if (callback) {
1689                   callback();
1690                 }
1691               }
1692             });
1693             break;
1694           default:
1695             var klass = fabric[camelize(capitalize(o.type))];
1696             if (klass && klass.fromObject) {
1697               _this.insertAt(klass.fromObject(o), index);
1698             }
1699             break;
1700         }
1701       });
1702       
1703       if (numTotalImages === 0 && callback) {
1704         callback();
1705       }
1706     },
1707     
1708     /**
1709      * Populates canvas with data from the specified dataless JSON
1710      * JSON format must conform to the one of `fabric.Canvas#toDatalessJSON`
1711      * @method loadFromDatalessJSON
1712      * @param {String} json JSON string
1713      * @param {Function} callback Callback, invoked when json is parsed 
1714      *                            and corresponding objects (e.g: fabric.Image) 
1715      *                            are initialized
1716      * @return {fabric.Canvas} instance
1717      * @chainable
1718      */
1719     loadFromDatalessJSON: function (json, callback) {
1720       
1721       if (!json) {
1722         return;
1723       }
1724 
1725       // serialize if it wasn't already
1726       var serialized = (typeof json === 'string')
1727         ? JSON.parse(json)
1728         : json;
1729       
1730       if (!serialized || (serialized && !serialized.objects)) return;
1731       
1732       this.clear();
1733 
1734       // TODO: test this
1735       this.backgroundColor = serialized.background;
1736       this._enlivenDatalessObjects(serialized.objects, callback);
1737     },
1738     
1739     /**
1740      * @method _enlivenDatalessObjects
1741      * @param {Array} objects
1742      * @param {Function} callback
1743      */
1744     _enlivenDatalessObjects: function (objects, callback) {
1745       
1746       /** @ignore */
1747       function onObjectLoaded(object, index) {
1748         _this.insertAt(object, index);
1749         object.setCoords();
1750         if (++numLoadedObjects === numTotalObjects) {
1751           callback && callback();
1752         }
1753       }
1754       
1755       var _this = this,
1756           numLoadedObjects = 0,
1757           numTotalObjects = objects.length;
1758       
1759       if (numTotalObjects === 0 && callback) {
1760         callback();
1761       }
1762       
1763       try {
1764         objects.forEach(function (obj, index) {
1765           
1766           var pathProp = obj.paths ? 'paths' : 'path';
1767           var path = obj[pathProp];
1768 
1769           delete obj[pathProp];
1770           
1771           if (typeof path !== 'string') {
1772             switch (obj.type) {
1773               case 'image':
1774               case 'text':
1775                 fabric[capitalize(obj.type)].fromObject(obj, function (o) {
1776                   onObjectLoaded(o, index);
1777                 });
1778                 break;
1779               default:
1780                 var klass = fabric[camelize(capitalize(obj.type))];
1781                 if (klass && klass.fromObject) {
1782                   // restore path
1783                   if (path) {
1784                     obj[pathProp] = path;
1785                   }
1786                   onObjectLoaded(klass.fromObject(obj), index);
1787                 }
1788                 break;
1789             }
1790           }
1791           else {
1792             if (obj.type === 'image') {
1793               _this.loadImageFromURL(path, function (image) {
1794                 image.setSourcePath(path);
1795 
1796                 extend(image, obj);
1797                 image.setAngle(obj.angle);
1798 
1799                 onObjectLoaded(image, index);
1800               });
1801             }
1802             else if (obj.type === 'text') {
1803               
1804               obj.path = path;
1805               var object = fabric.Text.fromObject(obj);
1806               var onscriptload = function () {
1807                 // TODO (kangax): find out why Opera refuses to work without this timeout
1808                 if (Object.prototype.toString.call(window.opera) === '[object Opera]') {
1809                   setTimeout(function () {
1810                     onObjectLoaded(object, index);
1811                   }, 500);
1812                 }
1813                 else {
1814                   onObjectLoaded(object, index);
1815                 }
1816               }
1817               
1818               fabric.util.getScript(path, onscriptload);
1819             }
1820             else {
1821               _this.loadSVGFromURL(path, function (elements, options) {
1822                 if (elements.length > 1) {
1823                   var object = new fabric.PathGroup(elements, obj);
1824                 }
1825                 else {
1826                   var object = elements[0];
1827                 }
1828                 object.setSourcePath(path);
1829 
1830                 // copy parameters from serialied json to object (left, top, scaleX, scaleY, etc.)
1831                 // skip this step if an object is a PathGroup, since we already passed it options object before
1832                 if (!(object instanceof fabric.PathGroup)) {
1833                   extend(object, obj);
1834                   if (typeof obj.angle !== 'undefined') {
1835                     object.setAngle(obj.angle);
1836                   }
1837                 }
1838 
1839                 onObjectLoaded(object, index);
1840               });
1841             }
1842           }
1843         }, this);
1844       } 
1845       catch(e) {
1846         fabric.log(e.message);
1847       }
1848     },
1849     
1850     /**
1851      * Loads an image from URL, creates an instance of fabric.Image and passes it to a callback
1852      * @function
1853      * @method loadImageFromURL
1854      * @param url {String} url of image to load
1855      * @param callback {Function} calback, invoked when image is loaded
1856      */
1857     loadImageFromURL: (function () {
1858       var imgCache = { };
1859 
1860       return function (url, callback) {
1861         // check cache first
1862         
1863         var _this = this;
1864         
1865         function checkIfLoaded() {
1866           var imgEl = document.getElementById(imgCache[url]);
1867           if (imgEl.width && imgEl.height) {
1868             callback(new fabric.Image(imgEl));
1869           }
1870           else {
1871             setTimeout(checkIfLoaded, 50);
1872           }
1873         }
1874 
1875         // get by id from cache
1876         if (imgCache[url]) {
1877           // id can be cached but image might still not be loaded, so we poll here
1878           checkIfLoaded();
1879         }
1880         // else append a new image element
1881         else {
1882           var imgEl = new Image();
1883           
1884           /** @ignore */
1885           imgEl.onload = function () {
1886             imgEl.onload = null;
1887             
1888             if (imgEl.width && imgEl.height) {
1889               callback(new fabric.Image(imgEl));
1890             }
1891           };
1892           
1893           imgEl.className = 'canvas-img-clone';
1894           imgEl.style.cssText = 'position:absolute;left:-9999px;top:-9999px;';
1895           imgEl.src = url;
1896           
1897           if (this.shouldCacheImages) {
1898             // TODO (kangax): replace Element.identify w. fabric -based alternative
1899             imgCache[url] = Element.identify(imgEl);
1900           }
1901           document.body.appendChild(imgEl);
1902         }
1903       }
1904     })(),
1905     
1906     /**
1907      * Takes url corresponding to an SVG document, and parses it to a set of objects
1908      * @method loadSVGFromURL
1909      * @param {String} url
1910      * @param {Function} callback
1911      */
1912     loadSVGFromURL: function (url, callback) {
1913       
1914       var _this = this;
1915       
1916       url = url.replace(/^\n\s*/, '').replace(/\?.*$/, '').trim();
1917       
1918       this.cache.has(url, function (hasUrl) {
1919         if (hasUrl) {
1920           _this.cache.get(url, function (value) {
1921             var enlivedRecord = _this._enlivenCachedObject(value);
1922             callback(enlivedRecord.objects, enlivedRecord.options);
1923           });
1924         }
1925         else {
1926           new fabric.util.request(url, {
1927             method: 'get',
1928             onComplete: onComplete
1929           });
1930         }
1931       });
1932       
1933       function onComplete(r) {
1934         
1935         var xml = r.responseXML;
1936         if (!xml) return;
1937         
1938         var doc = xml.documentElement;
1939         if (!doc) return;
1940         
1941         fabric.parseSVGDocument(doc, function (results, options) {
1942           _this.cache.set(url, {
1943             objects: fabric.util.array.invoke(results, 'toObject'),
1944             options: options
1945           });
1946           callback(results, options);
1947         });
1948       }
1949     },
1950     
1951     /**
1952      * @method _enlivenCachedObject
1953      */
1954     _enlivenCachedObject: function (cachedObject) {
1955       
1956       var objects = cachedObject.objects,
1957           options = cachedObject.options;
1958       
1959       objects = objects.map(function (o) {
1960         return fabric[capitalize(o.type)].fromObject(o);
1961       });
1962       
1963       return ({ objects: objects, options: options });
1964     },
1965     
1966     /**
1967      * Removes an object from canvas and returns it
1968      * @method remove
1969      * @param object {Object} Object to remove
1970      * @return {Object} removed object
1971      */
1972     remove: function (object) {
1973       removeFromArray(this._objects, object);
1974       if (this.getActiveObject() === object) {
1975         this.removeActiveObject();
1976       }
1977       this.renderAll();
1978       return object;
1979     },
1980     
1981     /**
1982      * Same as `fabric.Canvas#remove` but animated
1983      * @method fxRemove
1984      * @param {fabric.Object} object Object to remove
1985      * @param {Function} callback Callback, invoked on effect completion
1986      * @return {fabric.Canvas} thisArg
1987      * @chainable
1988      */
1989     fxRemove: function (object, callback) {
1990       var _this = this;
1991       object.fxRemove({
1992         onChange: this.renderAll.bind(this),
1993         onComplete: function () {
1994           _this.remove(object);
1995           if (typeof callback === 'function') {
1996             callback();
1997           }
1998         }
1999       });
2000       return this;
2001     },
2002     
2003     /**
2004      * Moves an object to the bottom of the stack of drawn objects
2005      * @method sendToBack
2006      * @param object {fabric.Object} Object to send to back
2007      * @return {fabric.Canvas} thisArg
2008      * @chainable
2009      */
2010     sendToBack: function (object) {
2011       removeFromArray(this._objects, object);
2012       this._objects.unshift(object);
2013       return this.renderAll();
2014     },
2015     
2016     /**
2017      * Moves an object to the top of the stack of drawn objects
2018      * @method bringToFront
2019      * @param object {fabric.Object} Object to send
2020      * @return {fabric.Canvas} thisArg
2021      * @chainable
2022      */
2023     bringToFront: function (object) {
2024       removeFromArray(this._objects, object);
2025       this._objects.push(object);
2026       return this.renderAll();
2027     },
2028     
2029     /**
2030      * Moves an object one level down in stack of drawn objects
2031      * @method sendBackwards
2032      * @param object {fabric.Object} Object to send
2033      * @return {fabric.Canvas} thisArg
2034      * @chainable
2035      */
2036     sendBackwards: function (object) {
2037       var idx = this._objects.indexOf(object),
2038           nextIntersectingIdx = idx;
2039       
2040       // if object is not on the bottom of stack
2041       if (idx !== 0) {
2042         
2043         // traverse down the stack looking for the nearest intersecting object
2044         for (var i=idx-1; i>=0; --i) {
2045           if (object.intersectsWithObject(this._objects[i])) {
2046             nextIntersectingIdx = i;
2047             break;
2048           }
2049         }
2050         removeFromArray(this._objects, object);
2051         this._objects.splice(nextIntersectingIdx, 0, object);
2052       }
2053       return this.renderAll();
2054     },
2055     
2056     /**
2057      * Moves an object one level up in stack of drawn objects
2058      * @method sendForward
2059      * @param object {fabric.Object} Object to send
2060      * @return {fabric.Canvas} thisArg
2061      * @chainable
2062      */
2063     bringForward: function (object) {
2064       var objects = this.getObjects(),
2065           idx = objects.indexOf(object),
2066           nextIntersectingIdx = idx;
2067 
2068       
2069       // if object is not on top of stack (last item in an array)
2070       if (idx !== objects.length-1) {
2071         
2072         // traverse up the stack looking for the nearest intersecting object
2073         for (var i = idx + 1, l = this._objects.length; i < l; ++i) {
2074           if (object.intersectsWithObject(objects[i])) {
2075             nextIntersectingIdx = i;
2076             break;
2077           }
2078         }
2079         removeFromArray(objects, object);
2080         objects.splice(nextIntersectingIdx, 0, object);
2081       }
2082       this.renderAll();
2083     },
2084     
2085     /**
2086      * Sets given object as active
2087      * @method setActiveObject
2088      * @param object {fabric.Object} Object to set as an active one
2089      * @return {fabric.Canvas} thisArg
2090      * @chainable
2091      */
2092     setActiveObject: function (object) {
2093       if (this._activeObject) {
2094         this._activeObject.setActive(false);
2095       }
2096       this._activeObject = object;
2097       object.setActive(true);
2098       
2099       this.renderAll();
2100       
2101       this.fire('object:selected', { target: object });
2102       return this;
2103     },
2104     
2105     /**
2106      * Returns currently active object
2107      * @method getActiveObject
2108      * @return {fabric.Object} active object
2109      */
2110     getActiveObject: function () {
2111       return this._activeObject;
2112     },
2113     
2114     /**
2115      * Removes currently active object
2116      * @method removeActiveObject
2117      * @return {fabric.Canvas} thisArg
2118      * @chainable
2119      */
2120     removeActiveObject: function () {
2121       if (this._activeObject) {
2122         this._activeObject.setActive(false);
2123       }
2124       this._activeObject = null;
2125       return this;
2126     },
2127     
2128     /**
2129      * Sets active group to a speicified one
2130      * @method setActiveGroup
2131      * @param {fabric.Group} group Group to set as a current one 
2132      * @return {fabric.Canvas} thisArg
2133      * @chainable
2134      */
2135     setActiveGroup: function (group) {
2136       this._activeGroup = group;
2137       return this;
2138     },
2139     
2140     /**
2141      * Returns currently active group
2142      * @method getActiveGroup
2143      * @return {fabric.Group} Current group
2144      */
2145     getActiveGroup: function () {
2146       return this._activeGroup;
2147     },
2148     
2149     /**
2150      * Removes currently active group
2151      * @method removeActiveGroup
2152      * @return {fabric.Canvas} thisArg
2153      */
2154     removeActiveGroup: function () {
2155       var g = this.getActiveGroup();
2156       if (g) {
2157         g.destroy();
2158       }
2159       return this.setActiveGroup(null);
2160     },
2161     
2162     /**
2163      * Returns object at specified index
2164      * @method item
2165      * @param {Number} index
2166      * @return {fabric.Object}
2167      */
2168     item: function (index) {
2169       return this.getObjects()[index];
2170     },
2171     
2172     /**
2173      * Deactivates all objects by calling their setActive(false)
2174      * @method deactivateAll
2175      * @return {fabric.Canvas} thisArg
2176      */
2177     deactivateAll: function () {
2178       var allObjects = this.getObjects(),
2179           i = 0,
2180           len = allObjects.length;
2181       for ( ; i < len; i++) {
2182         allObjects[i].setActive(false);
2183       }
2184       this.removeActiveGroup();
2185       this.removeActiveObject();
2186       return this;
2187     },
2188     
2189     /**
2190      * Returns number representation of an instance complexity
2191      * @method complexity
2192      * @return {Number} complexity
2193      */
2194     complexity: function () {
2195       return this.getObjects().reduce(function (memo, current) {
2196         memo += current.complexity ? current.complexity() : 0;
2197         return memo;
2198       }, 0);
2199     },
2200     
2201     /**
2202      * Iterates over all objects, invoking callback for each one of them
2203      * @method forEachObject
2204      * @return {fabric.Canvas} thisArg
2205      */
2206     forEachObject: function(callback, context) {
2207       var objects = this.getObjects(),
2208           i = objects.length;
2209       while (i--) {
2210         callback.call(context, objects[i], i, objects);
2211       }
2212       return this;
2213     },
2214     
2215     /**
2216      * Clears a canvas element and removes all event handlers.
2217      * @method dispose
2218      * @return {fabric.Canvas} thisArg
2219      * @chainable
2220      */
2221     dispose: function () {
2222       this.clear();
2223       removeListener(this.upperCanvasEl, 'mousedown', this._onMouseDown);
2224       removeListener(document, 'mousemove', this._onMouseMove);
2225       removeListener(window, 'resize', this._onResize);
2226       return this;
2227     },
2228     
2229     /**
2230      * Clones canvas instance
2231      * @method clone
2232      * @param {Object} [callback] Expects `onBeforeClone` and `onAfterClone` functions
2233      * @return {fabric.Canvas} Clone of this instance
2234      */
2235     clone: function (callback) {
2236       var el = document.createElement('canvas');
2237       
2238       el.width = this.getWidth();
2239       el.height = this.getHeight();
2240           
2241       // cache
2242       var clone = this.__clone || (this.__clone = new fabric.Canvas(el));
2243       clone.clipTo = this.clipTo;
2244       
2245       return clone.loadFromJSON(JSON.stringify(this.toJSON()), function () {
2246         if (callback) {
2247           callback(clone);
2248         }
2249       });
2250     },
2251     
2252     /**
2253      * @private
2254      * @method _toDataURL
2255      * @param {String} format
2256      * @param {Function} callback
2257      */
2258     _toDataURL: function (format, callback) {
2259       this.clone(function (clone) {
2260         callback(clone.toDataURL(format));
2261       });
2262     },
2263     
2264     /**
2265      * @private
2266      * @method _toDataURLWithMultiplier
2267      * @param {String} format
2268      * @param {Number} multiplier
2269      * @param {Function} callback
2270      */
2271     _toDataURLWithMultiplier: function (format, multiplier, callback) {
2272       this.clone(function (clone) {
2273         callback(clone.toDataURLWithMultiplier(format, multiplier));
2274       });
2275     },
2276     
2277     /**
2278      * @private
2279      * @method _resizeImageToFit
2280      * @param {HTMLImageElement} imgEl
2281      */
2282     _resizeImageToFit: function (imgEl) {
2283       
2284       var imageWidth = imgEl.width || imgEl.offsetWidth,
2285           widthScaleFactor = this.getWidth() / imageWidth;
2286       
2287       // scale image down so that it has original dimensions when printed in large resolution
2288       if (imageWidth) {
2289         imgEl.width = imageWidth * widthScaleFactor;
2290       }
2291     },
2292     
2293     /**
2294      * Used for caching SVG documents (loaded via `fabric.Canvas#loadSVGFromURL`)
2295      * @property
2296      * @namespace
2297      */
2298     cache: {
2299       
2300       /**
2301        * @method has
2302        * @param {String} name
2303        * @param {Function} callback
2304        */
2305       has: function (name, callback) { 
2306         callback(false);
2307       },
2308       
2309       /**
2310        * @method get
2311        * @param {String} url
2312        * @param {Function} callback
2313        */
2314       get: function (url, callback) {
2315         /* NOOP */
2316       },
2317       
2318       /**
2319        * @method set
2320        * @param {String} url
2321        * @param {Object} object
2322        */
2323       set: function (url, object) {
2324         /* NOOP */
2325       }
2326     }
2327   });
2328   
2329   /**
2330    * Returns a string representation of an instance
2331    * @method toString
2332    * @return {String} string representation of an instance
2333    */
2334   fabric.Canvas.prototype.toString = function () { // Assign explicitly since `extend` doesn't take care of DontEnum bug yet
2335     return '#<fabric.Canvas (' + this.complexity() + '): '+
2336            '{ objects: ' + this.getObjects().length + ' }>';
2337   };
2338   
2339   extend(fabric.Canvas, /** @scope fabric.Canvas */ {
2340     
2341     /**
2342      * @static
2343      * @property EMPTY_JSON
2344      * @type String
2345      */
2346     EMPTY_JSON: '{"objects": [], "background": "white"}',
2347     
2348     /**
2349      * Takes <canvas> element and transforms its data in such way that it becomes grayscale
2350      * @static
2351      * @method toGrayscale
2352      * @param {HTMLCanvasElement} canvasEl
2353      */
2354     toGrayscale: function (canvasEl) {
2355        var context = canvasEl.getContext('2d'),
2356            imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
2357            data = imageData.data, 
2358            iLen = imageData.width,
2359            jLen = imageData.height,
2360            index, average, i, j;
2361 
2362        for (i = 0; i < iLen; i++) {
2363          for (j = 0; j < jLen; j++) {
2364 
2365            index = (i * 4) * jLen + (j * 4);
2366            average = (data[index] + data[index + 1] + data[index + 2]) / 3;
2367 
2368            data[index]     = average;
2369            data[index + 1] = average;
2370            data[index + 2] = average;
2371          }
2372        }
2373 
2374        context.putImageData(imageData, 0, 0);
2375      },
2376     
2377     /**
2378      * Provides a way to check support of some of the canvas methods 
2379      * (either those of HTMLCanvasElement itself, or rendering context)
2380      *
2381      * @method supports
2382      * @param methodName {String} Method to check support for; 
2383      *                            Could be one of "getImageData" or "toDataURL"
2384      * @return {Boolean | null} `true` if method is supported (or at least exists), 
2385      *                          `null` if canvas element or context can not be initialized
2386      */
2387     supports: function (methodName) {
2388       var el = document.createElement('canvas');
2389       
2390       if (typeof G_vmlCanvasManager !== 'undefined') {
2391         G_vmlCanvasManager.initElement(el);
2392       }
2393       if (!el || !el.getContext) {
2394         return null;
2395       }
2396       
2397       var ctx = el.getContext('2d');
2398       if (!ctx) {
2399         return null;
2400       }
2401       
2402       switch (methodName) {
2403         
2404         case 'getImageData':
2405           return typeof ctx.getImageData !== 'undefined';
2406           
2407         case 'toDataURL':
2408           return typeof el.toDataURL !== 'undefined';
2409           
2410         default:
2411           return null;
2412       }
2413     }
2414   });
2415   
2416   /**
2417    * Returs JSON representation of canvas
2418    * @function
2419    * @method toJSON
2420    * @return {String} json string
2421    */
2422   fabric.Canvas.prototype.toJSON = fabric.Canvas.prototype.toObject;
2423   
2424   /**
2425    * @class fabric.Element
2426    * @alias fabric.Canvas
2427    * @deprecated
2428    * @constructor
2429    */
2430   fabric.Element = fabric.Canvas;
2431   
2432 })(this);