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