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