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