1 (function(){
  2   
  3   var global = this,
  4       fabric = global.fabric || (global.fabric = { }),
  5       extend = fabric.util.object.extend,
  6       clone = fabric.util.object.clone,
  7       toFixed = fabric.util.toFixed,
  8       capitalize = fabric.util.string.capitalize,
  9       getPointer = fabric.util.getPointer,
 10       slice = Array.prototype.slice
 11       
 12   if (fabric.Object) {
 13     return;
 14   }
 15   
 16   /** 
 17    * @class Object
 18    * @memberOf fabric
 19    */
 20   fabric.Object = fabric.util.createClass(/** @scope fabric.Object.prototype */ {
 21     
 22     /**
 23      * @property
 24      * @type String
 25      */
 26     type: 'object',
 27     
 28     /**
 29      * @property
 30      * @type Boolean
 31      */
 32     includeDefaultValues: true,
 33     
 34     /**
 35      * @constant
 36      * @type Number
 37      */
 38     NUM_FRACTION_DIGITS:        2,
 39     
 40     /**
 41      * @constant
 42      * @type Number
 43      */
 44     FX_DURATION:                500,
 45     
 46     /**
 47      * @constant
 48      * @type String
 49      */
 50     FX_TRANSITION:              'decel',
 51     
 52     /**
 53      * @constant
 54      * @type Number
 55      */
 56     MIN_SCALE_LIMIT:            0.1,
 57     
 58     /**
 59      * @property
 60      * @type Array
 61      */
 62     stateProperties:  ('top left width height scaleX scaleY flipX flipY ' +
 63                       'theta angle opacity cornersize fill overlayFill stroke ' +
 64                       'strokeWidth fillRule borderScaleFactor transformMatrix').split(' '),
 65     
 66     // TODO (kangax): rename to `defaultOptions`
 67     
 68     /**
 69      * @property
 70      * @type Object
 71      */
 72     options: {
 73       top:                      0,
 74       left:                     0,
 75       width:                    100,
 76       height:                   100,
 77       scaleX:                   1,
 78       scaleY:                   1,
 79       flipX:                    false,
 80       flipY:                    false,
 81       theta:                    0,
 82       opacity:                  1,
 83       angle:                    0,
 84       cornersize:               10,
 85       padding:                  0,
 86       borderColor:              'rgba(102,153,255,0.75)',
 87       cornerColor:              'rgba(102,153,255,0.5)',
 88       fill:                     'rgb(0,0,0)',
 89       overlayFill:              null,
 90       stroke:                   null,
 91       strokeWidth:              1,
 92       fillRule:                 'source-over',
 93       borderOpacityWhenMoving:  0.4,
 94       borderScaleFactor:        1,
 95       transformMatrix:          null
 96     },
 97     
 98     /**
 99      * @method callSuper
100      * @param {String} methodName
101      */
102     callSuper: function(methodName) {
103       var fn = this.constructor.superclass.prototype[methodName];
104       return (arguments.length > 1) 
105         ? fn.apply(this, slice.call(arguments, 1))
106         : fn.call(this);
107     },
108     
109     /**
110      * Constructor
111      * @method initialize
112      * @param {Object} [options] Options object
113      */
114     initialize: function(options) {
115       // overwrite default options with specified ones
116       this.setOptions(options);
117       // "import" state properties into an instance
118       this._importProperties();
119       // create "local" members
120       this.originalState = { };
121       // set initial coords
122       this.setCoords();
123       // setup state properties
124       this.saveState();
125     },
126     
127     /**
128      * @method setOptions
129      * @param {Object} [options]
130      */
131     setOptions: function(options) {
132       // this.constructor.superclass.prototype.options -> this.options -> options
133       this.options = extend(this._getOptions(), options);
134     },
135     
136     /**
137      * @private
138      * @method _getOptions
139      */
140     _getOptions: function() {
141       return extend(clone(this._getSuperOptions()), this.options);
142     },
143     
144     /**
145      * @private
146      * @method _getSuperOptions
147      */
148     _getSuperOptions: function() {
149       var c = this.constructor;
150       if (c) {
151         var s = c.superclass;
152         if (s) {
153           var p = s.prototype;
154           if (p && typeof p._getOptions == 'function') {
155             return p._getOptions();
156           }
157         }
158       }
159       return { };
160     },
161     
162     /**
163      * @private
164      * @method _importProperties
165      */
166     _importProperties: function() {
167       this.stateProperties.forEach(function(prop) {
168         (prop === 'angle') 
169           ? this.setAngle(this.options[prop])
170           : (this[prop] = this.options[prop]);
171       }, this);
172     },
173     
174     /**
175      * @method transform
176      * @param {CanvasRenderingContext2D} ctx Context
177      */
178     transform: function(ctx) {
179       ctx.globalAlpha = this.opacity;
180       ctx.translate(this.left, this.top);
181       ctx.rotate(this.theta);
182       ctx.scale(
183         this.scaleX * (this.flipX ? -1 : 1), 
184         this.scaleY * (this.flipY ? -1 : 1)
185       );
186     },
187     
188     /**
189      * Returns an object representation of an instance
190      * @method toObject
191      * @return {Object}
192      */
193     toObject: function() {
194       var object = {
195         type: this.type,
196         left: toFixed(this.left, this.NUM_FRACTION_DIGITS),
197         top: toFixed(this.top, this.NUM_FRACTION_DIGITS),
198         width: toFixed(this.width, this.NUM_FRACTION_DIGITS),
199         height: toFixed(this.height, this.NUM_FRACTION_DIGITS),
200         fill: this.fill,
201         overlayFill: this.overlayFill,
202         stroke: this.stroke,
203         strokeWidth: this.strokeWidth,
204         scaleX: toFixed(this.scaleX, this.NUM_FRACTION_DIGITS),
205         scaleY: toFixed(this.scaleY, this.NUM_FRACTION_DIGITS),
206         angle: toFixed(this.getAngle(), this.NUM_FRACTION_DIGITS),
207         flipX: this.flipX,
208         flipY: this.flipY,
209         opacity: toFixed(this.opacity, this.NUM_FRACTION_DIGITS)
210       };
211       
212       if (!this.includeDefaultValues) {
213         object = this._removeDefaultValues(object);
214       }
215       return object;
216     },
217     
218     /**
219      * @method toDatalessObject
220      */
221     toDatalessObject: function() {
222       // will be overwritten by subclasses
223       return this.toObject();
224     },
225     
226     /**
227      * @private
228      * @method _removeDefaultValues
229      */
230     _removeDefaultValues: function(object) {
231       var defaultOptions = fabric.Object.prototype.options;
232       this.stateProperties.forEach(function(prop) {
233         if (object[prop] === defaultOptions[prop]) {
234           delete object[prop];
235         }
236       });
237       return object;
238     },
239     
240     /**
241      * Returns true if an object is in its active state
242      * @return {Boolean} true if an object is in its active state
243      */
244     isActive: function() {
245       return !!this.active;
246     },
247     
248     /**
249      * Sets state of an object - `true` makes it active, `false` - inactive
250      * @param {Boolean} active
251      * @return {fabric.Object} thisArg
252      * @chainable
253      */
254     setActive: function(active) {
255       this.active = !!active;
256       return this;
257     },
258     
259     /**
260      * Returns a string representation of an instance
261      * @return {String}
262      */
263     toString: function() {
264       return "#<fabric." + capitalize(this.type) + ">";
265     },
266     
267     /**
268      * Basic setter
269      * @param {Any} property
270      * @param {Any} value
271      * @return {fabric.Object} thisArg
272      * @chainable
273      */
274     set: function(property, value) {
275       var shouldConstrainValue = (property === 'scaleX' || property === 'scaleY') && value < this.MIN_SCALE_LIMIT;
276       if (shouldConstrainValue) {
277         value = this.MIN_SCALE_LIMIT;
278       }
279       if (property === 'angle') {
280         this.setAngle(value);
281       }
282       else {
283         this[property] = value;
284       }
285       return this;
286     },
287     
288     /**
289      * Toggles specified property from `true` to `false` or from `false` to `true`
290      * @method toggle
291      * @param {String} property property to toggle
292      * @return {fabric.Object} thisArg
293      * @chainable
294      */
295     toggle: function(property) {
296       var value = this.get(property);
297       if (typeof value === 'boolean') {
298         this.set(property, !value);
299       }
300       return this;
301     },
302     
303     /**
304      * @method setSourcePath
305      * @param {String} value
306      * @return {fabric.Object} thisArg
307      * @chainable
308      */
309     setSourcePath: function(value) {
310       this.sourcePath = value;
311       return this;
312     },
313     
314     /**
315      * Basic getter
316      * @method get
317      * @param {Any} property
318      * @return {Any} value of a property
319      */
320     get: function(property) {
321       return (property === 'angle') 
322         ? this.getAngle() 
323         : this[property];
324     },
325     
326     /**
327      * @method render
328      * @param {CanvasRenderingContext2D} ctx context to render on
329      * @param {Boolean} noTransform
330      */
331     render: function(ctx, noTransform) {
332       
333       // do not render if width or height are zeros
334       if (this.width === 0 || this.height === 0) return;
335       
336       ctx.save();
337       
338       var m = this.transformMatrix;
339       if (m) {
340         ctx.setTransform(m[0], m[1], m[2], m[3], m[4], m[5]);
341       }
342       
343       if (!noTransform) {
344         this.transform(ctx);
345       }
346       
347       if (this.stroke) {
348         ctx.lineWidth = this.strokeWidth;
349         ctx.strokeStyle = this.stroke;
350       }
351       
352       if (this.overlayFill) {
353         ctx.fillStyle = this.overlayFill;
354       }
355       else if (this.fill) {
356         ctx.fillStyle = this.fill;
357       }
358       
359       this._render(ctx, noTransform);
360       
361       if (this.active && !noTransform) {
362         this.drawBorders(ctx);
363         this.hideCorners || this.drawCorners(ctx);
364       }
365       ctx.restore();
366     },
367     
368     /**
369      * Returns width of an object
370      * @method getWidth
371      * @return {Number} width value
372      */
373     getWidth: function() {
374       return this.width * this.scaleX;
375     },
376     
377     /**
378      * Returns height of an object
379      * @method getHeight
380      * @return {Number} height value
381      */
382     getHeight: function() {
383       return this.height * this.scaleY;
384     },
385     
386     /**
387      * Scales an object (equally by x and y)
388      * @method scale
389      * @param value {Number} scale factor
390      * @return {fabric.Object} thisArg
391      * @chainable
392      */
393     scale: function(value) {
394       this.scaleX = value;
395       this.scaleY = value;
396       return this;
397     },
398     
399     /**
400      * Scales an object to a given width (scaling by x/y equally)
401      * @method scaleToWidth
402      * @param value {Number} new width value
403      * @return {fabric.Object} thisArg
404      * @chainable
405      */
406     scaleToWidth: function(value) {
407       return this.scale(value / this.width);
408     },
409     
410     /**
411      * Scales an object to a given height (scaling by x/y equally)
412      * @method scaleToHeight
413      * @param value {Number} new height value
414      * @return {fabric.Object} thisArg
415      * @chainable
416      */
417     scaleToHeight: function(value) {
418       return this.scale(value / this.height);
419     },
420     
421     /**
422      * Sets object opacity 
423      * @method setOpacity
424      * @param value {Number} value 0-1
425      * @return {fabric.Object} thisArg
426      * @chainable
427      */
428     setOpacity: function(value) {
429       this.set('opacity', value);
430       return this;
431     },
432     
433     /**
434      * Returns object's angle value
435      * @method getAngle
436      * @return {Number} angle value
437      */
438     getAngle: function() {
439       return this.theta * 180 / Math.PI;
440     },
441     
442     /**
443      * Sets object's angle
444      * @method setAngle
445      * @param value {Number} angle value
446      * @return {Object} thisArg
447      */
448     setAngle: function(value) {
449       this.theta = value / 180 * Math.PI;
450       this.angle = value;
451       return this;
452     },
453     
454     /**
455      * Sets corner position coordinates based on current angle, width and height.
456      * @method setCoords
457      * return {fabric.Object} thisArg
458      * @chainable
459      */
460     setCoords: function() {
461       
462       this.currentWidth = this.width * this.scaleX;
463       this.currentHeight = this.height * this.scaleY;
464       
465       this._hypotenuse = Math.sqrt(
466         Math.pow(this.currentWidth / 2, 2) + 
467         Math.pow(this.currentHeight / 2, 2));
468         
469       this._angle = Math.atan(this.currentHeight / this.currentWidth);
470 
471       // offset added for rotate and scale actions
472       var offsetX = Math.cos(this._angle + this.theta) * this._hypotenuse,
473           offsetY = Math.sin(this._angle + this.theta) * this._hypotenuse,
474           theta = this.theta,
475           sinTh = Math.sin(theta),
476           cosTh = Math.cos(theta);
477 
478       var tl = {
479         x: this.left - offsetX,
480         y: this.top - offsetY
481       };
482       var tr = {
483         x: tl.x + (this.currentWidth * cosTh),
484         y: tl.y + (this.currentWidth * sinTh)
485       };
486       var br = {
487         x: tr.x - (this.currentHeight * sinTh),
488         y: tr.y + (this.currentHeight * cosTh)
489       };
490       var bl = {
491         x: tl.x - (this.currentHeight * sinTh),
492         y: tl.y + (this.currentHeight * cosTh)
493       };
494       var ml = {
495         x: tl.x - (this.currentHeight/2 * sinTh),
496         y: tl.y + (this.currentHeight/2 * cosTh)
497       };
498       var mt = {
499         x: tl.x + (this.currentWidth/2 * cosTh),
500         y: tl.y + (this.currentWidth/2 * sinTh)
501       };
502       var mr = {
503         x: tr.x - (this.currentHeight/2 * sinTh),
504         y: tr.y + (this.currentHeight/2 * cosTh)
505       }
506       var mb = {
507         x: bl.x + (this.currentWidth/2 * cosTh),
508         y: bl.y + (this.currentWidth/2 * sinTh)
509       }
510       
511       // clockwise
512       this.oCoords = { tl: tl, tr: tr, br: br, bl: bl, ml: ml, mt: mt, mr: mr, mb: mb };
513       
514       // set coordinates of the draggable boxes in the corners used to scale/rotate the image
515       this._setCornerCoords();
516       
517       return this;
518     },
519     
520     /**
521      * Draws borders of an object's bounding box. 
522      * Requires public properties: width, height
523      * Requires public options: padding, borderColor
524      * @method drawBorders
525      * @param {CanvasRenderingContext2D} ctx Context to draw on
526      * @return {fabric.Object} thisArg
527      * @chainable
528      */
529     drawBorders: function(ctx) {
530       var o = this.options,
531           padding = o.padding,
532           padding2 = padding * 2;
533       
534       ctx.save();
535       
536       ctx.globalAlpha = this.isMoving ? o.borderOpacityWhenMoving : 1;
537       ctx.strokeStyle = o.borderColor;
538       
539       var scaleX = 1 / (this.scaleX < this.MIN_SCALE_LIMIT ? this.MIN_SCALE_LIMIT : this.scaleX),
540           scaleY = 1 / (this.scaleY < this.MIN_SCALE_LIMIT ? this.MIN_SCALE_LIMIT : this.scaleY);
541       
542       // could be set by a group, that this object is contained within
543       ctx.lineWidth = 1 / this.borderScaleFactor;
544       
545       ctx.scale(scaleX, scaleY);
546       
547       var w = this.getWidth(),
548           h = this.getHeight();
549       
550       ctx.strokeRect(
551         ~~(-(w / 2) - padding) + 0.5, // offset needed to make lines look sharper
552         ~~(-(h / 2) - padding) + 0.5,
553         ~~(w + padding2),
554         ~~(h + padding2)
555       );
556       
557       ctx.restore();
558       return this;
559     },
560     
561     /**
562      * Draws corners of an object's bounding box.
563      * Requires public properties: width, height, scaleX, scaleY 
564      * Requires public options: cornersize, padding
565      * @method drawCorners
566      * @param {CanvasRenderingContext2D} ctx Context to draw on
567      * @return {fabric.Object} thisArg
568      * @chainable
569      */
570     drawCorners: function(ctx) {
571       var size = this.options.cornersize,
572           size2 = size / 2,
573           padding = this.options.padding,
574           left = -(this.width / 2),
575           top = -(this.height / 2),
576           _left, 
577           _top,
578           sizeX = size / this.scaleX,
579           sizeY = size / this.scaleY,
580           scaleOffsetY = (padding + size2) / this.scaleY,
581           scaleOffsetX = (padding + size2) / this.scaleX,
582           scaleOffsetSizeX = (padding + size2 - size) / this.scaleX,
583           scaleOffsetSizeY = (padding + size2 - size) / this.scaleY;
584           
585       ctx.save();
586       
587       ctx.globalAlpha = this.isMoving ? this.options.borderOpacityWhenMoving : 1;
588       ctx.fillStyle = this.options.cornerColor;
589       
590       // top-left
591       _left = left - scaleOffsetX;
592       _top = top - scaleOffsetY;
593       ctx.fillRect(_left, _top, sizeX, sizeY);
594       
595       // top-right
596       _left = left + this.width - scaleOffsetX;
597       _top = top - scaleOffsetY;
598       ctx.fillRect(_left, _top, sizeX, sizeY);
599       
600       // bottom-left
601       _left = left - scaleOffsetX;
602       _top = top + this.height + scaleOffsetSizeY;
603       ctx.fillRect(_left, _top, sizeX, sizeY);
604       
605       // bottom-right
606       _left = left + this.width + scaleOffsetSizeX;
607       _top = top + this.height + scaleOffsetSizeY;
608       ctx.fillRect(_left, _top, sizeX, sizeY);
609       
610       // middle-top
611       _left = left + this.width/2 - scaleOffsetX;
612       _top = top - scaleOffsetY;
613       ctx.fillRect(_left, _top, sizeX, sizeY);
614       
615       // middle-bottom
616       _left = left + this.width/2 - scaleOffsetX;
617       _top = top + this.height + scaleOffsetSizeY;
618       ctx.fillRect(_left, _top, sizeX, sizeY);
619       
620       // middle-right
621       _left = left + this.width + scaleOffsetSizeX;
622       _top = top + this.height/2 - scaleOffsetY;
623       ctx.fillRect(_left, _top, sizeX, sizeY);
624       
625       // middle-left
626       _left = left - scaleOffsetX;
627       _top = top + this.height/2 - scaleOffsetY;
628       ctx.fillRect(_left, _top, sizeX, sizeY);
629       
630       ctx.restore();
631       
632       return this;
633     },
634     
635     /**
636      * Clones an instance
637      * @method clone
638      * @param {Object} options object
639      * @return {fabric.Object} clone of an instance
640      */
641     clone: function(options) {
642       if (this.constructor.fromObject) {
643         return this.constructor.fromObject(this.toObject(), options);
644       }
645       return new fabric.Object(this.toObject());
646     },
647     
648     /**
649      * Creates an instance of fabric.Image out of an object
650      * @method cloneAsImage
651      * @param callback {Function} callback, invoked with an instance as a first argument
652      * @return {fabric.Object} thisArg
653      * @chainable
654      */
655     cloneAsImage: function(callback) {
656       if (fabric.Image) {
657         var i = new Image();
658         
659         /** @ignore */
660         i.onload = function() {
661           if (callback) {
662             callback(new fabric.Image(i), orig);
663           }
664           i = i.onload = null;
665         };
666         
667         var orig = {
668           angle: this.get('angle'),
669           flipX: this.get('flipX'),
670           flipY: this.get('flipY')
671         };
672 
673         // normalize angle
674         this.set('angle', 0).set('flipX', false).set('flipY', false);
675         i.src = this.toDataURL();
676       }
677       return this;
678     },
679     
680     /**
681      * Converts an object into a data-url-like string
682      * @method toDataURL
683      * @return {String} string of data
684      */
685     toDataURL: function() {
686       var el = document.createElement('canvas');
687       
688       el.width = this.getWidth();
689       el.height = this.getHeight();
690       
691       fabric.util.wrapElement(el, 'div');
692 
693       var canvas = new fabric.Element(el);
694       canvas.backgroundColor = 'transparent';
695       canvas.renderAll();
696       
697       var clone = this.clone();
698       clone.left = el.width / 2;
699       clone.top = el.height / 2;
700       
701       clone.setActive(false);
702       
703       canvas.add(clone);
704       var data = canvas.toDataURL('png');
705       
706       canvas.dispose();
707       canvas = clone = null;
708       return data;
709     },
710     
711     /**
712      * @method hasStateChanged
713      * @return {Boolean} true if instance' state has changed
714      */
715     hasStateChanged: function() {
716       return this.stateProperties.some(function(prop) {
717         return this[prop] !== this.originalState[prop];
718       }, this);
719     },
720     
721     /**
722      * @method saveState
723      * @return {fabric.Object} thisArg
724      * @chainable
725      */
726     saveState: function() {
727       this.stateProperties.forEach(function(prop) {
728         this.originalState[prop] = this.get(prop);
729       }, this);
730       return this;
731     },
732     
733     /**
734      * Returns true if object intersects with an area formed by 2 points
735      * @method intersectsWithRect
736      * @param {Object} selectionTL
737      * @param {Object} selectionBR
738      * @return {Boolean}
739      */
740     intersectsWithRect: function(selectionTL, selectionBR) {
741       var oCoords = this.oCoords,
742           tl = new fabric.Point(oCoords.tl.x, oCoords.tl.y),
743           tr = new fabric.Point(oCoords.tr.x, oCoords.tr.y),
744           bl = new fabric.Point(oCoords.bl.x, oCoords.bl.y),
745           br = new fabric.Point(oCoords.br.x, oCoords.br.y);
746       
747       var intersection = fabric.Intersection.intersectPolygonRectangle(
748         [tl, tr, br, bl],
749         selectionTL,
750         selectionBR
751       );
752       return (intersection.status === 'Intersection');
753     },
754     
755     /**
756      * Returns true if object intersects with another object
757      * @method intersectsWithObject
758      * @param {Object} other Object to test
759      * @return {Boolean}
760      */
761     intersectsWithObject: function(other) {
762       // extracts coords
763       function getCoords(oCoords) {
764         return {
765           tl: new fabric.Point(oCoords.tl.x, oCoords.tl.y),
766           tr: new fabric.Point(oCoords.tr.x, oCoords.tr.y),
767           bl: new fabric.Point(oCoords.bl.x, oCoords.bl.y),
768           br: new fabric.Point(oCoords.br.x, oCoords.br.y)
769         }
770       }
771       var thisCoords = getCoords(this.oCoords),
772           otherCoords = getCoords(other.oCoords);
773       var intersection = fabric.Intersection.intersectPolygonPolygon(
774         [thisCoords.tl, thisCoords.tr, thisCoords.br, thisCoords.bl],
775         [otherCoords.tl, otherCoords.tr, otherCoords.br, otherCoords.bl]
776       );
777       
778       return (intersection.status === 'Intersection');
779     },
780     
781     /**
782      * Returns true if object is fully contained within area formed by 2 points
783      * @method isContainedWithinRect
784      * @param {Object} selectionTL
785      * @param {Object} selectionBR
786      * @return {Boolean}
787      */
788     isContainedWithinRect: function(selectionTL, selectionBR) {
789       var oCoords = this.oCoords,
790           tl = new fabric.Point(oCoords.tl.x, oCoords.tl.y),
791           tr = new fabric.Point(oCoords.tr.x, oCoords.tr.y),
792           bl = new fabric.Point(oCoords.bl.x, oCoords.bl.y),
793           br = new fabric.Point(oCoords.br.x, oCoords.br.y);
794       return tl.x > selectionTL.x
795         && tr.x < selectionBR.x
796         && tl.y > selectionTL.y
797         && bl.y < selectionBR.y;
798     },
799     
800     /**
801      * @method isType
802      * @param type {String} type to check against
803      * @return {Boolean} true if specified type is identical to the type of instance
804      */
805     isType: function(type) {
806       return this.type === type;
807     },
808     
809     /**
810      * Determines which one of the four corners has been clicked
811      * @method _findTargetCorner
812      * @private
813      * @param e {Event} event object
814      * @param offset {Object} canvas offset
815      * @return {String|Boolean} corner code (tl, tr, bl, br, etc.), or false if nothing is found
816      */
817     _findTargetCorner: function(e, offset) {
818       var pointer = getPointer(e),
819           ex = pointer.x - offset.left,
820           ey = pointer.y - offset.top,
821           xpoints,
822           lines;
823       
824       for (var i in this.oCoords) {
825         lines = this._getImageLines(this.oCoords[i].corner, i);
826         xpoints = this._findCrossPoints(ex, ey, lines);
827         if (xpoints % 2 == 1 && xpoints != 0) {
828           this.__corner = i;
829           return i;
830         }   
831       }
832       return false;
833     },
834     
835     /**
836      * Helper method to determine how many cross points are between the 4 image edges
837      * and the horizontal line determined by the position of our mouse when clicked on canvas
838      * @method _findCrossPoints
839      * @private
840      * @param ex {Number} x coordinate of the mouse
841      * @param ey {Number} y coordinate of the mouse
842      * @param oCoords {Object} Coordinates of the image being evaluated
843      */   
844     _findCrossPoints: function(ex, ey, oCoords) {
845       var b1, b2, a1, a2, xi, yi,
846           xcount = 0,
847           iLine;
848           
849       for (var lineKey in oCoords) {
850         iLine = oCoords[lineKey];
851         // optimisation 1: line below dot. no cross
852         if ((iLine.o.y < ey) && (iLine.d.y < ey)) {
853           continue;
854         }
855         // optimisation 2: line above dot. no cross
856         if ((iLine.o.y >= ey) && (iLine.d.y >= ey)) {
857           continue;
858         }
859         // optimisation 3: vertical line case
860         if ((iLine.o.x == iLine.d.x) && (iLine.o.x >= ex)) { 
861           xi = iLine.o.x;
862           yi = ey;
863         }
864         // calculate the intersection point
865         else {
866           b1 = 0;
867           b2 = (iLine.d.y-iLine.o.y)/(iLine.d.x-iLine.o.x); 
868           a1 = ey-b1*ex;
869           a2 = iLine.o.y-b2*iLine.o.x;
870 
871           xi = - (a1-a2)/(b1-b2); 
872           yi = a1+b1*xi; 
873         }
874         // dont count xi < ex cases
875         if (xi >= ex) { 
876           xcount += 1;
877         }
878         // optimisation 4: specific for square images
879         if (xcount == 2) {
880           break;
881         }
882       }
883       return xcount;
884     },
885     
886     /**
887      * Method that returns an object with the image lines in it given the coordinates of the corners
888      * @method _getImageLines
889      * @private
890      * @param oCoords {Object} coordinates of the image corners
891      */
892     _getImageLines: function(oCoords, i) {
893       return {
894         topline: { 
895           o: oCoords.tl,
896           d: oCoords.tr
897         },
898         rightline: { 
899           o: oCoords.tr,
900           d: oCoords.br 
901         },
902         bottomline: { 
903           o: oCoords.br,
904           d: oCoords.bl 
905         },
906         leftline: { 
907           o: oCoords.bl,
908           d: oCoords.tl 
909         }
910       }
911     },
912     
913     /**
914      * Sets the coordinates of the draggable boxes in the corners of
915      * the image used to scale/rotate it.
916      * @method _setCornerCoords
917      * @private
918      */ 
919     _setCornerCoords: function() {
920       var coords = this.oCoords,
921           theta = this.theta,
922           cosOffset = this.cornersize * /*this.scaleX * */ Math.cos(theta),
923           sinOffset = this.cornersize * /*this.scaleY * */ Math.sin(theta),
924           size2 = this.cornersize / 2,
925           size2x = size2 - sinOffset,
926           size2y = size2,
927           corner;
928       
929       coords.tl.x -= size2x;
930       coords.tl.y -= size2y;
931       
932       coords.tl.corner = {
933         tl: {
934           x: coords.tl.x,
935           y: coords.tl.y
936         },
937         tr: {
938           x: coords.tl.x + cosOffset,
939           y: coords.tl.y + sinOffset
940         },
941         bl: {
942           x: coords.tl.x - sinOffset,
943           y: coords.tl.y + cosOffset
944         }
945       };
946       coords.tl.corner.br = {
947         x: coords.tl.corner.tr.x - sinOffset,
948         y: coords.tl.corner.tr.y + cosOffset
949       };
950       
951       coords.tl.x += size2x;
952       coords.tl.y += size2y;
953       
954       coords.tr.x += size2;
955       coords.tr.y -= size2;
956       coords.tr.corner = {
957         tl: {
958           x: coords.tr.x - cosOffset,
959           y: coords.tr.y - sinOffset
960         },
961         tr: {
962           x: coords.tr.x,
963           y: coords.tr.y
964         },
965         br: {
966           x: coords.tr.x - sinOffset,
967           y: coords.tr.y + cosOffset
968         }
969       };
970       coords.tr.corner.bl = {
971         x: coords.tr.corner.tl.x - sinOffset,
972         y: coords.tr.corner.tl.y + cosOffset
973       };
974       coords.tr.x -= size2;
975       coords.tr.y += size2;
976       
977       coords.bl.x -= size2;
978       coords.bl.y += size2;
979       coords.bl.corner = {
980         tl: {
981           x: coords.bl.x + sinOffset,
982           y: coords.bl.y - cosOffset
983         },
984         bl: {
985           x: coords.bl.x,
986           y: coords.bl.y
987         },
988         br: {
989           x: coords.bl.x + cosOffset,
990           y: coords.bl.y + sinOffset
991         }
992       };
993       coords.bl.corner.tr = {
994         x: coords.bl.corner.br.x + sinOffset,
995         y: coords.bl.corner.br.y - cosOffset
996       };
997       coords.bl.x += size2;
998       coords.bl.y -= size2;
999       
1000       coords.br.x += size2;
1001       coords.br.y += size2;
1002       coords.br.corner = {
1003         tr: {
1004           x: coords.br.x + sinOffset,
1005           y: coords.br.y - cosOffset
1006         },
1007         bl: {
1008           x: coords.br.x - cosOffset,
1009           y: coords.br.y - sinOffset
1010         },
1011         br: {
1012           x: coords.br.x,
1013           y: coords.br.y
1014         }
1015       };
1016       coords.br.corner.tl = {
1017         x: coords.br.corner.bl.x + sinOffset,
1018         y: coords.br.corner.bl.y - cosOffset
1019       };
1020       coords.br.x -= size2;
1021       coords.br.y -= size2;
1022       
1023       
1024       coords.ml.x -= size2;
1025       coords.ml.y -= size2;
1026       coords.ml.corner = {
1027         tl: {
1028           x: coords.ml.x,
1029           y: coords.ml.y
1030         },
1031         tr: {
1032           x: coords.ml.x + cosOffset,
1033           y: coords.ml.y + sinOffset
1034         },
1035         bl: {
1036           x: coords.ml.x - sinOffset,
1037           y: coords.ml.y + cosOffset
1038         }
1039       };
1040       coords.ml.corner.br = {
1041         x: coords.ml.corner.tr.x - sinOffset,
1042         y: coords.ml.corner.tr.y + cosOffset
1043       };
1044       coords.ml.x += size2;
1045       coords.ml.y += size2;
1046       
1047       coords.mt.x -= size2;
1048       coords.mt.y -= size2;
1049       coords.mt.corner = {
1050         tl: {
1051           x: coords.mt.x,
1052           y: coords.mt.y
1053         },
1054         tr: {
1055           x: coords.mt.x + cosOffset,
1056           y: coords.mt.y + sinOffset
1057         },
1058         bl: {
1059           x: coords.mt.x - sinOffset,
1060           y: coords.mt.y + cosOffset
1061         }
1062       };
1063       coords.mt.corner.br = {
1064         x: coords.mt.corner.tr.x - sinOffset,
1065         y: coords.mt.corner.tr.y + cosOffset
1066       };
1067       coords.mt.x += size2;
1068       coords.mt.y += size2;
1069       
1070       coords.mr.x -= size2;
1071       coords.mr.y -= size2;
1072       coords.mr.corner = {
1073         tl: {
1074           x: coords.mr.x,
1075           y: coords.mr.y
1076         },
1077         tr: {
1078           x: coords.mr.x + cosOffset,
1079           y: coords.mr.y + sinOffset
1080         },
1081         bl: {
1082           x: coords.mr.x - sinOffset,
1083           y: coords.mr.y + cosOffset
1084         }
1085       };
1086       coords.mr.corner.br = {
1087         x: coords.mr.corner.tr.x - sinOffset,
1088         y: coords.mr.corner.tr.y + cosOffset
1089       };
1090       coords.mr.x += size2;
1091       coords.mr.y += size2;
1092       
1093       coords.mb.x -= size2;
1094       coords.mb.y -= size2;
1095       coords.mb.corner = {
1096         tl: {
1097           x: coords.mb.x,
1098           y: coords.mb.y
1099         },
1100         tr: {
1101           x: coords.mb.x + cosOffset,
1102           y: coords.mb.y + sinOffset
1103         },
1104         bl: {
1105           x: coords.mb.x - sinOffset,
1106           y: coords.mb.y + cosOffset
1107         }
1108       };
1109       coords.mb.corner.br = {
1110         x: coords.mb.corner.tr.x - sinOffset,
1111         y: coords.mb.corner.tr.y + cosOffset
1112       };
1113       
1114       coords.mb.x += size2;
1115       coords.mb.y += size2;
1116       
1117       corner = coords.mb.corner;
1118       
1119       corner.tl.x -= size2;
1120       corner.tl.y -= size2;
1121       corner.tr.x -= size2;
1122       corner.tr.y -= size2;
1123       corner.br.x -= size2;
1124       corner.br.y -= size2;
1125       corner.bl.x -= size2;
1126       corner.bl.y -= size2;
1127     },
1128     
1129     /**
1130      * Makes object's color grayscale
1131      * @method toGrayscale
1132      * @return {fabric.Object} thisArg
1133      */
1134     toGrayscale: function() {
1135       var fillValue = this.get('fill');
1136       if (fillValue) {
1137         this.set('overlayFill', new fabric.Color(fillValue).toGrayscale().toRgb());
1138       }
1139       return this;
1140     },
1141     
1142     /**
1143      * @method complexity
1144      * @return {Number}
1145      */
1146     complexity: function() {
1147       return 0;
1148     },
1149     
1150     /**
1151      * @method getCenter
1152      * @return {Object} object with `x`, `y` properties corresponding to path center coordinates
1153      */
1154     getCenter: function() {
1155       return {
1156         x: this.get('left') + this.width / 2,
1157         y: this.get('top') + this.height / 2
1158       };
1159     },
1160     
1161     /**
1162      * @method straighten
1163      * @return {fabric.Object} thisArg
1164      * @chainable
1165      */
1166     straighten: function() {
1167       var angle = this._getAngleValueForStraighten();
1168       this.setAngle(angle);
1169       return this;
1170     },
1171     
1172     /**
1173      * @method fxStraighten
1174      * @param {Object} callbacks
1175      *                  - onComplete: invoked on completion
1176      *                  - onChange: invoked on every step of animation
1177      *
1178      * @return {fabric.Object} thisArg
1179      * @chainable
1180      */
1181     fxStraighten: function(callbacks) {
1182       callbacks = callbacks || { };
1183       
1184       var empty = function() { },
1185           onComplete = callbacks.onComplete || empty,
1186           onChange = callbacks.onChange || empty,
1187           _this = this;
1188       
1189       fabric.util.animate({
1190         startValue: this.get('angle'),
1191         endValue: this._getAngleValueForStraighten(),
1192         duration: this.FX_DURATION,
1193         onChange: function(value) {
1194           _this.setAngle(value);
1195           onChange();
1196         },
1197         onComplete: function() {
1198           _this.setCoords();
1199           onComplete();
1200         },
1201         onStart: function() {
1202           _this.setActive(false);
1203         }
1204       });
1205       
1206       return this;
1207     },
1208     
1209     /**
1210      * @method fxRemove
1211      * @param {Object} callbacks
1212      * @return {fabric.Object} thisArg
1213      * @chainable
1214      */
1215     fxRemove: function(callbacks) {
1216       callbacks || (callbacks = { });
1217       
1218       var empty = function() { },
1219           onComplete = callbacks.onComplete || empty,
1220           onChange = callbacks.onChange || empty,
1221           _this = this;
1222       
1223       fabric.util.animate({
1224         startValue: this.get('opacity'),
1225         endValue: 0,
1226         duration: this.FX_DURATION,
1227         onChange: function(value) {
1228           _this.set('opacity', value);
1229           onChange();
1230         },
1231         onComplete: onComplete,
1232         onStart: function() {
1233           _this.setActive(false);
1234         }
1235       });
1236       
1237       return this;
1238     },
1239     
1240     /**
1241      * @method _getAngleValueForStraighten
1242      * @return {Number} angle value
1243      * @private
1244      */
1245     _getAngleValueForStraighten: function() {
1246       var angle = this.get('angle');
1247       
1248       // TODO (kangax): can this be simplified?
1249       
1250       if      (angle > -225 && angle <= -135) { return -180;  }
1251       else if (angle > -135 && angle <= -45)  { return  -90;  }
1252       else if (angle > -45  && angle <= 45)   { return    0;  }
1253       else if (angle > 45   && angle <= 135)  { return   90;  }
1254       else if (angle > 135  && angle <= 225 ) { return  180;  }
1255       else if (angle > 225  && angle <= 315)  { return  270;  }
1256       else if (angle > 315)                   { return  360;  }
1257       
1258       return 0;
1259     },
1260     
1261     /**
1262      * Returns a JSON representation of an instance
1263      * @method toJSON
1264      * @return {String} json
1265      */
1266     toJSON: function() {
1267       // delegate, not alias
1268       return this.toObject();
1269     }
1270   });
1271   
1272   /**
1273    * @alias rotate -> setAngle
1274    */
1275   fabric.Object.prototype.rotate = fabric.Object.prototype.setAngle;
1276 })();