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