konva/src/Util.js

698 lines
21 KiB
JavaScript
Raw Normal View History

(function() {
/**
* Collection constructor. Collection extends
2015-01-27 15:07:51 +08:00
* Array. This class is used in conjunction with {@link Konva.Container#get}
* @constructor
2015-01-27 15:07:51 +08:00
* @memberof Konva
*/
2015-01-27 15:07:51 +08:00
Konva.Collection = function() {
var args = [].slice.call(arguments), length = args.length, i = 0;
this.length = length;
for(; i < length; i++) {
this[i] = args[i];
}
return this;
2013-06-02 13:03:02 +08:00
};
2015-01-27 15:07:51 +08:00
Konva.Collection.prototype = [];
/**
2013-05-16 13:03:52 +08:00
* iterate through node array and run a function for each node.
* The node and index is passed into the function
* @method
2015-01-27 15:07:51 +08:00
* @memberof Konva.Collection.prototype
* @param {Function} func
* @example
* // get all nodes with name foo inside layer, and set x to 10 for each
2014-04-04 11:17:09 +08:00
* layer.get('.foo').each(function(shape, n) {
* shape.setX(10);
* });
*/
2015-01-27 15:07:51 +08:00
Konva.Collection.prototype.each = function(func) {
for(var n = 0; n < this.length; n++) {
func(this[n], n);
}
};
/**
* convert collection into an array
* @method
2015-01-27 15:07:51 +08:00
* @memberof Konva.Collection.prototype
*/
2015-01-27 15:07:51 +08:00
Konva.Collection.prototype.toArray = function() {
var arr = [],
len = this.length,
n;
for(n = 0; n < len; n++) {
arr.push(this[n]);
}
return arr;
};
/**
* convert array into a collection
* @method
2015-01-27 15:07:51 +08:00
* @memberof Konva.Collection
* @param {Array} arr
*/
2015-01-27 15:07:51 +08:00
Konva.Collection.toCollection = function(arr) {
var collection = new Konva.Collection(),
len = arr.length,
n;
for(n = 0; n < len; n++) {
collection.push(arr[n]);
}
return collection;
};
2014-02-27 08:49:18 +08:00
// map one method by it's name
2015-01-27 15:07:51 +08:00
Konva.Collection._mapMethod = function(methodName) {
Konva.Collection.prototype[methodName] = function() {
2014-02-27 08:49:18 +08:00
var len = this.length,
i;
2014-02-27 08:49:18 +08:00
var args = [].slice.call(arguments);
for(i = 0; i < len; i++) {
this[i][methodName].apply(this[i], args);
}
2014-02-27 08:49:18 +08:00
return this;
};
};
2014-01-20 13:37:13 +08:00
2015-01-27 15:07:51 +08:00
Konva.Collection.mapMethods = function(constructor) {
2014-02-27 08:49:18 +08:00
var prot = constructor.prototype;
for(var methodName in prot) {
2015-01-27 15:07:51 +08:00
Konva.Collection._mapMethod(methodName);
}
};
/*
* Last updated November 2011
* By Simon Sarris
* www.simonsarris.com
* sarris@acm.org
*
* Free to use and distribute at will
* So long as you are nice to people, etc
*/
/*
* The usage of this class was inspired by some of the work done by a forked
2015-01-27 15:07:51 +08:00
* project, KonvaJS-Ext by Wappworks, which is based on Simon's Transform
2013-05-16 13:03:52 +08:00
* class. Modified by Eric Rowell
*/
/**
* Transform constructor
* @constructor
* @param {Array} [m] Optional six-element matrix
2015-01-27 15:07:51 +08:00
* @memberof Konva
*/
2015-01-27 15:07:51 +08:00
Konva.Transform = function(m) {
this.m = (m && m.slice()) || [1, 0, 0, 1, 0, 0];
2013-06-02 13:03:02 +08:00
};
2015-01-27 15:07:51 +08:00
Konva.Transform.prototype = {
/**
2015-01-27 15:07:51 +08:00
* Copy Konva.Transform object
* @method
2015-01-27 15:07:51 +08:00
* @memberof Konva.Transform.prototype
* @returns {Konva.Transform}
*/
copy: function() {
2015-01-27 15:07:51 +08:00
return new Konva.Transform(this.m);
},
/**
* Transform point
* @method
2015-01-27 15:07:51 +08:00
* @memberof Konva.Transform.prototype
* @param {Object} point 2D point(x, y)
* @returns {Object} 2D point(x, y)
*/
point: function(point) {
var m = this.m;
return {
x: m[0] * point.x + m[2] * point.y + m[4],
y: m[1] * point.x + m[3] * point.y + m[5]
};
},
/**
* Apply translation
2013-05-16 13:03:52 +08:00
* @method
2015-01-27 15:07:51 +08:00
* @memberof Konva.Transform.prototype
* @param {Number} x
* @param {Number} y
2015-01-27 15:07:51 +08:00
* @returns {Konva.Transform}
*/
translate: function(x, y) {
this.m[4] += this.m[0] * x + this.m[2] * y;
this.m[5] += this.m[1] * x + this.m[3] * y;
return this;
},
/**
* Apply scale
2013-05-16 13:03:52 +08:00
* @method
2015-01-27 15:07:51 +08:00
* @memberof Konva.Transform.prototype
* @param {Number} sx
* @param {Number} sy
2015-01-27 15:07:51 +08:00
* @returns {Konva.Transform}
*/
scale: function(sx, sy) {
this.m[0] *= sx;
this.m[1] *= sx;
this.m[2] *= sy;
this.m[3] *= sy;
return this;
},
/**
* Apply rotation
2013-05-16 13:03:52 +08:00
* @method
2015-01-27 15:07:51 +08:00
* @memberof Konva.Transform.prototype
* @param {Number} rad Angle in radians
2015-01-27 15:07:51 +08:00
* @returns {Konva.Transform}
*/
rotate: function(rad) {
var c = Math.cos(rad);
var s = Math.sin(rad);
var m11 = this.m[0] * c + this.m[2] * s;
var m12 = this.m[1] * c + this.m[3] * s;
var m21 = this.m[0] * -s + this.m[2] * c;
var m22 = this.m[1] * -s + this.m[3] * c;
this.m[0] = m11;
this.m[1] = m12;
this.m[2] = m21;
this.m[3] = m22;
return this;
},
/**
* Returns the translation
2013-05-16 13:03:52 +08:00
* @method
2015-01-27 15:07:51 +08:00
* @memberof Konva.Transform.prototype
* @returns {Object} 2D point(x, y)
*/
getTranslation: function() {
return {
x: this.m[4],
y: this.m[5]
};
},
/**
* Apply skew
2013-05-16 13:03:52 +08:00
* @method
2015-01-27 15:07:51 +08:00
* @memberof Konva.Transform.prototype
* @param {Number} sx
* @param {Number} sy
2015-01-27 15:07:51 +08:00
* @returns {Konva.Transform}
*/
skew: function(sx, sy) {
var m11 = this.m[0] + this.m[2] * sy;
var m12 = this.m[1] + this.m[3] * sy;
var m21 = this.m[2] + this.m[0] * sx;
var m22 = this.m[3] + this.m[1] * sx;
this.m[0] = m11;
this.m[1] = m12;
this.m[2] = m21;
this.m[3] = m22;
return this;
},
/**
* Transform multiplication
2013-05-16 13:03:52 +08:00
* @method
2015-01-27 15:07:51 +08:00
* @memberof Konva.Transform.prototype
* @param {Konva.Transform} matrix
* @returns {Konva.Transform}
*/
multiply: function(matrix) {
var m11 = this.m[0] * matrix.m[0] + this.m[2] * matrix.m[1];
var m12 = this.m[1] * matrix.m[0] + this.m[3] * matrix.m[1];
var m21 = this.m[0] * matrix.m[2] + this.m[2] * matrix.m[3];
var m22 = this.m[1] * matrix.m[2] + this.m[3] * matrix.m[3];
var dx = this.m[0] * matrix.m[4] + this.m[2] * matrix.m[5] + this.m[4];
var dy = this.m[1] * matrix.m[4] + this.m[3] * matrix.m[5] + this.m[5];
this.m[0] = m11;
this.m[1] = m12;
this.m[2] = m21;
this.m[3] = m22;
this.m[4] = dx;
this.m[5] = dy;
return this;
},
/**
* Invert the matrix
2013-05-16 13:03:52 +08:00
* @method
2015-01-27 15:07:51 +08:00
* @memberof Konva.Transform.prototype
* @returns {Konva.Transform}
*/
invert: function() {
var d = 1 / (this.m[0] * this.m[3] - this.m[1] * this.m[2]);
var m0 = this.m[3] * d;
var m1 = -this.m[1] * d;
var m2 = -this.m[2] * d;
var m3 = this.m[0] * d;
var m4 = d * (this.m[2] * this.m[5] - this.m[3] * this.m[4]);
var m5 = d * (this.m[1] * this.m[4] - this.m[0] * this.m[5]);
this.m[0] = m0;
this.m[1] = m1;
this.m[2] = m2;
this.m[3] = m3;
this.m[4] = m4;
this.m[5] = m5;
return this;
},
/**
* return matrix
2013-05-16 13:03:52 +08:00
* @method
2015-01-27 15:07:51 +08:00
* @memberof Konva.Transform.prototype
*/
getMatrix: function() {
return this.m;
2013-08-12 18:00:08 +08:00
},
/**
* set to absolute position via translation
* @method
2015-01-27 15:07:51 +08:00
* @memberof Konva.Transform.prototype
* @returns {Konva.Transform}
2013-08-12 18:00:08 +08:00
* @author ericdrowell
*/
setAbsolutePosition: function(x, y) {
var m0 = this.m[0],
m1 = this.m[1],
m2 = this.m[2],
m3 = this.m[3],
m4 = this.m[4],
m5 = this.m[5],
yt = ((m0 * (y - m5)) - (m1 * (x - m4))) / ((m0 * m3) - (m1 * m2)),
xt = (x - m4 - (m2 * yt)) / m0;
return this.translate(xt, yt);
}
};
// CONSTANTS
var CONTEXT_2D = '2d',
OBJECT_ARRAY = '[object Array]',
OBJECT_NUMBER = '[object Number]',
OBJECT_STRING = '[object String]',
PI_OVER_DEG180 = Math.PI / 180,
DEG180_OVER_PI = 180 / Math.PI,
HASH = '#',
2013-05-21 14:35:05 +08:00
EMPTY_STRING = '',
ZERO = '0',
2015-01-27 15:07:51 +08:00
KINETIC_WARNING = 'Konva warning: ',
KINETIC_ERROR = 'Konva error: ',
RGB_PAREN = 'rgb(',
COLORS = {
aqua: [0,255,255],
lime: [0,255,0],
silver: [192,192,192],
black: [0,0,0],
maroon: [128,0,0],
teal: [0,128,128],
blue: [0,0,255],
navy: [0,0,128],
white: [255,255,255],
fuchsia: [255,0,255],
olive:[128,128,0],
yellow: [255,255,0],
orange: [255,165,0],
gray: [128,128,128],
purple: [128,0,128],
green: [0,128,0],
red: [255,0,0],
pink: [255,192,203],
cyan: [0,255,255],
transparent: [255,255,255,0]
},
RGB_REGEX = /rgb\((\d{1,3}),(\d{1,3}),(\d{1,3})\)/;
/**
2013-05-16 13:03:52 +08:00
* @namespace Util
2015-01-27 15:07:51 +08:00
* @memberof Konva
*/
2015-01-27 15:07:51 +08:00
Konva.Util = {
/*
* cherry-picked utilities from underscore.js
*/
_isElement: function(obj) {
return !!(obj && obj.nodeType == 1);
},
_isFunction: function(obj) {
return !!(obj && obj.constructor && obj.call && obj.apply);
},
_isObject: function(obj) {
return (!!obj && obj.constructor == Object);
},
_isArray: function(obj) {
return Object.prototype.toString.call(obj) == OBJECT_ARRAY;
},
_isNumber: function(obj) {
return Object.prototype.toString.call(obj) == OBJECT_NUMBER;
},
_isString: function(obj) {
return Object.prototype.toString.call(obj) == OBJECT_STRING;
},
// Returns a function, that, when invoked, will only be triggered at most once
// during a given window of time. Normally, the throttled function will run
// as much as it can, without ever going more than once per `wait` duration;
// but if you'd like to disable the execution on the leading edge, pass
// `{leading: false}`. To disable execution on the trailing edge, ditto.
_throttle: function(func, wait, opts) {
var context, args, result;
var timeout = null;
var previous = 0;
var options = opts || {};
var later = function() {
previous = options.leading === false ? 0 : new Date().getTime();
timeout = null;
result = func.apply(context, args);
context = args = null;
};
return function() {
var now = new Date().getTime();
if (!previous && options.leading === false) {
previous = now;
}
var remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0) {
clearTimeout(timeout);
timeout = null;
previous = now;
result = func.apply(context, args);
context = args = null;
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
return result;
};
},
/*
* other utils
*/
_hasMethods: function(obj) {
var names = [],
key;
for(key in obj) {
if(this._isFunction(obj[key])) {
names.push(key);
}
}
return names.length > 0;
},
2014-02-27 19:55:39 +08:00
createCanvasElement: function() {
2015-01-27 15:07:51 +08:00
var canvas = Konva.document.createElement('canvas');
2014-08-10 15:51:43 +08:00
// on some environments canvas.style is readonly
try {
canvas.style = canvas.style || {};
} catch (e) {
}
2014-02-27 19:55:39 +08:00
return canvas;
},
isBrowser: function() {
return (typeof exports !== 'object');
},
_isInDocument: function(el) {
while(el = el.parentNode) {
2015-01-27 15:07:51 +08:00
if(el == Konva.document) {
return true;
}
}
return false;
},
_simplifyArray: function(arr) {
var retArr = [],
len = arr.length,
2015-01-27 15:07:51 +08:00
util = Konva.Util,
n, val;
for (n=0; n<len; n++) {
val = arr[n];
if (util._isNumber(val)) {
val = Math.round(val * 1000) / 1000;
}
else if (!util._isString(val)) {
val = val.toString();
}
retArr.push(val);
}
return retArr;
},
/*
* arg can be an image object or image data
*/
_getImage: function(arg, callback) {
2014-02-27 08:49:18 +08:00
var imageObj, canvas;
// if arg is null or undefined
if(!arg) {
callback(null);
}
// if arg is already an image object
else if(this._isElement(arg)) {
callback(arg);
}
// if arg is a string, then it's a data url
else if(this._isString(arg)) {
2015-01-27 15:07:51 +08:00
imageObj = new Konva.window.Image();
imageObj.onload = function() {
callback(imageObj);
2013-06-02 13:03:02 +08:00
};
imageObj.src = arg;
}
//if arg is an object that contains the data property, it's an image object
else if(arg.data) {
2015-01-27 15:07:51 +08:00
canvas = Konva.Util.createCanvasElement();
canvas.width = arg.width;
canvas.height = arg.height;
2014-02-27 08:49:18 +08:00
var _context = canvas.getContext(CONTEXT_2D);
_context.putImageData(arg, 0, 0);
this._getImage(canvas.toDataURL(), callback);
}
else {
callback(null);
}
},
_getRGBAString: function(obj) {
var red = obj.red || 0,
green = obj.green || 0,
blue = obj.blue || 0,
alpha = obj.alpha || 1;
return [
'rgba(',
red,
',',
green,
',',
blue,
',',
alpha,
')'
].join(EMPTY_STRING);
},
_rgbToHex: function(r, g, b) {
return ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
},
2013-04-14 22:43:02 +08:00
_hexToRgb: function(hex) {
2013-05-21 14:35:05 +08:00
hex = hex.replace(HASH, EMPTY_STRING);
2013-04-14 22:43:02 +08:00
var bigint = parseInt(hex, 16);
return {
r: (bigint >> 16) & 255,
g: (bigint >> 8) & 255,
b: bigint & 255
};
},
2013-05-16 13:03:52 +08:00
/**
* return random hex color
2013-05-16 13:03:52 +08:00
* @method
2015-01-27 15:07:51 +08:00
* @memberof Konva.Util.prototype
2013-05-16 13:03:52 +08:00
*/
2013-05-06 15:17:10 +08:00
getRandomColor: function() {
var randColor = (Math.random() * 0xFFFFFF << 0).toString(16);
while (randColor.length < 6) {
2014-02-27 08:49:18 +08:00
randColor = ZERO + randColor;
}
return HASH + randColor;
},
/**
* return value with default fallback
* @method
2015-01-27 15:07:51 +08:00
* @memberof Konva.Util.prototype
*/
get: function(val, def) {
if (val === undefined) {
return def;
}
else {
return val;
}
},
2013-05-16 13:03:52 +08:00
/**
* get RGB components of a color
* @method
2015-01-27 15:07:51 +08:00
* @memberof Konva.Util.prototype
* @param {String} color
* @example
2014-04-04 11:17:09 +08:00
* // each of the following examples return {r:0, g:0, b:255}
2015-01-27 15:07:51 +08:00
* var rgb = Konva.Util.getRGB('blue');
* var rgb = Konva.Util.getRGB('#0000ff');
* var rgb = Konva.Util.getRGB('rgb(0,0,255)');
2013-05-16 13:03:52 +08:00
*/
getRGB: function(color) {
2014-02-27 08:49:18 +08:00
var rgb;
// color string
if (color in COLORS) {
rgb = COLORS[color];
return {
r: rgb[0],
g: rgb[1],
b: rgb[2]
};
}
// hex
else if (color[0] === HASH) {
return this._hexToRgb(color.substring(1));
}
// rgb string
else if (color.substr(0, 4) === RGB_PAREN) {
rgb = RGB_REGEX.exec(color.replace(/ /g,''));
return {
r: parseInt(rgb[1], 10),
g: parseInt(rgb[2], 10),
b: parseInt(rgb[3], 10)
};
}
// default
else {
return {
r: 0,
g: 0,
b: 0
};
}
},
// o1 takes precedence over o2
_merge: function(o1, o2) {
var retObj = this._clone(o2);
for(var key in o1) {
if(this._isObject(o1[key])) {
retObj[key] = this._merge(o1[key], retObj[key]);
}
else {
retObj[key] = o1[key];
}
}
return retObj;
},
2013-12-02 15:47:24 +08:00
cloneObject: function(obj) {
var retObj = {};
for(var key in obj) {
if(this._isObject(obj[key])) {
retObj[key] = this.cloneObject(obj[key]);
}
else if (this._isArray(obj[key])) {
retObj[key] = this.cloneArray(obj[key]);
} else {
retObj[key] = obj[key];
}
}
return retObj;
},
cloneArray: function(arr) {
return arr.slice(0);
},
_degToRad: function(deg) {
return deg * PI_OVER_DEG180;
},
_radToDeg: function(rad) {
return rad * DEG180_OVER_PI;
},
_capitalize: function(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
},
error: function(str) {
throw new Error(KINETIC_ERROR + str);
},
warn: function(str) {
/*
* IE9 on Windows7 64bit will throw a JS error
* if we don't use window.console in the conditional
*/
2015-01-27 15:07:51 +08:00
if(Konva.root.console && console.warn && Konva.showWarnings) {
console.warn(KINETIC_WARNING + str);
}
},
2015-01-20 18:22:51 +08:00
extend: function(child, parent) {
function ctor() {
this.constructor = child;
}
2015-01-20 18:22:51 +08:00
ctor.prototype = parent.prototype;
var old_proto = child.prototype;
child.prototype = new ctor();
for (var key in old_proto) {
if (old_proto.hasOwnProperty(key)) {
child.prototype[key] = old_proto[key];
}
}
child.__super__ = parent.prototype;
},
/**
2013-05-16 13:03:52 +08:00
* adds methods to a constructor prototype
* @method
2015-01-27 15:07:51 +08:00
* @memberof Konva.Util.prototype
* @param {Function} constructor
* @param {Object} methods
*/
addMethods: function(constructor, methods) {
2014-02-27 08:49:18 +08:00
var key;
2014-02-27 08:49:18 +08:00
for (key in methods) {
constructor.prototype[key] = methods[key];
}
},
2014-02-27 08:49:18 +08:00
_getControlPoints: function(x0, y0, x1, y1, x2, y2, t) {
var d01 = Math.sqrt(Math.pow(x1 - x0, 2) + Math.pow(y1 - y0, 2)),
d12 = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)),
fa = t * d01 / (d01 + d12),
fb = t * d12 / (d01 + d12),
p1x = x1 - fa * (x2 - x0),
p1y = y1 - fa * (y2 - y0),
p2x = x1 + fb * (x2 - x0),
p2y = y1 + fb * (y2 - y0);
return [p1x ,p1y, p2x, p2y];
},
_expandPoints: function(p, tension) {
var len = p.length,
allPoints = [],
n, cp;
for (n=2; n<len-2; n+=2) {
2015-01-27 15:07:51 +08:00
cp = Konva.Util._getControlPoints(p[n-2], p[n-1], p[n], p[n+1], p[n+2], p[n+3], tension);
allPoints.push(cp[0]);
allPoints.push(cp[1]);
allPoints.push(p[n]);
allPoints.push(p[n+1]);
allPoints.push(cp[2]);
allPoints.push(cp[3]);
}
return allPoints;
},
_removeLastLetter: function(str) {
return str.substring(0, str.length - 1);
}
};
})();