2010-10-22 02:54:00 +00:00
|
|
|
(function(global) {
|
2010-06-09 22:34:55 +00:00
|
|
|
|
2010-10-22 02:54:00 +00:00
|
|
|
"use strict";
|
|
|
|
|
|
|
|
|
|
var fabric = global.fabric || (global.fabric = { }),
|
2010-07-26 23:20:19 +00:00
|
|
|
extend = fabric.util.object.extend,
|
|
|
|
|
clone = fabric.util.object.clone,
|
|
|
|
|
toFixed = fabric.util.toFixed,
|
|
|
|
|
capitalize = fabric.util.string.capitalize,
|
2010-07-27 18:07:59 +00:00
|
|
|
getPointer = fabric.util.getPointer,
|
2011-02-09 21:35:46 +00:00
|
|
|
degreesToRadians = fabric.util.degreesToRadians,
|
2011-01-06 20:20:56 +00:00
|
|
|
slice = Array.prototype.slice;
|
2010-07-09 23:43:50 +00:00
|
|
|
|
|
|
|
|
if (fabric.Object) {
|
2010-06-10 17:57:59 +00:00
|
|
|
return;
|
|
|
|
|
}
|
2010-06-09 22:34:55 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @class Object
|
2010-10-14 21:42:39 +00:00
|
|
|
* @memberOf fabric
|
2010-06-09 22:34:55 +00:00
|
|
|
*/
|
2010-10-14 21:42:39 +00:00
|
|
|
fabric.Object = fabric.util.createClass(/** @scope fabric.Object.prototype */ {
|
2010-06-09 22:34:55 +00:00
|
|
|
|
2010-10-15 02:16:24 +00:00
|
|
|
/**
|
|
|
|
|
* @property
|
|
|
|
|
* @type String
|
|
|
|
|
*/
|
2010-06-09 22:34:55 +00:00
|
|
|
type: 'object',
|
|
|
|
|
|
2010-10-15 02:16:24 +00:00
|
|
|
/**
|
|
|
|
|
* @property
|
|
|
|
|
* @type Boolean
|
|
|
|
|
*/
|
2010-06-09 22:34:55 +00:00
|
|
|
includeDefaultValues: true,
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @constant
|
2010-10-15 02:16:24 +00:00
|
|
|
* @type Number
|
2010-06-09 22:34:55 +00:00
|
|
|
*/
|
|
|
|
|
NUM_FRACTION_DIGITS: 2,
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @constant
|
2010-10-15 02:16:24 +00:00
|
|
|
* @type Number
|
2010-06-09 22:34:55 +00:00
|
|
|
*/
|
|
|
|
|
FX_DURATION: 500,
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @constant
|
2010-10-15 02:16:24 +00:00
|
|
|
* @type String
|
2010-06-09 22:34:55 +00:00
|
|
|
*/
|
|
|
|
|
FX_TRANSITION: 'decel',
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @constant
|
2010-10-15 02:16:24 +00:00
|
|
|
* @type Number
|
2010-06-09 22:34:55 +00:00
|
|
|
*/
|
|
|
|
|
MIN_SCALE_LIMIT: 0.1,
|
|
|
|
|
|
2010-10-15 02:16:24 +00:00
|
|
|
/**
|
2010-10-19 20:27:24 +00:00
|
|
|
* List of properties to consider when checking if state of an object is changed (fabric.Object#hasStateChanged);
|
|
|
|
|
* as well as for history (undo/redo) purposes
|
2010-10-15 02:16:24 +00:00
|
|
|
* @property
|
|
|
|
|
* @type Array
|
|
|
|
|
*/
|
2010-06-11 14:26:51 +00:00
|
|
|
stateProperties: ('top left width height scaleX scaleY flipX flipY ' +
|
|
|
|
|
'theta angle opacity cornersize fill overlayFill stroke ' +
|
|
|
|
|
'strokeWidth fillRule borderScaleFactor transformMatrix').split(' '),
|
2010-06-09 22:34:55 +00:00
|
|
|
|
2011-02-09 23:21:45 +00:00
|
|
|
top: 0,
|
|
|
|
|
left: 0,
|
|
|
|
|
width: 0,
|
|
|
|
|
height: 0,
|
|
|
|
|
scaleX: 1,
|
|
|
|
|
scaleY: 1,
|
|
|
|
|
flipX: false,
|
|
|
|
|
flipY: false,
|
|
|
|
|
theta: 0,
|
|
|
|
|
opacity: 1,
|
|
|
|
|
angle: 0,
|
|
|
|
|
cornersize: 12,
|
|
|
|
|
padding: 0,
|
|
|
|
|
borderColor: 'rgba(102,153,255,0.75)',
|
|
|
|
|
cornerColor: 'rgba(102,153,255,0.5)',
|
|
|
|
|
fill: 'rgb(0,0,0)',
|
|
|
|
|
fillRule: 'source-over',
|
|
|
|
|
overlayFill: null,
|
|
|
|
|
stroke: null,
|
|
|
|
|
strokeWidth: 1,
|
|
|
|
|
borderOpacityWhenMoving: 0.4,
|
|
|
|
|
borderScaleFactor: 1,
|
|
|
|
|
transformMatrix: null,
|
2010-06-09 22:34:55 +00:00
|
|
|
|
2010-10-14 21:42:39 +00:00
|
|
|
/**
|
|
|
|
|
* @method callSuper
|
|
|
|
|
* @param {String} methodName
|
|
|
|
|
*/
|
2010-06-09 22:34:55 +00:00
|
|
|
callSuper: function(methodName) {
|
|
|
|
|
var fn = this.constructor.superclass.prototype[methodName];
|
|
|
|
|
return (arguments.length > 1)
|
2010-07-27 18:07:59 +00:00
|
|
|
? fn.apply(this, slice.call(arguments, 1))
|
2010-06-09 22:34:55 +00:00
|
|
|
: fn.call(this);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
2010-10-14 21:42:39 +00:00
|
|
|
* Constructor
|
|
|
|
|
* @method initialize
|
|
|
|
|
* @param {Object} [options] Options object
|
2010-06-09 22:34:55 +00:00
|
|
|
*/
|
|
|
|
|
initialize: function(options) {
|
2011-02-09 23:21:45 +00:00
|
|
|
options && this.setOptions(options);
|
2010-06-09 22:34:55 +00:00
|
|
|
},
|
|
|
|
|
|
2010-10-14 21:42:39 +00:00
|
|
|
/**
|
|
|
|
|
* @method setOptions
|
|
|
|
|
* @param {Object} [options]
|
|
|
|
|
*/
|
2010-06-09 22:34:55 +00:00
|
|
|
setOptions: function(options) {
|
2011-02-06 07:58:32 +00:00
|
|
|
var i = this.stateProperties.length, prop;
|
|
|
|
|
while (i--) {
|
|
|
|
|
prop = this.stateProperties[i];
|
2011-03-02 00:26:24 +00:00
|
|
|
if (prop in options) {
|
2011-02-09 23:21:45 +00:00
|
|
|
(prop === 'angle')
|
|
|
|
|
? this.setAngle(options[prop])
|
|
|
|
|
: (this[prop] = options[prop]);
|
|
|
|
|
}
|
2011-02-06 07:58:32 +00:00
|
|
|
}
|
2010-06-09 22:34:55 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @method transform
|
|
|
|
|
* @param {CanvasRenderingContext2D} ctx Context
|
|
|
|
|
*/
|
|
|
|
|
transform: function(ctx) {
|
|
|
|
|
ctx.globalAlpha = this.opacity;
|
|
|
|
|
ctx.translate(this.left, this.top);
|
|
|
|
|
ctx.rotate(this.theta);
|
|
|
|
|
ctx.scale(
|
|
|
|
|
this.scaleX * (this.flipX ? -1 : 1),
|
|
|
|
|
this.scaleY * (this.flipY ? -1 : 1)
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns an object representation of an instance
|
|
|
|
|
* @method toObject
|
|
|
|
|
* @return {Object}
|
|
|
|
|
*/
|
|
|
|
|
toObject: function() {
|
2011-02-09 23:21:45 +00:00
|
|
|
|
2010-06-09 22:34:55 +00:00
|
|
|
var object = {
|
2011-02-09 23:21:45 +00:00
|
|
|
type: this.type,
|
|
|
|
|
left: toFixed(this.left, this.NUM_FRACTION_DIGITS),
|
|
|
|
|
top: toFixed(this.top, this.NUM_FRACTION_DIGITS),
|
|
|
|
|
width: toFixed(this.width, this.NUM_FRACTION_DIGITS),
|
|
|
|
|
height: toFixed(this.height, this.NUM_FRACTION_DIGITS),
|
|
|
|
|
fill: this.fill,
|
|
|
|
|
overlayFill: this.overlayFill,
|
|
|
|
|
stroke: this.stroke,
|
|
|
|
|
strokeWidth: this.strokeWidth,
|
|
|
|
|
scaleX: toFixed(this.scaleX, this.NUM_FRACTION_DIGITS),
|
|
|
|
|
scaleY: toFixed(this.scaleY, this.NUM_FRACTION_DIGITS),
|
|
|
|
|
angle: toFixed(this.getAngle(), this.NUM_FRACTION_DIGITS),
|
|
|
|
|
flipX: this.flipX,
|
|
|
|
|
flipY: this.flipY,
|
|
|
|
|
opacity: toFixed(this.opacity, this.NUM_FRACTION_DIGITS)
|
2010-06-09 22:34:55 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (!this.includeDefaultValues) {
|
|
|
|
|
object = this._removeDefaultValues(object);
|
|
|
|
|
}
|
2011-02-09 23:21:45 +00:00
|
|
|
|
2010-06-09 22:34:55 +00:00
|
|
|
return object;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
2010-10-19 20:27:24 +00:00
|
|
|
* Returns (dataless) object representation of an instance
|
2010-06-09 22:34:55 +00:00
|
|
|
* @method toDatalessObject
|
|
|
|
|
*/
|
|
|
|
|
toDatalessObject: function() {
|
|
|
|
|
// will be overwritten by subclasses
|
|
|
|
|
return this.toObject();
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @private
|
|
|
|
|
* @method _removeDefaultValues
|
|
|
|
|
*/
|
|
|
|
|
_removeDefaultValues: function(object) {
|
2010-07-09 23:43:50 +00:00
|
|
|
var defaultOptions = fabric.Object.prototype.options;
|
2010-06-17 14:00:47 +00:00
|
|
|
this.stateProperties.forEach(function(prop) {
|
2010-06-09 22:34:55 +00:00
|
|
|
if (object[prop] === defaultOptions[prop]) {
|
|
|
|
|
delete object[prop];
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return object;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns true if an object is in its active state
|
|
|
|
|
* @return {Boolean} true if an object is in its active state
|
|
|
|
|
*/
|
|
|
|
|
isActive: function() {
|
|
|
|
|
return !!this.active;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Sets state of an object - `true` makes it active, `false` - inactive
|
|
|
|
|
* @param {Boolean} active
|
2010-07-09 23:43:50 +00:00
|
|
|
* @return {fabric.Object} thisArg
|
2010-06-09 22:34:55 +00:00
|
|
|
* @chainable
|
|
|
|
|
*/
|
|
|
|
|
setActive: function(active) {
|
|
|
|
|
this.active = !!active;
|
|
|
|
|
return this;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns a string representation of an instance
|
|
|
|
|
* @return {String}
|
|
|
|
|
*/
|
|
|
|
|
toString: function() {
|
2010-07-26 23:20:19 +00:00
|
|
|
return "#<fabric." + capitalize(this.type) + ">";
|
2010-06-09 22:34:55 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Basic setter
|
|
|
|
|
* @param {Any} property
|
|
|
|
|
* @param {Any} value
|
2010-07-09 23:43:50 +00:00
|
|
|
* @return {fabric.Object} thisArg
|
2010-06-09 22:34:55 +00:00
|
|
|
* @chainable
|
|
|
|
|
*/
|
|
|
|
|
set: function(property, value) {
|
|
|
|
|
var shouldConstrainValue = (property === 'scaleX' || property === 'scaleY') && value < this.MIN_SCALE_LIMIT;
|
|
|
|
|
if (shouldConstrainValue) {
|
|
|
|
|
value = this.MIN_SCALE_LIMIT;
|
|
|
|
|
}
|
|
|
|
|
if (property === 'angle') {
|
|
|
|
|
this.setAngle(value);
|
|
|
|
|
}
|
|
|
|
|
else {
|
2010-07-09 22:58:33 +00:00
|
|
|
this[property] = value;
|
2010-06-09 22:34:55 +00:00
|
|
|
}
|
|
|
|
|
return this;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Toggles specified property from `true` to `false` or from `false` to `true`
|
|
|
|
|
* @method toggle
|
|
|
|
|
* @param {String} property property to toggle
|
2010-07-09 23:43:50 +00:00
|
|
|
* @return {fabric.Object} thisArg
|
2010-06-09 22:34:55 +00:00
|
|
|
* @chainable
|
|
|
|
|
*/
|
|
|
|
|
toggle: function(property) {
|
|
|
|
|
var value = this.get(property);
|
|
|
|
|
if (typeof value === 'boolean') {
|
|
|
|
|
this.set(property, !value);
|
|
|
|
|
}
|
|
|
|
|
return this;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @method setSourcePath
|
|
|
|
|
* @param {String} value
|
2010-07-09 23:43:50 +00:00
|
|
|
* @return {fabric.Object} thisArg
|
2010-06-09 22:34:55 +00:00
|
|
|
* @chainable
|
|
|
|
|
*/
|
|
|
|
|
setSourcePath: function(value) {
|
|
|
|
|
this.sourcePath = value;
|
|
|
|
|
return this;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Basic getter
|
|
|
|
|
* @method get
|
|
|
|
|
* @param {Any} property
|
|
|
|
|
* @return {Any} value of a property
|
|
|
|
|
*/
|
|
|
|
|
get: function(property) {
|
|
|
|
|
return (property === 'angle')
|
|
|
|
|
? this.getAngle()
|
|
|
|
|
: this[property];
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @method render
|
|
|
|
|
* @param {CanvasRenderingContext2D} ctx context to render on
|
|
|
|
|
* @param {Boolean} noTransform
|
|
|
|
|
*/
|
|
|
|
|
render: function(ctx, noTransform) {
|
|
|
|
|
|
|
|
|
|
// do not render if width or height are zeros
|
|
|
|
|
if (this.width === 0 || this.height === 0) return;
|
|
|
|
|
|
|
|
|
|
ctx.save();
|
|
|
|
|
|
|
|
|
|
var m = this.transformMatrix;
|
|
|
|
|
if (m) {
|
|
|
|
|
ctx.setTransform(m[0], m[1], m[2], m[3], m[4], m[5]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!noTransform) {
|
|
|
|
|
this.transform(ctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.stroke) {
|
|
|
|
|
ctx.lineWidth = this.strokeWidth;
|
|
|
|
|
ctx.strokeStyle = this.stroke;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.overlayFill) {
|
|
|
|
|
ctx.fillStyle = this.overlayFill;
|
|
|
|
|
}
|
|
|
|
|
else if (this.fill) {
|
|
|
|
|
ctx.fillStyle = this.fill;
|
|
|
|
|
}
|
|
|
|
|
|
2010-09-09 21:55:43 +00:00
|
|
|
this._render(ctx, noTransform);
|
2010-06-09 22:34:55 +00:00
|
|
|
|
|
|
|
|
if (this.active && !noTransform) {
|
|
|
|
|
this.drawBorders(ctx);
|
2010-08-02 18:13:07 +00:00
|
|
|
this.hideCorners || this.drawCorners(ctx);
|
2010-06-09 22:34:55 +00:00
|
|
|
}
|
|
|
|
|
ctx.restore();
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns width of an object
|
|
|
|
|
* @method getWidth
|
|
|
|
|
* @return {Number} width value
|
|
|
|
|
*/
|
|
|
|
|
getWidth: function() {
|
|
|
|
|
return this.width * this.scaleX;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns height of an object
|
|
|
|
|
* @method getHeight
|
|
|
|
|
* @return {Number} height value
|
|
|
|
|
*/
|
|
|
|
|
getHeight: function() {
|
|
|
|
|
return this.height * this.scaleY;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Scales an object (equally by x and y)
|
|
|
|
|
* @method scale
|
|
|
|
|
* @param value {Number} scale factor
|
2010-07-09 23:43:50 +00:00
|
|
|
* @return {fabric.Object} thisArg
|
2010-06-09 22:34:55 +00:00
|
|
|
* @chainable
|
|
|
|
|
*/
|
|
|
|
|
scale: function(value) {
|
|
|
|
|
this.scaleX = value;
|
|
|
|
|
this.scaleY = value;
|
|
|
|
|
return this;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Scales an object to a given width (scaling by x/y equally)
|
|
|
|
|
* @method scaleToWidth
|
|
|
|
|
* @param value {Number} new width value
|
2010-07-09 23:43:50 +00:00
|
|
|
* @return {fabric.Object} thisArg
|
2010-06-09 22:34:55 +00:00
|
|
|
* @chainable
|
|
|
|
|
*/
|
|
|
|
|
scaleToWidth: function(value) {
|
|
|
|
|
return this.scale(value / this.width);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Scales an object to a given height (scaling by x/y equally)
|
|
|
|
|
* @method scaleToHeight
|
|
|
|
|
* @param value {Number} new height value
|
2010-07-09 23:43:50 +00:00
|
|
|
* @return {fabric.Object} thisArg
|
2010-06-09 22:34:55 +00:00
|
|
|
* @chainable
|
|
|
|
|
*/
|
|
|
|
|
scaleToHeight: function(value) {
|
|
|
|
|
return this.scale(value / this.height);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Sets object opacity
|
|
|
|
|
* @method setOpacity
|
|
|
|
|
* @param value {Number} value 0-1
|
2010-07-09 23:43:50 +00:00
|
|
|
* @return {fabric.Object} thisArg
|
2010-06-09 22:34:55 +00:00
|
|
|
* @chainable
|
|
|
|
|
*/
|
|
|
|
|
setOpacity: function(value) {
|
|
|
|
|
this.set('opacity', value);
|
|
|
|
|
return this;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns object's angle value
|
|
|
|
|
* @method getAngle
|
|
|
|
|
* @return {Number} angle value
|
|
|
|
|
*/
|
|
|
|
|
getAngle: function() {
|
|
|
|
|
return this.theta * 180 / Math.PI;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Sets object's angle
|
|
|
|
|
* @method setAngle
|
|
|
|
|
* @param value {Number} angle value
|
|
|
|
|
* @return {Object} thisArg
|
|
|
|
|
*/
|
|
|
|
|
setAngle: function(value) {
|
|
|
|
|
this.theta = value / 180 * Math.PI;
|
|
|
|
|
this.angle = value;
|
|
|
|
|
return this;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Sets corner position coordinates based on current angle, width and height.
|
|
|
|
|
* @method setCoords
|
2010-07-09 23:43:50 +00:00
|
|
|
* return {fabric.Object} thisArg
|
2010-06-09 22:34:55 +00:00
|
|
|
* @chainable
|
|
|
|
|
*/
|
|
|
|
|
setCoords: function() {
|
|
|
|
|
|
|
|
|
|
this.currentWidth = this.width * this.scaleX;
|
|
|
|
|
this.currentHeight = this.height * this.scaleY;
|
|
|
|
|
|
|
|
|
|
this._hypotenuse = Math.sqrt(
|
|
|
|
|
Math.pow(this.currentWidth / 2, 2) +
|
|
|
|
|
Math.pow(this.currentHeight / 2, 2));
|
|
|
|
|
|
|
|
|
|
this._angle = Math.atan(this.currentHeight / this.currentWidth);
|
|
|
|
|
|
|
|
|
|
// offset added for rotate and scale actions
|
|
|
|
|
var offsetX = Math.cos(this._angle + this.theta) * this._hypotenuse,
|
|
|
|
|
offsetY = Math.sin(this._angle + this.theta) * this._hypotenuse,
|
|
|
|
|
theta = this.theta,
|
|
|
|
|
sinTh = Math.sin(theta),
|
|
|
|
|
cosTh = Math.cos(theta);
|
|
|
|
|
|
|
|
|
|
var tl = {
|
|
|
|
|
x: this.left - offsetX,
|
|
|
|
|
y: this.top - offsetY
|
|
|
|
|
};
|
|
|
|
|
var tr = {
|
|
|
|
|
x: tl.x + (this.currentWidth * cosTh),
|
|
|
|
|
y: tl.y + (this.currentWidth * sinTh)
|
|
|
|
|
};
|
|
|
|
|
var br = {
|
|
|
|
|
x: tr.x - (this.currentHeight * sinTh),
|
|
|
|
|
y: tr.y + (this.currentHeight * cosTh)
|
|
|
|
|
};
|
|
|
|
|
var bl = {
|
|
|
|
|
x: tl.x - (this.currentHeight * sinTh),
|
|
|
|
|
y: tl.y + (this.currentHeight * cosTh)
|
|
|
|
|
};
|
|
|
|
|
var ml = {
|
|
|
|
|
x: tl.x - (this.currentHeight/2 * sinTh),
|
|
|
|
|
y: tl.y + (this.currentHeight/2 * cosTh)
|
|
|
|
|
};
|
|
|
|
|
var mt = {
|
|
|
|
|
x: tl.x + (this.currentWidth/2 * cosTh),
|
|
|
|
|
y: tl.y + (this.currentWidth/2 * sinTh)
|
|
|
|
|
};
|
|
|
|
|
var mr = {
|
|
|
|
|
x: tr.x - (this.currentHeight/2 * sinTh),
|
|
|
|
|
y: tr.y + (this.currentHeight/2 * cosTh)
|
|
|
|
|
}
|
|
|
|
|
var mb = {
|
|
|
|
|
x: bl.x + (this.currentWidth/2 * cosTh),
|
|
|
|
|
y: bl.y + (this.currentWidth/2 * sinTh)
|
|
|
|
|
}
|
|
|
|
|
|
2011-01-23 08:30:22 +00:00
|
|
|
// debugging
|
|
|
|
|
|
|
|
|
|
// setTimeout(function() {
|
|
|
|
|
// canvas.contextTop.fillStyle = 'green';
|
|
|
|
|
// canvas.contextTop.fillRect(mb.x, mb.y, 3, 3);
|
|
|
|
|
// canvas.contextTop.fillRect(bl.x, bl.y, 3, 3);
|
|
|
|
|
// canvas.contextTop.fillRect(br.x, br.y, 3, 3);
|
|
|
|
|
// canvas.contextTop.fillRect(tl.x, tl.y, 3, 3);
|
|
|
|
|
// canvas.contextTop.fillRect(tr.x, tr.y, 3, 3);
|
|
|
|
|
// canvas.contextTop.fillRect(ml.x, ml.y, 3, 3);
|
|
|
|
|
// canvas.contextTop.fillRect(mr.x, mr.y, 3, 3);
|
|
|
|
|
// canvas.contextTop.fillRect(mt.x, mt.y, 3, 3);
|
|
|
|
|
// }, 50);
|
|
|
|
|
|
2010-06-09 22:34:55 +00:00
|
|
|
// clockwise
|
|
|
|
|
this.oCoords = { tl: tl, tr: tr, br: br, bl: bl, ml: ml, mt: mt, mr: mr, mb: mb };
|
|
|
|
|
|
|
|
|
|
// set coordinates of the draggable boxes in the corners used to scale/rotate the image
|
|
|
|
|
this._setCornerCoords();
|
|
|
|
|
|
|
|
|
|
return this;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Draws borders of an object's bounding box.
|
|
|
|
|
* Requires public properties: width, height
|
|
|
|
|
* Requires public options: padding, borderColor
|
|
|
|
|
* @method drawBorders
|
|
|
|
|
* @param {CanvasRenderingContext2D} ctx Context to draw on
|
2010-07-09 23:43:50 +00:00
|
|
|
* @return {fabric.Object} thisArg
|
2010-06-09 22:34:55 +00:00
|
|
|
* @chainable
|
|
|
|
|
*/
|
|
|
|
|
drawBorders: function(ctx) {
|
2011-02-09 23:21:45 +00:00
|
|
|
var padding = this.padding,
|
2010-06-09 22:34:55 +00:00
|
|
|
padding2 = padding * 2;
|
2011-03-21 07:53:23 +00:00
|
|
|
|
2010-06-09 22:34:55 +00:00
|
|
|
ctx.save();
|
2011-03-21 07:53:23 +00:00
|
|
|
|
2011-02-09 23:21:45 +00:00
|
|
|
ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1;
|
|
|
|
|
ctx.strokeStyle = this.borderColor;
|
2011-03-21 07:53:23 +00:00
|
|
|
|
2010-08-02 18:13:07 +00:00
|
|
|
var scaleX = 1 / (this.scaleX < this.MIN_SCALE_LIMIT ? this.MIN_SCALE_LIMIT : this.scaleX),
|
|
|
|
|
scaleY = 1 / (this.scaleY < this.MIN_SCALE_LIMIT ? this.MIN_SCALE_LIMIT : this.scaleY);
|
2011-03-21 07:53:23 +00:00
|
|
|
|
2010-06-09 22:34:55 +00:00
|
|
|
ctx.lineWidth = 1 / this.borderScaleFactor;
|
2011-03-21 07:53:23 +00:00
|
|
|
|
2010-06-09 22:34:55 +00:00
|
|
|
ctx.scale(scaleX, scaleY);
|
2011-03-21 07:53:23 +00:00
|
|
|
|
2010-06-09 22:34:55 +00:00
|
|
|
var w = this.getWidth(),
|
|
|
|
|
h = this.getHeight();
|
2011-03-21 07:53:23 +00:00
|
|
|
|
2010-06-09 22:34:55 +00:00
|
|
|
ctx.strokeRect(
|
2010-07-31 18:06:10 +00:00
|
|
|
~~(-(w / 2) - padding) + 0.5, // offset needed to make lines look sharper
|
|
|
|
|
~~(-(h / 2) - padding) + 0.5,
|
|
|
|
|
~~(w + padding2),
|
|
|
|
|
~~(h + padding2)
|
2010-06-09 22:34:55 +00:00
|
|
|
);
|
2011-03-21 07:53:23 +00:00
|
|
|
|
2010-06-09 22:34:55 +00:00
|
|
|
ctx.restore();
|
|
|
|
|
return this;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Draws corners of an object's bounding box.
|
|
|
|
|
* Requires public properties: width, height, scaleX, scaleY
|
|
|
|
|
* Requires public options: cornersize, padding
|
|
|
|
|
* @method drawCorners
|
|
|
|
|
* @param {CanvasRenderingContext2D} ctx Context to draw on
|
2010-07-09 23:43:50 +00:00
|
|
|
* @return {fabric.Object} thisArg
|
2010-06-09 22:34:55 +00:00
|
|
|
* @chainable
|
|
|
|
|
*/
|
|
|
|
|
drawCorners: function(ctx) {
|
2011-02-09 23:21:45 +00:00
|
|
|
var size = this.cornersize,
|
2010-06-09 22:34:55 +00:00
|
|
|
size2 = size / 2,
|
2011-02-09 23:21:45 +00:00
|
|
|
padding = this.padding,
|
2010-06-09 22:34:55 +00:00
|
|
|
left = -(this.width / 2),
|
|
|
|
|
top = -(this.height / 2),
|
|
|
|
|
_left,
|
|
|
|
|
_top,
|
|
|
|
|
sizeX = size / this.scaleX,
|
|
|
|
|
sizeY = size / this.scaleY,
|
|
|
|
|
scaleOffsetY = (padding + size2) / this.scaleY,
|
|
|
|
|
scaleOffsetX = (padding + size2) / this.scaleX,
|
|
|
|
|
scaleOffsetSizeX = (padding + size2 - size) / this.scaleX,
|
2011-03-21 07:53:23 +00:00
|
|
|
scaleOffsetSizeY = (padding + size2 - size) / this.scaleY,
|
|
|
|
|
height = this.height;
|
2010-06-09 22:34:55 +00:00
|
|
|
|
|
|
|
|
ctx.save();
|
|
|
|
|
|
2011-02-09 23:21:45 +00:00
|
|
|
ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1;
|
|
|
|
|
ctx.fillStyle = this.cornerColor;
|
2010-06-09 22:34:55 +00:00
|
|
|
|
|
|
|
|
// top-left
|
|
|
|
|
_left = left - scaleOffsetX;
|
|
|
|
|
_top = top - scaleOffsetY;
|
|
|
|
|
ctx.fillRect(_left, _top, sizeX, sizeY);
|
|
|
|
|
|
|
|
|
|
// top-right
|
|
|
|
|
_left = left + this.width - scaleOffsetX;
|
|
|
|
|
_top = top - scaleOffsetY;
|
|
|
|
|
ctx.fillRect(_left, _top, sizeX, sizeY);
|
|
|
|
|
|
|
|
|
|
// bottom-left
|
|
|
|
|
_left = left - scaleOffsetX;
|
2011-03-21 07:53:23 +00:00
|
|
|
_top = top + height + scaleOffsetSizeY;
|
2010-06-09 22:34:55 +00:00
|
|
|
ctx.fillRect(_left, _top, sizeX, sizeY);
|
|
|
|
|
|
|
|
|
|
// bottom-right
|
|
|
|
|
_left = left + this.width + scaleOffsetSizeX;
|
2011-03-21 07:53:23 +00:00
|
|
|
_top = top + height + scaleOffsetSizeY;
|
2010-06-09 22:34:55 +00:00
|
|
|
ctx.fillRect(_left, _top, sizeX, sizeY);
|
|
|
|
|
|
|
|
|
|
// middle-top
|
|
|
|
|
_left = left + this.width/2 - scaleOffsetX;
|
|
|
|
|
_top = top - scaleOffsetY;
|
|
|
|
|
ctx.fillRect(_left, _top, sizeX, sizeY);
|
|
|
|
|
|
|
|
|
|
// middle-bottom
|
|
|
|
|
_left = left + this.width/2 - scaleOffsetX;
|
2011-03-21 07:53:23 +00:00
|
|
|
_top = top + height + scaleOffsetSizeY;
|
2010-06-09 22:34:55 +00:00
|
|
|
ctx.fillRect(_left, _top, sizeX, sizeY);
|
|
|
|
|
|
|
|
|
|
// middle-right
|
|
|
|
|
_left = left + this.width + scaleOffsetSizeX;
|
2011-03-21 07:53:23 +00:00
|
|
|
_top = top + height/2 - scaleOffsetY;
|
2010-06-09 22:34:55 +00:00
|
|
|
ctx.fillRect(_left, _top, sizeX, sizeY);
|
|
|
|
|
|
|
|
|
|
// middle-left
|
|
|
|
|
_left = left - scaleOffsetX;
|
2011-03-21 07:53:23 +00:00
|
|
|
_top = top + height/2 - scaleOffsetY;
|
2010-06-09 22:34:55 +00:00
|
|
|
ctx.fillRect(_left, _top, sizeX, sizeY);
|
|
|
|
|
|
|
|
|
|
ctx.restore();
|
|
|
|
|
|
|
|
|
|
return this;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Clones an instance
|
|
|
|
|
* @method clone
|
|
|
|
|
* @param {Object} options object
|
2010-07-09 23:43:50 +00:00
|
|
|
* @return {fabric.Object} clone of an instance
|
2010-06-09 22:34:55 +00:00
|
|
|
*/
|
|
|
|
|
clone: function(options) {
|
|
|
|
|
if (this.constructor.fromObject) {
|
|
|
|
|
return this.constructor.fromObject(this.toObject(), options);
|
|
|
|
|
}
|
2010-07-09 23:43:50 +00:00
|
|
|
return new fabric.Object(this.toObject());
|
2010-06-09 22:34:55 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
2010-07-09 23:43:50 +00:00
|
|
|
* Creates an instance of fabric.Image out of an object
|
2010-06-09 22:34:55 +00:00
|
|
|
* @method cloneAsImage
|
|
|
|
|
* @param callback {Function} callback, invoked with an instance as a first argument
|
2010-07-09 23:43:50 +00:00
|
|
|
* @return {fabric.Object} thisArg
|
2010-06-09 22:34:55 +00:00
|
|
|
* @chainable
|
|
|
|
|
*/
|
|
|
|
|
cloneAsImage: function(callback) {
|
2010-07-09 23:43:50 +00:00
|
|
|
if (fabric.Image) {
|
2010-06-09 22:34:55 +00:00
|
|
|
var i = new Image();
|
2010-10-14 21:42:39 +00:00
|
|
|
|
|
|
|
|
/** @ignore */
|
2010-06-09 22:34:55 +00:00
|
|
|
i.onload = function() {
|
|
|
|
|
if (callback) {
|
2010-07-09 23:43:50 +00:00
|
|
|
callback(new fabric.Image(i), orig);
|
2010-06-09 22:34:55 +00:00
|
|
|
}
|
|
|
|
|
i = i.onload = null;
|
2010-10-14 21:42:39 +00:00
|
|
|
};
|
|
|
|
|
|
2010-06-09 22:34:55 +00:00
|
|
|
var orig = {
|
|
|
|
|
angle: this.get('angle'),
|
|
|
|
|
flipX: this.get('flipX'),
|
|
|
|
|
flipY: this.get('flipY')
|
2010-10-14 21:42:39 +00:00
|
|
|
};
|
2010-06-09 22:34:55 +00:00
|
|
|
|
|
|
|
|
// normalize angle
|
|
|
|
|
this.set('angle', 0).set('flipX', false).set('flipY', false);
|
|
|
|
|
i.src = this.toDataURL();
|
|
|
|
|
}
|
|
|
|
|
return this;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Converts an object into a data-url-like string
|
|
|
|
|
* @method toDataURL
|
|
|
|
|
* @return {String} string of data
|
|
|
|
|
*/
|
|
|
|
|
toDataURL: function() {
|
|
|
|
|
var el = document.createElement('canvas');
|
|
|
|
|
|
|
|
|
|
el.width = this.getWidth();
|
|
|
|
|
el.height = this.getHeight();
|
|
|
|
|
|
2010-07-10 01:50:13 +00:00
|
|
|
fabric.util.wrapElement(el, 'div');
|
2010-06-09 22:34:55 +00:00
|
|
|
|
2010-07-09 23:43:50 +00:00
|
|
|
var canvas = new fabric.Element(el);
|
2010-06-09 22:34:55 +00:00
|
|
|
canvas.backgroundColor = 'transparent';
|
|
|
|
|
canvas.renderAll();
|
|
|
|
|
|
|
|
|
|
var clone = this.clone();
|
|
|
|
|
clone.left = el.width / 2;
|
|
|
|
|
clone.top = el.height / 2;
|
|
|
|
|
|
|
|
|
|
clone.setActive(false);
|
|
|
|
|
|
|
|
|
|
canvas.add(clone);
|
|
|
|
|
var data = canvas.toDataURL('png');
|
|
|
|
|
|
|
|
|
|
canvas.dispose();
|
|
|
|
|
canvas = clone = null;
|
|
|
|
|
return data;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @method hasStateChanged
|
|
|
|
|
* @return {Boolean} true if instance' state has changed
|
|
|
|
|
*/
|
|
|
|
|
hasStateChanged: function() {
|
2010-06-17 14:00:47 +00:00
|
|
|
return this.stateProperties.some(function(prop) {
|
2010-06-09 22:34:55 +00:00
|
|
|
return this[prop] !== this.originalState[prop];
|
|
|
|
|
}, this);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @method saveState
|
2010-07-09 23:43:50 +00:00
|
|
|
* @return {fabric.Object} thisArg
|
2010-06-09 22:34:55 +00:00
|
|
|
* @chainable
|
|
|
|
|
*/
|
|
|
|
|
saveState: function() {
|
2010-06-17 14:00:47 +00:00
|
|
|
this.stateProperties.forEach(function(prop) {
|
2010-06-09 22:34:55 +00:00
|
|
|
this.originalState[prop] = this.get(prop);
|
|
|
|
|
}, this);
|
|
|
|
|
return this;
|
|
|
|
|
},
|
|
|
|
|
|
2011-02-06 07:58:32 +00:00
|
|
|
/**
|
|
|
|
|
* @method setupState
|
|
|
|
|
*/
|
|
|
|
|
setupState: function() {
|
|
|
|
|
this.originalState = { };
|
|
|
|
|
this.saveState();
|
|
|
|
|
},
|
|
|
|
|
|
2010-06-09 22:34:55 +00:00
|
|
|
/**
|
|
|
|
|
* Returns true if object intersects with an area formed by 2 points
|
|
|
|
|
* @method intersectsWithRect
|
|
|
|
|
* @param {Object} selectionTL
|
|
|
|
|
* @param {Object} selectionBR
|
|
|
|
|
* @return {Boolean}
|
|
|
|
|
*/
|
|
|
|
|
intersectsWithRect: function(selectionTL, selectionBR) {
|
|
|
|
|
var oCoords = this.oCoords,
|
2010-07-09 23:43:50 +00:00
|
|
|
tl = new fabric.Point(oCoords.tl.x, oCoords.tl.y),
|
|
|
|
|
tr = new fabric.Point(oCoords.tr.x, oCoords.tr.y),
|
|
|
|
|
bl = new fabric.Point(oCoords.bl.x, oCoords.bl.y),
|
|
|
|
|
br = new fabric.Point(oCoords.br.x, oCoords.br.y);
|
2010-06-09 22:34:55 +00:00
|
|
|
|
2010-07-09 23:43:50 +00:00
|
|
|
var intersection = fabric.Intersection.intersectPolygonRectangle(
|
2010-06-09 22:34:55 +00:00
|
|
|
[tl, tr, br, bl],
|
|
|
|
|
selectionTL,
|
|
|
|
|
selectionBR
|
|
|
|
|
);
|
|
|
|
|
return (intersection.status === 'Intersection');
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns true if object intersects with another object
|
|
|
|
|
* @method intersectsWithObject
|
|
|
|
|
* @param {Object} other Object to test
|
|
|
|
|
* @return {Boolean}
|
|
|
|
|
*/
|
|
|
|
|
intersectsWithObject: function(other) {
|
|
|
|
|
// extracts coords
|
|
|
|
|
function getCoords(oCoords) {
|
|
|
|
|
return {
|
2010-07-09 23:43:50 +00:00
|
|
|
tl: new fabric.Point(oCoords.tl.x, oCoords.tl.y),
|
|
|
|
|
tr: new fabric.Point(oCoords.tr.x, oCoords.tr.y),
|
|
|
|
|
bl: new fabric.Point(oCoords.bl.x, oCoords.bl.y),
|
|
|
|
|
br: new fabric.Point(oCoords.br.x, oCoords.br.y)
|
2010-06-09 22:34:55 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
var thisCoords = getCoords(this.oCoords),
|
|
|
|
|
otherCoords = getCoords(other.oCoords);
|
2010-07-09 23:43:50 +00:00
|
|
|
var intersection = fabric.Intersection.intersectPolygonPolygon(
|
2010-06-09 22:34:55 +00:00
|
|
|
[thisCoords.tl, thisCoords.tr, thisCoords.br, thisCoords.bl],
|
|
|
|
|
[otherCoords.tl, otherCoords.tr, otherCoords.br, otherCoords.bl]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return (intersection.status === 'Intersection');
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns true if object is fully contained within area formed by 2 points
|
|
|
|
|
* @method isContainedWithinRect
|
|
|
|
|
* @param {Object} selectionTL
|
|
|
|
|
* @param {Object} selectionBR
|
|
|
|
|
* @return {Boolean}
|
|
|
|
|
*/
|
|
|
|
|
isContainedWithinRect: function(selectionTL, selectionBR) {
|
|
|
|
|
var oCoords = this.oCoords,
|
2010-07-09 23:43:50 +00:00
|
|
|
tl = new fabric.Point(oCoords.tl.x, oCoords.tl.y),
|
|
|
|
|
tr = new fabric.Point(oCoords.tr.x, oCoords.tr.y),
|
|
|
|
|
bl = new fabric.Point(oCoords.bl.x, oCoords.bl.y),
|
|
|
|
|
br = new fabric.Point(oCoords.br.x, oCoords.br.y);
|
2010-06-09 22:34:55 +00:00
|
|
|
return tl.x > selectionTL.x
|
|
|
|
|
&& tr.x < selectionBR.x
|
|
|
|
|
&& tl.y > selectionTL.y
|
|
|
|
|
&& bl.y < selectionBR.y;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @method isType
|
|
|
|
|
* @param type {String} type to check against
|
|
|
|
|
* @return {Boolean} true if specified type is identical to the type of instance
|
|
|
|
|
*/
|
|
|
|
|
isType: function(type) {
|
|
|
|
|
return this.type === type;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Determines which one of the four corners has been clicked
|
|
|
|
|
* @method _findTargetCorner
|
|
|
|
|
* @private
|
|
|
|
|
* @param e {Event} event object
|
|
|
|
|
* @param offset {Object} canvas offset
|
|
|
|
|
* @return {String|Boolean} corner code (tl, tr, bl, br, etc.), or false if nothing is found
|
|
|
|
|
*/
|
|
|
|
|
_findTargetCorner: function(e, offset) {
|
2010-07-26 23:20:19 +00:00
|
|
|
var pointer = getPointer(e),
|
2010-06-09 22:34:55 +00:00
|
|
|
ex = pointer.x - offset.left,
|
|
|
|
|
ey = pointer.y - offset.top,
|
|
|
|
|
xpoints,
|
|
|
|
|
lines;
|
|
|
|
|
|
|
|
|
|
for (var i in this.oCoords) {
|
|
|
|
|
lines = this._getImageLines(this.oCoords[i].corner, i);
|
2011-01-23 08:30:22 +00:00
|
|
|
// debugging
|
|
|
|
|
// canvas.contextTop.fillRect(lines.bottomline.d.x, lines.bottomline.d.y, 2, 2);
|
|
|
|
|
// canvas.contextTop.fillRect(lines.bottomline.o.x, lines.bottomline.o.y, 2, 2);
|
|
|
|
|
//
|
|
|
|
|
// canvas.contextTop.fillRect(lines.leftline.d.x, lines.leftline.d.y, 2, 2);
|
|
|
|
|
// canvas.contextTop.fillRect(lines.leftline.o.x, lines.leftline.o.y, 2, 2);
|
|
|
|
|
//
|
|
|
|
|
// canvas.contextTop.fillRect(lines.topline.d.x, lines.topline.d.y, 2, 2);
|
|
|
|
|
// canvas.contextTop.fillRect(lines.topline.o.x, lines.topline.o.y, 2, 2);
|
|
|
|
|
//
|
|
|
|
|
// canvas.contextTop.fillRect(lines.rightline.d.x, lines.rightline.d.y, 2, 2);
|
|
|
|
|
// canvas.contextTop.fillRect(lines.rightline.o.x, lines.rightline.o.y, 2, 2);
|
|
|
|
|
|
2010-06-09 22:34:55 +00:00
|
|
|
xpoints = this._findCrossPoints(ex, ey, lines);
|
|
|
|
|
if (xpoints % 2 == 1 && xpoints != 0) {
|
|
|
|
|
this.__corner = i;
|
|
|
|
|
return i;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Helper method to determine how many cross points are between the 4 image edges
|
|
|
|
|
* and the horizontal line determined by the position of our mouse when clicked on canvas
|
|
|
|
|
* @method _findCrossPoints
|
|
|
|
|
* @private
|
|
|
|
|
* @param ex {Number} x coordinate of the mouse
|
|
|
|
|
* @param ey {Number} y coordinate of the mouse
|
|
|
|
|
* @param oCoords {Object} Coordinates of the image being evaluated
|
|
|
|
|
*/
|
|
|
|
|
_findCrossPoints: function(ex, ey, oCoords) {
|
|
|
|
|
var b1, b2, a1, a2, xi, yi,
|
|
|
|
|
xcount = 0,
|
|
|
|
|
iLine;
|
|
|
|
|
|
|
|
|
|
for (var lineKey in oCoords) {
|
|
|
|
|
iLine = oCoords[lineKey];
|
|
|
|
|
// optimisation 1: line below dot. no cross
|
|
|
|
|
if ((iLine.o.y < ey) && (iLine.d.y < ey)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
// optimisation 2: line above dot. no cross
|
|
|
|
|
if ((iLine.o.y >= ey) && (iLine.d.y >= ey)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
// optimisation 3: vertical line case
|
|
|
|
|
if ((iLine.o.x == iLine.d.x) && (iLine.o.x >= ex)) {
|
|
|
|
|
xi = iLine.o.x;
|
|
|
|
|
yi = ey;
|
|
|
|
|
}
|
|
|
|
|
// calculate the intersection point
|
|
|
|
|
else {
|
|
|
|
|
b1 = 0;
|
|
|
|
|
b2 = (iLine.d.y-iLine.o.y)/(iLine.d.x-iLine.o.x);
|
|
|
|
|
a1 = ey-b1*ex;
|
|
|
|
|
a2 = iLine.o.y-b2*iLine.o.x;
|
|
|
|
|
|
|
|
|
|
xi = - (a1-a2)/(b1-b2);
|
|
|
|
|
yi = a1+b1*xi;
|
|
|
|
|
}
|
|
|
|
|
// dont count xi < ex cases
|
|
|
|
|
if (xi >= ex) {
|
|
|
|
|
xcount += 1;
|
|
|
|
|
}
|
|
|
|
|
// optimisation 4: specific for square images
|
|
|
|
|
if (xcount == 2) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return xcount;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Method that returns an object with the image lines in it given the coordinates of the corners
|
|
|
|
|
* @method _getImageLines
|
|
|
|
|
* @private
|
|
|
|
|
* @param oCoords {Object} coordinates of the image corners
|
|
|
|
|
*/
|
|
|
|
|
_getImageLines: function(oCoords, i) {
|
|
|
|
|
return {
|
|
|
|
|
topline: {
|
|
|
|
|
o: oCoords.tl,
|
|
|
|
|
d: oCoords.tr
|
|
|
|
|
},
|
|
|
|
|
rightline: {
|
|
|
|
|
o: oCoords.tr,
|
|
|
|
|
d: oCoords.br
|
|
|
|
|
},
|
|
|
|
|
bottomline: {
|
|
|
|
|
o: oCoords.br,
|
|
|
|
|
d: oCoords.bl
|
|
|
|
|
},
|
|
|
|
|
leftline: {
|
|
|
|
|
o: oCoords.bl,
|
|
|
|
|
d: oCoords.tl
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Sets the coordinates of the draggable boxes in the corners of
|
|
|
|
|
* the image used to scale/rotate it.
|
|
|
|
|
* @method _setCornerCoords
|
|
|
|
|
* @private
|
|
|
|
|
*/
|
|
|
|
|
_setCornerCoords: function() {
|
|
|
|
|
var coords = this.oCoords,
|
2011-02-09 21:35:46 +00:00
|
|
|
theta = degreesToRadians(45 - this.getAngle()),
|
2011-01-23 08:30:22 +00:00
|
|
|
cornerHypotenuse = Math.sqrt(2 * Math.pow(this.cornersize, 2)) / 2,
|
|
|
|
|
cosHalfOffset = cornerHypotenuse * Math.cos(theta),
|
2011-02-09 21:35:46 +00:00
|
|
|
sinHalfOffset = cornerHypotenuse * Math.sin(theta);
|
2011-01-23 08:30:22 +00:00
|
|
|
|
2010-06-09 22:34:55 +00:00
|
|
|
coords.tl.corner = {
|
|
|
|
|
tl: {
|
2011-01-23 08:30:22 +00:00
|
|
|
x: coords.tl.x - sinHalfOffset,
|
|
|
|
|
y: coords.tl.y - cosHalfOffset
|
2010-06-09 22:34:55 +00:00
|
|
|
},
|
|
|
|
|
tr: {
|
2011-01-23 08:30:22 +00:00
|
|
|
x: coords.tl.x + cosHalfOffset,
|
|
|
|
|
y: coords.tl.y - sinHalfOffset
|
2010-06-09 22:34:55 +00:00
|
|
|
},
|
|
|
|
|
bl: {
|
2011-01-23 08:30:22 +00:00
|
|
|
x: coords.tl.x - cosHalfOffset,
|
|
|
|
|
y: coords.tl.y + sinHalfOffset
|
|
|
|
|
},
|
|
|
|
|
br: {
|
|
|
|
|
x: coords.tl.x + sinHalfOffset,
|
|
|
|
|
y: coords.tl.y + cosHalfOffset
|
2010-06-09 22:34:55 +00:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
coords.tr.corner = {
|
|
|
|
|
tl: {
|
2011-01-23 08:30:22 +00:00
|
|
|
x: coords.tr.x - sinHalfOffset,
|
|
|
|
|
y: coords.tr.y - cosHalfOffset
|
2010-06-09 22:34:55 +00:00
|
|
|
},
|
|
|
|
|
tr: {
|
2011-01-23 08:30:22 +00:00
|
|
|
x: coords.tr.x + cosHalfOffset,
|
|
|
|
|
y: coords.tr.y - sinHalfOffset
|
2010-06-09 22:34:55 +00:00
|
|
|
},
|
|
|
|
|
br: {
|
2011-01-23 08:30:22 +00:00
|
|
|
x: coords.tr.x + sinHalfOffset,
|
|
|
|
|
y: coords.tr.y + cosHalfOffset
|
|
|
|
|
},
|
|
|
|
|
bl: {
|
|
|
|
|
x: coords.tr.x - cosHalfOffset,
|
|
|
|
|
y: coords.tr.y + sinHalfOffset
|
2010-06-09 22:34:55 +00:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
coords.bl.corner = {
|
|
|
|
|
tl: {
|
2011-01-23 08:30:22 +00:00
|
|
|
x: coords.bl.x - sinHalfOffset,
|
|
|
|
|
y: coords.bl.y - cosHalfOffset
|
2010-06-09 22:34:55 +00:00
|
|
|
},
|
|
|
|
|
bl: {
|
2011-01-23 08:30:22 +00:00
|
|
|
x: coords.bl.x - cosHalfOffset,
|
|
|
|
|
y: coords.bl.y + sinHalfOffset
|
2010-06-09 22:34:55 +00:00
|
|
|
},
|
|
|
|
|
br: {
|
2011-01-23 08:30:22 +00:00
|
|
|
x: coords.bl.x + sinHalfOffset,
|
|
|
|
|
y: coords.bl.y + cosHalfOffset
|
|
|
|
|
},
|
|
|
|
|
tr: {
|
|
|
|
|
x: coords.bl.x + cosHalfOffset,
|
|
|
|
|
y: coords.bl.y - sinHalfOffset
|
2010-06-09 22:34:55 +00:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
coords.br.corner = {
|
|
|
|
|
tr: {
|
2011-01-23 08:30:22 +00:00
|
|
|
x: coords.br.x + cosHalfOffset,
|
|
|
|
|
y: coords.br.y - sinHalfOffset
|
2010-06-09 22:34:55 +00:00
|
|
|
},
|
|
|
|
|
bl: {
|
2011-01-23 08:30:22 +00:00
|
|
|
x: coords.br.x - cosHalfOffset,
|
|
|
|
|
y: coords.br.y + sinHalfOffset
|
2010-06-09 22:34:55 +00:00
|
|
|
},
|
|
|
|
|
br: {
|
2011-01-23 08:30:22 +00:00
|
|
|
x: coords.br.x + sinHalfOffset,
|
|
|
|
|
y: coords.br.y + cosHalfOffset
|
|
|
|
|
},
|
|
|
|
|
tl: {
|
|
|
|
|
x: coords.br.x - sinHalfOffset,
|
|
|
|
|
y: coords.br.y - cosHalfOffset
|
2010-06-09 22:34:55 +00:00
|
|
|
}
|
|
|
|
|
};
|
2011-01-23 08:30:22 +00:00
|
|
|
|
2010-06-09 22:34:55 +00:00
|
|
|
coords.ml.corner = {
|
|
|
|
|
tl: {
|
2011-01-23 08:30:22 +00:00
|
|
|
x: coords.ml.x - sinHalfOffset,
|
|
|
|
|
y: coords.ml.y - cosHalfOffset
|
2010-06-09 22:34:55 +00:00
|
|
|
},
|
|
|
|
|
tr: {
|
2011-01-23 08:30:22 +00:00
|
|
|
x: coords.ml.x + cosHalfOffset,
|
|
|
|
|
y: coords.ml.y - sinHalfOffset
|
2010-06-09 22:34:55 +00:00
|
|
|
},
|
|
|
|
|
bl: {
|
2011-01-23 08:30:22 +00:00
|
|
|
x: coords.ml.x - cosHalfOffset,
|
|
|
|
|
y: coords.ml.y + sinHalfOffset
|
|
|
|
|
},
|
|
|
|
|
br: {
|
|
|
|
|
x: coords.ml.x + sinHalfOffset,
|
|
|
|
|
y: coords.ml.y + cosHalfOffset
|
2010-06-09 22:34:55 +00:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
coords.mt.corner = {
|
|
|
|
|
tl: {
|
2011-01-23 08:30:22 +00:00
|
|
|
x: coords.mt.x - sinHalfOffset,
|
|
|
|
|
y: coords.mt.y - cosHalfOffset
|
2010-06-09 22:34:55 +00:00
|
|
|
},
|
|
|
|
|
tr: {
|
2011-01-23 08:30:22 +00:00
|
|
|
x: coords.mt.x + cosHalfOffset,
|
|
|
|
|
y: coords.mt.y - sinHalfOffset
|
2010-06-09 22:34:55 +00:00
|
|
|
},
|
|
|
|
|
bl: {
|
2011-01-23 08:30:22 +00:00
|
|
|
x: coords.mt.x - cosHalfOffset,
|
|
|
|
|
y: coords.mt.y + sinHalfOffset
|
|
|
|
|
},
|
|
|
|
|
br: {
|
|
|
|
|
x: coords.mt.x + sinHalfOffset,
|
|
|
|
|
y: coords.mt.y + cosHalfOffset
|
2010-06-09 22:34:55 +00:00
|
|
|
}
|
|
|
|
|
};
|
2011-01-23 08:30:22 +00:00
|
|
|
|
2010-06-09 22:34:55 +00:00
|
|
|
coords.mr.corner = {
|
|
|
|
|
tl: {
|
2011-01-23 08:30:22 +00:00
|
|
|
x: coords.mr.x - sinHalfOffset,
|
|
|
|
|
y: coords.mr.y - cosHalfOffset
|
2010-06-09 22:34:55 +00:00
|
|
|
},
|
|
|
|
|
tr: {
|
2011-01-23 08:30:22 +00:00
|
|
|
x: coords.mr.x + cosHalfOffset,
|
|
|
|
|
y: coords.mr.y - sinHalfOffset
|
2010-06-09 22:34:55 +00:00
|
|
|
},
|
|
|
|
|
bl: {
|
2011-01-23 08:30:22 +00:00
|
|
|
x: coords.mr.x - cosHalfOffset,
|
|
|
|
|
y: coords.mr.y + sinHalfOffset
|
|
|
|
|
},
|
|
|
|
|
br: {
|
|
|
|
|
x: coords.mr.x + sinHalfOffset,
|
|
|
|
|
y: coords.mr.y + cosHalfOffset
|
2010-06-09 22:34:55 +00:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
coords.mb.corner = {
|
|
|
|
|
tl: {
|
2011-01-23 08:30:22 +00:00
|
|
|
x: coords.mb.x - sinHalfOffset,
|
|
|
|
|
y: coords.mb.y - cosHalfOffset
|
2010-06-09 22:34:55 +00:00
|
|
|
},
|
|
|
|
|
tr: {
|
2011-01-23 08:30:22 +00:00
|
|
|
x: coords.mb.x + cosHalfOffset,
|
|
|
|
|
y: coords.mb.y - sinHalfOffset
|
2010-06-09 22:34:55 +00:00
|
|
|
},
|
|
|
|
|
bl: {
|
2011-01-23 08:30:22 +00:00
|
|
|
x: coords.mb.x - cosHalfOffset,
|
|
|
|
|
y: coords.mb.y + sinHalfOffset
|
|
|
|
|
},
|
|
|
|
|
br: {
|
|
|
|
|
x: coords.mb.x + sinHalfOffset,
|
|
|
|
|
y: coords.mb.y + cosHalfOffset
|
2010-06-09 22:34:55 +00:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Makes object's color grayscale
|
|
|
|
|
* @method toGrayscale
|
2010-07-09 23:43:50 +00:00
|
|
|
* @return {fabric.Object} thisArg
|
2010-06-09 22:34:55 +00:00
|
|
|
*/
|
|
|
|
|
toGrayscale: function() {
|
|
|
|
|
var fillValue = this.get('fill');
|
|
|
|
|
if (fillValue) {
|
2010-07-09 23:43:50 +00:00
|
|
|
this.set('overlayFill', new fabric.Color(fillValue).toGrayscale().toRgb());
|
2010-06-09 22:34:55 +00:00
|
|
|
}
|
|
|
|
|
return this;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @method complexity
|
|
|
|
|
* @return {Number}
|
|
|
|
|
*/
|
|
|
|
|
complexity: function() {
|
|
|
|
|
return 0;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @method getCenter
|
|
|
|
|
* @return {Object} object with `x`, `y` properties corresponding to path center coordinates
|
|
|
|
|
*/
|
|
|
|
|
getCenter: function() {
|
|
|
|
|
return {
|
|
|
|
|
x: this.get('left') + this.width / 2,
|
|
|
|
|
y: this.get('top') + this.height / 2
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @method straighten
|
2010-07-09 23:43:50 +00:00
|
|
|
* @return {fabric.Object} thisArg
|
2010-06-09 22:34:55 +00:00
|
|
|
* @chainable
|
|
|
|
|
*/
|
|
|
|
|
straighten: function() {
|
|
|
|
|
var angle = this._getAngleValueForStraighten();
|
|
|
|
|
this.setAngle(angle);
|
|
|
|
|
return this;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @method fxStraighten
|
|
|
|
|
* @param {Object} callbacks
|
|
|
|
|
* - onComplete: invoked on completion
|
|
|
|
|
* - onChange: invoked on every step of animation
|
|
|
|
|
*
|
2010-07-09 23:43:50 +00:00
|
|
|
* @return {fabric.Object} thisArg
|
2010-06-09 22:34:55 +00:00
|
|
|
* @chainable
|
|
|
|
|
*/
|
|
|
|
|
fxStraighten: function(callbacks) {
|
|
|
|
|
callbacks = callbacks || { };
|
|
|
|
|
|
2010-07-09 18:26:49 +00:00
|
|
|
var empty = function() { },
|
|
|
|
|
onComplete = callbacks.onComplete || empty,
|
|
|
|
|
onChange = callbacks.onChange || empty,
|
|
|
|
|
_this = this;
|
2010-06-09 22:34:55 +00:00
|
|
|
|
2010-07-10 01:50:13 +00:00
|
|
|
fabric.util.animate({
|
2010-07-09 18:26:49 +00:00
|
|
|
startValue: this.get('angle'),
|
|
|
|
|
endValue: this._getAngleValueForStraighten(),
|
|
|
|
|
duration: this.FX_DURATION,
|
|
|
|
|
onChange: function(value) {
|
|
|
|
|
_this.setAngle(value);
|
|
|
|
|
onChange();
|
|
|
|
|
},
|
|
|
|
|
onComplete: function() {
|
|
|
|
|
_this.setCoords();
|
|
|
|
|
onComplete();
|
|
|
|
|
},
|
|
|
|
|
onStart: function() {
|
|
|
|
|
_this.setActive(false);
|
|
|
|
|
}
|
|
|
|
|
});
|
2010-06-09 22:34:55 +00:00
|
|
|
|
|
|
|
|
return this;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @method fxRemove
|
|
|
|
|
* @param {Object} callbacks
|
2010-07-09 23:43:50 +00:00
|
|
|
* @return {fabric.Object} thisArg
|
2010-06-09 22:34:55 +00:00
|
|
|
* @chainable
|
|
|
|
|
*/
|
|
|
|
|
fxRemove: function(callbacks) {
|
2010-07-09 18:26:49 +00:00
|
|
|
callbacks || (callbacks = { });
|
|
|
|
|
|
2010-07-09 18:22:35 +00:00
|
|
|
var empty = function() { },
|
|
|
|
|
onComplete = callbacks.onComplete || empty,
|
|
|
|
|
onChange = callbacks.onChange || empty,
|
|
|
|
|
_this = this;
|
|
|
|
|
|
2010-07-10 01:50:13 +00:00
|
|
|
fabric.util.animate({
|
2010-07-09 18:22:35 +00:00
|
|
|
startValue: this.get('opacity'),
|
|
|
|
|
endValue: 0,
|
|
|
|
|
duration: this.FX_DURATION,
|
|
|
|
|
onChange: function(value) {
|
|
|
|
|
_this.set('opacity', value);
|
|
|
|
|
onChange();
|
|
|
|
|
},
|
|
|
|
|
onComplete: onComplete,
|
|
|
|
|
onStart: function() {
|
|
|
|
|
_this.setActive(false);
|
|
|
|
|
}
|
|
|
|
|
});
|
2010-06-09 22:34:55 +00:00
|
|
|
|
|
|
|
|
return this;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @method _getAngleValueForStraighten
|
|
|
|
|
* @return {Number} angle value
|
|
|
|
|
* @private
|
|
|
|
|
*/
|
|
|
|
|
_getAngleValueForStraighten: function() {
|
|
|
|
|
var angle = this.get('angle');
|
|
|
|
|
|
|
|
|
|
// TODO (kangax): can this be simplified?
|
|
|
|
|
|
|
|
|
|
if (angle > -225 && angle <= -135) { return -180; }
|
|
|
|
|
else if (angle > -135 && angle <= -45) { return -90; }
|
|
|
|
|
else if (angle > -45 && angle <= 45) { return 0; }
|
|
|
|
|
else if (angle > 45 && angle <= 135) { return 90; }
|
|
|
|
|
else if (angle > 135 && angle <= 225 ) { return 180; }
|
|
|
|
|
else if (angle > 225 && angle <= 315) { return 270; }
|
|
|
|
|
else if (angle > 315) { return 360; }
|
|
|
|
|
|
|
|
|
|
return 0;
|
2010-07-29 17:50:09 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns a JSON representation of an instance
|
|
|
|
|
* @method toJSON
|
|
|
|
|
* @return {String} json
|
|
|
|
|
*/
|
|
|
|
|
toJSON: function() {
|
|
|
|
|
// delegate, not alias
|
|
|
|
|
return this.toObject();
|
2011-01-09 06:38:54 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
setGradientFill: function(ctx, options) {
|
|
|
|
|
this.set('fill', fabric.Gradient.forObject(this, ctx, options));
|
2010-06-09 22:34:55 +00:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @alias rotate -> setAngle
|
|
|
|
|
*/
|
2010-07-09 23:43:50 +00:00
|
|
|
fabric.Object.prototype.rotate = fabric.Object.prototype.setAngle;
|
2010-10-22 02:54:00 +00:00
|
|
|
})(this);
|