From 45bc983dfc7138644cacca54122f10f2b48d194f Mon Sep 17 00:00:00 2001 From: Eric Rowell Date: Tue, 21 Jan 2014 20:50:30 -0800 Subject: [PATCH] added dev and minified versions of kinetic to root without version in the name. updated bower build steps --- Gruntfile.js | 21 +- bower-template.json | 2 +- bower.json | 2 +- kinetic.js | 13844 ++++++++++++++++++++++ kinetic-v5.0.1.min.js => kinetic.min.js | 0 5 files changed, 13864 insertions(+), 5 deletions(-) create mode 100644 kinetic.js rename kinetic-v5.0.1.min.js => kinetic.min.js (100%) diff --git a/Gruntfile.js b/Gruntfile.js index 88f18e26..25396196 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -185,11 +185,16 @@ module.exports = function(grunt) { all: ['src/**/*.js'] }, copy: { - prod: { + prod1: { nonull: true, src: 'dist/kinetic-v<%= pkg.version %>.min.js', - dest: 'kinetic-v<%= pkg.version %>.min.js', + dest: 'kinetic.min.js', }, + prod2: { + nonull: true, + src: 'dist/kinetic-v<%= pkg.version %>.js', + dest: 'kinetic.js', + } } }; @@ -215,6 +220,16 @@ module.exports = function(grunt) { // Tasks grunt.registerTask('dev', ['clean', 'concat:dev', 'replace:dev']); grunt.registerTask('beta', ['clean', 'concat:beta', 'replace:beta']); - grunt.registerTask('full', ['clean', 'concat:prod', 'uglify', 'replace:prod1', 'replace:prod2', 'replace:prod3', 'copy', 'replace:prod4']); + grunt.registerTask('full', [ + 'clean', + 'concat:prod', + 'uglify', + 'replace:prod1', + 'replace:prod2', + 'replace:prod3', + 'replace:prod4', + 'copy:prod1', + 'copy:prod2' + ]); grunt.registerTask('hint', ['clean', 'concat:dev', 'replace:dev', 'jshint']); }; diff --git a/bower-template.json b/bower-template.json index e8ee2bde..258f34c6 100644 --- a/bower-template.json +++ b/bower-template.json @@ -18,5 +18,5 @@ "test", "tests" ], - "main": "kinetic-v@@version.min.js" + "main": "kinetic.min.js" } \ No newline at end of file diff --git a/bower.json b/bower.json index eb0b2720..23ef2f57 100644 --- a/bower.json +++ b/bower.json @@ -18,5 +18,5 @@ "test", "tests" ], - "main": "kinetic-v5.0.1.min.js" + "main": "kinetic.min.js" } \ No newline at end of file diff --git a/kinetic.js b/kinetic.js new file mode 100644 index 00000000..6ba21538 --- /dev/null +++ b/kinetic.js @@ -0,0 +1,13844 @@ +/* + * KineticJS JavaScript Framework v5.0.1 + * http://www.kineticjs.com/ + * Copyright 2013, Eric Rowell + * Licensed under the MIT or GPL Version 2 licenses. + * Date: 2014-01-21 + * + * Copyright (C) 2011 - 2013 by Eric Rowell + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +/** + * @namespace Kinetic + */ +var Kinetic = {}; +(function() { + Kinetic = { + // public + version: '5.0.1', + + // private + stages: [], + idCounter: 0, + ids: {}, + names: {}, + shapes: {}, + listenClickTap: false, + inDblClickWindow: false, + + // configurations + enableTrace: false, + traceArrMax: 100, + dblClickWindow: 400, + pixelRatio: undefined, + + // user agent + UA: (function() { + var ua = navigator.userAgent.toLowerCase(), + // jQuery UA regex + match = /(chrome)[ \/]([\w.]+)/.exec( ua ) || + /(webkit)[ \/]([\w.]+)/.exec( ua ) || + /(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua ) || + /(msie) ([\w.]+)/.exec( ua ) || + ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua ) || + []; + + return { + browser: match[ 1 ] || '', + version: match[ 2 ] || '0' + }; + })(), + + /** + * @namespace Filters + * @memberof Kinetic + */ + Filters: {}, + + /** + * Node constructor. Nodes are entities that can be transformed, layered, + * and have bound events. The stage, layers, groups, and shapes all extend Node. + * @constructor + * @memberof Kinetic + * @abstract + * @param {Object} config + * @param {Number} [config.x] + * @param {Number} [config.y] + * @param {Number} [config.width] + * @param {Number} [config.height] + * @param {Boolean} [config.visible] + * @param {Boolean} [config.listening] whether or not the node is listening for events + * @param {String} [config.id] unique id + * @param {String} [config.name] non-unique name + * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 + * @param {Object} [config.scale] set scale + * @param {Number} [config.scaleX] set scale x + * @param {Number} [config.scaleY] set scale y + * @param {Number} [config.rotation] rotation in degrees + * @param {Object} [config.offset] offset from center point and rotation point + * @param {Number} [config.offsetX] set offset x + * @param {Number} [config.offsetY] set offset y + * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop + * the entire stage by dragging any portion of the stage + * @param {Function} [config.dragBoundFunc] + */ + Node: function(config) { + this._init(config); + }, + + /** + * Shape constructor. Shapes are primitive objects such as rectangles, + * circles, text, lines, etc. + * @constructor + * @memberof Kinetic + * @augments Kinetic.Node + * @param {Object} config + * @param {String} [config.fill] fill color + * @param {Integer} [config.fillRed] set fill red component + * @param {Integer} [config.fillGreen] set fill green component + * @param {Integer} [config.fillBlue] set fill blue component + * @param {Integer} [config.fillAlpha] set fill alpha component + * @param {Image} [config.fillPatternImage] fill pattern image + * @param {Number} [config.fillPatternX] + * @param {Number} [config.fillPatternY] + * @param {Object} [config.fillPatternOffset] object with x and y component + * @param {Number} [config.fillPatternOffsetX] + * @param {Number} [config.fillPatternOffsetY] + * @param {Object} [config.fillPatternScale] object with x and y component + * @param {Number} [config.fillPatternScaleX] + * @param {Number} [config.fillPatternScaleY] + * @param {Number} [config.fillPatternRotation] + * @param {String} [config.fillPatternRepeat] can be "repeat", "repeat-x", "repeat-y", or "no-repeat". The default is "no-repeat" + * @param {Object} [config.fillLinearGradientStartPoint] object with x and y component + * @param {Number} [config.fillLinearGradientStartPointX] + * @param {Number} [config.fillLinearGradientStartPointY] + * @param {Object} [config.fillLinearGradientEndPoint] object with x and y component + * @param {Number} [config.fillLinearGradientEndPointX] + * @param {Number} [config.fillLinearGradientEndPointY] + * @param {Array} [config.fillLinearGradientColorStops] array of color stops + * @param {Object} [config.fillRadialGradientStartPoint] object with x and y component + * @param {Number} [config.fillRadialGradientStartPointX] + * @param {Number} [config.fillRadialGradientStartPointY] + * @param {Object} [config.fillRadialGradientEndPoint] object with x and y component + * @param {Number} [config.fillRadialGradientEndPointX] + * @param {Number} [config.fillRadialGradientEndPointY] + * @param {Number} [config.fillRadialGradientStartRadius] + * @param {Number} [config.fillRadialGradientEndRadius] + * @param {Array} [config.fillRadialGradientColorStops] array of color stops + * @param {Boolean} [config.fillEnabled] flag which enables or disables the fill. The default value is true + * @param {String} [config.fillPriority] can be color, linear-gradient, radial-graident, or pattern. The default value is color. The fillPriority property makes it really easy to toggle between different fill types. For example, if you want to toggle between a fill color style and a fill pattern style, simply set the fill property and the fillPattern properties, and then use setFillPriority('color') to render the shape with a color fill, or use setFillPriority('pattern') to render the shape with the pattern fill configuration + * @param {String} [config.stroke] stroke color + * @param {Integer} [config.strokeRed] set stroke red component + * @param {Integer} [config.strokeGreen] set stroke green component + * @param {Integer} [config.strokeBlue] set stroke blue component + * @param {Integer} [config.strokeAlpha] set stroke alpha component + * @param {Number} [config.strokeWidth] stroke width + * @param {Boolean} [config.strokeScaleEnabled] flag which enables or disables stroke scale. The default is true + * @param {Boolean} [config.strokeEnabled] flag which enables or disables the stroke. The default value is true + * @param {String} [config.lineJoin] can be miter, round, or bevel. The default + * is miter + * @param {String} [config.lineCap] can be butt, round, or sqare. The default + * is butt + * @param {String} [config.shadowColor] + * @param {Integer} [config.shadowColorRed] set shadow color red component + * @param {Integer} [config.shadowColorGreen] set shadow color green component + * @param {Integer} [config.shadowColorBlue] set shadow color blue component + * @param {Integer} [config.shadowColorAlpha] set shadow color alpha component + * @param {Number} [config.shadowBlur] + * @param {Object} [config.shadowOffset] object with x and y component + * @param {Number} [config.shadowOffsetX] + * @param {Number} [config.shadowOffsetY] + * @param {Number} [config.shadowOpacity] shadow opacity. Can be any real number + * between 0 and 1 + * @param {Boolean} [config.shadowEnabled] flag which enables or disables the shadow. The default value is true + * @param {Array} [config.dash] + * @param {Boolean} [config.dashEnabled] flag which enables or disables the dashArray. The default value is true + * @param {Number} [config.x] + * @param {Number} [config.y] + * @param {Number} [config.width] + * @param {Number} [config.height] + * @param {Boolean} [config.visible] + * @param {Boolean} [config.listening] whether or not the node is listening for events + * @param {String} [config.id] unique id + * @param {String} [config.name] non-unique name + * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 + * @param {Object} [config.scale] set scale + * @param {Number} [config.scaleX] set scale x + * @param {Number} [config.scaleY] set scale y + * @param {Number} [config.rotation] rotation in degrees + * @param {Object} [config.offset] offset from center point and rotation point + * @param {Number} [config.offsetX] set offset x + * @param {Number} [config.offsetY] set offset y + * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop + * the entire stage by dragging any portion of the stage + * @param {Function} [config.dragBoundFunc] + * @example + * var customShape = new Kinetic.Shape({
+ * x: 5,
+ * y: 10,
+ * fill: 'red',
+ * // a Kinetic.Canvas renderer is passed into the drawFunc function
+ * drawFunc: function(context) {
+ * context.beginPath();
+ * context.moveTo(200, 50);
+ * context.lineTo(420, 80);
+ * context.quadraticCurveTo(300, 100, 260, 170);
+ * context.closePath();
+ * context.fillStrokeShape(this);
+ * }
+ *}); + */ + Shape: function(config) { + this.__init(config); + }, + + /** + * Container constructor.  Containers are used to contain nodes or other containers + * @constructor + * @memberof Kinetic + * @augments Kinetic.Node + * @abstract + * @param {Object} config + * @param {Number} [config.x] + * @param {Number} [config.y] + * @param {Number} [config.width] + * @param {Number} [config.height] + * @param {Boolean} [config.visible] + * @param {Boolean} [config.listening] whether or not the node is listening for events + * @param {String} [config.id] unique id + * @param {String} [config.name] non-unique name + * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 + * @param {Object} [config.scale] set scale + * @param {Number} [config.scaleX] set scale x + * @param {Number} [config.scaleY] set scale y + * @param {Number} [config.rotation] rotation in degrees + * @param {Object} [config.offset] offset from center point and rotation point + * @param {Number} [config.offsetX] set offset x + * @param {Number} [config.offsetY] set offset y + * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop + * the entire stage by dragging any portion of the stage + * @param {Function} [config.dragBoundFunc] + * @param {Function} [config.clipFunc] clipping function + + */ + Container: function(config) { + this.__init(config); + }, + + /** + * Stage constructor. A stage is used to contain multiple layers + * @constructor + * @memberof Kinetic + * @augments Kinetic.Container + * @param {Object} config + * @param {String|DomElement} config.container Container id or DOM element + * @param {Number} [config.x] + * @param {Number} [config.y] + * @param {Number} [config.width] + * @param {Number} [config.height] + * @param {Boolean} [config.visible] + * @param {Boolean} [config.listening] whether or not the node is listening for events + * @param {String} [config.id] unique id + * @param {String} [config.name] non-unique name + * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 + * @param {Object} [config.scale] set scale + * @param {Number} [config.scaleX] set scale x + * @param {Number} [config.scaleY] set scale y + * @param {Number} [config.rotation] rotation in degrees + * @param {Object} [config.offset] offset from center point and rotation point + * @param {Number} [config.offsetX] set offset x + * @param {Number} [config.offsetY] set offset y + * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop + * the entire stage by dragging any portion of the stage + * @param {Function} [config.dragBoundFunc] + * @param {Function} [config.clipFunc] clipping function + + * @example + * var stage = new Kinetic.Stage({
+ * width: 500,
+ * height: 800,
+ * container: 'containerId'
+ * }); + */ + Stage: function(config) { + this.___init(config); + }, + + /** + * Layer constructor. Layers are tied to their own canvas element and are used + * to contain groups or shapes + * @constructor + * @memberof Kinetic + * @augments Kinetic.Container + * @param {Object} config + * @param {Boolean} [config.clearBeforeDraw] set this property to false if you don't want + * to clear the canvas before each layer draw. The default value is true. + * @param {Number} [config.x] + * @param {Number} [config.y] + * @param {Number} [config.width] + * @param {Number} [config.height] + * @param {Boolean} [config.visible] + * @param {Boolean} [config.listening] whether or not the node is listening for events + * @param {String} [config.id] unique id + * @param {String} [config.name] non-unique name + * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 + * @param {Object} [config.scale] set scale + * @param {Number} [config.scaleX] set scale x + * @param {Number} [config.scaleY] set scale y + * @param {Number} [config.rotation] rotation in degrees + * @param {Object} [config.offset] offset from center point and rotation point + * @param {Number} [config.offsetX] set offset x + * @param {Number} [config.offsetY] set offset y + * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop + * the entire stage by dragging any portion of the stage + * @param {Function} [config.dragBoundFunc] + * @param {Function} [config.clipFunc] clipping function + + * @example + * var layer = new Kinetic.Layer(); + */ + Layer: function(config) { + this.___init(config); + }, + + /** + * Group constructor. Groups are used to contain shapes or other groups. + * @constructor + * @memberof Kinetic + * @augments Kinetic.Container + * @param {Object} config + * @param {Number} [config.x] + * @param {Number} [config.y] + * @param {Number} [config.width] + * @param {Number} [config.height] + * @param {Boolean} [config.visible] + * @param {Boolean} [config.listening] whether or not the node is listening for events + * @param {String} [config.id] unique id + * @param {String} [config.name] non-unique name + * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 + * @param {Object} [config.scale] set scale + * @param {Number} [config.scaleX] set scale x + * @param {Number} [config.scaleY] set scale y + * @param {Number} [config.rotation] rotation in degrees + * @param {Object} [config.offset] offset from center point and rotation point + * @param {Number} [config.offsetX] set offset x + * @param {Number} [config.offsetY] set offset y + * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop + * the entire stage by dragging any portion of the stage + * @param {Function} [config.dragBoundFunc] + * @param {Function} [config.clipFunc] clipping function + + * @example + * var group = new Kinetic.Group(); + */ + Group: function(config) { + this.___init(config); + }, + + /** + * returns whether or not drag and drop is currently active + * @method + * @memberof Kinetic + */ + isDragging: function() { + var dd = Kinetic.DD; + + // if DD is not included with the build, then + // drag and drop is not even possible + if (!dd) { + return false; + } + // if DD is included with the build + else { + return dd.isDragging; + } + }, + /** + * returns whether or not a drag and drop operation is ready, but may + * not necessarily have started + * @method + * @memberof Kinetic + */ + isDragReady: function() { + var dd = Kinetic.DD; + + // if DD is not included with the build, then + // drag and drop is not even possible + if (!dd) { + return false; + } + // if DD is included with the build + else { + return !!dd.node; + } + }, + _addId: function(node, id) { + if(id !== undefined) { + this.ids[id] = node; + } + }, + _removeId: function(id) { + if(id !== undefined) { + delete this.ids[id]; + } + }, + _addName: function(node, name) { + if(name !== undefined) { + if(this.names[name] === undefined) { + this.names[name] = []; + } + this.names[name].push(node); + } + }, + _removeName: function(name, _id) { + if(name !== undefined) { + var nodes = this.names[name]; + if(nodes !== undefined) { + for(var n = 0; n < nodes.length; n++) { + var no = nodes[n]; + if(no._id === _id) { + nodes.splice(n, 1); + } + } + if(nodes.length === 0) { + delete this.names[name]; + } + } + } + } + }; +})(); + +// Uses Node, AMD or browser globals to create a module. + +// If you want something that will work in other stricter CommonJS environments, +// or if you need to create a circular dependency, see commonJsStrict.js + +// Defines a module "returnExports" that depends another module called "b". +// Note that the name of the module is implied by the file name. It is best +// if the file name and the exported global have matching names. + +// If the 'b' module also uses this type of boilerplate, then +// in the browser, it will create a global .b that is used below. + +// If you do not want to support the browser global path, then you +// can remove the `root` use and the passing `this` as the first arg to +// the top function. + +// if the module has no dependencies, the above pattern can be simplified to +( function(root, factory) { + if( typeof exports === 'object') { + // Node. Does not work with strict CommonJS, but + // only CommonJS-like enviroments that support module.exports, + // like Node. + module.exports = factory(); + } + else if( typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(factory); + } + else { + // Browser globals (root is window) + root.returnExports = factory(); + } +}(this, function() { + + // Just return a value to define the module export. + // This example returns an object, but the module + // can return a function as the exported value. + return Kinetic; +})); +;(function() { + /** + * Collection constructor. Collection extends + * Array. This class is used in conjunction with {@link Kinetic.Container#get} + * @constructor + * @memberof Kinetic + */ + Kinetic.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; + }; + Kinetic.Collection.prototype = []; + /** + * iterate through node array and run a function for each node. + * The node and index is passed into the function + * @method + * @memberof Kinetic.Collection.prototype + * @param {Function} func + * @example + * // get all nodes with name foo inside layer, and set x to 10 for each + * layer.get('.foo').each(function(shape, n) {
+ * shape.setX(10);
+ * }); + */ + Kinetic.Collection.prototype.each = function(func) { + for(var n = 0; n < this.length; n++) { + func(this[n], n); + } + }; + /** + * convert collection into an array + * @method + * @memberof Kinetic.Collection.prototype + */ + Kinetic.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 + * @memberof Kinetic.Collection + * @param {Array} arr + */ + Kinetic.Collection.toCollection = function(arr) { + var collection = new Kinetic.Collection(), + len = arr.length, + n; + + for(n = 0; n < len; n++) { + collection.push(arr[n]); + } + return collection; + }; + + Kinetic.Collection.mapMethods = function(constructor) { + var prot = constructor.prototype, + key; + + for(key in prot) { + // induce scope + (function(methodName) { + Kinetic.Collection.prototype[methodName] = function() { + var len = this.length, + i; + + args = [].slice.call(arguments); + for(i = 0; i < len; i++) { + this[i][methodName].apply(this[i], args); + } + + return this; + }; + })(key); + } + }; + + /* + * 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 + * project, KineticJS-Ext by Wappworks, which is based on Simon's Transform + * class. Modified by Eric Rowell + */ + + /** + * Transform constructor + * @constructor + * @memberof Kinetic + */ + Kinetic.Transform = function() { + this.m = [1, 0, 0, 1, 0, 0]; + }; + + Kinetic.Transform.prototype = { + /** + * Apply translation + * @method + * @memberof Kinetic.Transform.prototype + * @param {Number} x + * @param {Number} y + */ + 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; + }, + /** + * Apply scale + * @method + * @memberof Kinetic.Transform.prototype + * @param {Number} sx + * @param {Number} sy + */ + scale: function(sx, sy) { + this.m[0] *= sx; + this.m[1] *= sx; + this.m[2] *= sy; + this.m[3] *= sy; + }, + /** + * Apply rotation + * @method + * @memberof Kinetic.Transform.prototype + * @param {Number} rad Angle in radians + */ + 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; + }, + /** + * Returns the translation + * @method + * @memberof Kinetic.Transform.prototype + * @returns {Object} 2D point(x, y) + */ + getTranslation: function() { + return { + x: this.m[4], + y: this.m[5] + }; + }, + /** + * Apply skew + * @method + * @memberof Kinetic.Transform.prototype + * @param {Number} sx + * @param {Number} sy + */ + 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; + }, + /** + * Transform multiplication + * @method + * @memberof Kinetic.Transform.prototype + * @param {Kinetic.Transform} matrix + */ + 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; + }, + /** + * Invert the matrix + * @method + * @memberof Kinetic.Transform.prototype + */ + 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 matrix + * @method + * @memberof Kinetic.Transform.prototype + */ + getMatrix: function() { + return this.m; + }, + /** + * set to absolute position via translation + * @method + * @memberof Kinetic.Transform.prototype + * @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; + + this.translate(xt, yt); + } + }; + + // CONSTANTS + var CANVAS = 'canvas', + 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 = '#', + EMPTY_STRING = '', + ZERO = '0', + KINETIC_WARNING = 'Kinetic warning: ', + KINETIC_ERROR = 'Kinetic 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})\)/; + + /** + * @namespace Util + * @memberof Kinetic + */ + Kinetic.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; + }, + /* + * other utils + */ + _hasMethods: function(obj) { + var names = [], + key; + + for(key in obj) { + if(this._isFunction(obj[key])) { + names.push(key); + } + } + return names.length > 0; + }, + _isInDocument: function(el) { + while(el = el.parentNode) { + if(el == document) { + return true; + } + } + return false; + }, + _simplifyArray: function(arr) { + var retArr = [], + len = arr.length, + util = Kinetic.Util, + n, val; + + for (n=0; n> 16) & 255, + g: (bigint >> 8) & 255, + b: bigint & 255 + }; + }, + /** + * return random hex color + * @method + * @memberof Kinetic.Util.prototype + */ + getRandomColor: function() { + var randColor = (Math.random() * 0xFFFFFF << 0).toString(16); + while (randColor.length < 6) { + randColor = ZERO + randColor; + } + return HASH + randColor; + }, + /** + * return value with default fallback + * @method + * @memberof Kinetic.Util.prototype + */ + get: function(val, def) { + if (val === undefined) { + return def; + } + else { + return val; + } + }, + /** + * get RGB components of a color + * @method + * @memberof Kinetic.Util.prototype + * @param {String} color + * @example + * // each of the following examples return {r:0, g:0, b:255}
+ * var rgb = Kinetic.Util.getRGB('blue');
+ * var rgb = Kinetic.Util.getRGB('#0000ff');
+ * var rgb = Kinetic.Util.getRGB('rgb(0,0,255)'); + */ + getRGB: function(color) { + 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; + }, + cloneObject: function(obj) { + var retObj = {}; + for(var key in obj) { + if(this._isObject(obj[key])) { + retObj[key] = this._clone(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 + */ + if(window.console && console.warn) { + console.warn(KINETIC_WARNING + str); + } + }, + extend: function(c1, c2) { + for(var key in c2.prototype) { + if(!( key in c1.prototype)) { + c1.prototype[key] = c2.prototype[key]; + } + } + }, + /** + * adds methods to a constructor prototype + * @method + * @memberof Kinetic.Util.prototype + * @param {Function} constructor + * @param {Object} methods + */ + addMethods: function(constructor, methods) { + var key; + + for (key in methods) { + constructor.prototype[key] = methods[key]; + } + }, + _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= Kinetic.traceArrMax) { + traceArr.shift(); + } + }, + /** + * reset canvas context transform + * @method + * @memberof Kinetic.Context.prototype + */ + reset: function() { + var pixelRatio = this.getCanvas().getPixelRatio(); + this.setTransform(1 * pixelRatio, 0, 0, 1 * pixelRatio, 0, 0); + }, + /** + * get canvas + * @method + * @memberof Kinetic.Context.prototype + * @returns {Kinetic.Canvas} + */ + getCanvas: function() { + return this.canvas; + }, + /** + * clear canvas + * @method + * @memberof Kinetic.Context.prototype + * @param {Object} [bounds] + * @param {Number} [bounds.x] + * @param {Number} [bounds.y] + * @param {Number} [bounds.width] + * @param {Number} [bounds.height] + */ + clear: function(bounds) { + var canvas = this.getCanvas(), + pos, size; + + if (bounds) { + this.clearRect(bounds.x || 0, bounds.y || 0, bounds.width || 0, bounds.height || 0); + } + else { + this.clearRect(0, 0, canvas.getWidth(), canvas.getHeight()); + } + }, + _applyLineCap: function(shape) { + var lineCap = shape.getLineCap(); + if(lineCap) { + this.setAttr('lineCap', lineCap); + } + }, + _applyOpacity: function(shape) { + var absOpacity = shape.getAbsoluteOpacity(); + if(absOpacity !== 1) { + this.setAttr('globalAlpha', absOpacity); + } + }, + _applyLineJoin: function(shape) { + var lineJoin = shape.getLineJoin(); + if(lineJoin) { + this.setAttr('lineJoin', lineJoin); + } + }, + _applyTransform: function(shape) { + var transformsEnabled = shape.getTransformsEnabled(), + m; + + if (transformsEnabled === 'all') { + m = shape.getAbsoluteTransform().getMatrix(); + this.transform(m[0], m[1], m[2], m[3], m[4], m[5]); + } + else if (transformsEnabled === 'position') { + // best performance for position only transforms + this.translate(shape.getX(), shape.getY()); + } + }, + setAttr: function(attr, val) { + this._context[attr] = val; + }, + + // context pass through methods + arc: function() { + var a = arguments; + this._context.arc(a[0], a[1], a[2], a[3], a[4], a[5]); + }, + beginPath: function() { + this._context.beginPath(); + }, + bezierCurveTo: function() { + var a = arguments; + this._context.bezierCurveTo(a[0], a[1], a[2], a[3], a[4], a[5]); + }, + clearRect: function() { + var a = arguments; + this._context.clearRect(a[0], a[1], a[2], a[3]); + }, + clip: function() { + this._context.clip(); + }, + closePath: function() { + this._context.closePath(); + }, + createImageData: function() { + var a = arguments; + if(a.length === 2) { + return this._context.createImageData(a[0], a[1]); + } + else if(a.length === 1) { + return this._context.createImageData(a[0]); + } + }, + createLinearGradient: function() { + var a = arguments; + return this._context.createLinearGradient(a[0], a[1], a[2], a[3]); + }, + createPattern: function() { + var a = arguments; + return this._context.createPattern(a[0], a[1]); + }, + createRadialGradient: function() { + var a = arguments; + return this._context.createRadialGradient(a[0], a[1], a[2], a[3], a[4], a[5]); + }, + drawImage: function() { + var a = arguments, + _context = this._context; + + if(a.length === 3) { + _context.drawImage(a[0], a[1], a[2]); + } + else if(a.length === 5) { + _context.drawImage(a[0], a[1], a[2], a[3], a[4]); + } + else if(a.length === 9) { + _context.drawImage(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8]); + } + }, + fill: function() { + this._context.fill(); + }, + fillText: function() { + var a = arguments; + this._context.fillText(a[0], a[1], a[2]); + }, + getImageData: function() { + var a = arguments; + return this._context.getImageData(a[0], a[1], a[2], a[3]); + }, + lineTo: function() { + var a = arguments; + this._context.lineTo(a[0], a[1]); + }, + moveTo: function() { + var a = arguments; + this._context.moveTo(a[0], a[1]); + }, + rect: function() { + var a = arguments; + this._context.rect(a[0], a[1], a[2], a[3]); + }, + putImageData: function() { + var a = arguments; + this._context.putImageData(a[0], a[1], a[2]); + }, + quadraticCurveTo: function() { + var a = arguments; + this._context.quadraticCurveTo(a[0], a[1], a[2], a[3]); + }, + restore: function() { + this._context.restore(); + }, + rotate: function() { + var a = arguments; + this._context.rotate(a[0]); + }, + save: function() { + this._context.save(); + }, + scale: function() { + var a = arguments; + this._context.scale(a[0], a[1]); + }, + setLineDash: function() { + var a = arguments, + _context = this._context; + + // works for Chrome and IE11 + if(this._context.setLineDash) { + _context.setLineDash(a[0]); + } + // verified that this works in firefox + else if('mozDash' in _context) { + _context.mozDash = a[0]; + } + // does not currently work for Safari + else if('webkitLineDash' in _context) { + _context.webkitLineDash = a[0]; + } + + // no support for IE9 and IE10 + }, + setTransform: function() { + var a = arguments; + this._context.setTransform(a[0], a[1], a[2], a[3], a[4], a[5]); + }, + stroke: function() { + this._context.stroke(); + }, + strokeText: function() { + var a = arguments; + this._context.strokeText(a[0], a[1], a[2]); + }, + transform: function() { + var a = arguments; + this._context.transform(a[0], a[1], a[2], a[3], a[4], a[5]); + }, + translate: function() { + var a = arguments; + this._context.translate(a[0], a[1]); + }, + _enableTrace: function() { + var that = this, + len = CONTEXT_METHODS.length, + _simplifyArray = Kinetic.Util._simplifyArray, + origSetter = this.setAttr, + n, args; + + // methods + for (n=0; n 255) { + return 255; + } + else if (val < 0) { + return 0; + } + else { + return Math.round(val); + } + }, + alphaComponent: function(val) { + if (val > 1) { + return 1; + } + // chrome does not honor alpha values of 0 + else if (val < 0.0001) { + return 0.0001; + } + else { + return val; + } + } + }; +})();;(function() { + // CONSTANTS + var ABSOLUTE_OPACITY = 'absoluteOpacity', + ABSOLUTE_TRANSFORM = 'absoluteTransform', + BEFORE = 'before', + CHANGE = 'Change', + CHILDREN = 'children', + DOT = '.', + EMPTY_STRING = '', + GET = 'get', + ID = 'id', + KINETIC = 'kinetic', + LISTENING = 'listening', + //LISTENING_ENABLED = 'listeningEnabled', + MOUSEENTER = 'mouseenter', + MOUSELEAVE = 'mouseleave', + NAME = 'name', + SET = 'set', + SHAPE = 'Shape', + SPACE = ' ', + STAGE = 'stage', + TRANSFORM = 'transform', + UPPER_STAGE = 'Stage', + VISIBLE = 'visible', + + TRANSFORM_CHANGE_STR = [ + 'xChange.kinetic', + 'yChange.kinetic', + 'scaleXChange.kinetic', + 'scaleYChange.kinetic', + 'skewXChange.kinetic', + 'skewYChange.kinetic', + 'rotationChange.kinetic', + 'offsetXChange.kinetic', + 'offsetYChange.kinetic', + 'transformsEnabledChange.kinetic' + ].join(SPACE); + + Kinetic.Util.addMethods(Kinetic.Node, { + _init: function(config) { + var that = this; + this._id = Kinetic.idCounter++; + this.eventListeners = {}; + this.attrs = {}; + this._cache = {}; + this._filterUpToDate = false; + this.setAttrs(config); + + // event bindings for cache handling + this.on(TRANSFORM_CHANGE_STR, function() { + this._clearCache(TRANSFORM); + that._clearSelfAndDescendantCache(ABSOLUTE_TRANSFORM); + }); + this.on('visibleChange.kinetic', function() { + that._clearSelfAndDescendantCache(VISIBLE); + }); + this.on('listeningChange.kinetic', function() { + that._clearSelfAndDescendantCache(LISTENING); + }); + this.on('opacityChange.kinetic', function() { + that._clearSelfAndDescendantCache(ABSOLUTE_OPACITY); + }); + }, + _clearCache: function(attr){ + if (attr) { + delete this._cache[attr]; + } + else { + this._cache = {}; + } + }, + _getCache: function(attr, privateGetter){ + var cache = this._cache[attr]; + + // if not cached, we need to set it using the private getter method. + if (cache === undefined) { + this._cache[attr] = privateGetter.call(this); + } + + return this._cache[attr]; + }, + /* + * when the logic for a cached result depends on ancestor propagation, use this + * method to clear self and children cache + */ + _clearSelfAndDescendantCache: function(attr) { + this._clearCache(attr); + + if (this.children) { + this.getChildren().each(function(node) { + node._clearSelfAndDescendantCache(attr); + }); + } + }, + /** + * clear cached canvas + * @method + * @memberof Kinetic.Node.prototype + * @returns {Kinetic.Node} + * @example + * node.clearCache(); + */ + clearCache: function() { + delete this._cache.canvas; + this._filterUpToDate = false; + return this; + }, + /** + * cache node to improve drawing performance, apply filters, or create more accurate + * hit regions + * @method + * @memberof Kinetic.Node.prototype + * @param {Object} config + * @param {Number} [config.x] + * @param {Number} [config.y] + * @param {Number} [config.width] + * @param {Number} [config.height] + * @param {Boolean} [config.showBorder] when set to true, a red border will be drawn around the cached + * region for debugging purposes + * @returns {Kinetic.Node} + * @example + * // cache a shape with the x,y position of the bounding box at the center and
+ * // the width and height of the bounding box equal to the width and height of
+ * // the shape obtained from shape.width() and shape.height()
+ * image.cache();

+ * + * // cache a node and define the bounding box position and size
+ * node.cache({
+ * x: -30,
+ * y: -30,
+ * width: 100,
+ * height: 200
+ * });

+ * + * // cache a node and draw a red border around the bounding box
+ * // for debugging purposes
+ * node.cache({
+ * x: -30,
+ * y: -30,
+ * width: 100,
+ * height: 200,
+ * drawBorder: true
+ * }); + */ + cache: function(config) { + var conf = config || {}, + x = conf.x || 0, + y = conf.y || 0, + width = conf.width || this.width(), + height = conf.height || this.height(), + drawBorder = conf.drawBorder || false, + cachedSceneCanvas = new Kinetic.SceneCanvas({ + pixelRatio: 1, + width: width, + height: height + }), + cachedFilterCanvas = new Kinetic.SceneCanvas({ + pixelRatio: 1, + width: width, + height: height + }), + cachedHitCanvas = new Kinetic.HitCanvas({ + width: width, + height: height + }), + origTransEnabled = this.transformsEnabled(), + origX = this.x(), + origY = this.y(), + sceneContext; + + this.clearCache(); + + this.transformsEnabled('position'); + this.x(x * -1); + this.y(y * -1); + + this.drawScene(cachedSceneCanvas); + this.drawHit(cachedHitCanvas); + + // this will draw a red border around the cached box for + // debugging purposes + if (drawBorder) { + sceneContext = cachedSceneCanvas.getContext(); + sceneContext.save(); + sceneContext.beginPath(); + sceneContext.rect(0, 0, width, height); + sceneContext.closePath(); + sceneContext.setAttr('strokeStyle', 'red'); + sceneContext.setAttr('lineWidth', 5); + sceneContext.stroke(); + sceneContext.restore(); + } + + this.x(origX); + this.y(origY); + this.transformsEnabled(origTransEnabled); + + this._cache.canvas = { + scene: cachedSceneCanvas, + filter: cachedFilterCanvas, + hit: cachedHitCanvas + }; + + return this; + }, + _drawCachedSceneCanvas: function(context) { + context.save(); + context._applyTransform(this); + context.drawImage(this._getCachedSceneCanvas()._canvas, 0, 0); + context.restore(); + }, + _getCachedSceneCanvas: function() { + var filters = this.filters(), + cachedCanvas = this._cache.canvas, + sceneCanvas = cachedCanvas.scene, + filterCanvas = cachedCanvas.filter, + filterContext = filterCanvas.getContext(), + len, imageData, n, filter; + + if (filters) { + if (!this._filterUpToDate) { + try { + len = filters.length; + filterContext.clear(); + // copy cached canvas onto filter context + filterContext.drawImage(sceneCanvas._canvas, 0, 0); + imageData = filterContext.getImageData(0, 0, filterCanvas.getWidth(), filterCanvas.getHeight()); + + // apply filters to filter context + for (n=0; n + * node.on('click', function() {
+ * console.log('you clicked me!');
+ * });

+ * + * // get the target node
+ * node.on('click', function(evt) {
+ * console.log(evt.targetNode);
+ * });

+ * + * // stop event propagation
+ * node.on('click', function(evt) {
+ * evt.cancelBubble = true;
+ * });

+ * + * // bind multiple listeners
+ * node.on('click touchstart', function() {
+ * console.log('you clicked/touched me!');
+ * });

+ * + * // namespace listener
+ * node.on('click.foo', function() {
+ * console.log('you clicked/touched me!');
+ * }); + */ + on: function(evtStr, handler) { + var events = evtStr.split(SPACE), + len = events.length, + n, event, parts, baseEvent, name; + + /* + * loop through types and attach event listeners to + * each one. eg. 'click mouseover.namespace mouseout' + * will create three event bindings + */ + for(n = 0; n < len; n++) { + event = events[n]; + parts = event.split(DOT); + baseEvent = parts[0]; + name = parts[1] || EMPTY_STRING; + + // create events array if it doesn't exist + if(!this.eventListeners[baseEvent]) { + this.eventListeners[baseEvent] = []; + } + + this.eventListeners[baseEvent].push({ + name: name, + handler: handler + }); + + // NOTE: this flag is set to true when any event handler is added, even non + // mouse or touch gesture events. This improves performance for most + // cases where users aren't using events, but is still very light weight. + // To ensure perfect accuracy, devs can explicitly set listening to false. + /* + if (name !== KINETIC) { + this._listeningEnabled = true; + this._clearSelfAndAncestorCache(LISTENING_ENABLED); + } + */ + } + + return this; + }, + /** + * remove event bindings from the node. Pass in a string of + * event types delimmited by a space to remove multiple event + * bindings at once such as 'mousedown mouseup mousemove'. + * include a namespace to remove an event binding by name + * such as 'click.foobar'. If you only give a name like '.foobar', + * all events in that namespace will be removed. + * @method + * @memberof Kinetic.Node.prototype + * @param {String} evtStr e.g. 'click', 'mousedown touchstart', '.foobar' + * @returns {Kinetic.Node} + * @example + * // remove listener
+ * node.off('click');

+ * + * // remove multiple listeners
+ * node.off('click touchstart');

+ * + * // remove listener by name
+ * node.off('click.foo'); + */ + off: function(evtStr) { + var events = evtStr.split(SPACE), + len = events.length, + n, t, event, parts, baseEvent, name; + + for(n = 0; n < len; n++) { + event = events[n]; + parts = event.split(DOT); + baseEvent = parts[0]; + name = parts[1]; + + if(baseEvent) { + if(this.eventListeners[baseEvent]) { + this._off(baseEvent, name); + } + } + else { + for(t in this.eventListeners) { + this._off(t, name); + } + } + } + return this; + }, + /** + * remove self from parent, but don't destroy + * @method + * @memberof Kinetic.Node.prototype + * @returns {Kinetic.Node} + * @example + * node.remove(); + */ + remove: function() { + var parent = this.getParent(); + + if(parent && parent.children) { + parent.children.splice(this.index, 1); + parent._setChildrenIndices(); + delete this.parent; + } + + // every cached attr that is calculated via node tree + // traversal must be cleared when removing a node + this._clearSelfAndDescendantCache(STAGE); + this._clearSelfAndDescendantCache(ABSOLUTE_TRANSFORM); + this._clearSelfAndDescendantCache(VISIBLE); + this._clearSelfAndDescendantCache(LISTENING); + this._clearSelfAndDescendantCache(ABSOLUTE_OPACITY); + + return this; + }, + /** + * remove and destroy self + * @method + * @memberof Kinetic.Node.prototype + * @example + * node.destroy(); + */ + destroy: function() { + // remove from ids and names hashes + Kinetic._removeId(this.getId()); + Kinetic._removeName(this.getName(), this._id); + + this.remove(); + }, + /** + * get attr + * @method + * @memberof Kinetic.Node.prototype + * @param {String} attr + * @returns {Integer|String|Object|Array} + * @example + * var x = node.getAttr('x'); + */ + getAttr: function(attr) { + var method = GET + Kinetic.Util._capitalize(attr); + if(Kinetic.Util._isFunction(this[method])) { + return this[method](); + } + // otherwise get directly + else { + return this.attrs[attr]; + } + }, + /** + * get ancestors + * @method + * @memberof Kinetic.Node.prototype + * @returns {Kinetic.Collection} + * @example + * shape.getAncestors().each(function(node) { + * console.log(node.getId()); + * }) + */ + getAncestors: function() { + var parent = this.getParent(), + ancestors = new Kinetic.Collection(); + + while (parent) { + ancestors.push(parent); + parent = parent.getParent(); + } + + return ancestors; + }, + /** + * get attrs object literal + * @method + * @memberof Kinetic.Node.prototype + * @returns {Object} + */ + getAttrs: function() { + return this.attrs || {}; + }, + /** + * set multiple attrs at once using an object literal + * @method + * @memberof Kinetic.Node.prototype + * @param {Object} config object containing key value pairs + * @returns {Kinetic.Node} + * @example + * node.setAttrs({
+ * x: 5,
+ * fill: 'red'
+ * });
+ */ + setAttrs: function(config) { + var key, method; + + if(config) { + for(key in config) { + if (key === CHILDREN) { + + } + else { + method = SET + Kinetic.Util._capitalize(key); + // use setter if available + if(Kinetic.Util._isFunction(this[method])) { + this[method](config[key]); + } + // otherwise set directly + else { + this._setAttr(key, config[key]); + } + } + } + } + return this; + }, + /** + * determine if node is listening for events by taking into account ancestors. + * + * Parent | Self | isListening + * listening | listening | + * ----------+-----------+------------ + * T | T | T + * T | F | F + * F | T | T + * F | F | F + * ----------+-----------+------------ + * T | I | T + * F | I | F + * I | I | T + * + * @method + * @memberof Kinetic.Node.prototype + * @returns {Boolean} + */ + isListening: function() { + return this._getCache(LISTENING, this._isListening); + }, + _isListening: function() { + var listening = this.getListening(), + parent = this.getParent(); + + // the following conditions are a simplification of the truth table above. + // please modify carefully + if (listening === 'inherit') { + if (parent) { + return parent.isListening(); + } + else { + return true; + } + } + else { + return listening; + } + }, + /** + * determine if node is visible by taking into account ancestors. + * + * Parent | Self | isVisible + * visible | visible | + * ----------+-----------+------------ + * T | T | T + * T | F | F + * F | T | T + * F | F | F + * ----------+-----------+------------ + * T | I | T + * F | I | F + * I | I | T + + * @method + * @memberof Kinetic.Node.prototype + * @returns {Boolean} + */ + isVisible: function() { + return this._getCache(VISIBLE, this._isVisible); + }, + _isVisible: function() { + var visible = this.getVisible(), + parent = this.getParent(); + + // the following conditions are a simplification of the truth table above. + // please modify carefully + if (visible === 'inherit') { + if (parent) { + return parent.isVisible(); + } + else { + return true; + } + } + else { + return visible; + } + }, + /** + * determine if listening is enabled by taking into account descendants. If self or any children + * have _isListeningEnabled set to true, then self also has listening enabled. + * @method + * @memberof Kinetic.Node.prototype + * @returns {Boolean} + */ + shouldDrawHit: function() { + var layer = this.getLayer(); + return layer && layer.hitGraphEnabled() && this.isListening() && this.isVisible() && !Kinetic.isDragging(); + }, + /** + * show node + * @method + * @memberof Kinetic.Node.prototype + * @returns {Kinetic.Node} + */ + show: function() { + this.setVisible(true); + return this; + }, + /** + * hide node. Hidden nodes are no longer detectable + * @method + * @memberof Kinetic.Node.prototype + * @returns {Kinetic.Node} + */ + hide: function() { + this.setVisible(false); + return this; + }, + /** + * get zIndex relative to the node's siblings who share the same parent + * @method + * @memberof Kinetic.Node.prototype + * @returns {Integer} + */ + getZIndex: function() { + return this.index || 0; + }, + /** + * get absolute z-index which takes into account sibling + * and ancestor indices + * @method + * @memberof Kinetic.Node.prototype + * @returns {Integer} + */ + getAbsoluteZIndex: function() { + var depth = this.getDepth(), + that = this, + index = 0, + nodes, len, n, child; + + function addChildren(children) { + nodes = []; + len = children.length; + for(n = 0; n < len; n++) { + child = children[n]; + index++; + + if(child.nodeType !== SHAPE) { + nodes = nodes.concat(child.getChildren().toArray()); + } + + if(child._id === that._id) { + n = len; + } + } + + if(nodes.length > 0 && nodes[0].getDepth() <= depth) { + addChildren(nodes); + } + } + if(that.nodeType !== UPPER_STAGE) { + addChildren(that.getStage().getChildren()); + } + + return index; + }, + /** + * get node depth in node tree. Returns an integer.

+ * e.g. Stage depth will always be 0. Layers will always be 1. Groups and Shapes will always + * be >= 2 + * @method + * @memberof Kinetic.Node.prototype + * @returns {Integer} + */ + getDepth: function() { + var depth = 0, + parent = this.parent; + + while(parent) { + depth++; + parent = parent.parent; + } + return depth; + }, + setPosition: function(pos) { + this.setX(pos.x); + this.setY(pos.y); + return this; + }, + getPosition: function() { + return { + x: this.getX(), + y: this.getY() + }; + }, + /** + * get absolute position relative to the top left corner of the stage container div + * @method + * @memberof Kinetic.Node.prototype + * @returns {Object} + */ + getAbsolutePosition: function() { + var absoluteMatrix = this.getAbsoluteTransform().getMatrix(), + absoluteTransform = new Kinetic.Transform(), + offset = this.offset(); + + // clone the matrix array + absoluteTransform.m = absoluteMatrix.slice(); + absoluteTransform.translate(offset.x, offset.y); + + return absoluteTransform.getTranslation(); + }, + /** + * set absolute position + * @method + * @memberof Kinetic.Node.prototype + * @param {Object} pos + * @param {Number} pos.x + * @param {Number} pos.y + * @returns {Kinetic.Node} + */ + setAbsolutePosition: function(pos) { + var origTrans = this._clearTransform(), + it; + + // don't clear translation + this.attrs.x = origTrans.x; + this.attrs.y = origTrans.y; + delete origTrans.x; + delete origTrans.y; + + // unravel transform + it = this.getAbsoluteTransform(); + + it.invert(); + it.translate(pos.x, pos.y); + pos = { + x: this.attrs.x + it.getTranslation().x, + y: this.attrs.y + it.getTranslation().y + }; + + this.setPosition({x:pos.x, y:pos.y}); + this._setTransform(origTrans); + + return this; + }, + _setTransform: function(trans) { + var key; + + for(key in trans) { + this.attrs[key] = trans[key]; + } + + this._clearCache(TRANSFORM); + this._clearSelfAndDescendantCache(ABSOLUTE_TRANSFORM); + }, + _clearTransform: function() { + var trans = { + x: this.getX(), + y: this.getY(), + rotation: this.getRotation(), + scaleX: this.getScaleX(), + scaleY: this.getScaleY(), + offsetX: this.getOffsetX(), + offsetY: this.getOffsetY(), + skewX: this.getSkewX(), + skewY: this.getSkewY() + }; + + this.attrs.x = 0; + this.attrs.y = 0; + this.attrs.rotation = 0; + this.attrs.scaleX = 1; + this.attrs.scaleY = 1; + this.attrs.offsetX = 0; + this.attrs.offsetY = 0; + this.attrs.skewX = 0; + this.attrs.skewY = 0; + + this._clearCache(TRANSFORM); + this._clearSelfAndDescendantCache(ABSOLUTE_TRANSFORM); + + // return original transform + return trans; + }, + /** + * move node by an amount relative to its current position + * @method + * @memberof Kinetic.Node.prototype + * @param {Object} change + * @param {Number} change.x + * @param {Number} change.y + * @returns {Kinetic.Node} + * @example + * // move node in x direction by 1px and y direction by 2px
+ * node.move({
+ * x: 1,
+ * y: 2)
+ * }); + */ + move: function(change) { + var changeX = change.x, + changeY = change.y, + x = this.getX(), + y = this.getY(); + + if(changeX !== undefined) { + x += changeX; + } + + if(changeY !== undefined) { + y += changeY; + } + + this.setPosition({x:x, y:y}); + return this; + }, + _eachAncestorReverse: function(func, includeSelf) { + var family = [], + parent = this.getParent(), + len, n; + + // build family by traversing ancestors + if(includeSelf) { + family.unshift(this); + } + while(parent) { + family.unshift(parent); + parent = parent.parent; + } + + len = family.length; + for(n = 0; n < len; n++) { + func(family[n]); + } + }, + /** + * rotate node by an amount in degrees relative to its current rotation + * @method + * @memberof Kinetic.Node.prototype + * @param {Number} theta + * @returns {Kinetic.Node} + */ + rotate: function(theta) { + this.setRotation(this.getRotation() + theta); + return this; + }, + /** + * move node to the top of its siblings + * @method + * @memberof Kinetic.Node.prototype + * @returns {Boolean} + */ + moveToTop: function() { + var index = this.index; + this.parent.children.splice(index, 1); + this.parent.children.push(this); + this.parent._setChildrenIndices(); + return true; + }, + /** + * move node up + * @method + * @memberof Kinetic.Node.prototype + * @returns {Boolean} + */ + moveUp: function() { + var index = this.index, + len = this.parent.getChildren().length; + if(index < len - 1) { + this.parent.children.splice(index, 1); + this.parent.children.splice(index + 1, 0, this); + this.parent._setChildrenIndices(); + return true; + } + return false; + }, + /** + * move node down + * @method + * @memberof Kinetic.Node.prototype + * @returns {Boolean} + */ + moveDown: function() { + var index = this.index; + if(index > 0) { + this.parent.children.splice(index, 1); + this.parent.children.splice(index - 1, 0, this); + this.parent._setChildrenIndices(); + return true; + } + return false; + }, + /** + * move node to the bottom of its siblings + * @method + * @memberof Kinetic.Node.prototype + * @returns {Boolean} + */ + moveToBottom: function() { + var index = this.index; + if(index > 0) { + this.parent.children.splice(index, 1); + this.parent.children.unshift(this); + this.parent._setChildrenIndices(); + return true; + } + return false; + }, + /** + * set zIndex relative to siblings + * @method + * @memberof Kinetic.Node.prototype + * @param {Integer} zIndex + * @returns {Kinetic.Node} + */ + setZIndex: function(zIndex) { + var index = this.index; + this.parent.children.splice(index, 1); + this.parent.children.splice(zIndex, 0, this); + this.parent._setChildrenIndices(); + return this; + }, + /** + * get absolute opacity + * @method + * @memberof Kinetic.Node.prototype + * @returns {Number} + */ + getAbsoluteOpacity: function() { + return this._getCache(ABSOLUTE_OPACITY, this._getAbsoluteOpacity); + }, + _getAbsoluteOpacity: function() { + var absOpacity = this.getOpacity(); + if(this.getParent()) { + absOpacity *= this.getParent().getAbsoluteOpacity(); + } + return absOpacity; + }, + /** + * move node to another container + * @method + * @memberof Kinetic.Node.prototype + * @param {Container} newContainer + * @returns {Kinetic.Node} + * @example + * // move node from current layer into layer2
+ * node.moveTo(layer2); + */ + moveTo: function(newContainer) { + Kinetic.Node.prototype.remove.call(this); + newContainer.add(this); + return this; + }, + /** + * convert Node into an object for serialization. Returns an object. + * @method + * @memberof Kinetic.Node.prototype + * @returns {Object} + */ + toObject: function() { + var type = Kinetic.Util, + obj = {}, + attrs = this.getAttrs(), + key, val, getter, defaultValue; + + obj.attrs = {}; + + // serialize only attributes that are not function, image, DOM, or objects with methods + for(key in attrs) { + val = attrs[key]; + if (!type._isFunction(val) && !type._isElement(val) && !(type._isObject(val) && type._hasMethods(val))) { + getter = this[key]; + // remove attr value so that we can extract the default value from the getter + delete attrs[key]; + defaultValue = getter ? getter.call(this) : null; + // restore attr value + attrs[key] = val; + if (defaultValue !== val) { + obj.attrs[key] = val; + } + } + } + + obj.className = this.getClassName(); + return obj; + }, + /** + * convert Node into a JSON string. Returns a JSON string. + * @method + * @memberof Kinetic.Node.prototype + * @returns {String}} + */ + toJSON: function() { + return JSON.stringify(this.toObject()); + }, + /** + * get parent container + * @method + * @memberof Kinetic.Node.prototype + * @returns {Kinetic.Node} + */ + getParent: function() { + return this.parent; + }, + /** + * get layer ancestor + * @method + * @memberof Kinetic.Node.prototype + * @returns {Kinetic.Layer} + */ + getLayer: function() { + var parent = this.getParent(); + return parent ? parent.getLayer() : null; + }, + /** + * get stage ancestor + * @method + * @memberof Kinetic.Node.prototype + * @returns {Kinetic.Stage} + */ + getStage: function() { + return this._getCache(STAGE, this._getStage); + }, + _getStage: function() { + var parent = this.getParent(); + if(parent) { + return parent.getStage(); + } + else { + return undefined; + } + }, + /** + * fire event + * @method + * @memberof Kinetic.Node.prototype + * @param {String} eventType event type. can be a regular event, like click, mouseover, or mouseout, or it can be a custom event, like myCustomEvent + * @param {EventObject} [evt] event object + * @param {Boolean} [bubble] setting the value to false, or leaving it undefined, will result in the event + * not bubbling. Setting the value to true will result in the event bubbling. + * @returns {Kinetic.Node} + * @example + * // manually fire click event
+ * node.fire('click');

+ * + * // fire custom event
+ * node.fire('foo');

+ * + * // fire custom event with custom event object
+ * node.fire('foo', {
+ * bar: 10
+ * });

+ * + * // fire click event that bubbles
+ * node.fire('click', null, true); + */ + fire: function(eventType, evt, bubble) { + // bubble + if (bubble) { + this._fireAndBubble(eventType, evt || {}); + } + // no bubble + else { + this._fire(eventType, evt || {}); + } + return this; + }, + /** + * get absolute transform of the node which takes into + * account its ancestor transforms + * @method + * @memberof Kinetic.Node.prototype + * @returns {Kinetic.Transform} + */ + getAbsoluteTransform: function() { + return this._getCache(ABSOLUTE_TRANSFORM, this._getAbsoluteTransform); + }, + _getAbsoluteTransform: function() { + var at = new Kinetic.Transform(), + transformsEnabled, trans; + + // start with stage and traverse downwards to self + this._eachAncestorReverse(function(node) { + transformsEnabled = node.transformsEnabled(); + trans = node.getTransform(); + + if (transformsEnabled === 'all') { + at.multiply(trans); + } + else if (transformsEnabled === 'position') { + at.translate(node.x(), node.y()); + } + }, true); + return at; + }, + /** + * get transform of the node + * @method + * @memberof Kinetic.Node.prototype + * @returns {Kinetic.Transform} + */ + getTransform: function() { + return this._getCache(TRANSFORM, this._getTransform); + }, + _getTransform: function() { + var m = new Kinetic.Transform(), + x = this.getX(), + y = this.getY(), + rotation = this.getRotation() * Math.PI / 180, + scaleX = this.getScaleX(), + scaleY = this.getScaleY(), + skewX = this.getSkewX(), + skewY = this.getSkewY(), + offsetX = this.getOffsetX(), + offsetY = this.getOffsetY(); + + if(x !== 0 || y !== 0) { + m.translate(x, y); + } + if(rotation !== 0) { + m.rotate(rotation); + } + if(skewX !== 0 || skewY !== 0) { + m.skew(skewX, skewY); + } + if(scaleX !== 1 || scaleY !== 1) { + m.scale(scaleX, scaleY); + } + if(offsetX !== 0 || offsetY !== 0) { + m.translate(-1 * offsetX, -1 * offsetY); + } + + return m; + }, + /** + * clone node. Returns a new Node instance with identical attributes. You can also override + * the node properties with an object literal, enabling you to use an existing node as a template + * for another node + * @method + * @memberof Kinetic.Node.prototype + * @param {Object} attrs override attrs + * @returns {Kinetic.Node} + * @example + * // simple clone
+ * var clone = node.clone();

+ * + * // clone a node and override the x position
+ * var clone = rect.clone({
+ * x: 5
+ * }); + */ + clone: function(obj) { + // instantiate new node + var className = this.getClassName(), + node = new Kinetic[className](this.attrs), + key, allListeners, len, n, listener; + + // copy over listeners + for(key in this.eventListeners) { + allListeners = this.eventListeners[key]; + len = allListeners.length; + for(n = 0; n < len; n++) { + listener = allListeners[n]; + /* + * don't include kinetic namespaced listeners because + * these are generated by the constructors + */ + if(listener.name.indexOf(KINETIC) < 0) { + // if listeners array doesn't exist, then create it + if(!node.eventListeners[key]) { + node.eventListeners[key] = []; + } + node.eventListeners[key].push(listener); + } + } + } + + // apply attr overrides + node.setAttrs(obj); + return node; + }, + /** + * Creates a composite data URL. If MIME type is not + * specified, then "image/png" will result. For "image/jpeg", specify a quality + * level as quality (range 0.0 - 1.0) + * @method + * @memberof Kinetic.Node.prototype + * @param {Object} config + * @param {String} [config.mimeType] can be "image/png" or "image/jpeg". + * "image/png" is the default + * @param {Number} [config.x] x position of canvas section + * @param {Number} [config.y] y position of canvas section + * @param {Number} [config.width] width of canvas section + * @param {Number} [config.height] height of canvas section + * @param {Number} [config.quality] jpeg quality. If using an "image/jpeg" mimeType, + * you can specify the quality from 0 to 1, where 0 is very poor quality and 1 + * is very high quality + * @returns {String} + */ + toDataURL: function(config) { + config = config || {}; + + var mimeType = config.mimeType || null, + quality = config.quality || null, + stage = this.getStage(), + x = config.x || 0, + y = config.y || 0, + canvas = new Kinetic.SceneCanvas({ + width: config.width || this.getWidth() || (stage ? stage.getWidth() : 0), + height: config.height || this.getHeight() || (stage ? stage.getHeight() : 0), + pixelRatio: 1 + }), + context = canvas.getContext(); + + context.save(); + + if(x || y) { + context.translate(-1 * x, -1 * y); + } + + this.drawScene(canvas); + context.restore(); + + return canvas.toDataURL(mimeType, quality); + }, + /** + * converts node into an image. Since the toImage + * method is asynchronous, a callback is required. toImage is most commonly used + * to cache complex drawings as an image so that they don't have to constantly be redrawn + * @method + * @memberof Kinetic.Node.prototype + * @param {Object} config + * @param {Function} config.callback function executed when the composite has completed + * @param {String} [config.mimeType] can be "image/png" or "image/jpeg". + * "image/png" is the default + * @param {Number} [config.x] x position of canvas section + * @param {Number} [config.y] y position of canvas section + * @param {Number} [config.width] width of canvas section + * @param {Number} [config.height] height of canvas section + * @param {Number} [config.quality] jpeg quality. If using an "image/jpeg" mimeType, + * you can specify the quality from 0 to 1, where 0 is very poor quality and 1 + * is very high quality + * @example + * var image = node.toImage({
+ * callback: function(img) {
+ * // do stuff with img
+ * }
+ * }); + */ + toImage: function(config) { + Kinetic.Util._getImage(this.toDataURL(config), function(img) { + config.callback(img); + }); + }, + /** + * set size + * @method + * @memberof Kinetic.Node.prototype + * @param {Object} size + * @param {Number} width + * @param {Number} height + * @returns {Kinetic.Node} + */ + setSize: function(size) { + this.setWidth(size.width); + this.setHeight(size.height); + return this; + }, + /** + * get size + * @method + * @memberof Kinetic.Node.prototype + * @returns {Object} + */ + getSize: function() { + return { + width: this.getWidth(), + height: this.getHeight() + }; + }, + /** + * get width + * @method + * @memberof Kinetic.Node.prototype + * @returns {Integer} + */ + getWidth: function() { + return this.attrs.width || 0; + }, + /** + * get height + * @method + * @memberof Kinetic.Node.prototype + * @returns {Integer} + */ + getHeight: function() { + return this.attrs.height || 0; + }, + /** + * get class name, which may return Stage, Layer, Group, or shape class names like Rect, Circle, Text, etc. + * @method + * @memberof Kinetic.Node.prototype + * @returns {String} + */ + getClassName: function() { + return this.className || this.nodeType; + }, + /** + * get the node type, which may return Stage, Layer, Group, or Node + * @method + * @memberof Kinetic.Node.prototype + * @returns {String} + */ + getType: function() { + return this.nodeType; + }, + _get: function(selector) { + return this.nodeType === selector ? [this] : []; + }, + _off: function(type, name) { + var evtListeners = this.eventListeners[type], + i, evtName; + + for(i = 0; i < evtListeners.length; i++) { + evtName = evtListeners[i].name; + // the following two conditions must be true in order to remove a handler: + // 1) the current event name cannot be kinetic unless the event name is kinetic + // this enables developers to force remove a kinetic specific listener for whatever reason + // 2) an event name is not specified, or if one is specified, it matches the current event name + if((evtName !== 'kinetic' || name === 'kinetic') && (!name || evtName === name)) { + evtListeners.splice(i, 1); + if(evtListeners.length === 0) { + delete this.eventListeners[type]; + break; + } + i--; + } + } + }, + _fireBeforeChangeEvent: function(attr, oldVal, newVal) { + this._fire([BEFORE, Kinetic.Util._capitalize(attr), CHANGE].join(EMPTY_STRING), { + oldVal: oldVal, + newVal: newVal + }); + }, + _fireChangeEvent: function(attr, oldVal, newVal) { + this._fire(attr + CHANGE, { + oldVal: oldVal, + newVal: newVal + }); + }, + /** + * set id + * @method + * @memberof Kinetic.Node.prototype + * @param {String} id + * @returns {Kinetic.Node} + */ + setId: function(id) { + var oldId = this.getId(); + + Kinetic._removeId(oldId); + Kinetic._addId(this, id); + this._setAttr(ID, id); + return this; + }, + setName: function(name) { + var oldName = this.getName(); + + Kinetic._removeName(oldName, this._id); + Kinetic._addName(this, name); + this._setAttr(NAME, name); + return this; + }, + /** + * set attr + * @method + * @memberof Kinetic.Node.prototype + * @param {String} attr + * @param {*} val + * @returns {Kinetic.Node} + * @example + * node.setAttr('x', 5); + */ + setAttr: function() { + var args = Array.prototype.slice.call(arguments), + attr = args[0], + val = args[1], + method = SET + Kinetic.Util._capitalize(attr), + func = this[method]; + + if(Kinetic.Util._isFunction(func)) { + func.call(this, val); + } + // otherwise set directly + else { + this._setAttr(attr, val); + } + return this; + }, + _setAttr: function(key, val) { + var oldVal; + if(val !== undefined) { + oldVal = this.attrs[key]; + this.attrs[key] = val; + this._fireChangeEvent(key, oldVal, val); + } + }, + _setComponentAttr: function(key, component, val) { + var oldVal; + if(val !== undefined) { + oldVal = this.attrs[key]; + + if (!oldVal) { + // set value to default value using getAttr + this.attrs[key] = this.getAttr(key); + } + + //this._fireBeforeChangeEvent(key, oldVal, val); + this.attrs[key][component] = val; + this._fireChangeEvent(key, oldVal, val); + } + }, + _fireAndBubble: function(eventType, evt, compareShape) { + var okayToRun = true; + + if(evt && this.nodeType === SHAPE) { + evt.targetNode = this; + } + + if(eventType === MOUSEENTER && compareShape && this._id === compareShape._id) { + okayToRun = false; + } + else if(eventType === MOUSELEAVE && compareShape && this._id === compareShape._id) { + okayToRun = false; + } + + if(okayToRun) { + this._fire(eventType, evt); + + // simulate event bubbling + if(evt && !evt.cancelBubble && this.parent) { + if(compareShape && compareShape.parent) { + this._fireAndBubble.call(this.parent, eventType, evt, compareShape.parent); + } + else { + this._fireAndBubble.call(this.parent, eventType, evt); + } + } + } + }, + _fire: function(eventType, evt) { + var events = this.eventListeners[eventType], + i; + + if (events) { + for(i = 0; i < events.length; i++) { + events[i].handler.call(this, evt); + } + } + }, + /** + * draw both scene and hit graphs. If the node being drawn is the stage, all of the layers will be cleared and redrawn + * @method + * @memberof Kinetic.Node.prototype + * @returns {Kinetic.Node} + */ + draw: function() { + this.drawScene(); + this.drawHit(); + return this; + } + }); + + /** + * create node with JSON string. De-serializtion does not generate custom + * shape drawing functions, images, or event handlers (this would make the + * serialized object huge). If your app uses custom shapes, images, and + * event handlers (it probably does), then you need to select the appropriate + * shapes after loading the stage and set these properties via on(), setDrawFunc(), + * and setImage() methods + * @method + * @memberof Kinetic.Node + * @param {String} JSON string + * @param {DomElement} [container] optional container dom element used only if you're + * creating a stage node + */ + Kinetic.Node.create = function(json, container) { + return this._createNode(JSON.parse(json), container); + }; + Kinetic.Node._createNode = function(obj, container) { + var className = Kinetic.Node.prototype.getClassName.call(obj), + children = obj.children, + no, len, n; + + // if container was passed in, add it to attrs + if(container) { + obj.attrs.container = container; + } + + no = new Kinetic[className](obj.attrs); + if(children) { + len = children.length; + for(n = 0; n < len; n++) { + no.add(this._createNode(children[n])); + } + } + + return no; + }; + + + // =========================== add getters setters =========================== + + Kinetic.Factory.addOverloadedGetterSetter(Kinetic.Node, 'position'); + /** + * get/set node position relative to parent + * @name position + * @method + * @memberof Kinetic.Node.prototype + * @param {Object} pos + * @param {Number} pos.x + * @param {Nubmer} pos.y + * @returns {Object} + * @example + * // get position
+ * var position = node.position();

+ * + * // set position
+ * node.position({
+ * x: 5
+ * y: 10
+ * }); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Node, 'x', 0); + + /** + * get/set x position + * @name x + * @method + * @memberof Kinetic.Node.prototype + * @param {Number} x + * @returns {Object} + * @example + * // get x
+ * var x = node.x();

+ * + * // set x
+ * node.x(5); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Node, 'y', 0); + + /** + * get/set y position + * @name y + * @method + * @memberof Kinetic.Node.prototype + * @param {Number} y + * @returns {Integer} + * @example + * // get y
+ * var y = node.y();

+ * + * // set y
+ * node.y(5); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Node, 'opacity', 1); + + /** + * get/set opacity. Opacity values range from 0 to 1. + * A node with an opacity of 0 is fully transparent, and a node + * with an opacity of 1 is fully opaque + * @name opacity + * @method + * @memberof Kinetic.Node.prototype + * @param {Object} opacity + * @returns {Number} + * @example + * // get opacity
+ * var opacity = node.opacity();

+ * + * // set opacity
+ * node.opacity(0.5); + */ + + Kinetic.Factory.addGetter(Kinetic.Node, 'name'); + Kinetic.Factory.addOverloadedGetterSetter(Kinetic.Node, 'name'); + + /** + * get/set name + * @name name + * @method + * @memberof Kinetic.Node.prototype + * @param {String} name + * @returns {String} + * @example + * // get name
+ * var name = node.name();

+ * + * // set name
+ * node.name('foo'); + */ + + Kinetic.Factory.addGetter(Kinetic.Node, 'id'); + Kinetic.Factory.addOverloadedGetterSetter(Kinetic.Node, 'id'); + + /** + * get/set id + * @name id + * @method + * @memberof Kinetic.Node.prototype + * @param {String} id + * @returns {String} + * @example + * // get id
+ * var name = node.id();

+ * + * // set id
+ * node.id('foo'); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Node, 'rotation', 0); + + /** + * get/set rotation in degrees + * @name rotation + * @method + * @memberof Kinetic.Node.prototype + * @param {Number} rotation + * @returns {Number} + * @example + * // get rotation in degrees
+ * var rotation = node.rotation();

+ * + * // set rotation in degrees
+ * node.rotation(45); + */ + + Kinetic.Factory.addComponentsGetterSetter(Kinetic.Node, 'scale', ['x', 'y']); + + /** + * get/set scale + * @name scale + * @param {Object} scale + * @param {Number} scale.x + * @param {Number} scale.y + * @method + * @memberof Kinetic.Node.prototype + * @returns {Object} + * @example + * // get scale
+ * var scale = node.scale();

+ * + * // set scale
+ * shape.scale({
+ * x: 2
+ * y: 3
+ * }); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Node, 'scaleX', 1); + + /** + * get/set scale x + * @name scaleX + * @param {Number} x + * @method + * @memberof Kinetic.Node.prototype + * @returns {Number} + * @example + * // get scale x
+ * var scaleX = node.scaleX();

+ * + * // set scale x
+ * node.scaleX(2); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Node, 'scaleY', 1); + + /** + * get/set scale y + * @name scaleY + * @param {Number} y + * @method + * @memberof Kinetic.Node.prototype + * @returns {Number} + * @example + * // get scale y
+ * var scaleY = node.scaleY();

+ * + * // set scale y
+ * node.scaleY(2); + */ + + Kinetic.Factory.addComponentsGetterSetter(Kinetic.Node, 'skew', ['x', 'y']); + + /** + * get/set skew + * @name skew + * @param {Object} skew + * @param {Number} skew.x + * @param {Number} skew.y + * @method + * @memberof Kinetic.Node.prototype + * @returns {Object} + * @example + * // get skew
+ * var skew = node.skew();

+ * + * // set skew
+ * node.skew({
+ * x: 20
+ * y: 10 + * }); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Node, 'skewX', 0); + + /** + * get/set skew x + * @name skewX + * @param {Number} x + * @method + * @memberof Kinetic.Node.prototype + * @returns {Number} + * @example + * // get skew x
+ * var skewX = node.skewX();

+ * + * // set skew x
+ * node.skewX(3); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Node, 'skewY', 0); + + /** + * get/set skew y + * @name skewY + * @param {Number} y + * @method + * @memberof Kinetic.Node.prototype + * @returns {Number} + * @example + * // get skew y
+ * var skewY = node.skewY();

+ * + * // set skew y
+ * node.skewY(3); + */ + + Kinetic.Factory.addComponentsGetterSetter(Kinetic.Node, 'offset', ['x', 'y']); + + /** + * get/set offset. Offsets the default position and rotation point + * @method + * @memberof Kinetic.Node.prototype + * @param {Object} offset + * @param {Number} offset.x + * @param {Number} offset.y + * @returns {Object} + * @example + * // get offset
+ * var offset = node.offset();

+ * + * // set offset
+ * node.offset({
+ * x: 20
+ * y: 10
+ * }); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Node, 'offsetX', 0); + + /** + * get/set offset x + * @name offsetX + * @memberof Kinetic.Node.prototype + * @param {Number} x + * @returns {Number} + * @example + * // get offset x
+ * var offsetX = node.offsetX();

+ * + * // set offset x
+ * node.offsetX(3); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Node, 'offsetY', 0); + + /** + * get/set offset y + * @name offsetY + * @method + * @memberof Kinetic.Node.prototype + * @param {Number} y + * @returns {Number} + * @example + * // get offset y
+ * var offsetY = node.offsetY();

+ * + * // set offset y
+ * node.offsetY(3); + */ + + Kinetic.Factory.addSetter(Kinetic.Node, 'width', 0); + Kinetic.Factory.addOverloadedGetterSetter(Kinetic.Node, 'width'); + /** + * get/set width + * @name width + * @method + * @memberof Kinetic.Node.prototype + * @param {Number} width + * @returns {Number} + * @example + * // get width
+ * var width = node.width();

+ * + * // set width
+ * node.width(100); + */ + + Kinetic.Factory.addSetter(Kinetic.Node, 'height', 0); + Kinetic.Factory.addOverloadedGetterSetter(Kinetic.Node, 'height'); + /** + * get/set height + * @name height + * @method + * @memberof Kinetic.Node.prototype + * @param {Number} height + * @returns {Number} + * @example + * // get height
+ * var height = node.height();

+ * + * // set height
+ * node.height(100); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Node, 'listening', 'inherit'); + /** + * get/set listenig attr. If you need to determine if a node is listening or not + * by taking into account its parents, use the isListening() method + * @name listening + * @method + * @memberof Kinetic.Node.prototype + * @param {Boolean|String} listening Can be "inherit", true, or false. The default is "inherit". + * @returns {Boolean|String} + * @example + * // get listening attr
+ * var listening = node.listening();

+ * + * // stop listening for events
+ * node.listening(false);

+ * + * // listen for events
+ * node.listening(true);

+ * + * // listen to events according to the parent
+ * node.listening('inherit'); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Node, 'filters', undefined, function(val) {this._filterUpToDate = false;return val;}); + /** + * get/set filters. Filters are applied to cached canvases + * @name filters + * @method + * @memberof Kinetic.Node.prototype + * @param {Array} filters array of filters + * @returns {Array} + * @example + * // get filters
+ * var filters = node.filters();

+ * + * // set a single filter
+ * node.cache();
+ * node.filters([Kinetic.Filters.Blur]);

+ * + * // set multiple filters
+ * node.cache();
+ * node.filters([
+ * Kinetic.Filters.Blur,
+ * Kinetic.Filters.Sepia,
+ * Kinetic.Filters.Invert
+ * ]); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Node, 'visible', 'inherit'); + /** + * get/set visible attr. Can be "inherit", true, or false. The default is "inherit". + * If you need to determine if a node is visible or not + * by taking into account its parents, use the isVisible() method + * @name visible + * @method + * @memberof Kinetic.Node.prototype + * @param {Boolean|String} visible + * @returns {Boolean|String} + * @example + * // get visible attr
+ * var visible = node.visible();

+ * + * // make invisible
+ * node.visible(false);

+ * + * // make visible
+ * node.visible(true);

+ * + * // make visible according to the parent
+ * node.visible('inherit'); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Node, 'transformsEnabled', 'all'); + + /** + * get/set transforms that are enabled. Can be "all", "none", or "position". The default + * is "all" + * @name transformsEnabled + * @method + * @memberof Kinetic.Node.prototype + * @param {String} enabled + * @returns {String} + * @example + * // enable position transform only to improve draw performance
+ * node.transformsEnabled('position');

+ * + * // enable all transforms
+ * node.transformsEnabled('all'); + */ + + Kinetic.Factory.backCompat(Kinetic.Node, { + rotateDeg: 'rotate', + setRotationDeg: 'setRotation', + getRotationDeg: 'getRotation' + }); + + Kinetic.Collection.mapMethods(Kinetic.Node); +})(); +;(function() { + /** + * Grayscale Filter + * @function + * @memberof Kinetic.Filters + * @param {Object} imageData + */ + Kinetic.Filters.Grayscale = function(imageData) { + var data = imageData.data, + len = data.length, + i, brightness; + + for(i = 0; i < len; i += 4) { + brightness = 0.34 * data[i] + 0.5 * data[i + 1] + 0.16 * data[i + 2]; + // red + data[i] = brightness; + // green + data[i + 1] = brightness; + // blue + data[i + 2] = brightness; + } + }; +})(); +;(function() { + /** + * Brighten Filter. + * @function + * @memberof Kinetic.Filters + * @param {Object} imageData + */ + Kinetic.Filters.Brighten = function(imageData) { + var brightness = this.brightness() * 255, + data = imageData.data, + len = data.length, + i; + + for(i = 0; i < len; i += 4) { + // red + data[i] += brightness; + // green + data[i + 1] += brightness; + // blue + data[i + 2] += brightness; + } + }; + + Kinetic.Factory.addGetterSetter(Kinetic.Node, 'brightness', 0, null, Kinetic.Factory.afterSetFilter); + /** + * get/set filter brightness. The brightness is a number between -1 and 1.  Positive values + * brighten the pixels and negative values darken them. + * @name brightness + * @method + * @memberof Kinetic.Image.prototype + * @param {Number} brightness value between -1 and 1 + * @returns {Number} + */ + +})(); +;(function() { + /** + * Invert Filter + * @function + * @memberof Kinetic.Filters + * @param {Object} imageData + */ + Kinetic.Filters.Invert = function(imageData) { + var data = imageData.data, + len = data.length, + i; + + for(i = 0; i < len; i += 4) { + // red + data[i] = 255 - data[i]; + // green + data[i + 1] = 255 - data[i + 1]; + // blue + data[i + 2] = 255 - data[i + 2]; + } + }; +})();;/* + the Gauss filter + master repo: https://github.com/pavelpower/kineticjsGaussFilter/ +*/ +(function() { + /* + + StackBlur - a fast almost Gaussian Blur For Canvas + + Version: 0.5 + Author: Mario Klingemann + Contact: mario@quasimondo.com + Website: http://www.quasimondo.com/StackBlurForCanvas + Twitter: @quasimondo + + In case you find this class useful - especially in commercial projects - + I am not totally unhappy for a small donation to my PayPal account + mario@quasimondo.de + + Or support me on flattr: + https://flattr.com/thing/72791/StackBlur-a-fast-almost-Gaussian-Blur-Effect-for-CanvasJavascript + + Copyright (c) 2010 Mario Klingemann + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + */ + + function BlurStack() { + this.r = 0; + this.g = 0; + this.b = 0; + this.a = 0; + this.next = null; + } + + var mul_table = [ + 512,512,456,512,328,456,335,512,405,328,271,456,388,335,292,512, + 454,405,364,328,298,271,496,456,420,388,360,335,312,292,273,512, + 482,454,428,405,383,364,345,328,312,298,284,271,259,496,475,456, + 437,420,404,388,374,360,347,335,323,312,302,292,282,273,265,512, + 497,482,468,454,441,428,417,405,394,383,373,364,354,345,337,328, + 320,312,305,298,291,284,278,271,265,259,507,496,485,475,465,456, + 446,437,428,420,412,404,396,388,381,374,367,360,354,347,341,335, + 329,323,318,312,307,302,297,292,287,282,278,273,269,265,261,512, + 505,497,489,482,475,468,461,454,447,441,435,428,422,417,411,405, + 399,394,389,383,378,373,368,364,359,354,350,345,341,337,332,328, + 324,320,316,312,309,305,301,298,294,291,287,284,281,278,274,271, + 268,265,262,259,257,507,501,496,491,485,480,475,470,465,460,456, + 451,446,442,437,433,428,424,420,416,412,408,404,400,396,392,388, + 385,381,377,374,370,367,363,360,357,354,350,347,344,341,338,335, + 332,329,326,323,320,318,315,312,310,307,304,302,299,297,294,292, + 289,287,285,282,280,278,275,273,271,269,267,265,263,261,259]; + + var shg_table = [ + 9, 11, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17, + 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24 ]; + + function filterGaussBlurRGBA( imageData, radius) { + + var pixels = imageData.data, + width = imageData.width, + height = imageData.height; + + var x, y, i, p, yp, yi, yw, r_sum, g_sum, b_sum, a_sum, + r_out_sum, g_out_sum, b_out_sum, a_out_sum, + r_in_sum, g_in_sum, b_in_sum, a_in_sum, + pr, pg, pb, pa, rbs; + + var div = radius + radius + 1, + widthMinus1 = width - 1, + heightMinus1 = height - 1, + radiusPlus1 = radius + 1, + sumFactor = radiusPlus1 * ( radiusPlus1 + 1 ) / 2, + stackStart = new BlurStack(), + stackEnd = null, + stack = stackStart, + stackIn = null, + stackOut = null, + mul_sum = mul_table[radius], + shg_sum = shg_table[radius]; + + for ( i = 1; i < div; i++ ) { + stack = stack.next = new BlurStack(); + if ( i == radiusPlus1 ) stackEnd = stack; + } + + stack.next = stackStart; + + yw = yi = 0; + + for ( y = 0; y < height; y++ ) + { + r_in_sum = g_in_sum = b_in_sum = a_in_sum = r_sum = g_sum = b_sum = a_sum = 0; + + r_out_sum = radiusPlus1 * ( pr = pixels[yi] ); + g_out_sum = radiusPlus1 * ( pg = pixels[yi+1] ); + b_out_sum = radiusPlus1 * ( pb = pixels[yi+2] ); + a_out_sum = radiusPlus1 * ( pa = pixels[yi+3] ); + + r_sum += sumFactor * pr; + g_sum += sumFactor * pg; + b_sum += sumFactor * pb; + a_sum += sumFactor * pa; + + stack = stackStart; + + for( i = 0; i < radiusPlus1; i++ ) + { + stack.r = pr; + stack.g = pg; + stack.b = pb; + stack.a = pa; + stack = stack.next; + } + + for( i = 1; i < radiusPlus1; i++ ) + { + p = yi + (( widthMinus1 < i ? widthMinus1 : i ) << 2 ); + r_sum += ( stack.r = ( pr = pixels[p])) * ( rbs = radiusPlus1 - i ); + g_sum += ( stack.g = ( pg = pixels[p+1])) * rbs; + b_sum += ( stack.b = ( pb = pixels[p+2])) * rbs; + a_sum += ( stack.a = ( pa = pixels[p+3])) * rbs; + + r_in_sum += pr; + g_in_sum += pg; + b_in_sum += pb; + a_in_sum += pa; + + stack = stack.next; + } + + + stackIn = stackStart; + stackOut = stackEnd; + for ( x = 0; x < width; x++ ) + { + pixels[yi+3] = pa = (a_sum * mul_sum) >> shg_sum; + if ( pa !== 0 ) + { + pa = 255 / pa; + pixels[yi] = ((r_sum * mul_sum) >> shg_sum) * pa; + pixels[yi+1] = ((g_sum * mul_sum) >> shg_sum) * pa; + pixels[yi+2] = ((b_sum * mul_sum) >> shg_sum) * pa; + } else { + pixels[yi] = pixels[yi+1] = pixels[yi+2] = 0; + } + + r_sum -= r_out_sum; + g_sum -= g_out_sum; + b_sum -= b_out_sum; + a_sum -= a_out_sum; + + r_out_sum -= stackIn.r; + g_out_sum -= stackIn.g; + b_out_sum -= stackIn.b; + a_out_sum -= stackIn.a; + + p = ( yw + ( ( p = x + radius + 1 ) < widthMinus1 ? p : widthMinus1 ) ) << 2; + + r_in_sum += ( stackIn.r = pixels[p]); + g_in_sum += ( stackIn.g = pixels[p+1]); + b_in_sum += ( stackIn.b = pixels[p+2]); + a_in_sum += ( stackIn.a = pixels[p+3]); + + r_sum += r_in_sum; + g_sum += g_in_sum; + b_sum += b_in_sum; + a_sum += a_in_sum; + + stackIn = stackIn.next; + + r_out_sum += ( pr = stackOut.r ); + g_out_sum += ( pg = stackOut.g ); + b_out_sum += ( pb = stackOut.b ); + a_out_sum += ( pa = stackOut.a ); + + r_in_sum -= pr; + g_in_sum -= pg; + b_in_sum -= pb; + a_in_sum -= pa; + + stackOut = stackOut.next; + + yi += 4; + } + yw += width; + } + + + for ( x = 0; x < width; x++ ) + { + g_in_sum = b_in_sum = a_in_sum = r_in_sum = g_sum = b_sum = a_sum = r_sum = 0; + + yi = x << 2; + r_out_sum = radiusPlus1 * ( pr = pixels[yi]); + g_out_sum = radiusPlus1 * ( pg = pixels[yi+1]); + b_out_sum = radiusPlus1 * ( pb = pixels[yi+2]); + a_out_sum = radiusPlus1 * ( pa = pixels[yi+3]); + + r_sum += sumFactor * pr; + g_sum += sumFactor * pg; + b_sum += sumFactor * pb; + a_sum += sumFactor * pa; + + stack = stackStart; + + for( i = 0; i < radiusPlus1; i++ ) + { + stack.r = pr; + stack.g = pg; + stack.b = pb; + stack.a = pa; + stack = stack.next; + } + + yp = width; + + for( i = 1; i <= radius; i++ ) + { + yi = ( yp + x ) << 2; + + r_sum += ( stack.r = ( pr = pixels[yi])) * ( rbs = radiusPlus1 - i ); + g_sum += ( stack.g = ( pg = pixels[yi+1])) * rbs; + b_sum += ( stack.b = ( pb = pixels[yi+2])) * rbs; + a_sum += ( stack.a = ( pa = pixels[yi+3])) * rbs; + + r_in_sum += pr; + g_in_sum += pg; + b_in_sum += pb; + a_in_sum += pa; + + stack = stack.next; + + if( i < heightMinus1 ) + { + yp += width; + } + } + + yi = x; + stackIn = stackStart; + stackOut = stackEnd; + for ( y = 0; y < height; y++ ) + { + p = yi << 2; + pixels[p+3] = pa = (a_sum * mul_sum) >> shg_sum; + if ( pa > 0 ) + { + pa = 255 / pa; + pixels[p] = ((r_sum * mul_sum) >> shg_sum ) * pa; + pixels[p+1] = ((g_sum * mul_sum) >> shg_sum ) * pa; + pixels[p+2] = ((b_sum * mul_sum) >> shg_sum ) * pa; + } else { + pixels[p] = pixels[p+1] = pixels[p+2] = 0; + } + + r_sum -= r_out_sum; + g_sum -= g_out_sum; + b_sum -= b_out_sum; + a_sum -= a_out_sum; + + r_out_sum -= stackIn.r; + g_out_sum -= stackIn.g; + b_out_sum -= stackIn.b; + a_out_sum -= stackIn.a; + + p = ( x + (( ( p = y + radiusPlus1) < heightMinus1 ? p : heightMinus1 ) * width )) << 2; + + r_sum += ( r_in_sum += ( stackIn.r = pixels[p])); + g_sum += ( g_in_sum += ( stackIn.g = pixels[p+1])); + b_sum += ( b_in_sum += ( stackIn.b = pixels[p+2])); + a_sum += ( a_in_sum += ( stackIn.a = pixels[p+3])); + + stackIn = stackIn.next; + + r_out_sum += ( pr = stackOut.r ); + g_out_sum += ( pg = stackOut.g ); + b_out_sum += ( pb = stackOut.b ); + a_out_sum += ( pa = stackOut.a ); + + r_in_sum -= pr; + g_in_sum -= pg; + b_in_sum -= pb; + a_in_sum -= pa; + + stackOut = stackOut.next; + + yi += width; + } + } + } + + /** + * Blur Filter + * @function + * @memberof Kinetic.Filters + * @param {Object} imageData + */ + Kinetic.Filters.Blur = function(imageData) { + var radius = this.blurRadius() | 0; + + if (radius > 0) { + filterGaussBlurRGBA(imageData, radius); + } + }; + + Kinetic.Factory.addGetterSetter(Kinetic.Node, 'blurRadius', 0, null, Kinetic.Factory.afterSetFilter); + + /** + * get/set blur radius + * @name blurRadius + * @method + * @memberof Kinetic.Node.prototype + * @param {Integer} radius + * @returns {Integer} + */ +})();;(function() { + + function pixelAt(idata, x, y) { + var idx = (y * idata.width + x) * 4; + var d = []; + d.push(idata.data[idx++], idata.data[idx++], idata.data[idx++], idata.data[idx++]); + return d; + } + + function rgbDistance(p1, p2) { + return Math.sqrt(Math.pow(p1[0] - p2[0], 2) + Math.pow(p1[1] - p2[1], 2) + Math.pow(p1[2] - p2[2], 2)); + } + + function rgbMean(pTab) { + var m = [0, 0, 0]; + + for (var i = 0; i < pTab.length; i++) { + m[0] += pTab[i][0]; + m[1] += pTab[i][1]; + m[2] += pTab[i][2]; + } + + m[0] /= pTab.length; + m[1] /= pTab.length; + m[2] /= pTab.length; + + return m; + } + + function backgroundMask(idata, threshold) { + var rgbv_no = pixelAt(idata, 0, 0); + var rgbv_ne = pixelAt(idata, idata.width - 1, 0); + var rgbv_so = pixelAt(idata, 0, idata.height - 1); + var rgbv_se = pixelAt(idata, idata.width - 1, idata.height - 1); + + + var thres = threshold || 10; + if (rgbDistance(rgbv_no, rgbv_ne) < thres && rgbDistance(rgbv_ne, rgbv_se) < thres && rgbDistance(rgbv_se, rgbv_so) < thres && rgbDistance(rgbv_so, rgbv_no) < thres) { + + // Mean color + var mean = rgbMean([rgbv_ne, rgbv_no, rgbv_se, rgbv_so]); + + // Mask based on color distance + var mask = []; + for (var i = 0; i < idata.width * idata.height; i++) { + var d = rgbDistance(mean, [idata.data[i * 4], idata.data[i * 4 + 1], idata.data[i * 4 + 2]]); + mask[i] = (d < thres) ? 0 : 255; + } + + return mask; + } + } + + function applyMask(idata, mask) { + for (var i = 0; i < idata.width * idata.height; i++) { + idata.data[4 * i + 3] = mask[i]; + } + } + + function erodeMask(mask, sw, sh) { + + var weights = [1, 1, 1, 1, 0, 1, 1, 1, 1]; + var side = Math.round(Math.sqrt(weights.length)); + var halfSide = Math.floor(side / 2); + + var maskResult = []; + for (var y = 0; y < sh; y++) { + for (var x = 0; x < sw; x++) { + + var so = y * sw + x; + var a = 0; + for (var cy = 0; cy < side; cy++) { + for (var cx = 0; cx < side; cx++) { + var scy = y + cy - halfSide; + var scx = x + cx - halfSide; + + if (scy >= 0 && scy < sh && scx >= 0 && scx < sw) { + + var srcOff = scy * sw + scx; + var wt = weights[cy * side + cx]; + + a += mask[srcOff] * wt; + } + } + } + + maskResult[so] = (a === 255 * 8) ? 255 : 0; + } + } + + return maskResult; + } + + function dilateMask(mask, sw, sh) { + + var weights = [1, 1, 1, 1, 1, 1, 1, 1, 1]; + var side = Math.round(Math.sqrt(weights.length)); + var halfSide = Math.floor(side / 2); + + var maskResult = []; + for (var y = 0; y < sh; y++) { + for (var x = 0; x < sw; x++) { + + var so = y * sw + x; + var a = 0; + for (var cy = 0; cy < side; cy++) { + for (var cx = 0; cx < side; cx++) { + var scy = y + cy - halfSide; + var scx = x + cx - halfSide; + + if (scy >= 0 && scy < sh && scx >= 0 && scx < sw) { + + var srcOff = scy * sw + scx; + var wt = weights[cy * side + cx]; + + a += mask[srcOff] * wt; + } + } + } + + maskResult[so] = (a >= 255 * 4) ? 255 : 0; + } + } + + return maskResult; + } + + function smoothEdgeMask(mask, sw, sh) { + + var weights = [1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9]; + var side = Math.round(Math.sqrt(weights.length)); + var halfSide = Math.floor(side / 2); + + var maskResult = []; + for (var y = 0; y < sh; y++) { + for (var x = 0; x < sw; x++) { + + var so = y * sw + x; + var a = 0; + for (var cy = 0; cy < side; cy++) { + for (var cx = 0; cx < side; cx++) { + var scy = y + cy - halfSide; + var scx = x + cx - halfSide; + + if (scy >= 0 && scy < sh && scx >= 0 && scx < sw) { + + var srcOff = scy * sw + scx; + var wt = weights[cy * side + cx]; + + a += mask[srcOff] * wt; + } + } + } + + maskResult[so] = a; + } + } + + return maskResult; + } + + /** + * Mask Filter + * @function + * @memberof Kinetic.Filters + * @param {Object} imageData + */ + Kinetic.Filters.Mask = function(imageData) { + // Detect pixels close to the background color + var threshold = this.threshold(), + mask = backgroundMask(imageData, threshold); + if (mask) { + // Erode + mask = erodeMask(mask, imageData.width, imageData.height); + + // Dilate + mask = dilateMask(mask, imageData.width, imageData.height); + + // Gradient + mask = smoothEdgeMask(mask, imageData.width, imageData.height); + + // Apply mask + applyMask(imageData, mask); + + // todo : Update hit region function according to mask + } + + return imageData; + }; + + Kinetic.Factory.addGetterSetter(Kinetic.Node, 'threshold', 0, null, Kinetic.Factory.afterSetFilter); +})(); +;(function () { + /** + * RGB Filter + * @function + * @memberof Kinetic.Filters + * @param {Object} imageData + * @author ippo615 + */ + Kinetic.Filters.RGB = function (imageData) { + var data = imageData.data, + nPixels = data.length, + red = this.red(), + green = this.green(), + blue = this.blue(), + i, brightness; + + for (i = 0; i < nPixels; i += 4) { + brightness = (0.34 * data[i] + 0.5 * data[i + 1] + 0.16 * data[i + 2])/255; + data[i ] = brightness*red; // r + data[i + 1] = brightness*green; // g + data[i + 2] = brightness*blue; // b + data[i + 3] = data[i + 3]; // alpha + } + }; + + Kinetic.Factory.addGetterSetter(Kinetic.Node, 'red', 0, function(val) { + this._filterUpToDate = false; + if (val > 255) { + return 255; + } + else if (val < 0) { + return 0; + } + else { + return Math.round(val); + } + }); + /** + * get/set filter red value + * @name red + * @method + * @memberof Kinetic.Node.prototype + * @param {Integer} red value between 0 and 255 + * @returns {Integer} + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Node, 'green', 0, function(val) { + this._filterUpToDate = false; + if (val > 255) { + return 255; + } + else if (val < 0) { + return 0; + } + else { + return Math.round(val); + } + }); + /** + * get/set filter green value + * @name green + * @method + * @memberof Kinetic.Node.prototype + * @param {Integer} green value between 0 and 255 + * @returns {Integer} + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Node, 'blue', 0, Kinetic.Validators.RGBComponent, Kinetic.Factory.afterSetFilter); + /** + * get/set filter blue value + * @name blue + * @method + * @memberof Kinetic.Node.prototype + * @param {Integer} blue value between 0 and 255 + * @returns {Integer} + */ +})(); +;(function () { + + /** + * HSV Filter. Adjusts the hue, saturation and value + * @function + * @memberof Kinetic.Filters + * @param {Object} imageData + * @author ippo615 + */ + + Kinetic.Filters.HSV = function (imageData) { + var data = imageData.data, + nPixels = data.length, + v = this.value(), + s = this.saturation(), + h = Math.abs((this.hue()) + 360) % 360, + i; + + // Basis for the technique used: + // http://beesbuzz.biz/code/hsv_color_transforms.php + // V is the value multiplier (1 for none, 2 for double, 0.5 for half) + // S is the saturation multiplier (1 for none, 2 for double, 0.5 for half) + // H is the hue shift in degrees (0 to 360) + // vsu = V*S*cos(H*PI/180); + // vsw = V*S*sin(H*PI/180); + //[ .299V+.701vsu+.168vsw .587V-.587vsu+.330vsw .114V-.114vsu-.497vsw ] [R] + //[ .299V-.299vsu-.328vsw .587V+.413vsu+.035vsw .114V-.114vsu+.292vsw ]*[G] + //[ .299V-.300vsu+1.25vsw .587V-.588vsu-1.05vsw .114V+.886vsu-.203vsw ] [B] + + // Precompute the values in the matrix: + var vsu = v*s*Math.cos(h*Math.PI/180), + vsw = v*s*Math.sin(h*Math.PI/180); + // (result spot)(source spot) + var rr = 0.299*v+0.701*vsu+0.167*vsw, + rg = 0.587*v-0.587*vsu+0.330*vsw, + rb = 0.114*v-0.114*vsu-0.497*vsw; + var gr = 0.299*v-0.299*vsu-0.328*vsw, + gg = 0.587*v+0.413*vsu+0.035*vsw, + gb = 0.114*v-0.114*vsu+0.293*vsw; + var br = 0.299*v-0.300*vsu+1.250*vsw, + bg = 0.587*v-0.586*vsu-1.050*vsw, + bb = 0.114*v+0.886*vsu-0.200*vsw; + + var r,g,b,a; + + for (i = 0; i < nPixels; i += 4) { + r = data[i+0]; + g = data[i+1]; + b = data[i+2]; + a = data[i+3]; + + data[i+0] = rr*r + rg*g + rb*b; + data[i+1] = gr*r + gg*g + gb*b; + data[i+2] = br*r + bg*g + bb*b; + data[i+3] = a; // alpha + } + + }; + + Kinetic.Factory.addGetterSetter(Kinetic.Node, 'hue', 0, null, Kinetic.Factory.afterSetFilter); + /** + * get/set hsv hue in degrees + * @name hue + * @method + * @memberof Kinetic.Node.prototype + * @param {Number} hue value between 0 and 359 + * @returns {Number} + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Node, 'saturation', 1, null, Kinetic.Factory.afterSetFilter); + /** + * get/set hsv saturation + * @name saturation + * @method + * @memberof Kinetic.Node.prototype + * @param {Number} saturation 1 is no change, 0.5 halves the saturation, 2.0 doubles, etc.. + * @returns {Number} + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Node, 'value', 1, null, Kinetic.Factory.afterSetFilter); + /** + * get/set hsv value + * @name value + * @method + * @memberof Kinetic.Node.prototype + * @param {Number} value 1 is no change, 0.5 halves the value, 2.0 doubles, etc.. + * @returns {Number} + */ +})(); +;(function () { + /** + * Emboss Filter + * @function + * @memberof Kinetic.Filters + * @param {Object} imageData + * Pixastic Lib - Emboss filter - v0.1.0 + * Copyright (c) 2008 Jacob Seidelin, jseidelin@nihilogic.dk, http://blog.nihilogic.dk/ + * License: [http://www.pixastic.com/lib/license.txt] + */ + Kinetic.Filters.Emboss = function (imageData) { + + // pixastic strength is between 0 and 10. I want it between 0 and 1 + // pixastic greyLevel is between 0 and 255. I want it between 0 and 1. Also, + // a max value of greyLevel yields a white emboss, and the min value yields a black + // emboss. Therefore, I changed greyLevel to whiteLevel + var strength = this.embossStrength() * 10, + greyLevel = this.embossWhiteLevel() * 255, + direction = this.embossDirection(), + blend = this.embossBlend(), + dirY = 0, + dirX = 0, + data = imageData.data, + invertAlpha = false, + w = imageData.width, + h = imageData.height, + w4 = w*4, + y = h; + + switch (direction) { + case 'top-left': + dirY = -1; + dirX = -1; + break; + case 'top': + dirY = -1; + dirX = 0; + break; + case 'top-right': + dirY = -1; + dirX = 1; + break; + case 'right': + dirY = 0; + dirX = 1; + break; + case 'bottom-right': + dirY = 1; + dirX = 1; + break; + case 'bottom': + dirY = 1; + dirX = 0; + break; + case 'bottom-left': + dirY = 1; + dirX = -1; + break; + case 'left': + dirY = 0; + dirX = -1; + break; + } + + do { + var offsetY = (y-1)*w4; + + var otherY = dirY; + if (y + otherY < 1) otherY = 0; + if (y + otherY > h) otherY = 0; + + var offsetYOther = (y-1+otherY)*w*4; + + var x = w; + do { + var offset = offsetY + (x-1)*4; + + var otherX = dirX; + if (x + otherX < 1) otherX = 0; + if (x + otherX > w) otherX = 0; + + var offsetOther = offsetYOther + (x-1+otherX)*4; + + var dR = data[offset] - data[offsetOther]; + var dG = data[offset+1] - data[offsetOther+1]; + var dB = data[offset+2] - data[offsetOther+2]; + + var dif = dR; + var absDif = dif > 0 ? dif : -dif; + + var absG = dG > 0 ? dG : -dG; + var absB = dB > 0 ? dB : -dB; + + if (absG > absDif) { + dif = dG; + } + if (absB > absDif) { + dif = dB; + } + + dif *= strength; + + if (blend) { + var r = data[offset] + dif; + var g = data[offset+1] + dif; + var b = data[offset+2] + dif; + + data[offset] = (r > 255) ? 255 : (r < 0 ? 0 : r); + data[offset+1] = (g > 255) ? 255 : (g < 0 ? 0 : g); + data[offset+2] = (b > 255) ? 255 : (b < 0 ? 0 : b); + } else { + var grey = greyLevel - dif; + if (grey < 0) { + grey = 0; + } else if (grey > 255) { + grey = 255; + } + + data[offset] = data[offset+1] = data[offset+2] = grey; + } + + } while (--x); + } while (--y); + }; + + Kinetic.Factory.addGetterSetter(Kinetic.Node, 'embossStrength', 0.5, null, Kinetic.Factory.afterSetFilter); + /** + * get/set emboss strength + * @name embossStrength + * @method + * @memberof Kinetic.Node.prototype + * @param {Number} level between 0 and 1. Default is 0.5 + * @returns {Number} + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Node, 'embossWhiteLevel', 0.5, null, Kinetic.Factory.afterSetFilter); + /** + * get/set emboss white level + * @name embossWhiteLevel + * @method + * @memberof Kinetic.Node.prototype + * @param {Number} embossWhiteLevel between 0 and 1. Default is 0.5 + * @returns {Number} + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Node, 'embossDirection', 'top-left', null, Kinetic.Factory.afterSetFilter); + /** + * get/set emboss direction + * @name embossDirection + * @method + * @memberof Kinetic.Node.prototype + * @param {String} embossDirection can be top-left, top, top-right, right, bottom-right, bottom, bottom-left or left + * The default is top-left + * @returns {String} + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Node, 'embossBlend', false, null, Kinetic.Factory.afterSetFilter); + /** + * get/set emboss blend + * @name embossBlend + * @method + * @memberof Kinetic.Node.prototype + * @param {Boolean} embossBlend + * @returns {Boolean} + */ + +})(); + + +;(function () { + function remap(fromValue, fromMin, fromMax, toMin, toMax) { + // Compute the range of the data + var fromRange = fromMax - fromMin, + toRange = toMax - toMin, + toValue; + + // If either range is 0, then the value can only be mapped to 1 value + if (fromRange === 0) { + return toMin + toRange / 2; + } + if (toRange === 0) { + return toMin; + } + + // (1) untranslate, (2) unscale, (3) rescale, (4) retranslate + toValue = (fromValue - fromMin) / fromRange; + toValue = (toRange * toValue) + toMin; + + return toValue; + } + + + /** + * Enhance Filter. Adjusts the colors so that they span the widest + * possible range (ie 0-255). Performs w*h pixel reads and w*h pixel + * writes. + * @function + * @memberof Kinetic.Filters + * @param {Object} imageData + * @author ippo615 + */ + Kinetic.Filters.Enhance = function (imageData) { + var data = imageData.data, + nSubPixels = data.length, + rMin = data[0], rMax = rMin, r, + gMin = data[1], gMax = gMin, g, + bMin = data[2], bMax = bMin, b, + aMin = data[3], aMax = aMin, a, + i; + + // If we are not enhancing anything - don't do any computation + var enhanceAmount = this.enhance(); + if( enhanceAmount === 0 ){ return; } + + // 1st Pass - find the min and max for each channel: + for (i = 0; i < nSubPixels; i += 4) { + r = data[i + 0]; + if (r < rMin) { rMin = r; } else + if (r > rMax) { rMax = r; } + g = data[i + 1]; + if (g < gMin) { gMin = g; } else + if (g > gMax) { gMax = g; } + b = data[i + 2]; + if (b < bMin) { bMin = b; } else + if (b > bMax) { bMax = b; } + //a = data[i + 3]; + //if (a < aMin) { aMin = a; } else + //if (a > aMax) { aMax = a; } + } + + // If there is only 1 level - don't remap + if( rMax === rMin ){ rMax = 255; rMin = 0; } + if( gMax === gMin ){ gMax = 255; gMin = 0; } + if( bMax === bMin ){ bMax = 255; bMin = 0; } + if( aMax === aMin ){ aMax = 255; aMin = 0; } + + var rMid, rGoalMax,rGoalMin, + gMid, gGoalMax,gGoalMin, + bMid, bGoalMax,aGoalMin, + aMid, aGoalMax,bGoalMin; + + // If the enhancement is positive - stretch the histogram + if( enhanceAmount > 0 ){ + rGoalMax = rMax + enhanceAmount*(255-rMax); + rGoalMin = rMin - enhanceAmount*(rMin-0); + gGoalMax = gMax + enhanceAmount*(255-gMax); + gGoalMin = gMin - enhanceAmount*(gMin-0); + bGoalMax = bMax + enhanceAmount*(255-bMax); + bGoalMin = bMin - enhanceAmount*(bMin-0); + aGoalMax = aMax + enhanceAmount*(255-aMax); + aGoalMin = aMin - enhanceAmount*(aMin-0); + // If the enhancement is negative - compress the histogram + }else{ + rMid = (rMax + rMin)*0.5; + rGoalMax = rMax + enhanceAmount*(rMax-rMid); + rGoalMin = rMin + enhanceAmount*(rMin-rMid); + gMid = (gMax + gMin)*0.5; + gGoalMax = gMax + enhanceAmount*(gMax-gMid); + gGoalMin = gMin + enhanceAmount*(gMin-gMid); + bMid = (bMax + bMin)*0.5; + bGoalMax = bMax + enhanceAmount*(bMax-bMid); + bGoalMin = bMin + enhanceAmount*(bMin-bMid); + aMid = (aMax + aMin)*0.5; + aGoalMax = aMax + enhanceAmount*(aMax-aMid); + aGoalMin = aMin + enhanceAmount*(aMin-aMid); + } + + // Pass 2 - remap everything, except the alpha + for (i = 0; i < nSubPixels; i += 4) { + data[i + 0] = remap(data[i + 0], rMin, rMax, rGoalMin, rGoalMax); + data[i + 1] = remap(data[i + 1], gMin, gMax, gGoalMin, gGoalMax); + data[i + 2] = remap(data[i + 2], bMin, bMax, bGoalMin, bGoalMax); + //data[i + 3] = remap(data[i + 3], aMin, aMax, aGoalMin, aGoalMax); + } + }; + + Kinetic.Factory.addGetterSetter(Kinetic.Node, 'enhance', 0, null, Kinetic.Factory.afterSetFilter); + + /** + * get/set enhance + * @name enhance + * @method + * @memberof Kinetic.Node.prototype + * @param {Float} amount + * @returns {Float} + */ +})(); +;(function () { + + /** + * Posterize Filter. Adjusts the channels so that there are no more + * than n different values for that channel. This is also applied + * to the alpha channel. + * @function + * @author ippo615 + * @memberof Kinetic.Filters + * @param {Object} imageData + */ + + Kinetic.Filters.Posterize = function (imageData) { + // level must be between 1 and 255 + var levels = Math.round(this.levels() * 254) + 1, + data = imageData.data, + len = data.length, + scale = (255 / levels), + i; + + for (i = 0; i < len; i += 1) { + data[i] = Math.floor(data[i] / scale) * scale; + } + }; + + Kinetic.Factory.addGetterSetter(Kinetic.Node, 'levels', 0.5, null, Kinetic.Factory.afterSetFilter); + + /** + * get/set levels. Must be a number between 0 and 1 + * @name levels + * @method + * @memberof Kinetic.Node.prototype + * @param {Number} level between 0 and 1 + * @returns {Number} + */ +})();;(function () { + + /** + * Noise Filter. Randomly adds or substracts to the color channels + * @function + * @memberof Kinetic.Filters + * @param {Object} imagedata + * @author ippo615 + */ + Kinetic.Filters.Noise = function (imageData) { + var amount = this.noise() * 255, + data = imageData.data, + nPixels = data.length, + half = amount / 2, + i; + + for (i = 0; i < nPixels; i += 4) { + data[i + 0] += half - 2 * half * Math.random(); + data[i + 1] += half - 2 * half * Math.random(); + data[i + 2] += half - 2 * half * Math.random(); + } + }; + + Kinetic.Factory.addGetterSetter(Kinetic.Node, 'noise', 0.2, null, Kinetic.Factory.afterSetFilter); + + /** + * get/set noise amount. Must be a value between 0 and 1 + * @name noise + * @method + * @memberof Kinetic.Node.prototype + * @param {Number} noise + * @returns {Number} + */ +})(); +;(function () { + + /** + * Pixelate Filter. Averages groups of pixels and redraws + * them as larger pixels + * @function + * @memberof Kinetic.Filters + * @param {Object} imageData + * @author ippo615 + */ + + Kinetic.Filters.Pixelate = function (imageData) { + + var pixelSize = Math.ceil(this.pixelSize()), + width = imageData.width, + height = imageData.height, + imageData = imageData.data, + x, y, i, + pixelsPerBin = pixelSize * pixelSize, + red, green, blue, alpha, + nBinsX = Math.ceil(width / pixelSize), + nBinsY = Math.ceil(height / pixelSize), + xBinStart, xBinEnd, yBinStart, yBinEnd, + xBin, yBin, pixelsInBin; + + for (xBin = 0; xBin < nBinsX; xBin += 1) { + for (yBin = 0; yBin < nBinsY; yBin += 1) { + + // Initialize the color accumlators to 0 + red = 0; + green = 0; + blue = 0; + alpha = 0; + + // Determine which pixels are included in this bin + xBinStart = xBin * pixelSize; + xBinEnd = xBinStart + pixelSize; + yBinStart = yBin * pixelSize; + yBinEnd = yBinStart + pixelSize; + + // Add all of the pixels to this bin! + pixelsInBin = 0; + for (x = xBinStart; x < xBinEnd; x += 1) { + if( x >= width ){ continue; } + for (y = yBinStart; y < yBinEnd; y += 1) { + if( y >= height ){ continue; } + i = (width * y + x) * 4; + red += imageData[i + 0]; + green += imageData[i + 1]; + blue += imageData[i + 2]; + alpha += imageData[i + 3]; + pixelsInBin += 1; + } + } + + // Make sure the channels are between 0-255 + red = red / pixelsInBin; + green = green / pixelsInBin; + blue = blue / pixelsInBin; + alphas = alpha / pixelsInBin; + + // Draw this bin + for (x = xBinStart; x < xBinEnd; x += 1) { + if( x >= width ){ continue; } + for (y = yBinStart; y < yBinEnd; y += 1) { + if( y >= height ){ continue; } + i = (width * y + x) * 4; + imageData[i + 0] = red; + imageData[i + 1] = green; + imageData[i + 2] = blue; + imageData[i + 3] = alpha; + } + } + } + } + + }; + + Kinetic.Factory.addGetterSetter(Kinetic.Node, 'pixelSize', 8, null, Kinetic.Factory.afterSetFilter); + + /** + * get/set pixel size + * @name pixelSize + * @method + * @memberof Kinetic.Node.prototype + * @param {Integer} pixelSize + * @returns {Integer} + */ +})();;(function () { + + /** + * Threshold Filter. Pushes any value above the mid point to + * the max and any value below the mid point to the min. + * This affects the alpha channel. + * @function + * @memberof Kinetic.Filters + * @param {Object} imageData + * @author ippo615 + */ + + Kinetic.Filters.Threshold = function (imageData) { + var level = this.threshold() * 255, + data = imageData.data, + len = data.length, + i; + + for (i = 0; i < len; i += 1) { + data[i] = data[i] < level ? 0 : 255; + } + }; + + Kinetic.Factory.addGetterSetter(Kinetic.Node, 'threshold', 0.5, null, Kinetic.Factory.afterSetFilter); + + /** + * get/set threshold. Must be a value between 0 and 1 + * @name threshold + * @method + * @memberof Kinetic.Node.prototype + * @param {Number} threshold + * @returns {Number} + */ +})();;(function() { + /** + * Sepia Filter + * Based on: Pixastic Lib - Sepia filter - v0.1.0 + * Copyright (c) 2008 Jacob Seidelin, jseidelin@nihilogic.dk, http://blog.nihilogic.dk/ + * @function + * @memberof Kinetic.Filters + * @param {Object} imageData + * @author Jacob Seidelin + * @license MPL v1.1 [http://www.pixastic.com/lib/license.txt] + */ + Kinetic.Filters.Sepia = function (imageData) { + var data = imageData.data, + w = imageData.width, + y = imageData.height, + w4 = w*4, + offsetY, x, offset, or, og, ob, r, g, b; + + do { + offsetY = (y-1)*w4; + x = w; + do { + offset = offsetY + (x-1)*4; + + or = data[offset]; + og = data[offset+1]; + ob = data[offset+2]; + + r = or * 0.393 + og * 0.769 + ob * 0.189; + g = or * 0.349 + og * 0.686 + ob * 0.168; + b = or * 0.272 + og * 0.534 + ob * 0.131; + + data[offset] = r > 255 ? 255 : r; + data[offset+1] = g > 255 ? 255 : g; + data[offset+2] = b > 255 ? 255 : b; + data[offset+3] = data[offset+3]; + } while (--x); + } while (--y); + }; +})(); +;(function () { + /** + * Solarize Filter + * @function + * @memberof Kinetic.Filters + * @param {Object} imageData + * Pixastic Lib - Solarize filter - v0.1.0 + * Copyright (c) 2008 Jacob Seidelin, jseidelin@nihilogic.dk, http://blog.nihilogic.dk/ + * License: [http://www.pixastic.com/lib/license.txt] + */ + Kinetic.Filters.Solarize = function (imageData) { + var data = imageData.data, + w = imageData.width, + h = imageData.height, + w4 = w*4, + y = h; + + do { + var offsetY = (y-1)*w4; + var x = w; + do { + var offset = offsetY + (x-1)*4; + var r = data[offset]; + var g = data[offset+1]; + var b = data[offset+2]; + + if (r > 127) r = 255 - r; + if (g > 127) g = 255 - g; + if (b > 127) b = 255 - b; + + data[offset] = r; + data[offset+1] = g; + data[offset+2] = b; + + } while (--x); + } while (--y); + }; +})(); + + +;(function () { + + /* + * ToPolar Filter. Converts image data to polar coordinates. Performs + * w*h*4 pixel reads and w*h pixel writes. The r axis is placed along + * what would be the y axis and the theta axis along the x axis. + * @function + * @author ippo615 + * @memberof Kinetic.Filters + * @param {ImageData} src, the source image data (what will be transformed) + * @param {ImageData} dst, the destination image data (where it will be saved) + * @param {Object} opt + * @param {Number} [opt.polarCenterX] horizontal location for the center of the circle, + * default is in the middle + * @param {Number} [opt.polarCenterY] vertical location for the center of the circle, + * default is in the middle + */ + + var ToPolar = function(src,dst,opt){ + + var srcPixels = src.data, + dstPixels = dst.data, + xSize = src.width, + ySize = src.height, + xMid = opt.polarCenterX || xSize/2, + yMid = opt.polarCenterY || ySize/2, + i, m, x, y, k, tmp, r=0,g=0,b=0,a=0; + + // Find the largest radius + var rad, rMax = Math.sqrt( xMid*xMid + yMid*yMid ); + x = xSize - xMid; + y = ySize - yMid; + rad = Math.sqrt( x*x + y*y ); + rMax = (rad > rMax)?rad:rMax; + + // We'll be uisng y as the radius, and x as the angle (theta=t) + var rSize = ySize, + tSize = xSize, + radius, theta; + + // We want to cover all angles (0-360) and we need to convert to + // radians (*PI/180) + var conversion = 360/tSize*Math.PI/180, sin, cos; + + var x1, x2, x1i, x2i, y1, y2, y1i, y2i, scale; + + for( theta=0; theta= xSize-0.5 ){ x = xSize-1; } + if( y <= 1 ){ y = 1; } + if( y >= ySize-0.5 ){ y = ySize-1; } + + // Interpolate x and y by going +-0.5 around the pixel's central point + // this gives us the 4 nearest pixels to our 1x1 non-aligned pixel. + // We average the vaules of those pixels based on how much of our + // non-aligned pixel overlaps each of them. + x1 = x - 0.5; + x2 = x + 0.5; + x1i = Math.floor(x1); + x2i = Math.floor(x2); + y1 = y - 0.5; + y2 = y + 0.5; + y1i = Math.floor(y1); + y2i = Math.floor(y2); + + scale = (1-(x1-x1i))*(1-(y1-y1i)); + i = (y1i*xSize + x1i)*4; + r = srcPixels[i+0]*scale; + g = srcPixels[i+1]*scale; + b = srcPixels[i+2]*scale; + a = srcPixels[i+3]*scale; + + scale = (1-(x1-x1i))*(y2-y2i); + i = (y2i*xSize + x1i)*4; + r += srcPixels[i+0]*scale; + g += srcPixels[i+1]*scale; + b += srcPixels[i+2]*scale; + a += srcPixels[i+3]*scale; + + scale = (x2-x2i)*(y2-y2i); + i = (y2i*xSize + x2i)*4; + r += srcPixels[i+0]*scale; + g += srcPixels[i+1]*scale; + b += srcPixels[i+2]*scale; + a += srcPixels[i+3]*scale; + + scale = (x2-x2i)*(1-(y1-y1i)); + i = (y1i*xSize + x2i)*4; + r += srcPixels[i+0]*scale; + g += srcPixels[i+1]*scale; + b += srcPixels[i+2]*scale; + a += srcPixels[i+3]*scale; + + // Store it + //i = (theta * xSize + radius) * 4; + i = (theta + radius*xSize) * 4; + dstPixels[i+0] = r; + dstPixels[i+1] = g; + dstPixels[i+2] = b; + dstPixels[i+3] = a; + + } + } + }; + + /* + * FromPolar Filter. Converts image data from polar coordinates back to rectangular. + * Performs w*h*4 pixel reads and w*h pixel writes. + * @function + * @author ippo615 + * @memberof Kinetic.Filters + * @param {ImageData} src, the source image data (what will be transformed) + * @param {ImageData} dst, the destination image data (where it will be saved) + * @param {Object} opt + * @param {Number} [opt.polarCenterX] horizontal location for the center of the circle, + * default is in the middle + * @param {Number} [opt.polarCenterY] vertical location for the center of the circle, + * default is in the middle + * @param {Number} [opt.polarRotation] amount to rotate the image counterclockwis, + * 0 is no rotation, 360 degrees is a full rotation + */ + + var FromPolar = function(src,dst,opt){ + + var srcPixels = src.data, + dstPixels = dst.data, + xSize = src.width, + ySize = src.height, + xMid = opt.polarCenterX || xSize/2, + yMid = opt.polarCenterY || ySize/2, + i, m, x, y, dx, dy, k, tmp, r=0,g=0,b=0,a=0; + + + // Find the largest radius + var rad, rMax = Math.sqrt( xMid*xMid + yMid*yMid ); + x = xSize - xMid; + y = ySize - yMid; + rad = Math.sqrt( x*x + y*y ); + rMax = (rad > rMax)?rad:rMax; + + // We'll be uisng x as the radius, and y as the angle (theta=t) + var rSize = ySize, + tSize = xSize, + radius, theta, + phaseShift = opt.polarRotation || 0; + + // We need to convert to degrees and we need to make sure + // it's between (0-360) + // var conversion = tSize/360*180/Math.PI; + var conversion = tSize/360*180/Math.PI; + + var x1, x2, x1i, x2i, y1, y2, y1i, y2i, scale; + + for( x=0; x xSize ){ + xStart = sectionSize; + xEnd = 0; + xDelta = -1; + } + for( y=0; y + * var velocity = 50;

+ * + * var anim = new Kinetic.Animation(function(frame) {
+ * var dist = velocity * (frame.timeDiff / 1000);
+ * node.move(dist, 0);
+ * }, layer);

+ * + * anim.start(); + */ + Kinetic.Animation = function(func, layers) { + this.func = func; + this.setLayers(layers); + this.id = Kinetic.Animation.animIdCounter++; + this.frame = { + time: 0, + timeDiff: 0, + lastTime: new Date().getTime() + }; + }; + /* + * Animation methods + */ + Kinetic.Animation.prototype = { + /** + * set layers to be redrawn on each animation frame + * @method + * @memberof Kinetic.Animation.prototype + * @param {Kinetic.Layer|Array} [layers] layer(s) to be redrawn.  Can be a layer, an array of layers, or null. Not specifying a node will result in no redraw. + */ + setLayers: function(layers) { + var lays = []; + // if passing in no layers + if (!layers) { + lays = []; + } + // if passing in an array of Layers + // NOTE: layers could be an array or Kinetic.Collection. for simplicity, I'm just inspecting + // the length property to check for both cases + else if (layers.length > 0) { + lays = layers; + } + // if passing in a Layer + else { + lays = [layers]; + } + + this.layers = lays; + }, + /** + * get layers + * @method + * @memberof Kinetic.Animation.prototype + */ + getLayers: function() { + return this.layers; + }, + /** + * add layer. Returns true if the layer was added, and false if it was not + * @method + * @memberof Kinetic.Animation.prototype + * @param {Kinetic.Layer} layer + */ + addLayer: function(layer) { + var layers = this.layers, + len, n; + + if (layers) { + len = layers.length; + + // don't add the layer if it already exists + for (n = 0; n < len; n++) { + if (layers[n]._id === layer._id) { + return false; + } + } + } + else { + this.layers = []; + } + + this.layers.push(layer); + return true; + }, + /** + * determine if animation is running or not. returns true or false + * @method + * @memberof Kinetic.Animation.prototype + */ + isRunning: function() { + var a = Kinetic.Animation, animations = a.animations; + for(var n = 0; n < animations.length; n++) { + if(animations[n].id === this.id) { + return true; + } + } + return false; + }, + /** + * start animation + * @method + * @memberof Kinetic.Animation.prototype + */ + start: function() { + this.stop(); + this.frame.timeDiff = 0; + this.frame.lastTime = new Date().getTime(); + Kinetic.Animation._addAnimation(this); + }, + /** + * stop animation + * @method + * @memberof Kinetic.Animation.prototype + */ + stop: function() { + Kinetic.Animation._removeAnimation(this); + }, + _updateFrameObject: function(time) { + this.frame.timeDiff = time - this.frame.lastTime; + this.frame.lastTime = time; + this.frame.time += this.frame.timeDiff; + this.frame.frameRate = 1000 / this.frame.timeDiff; + } + }; + Kinetic.Animation.animations = []; + Kinetic.Animation.animIdCounter = 0; + Kinetic.Animation.animRunning = false; + + Kinetic.Animation._addAnimation = function(anim) { + this.animations.push(anim); + this._handleAnimation(); + }; + Kinetic.Animation._removeAnimation = function(anim) { + var id = anim.id, animations = this.animations, len = animations.length; + for(var n = 0; n < len; n++) { + if(animations[n].id === id) { + this.animations.splice(n, 1); + break; + } + } + }; + + Kinetic.Animation._runFrames = function() { + var layerHash = {}, + animations = this.animations, + anim, layers, func, n, i, layersLen, layer, key; + /* + * loop through all animations and execute animation + * function. if the animation object has specified node, + * we can add the node to the nodes hash to eliminate + * drawing the same node multiple times. The node property + * can be the stage itself or a layer + */ + /* + * WARNING: don't cache animations.length because it could change while + * the for loop is running, causing a JS error + */ + for(n = 0; n < animations.length; n++) { + anim = animations[n]; + layers = anim.layers; + func = anim.func; + + anim._updateFrameObject(new Date().getTime()); + layersLen = layers.length; + + for (i=0; i 0) { + this._runFrames(); + Kinetic.Animation.requestAnimFrame(function() { + that._animationLoop(); + }); + } + else { + this.animRunning = false; + } + }; + Kinetic.Animation._handleAnimation = function() { + var that = this; + if(!this.animRunning) { + this.animRunning = true; + that._animationLoop(); + } + }; + var RAF = (function() { + return window.requestAnimationFrame + || window.webkitRequestAnimationFrame + || window.mozRequestAnimationFrame + || window.oRequestAnimationFrame + || window.msRequestAnimationFrame + || FRAF; + })(); + + function FRAF(callback) { + window.setTimeout(callback, 1000 / 60); + } + + Kinetic.Animation.requestAnimFrame = function(callback) { + var raf = Kinetic.isDragging ? FRAF : RAF; + raf(callback); + }; + + var moveTo = Kinetic.Node.prototype.moveTo; + Kinetic.Node.prototype.moveTo = function(container) { + moveTo.call(this, container); + }; + + /** + * batch draw + * @method + * @memberof Kinetic.Layer.prototype + */ + Kinetic.Layer.prototype.batchDraw = function() { + var that = this; + + if (!this.batchAnim) { + this.batchAnim = new Kinetic.Animation(function() { + if (that.lastBatchDrawTime && new Date().getTime() - that.lastBatchDrawTime > BATCH_DRAW_STOP_TIME_DIFF) { + that.batchAnim.stop(); + } + }, this); + } + + this.lastBatchDrawTime = new Date().getTime(); + + if (!this.batchAnim.isRunning()) { + this.draw(); + this.batchAnim.start(); + } + }; + + /** + * batch draw + * @method + * @memberof Kinetic.Stage.prototype + */ + Kinetic.Stage.prototype.batchDraw = function() { + this.getChildren().each(function(layer) { + layer.batchDraw(); + }); + }; +})();;(function() { + var blacklist = { + node: 1, + duration: 1, + easing: 1, + onFinish: 1, + yoyo: 1 + }, + + PAUSED = 1, + PLAYING = 2, + REVERSING = 3, + + idCounter = 0; + + /** + * Tween constructor. Tweens enable you to animate a node between the current state and a new state. + * You can play, pause, reverse, seek, reset, and finish tweens. By default, tweens are animated using + * a linear easing. For more tweening options, check out {@link Kinetic.Easings} + * @constructor + * @memberof Kinetic + * @example + * // instantiate new tween which fully rotates a node in 1 second + * var tween = new Kinetic.Tween({
+ * node: node,
+ * rotationDeg: 360,
+ * duration: 1,
+ * easing: Kinetic.Easings.EaseInOut
+ * });

+ * + * // play tween
+ * tween.play();

+ * + * // pause tween
+ * tween.pause(); + */ + Kinetic.Tween = function(config) { + var that = this, + node = config.node, + nodeId = node._id, + duration = config.duration || 1, + easing = config.easing || Kinetic.Easings.Linear, + yoyo = !!config.yoyo, + key, tween, start, tweenId; + + this.node = node; + this._id = idCounter++; + + this.anim = new Kinetic.Animation(function() { + that.tween.onEnterFrame(); + }, node.getLayer() || node.getLayers()); + + this.tween = new Tween(key, function(i) { + that._tweenFunc(i); + }, easing, 0, 1, duration * 1000, yoyo); + + this._addListeners(); + + // init attrs map + if (!Kinetic.Tween.attrs[nodeId]) { + Kinetic.Tween.attrs[nodeId] = {}; + } + if (!Kinetic.Tween.attrs[nodeId][this._id]) { + Kinetic.Tween.attrs[nodeId][this._id] = {}; + } + // init tweens map + if (!Kinetic.Tween.tweens[nodeId]) { + Kinetic.Tween.tweens[nodeId] = {}; + } + + for (key in config) { + if (blacklist[key] === undefined) { + this._addAttr(key, config[key]); + } + } + + this.reset(); + + // callbacks + this.onFinish = config.onFinish; + this.onReset = config.onReset; + }; + + // start/diff object = attrs.nodeId.tweenId.attr + Kinetic.Tween.attrs = {}; + // tweenId = tweens.nodeId.attr + Kinetic.Tween.tweens = {}; + + Kinetic.Tween.prototype = { + _addAttr: function(key, end) { + var node = this.node, + nodeId = node._id, + start, diff, tweenId, n, len, startVal, endVal; + + // remove conflict from tween map if it exists + tweenId = Kinetic.Tween.tweens[nodeId][key]; + + if (tweenId) { + delete Kinetic.Tween.attrs[nodeId][tweenId][key]; + } + + // add to tween map + start = node.getAttr(key); + + if (Kinetic.Util._isArray(end)) { + diff = []; + len = end.length; + for (n=0; n this.duration) { + if(this.yoyo) { + this._time = this.duration; + this.reverse(); + } + else { + this.finish(); + } + } + else if(t < 0) { + if(this.yoyo) { + this._time = 0; + this.play(); + } + else { + this.reset(); + } + } + else { + this._time = t; + this.update(); + } + }, + getTime: function() { + return this._time; + }, + setPosition: function(p) { + this.prevPos = this._pos; + this.propFunc(p); + this._pos = p; + }, + getPosition: function(t) { + if(t === undefined) { + t = this._time; + } + return this.func(t, this.begin, this._change, this.duration); + }, + play: function() { + this.state = PLAYING; + this._startTime = this.getTimer() - this._time; + this.onEnterFrame(); + this.fire('onPlay'); + }, + reverse: function() { + this.state = REVERSING; + this._time = this.duration - this._time; + this._startTime = this.getTimer() - this._time; + this.onEnterFrame(); + this.fire('onReverse'); + }, + seek: function(t) { + this.pause(); + this._time = t; + this.update(); + this.fire('onSeek'); + }, + reset: function() { + this.pause(); + this._time = 0; + this.update(); + this.fire('onReset'); + }, + finish: function() { + this.pause(); + this._time = this.duration; + this.update(); + this.fire('onFinish'); + }, + update: function() { + this.setPosition(this.getPosition(this._time)); + }, + onEnterFrame: function() { + var t = this.getTimer() - this._startTime; + if(this.state === PLAYING) { + this.setTime(t); + } + else if (this.state === REVERSING) { + this.setTime(this.duration - t); + } + }, + pause: function() { + this.state = PAUSED; + this.fire('onPause'); + }, + getTimer: function() { + return new Date().getTime(); + } + }; + + /* + * These eases were ported from an Adobe Flash tweening library to JavaScript + * by Xaric + */ + + /** + * @namespace Easings + * @memberof Kinetic + */ + Kinetic.Easings = { + /** + * back ease in + * @function + * @memberof Kinetic.Easings + */ + 'BackEaseIn': function(t, b, c, d, a, p) { + var s = 1.70158; + return c * (t /= d) * t * ((s + 1) * t - s) + b; + }, + /** + * back ease out + * @function + * @memberof Kinetic.Easings + */ + 'BackEaseOut': function(t, b, c, d, a, p) { + var s = 1.70158; + return c * (( t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b; + }, + /** + * back ease in out + * @function + * @memberof Kinetic.Easings + */ + 'BackEaseInOut': function(t, b, c, d, a, p) { + var s = 1.70158; + if((t /= d / 2) < 1) { + return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b; + } + return c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b; + }, + /** + * elastic ease in + * @function + * @memberof Kinetic.Easings + */ + 'ElasticEaseIn': function(t, b, c, d, a, p) { + // added s = 0 + var s = 0; + if(t === 0) { + return b; + } + if((t /= d) == 1) { + return b + c; + } + if(!p) { + p = d * 0.3; + } + if(!a || a < Math.abs(c)) { + a = c; + s = p / 4; + } + else { + s = p / (2 * Math.PI) * Math.asin(c / a); + } + return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b; + }, + /** + * elastic ease out + * @function + * @memberof Kinetic.Easings + */ + 'ElasticEaseOut': function(t, b, c, d, a, p) { + // added s = 0 + var s = 0; + if(t === 0) { + return b; + } + if((t /= d) == 1) { + return b + c; + } + if(!p) { + p = d * 0.3; + } + if(!a || a < Math.abs(c)) { + a = c; + s = p / 4; + } + else { + s = p / (2 * Math.PI) * Math.asin(c / a); + } + return (a * Math.pow(2, -10 * t) * Math.sin((t * d - s) * (2 * Math.PI) / p) + c + b); + }, + /** + * elastic ease in out + * @function + * @memberof Kinetic.Easings + */ + 'ElasticEaseInOut': function(t, b, c, d, a, p) { + // added s = 0 + var s = 0; + if(t === 0) { + return b; + } + if((t /= d / 2) == 2) { + return b + c; + } + if(!p) { + p = d * (0.3 * 1.5); + } + if(!a || a < Math.abs(c)) { + a = c; + s = p / 4; + } + else { + s = p / (2 * Math.PI) * Math.asin(c / a); + } + if(t < 1) { + return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b; + } + return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p) * 0.5 + c + b; + }, + /** + * bounce ease out + * @function + * @memberof Kinetic.Easings + */ + 'BounceEaseOut': function(t, b, c, d) { + if((t /= d) < (1 / 2.75)) { + return c * (7.5625 * t * t) + b; + } + else if(t < (2 / 2.75)) { + return c * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75) + b; + } + else if(t < (2.5 / 2.75)) { + return c * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375) + b; + } + else { + return c * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375) + b; + } + }, + /** + * bounce ease in + * @function + * @memberof Kinetic.Easings + */ + 'BounceEaseIn': function(t, b, c, d) { + return c - Kinetic.Easings.BounceEaseOut(d - t, 0, c, d) + b; + }, + /** + * bounce ease in out + * @function + * @memberof Kinetic.Easings + */ + 'BounceEaseInOut': function(t, b, c, d) { + if(t < d / 2) { + return Kinetic.Easings.BounceEaseIn(t * 2, 0, c, d) * 0.5 + b; + } + else { + return Kinetic.Easings.BounceEaseOut(t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b; + } + }, + /** + * ease in + * @function + * @memberof Kinetic.Easings + */ + 'EaseIn': function(t, b, c, d) { + return c * (t /= d) * t + b; + }, + /** + * ease out + * @function + * @memberof Kinetic.Easings + */ + 'EaseOut': function(t, b, c, d) { + return -c * (t /= d) * (t - 2) + b; + }, + /** + * ease in out + * @function + * @memberof Kinetic.Easings + */ + 'EaseInOut': function(t, b, c, d) { + if((t /= d / 2) < 1) { + return c / 2 * t * t + b; + } + return -c / 2 * ((--t) * (t - 2) - 1) + b; + }, + /** + * strong ease in + * @function + * @memberof Kinetic.Easings + */ + 'StrongEaseIn': function(t, b, c, d) { + return c * (t /= d) * t * t * t * t + b; + }, + /** + * strong ease out + * @function + * @memberof Kinetic.Easings + */ + 'StrongEaseOut': function(t, b, c, d) { + return c * (( t = t / d - 1) * t * t * t * t + 1) + b; + }, + /** + * strong ease in out + * @function + * @memberof Kinetic.Easings + */ + 'StrongEaseInOut': function(t, b, c, d) { + if((t /= d / 2) < 1) { + return c / 2 * t * t * t * t * t + b; + } + return c / 2 * ((t -= 2) * t * t * t * t + 2) + b; + }, + /** + * linear + * @function + * @memberof Kinetic.Easings + */ + 'Linear': function(t, b, c, d) { + return c * t / d + b; + } + }; +})(); +;(function() { + Kinetic.DD = { + // properties + anim: new Kinetic.Animation(), + isDragging: false, + offset: { + x: 0, + y: 0 + }, + node: null, + + // methods + _drag: function(evt) { + var dd = Kinetic.DD, + node = dd.node; + + if(node) { + node._setDragPosition(evt); + + if(!dd.isDragging) { + dd.isDragging = true; + node.fire('dragstart', evt, true); + } + + // execute ondragmove if defined + node.fire('dragmove', evt, true); + } + }, + _endDragBefore: function(evt) { + var dd = Kinetic.DD, + node = dd.node, + nodeType, layer; + + if(node) { + nodeType = node.nodeType, + layer = node.getLayer(); + dd.anim.stop(); + + // only fire dragend event if the drag and drop + // operation actually started. + if(dd.isDragging) { + dd.isDragging = false; + Kinetic.listenClickTap = false; + + if (evt) { + evt.dragEndNode = node; + } + } + + delete dd.node; + + (layer || node).draw(); + } + }, + _endDragAfter: function(evt) { + evt = evt || {}; + + var dragEndNode = evt.dragEndNode; + + if (evt && dragEndNode) { + dragEndNode.fire('dragend', evt, true); + } + } + }; + + // Node extenders + + /** + * initiate drag and drop + * @method + * @memberof Kinetic.Node.prototype + */ + Kinetic.Node.prototype.startDrag = function() { + var dd = Kinetic.DD, + stage = this.getStage(), + layer = this.getLayer(), + pos = stage.getPointerPosition(), + ap = this.getAbsolutePosition(); + + if(pos) { + if (dd.node) { + dd.node.stopDrag(); + } + + dd.node = this; + dd.offset.x = pos.x - ap.x; + dd.offset.y = pos.y - ap.y; + dd.anim.setLayers(layer || this.getLayers()); + dd.anim.start(); + + this._setDragPosition(); + } + }; + + Kinetic.Node.prototype._setDragPosition = function(evt) { + var dd = Kinetic.DD, + pos = this.getStage().getPointerPosition(), + dbf = this.getDragBoundFunc(), + newNodePos = { + x: pos.x - dd.offset.x, + y: pos.y - dd.offset.y + }; + + if(dbf !== undefined) { + newNodePos = dbf.call(this, newNodePos, evt); + } + + this.setAbsolutePosition(newNodePos); + }; + + /** + * stop drag and drop + * @method + * @memberof Kinetic.Node.prototype + */ + Kinetic.Node.prototype.stopDrag = function() { + var dd = Kinetic.DD, + evt = {}; + dd._endDragBefore(evt); + dd._endDragAfter(evt); + }; + + Kinetic.Node.prototype.setDraggable = function(draggable) { + this._setAttr('draggable', draggable); + this._dragChange(); + }; + + var origDestroy = Kinetic.Node.prototype.destroy; + + Kinetic.Node.prototype.destroy = function() { + var dd = Kinetic.DD; + + // stop DD + if(dd.node && dd.node._id === this._id) { + + this.stopDrag(); + } + + origDestroy.call(this); + }; + + /** + * determine if node is currently in drag and drop mode + * @method + * @memberof Kinetic.Node.prototype + */ + Kinetic.Node.prototype.isDragging = function() { + var dd = Kinetic.DD; + return dd.node && dd.node._id === this._id && dd.isDragging; + }; + + Kinetic.Node.prototype._listenDrag = function() { + var that = this; + + this._dragCleanup(); + + if (this.getClassName() === 'Stage') { + this.on('contentMousedown.kinetic contentTouchstart.kinetic', function(evt) { + if(!Kinetic.DD.node) { + that.startDrag(evt); + } + }); + } + else { + this.on('mousedown.kinetic touchstart.kinetic', function(evt) { + if(!Kinetic.DD.node) { + that.startDrag(evt); + } + }); + } + + // listening is required for drag and drop + /* + this._listeningEnabled = true; + this._clearSelfAndAncestorCache('listeningEnabled'); + */ + }; + + Kinetic.Node.prototype._dragChange = function() { + if(this.attrs.draggable) { + this._listenDrag(); + } + else { + // remove event listeners + this._dragCleanup(); + + /* + * force drag and drop to end + * if this node is currently in + * drag and drop mode + */ + var stage = this.getStage(); + var dd = Kinetic.DD; + if(stage && dd.node && dd.node._id === this._id) { + dd.node.stopDrag(); + } + } + }; + + Kinetic.Node.prototype._dragCleanup = function() { + if (this.getClassName() === 'Stage') { + this.off('contentMousedown.kinetic'); + this.off('contentTouchstart.kinetic'); + } else { + this.off('mousedown.kinetic'); + this.off('touchstart.kinetic'); + } + }; + + Kinetic.Factory.addGetterSetter(Kinetic.Node, 'dragBoundFunc'); + + /** + * get/set drag bound function. This is used to override the default + * drag and drop position + * @name dragBoundFunc + * @method + * @memberof Kinetic.Node.prototype + * @param {Function} dragBoundFunc + * @returns {Function} + * @example + * // get drag bound function
+ * var dragBoundFunc = node.dragBoundFunc();

+ * + * // create vertical drag and drop
+ * node.dragBoundFunc(function(){
+ * return {
+ * x: this.getAbsolutePosition().x,
+ * y: pos.y
+ * };
+ * }); + */ + + Kinetic.Factory.addGetter(Kinetic.Node, 'draggable', false); + Kinetic.Factory.addOverloadedGetterSetter(Kinetic.Node, 'draggable'); + + /** + * get/set draggable flag + * @name draggable + * @method + * @memberof Kinetic.Node.prototype + * @param {Boolean} draggable + * @returns {Boolean} + * @example + * // get draggable flag
+ * var draggable = node.draggable();

+ * + * // enable drag and drop
+ * node.draggable(true);

+ * + * // disable drag and drop
+ * node.draggable(false); + */ + + var html = document.documentElement; + html.addEventListener('mouseup', Kinetic.DD._endDragBefore, true); + html.addEventListener('touchend', Kinetic.DD._endDragBefore, true); + + html.addEventListener('mouseup', Kinetic.DD._endDragAfter, false); + html.addEventListener('touchend', Kinetic.DD._endDragAfter, false); + +})(); +;(function() { + Kinetic.Util.addMethods(Kinetic.Container, { + __init: function(config) { + this.children = new Kinetic.Collection(); + Kinetic.Node.call(this, config); + }, + /** + * returns a {@link Kinetic.Collection} of direct descendant nodes + * @method + * @memberof Kinetic.Container.prototype + */ + getChildren: function() { + return this.children; + }, + /** + * determine if node has children + * @method + * @memberof Kinetic.Container.prototype + * @returns {Boolean} + */ + hasChildren: function() { + return this.getChildren().length > 0; + }, + /** + * remove all children + * @method + * @memberof Kinetic.Container.prototype + */ + removeChildren: function() { + var children = this.children, + child; + + while(children.length > 0) { + child = children[0]; + if (child.hasChildren()) { + child.removeChildren(); + } + child.remove(); + } + + return this; + }, + /** + * destroy all children + * @method + * @memberof Kinetic.Container.prototype + */ + destroyChildren: function() { + var children = this.children; + while(children.length > 0) { + children[0].destroy(); + } + return this; + }, + /** + * add node to container + * @method + * @memberof Kinetic.Container.prototype + * @param {Node} child + * @returns {Container} + */ + add: function(child) { + var children = this.children; + + this._validateAdd(child); + child.index = children.length; + child.parent = this; + children.push(child); + this._fire('add', { + child: child + }); + + // chainable + return this; + }, + destroy: function() { + // destroy children + if (this.hasChildren()) { + this.destroyChildren(); + } + // then destroy self + Kinetic.Node.prototype.destroy.call(this); + }, + /** + * return a {@link Kinetic.Collection} of nodes that match the selector. Use '#' for id selections + * and '.' for name selections. You can also select by type or class name. Pass multiple selectors + * separated by a space. + * @method + * @memberof Kinetic.Container.prototype + * @param {String} selector + * @returns {Collection} + * @example + * // select node with id foo
+ * var node = stage.find('#foo');

+ * + * // select nodes with name bar inside layer
+ * var nodes = layer.find('.bar');

+ * + * // select all groups inside layer
+ * var nodes = layer.find('Group');

+ * + * // select all rectangles inside layer
+ * var nodes = layer.find('Rect');

+ * + * // select node with an id of foo or a name of bar inside layer
+ * var nodes = layer.find('#foo, .bar'); + */ + find: function(selector) { + var retArr = [], + selectorArr = selector.replace(/ /g, '').split(','), + len = selectorArr.length, + n, i, sel, arr, node, children, clen; + + for (n = 0; n < len; n++) { + sel = selectorArr[n]; + + // id selector + if(sel.charAt(0) === '#') { + node = this._getNodeById(sel.slice(1)); + if(node) { + retArr.push(node); + } + } + // name selector + else if(sel.charAt(0) === '.') { + arr = this._getNodesByName(sel.slice(1)); + retArr = retArr.concat(arr); + } + // unrecognized selector, pass to children + else { + children = this.getChildren(); + clen = children.length; + for(i = 0; i < clen; i++) { + retArr = retArr.concat(children[i]._get(sel)); + } + } + } + + return Kinetic.Collection.toCollection(retArr); + }, + _getNodeById: function(key) { + var node = Kinetic.ids[key]; + + if(node !== undefined && this.isAncestorOf(node)) { + return node; + } + return null; + }, + _getNodesByName: function(key) { + var arr = Kinetic.names[key] || []; + return this._getDescendants(arr); + }, + _get: function(selector) { + var retArr = Kinetic.Node.prototype._get.call(this, selector); + var children = this.getChildren(); + var len = children.length; + for(var n = 0; n < len; n++) { + retArr = retArr.concat(children[n]._get(selector)); + } + return retArr; + }, + // extenders + toObject: function() { + var obj = Kinetic.Node.prototype.toObject.call(this); + + obj.children = []; + + var children = this.getChildren(); + var len = children.length; + for(var n = 0; n < len; n++) { + var child = children[n]; + obj.children.push(child.toObject()); + } + + return obj; + }, + _getDescendants: function(arr) { + var retArr = []; + var len = arr.length; + for(var n = 0; n < len; n++) { + var node = arr[n]; + if(this.isAncestorOf(node)) { + retArr.push(node); + } + } + + return retArr; + }, + /** + * determine if node is an ancestor + * of descendant + * @method + * @memberof Kinetic.Container.prototype + * @param {Kinetic.Node} node + */ + isAncestorOf: function(node) { + var parent = node.getParent(); + while(parent) { + if(parent._id === this._id) { + return true; + } + parent = parent.getParent(); + } + + return false; + }, + clone: function(obj) { + // call super method + var node = Kinetic.Node.prototype.clone.call(this, obj); + + this.getChildren().each(function(no) { + node.add(no.clone()); + }); + return node; + }, + /** + * get all shapes that intersect a point. Note: because this method must clear a temporary + * canvas and redraw every shape inside the container, it should only be used for special sitations + * because it performs very poorly. Please use the {@link Kinetic.Stage#getIntersection} method if at all possible + * because it performs much better + * @method + * @memberof Kinetic.Container.prototype + * @param {Object} pos + * @param {Number} pos.x + * @param {Number} pos.y + * @returns {Array} array of shapes + */ + getAllIntersections: function(pos) { + var arr = []; + + this.find('Shape').each(function(shape) { + if(shape.isVisible() && shape.intersects(pos)) { + arr.push(shape); + } + }); + + return arr; + }, + _setChildrenIndices: function() { + this.children.each(function(child, n) { + child.index = n; + }); + }, + drawScene: function(can) { + var layer = this.getLayer(), + canvas = can || (layer && layer.getCanvas()), + context = canvas && canvas.getContext(), + cachedCanvas = this._cache.canvas, + cachedSceneCanvas = cachedCanvas && cachedCanvas.scene; + + if (this.isVisible()) { + if (cachedSceneCanvas) { + this._drawCachedSceneCanvas(context); + } + else { + this._drawChildren(canvas, 'drawScene'); + } + } + return this; + }, + drawHit: function(can) { + var layer = this.getLayer(), + canvas = can || (layer && layer.hitCanvas), + context = canvas && canvas.getContext(), + cachedCanvas = this._cache.canvas, + cachedHitCanvas = cachedCanvas && cachedCanvas.hit; + + if (this.shouldDrawHit()) { + if (cachedHitCanvas) { + this._drawCachedHitCanvas(context); + } + else { + this._drawChildren(canvas, 'drawHit'); + } + } + return this; + }, + _drawChildren: function(canvas, drawMethod) { + var context = canvas && canvas.getContext(), + clipWidth = this.getClipWidth(), + clipHeight = this.getClipHeight(), + hasClip = clipWidth && clipHeight, + clipX, clipY; + + if (hasClip) { + clipX = this.getClipX(); + clipY = this.getClipY(); + + context.save(); + context._applyTransform(this); + context.beginPath(); + context.rect(clipX, clipY, clipWidth, clipHeight); + context.clip(); + context.reset(); + } + + this.children.each(function(child) { + child[drawMethod](canvas); + }); + + if (hasClip) { + context.restore(); + } + } + }); + + Kinetic.Util.extend(Kinetic.Container, Kinetic.Node); + // deprecated methods + Kinetic.Container.prototype.get = Kinetic.Container.prototype.find; + + // add getters setters + Kinetic.Factory.addComponentsGetterSetter(Kinetic.Container, 'clip', ['x', 'y', 'width', 'height']); + /** + * get/set clip + * @method + * @name clip + * @memberof Kinetic.Container.prototype + * @param {Object} clip + * @param {Number} clip.x + * @param {Number} clip.y + * @param {Number} clip.width + * @param {Number} clip.height + * @returns {Object} + * @example + * // get clip
+ * var clip = container.clip();

+ * + * // set clip
+ * container.setClip({
+ * x: 20,
+ * y: 20,
+ * width: 20,
+ * height: 20
+ * }); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Container, 'clipX'); + /** + * get/set clip x + * @name clipX + * @method + * @memberof Kinetic.Container.prototype + * @param {Number} x + * @returns {Number} + * @example + * // get clip x
+ * var clipX = container.clipX();

+ * + * // set clip x
+ * container.clipX(10); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Container, 'clipY'); + /** + * get/set clip y + * @name clipY + * @method + * @memberof Kinetic.Container.prototype + * @param {Number} y + * @returns {Number} + * @example + * // get clip y
+ * var clipY = container.clipY();

+ * + * // set clip y
+ * container.clipY(10); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Container, 'clipWidth'); + /** + * get/set clip width + * @name clipWidth + * @method + * @memberof Kinetic.Container.prototype + * @param {Number} width + * @returns {Number} + * @example + * // get clip width
+ * var clipWidth = container.clipWidth();

+ * + * // set clip width
+ * container.clipWidth(100); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Container, 'clipHeight'); + /** + * get/set clip height + * @name clipHeight + * @method + * @memberof Kinetic.Container.prototype + * @param {Number} height + * @returns {Number} + * @example + * // get clip height
+ * var clipHeight = container.clipHeight();

+ * + * // set clip height
+ * container.clipHeight(100); + */ + + Kinetic.Collection.mapMethods(Kinetic.Container); +})(); +;(function() { + var HAS_SHADOW = 'hasShadow'; + + function _fillFunc(context) { + context.fill(); + } + function _strokeFunc(context) { + context.stroke(); + } + function _fillFuncHit(context) { + context.fill(); + } + function _strokeFuncHit(context) { + context.stroke(); + } + + function _clearHasShadowCache() { + this._clearCache(HAS_SHADOW); + } + + Kinetic.Util.addMethods(Kinetic.Shape, { + __init: function(config) { + this.nodeType = 'Shape'; + this._fillFunc = _fillFunc; + this._strokeFunc = _strokeFunc; + this._fillFuncHit = _fillFuncHit; + this._strokeFuncHit = _strokeFuncHit; + + // set colorKey + var shapes = Kinetic.shapes; + var key; + + while(true) { + key = Kinetic.Util.getRandomColor(); + if(key && !( key in shapes)) { + break; + } + } + + this.colorKey = key; + shapes[key] = this; + + // call super constructor + Kinetic.Node.call(this, config); + + this.on('shadowColorChange.kinetic shadowBlurChange.kinetic shadowOffsetChange.kinetic shadowOpacityChange.kinetic shadowEnabledChanged.kinetic', _clearHasShadowCache); + }, + hasChildren: function() { + return false; + }, + getChildren: function() { + return []; + }, + /** + * get canvas context tied to the layer + * @method + * @memberof Kinetic.Shape.prototype + * @returns {Kinetic.Context} + */ + getContext: function() { + return this.getLayer().getContext(); + }, + /** + * get canvas renderer tied to the layer. Note that this returns a canvas renderer, not a canvas element + * @method + * @memberof Kinetic.Shape.prototype + * @returns {Kinetic.Canvas} + */ + getCanvas: function() { + return this.getLayer().getCanvas(); + }, + /** + * returns whether or not a shadow will be rendered + * @method + * @memberof Kinetic.Shape.prototype + * @returns {Boolean} + */ + hasShadow: function() { + return this._getCache(HAS_SHADOW, this._hasShadow); + }, + _hasShadow: function() { + return this.getShadowEnabled() && (this.getShadowOpacity() !== 0 && !!(this.getShadowColor() || this.getShadowBlur() || this.getShadowOffsetX() || this.getShadowOffsetY())); + }, + /** + * returns whether or not the shape will be filled + * @method + * @memberof Kinetic.Shape.prototype + * @returns {Boolean} + */ + hasFill: function() { + return !!(this.getFill() || this.getFillPatternImage() || this.getFillLinearGradientColorStops() || this.getFillRadialGradientColorStops()); + }, + /** + * returns whether or not the shape will be stroked + * @method + * @memberof Kinetic.Shape.prototype + * @returns {Boolean} + */ + hasStroke: function() { + return !!(this.stroke() || this.strokeRed() || this.strokeGreen() || this.strokeBlue()); + }, + _get: function(selector) { + return this.className === selector || this.nodeType === selector ? [this] : []; + }, + /** + * determines if point is in the shape, regardless if other shapes are on top of it. Note: because + * this method clears a temporary canvas and then redraws the shape, it performs very poorly if executed many times + * consecutively. Please use the {@link Kinetic.Stage#getIntersection} method if at all possible + * because it performs much better + * @method + * @memberof Kinetic.Shape.prototype + * @param {Object} point + * @param {Number} point.x + * @param {Number} point.y + * @returns {Boolean} + */ + intersects: function(pos) { + var stage = this.getStage(), + bufferHitCanvas = stage.bufferHitCanvas, + p; + + bufferHitCanvas.getContext().clear(); + this.drawScene(bufferHitCanvas); + p = bufferHitCanvas.context.getImageData(pos.x | 0, pos.y | 0, 1, 1).data; + return p[3] > 0; + }, + // extends Node.prototype.destroy + destroy: function() { + Kinetic.Node.prototype.destroy.call(this); + delete Kinetic.shapes[this.colorKey]; + }, + _useBufferCanvas: function() { + return (this.hasShadow() || this.getAbsoluteOpacity() !== 1) && this.hasFill() && this.hasStroke(); + }, + drawScene: function(can) { + var canvas = can || this.getLayer().getCanvas(), + context = canvas.getContext(), + cachedCanvas = this._cache.canvas, + drawFunc = this.sceneFunc(), + hasShadow = this.hasShadow(), + stage, bufferCanvas, bufferContext; + + if(this.isVisible()) { + if (cachedCanvas) { + this._drawCachedSceneCanvas(context); + } + else if (drawFunc) { + context.save(); + // if buffer canvas is needed + if (this._useBufferCanvas()) { + stage = this.getStage(); + bufferCanvas = stage.bufferCanvas; + bufferContext = bufferCanvas.getContext(); + bufferContext.clear(); + bufferContext.save(); + bufferContext._applyLineJoin(this); + bufferContext._applyTransform(this); + + drawFunc.call(this, bufferContext); + bufferContext.restore(); + + if (hasShadow) { + context.save(); + context._applyShadow(this); + context.drawImage(bufferCanvas._canvas, 0, 0); + context.restore(); + } + + context._applyOpacity(this); + context.drawImage(bufferCanvas._canvas, 0, 0); + } + // if buffer canvas is not needed + else { + context._applyLineJoin(this); + context._applyTransform(this); + + if (hasShadow) { + context.save(); + context._applyShadow(this); + drawFunc.call(this, context); + context.restore(); + } + + context._applyOpacity(this); + drawFunc.call(this, context); + } + context.restore(); + } + } + + return this; + }, + drawHit: function(can) { + var canvas = can || this.getLayer().hitCanvas, + context = canvas.getContext(), + drawFunc = this.hitFunc() || this.sceneFunc(), + cachedCanvas = this._cache.canvas, + cachedHitCanvas = cachedCanvas && cachedCanvas.hit; + + if(this.shouldDrawHit()) { + + if (cachedHitCanvas) { + this._drawCachedHitCanvas(context); + } + else if (drawFunc) { + context.save(); + context._applyLineJoin(this); + context._applyTransform(this); + + drawFunc.call(this, context); + context.restore(); + } + + } + + return this; + }, + /** + * draw hit graph using the cached scene canvas + * @method + * @memberof Kinetic.Shape.prototype + * @param {Integer} alphaThreshold alpha channel threshold that determines whether or not + * a pixel should be drawn onto the hit graph. Must be a value between 0 and 255. + * The default is 0 + * @returns {Kinetic.Shape} + * @example + * shape.cache(); + * shape.drawHitFromCache(); + */ + drawHitFromCache: function(alphaThreshold) { + var threshold = alphaThreshold || 0, + cachedCanvas = this._cache.canvas, + sceneCanvas = this._getCachedSceneCanvas(), + sceneContext = sceneCanvas.getContext(), + hitCanvas = cachedCanvas.hit, + hitContext = hitCanvas.getContext(), + width = sceneCanvas.getWidth(), + height = sceneCanvas.getHeight(), + sceneImageData, sceneData, hitImageData, hitData, len, rgbColorKey, i, alpha; + + hitContext.clear(); + + try { + sceneImageData = sceneContext.getImageData(0, 0, width, height); + sceneData = sceneImageData.data; + hitImageData = hitContext.getImageData(0, 0, width, height); + hitData = hitImageData.data; + len = sceneData.length; + rgbColorKey = Kinetic.Util._hexToRgb(this.colorKey); + + // replace non transparent pixels with color key + for(i = 0; i < len; i += 4) { + alpha = sceneData[i + 3]; + if (alpha > threshold) { + hitData[i] = rgbColorKey.r; + hitData[i + 1] = rgbColorKey.g; + hitData[i + 2] = rgbColorKey.b; + hitData[i + 3] = 255; + } + } + + hitContext.putImageData(hitImageData, 0, 0); + } + catch(e) { + Kinetic.Util.warn('Unable to draw hit graph from cached scene canvas. ' + e.message); + } + + return this; + }, + }); + Kinetic.Util.extend(Kinetic.Shape, Kinetic.Node); + + // add getters and setters + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'stroke'); + + /** + * get/set stroke color + * @name stroke + * @method + * @memberof Kinetic.Shape.prototype + * @param {String} color + * @returns {String} + * @example + * // get stroke color
+ * var stroke = shape.stroke();

+ * + * // set stroke color with color string
+ * shape.stroke('green');

+ * + * // set stroke color with hex
+ * shape.stroke('#00ff00');

+ * + * // set stroke color with rgb
+ * shape.stroke('rgb(0,255,0)');

+ * + * // set stroke color with rgba and make it 50% opaque
+ * shape.stroke('rgba(0,255,0,0.5'); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'strokeRed', 0, Kinetic.Validators.RGBComponent); + + /** + * get/set stroke red component + * @name strokeRed + * @method + * @memberof Kinetic.Shape.prototype + * @param {Integer} red + * @returns {Integer} + * @example + * // get stroke red component
+ * var strokeRed = shape.strokeRed();

+ * + * // set stroke red component
+ * shape.strokeRed(0); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'strokeGreen', 0, Kinetic.Validators.RGBComponent); + + /** + * get/set stroke green component + * @name strokeGreen + * @method + * @memberof Kinetic.Shape.prototype + * @param {Integer} green + * @returns {Integer} + * @example + * // get stroke green component
+ * var strokeGreen = shape.strokeGreen();

+ * + * // set stroke green component
+ * shape.strokeGreen(255); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'strokeBlue', 0, Kinetic.Validators.RGBComponent); + + /** + * get/set stroke blue component + * @name strokeBlue + * @method + * @memberof Kinetic.Shape.prototype + * @param {Integer} blue + * @returns {Integer} + * @example + * // get stroke blue component
+ * var strokeBlue = shape.strokeBlue();

+ * + * // set stroke blue component
+ * shape.strokeBlue(0); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'strokeAlpha', 1, Kinetic.Validators.alphaComponent); + + /** + * get/set stroke alpha component. Alpha is a real number between 0 and 1. The default + * is 1. + * @name strokeAlpha + * @method + * @memberof Kinetic.Shape.prototype + * @param {Number} alpha + * @returns {Number} + * @example + * // get stroke alpha component
+ * var strokeAlpha = shape.strokeAlpha();

+ * + * // set stroke alpha component
+ * shape.strokeAlpha(0.5); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'strokeWidth', 2); + + /** + * get/set stroke width + * @name strokeWidth + * @method + * @memberof Kinetic.Shape.prototype + * @param {Number} strokeWidth + * @returns {Number} + * @example + * // get stroke width
+ * var strokeWidth = shape.strokeWidth();

+ * + * // set stroke width
+ * shape.strokeWidth(); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'lineJoin'); + + /** + * get/set line join. Can be miter, round, or bevel. The + * default is miter + * @name lineJoin + * @method + * @memberof Kinetic.Shape.prototype + * @param {String} lineJoin + * @returns {String} + * @example + * // get line join
+ * var lineJoin = shape.lineJoin();

+ * + * // set line join
+ * shape.lineJoin('round'); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'lineCap'); + + /** + * get/set line cap. Can be butt, round, or square + * @name lineCap + * @method + * @memberof Kinetic.Shape.prototype + * @param {String} lineCap + * @returns {String} + * @example + * // get line cap
+ * var lineCap = shape.lineCap();

+ * + * // set line cap
+ * shape.lineCap('round'); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'sceneFunc'); + + /** + * get/set scene draw function + * @name sceneFunc + * @method + * @memberof Kinetic.Shape.prototype + * @param {Function} drawFunc drawing function + * @returns {Function} + * @example + * // get scene draw function
+ * var sceneFunc = shape.sceneFunc();

+ * + * // set scene draw function
+ * shape.sceneFunc(function(context) {
+ * context.beginPath();
+ * context.rect(0, 0, this.width(), this.height());
+ * context.closePath();
+ * context.fillStrokeShape(this);
+ * }); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'hitFunc'); + + /** + * get/set hit draw function + * @name hitFunc + * @method + * @memberof Kinetic.Shape.prototype + * @param {Function} drawFunc drawing function + * @returns {Function} + * @example + * // get hit draw function
+ * var hitFunc = shape.hitFunc();

+ * + * // set hit draw function
+ * shape.hitFunc(function(context) {
+ * context.beginPath();
+ * context.rect(0, 0, this.width(), this.height());
+ * context.closePath();
+ * context.fillStrokeShape(this);
+ * }); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'dash'); + + /** + * get/set dash array for stroke. + * @name dash + * @method + * @memberof Kinetic.Shape.prototype + * @param {Array} dash + * @returns {Array} + * @example + * // apply dashed stroke that is 10px long and 5 pixels apart
+ * line.dash([10, 5]);

+ * + * // apply dashed stroke that is made up of alternating dashed
+ * // lines that are 10px long and 20px apart, and dots that have
+ * // a radius of 5px and are 20px apart
+ * line.dash([10, 20, 0.001, 20]); + */ + + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'shadowColor'); + + /** + * get/set shadow color + * @name shadowColor + * @method + * @memberof Kinetic.Shape.prototype + * @param {String} color + * @returns {String} + * @example + * // get shadow color
+ * var shadow = shape.shadowColor();

+ * + * // set shadow color with color string
+ * shape.shadowColor('green');

+ * + * // set shadow color with hex
+ * shape.shadowColor('#00ff00');

+ * + * // set shadow color with rgb
+ * shape.shadowColor('rgb(0,255,0)');

+ * + * // set shadow color with rgba and make it 50% opaque
+ * shape.shadowColor('rgba(0,255,0,0.5'); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'shadowRed', 0, Kinetic.Validators.RGBComponent); + + /** + * get/set shadow red component + * @name shadowRed + * @method + * @memberof Kinetic.Shape.prototype + * @param {Integer} red + * @returns {Integer} + * @example + * // get shadow red component
+ * var shadowRed = shape.shadowRed();

+ * + * // set shadow red component
+ * shape.shadowRed(0); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'shadowGreen', 0, Kinetic.Validators.RGBComponent); + + /** + * get/set shadow green component + * @name shadowGreen + * @method + * @memberof Kinetic.Shape.prototype + * @param {Integer} green + * @returns {Integer} + * @example + * // get shadow green component
+ * var shadowGreen = shape.shadowGreen();

+ * + * // set shadow green component
+ * shape.shadowGreen(255); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'shadowBlue', 0, Kinetic.Validators.RGBComponent); + + /** + * get/set shadow blue component + * @name shadowBlue + * @method + * @memberof Kinetic.Shape.prototype + * @param {Integer} blue + * @returns {Integer} + * @example + * // get shadow blue component
+ * var shadowBlue = shape.shadowBlue();

+ * + * // set shadow blue component
+ * shape.shadowBlue(0); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'shadowAlpha', 1, Kinetic.Validators.alphaComponent); + + /** + * get/set shadow alpha component. Alpha is a real number between 0 and 1. The default + * is 1. + * @name shadowAlpha + * @method + * @memberof Kinetic.Shape.prototype + * @param {Number} alpha + * @returns {Number} + * @example + * // get shadow alpha component
+ * var shadowAlpha = shape.shadowAlpha();

+ * + * // set shadow alpha component
+ * shape.shadowAlpha(0.5); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'shadowBlur'); + + /** + * get/set shadow blur + * @name shadowBlur + * @method + * @memberof Kinetic.Shape.prototype + * @param {Number} blur + * @returns {Number} + * @example + * // get shadow blur
+ * var shadowBlur = shape.shadowBlur();

+ * + * // set shadow blur
+ * shape.shadowBlur(10); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'shadowOpacity'); + + /** + * get/set shadow opacity. must be a value between 0 and 1 + * @name shadowOpacity + * @method + * @memberof Kinetic.Shape.prototype + * @param {Number} opacity + * @returns {Number} + * @example + * // get shadow opacity
+ * var shadowOpacity = shape.shadowOpacity();

+ * + * // set shadow opacity
+ * shape.shadowOpacity(0.5); + */ + + Kinetic.Factory.addComponentsGetterSetter(Kinetic.Shape, 'shadowOffset', ['x', 'y']); + + /** + * get/set shadow offset + * @name shadowOffset + * @method + * @memberof Kinetic.Shape.prototype + * @param {Object} offset + * @param {Number} offset.x + * @param {Number} offset.y + * @returns {Object} + * @example + * // get shadow offset
+ * var shadowOffset = shape.shadowOffset();

+ * + * // set shadow offset
+ * shape.shadowOffset({
+ * x: 20
+ * y: 10
+ * }); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'shadowOffsetX', 0); + + /** + * get/set shadow offset x + * @name shadowOffsetX + * @method + * @memberof Kinetic.Shape.prototype + * @param {Number} x + * @returns {Number} + * @example + * // get shadow offset x
+ * var shadowOffsetX = shape.shadowOffsetX();

+ * + * // set shadow offset x
+ * shape.shadowOffsetX(5); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'shadowOffsetY', 0); + + /** + * get/set shadow offset y + * @name shadowOffsetY + * @method + * @memberof Kinetic.Shape.prototype + * @param {Number} y + * @returns {Number} + * @example + * // get shadow offset y
+ * var shadowOffsetY = shape.shadowOffsetY();

+ * + * // set shadow offset y
+ * shape.shadowOffsetY(5); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'fillPatternImage'); + + /** + * get/set fill pattern image + * @name fillPatternImage + * @method + * @memberof Kinetic.Shape.prototype + * @param {Image} image object + * @returns {Image} + * @example + * // get fill pattern image
+ * var fillPatternImage = shape.fillPatternImage();

+ * + * // set fill pattern image
+ * var imageObj = new Image();
+ * imageObj.onload = function() {
+ * shape.fillPatternImage(imageObj);
+ * };
+ * imageObj.src = 'path/to/image/jpg'; + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'fill'); + + /** + * get/set fill color + * @name fill + * @method + * @memberof Kinetic.Shape.prototype + * @param {String} color + * @returns {String} + * @example + * // get fill color
+ * var fill = shape.fill();

+ * + * // set fill color with color string
+ * shape.fill('green');

+ * + * // set fill color with hex
+ * shape.fill('#00ff00');

+ * + * // set fill color with rgb
+ * shape.fill('rgb(0,255,0)');

+ * + * // set fill color with rgba and make it 50% opaque
+ * shape.fill('rgba(0,255,0,0.5'); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'fillRed', 0, Kinetic.Validators.RGBComponent); + + /** + * get/set fill red component + * @name fillRed + * @method + * @memberof Kinetic.Shape.prototype + * @param {Integer} red + * @returns {Integer} + * @example + * // get fill red component
+ * var fillRed = shape.fillRed();

+ * + * // set fill red component
+ * shape.fillRed(0); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'fillGreen', 0, Kinetic.Validators.RGBComponent); + + /** + * get/set fill green component + * @name fillGreen + * @method + * @memberof Kinetic.Shape.prototype + * @param {Integer} green + * @returns {Integer} + * @example + * // get fill green component
+ * var fillGreen = shape.fillGreen();

+ * + * // set fill green component
+ * shape.fillGreen(255); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'fillBlue', 0, Kinetic.Validators.RGBComponent); + + /** + * get/set fill blue component + * @name fillBlue + * @method + * @memberof Kinetic.Shape.prototype + * @param {Integer} blue + * @returns {Integer} + * @example + * // get fill blue component
+ * var fillBlue = shape.fillBlue();

+ * + * // set fill blue component
+ * shape.fillBlue(0); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'fillAlpha', 1, Kinetic.Validators.alphaComponent); + + /** + * get/set fill alpha component. Alpha is a real number between 0 and 1. The default + * is 1. + * @name fillAlpha + * @method + * @memberof Kinetic.Shape.prototype + * @param {Number} alpha + * @returns {Number} + * @example + * // get fill alpha component
+ * var fillAlpha = shape.fillAlpha();

+ * + * // set fill alpha component
+ * shape.fillAlpha(0.5); + */ + + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'fillPatternX', 0); + + /** + * get/set fill pattern x + * @name fillPatternX + * @method + * @memberof Kinetic.Shape.prototype + * @param {Number} x + * @returns {Number} + * @example + * // get fill pattern x
+ * var fillPatternX = shape.fillPatternX();

+ * + * // set fill pattern x
+ * shape.fillPatternX(20); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'fillPatternY', 0); + + /** + * get/set fill pattern y + * @name fillPatternY + * @method + * @memberof Kinetic.Shape.prototype + * @param {Number} y + * @returns {Number} + * @example + * // get fill pattern y
+ * var fillPatternY = shape.fillPatternY();

+ * + * // set fill pattern y
+ * shape.fillPatternY(20); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'fillLinearGradientColorStops'); + + /** + * get/set fill linear gradient color stops + * @name fillLinearGradientColorStops + * @method + * @memberof Kinetic.Shape.prototype + * @param {Array} colorStops + * @returns {Array} colorStops + * @example + * // get fill linear gradient color stops
+ * var colorStops = shape.fillLinearGradientColorStops();

+ * + * // create a linear gradient that starts with red, changes to blue
+ * // halfway through, and then changes to green
+ * shape.fillLinearGradientColorStops(0, 'red', 0.5, 'blue', 1, 'green'); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'fillRadialGradientStartRadius', 0); + + /** + * get/set fill radial gradient start radius + * @name fillRadialGradientStartRadius + * @method + * @memberof Kinetic.Shape.prototype + * @param {Number} radius + * @returns {Number} + * @example + * // get radial gradient start radius
+ * var startRadius = shape.fillRadialGradientStartRadius();

+ * + * // set radial gradient start radius
+ * shape.fillRadialGradientStartRadius(0); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'fillRadialGradientEndRadius', 0); + + /** + * get/set fill radial gradient end radius + * @name fillRadialGradientEndRadius + * @method + * @memberof Kinetic.Shape.prototype + * @param {Number} radius + * @returns {Number} + * @example + * // get radial gradient end radius
+ * var endRadius = shape.fillRadialGradientEndRadius();

+ * + * // set radial gradient end radius
+ * shape.fillRadialGradientEndRadius(100); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'fillRadialGradientColorStops'); + + /** + * get/set fill radial gradient color stops + * @name fillRadialGradientColorStops + * @method + * @memberof Kinetic.Shape.prototype + * @param {Number} colorStops + * @returns {Array} + * @example + * // get fill radial gradient color stops
+ * var colorStops = shape.fillRadialGradientColorStops();

+ * + * // create a radial gradient that starts with red, changes to blue
+ * // halfway through, and then changes to green
+ * shape.fillRadialGradientColorStops(0, 'red', 0.5, 'blue', 1, 'green'); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'fillPatternRepeat', 'repeat'); + + /** + * get/set fill pattern repeat. Can be 'repeat', 'repeat-x', 'repeat-y', or 'no-repeat'. The default is 'repeat' + * @name fillPatternRepeat + * @method + * @memberof Kinetic.Shape.prototype + * @param {String} repeat + * @returns {String} + * @example + * // get fill pattern repeat
+ * var repeat = shape.fillPatternRepeat();

+ * + * // repeat pattern in x direction only
+ * shape.fillPatternRepeat('repeat-x');

+ * + * // do not repeat the pattern
+ * shape.fillPatternRepeat('no repeat'); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'fillEnabled', true); + + /** + * get/set fill enabled flag + * @name fillEnabled + * @method + * @memberof Kinetic.Shape.prototype + * @param {Boolean} enabled + * @returns {Boolean} + * @example + * // get fill enabled flag
+ * var fillEnabled = shape.fillEnabled();

+ * + * // disable fill
+ * shape.fillEnabled(false);

+ * + * // enable fill
+ * shape.fillEnabled(true); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'strokeEnabled', true); + + /** + * get/set stroke enabled flag + * @name strokeEnabled + * @method + * @memberof Kinetic.Shape.prototype + * @param {Boolean} enabled + * @returns {Boolean} + * @example + * // get stroke enabled flag
+ * var strokeEnabled = shape.strokeEnabled();

+ * + * // disable stroke
+ * shape.strokeEnabled(false);

+ * + * // enable stroke
+ * shape.strokeEnabled(true); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'shadowEnabled', true); + + /** + * get/set shadow enabled flag + * @name shadowEnabled + * @method + * @memberof Kinetic.Shape.prototype + * @param {Boolean} enabled + * @returns {Boolean} + * @example + * // get shadow enabled flag
+ * var shadowEnabled = shape.shadowEnabled();

+ * + * // disable shadow
+ * shape.shadowEnabled(false);

+ * + * // enable shadow
+ * shape.shadowEnabled(true); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'dashEnabled', true); + + /** + * get/set dash enabled flag + * @name dashEnabled + * @method + * @memberof Kinetic.Shape.prototype + * @param {Boolean} enabled + * @returns {Boolean} + * @example + * // get dash enabled flag
+ * var dashEnabled = shape.dashEnabled();

+ * + * // disable dash
+ * shape.dashEnabled(false);

+ * + * // enable dash
+ * shape.dashEnabled(true); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'strokeScaleEnabled', true); + + /** + * get/set strokeScale enabled flag + * @name strokeScaleEnabled + * @method + * @memberof Kinetic.Shape.prototype + * @param {Boolean} enabled + * @returns {Boolean} + * @example + * // get stroke scale enabled flag
+ * var strokeScaleEnabled = shape.strokeScaleEnabled();

+ * + * // disable stroke scale
+ * shape.strokeScaleEnabled(false);

+ * + * // enable stroke scale
+ * shape.strokeScaleEnabled(true); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'fillPriority', 'color'); + + /** + * get/set fill priority. can be color, pattern, linear-gradient, or radial-gradient. The default is color. + * This is handy if you want to toggle between different fill types. + * @name fillPriority + * @method + * @memberof Kinetic.Shape.prototype + * @param {String} priority + * @returns {String} + * @example + * // get fill priority
+ * var fillPriority = shape.fillPriority();

+ * + * // set fill priority
+ * shape.fillPriority('linear-gradient'); + */ + + Kinetic.Factory.addComponentsGetterSetter(Kinetic.Shape, 'fillPatternOffset', ['x', 'y']); + + /** + * get/set fill pattern offset + * @name fillPatternOffset + * @method + * @memberof Kinetic.Shape.prototype + * @param {Object} offset + * @param {Number} offset.x + * @param {Number} offset.y + * @returns {Object} + * @example + * // get fill pattern offset
+ * var patternOffset = shape.fillPatternOffset();

+ * + * // set fill pattern offset
+ * shape.fillPatternOffset({
+ * x: 20
+ * y: 10
+ * }); + */ + + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'fillPatternOffsetX', 0); + /** + * get/set fill pattern offset x + * @name fillPatternOffsetX + * @method + * @memberof Kinetic.Shape.prototype + * @param {Number} x + * @returns {Number} + * @example + * // get fill pattern offset x
+ * var patternOffsetX = shape.fillPatternOffsetX();

+ * + * // set fill pattern offset x
+ * shape.fillPatternOffsetX(20); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'fillPatternOffsetY', 0); + /** + * get/set fill pattern offset y + * @name fillPatternOffsetY + * @method + * @memberof Kinetic.Shape.prototype + * @param {Number} y + * @returns {Number} + * @example + * // get fill pattern offset y
+ * var patternOffsetY = shape.fillPatternOffsetY();

+ * + * // set fill pattern offset y
+ * shape.fillPatternOffsetY(10); + */ + + Kinetic.Factory.addComponentsGetterSetter(Kinetic.Shape, 'fillPatternScale', ['x', 'y']); + + /** + * get/set fill pattern scale + * @name fillPatternScale + * @method + * @memberof Kinetic.Shape.prototype + * @param {Object} scale + * @param {Number} scale.x + * @param {Number} scale.y + * @returns {Object} + * @example + * // get fill pattern scale
+ * var patternScale = shape.fillPatternScale();

+ * + * // set fill pattern scale
+ * shape.fillPatternScale({
+ * x: 2
+ * y: 2
+ * }); + */ + + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'fillPatternScaleX', 1); + /** + * get/set fill pattern scale x + * @name fillPatternScaleX + * @method + * @memberof Kinetic.Shape.prototype + * @param {Number} x + * @returns {Number} + * @example + * // get fill pattern scale x
+ * var patternScaleX = shape.fillPatternScaleX();

+ * + * // set fill pattern scale x
+ * shape.fillPatternScaleX(2); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'fillPatternScaleY', 1); + /** + * get/set fill pattern scale y + * @name fillPatternScaleY + * @method + * @memberof Kinetic.Shape.prototype + * @param {Number} y + * @returns {Number} + * @example + * // get fill pattern scale y
+ * var patternScaleY = shape.fillPatternScaleY();

+ * + * // set fill pattern scale y
+ * shape.fillPatternScaleY(2); + */ + + Kinetic.Factory.addComponentsGetterSetter(Kinetic.Shape, 'fillLinearGradientStartPoint', ['x', 'y']); + + /** + * get/set fill linear gradient start point + * @name fillLinearGradientStartPoint + * @method + * @memberof Kinetic.Shape.prototype + * @param {Object} startPoint + * @param {Number} startPoint.x + * @param {Number} startPoint.y + * @returns {Object} + * @example + * // get fill linear gradient start point
+ * var startPoint = shape.fillLinearGradientStartPoint();

+ * + * // set fill linear gradient start point
+ * shape.fillLinearGradientStartPoint({
+ * x: 20
+ * y: 10
+ * }); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'fillLinearGradientStartPointX', 0); + /** + * get/set fill linear gradient start point x + * @name fillLinearGradientStartPointX + * @method + * @memberof Kinetic.Shape.prototype + * @param {Number} x + * @returns {Number} + * @example + * // get fill linear gradient start point x
+ * var startPointX = shape.fillLinearGradientStartPointX();

+ * + * // set fill linear gradient start point x
+ * shape.fillLinearGradientStartPointX(20); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'fillLinearGradientStartPointY', 0); + /** + * get/set fill linear gradient start point y + * @name fillLinearGradientStartPointY + * @method + * @memberof Kinetic.Shape.prototype + * @param {Number} y + * @returns {Number} + * @example + * // get fill linear gradient start point y
+ * var startPointY = shape.fillLinearGradientStartPointY();

+ * + * // set fill linear gradient start point y
+ * shape.fillLinearGradientStartPointY(20); + */ + + Kinetic.Factory.addComponentsGetterSetter(Kinetic.Shape, 'fillLinearGradientEndPoint', ['x', 'y']); + + /** + * get/set fill linear gradient end point + * @name fillLinearGradientEndPoint + * @method + * @memberof Kinetic.Shape.prototype + * @param {Object} endPoint + * @param {Number} endPoint.x + * @param {Number} endPoint.y + * @returns {Object} + * @example + * // get fill linear gradient end point
+ * var endPoint = shape.fillLinearGradientEndPoint();

+ * + * // set fill linear gradient end point
+ * shape.fillLinearGradientEndPoint({
+ * x: 20
+ * y: 10
+ * }); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'fillLinearGradientEndPointX', 0); + /** + * get/set fill linear gradient end point x + * @name fillLinearGradientEndPointX + * @method + * @memberof Kinetic.Shape.prototype + * @param {Number} x + * @returns {Number} + * @example + * // get fill linear gradient end point x
+ * var endPointX = shape.fillLinearGradientEndPointX();

+ * + * // set fill linear gradient end point x
+ * shape.fillLinearGradientEndPointX(20); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'fillLinearGradientEndPointY', 0); + /** + * get/set fill linear gradient end point y + * @name fillLinearGradientEndPointY + * @method + * @memberof Kinetic.Shape.prototype + * @param {Number} y + * @returns {Number} + * @example + * // get fill linear gradient end point y
+ * var endPointY = shape.fillLinearGradientEndPointY();

+ * + * // set fill linear gradient end point y
+ * shape.fillLinearGradientEndPointY(20); + */ + + Kinetic.Factory.addComponentsGetterSetter(Kinetic.Shape, 'fillRadialGradientStartPoint', ['x', 'y']); + + /** + * get/set fill radial gradient start point + * @name fillRadialGradientStartPoint + * @method + * @memberof Kinetic.Shape.prototype + * @param {Object} startPoint + * @param {Number} startPoint.x + * @param {Number} startPoint.y + * @returns {Object} + * @example + * // get fill radial gradient start point
+ * var startPoint = shape.fillRadialGradientStartPoint();

+ * + * // set fill radial gradient start point
+ * shape.fillRadialGradientStartPoint({
+ * x: 20
+ * y: 10
+ * }); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'fillRadialGradientStartPointX', 0); + /** + * get/set fill radial gradient start point x + * @name fillRadialGradientStartPointX + * @method + * @memberof Kinetic.Shape.prototype + * @param {Number} x + * @returns {Number} + * @example + * // get fill radial gradient start point x
+ * var startPointX = shape.fillRadialGradientStartPointX();

+ * + * // set fill radial gradient start point x
+ * shape.fillRadialGradientStartPointX(20); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'fillRadialGradientStartPointY', 0); + /** + * get/set fill radial gradient start point y + * @name fillRadialGradientStartPointY + * @method + * @memberof Kinetic.Shape.prototype + * @param {Number} y + * @returns {Number} + * @example + * // get fill radial gradient start point y
+ * var startPointY = shape.fillRadialGradientStartPointY();

+ * + * // set fill radial gradient start point y
+ * shape.fillRadialGradientStartPointY(20); + */ + + Kinetic.Factory.addComponentsGetterSetter(Kinetic.Shape, 'fillRadialGradientEndPoint', ['x', 'y']); + + /** + * get/set fill radial gradient end point + * @name fillRadialGradientEndPoint + * @method + * @memberof Kinetic.Shape.prototype + * @param {Object} endPoint + * @param {Number} endPoint.x + * @param {Number} endPoint.y + * @returns {Object} + * @example + * // get fill radial gradient end point
+ * var endPoint = shape.fillRadialGradientEndPoint();

+ * + * // set fill radial gradient end point
+ * shape.fillRadialGradientEndPoint({
+ * x: 20
+ * y: 10
+ * }); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'fillRadialGradientEndPointX', 0); + /** + * get/set fill radial gradient end point x + * @name fillRadialGradientEndPointX + * @method + * @memberof Kinetic.Shape.prototype + * @param {Number} x + * @returns {Number} + * @example + * // get fill radial gradient end point x
+ * var endPointX = shape.fillRadialGradientEndPointX();

+ * + * // set fill radial gradient end point x
+ * shape.fillRadialGradientEndPointX(20); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'fillRadialGradientEndPointY', 0); + /** + * get/set fill radial gradient end point y + * @name fillRadialGradientEndPointY + * @method + * @memberof Kinetic.Shape.prototype + * @param {Number} y + * @returns {Number} + * @example + * // get fill radial gradient end point y
+ * var endPointY = shape.fillRadialGradientEndPointY();

+ * + * // set fill radial gradient end point y
+ * shape.fillRadialGradientEndPointY(20); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Shape, 'fillPatternRotation', 0); + + /** + * get/set fill pattern rotation in degrees + * @name fillPatternRotation + * @method + * @memberof Kinetic.Shape.prototype + * @param {Number} rotation + * @returns {Kinetic.Shape} + * @example + * // get fill pattern rotation
+ * var patternRotation = shape.fillPatternRotation();

+ * + * // set fill pattern rotation
+ * shape.fillPatternRotation(20); + */ + + + Kinetic.Factory.backCompat(Kinetic.Shape, { + dashArray: 'dash', + getDashArray: 'getDash', + setDashArray: 'getDash', + + drawFunc: 'sceneFunc', + getDrawFunc: 'getSceneFunc', + setDrawFunc: 'setSceneFunc', + + drawHitFunc: 'hitFunc', + getDrawHitFunc: 'getHitFunc', + setDrawHitFunc: 'setHitFunc' + }); + + Kinetic.Collection.mapMethods(Kinetic.Shape); +})(); +;(function() { + // CONSTANTS + var STAGE = 'Stage', + STRING = 'string', + PX = 'px', + + MOUSEOUT = 'mouseout', + MOUSELEAVE = 'mouseleave', + MOUSEOVER = 'mouseover', + MOUSEENTER = 'mouseenter', + MOUSEMOVE = 'mousemove', + MOUSEDOWN = 'mousedown', + MOUSEUP = 'mouseup', + CLICK = 'click', + DBL_CLICK = 'dblclick', + TOUCHSTART = 'touchstart', + TOUCHEND = 'touchend', + TAP = 'tap', + DBL_TAP = 'dbltap', + TOUCHMOVE = 'touchmove', + + CONTENT_MOUSEOUT = 'contentMouseout', + CONTENT_MOUSELEAVE = 'contentMouseleave', + CONTENT_MOUSEOVER = 'contentMouseover', + CONTENT_MOUSEENTER = 'contentMouseenter', + CONTENT_MOUSEMOVE = 'contentMousemove', + CONTENT_MOUSEDOWN = 'contentMousedown', + CONTENT_MOUSEUP = 'contentMouseup', + CONTENT_CLICK = 'contentClick', + CONTENT_DBL_CLICK = 'contentDblclick', + CONTENT_TOUCHSTART = 'contentTouchstart', + CONTENT_TOUCHEND = 'contentTouchend', + CONTENT_TAP = 'contentTap', + CONTENT_DBL_TAP = 'contentDbltap', + CONTENT_TOUCHMOVE = 'contentTouchmove', + + DIV = 'div', + RELATIVE = 'relative', + INLINE_BLOCK = 'inline-block', + KINETICJS_CONTENT = 'kineticjs-content', + SPACE = ' ', + UNDERSCORE = '_', + CONTAINER = 'container', + EMPTY_STRING = '', + EVENTS = [MOUSEDOWN, MOUSEMOVE, MOUSEUP, MOUSEOUT, TOUCHSTART, TOUCHMOVE, TOUCHEND, MOUSEOVER], + + // cached variables + eventsLength = EVENTS.length; + + function addEvent(ctx, eventName) { + ctx.content.addEventListener(eventName, function(evt) { + ctx[UNDERSCORE + eventName](evt); + }, false); + } + + Kinetic.Util.addMethods(Kinetic.Stage, { + ___init: function(config) { + this.nodeType = STAGE; + // call super constructor + Kinetic.Container.call(this, config); + this._id = Kinetic.idCounter++; + this._buildDOM(); + this._bindContentEvents(); + this._enableNestedTransforms = false; + Kinetic.stages.push(this); + }, + _validateAdd: function(child) { + if (child.getType() !== 'Layer') { + Kinetic.Util.error('You may only add layers to the stage.'); + } + }, + /** + * set container dom element which contains the stage wrapper div element + * @method + * @memberof Kinetic.Stage.prototype + * @param {DomElement} container can pass in a dom element or id string + */ + setContainer: function(container) { + if( typeof container === STRING) { + container = document.getElementById(container); + } + this._setAttr(CONTAINER, container); + return this; + }, + shouldDrawHit: function() { + return true; + }, + draw: function() { + Kinetic.Node.prototype.draw.call(this); + return this; + }, + /** + * draw layer scene graphs + * @name draw + * @method + * @memberof Kinetic.Stage.prototype + */ + + /** + * draw layer hit graphs + * @name drawHit + * @method + * @memberof Kinetic.Stage.prototype + */ + + /** + * set height + * @method + * @memberof Kinetic.Stage.prototype + * @param {Number} height + */ + setHeight: function(height) { + Kinetic.Node.prototype.setHeight.call(this, height); + this._resizeDOM(); + return this; + }, + /** + * set width + * @method + * @memberof Kinetic.Stage.prototype + * @param {Number} width + */ + setWidth: function(width) { + Kinetic.Node.prototype.setWidth.call(this, width); + this._resizeDOM(); + return this; + }, + /** + * clear all layers + * @method + * @memberof Kinetic.Stage.prototype + */ + clear: function() { + var layers = this.children, + len = layers.length, + n; + + for(n = 0; n < len; n++) { + layers[n].clear(); + } + return this; + }, + /** + * remove stage + * @method + * @memberof Kinetic.Stage.prototype + */ + destroy: function() { + var content = this.content; + Kinetic.Container.prototype.destroy.call(this); + + if(content && Kinetic.Util._isInDocument(content)) { + this.getContainer().removeChild(content); + } + }, + /** + * get pointer position which can be a touch position or mouse position + * @method + * @memberof Kinetic.Stage.prototype + * @returns {Object} + */ + getPointerPosition: function() { + return this.pointerPos; + }, + getStage: function() { + return this; + }, + /** + * get stage content div element which has the + * the class name "kineticjs-content" + * @method + * @memberof Kinetic.Stage.prototype + */ + getContent: function() { + return this.content; + }, + /** + * Creates a composite data URL and requires a callback because the composite is generated asynchronously. + * @method + * @memberof Kinetic.Stage.prototype + * @param {Object} config + * @param {Function} config.callback function executed when the composite has completed + * @param {String} [config.mimeType] can be "image/png" or "image/jpeg". + * "image/png" is the default + * @param {Number} [config.x] x position of canvas section + * @param {Number} [config.y] y position of canvas section + * @param {Number} [config.width] width of canvas section + * @param {Number} [config.height] height of canvas section + * @param {Number} [config.quality] jpeg quality. If using an "image/jpeg" mimeType, + * you can specify the quality from 0 to 1, where 0 is very poor quality and 1 + * is very high quality + */ + toDataURL: function(config) { + config = config || {}; + + var mimeType = config.mimeType || null, + quality = config.quality || null, + x = config.x || 0, + y = config.y || 0, + canvas = new Kinetic.SceneCanvas({ + width: config.width || this.getWidth(), + height: config.height || this.getHeight(), + pixelRatio: 1 + }), + _context = canvas.getContext()._context, + layers = this.children; + + if(x || y) { + _context.translate(-1 * x, -1 * y); + } + + function drawLayer(n) { + var layer = layers[n], + layerUrl = layer.toDataURL(), + imageObj = new Image(); + + imageObj.onload = function() { + _context.drawImage(imageObj, 0, 0); + + if(n < layers.length - 1) { + drawLayer(n + 1); + } + else { + config.callback(canvas.toDataURL(mimeType, quality)); + } + }; + imageObj.src = layerUrl; + } + drawLayer(0); + }, + /** + * converts stage into an image. + * @method + * @memberof Kinetic.Stage.prototype + * @param {Object} config + * @param {Function} config.callback function executed when the composite has completed + * @param {String} [config.mimeType] can be "image/png" or "image/jpeg". + * "image/png" is the default + * @param {Number} [config.x] x position of canvas section + * @param {Number} [config.y] y position of canvas section + * @param {Number} [config.width] width of canvas section + * @param {Number} [config.height] height of canvas section + * @param {Number} [config.quality] jpeg quality. If using an "image/jpeg" mimeType, + * you can specify the quality from 0 to 1, where 0 is very poor quality and 1 + * is very high quality + */ + toImage: function(config) { + var cb = config.callback; + + config.callback = function(dataUrl) { + Kinetic.Util._getImage(dataUrl, function(img) { + cb(img); + }); + }; + this.toDataURL(config); + }, + /** + * get visible intersection shape. This is the preferred + * method for determining if a point intersects a shape or not + * @method + * @memberof Kinetic.Stage.prototype + * @param {Object} pos + * @param {Number} pos.x + * @param {Number} pos.y + * @returns {Kinetic.Shape} + */ + getIntersection: function(pos) { + var layers = this.getChildren(), + len = layers.length, + end = len - 1, + n, shape; + + for(n = end; n >= 0; n--) { + shape = layers[n].getIntersection(pos); + if (shape) { + return shape; + } + } + + return null; + }, + _resizeDOM: function() { + if(this.content) { + var width = this.getWidth(), + height = this.getHeight(), + layers = this.getChildren(), + len = layers.length, + n, layer; + + // set content dimensions + this.content.style.width = width + PX; + this.content.style.height = height + PX; + + this.bufferCanvas.setSize(width, height); + this.bufferHitCanvas.setSize(width, height); + + // set layer dimensions + for(n = 0; n < len; n++) { + layer = layers[n]; + layer.getCanvas().setSize(width, height); + layer.hitCanvas.setSize(width, height); + layer.draw(); + } + } + }, + /** + * add layer to stage + * @method + * @memberof Kinetic.Stage.prototype + * @param {Kinetic.Layer} layer + */ + add: function(layer) { + Kinetic.Container.prototype.add.call(this, layer); + layer.canvas.setSize(this.attrs.width, this.attrs.height); + layer.hitCanvas.setSize(this.attrs.width, this.attrs.height); + + // draw layer and append canvas to container + layer.draw(); + this.content.appendChild(layer.canvas._canvas); + + // chainable + return this; + }, + getParent: function() { + return null; + }, + getLayer: function() { + return null; + }, + /** + * returns a {@link Kinetic.Collection} of layers + * @method + * @memberof Kinetic.Stage.prototype + */ + getLayers: function() { + return this.getChildren(); + }, + _bindContentEvents: function() { + var that = this, + n; + + for (n = 0; n < eventsLength; n++) { + addEvent(this, EVENTS[n]); + } + }, + _mouseover: function(evt) { + this._fire(CONTENT_MOUSEOVER, evt); + }, + _mouseout: function(evt) { + this._setPointerPosition(evt); + var targetShape = this.targetShape; + + if(targetShape && !Kinetic.isDragging()) { + targetShape._fireAndBubble(MOUSEOUT, evt); + targetShape._fireAndBubble(MOUSELEAVE, evt); + this.targetShape = null; + } + this.pointerPos = undefined; + + this._fire(CONTENT_MOUSEOUT, evt); + }, + _mousemove: function(evt) { + this._setPointerPosition(evt); + var dd = Kinetic.DD, + shape = this.getIntersection(this.getPointerPosition()); + + if(shape && shape.isListening()) { + if(!Kinetic.isDragging() && (!this.targetShape || this.targetShape._id !== shape._id)) { + if(this.targetShape) { + this.targetShape._fireAndBubble(MOUSEOUT, evt, shape); + this.targetShape._fireAndBubble(MOUSELEAVE, evt, shape); + } + shape._fireAndBubble(MOUSEOVER, evt, this.targetShape); + shape._fireAndBubble(MOUSEENTER, evt, this.targetShape); + this.targetShape = shape; + } + else { + shape._fireAndBubble(MOUSEMOVE, evt); + } + } + /* + * if no shape was detected, clear target shape and try + * to run mouseout from previous target shape + */ + else { + if(this.targetShape && !Kinetic.isDragging()) { + this.targetShape._fireAndBubble(MOUSEOUT, evt); + this.targetShape._fireAndBubble(MOUSELEAVE, evt); + this.targetShape = null; + } + + } + + // content event + this._fire(CONTENT_MOUSEMOVE, evt); + + if(dd) { + dd._drag(evt); + } + + // always call preventDefault for desktop events because some browsers + // try to drag and drop the canvas element + if (evt.preventDefault) { + evt.preventDefault(); + } + }, + _mousedown: function(evt) { + this._setPointerPosition(evt); + var shape = this.getIntersection(this.getPointerPosition()); + + Kinetic.listenClickTap = true; + + if (shape && shape.isListening()) { + this.clickStartShape = shape; + shape._fireAndBubble(MOUSEDOWN, evt); + } + + // content event + this._fire(CONTENT_MOUSEDOWN, evt); + + // always call preventDefault for desktop events because some browsers + // try to drag and drop the canvas element + if (evt.preventDefault) { + evt.preventDefault(); + } + }, + _mouseup: function(evt) { + this._setPointerPosition(evt); + var that = this, + shape = this.getIntersection(this.getPointerPosition()), + clickStartShape = this.clickStartShape, + fireDblClick = false; + + if(Kinetic.inDblClickWindow) { + fireDblClick = true; + Kinetic.inDblClickWindow = false; + } + else { + Kinetic.inDblClickWindow = true; + } + + setTimeout(function() { + Kinetic.inDblClickWindow = false; + }, Kinetic.dblClickWindow); + + if (shape && shape.isListening()) { + shape._fireAndBubble(MOUSEUP, evt); + + // detect if click or double click occurred + if(Kinetic.listenClickTap && clickStartShape && clickStartShape._id === shape._id) { + shape._fireAndBubble(CLICK, evt); + + if(fireDblClick) { + shape._fireAndBubble(DBL_CLICK, evt); + } + } + } + // content events + this._fire(CONTENT_MOUSEUP, evt); + if (Kinetic.listenClickTap) { + this._fire(CONTENT_CLICK, evt); + if(fireDblClick) { + this._fire(CONTENT_DBL_CLICK, evt); + } + } + + Kinetic.listenClickTap = false; + + // always call preventDefault for desktop events because some browsers + // try to drag and drop the canvas element + if (evt.preventDefault) { + evt.preventDefault(); + } + }, + _touchstart: function(evt) { + this._setPointerPosition(evt); + var shape = this.getIntersection(this.getPointerPosition()); + + Kinetic.listenClickTap = true; + + if (shape && shape.isListening()) { + this.tapStartShape = shape; + shape._fireAndBubble(TOUCHSTART, evt); + + // only call preventDefault if the shape is listening for events + if (shape.isListening() && evt.preventDefault) { + evt.preventDefault(); + } + } + // content event + this._fire(CONTENT_TOUCHSTART, evt); + }, + _touchend: function(evt) { + this._setPointerPosition(evt); + var that = this, + shape = this.getIntersection(this.getPointerPosition()); + fireDblClick = false; + + if(Kinetic.inDblClickWindow) { + fireDblClick = true; + Kinetic.inDblClickWindow = false; + } + else { + Kinetic.inDblClickWindow = true; + } + + setTimeout(function() { + Kinetic.inDblClickWindow = false; + }, Kinetic.dblClickWindow); + + if (shape && shape.isListening()) { + shape._fireAndBubble(TOUCHEND, evt); + + // detect if tap or double tap occurred + if(Kinetic.listenClickTap && shape._id === this.tapStartShape._id) { + shape._fireAndBubble(TAP, evt); + + if(fireDblClick) { + shape._fireAndBubble(DBL_TAP, evt); + } + } + // only call preventDefault if the shape is listening for events + if (shape.isListening() && evt.preventDefault) { + evt.preventDefault(); + } + } + // content events + if (Kinetic.listenClickTap) { + this._fire(CONTENT_TOUCHEND, evt); + if(fireDblClick) { + this._fire(CONTENT_DBL_TAP, evt); + } + } + + Kinetic.listenClickTap = false; + }, + _touchmove: function(evt) { + this._setPointerPosition(evt); + var dd = Kinetic.DD, + shape = this.getIntersection(this.getPointerPosition()); + + if (shape && shape.isListening()) { + shape._fireAndBubble(TOUCHMOVE, evt); + + // only call preventDefault if the shape is listening for events + if (shape.isListening() && evt.preventDefault) { + evt.preventDefault(); + } + } + this._fire(CONTENT_TOUCHMOVE, evt); + + // start drag and drop + if(dd) { + dd._drag(evt); + } + }, + _setPointerPosition: function(evt) { + var evt = evt ? evt : window.event, + contentPosition = this._getContentPosition(), + offsetX = evt.offsetX, + clientX = evt.clientX, + x = null, + y = null, + touch; + + // touch events + if(evt.touches !== undefined) { + // currently, only handle one finger + if (evt.touches.length === 1) { + + touch = evt.touches[0]; + + // get the information for finger #1 + x = touch.clientX - contentPosition.left; + y = touch.clientY - contentPosition.top; + } + } + // mouse events + else { + // if offsetX is defined, assume that offsetY is defined as well + if (offsetX !== undefined) { + x = offsetX; + y = evt.offsetY; + } + // we unforunately have to use UA detection here because accessing + // the layerX or layerY properties in newer veresions of Chrome + // throws a JS warning. layerX and layerY are required for FF + // when the container is transformed via CSS. + else if (Kinetic.UA.browser === 'mozilla') { + x = evt.layerX; + y = evt.layerY; + } + // if clientX is defined, assume that clientY is defined as well + else if (clientX !== undefined && contentPosition) { + x = clientX - contentPosition.left; + y = evt.clientY - contentPosition.top; + } + } + + if (x !== null && y !== null) { + this.pointerPos = { + x: x, + y: y + }; + } + }, + _getContentPosition: function() { + var rect = this.content.getBoundingClientRect ? this.content.getBoundingClientRect() : { top: 0, left: 0 }; + return { + top: rect.top, + left: rect.left + }; + }, + _buildDOM: function() { + var container = this.getContainer(); + + // clear content inside container + container.innerHTML = EMPTY_STRING; + + // content + this.content = document.createElement(DIV); + this.content.style.position = RELATIVE; + this.content.style.display = INLINE_BLOCK; + this.content.className = KINETICJS_CONTENT; + this.content.setAttribute('role', 'presentation'); + container.appendChild(this.content); + + // the buffer canvas pixel ratio must be 1 because it is used as an + // intermediate canvas before copying the result onto a scene canvas. + // not setting it to 1 will result in an over compensation + this.bufferCanvas = new Kinetic.SceneCanvas({ + pixelRatio: 1 + }); + this.bufferHitCanvas = new Kinetic.HitCanvas(); + + this._resizeDOM(); + }, + _onContent: function(typesStr, handler) { + var types = typesStr.split(SPACE), + len = types.length, + n, baseEvent; + + for(n = 0; n < len; n++) { + baseEvent = types[n]; + this.content.addEventListener(baseEvent, handler, false); + } + } + }); + Kinetic.Util.extend(Kinetic.Stage, Kinetic.Container); + + // add getters and setters + Kinetic.Factory.addGetter(Kinetic.Stage, 'container'); + Kinetic.Factory.addOverloadedGetterSetter(Kinetic.Stage, 'container'); + + /** + * get container DOM element + * @name container + * @method + * @memberof Kinetic.Stage.prototype + * @returns {DomElement} container + * @example + * // get container
+ * var container = stage.container();

+ * + * // set container
+ * var container = document.createElement('div');
+ * body.appendChild(container);
+ * stage.container(container); + */ + +})(); +;(function() { + // constants + var HASH = '#', + BEFORE_DRAW ='beforeDraw', + DRAW = 'draw', + + /* + * 2 - 3 - 4 + * | | + * 1 - 0 5 + * | + * 8 - 7 - 6 + */ + INTERSECTION_OFFSETS = [ + {x: 0, y: 0}, // 0 + {x: -1, y: 0}, // 1 + {x: -1, y: -1}, // 2 + {x: 0, y: -1}, // 3 + {x: 1, y: -1}, // 4 + {x: 1, y: 0}, // 5 + {x: 1, y: 1}, // 6 + {x: 0, y: 1}, // 7 + {x: -1, y: 1} // 8 + ], + INTERSECTION_OFFSETS_LEN = INTERSECTION_OFFSETS.length; + + + Kinetic.Util.addMethods(Kinetic.Layer, { + ___init: function(config) { + this.nodeType = 'Layer'; + this.canvas = new Kinetic.SceneCanvas(); + this.hitCanvas = new Kinetic.HitCanvas(); + // call super constructor + Kinetic.Container.call(this, config); + }, + _validateAdd: function(child) { + var type = child.getType(); + if (type !== 'Group' && type !== 'Shape') { + Kinetic.Util.error('You may only add groups and shapes to a layer.'); + } + }, + /** + * get visible intersection shape. This is the preferred + * method for determining if a point intersects a shape or not + * @method + * @memberof Kinetic.Layer.prototype + * @param {Object} pos + * @param {Number} pos.x + * @param {Number} pos.y + * @returns {Kinetic.Shape} + */ + getIntersection: function(pos) { + var obj, i, intersectionOffset, shape; + + if(this.isVisible()) { + for (i=0; i 0) { + return { + antialiased: true + }; + } + // empty pixel + else { + return {}; + } + }, + drawScene: function(can) { + var layer = this.getLayer(), + canvas = can || (layer && layer.getCanvas()); + + this._fire(BEFORE_DRAW, { + node: this + }); + + if(this.getClearBeforeDraw()) { + canvas.getContext().clear(); + } + + Kinetic.Container.prototype.drawScene.call(this, canvas); + + this._fire(DRAW, { + node: this + }); + + return this; + }, + drawHit: function(can) { + var layer = this.getLayer(), + canvas = can || (layer && layer.hitCanvas); + + if(layer && layer.getClearBeforeDraw()) { + layer.getHitCanvas().getContext().clear(); + } + + Kinetic.Container.prototype.drawHit.call(this, canvas); + return this; + }, + /** + * get layer canvas + * @method + * @memberof Kinetic.Layer.prototype + */ + getCanvas: function() { + return this.canvas; + }, + /** + * get layer hit canvas + * @method + * @memberof Kinetic.Layer.prototype + */ + getHitCanvas: function() { + return this.hitCanvas; + }, + /** + * get layer canvas context + * @method + * @memberof Kinetic.Layer.prototype + */ + getContext: function() { + return this.getCanvas().getContext(); + }, + /** + * clear scene and hit canvas contexts tied to the layer + * @method + * @memberof Kinetic.Node.prototype + * @param {Object} [bounds] + * @param {Number} [bounds.x] + * @param {Number} [bounds.y] + * @param {Number} [bounds.width] + * @param {Number} [bounds.height] + * @example + * layer.clear();
+ * layer.clear(0, 0, 100, 100); + */ + clear: function(bounds) { + var context = this.getContext(), + hitContext = this.getHitCanvas().getContext(); + + context.clear(bounds); + hitContext.clear(bounds); + return this; + }, + // extend Node.prototype.setVisible + setVisible: function(visible) { + Kinetic.Node.prototype.setVisible.call(this, visible); + if(visible) { + this.getCanvas()._canvas.style.display = 'block'; + this.hitCanvas._canvas.style.display = 'block'; + } + else { + this.getCanvas()._canvas.style.display = 'none'; + this.hitCanvas._canvas.style.display = 'none'; + } + return this; + }, + // extend Node.prototype.setZIndex + setZIndex: function(index) { + Kinetic.Node.prototype.setZIndex.call(this, index); + var stage = this.getStage(); + if(stage) { + stage.content.removeChild(this.getCanvas()._canvas); + + if(index < stage.getChildren().length - 1) { + stage.content.insertBefore(this.getCanvas()._canvas, stage.getChildren()[index + 1].getCanvas()._canvas); + } + else { + stage.content.appendChild(this.getCanvas()._canvas); + } + } + return this; + }, + // extend Node.prototype.moveToTop + moveToTop: function() { + Kinetic.Node.prototype.moveToTop.call(this); + var stage = this.getStage(); + if(stage) { + stage.content.removeChild(this.getCanvas()._canvas); + stage.content.appendChild(this.getCanvas()._canvas); + } + }, + // extend Node.prototype.moveUp + moveUp: function() { + if(Kinetic.Node.prototype.moveUp.call(this)) { + var stage = this.getStage(); + if(stage) { + stage.content.removeChild(this.getCanvas()._canvas); + + if(this.index < stage.getChildren().length - 1) { + stage.content.insertBefore(this.getCanvas()._canvas, stage.getChildren()[this.index + 1].getCanvas()._canvas); + } + else { + stage.content.appendChild(this.getCanvas()._canvas); + } + } + } + }, + // extend Node.prototype.moveDown + moveDown: function() { + if(Kinetic.Node.prototype.moveDown.call(this)) { + var stage = this.getStage(); + if(stage) { + var children = stage.getChildren(); + stage.content.removeChild(this.getCanvas()._canvas); + stage.content.insertBefore(this.getCanvas()._canvas, children[this.index + 1].getCanvas()._canvas); + } + } + }, + // extend Node.prototype.moveToBottom + moveToBottom: function() { + if(Kinetic.Node.prototype.moveToBottom.call(this)) { + var stage = this.getStage(); + if(stage) { + var children = stage.getChildren(); + stage.content.removeChild(this.getCanvas()._canvas); + stage.content.insertBefore(this.getCanvas()._canvas, children[1].getCanvas()._canvas); + } + } + }, + getLayer: function() { + return this; + }, + remove: function() { + var stage = this.getStage(), + canvas = this.getCanvas(), + _canvas = canvas._canvas; + + Kinetic.Node.prototype.remove.call(this); + + if(stage && _canvas && Kinetic.Util._isInDocument(_canvas)) { + stage.content.removeChild(_canvas); + } + return this; + }, + getStage: function() { + return this.parent; + }, + /** + * enable hit graph + * @name enableHitGraph + * @method + * @memberof Kinetic.Layer.prototype + * @returns {Node} + */ + enableHitGraph: function() { + this.setHitGraphEnabled(true); + return this; + }, + /** + * disable hit graph + * @name enableHitGraph + * @method + * @memberof Kinetic.Layer.prototype + * @returns {Node} + */ + disableHitGraph: function() { + this.setHitGraphEnabled(false); + return this; + } + }); + Kinetic.Util.extend(Kinetic.Layer, Kinetic.Container); + + // add getters and setters + Kinetic.Factory.addGetterSetter(Kinetic.Layer, 'clearBeforeDraw', true); + /** + * get/set clearBeforeDraw flag which determines if the layer is cleared or not + * before drawing + * @name clearBeforeDraw + * @method + * @memberof Kinetic.Layer.prototype + * @param {Boolean} clearBeforeDraw + * @returns {Boolean} + * @example + * // get clearBeforeDraw flag
+ * var clearBeforeDraw = layer.clearBeforeDraw();

+ * + * // disable clear before draw
+ * layer.clearBeforeDraw(false);

+ * + * // enable clear before draw
+ * layer.clearBeforeDraw(true); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Layer, 'hitGraphEnabled', true); + /** + * get/set hitGraphEnabled flag. Disabling the hit graph will greatly increase + * draw performance because the hit graph will not be redrawn each time the layer is + * drawn. This, however, also disables mouse/touch event detection + * @name hitGraphEnabled + * @method + * @memberof Kinetic.Layer.prototype + * @param {Boolean} enabled + * @returns {Boolean} + * @example + * // get hitGraphEnabled flag
+ * var hitGraphEnabled = layer.hitGraphEnabled();

+ * + * // disable hit graph
+ * layer.hitGraphEnabled(false);

+ * + * // enable hit graph
+ * layer.hitGraphEnabled(true); + */ + + Kinetic.Collection.mapMethods(Kinetic.Layer); +})(); +;(function() { + Kinetic.Util.addMethods(Kinetic.Group, { + ___init: function(config) { + this.nodeType = 'Group'; + // call super constructor + Kinetic.Container.call(this, config); + }, + _validateAdd: function(child) { + var type = child.getType(); + if (type !== 'Group' && type !== 'Shape') { + Kinetic.Util.error('You may only add groups and shapes to groups.'); + } + } + }); + Kinetic.Util.extend(Kinetic.Group, Kinetic.Container); + + Kinetic.Collection.mapMethods(Kinetic.Group); +})(); +;(function() { + /** + * Rect constructor + * @constructor + * @memberof Kinetic + * @augments Kinetic.Shape + * @param {Object} config + * @param {Number} [config.cornerRadius] + * @param {String} [config.fill] fill color + * @param {Integer} [config.fillRed] set fill red component + * @param {Integer} [config.fillGreen] set fill green component + * @param {Integer} [config.fillBlue] set fill blue component + * @param {Integer} [config.fillAlpha] set fill alpha component + * @param {Image} [config.fillPatternImage] fill pattern image + * @param {Number} [config.fillPatternX] + * @param {Number} [config.fillPatternY] + * @param {Object} [config.fillPatternOffset] object with x and y component + * @param {Number} [config.fillPatternOffsetX] + * @param {Number} [config.fillPatternOffsetY] + * @param {Object} [config.fillPatternScale] object with x and y component + * @param {Number} [config.fillPatternScaleX] + * @param {Number} [config.fillPatternScaleY] + * @param {Number} [config.fillPatternRotation] + * @param {String} [config.fillPatternRepeat] can be "repeat", "repeat-x", "repeat-y", or "no-repeat". The default is "no-repeat" + * @param {Object} [config.fillLinearGradientStartPoint] object with x and y component + * @param {Number} [config.fillLinearGradientStartPointX] + * @param {Number} [config.fillLinearGradientStartPointY] + * @param {Object} [config.fillLinearGradientEndPoint] object with x and y component + * @param {Number} [config.fillLinearGradientEndPointX] + * @param {Number} [config.fillLinearGradientEndPointY] + * @param {Array} [config.fillLinearGradientColorStops] array of color stops + * @param {Object} [config.fillRadialGradientStartPoint] object with x and y component + * @param {Number} [config.fillRadialGradientStartPointX] + * @param {Number} [config.fillRadialGradientStartPointY] + * @param {Object} [config.fillRadialGradientEndPoint] object with x and y component + * @param {Number} [config.fillRadialGradientEndPointX] + * @param {Number} [config.fillRadialGradientEndPointY] + * @param {Number} [config.fillRadialGradientStartRadius] + * @param {Number} [config.fillRadialGradientEndRadius] + * @param {Array} [config.fillRadialGradientColorStops] array of color stops + * @param {Boolean} [config.fillEnabled] flag which enables or disables the fill. The default value is true + * @param {String} [config.fillPriority] can be color, linear-gradient, radial-graident, or pattern. The default value is color. The fillPriority property makes it really easy to toggle between different fill types. For example, if you want to toggle between a fill color style and a fill pattern style, simply set the fill property and the fillPattern properties, and then use setFillPriority('color') to render the shape with a color fill, or use setFillPriority('pattern') to render the shape with the pattern fill configuration + * @param {String} [config.stroke] stroke color + * @param {Integer} [config.strokeRed] set stroke red component + * @param {Integer} [config.strokeGreen] set stroke green component + * @param {Integer} [config.strokeBlue] set stroke blue component + * @param {Integer} [config.strokeAlpha] set stroke alpha component + * @param {Number} [config.strokeWidth] stroke width + * @param {Boolean} [config.strokeScaleEnabled] flag which enables or disables stroke scale. The default is true + * @param {Boolean} [config.strokeEnabled] flag which enables or disables the stroke. The default value is true + * @param {String} [config.lineJoin] can be miter, round, or bevel. The default + * is miter + * @param {String} [config.lineCap] can be butt, round, or sqare. The default + * is butt + * @param {String} [config.shadowColor] + * @param {Integer} [config.shadowColorRed] set shadow color red component + * @param {Integer} [config.shadowColorGreen] set shadow color green component + * @param {Integer} [config.shadowColorBlue] set shadow color blue component + * @param {Integer} [config.shadowColorAlpha] set shadow color alpha component + * @param {Number} [config.shadowBlur] + * @param {Object} [config.shadowOffset] object with x and y component + * @param {Number} [config.shadowOffsetX] + * @param {Number} [config.shadowOffsetY] + * @param {Number} [config.shadowOpacity] shadow opacity. Can be any real number + * between 0 and 1 + * @param {Boolean} [config.shadowEnabled] flag which enables or disables the shadow. The default value is true + * @param {Array} [config.dash] + * @param {Boolean} [config.dashEnabled] flag which enables or disables the dashArray. The default value is true + * @param {Number} [config.x] + * @param {Number} [config.y] + * @param {Number} [config.width] + * @param {Number} [config.height] + * @param {Boolean} [config.visible] + * @param {Boolean} [config.listening] whether or not the node is listening for events + * @param {String} [config.id] unique id + * @param {String} [config.name] non-unique name + * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 + * @param {Object} [config.scale] set scale + * @param {Number} [config.scaleX] set scale x + * @param {Number} [config.scaleY] set scale y + * @param {Number} [config.rotation] rotation in degrees + * @param {Object} [config.offset] offset from center point and rotation point + * @param {Number} [config.offsetX] set offset x + * @param {Number} [config.offsetY] set offset y + * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop + * the entire stage by dragging any portion of the stage + * @param {Function} [config.dragBoundFunc] + * @example + * var rect = new Kinetic.Rect({
+ * width: 100,
+ * height: 50,
+ * fill: 'red',
+ * stroke: 'black'
+ * strokeWidth: 5
+ * }); + */ + Kinetic.Rect = function(config) { + this.___init(config); + }; + + Kinetic.Rect.prototype = { + ___init: function(config) { + Kinetic.Shape.call(this, config); + this.className = 'Rect'; + this.sceneFunc(this._sceneFunc); + }, + _sceneFunc: function(context) { + var cornerRadius = this.getCornerRadius(), + width = this.getWidth(), + height = this.getHeight(); + + + context.beginPath(); + + if(!cornerRadius) { + // simple rect - don't bother doing all that complicated maths stuff. + context.rect(0, 0, width, height); + } + else { + // arcTo would be nicer, but browser support is patchy (Opera) + context.moveTo(cornerRadius, 0); + context.lineTo(width - cornerRadius, 0); + context.arc(width - cornerRadius, cornerRadius, cornerRadius, Math.PI * 3 / 2, 0, false); + context.lineTo(width, height - cornerRadius); + context.arc(width - cornerRadius, height - cornerRadius, cornerRadius, 0, Math.PI / 2, false); + context.lineTo(cornerRadius, height); + context.arc(cornerRadius, height - cornerRadius, cornerRadius, Math.PI / 2, Math.PI, false); + context.lineTo(0, cornerRadius); + context.arc(cornerRadius, cornerRadius, cornerRadius, Math.PI, Math.PI * 3 / 2, false); + } + context.closePath(); + context.fillStrokeShape(this); + } + }; + + Kinetic.Util.extend(Kinetic.Rect, Kinetic.Shape); + + Kinetic.Factory.addGetterSetter(Kinetic.Rect, 'cornerRadius', 0); + /** + * get/set corner radius + * @name cornerRadius + * @method + * @memberof Kinetic.Rect.prototype + * @param {Number} cornerRadius + * @returns {Number} + * @example + * // get corner radius
+ * var cornerRadius = rect.cornerRadius();

+ * + * // set corner radius
+ * rect.cornerRadius(10); + */ + + Kinetic.Collection.mapMethods(Kinetic.Rect); +})(); +;(function() { + // the 0.0001 offset fixes a bug in Chrome 27 + var PIx2 = (Math.PI * 2) - 0.0001, + CIRCLE = 'Circle'; + + /** + * Circle constructor + * @constructor + * @memberof Kinetic + * @augments Kinetic.Shape + * @param {Object} config + * @param {Number} config.radius + * @param {String} [config.fill] fill color + * @param {Integer} [config.fillRed] set fill red component + * @param {Integer} [config.fillGreen] set fill green component + * @param {Integer} [config.fillBlue] set fill blue component + * @param {Integer} [config.fillAlpha] set fill alpha component + * @param {Image} [config.fillPatternImage] fill pattern image + * @param {Number} [config.fillPatternX] + * @param {Number} [config.fillPatternY] + * @param {Object} [config.fillPatternOffset] object with x and y component + * @param {Number} [config.fillPatternOffsetX] + * @param {Number} [config.fillPatternOffsetY] + * @param {Object} [config.fillPatternScale] object with x and y component + * @param {Number} [config.fillPatternScaleX] + * @param {Number} [config.fillPatternScaleY] + * @param {Number} [config.fillPatternRotation] + * @param {String} [config.fillPatternRepeat] can be "repeat", "repeat-x", "repeat-y", or "no-repeat". The default is "no-repeat" + * @param {Object} [config.fillLinearGradientStartPoint] object with x and y component + * @param {Number} [config.fillLinearGradientStartPointX] + * @param {Number} [config.fillLinearGradientStartPointY] + * @param {Object} [config.fillLinearGradientEndPoint] object with x and y component + * @param {Number} [config.fillLinearGradientEndPointX] + * @param {Number} [config.fillLinearGradientEndPointY] + * @param {Array} [config.fillLinearGradientColorStops] array of color stops + * @param {Object} [config.fillRadialGradientStartPoint] object with x and y component + * @param {Number} [config.fillRadialGradientStartPointX] + * @param {Number} [config.fillRadialGradientStartPointY] + * @param {Object} [config.fillRadialGradientEndPoint] object with x and y component + * @param {Number} [config.fillRadialGradientEndPointX] + * @param {Number} [config.fillRadialGradientEndPointY] + * @param {Number} [config.fillRadialGradientStartRadius] + * @param {Number} [config.fillRadialGradientEndRadius] + * @param {Array} [config.fillRadialGradientColorStops] array of color stops + * @param {Boolean} [config.fillEnabled] flag which enables or disables the fill. The default value is true + * @param {String} [config.fillPriority] can be color, linear-gradient, radial-graident, or pattern. The default value is color. The fillPriority property makes it really easy to toggle between different fill types. For example, if you want to toggle between a fill color style and a fill pattern style, simply set the fill property and the fillPattern properties, and then use setFillPriority('color') to render the shape with a color fill, or use setFillPriority('pattern') to render the shape with the pattern fill configuration + * @param {String} [config.stroke] stroke color + * @param {Integer} [config.strokeRed] set stroke red component + * @param {Integer} [config.strokeGreen] set stroke green component + * @param {Integer} [config.strokeBlue] set stroke blue component + * @param {Integer} [config.strokeAlpha] set stroke alpha component + * @param {Number} [config.strokeWidth] stroke width + * @param {Boolean} [config.strokeScaleEnabled] flag which enables or disables stroke scale. The default is true + * @param {Boolean} [config.strokeEnabled] flag which enables or disables the stroke. The default value is true + * @param {String} [config.lineJoin] can be miter, round, or bevel. The default + * is miter + * @param {String} [config.lineCap] can be butt, round, or sqare. The default + * is butt + * @param {String} [config.shadowColor] + * @param {Integer} [config.shadowColorRed] set shadow color red component + * @param {Integer} [config.shadowColorGreen] set shadow color green component + * @param {Integer} [config.shadowColorBlue] set shadow color blue component + * @param {Integer} [config.shadowColorAlpha] set shadow color alpha component + * @param {Number} [config.shadowBlur] + * @param {Object} [config.shadowOffset] object with x and y component + * @param {Number} [config.shadowOffsetX] + * @param {Number} [config.shadowOffsetY] + * @param {Number} [config.shadowOpacity] shadow opacity. Can be any real number + * between 0 and 1 + * @param {Boolean} [config.shadowEnabled] flag which enables or disables the shadow. The default value is true + * @param {Array} [config.dash] + * @param {Boolean} [config.dashEnabled] flag which enables or disables the dashArray. The default value is true + * @param {Number} [config.x] + * @param {Number} [config.y] + * @param {Number} [config.width] + * @param {Number} [config.height] + * @param {Boolean} [config.visible] + * @param {Boolean} [config.listening] whether or not the node is listening for events + * @param {String} [config.id] unique id + * @param {String} [config.name] non-unique name + * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 + * @param {Object} [config.scale] set scale + * @param {Number} [config.scaleX] set scale x + * @param {Number} [config.scaleY] set scale y + * @param {Number} [config.rotation] rotation in degrees + * @param {Object} [config.offset] offset from center point and rotation point + * @param {Number} [config.offsetX] set offset x + * @param {Number} [config.offsetY] set offset y + * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop + * the entire stage by dragging any portion of the stage + * @param {Function} [config.dragBoundFunc] + * @example + * // create circle + * var circle = new Kinetic.Circle({
+ * radius: 40,
+ * fill: 'red',
+ * stroke: 'black'
+ * strokeWidth: 5
+ * }); + */ + Kinetic.Circle = function(config) { + this.___init(config); + }; + + Kinetic.Circle.prototype = { + ___init: function(config) { + // call super constructor + Kinetic.Shape.call(this, config); + this.className = CIRCLE; + this.sceneFunc(this._sceneFunc); + }, + _sceneFunc: function(context) { + context.beginPath(); + context.arc(0, 0, this.getRadius(), 0, PIx2, false); + context.closePath(); + context.fillStrokeShape(this); + }, + // implements Shape.prototype.getWidth() + getWidth: function() { + return this.getRadius() * 2; + }, + // implements Shape.prototype.getHeight() + getHeight: function() { + return this.getRadius() * 2; + }, + // implements Shape.prototype.setWidth() + setWidth: function(width) { + Kinetic.Node.prototype.setWidth.call(this, width); + this.setRadius(width / 2); + }, + // implements Shape.prototype.setHeight() + setHeight: function(height) { + Kinetic.Node.prototype.setHeight.call(this, height); + this.setRadius(height / 2); + } + }; + Kinetic.Util.extend(Kinetic.Circle, Kinetic.Shape); + + // add getters setters + Kinetic.Factory.addGetterSetter(Kinetic.Circle, 'radius', 0); + + /** + * get/set radius + * @name radius + * @method + * @memberof Kinetic.Circle.prototype + * @param {Number} radius + * @returns {Number} + * @example + * // get radius
+ * var radius = circle.radius();

+ * + * // set radius
+ * circle.radius(10);
+ */ + + Kinetic.Collection.mapMethods(Kinetic.Circle); +})(); +;(function() { + // the 0.0001 offset fixes a bug in Chrome 27 + var PIx2 = (Math.PI * 2) - 0.0001, + ELLIPSE = 'Ellipse'; + + /** + * Ellipse constructor + * @constructor + * @augments Kinetic.Shape + * @param {Object} config + * @param {Number|Array|Object} config.radius defines x and y radius + * @@ShapeParams + * @@NodeParams + */ + Kinetic.Ellipse = function(config) { + this.___init(config); + }; + + Kinetic.Ellipse.prototype = { + ___init: function(config) { + // call super constructor + Kinetic.Shape.call(this, config); + this.className = ELLIPSE; + this.sceneFunc(this._sceneFunc); + }, + _sceneFunc: function(context) { + var r = this.getRadius(), + rx = r.x, + ry = r.y; + + context.beginPath(); + context.save(); + if(rx !== ry) { + context.scale(1, ry / rx); + } + context.arc(0, 0, rx, 0, PIx2, false); + context.restore(); + context.closePath(); + context.fillStrokeShape(this); + }, + // implements Shape.prototype.getWidth() + getWidth: function() { + return this.getRadius().x * 2; + }, + // implements Shape.prototype.getHeight() + getHeight: function() { + return this.getRadius().y * 2; + }, + // implements Shape.prototype.setWidth() + setWidth: function(width) { + Kinetic.Node.prototype.setWidth.call(this, width); + this.setRadius({ + x: width / 2 + }); + }, + // implements Shape.prototype.setHeight() + setHeight: function(height) { + Kinetic.Node.prototype.setHeight.call(this, height); + this.setRadius({ + y: height / 2 + }); + } + }; + Kinetic.Util.extend(Kinetic.Ellipse, Kinetic.Shape); + + // add getters setters + Kinetic.Factory.addComponentsGetterSetter(Kinetic.Ellipse, 'radius', ['x', 'y']); + + /** + * get/set radius + * @name radius + * @method + * @memberof Kinetic.Ellipse.prototype + * @param {Object} radius + * @param {Number} radius.x + * @param {Number} radius.y + * @returns {Object} + * @example + * // get radius
+ * var radius = ellipse.radius();

+ * + * // set radius
+ * ellipse.radius({
+ * x: 200,
+ * y: 100
+ * }); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Ellipse, 'radiusX', 0); + /** + * get/set radius x + * @name radiusX + * @method + * @memberof Kinetic.Ellipse.prototype + * @param {Number} x + * @returns {Number} + * @example + * // get radius x
+ * var radiusX = ellipse.radiusX();

+ * + * // set radius x
+ * ellipse.radiusX(200); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Ellipse, 'radiusY', 0); + /** + * get/set radius y + * @name radiusY + * @method + * @memberof Kinetic.Ellipse.prototype + * @param {Number} y + * @returns {Number} + * @example + * // get radius y
+ * var radiusY = ellipse.radiusY();

+ * + * // set radius y
+ * ellipse.radiusY(200); + */ + + Kinetic.Collection.mapMethods(Kinetic.Ellipse); + +})();;(function() { + // the 0.0001 offset fixes a bug in Chrome 27 + var PIx2 = (Math.PI * 2) - 0.0001; + + /** + * Ring constructor + * @constructor + * @augments Kinetic.Shape + * @param {Object} config + * @param {Number} config.innerRadius + * @param {Number} config.outerRadius + * @param {Boolean} [config.clockwise] + * @param {String} [config.fill] fill color + * @param {Integer} [config.fillRed] set fill red component + * @param {Integer} [config.fillGreen] set fill green component + * @param {Integer} [config.fillBlue] set fill blue component + * @param {Integer} [config.fillAlpha] set fill alpha component + * @param {Image} [config.fillPatternImage] fill pattern image + * @param {Number} [config.fillPatternX] + * @param {Number} [config.fillPatternY] + * @param {Object} [config.fillPatternOffset] object with x and y component + * @param {Number} [config.fillPatternOffsetX] + * @param {Number} [config.fillPatternOffsetY] + * @param {Object} [config.fillPatternScale] object with x and y component + * @param {Number} [config.fillPatternScaleX] + * @param {Number} [config.fillPatternScaleY] + * @param {Number} [config.fillPatternRotation] + * @param {String} [config.fillPatternRepeat] can be "repeat", "repeat-x", "repeat-y", or "no-repeat". The default is "no-repeat" + * @param {Object} [config.fillLinearGradientStartPoint] object with x and y component + * @param {Number} [config.fillLinearGradientStartPointX] + * @param {Number} [config.fillLinearGradientStartPointY] + * @param {Object} [config.fillLinearGradientEndPoint] object with x and y component + * @param {Number} [config.fillLinearGradientEndPointX] + * @param {Number} [config.fillLinearGradientEndPointY] + * @param {Array} [config.fillLinearGradientColorStops] array of color stops + * @param {Object} [config.fillRadialGradientStartPoint] object with x and y component + * @param {Number} [config.fillRadialGradientStartPointX] + * @param {Number} [config.fillRadialGradientStartPointY] + * @param {Object} [config.fillRadialGradientEndPoint] object with x and y component + * @param {Number} [config.fillRadialGradientEndPointX] + * @param {Number} [config.fillRadialGradientEndPointY] + * @param {Number} [config.fillRadialGradientStartRadius] + * @param {Number} [config.fillRadialGradientEndRadius] + * @param {Array} [config.fillRadialGradientColorStops] array of color stops + * @param {Boolean} [config.fillEnabled] flag which enables or disables the fill. The default value is true + * @param {String} [config.fillPriority] can be color, linear-gradient, radial-graident, or pattern. The default value is color. The fillPriority property makes it really easy to toggle between different fill types. For example, if you want to toggle between a fill color style and a fill pattern style, simply set the fill property and the fillPattern properties, and then use setFillPriority('color') to render the shape with a color fill, or use setFillPriority('pattern') to render the shape with the pattern fill configuration + * @param {String} [config.stroke] stroke color + * @param {Integer} [config.strokeRed] set stroke red component + * @param {Integer} [config.strokeGreen] set stroke green component + * @param {Integer} [config.strokeBlue] set stroke blue component + * @param {Integer} [config.strokeAlpha] set stroke alpha component + * @param {Number} [config.strokeWidth] stroke width + * @param {Boolean} [config.strokeScaleEnabled] flag which enables or disables stroke scale. The default is true + * @param {Boolean} [config.strokeEnabled] flag which enables or disables the stroke. The default value is true + * @param {String} [config.lineJoin] can be miter, round, or bevel. The default + * is miter + * @param {String} [config.lineCap] can be butt, round, or sqare. The default + * is butt + * @param {String} [config.shadowColor] + * @param {Integer} [config.shadowColorRed] set shadow color red component + * @param {Integer} [config.shadowColorGreen] set shadow color green component + * @param {Integer} [config.shadowColorBlue] set shadow color blue component + * @param {Integer} [config.shadowColorAlpha] set shadow color alpha component + * @param {Number} [config.shadowBlur] + * @param {Object} [config.shadowOffset] object with x and y component + * @param {Number} [config.shadowOffsetX] + * @param {Number} [config.shadowOffsetY] + * @param {Number} [config.shadowOpacity] shadow opacity. Can be any real number + * between 0 and 1 + * @param {Boolean} [config.shadowEnabled] flag which enables or disables the shadow. The default value is true + * @param {Array} [config.dash] + * @param {Boolean} [config.dashEnabled] flag which enables or disables the dashArray. The default value is true + * @param {Number} [config.x] + * @param {Number} [config.y] + * @param {Number} [config.width] + * @param {Number} [config.height] + * @param {Boolean} [config.visible] + * @param {Boolean} [config.listening] whether or not the node is listening for events + * @param {String} [config.id] unique id + * @param {String} [config.name] non-unique name + * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 + * @param {Object} [config.scale] set scale + * @param {Number} [config.scaleX] set scale x + * @param {Number} [config.scaleY] set scale y + * @param {Number} [config.rotation] rotation in degrees + * @param {Object} [config.offset] offset from center point and rotation point + * @param {Number} [config.offsetX] set offset x + * @param {Number} [config.offsetY] set offset y + * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop + * the entire stage by dragging any portion of the stage + * @param {Function} [config.dragBoundFunc] + * @example + * var ring = new Kinetic.Ring({
+ * innerRadius: 40,
+ * outerRadius: 80,
+ * fill: 'red',
+ * stroke: 'black',
+ * strokeWidth: 5
+ * }); + */ + Kinetic.Ring = function(config) { + this.___init(config); + }; + + Kinetic.Ring.prototype = { + ___init: function(config) { + // call super constructor + Kinetic.Shape.call(this, config); + this.className = 'Ring'; + this.sceneFunc(this._sceneFunc); + }, + _sceneFunc: function(context) { + context.beginPath(); + context.arc(0, 0, this.getInnerRadius(), 0, PIx2, false); + context.moveTo(this.getOuterRadius(), 0); + context.arc(0, 0, this.getOuterRadius(), PIx2, 0, true); + context.closePath(); + context.fillStrokeShape(this); + }, + // implements Shape.prototype.getWidth() + getWidth: function() { + return this.getOuterRadius() * 2; + }, + // implements Shape.prototype.getHeight() + getHeight: function() { + return this.getOuterRadius() * 2; + }, + // implements Shape.prototype.setWidth() + setWidth: function(width) { + Kinetic.Node.prototype.setWidth.call(this, width); + this.setOuterRadius(width / 2); + }, + // implements Shape.prototype.setHeight() + setHeight: function(height) { + Kinetic.Node.prototype.setHeight.call(this, height); + this.setOuterRadius(height / 2); + } + }; + Kinetic.Util.extend(Kinetic.Ring, Kinetic.Shape); + + // add getters setters + Kinetic.Factory.addGetterSetter(Kinetic.Ring, 'innerRadius', 0); + + /** + * get/set innerRadius + * @name innerRadius + * @method + * @memberof Kinetic.Ring.prototype + * @param {Number} innerRadius + * @returns {Number} + * @example + * // get inner radius
+ * var innerRadius = ring.innerRadius();

+ * + * // set inner radius
+ * ring.innerRadius(20); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Ring, 'outerRadius', 0); + + /** + * get/set outerRadius + * @name outerRadius + * @method + * @memberof Kinetic.Ring.prototype + * @param {Number} outerRadius + * @returns {Number} + * @example + * // get outer radius
+ * var outerRadius = ring.outerRadius();

+ * + * // set outer radius
+ * ring.outerRadius(20); + */ + + Kinetic.Collection.mapMethods(Kinetic.Ring); +})(); +;(function() { + /** + * Wedge constructor + * @constructor + * @augments Kinetic.Shape + * @param {Object} config + * @param {Number} config.angle in degrees + * @param {Number} config.radius + * @param {Boolean} [config.clockwise] + * @param {String} [config.fill] fill color + * @param {Integer} [config.fillRed] set fill red component + * @param {Integer} [config.fillGreen] set fill green component + * @param {Integer} [config.fillBlue] set fill blue component + * @param {Integer} [config.fillAlpha] set fill alpha component + * @param {Image} [config.fillPatternImage] fill pattern image + * @param {Number} [config.fillPatternX] + * @param {Number} [config.fillPatternY] + * @param {Object} [config.fillPatternOffset] object with x and y component + * @param {Number} [config.fillPatternOffsetX] + * @param {Number} [config.fillPatternOffsetY] + * @param {Object} [config.fillPatternScale] object with x and y component + * @param {Number} [config.fillPatternScaleX] + * @param {Number} [config.fillPatternScaleY] + * @param {Number} [config.fillPatternRotation] + * @param {String} [config.fillPatternRepeat] can be "repeat", "repeat-x", "repeat-y", or "no-repeat". The default is "no-repeat" + * @param {Object} [config.fillLinearGradientStartPoint] object with x and y component + * @param {Number} [config.fillLinearGradientStartPointX] + * @param {Number} [config.fillLinearGradientStartPointY] + * @param {Object} [config.fillLinearGradientEndPoint] object with x and y component + * @param {Number} [config.fillLinearGradientEndPointX] + * @param {Number} [config.fillLinearGradientEndPointY] + * @param {Array} [config.fillLinearGradientColorStops] array of color stops + * @param {Object} [config.fillRadialGradientStartPoint] object with x and y component + * @param {Number} [config.fillRadialGradientStartPointX] + * @param {Number} [config.fillRadialGradientStartPointY] + * @param {Object} [config.fillRadialGradientEndPoint] object with x and y component + * @param {Number} [config.fillRadialGradientEndPointX] + * @param {Number} [config.fillRadialGradientEndPointY] + * @param {Number} [config.fillRadialGradientStartRadius] + * @param {Number} [config.fillRadialGradientEndRadius] + * @param {Array} [config.fillRadialGradientColorStops] array of color stops + * @param {Boolean} [config.fillEnabled] flag which enables or disables the fill. The default value is true + * @param {String} [config.fillPriority] can be color, linear-gradient, radial-graident, or pattern. The default value is color. The fillPriority property makes it really easy to toggle between different fill types. For example, if you want to toggle between a fill color style and a fill pattern style, simply set the fill property and the fillPattern properties, and then use setFillPriority('color') to render the shape with a color fill, or use setFillPriority('pattern') to render the shape with the pattern fill configuration + * @param {String} [config.stroke] stroke color + * @param {Integer} [config.strokeRed] set stroke red component + * @param {Integer} [config.strokeGreen] set stroke green component + * @param {Integer} [config.strokeBlue] set stroke blue component + * @param {Integer} [config.strokeAlpha] set stroke alpha component + * @param {Number} [config.strokeWidth] stroke width + * @param {Boolean} [config.strokeScaleEnabled] flag which enables or disables stroke scale. The default is true + * @param {Boolean} [config.strokeEnabled] flag which enables or disables the stroke. The default value is true + * @param {String} [config.lineJoin] can be miter, round, or bevel. The default + * is miter + * @param {String} [config.lineCap] can be butt, round, or sqare. The default + * is butt + * @param {String} [config.shadowColor] + * @param {Integer} [config.shadowColorRed] set shadow color red component + * @param {Integer} [config.shadowColorGreen] set shadow color green component + * @param {Integer} [config.shadowColorBlue] set shadow color blue component + * @param {Integer} [config.shadowColorAlpha] set shadow color alpha component + * @param {Number} [config.shadowBlur] + * @param {Object} [config.shadowOffset] object with x and y component + * @param {Number} [config.shadowOffsetX] + * @param {Number} [config.shadowOffsetY] + * @param {Number} [config.shadowOpacity] shadow opacity. Can be any real number + * between 0 and 1 + * @param {Boolean} [config.shadowEnabled] flag which enables or disables the shadow. The default value is true + * @param {Array} [config.dash] + * @param {Boolean} [config.dashEnabled] flag which enables or disables the dashArray. The default value is true + * @param {Number} [config.x] + * @param {Number} [config.y] + * @param {Number} [config.width] + * @param {Number} [config.height] + * @param {Boolean} [config.visible] + * @param {Boolean} [config.listening] whether or not the node is listening for events + * @param {String} [config.id] unique id + * @param {String} [config.name] non-unique name + * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 + * @param {Object} [config.scale] set scale + * @param {Number} [config.scaleX] set scale x + * @param {Number} [config.scaleY] set scale y + * @param {Number} [config.rotation] rotation in degrees + * @param {Object} [config.offset] offset from center point and rotation point + * @param {Number} [config.offsetX] set offset x + * @param {Number} [config.offsetY] set offset y + * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop + * the entire stage by dragging any portion of the stage + * @param {Function} [config.dragBoundFunc] + * @example + * // draw a wedge that's pointing downwards
+ * var wedge = new Kinetic.Wedge({
+ * radius: 40,
+ * fill: 'red',
+ * stroke: 'black'
+ * strokeWidth: 5,
+ * angleDeg: 60,
+ * rotationDeg: -120
+ * }); + */ + Kinetic.Wedge = function(config) { + this.___init(config); + }; + + Kinetic.Wedge.prototype = { + ___init: function(config) { + // call super constructor + Kinetic.Shape.call(this, config); + this.className = 'Wedge'; + this.sceneFunc(this._sceneFunc); + }, + _sceneFunc: function(context) { + context.beginPath(); + context.arc(0, 0, this.getRadius(), 0, this.getAngle() * Math.PI / 180, this.getClockwise()); + context.lineTo(0, 0); + context.closePath(); + context.fillStrokeShape(this); + } + }; + Kinetic.Util.extend(Kinetic.Wedge, Kinetic.Shape); + + // add getters setters + Kinetic.Factory.addGetterSetter(Kinetic.Wedge, 'radius', 0); + + /** + * get/set radius + * @name radius + * @method + * @memberof Kinetic.Wedge.prototype + * @param {Number} radius + * @returns {Number} + * @example + * // get radius
+ * var radius = wedge.radius();

+ * + * // set radius
+ * wedge.radius(10);
+ */ + + Kinetic.Factory.addGetterSetter(Kinetic.Wedge, 'angle', 0); + + /** + * get/set angle in degrees + * @name angle + * @method + * @memberof Kinetic.Wedge.prototype + * @param {Number} angle + * @returns {Number} + * @example + * // get angle
+ * var angle = wedge.angle();

+ * + * // set angle
+ * wedge.angle(20); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Wedge, 'clockwise', false); + + /** + * get/set clockwise flag + * @name clockwise + * @method + * @memberof Kinetic.Wedge.prototype + * @param {Number} clockwise + * @returns {Number} + * @example + * // get clockwise flag
+ * var clockwise = wedge.clockwise();

+ * + * // draw wedge counter-clockwise
+ * wedge.clockwise(false);

+ * + * // draw wedge clockwise
+ * wedge.clockwise(true); + */ + + Kinetic.Factory.backCompat(Kinetic.Wedge, { + angleDeg: 'angle', + getAngleDeg: 'getAngle', + setAngleDeg: 'setAngle' + }); + + Kinetic.Collection.mapMethods(Kinetic.Wedge); +})(); +;(function() { + /** + * Arc constructor + * @constructor + * @augments Kinetic.Shape + * @param {Object} config + * @param {Number} config.angle in degrees + * @param {Number} config.innerRadius + * @param {Number} config.outerRadius + * @param {Boolean} [config.clockwise] + * @param {String} [config.fill] fill color + * @param {Integer} [config.fillRed] set fill red component + * @param {Integer} [config.fillGreen] set fill green component + * @param {Integer} [config.fillBlue] set fill blue component + * @param {Integer} [config.fillAlpha] set fill alpha component + * @param {Image} [config.fillPatternImage] fill pattern image + * @param {Number} [config.fillPatternX] + * @param {Number} [config.fillPatternY] + * @param {Object} [config.fillPatternOffset] object with x and y component + * @param {Number} [config.fillPatternOffsetX] + * @param {Number} [config.fillPatternOffsetY] + * @param {Object} [config.fillPatternScale] object with x and y component + * @param {Number} [config.fillPatternScaleX] + * @param {Number} [config.fillPatternScaleY] + * @param {Number} [config.fillPatternRotation] + * @param {String} [config.fillPatternRepeat] can be "repeat", "repeat-x", "repeat-y", or "no-repeat". The default is "no-repeat" + * @param {Object} [config.fillLinearGradientStartPoint] object with x and y component + * @param {Number} [config.fillLinearGradientStartPointX] + * @param {Number} [config.fillLinearGradientStartPointY] + * @param {Object} [config.fillLinearGradientEndPoint] object with x and y component + * @param {Number} [config.fillLinearGradientEndPointX] + * @param {Number} [config.fillLinearGradientEndPointY] + * @param {Array} [config.fillLinearGradientColorStops] array of color stops + * @param {Object} [config.fillRadialGradientStartPoint] object with x and y component + * @param {Number} [config.fillRadialGradientStartPointX] + * @param {Number} [config.fillRadialGradientStartPointY] + * @param {Object} [config.fillRadialGradientEndPoint] object with x and y component + * @param {Number} [config.fillRadialGradientEndPointX] + * @param {Number} [config.fillRadialGradientEndPointY] + * @param {Number} [config.fillRadialGradientStartRadius] + * @param {Number} [config.fillRadialGradientEndRadius] + * @param {Array} [config.fillRadialGradientColorStops] array of color stops + * @param {Boolean} [config.fillEnabled] flag which enables or disables the fill. The default value is true + * @param {String} [config.fillPriority] can be color, linear-gradient, radial-graident, or pattern. The default value is color. The fillPriority property makes it really easy to toggle between different fill types. For example, if you want to toggle between a fill color style and a fill pattern style, simply set the fill property and the fillPattern properties, and then use setFillPriority('color') to render the shape with a color fill, or use setFillPriority('pattern') to render the shape with the pattern fill configuration + * @param {String} [config.stroke] stroke color + * @param {Integer} [config.strokeRed] set stroke red component + * @param {Integer} [config.strokeGreen] set stroke green component + * @param {Integer} [config.strokeBlue] set stroke blue component + * @param {Integer} [config.strokeAlpha] set stroke alpha component + * @param {Number} [config.strokeWidth] stroke width + * @param {Boolean} [config.strokeScaleEnabled] flag which enables or disables stroke scale. The default is true + * @param {Boolean} [config.strokeEnabled] flag which enables or disables the stroke. The default value is true + * @param {String} [config.lineJoin] can be miter, round, or bevel. The default + * is miter + * @param {String} [config.lineCap] can be butt, round, or sqare. The default + * is butt + * @param {String} [config.shadowColor] + * @param {Integer} [config.shadowColorRed] set shadow color red component + * @param {Integer} [config.shadowColorGreen] set shadow color green component + * @param {Integer} [config.shadowColorBlue] set shadow color blue component + * @param {Integer} [config.shadowColorAlpha] set shadow color alpha component + * @param {Number} [config.shadowBlur] + * @param {Object} [config.shadowOffset] object with x and y component + * @param {Number} [config.shadowOffsetX] + * @param {Number} [config.shadowOffsetY] + * @param {Number} [config.shadowOpacity] shadow opacity. Can be any real number + * between 0 and 1 + * @param {Boolean} [config.shadowEnabled] flag which enables or disables the shadow. The default value is true + * @param {Array} [config.dash] + * @param {Boolean} [config.dashEnabled] flag which enables or disables the dashArray. The default value is true + * @param {Number} [config.x] + * @param {Number} [config.y] + * @param {Number} [config.width] + * @param {Number} [config.height] + * @param {Boolean} [config.visible] + * @param {Boolean} [config.listening] whether or not the node is listening for events + * @param {String} [config.id] unique id + * @param {String} [config.name] non-unique name + * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 + * @param {Object} [config.scale] set scale + * @param {Number} [config.scaleX] set scale x + * @param {Number} [config.scaleY] set scale y + * @param {Number} [config.rotation] rotation in degrees + * @param {Object} [config.offset] offset from center point and rotation point + * @param {Number} [config.offsetX] set offset x + * @param {Number} [config.offsetY] set offset y + * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop + * the entire stage by dragging any portion of the stage + * @param {Function} [config.dragBoundFunc] + * @example + * // draw a Arc that's pointing downwards
+ * var arc = new Kinetic.Arc({
+ * innerRadius: 40,
+ * outerRadius: 80,
+ * fill: 'red',
+ * stroke: 'black'
+ * strokeWidth: 5,
+ * angle: 60,
+ * rotationDeg: -120
+ * }); + */ + Kinetic.Arc = function(config) { + this.___init(config); + }; + + Kinetic.Arc.prototype = { + ___init: function(config) { + // call super constructor + Kinetic.Shape.call(this, config); + this.className = 'Arc'; + this.sceneFunc(this._sceneFunc); + }, + _sceneFunc: function(context) { + var angle = this.angle() * Math.PI / 180, + clockwise = this.clockwise(); + + context.beginPath(); + context.arc(0, 0, this.getOuterRadius(), 0, angle, clockwise); + context.arc(0, 0, this.getInnerRadius(), angle, 0, !clockwise); + context.closePath(); + context.fillStrokeShape(this); + } + }; + Kinetic.Util.extend(Kinetic.Arc, Kinetic.Shape); + + // add getters setters + Kinetic.Factory.addGetterSetter(Kinetic.Arc, 'innerRadius', 0); + + /** + * get/set innerRadius + * @name innerRadius + * @method + * @memberof Kinetic.Arc.prototype + * @param {Number} innerRadius + * @returns {Number} + * @example + * // get inner radius + * var innerRadius = arc.innerRadius(); + * + * // set inner radius + * arc.innerRadius(20); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Arc, 'outerRadius', 0); + + /** + * get/set outerRadius + * @name outerRadius + * @method + * @memberof Kinetic.Arc.prototype + * @param {Number} outerRadius + * @returns {Number} + * @example + * // get outer radius
+ * var outerRadius = arc.outerRadius();

+ * + * // set outer radius
+ * arc.outerRadius(20); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Arc, 'angle', 0); + + /** + * get/set angle in degrees + * @name angle + * @method + * @memberof Kinetic.Arc.prototype + * @param {Number} angle + * @returns {Number} + * @example + * // get angle
+ * var angle = arc.angle();

+ * + * // set angle
+ * arc.angle(20); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Arc, 'clockwise', false); + + /** + * get/set clockwise flag + * @name clockwise + * @method + * @memberof Kinetic.Arc.prototype + * @param {Number} clockwise + * @returns {Number} + * @example + * // get clockwise flag
+ * var clockwise = arc.clockwise();

+ * + * // draw arc counter-clockwise
+ * arc.clockwise(false);

+ * + * // draw arc clockwise
+ * arc.clockwise(true); + */ + + Kinetic.Collection.mapMethods(Kinetic.Arc); +})(); +;(function() { + + // CONSTANTS + var IMAGE = 'Image', + SET = 'set'; + + /** + * Image constructor + * @constructor + * @memberof Kinetic + * @augments Kinetic.Shape + * @param {Object} config + * @param {ImageObject} config.image + * @param {Object} [config.crop] + * @param {String} [config.fill] fill color + * @param {Integer} [config.fillRed] set fill red component + * @param {Integer} [config.fillGreen] set fill green component + * @param {Integer} [config.fillBlue] set fill blue component + * @param {Integer} [config.fillAlpha] set fill alpha component + * @param {Image} [config.fillPatternImage] fill pattern image + * @param {Number} [config.fillPatternX] + * @param {Number} [config.fillPatternY] + * @param {Object} [config.fillPatternOffset] object with x and y component + * @param {Number} [config.fillPatternOffsetX] + * @param {Number} [config.fillPatternOffsetY] + * @param {Object} [config.fillPatternScale] object with x and y component + * @param {Number} [config.fillPatternScaleX] + * @param {Number} [config.fillPatternScaleY] + * @param {Number} [config.fillPatternRotation] + * @param {String} [config.fillPatternRepeat] can be "repeat", "repeat-x", "repeat-y", or "no-repeat". The default is "no-repeat" + * @param {Object} [config.fillLinearGradientStartPoint] object with x and y component + * @param {Number} [config.fillLinearGradientStartPointX] + * @param {Number} [config.fillLinearGradientStartPointY] + * @param {Object} [config.fillLinearGradientEndPoint] object with x and y component + * @param {Number} [config.fillLinearGradientEndPointX] + * @param {Number} [config.fillLinearGradientEndPointY] + * @param {Array} [config.fillLinearGradientColorStops] array of color stops + * @param {Object} [config.fillRadialGradientStartPoint] object with x and y component + * @param {Number} [config.fillRadialGradientStartPointX] + * @param {Number} [config.fillRadialGradientStartPointY] + * @param {Object} [config.fillRadialGradientEndPoint] object with x and y component + * @param {Number} [config.fillRadialGradientEndPointX] + * @param {Number} [config.fillRadialGradientEndPointY] + * @param {Number} [config.fillRadialGradientStartRadius] + * @param {Number} [config.fillRadialGradientEndRadius] + * @param {Array} [config.fillRadialGradientColorStops] array of color stops + * @param {Boolean} [config.fillEnabled] flag which enables or disables the fill. The default value is true + * @param {String} [config.fillPriority] can be color, linear-gradient, radial-graident, or pattern. The default value is color. The fillPriority property makes it really easy to toggle between different fill types. For example, if you want to toggle between a fill color style and a fill pattern style, simply set the fill property and the fillPattern properties, and then use setFillPriority('color') to render the shape with a color fill, or use setFillPriority('pattern') to render the shape with the pattern fill configuration + * @param {String} [config.stroke] stroke color + * @param {Integer} [config.strokeRed] set stroke red component + * @param {Integer} [config.strokeGreen] set stroke green component + * @param {Integer} [config.strokeBlue] set stroke blue component + * @param {Integer} [config.strokeAlpha] set stroke alpha component + * @param {Number} [config.strokeWidth] stroke width + * @param {Boolean} [config.strokeScaleEnabled] flag which enables or disables stroke scale. The default is true + * @param {Boolean} [config.strokeEnabled] flag which enables or disables the stroke. The default value is true + * @param {String} [config.lineJoin] can be miter, round, or bevel. The default + * is miter + * @param {String} [config.lineCap] can be butt, round, or sqare. The default + * is butt + * @param {String} [config.shadowColor] + * @param {Integer} [config.shadowColorRed] set shadow color red component + * @param {Integer} [config.shadowColorGreen] set shadow color green component + * @param {Integer} [config.shadowColorBlue] set shadow color blue component + * @param {Integer} [config.shadowColorAlpha] set shadow color alpha component + * @param {Number} [config.shadowBlur] + * @param {Object} [config.shadowOffset] object with x and y component + * @param {Number} [config.shadowOffsetX] + * @param {Number} [config.shadowOffsetY] + * @param {Number} [config.shadowOpacity] shadow opacity. Can be any real number + * between 0 and 1 + * @param {Boolean} [config.shadowEnabled] flag which enables or disables the shadow. The default value is true + * @param {Array} [config.dash] + * @param {Boolean} [config.dashEnabled] flag which enables or disables the dashArray. The default value is true + * @param {Number} [config.x] + * @param {Number} [config.y] + * @param {Number} [config.width] + * @param {Number} [config.height] + * @param {Boolean} [config.visible] + * @param {Boolean} [config.listening] whether or not the node is listening for events + * @param {String} [config.id] unique id + * @param {String} [config.name] non-unique name + * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 + * @param {Object} [config.scale] set scale + * @param {Number} [config.scaleX] set scale x + * @param {Number} [config.scaleY] set scale y + * @param {Number} [config.rotation] rotation in degrees + * @param {Object} [config.offset] offset from center point and rotation point + * @param {Number} [config.offsetX] set offset x + * @param {Number} [config.offsetY] set offset y + * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop + * the entire stage by dragging any portion of the stage + * @param {Function} [config.dragBoundFunc] + * @example + * var imageObj = new Image();
+ * imageObj.onload = function() {
+ * var image = new Kinetic.Image({
+ * x: 200,
+ * y: 50,
+ * image: imageObj,
+ * width: 100,
+ * height: 100
+ * });
+ * };
+ * imageObj.src = '/path/to/image.jpg' + */ + Kinetic.Image = function(config) { + this.___init(config); + }; + + Kinetic.Image.prototype = { + ___init: function(config) { + // call super constructor + Kinetic.Shape.call(this, config); + this.className = IMAGE; + this.sceneFunc(this._sceneFunc); + this.hitFunc(this._hitFunc); + }, + _useBufferCanvas: function() { + return (this.hasShadow() || this.getAbsoluteOpacity() !== 1) && this.hasStroke(); + }, + _sceneFunc: function(context) { + var width = this.getWidth(), + height = this.getHeight(), + image = this.getImage(), + crop, cropWidth, cropHeight, params; + + if (image) { + crop = this.getCrop(), + cropWidth = crop.width; + cropHeight = crop.height; + if (cropWidth && cropHeight) { + params = [image, crop.x, crop.y, cropWidth, cropHeight, 0, 0, width, height]; + } + else { + params = [image, 0, 0, width, height]; + } + } + + context.beginPath(); + context.rect(0, 0, width, height); + context.closePath(); + context.fillStrokeShape(this); + + if (image) { + context.drawImage.apply(context, params); + } + }, + _hitFunc: function(context) { + var width = this.getWidth(), + height = this.getHeight(); + + context.beginPath(); + context.rect(0, 0, width, height); + context.closePath(); + context.fillStrokeShape(this); + }, + getWidth: function() { + var image = this.getImage(); + return this.attrs.width || (image ? image.width : 0); + }, + getHeight: function() { + var image = this.getImage(); + return this.attrs.height || (image ? image.height : 0); + } + }; + Kinetic.Util.extend(Kinetic.Image, Kinetic.Shape); + + // add getters setters + Kinetic.Factory.addGetterSetter(Kinetic.Image, 'image'); + + /** + * set image + * @name setImage + * @method + * @memberof Kinetic.Image.prototype + * @param {ImageObject} image + */ + + /** + * get image + * @name getImage + * @method + * @memberof Kinetic.Image.prototype + * @returns {ImageObject} + */ + + Kinetic.Factory.addComponentsGetterSetter(Kinetic.Image, 'crop', ['x', 'y', 'width', 'height']); + /** + * get/set crop + * @method + * @name crop + * @memberof Kinetic.Image.prototype + * @param {Object} crop + * @param {Number} crop.x + * @param {Number} crop.y + * @param {Number} crop.width + * @param {Number} crop.height + * @returns {Object} + * @example + * // get crop
+ * var crop = image.crop();

+ * + * // set crop
+ * image.crop({
+ * x: 20,
+ * y: 20,
+ * width: 20,
+ * height: 20
+ * }); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Image, 'cropX', 0); + /** + * get/set crop x + * @method + * @name cropX + * @memberof Kinetic.Image.prototype + * @param {Number} x + * @returns {Number} + * @example + * // get crop x
+ * var cropX = image.cropX();

+ * + * // set crop x
+ * image.cropX(20); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Image, 'cropY', 0); + /** + * get/set crop y + * @name cropY + * @method + * @memberof Kinetic.Image.prototype + * @param {Number} y + * @returns {Number} + * @example + * // get crop y
+ * var cropY = image.cropY();

+ * + * // set crop y
+ * image.cropY(20); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Image, 'cropWidth', 0); + /** + * get/set crop width + * @name cropWidth + * @method + * @memberof Kinetic.Image.prototype + * @param {Number} width + * @returns {Number} + * @example + * // get crop width
+ * var cropWidth = image.cropWidth();

+ * + * // set crop width
+ * image.cropWidth(20); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Image, 'cropHeight', 0); + /** + * get/set crop height + * @name cropHeight + * @method + * @memberof Kinetic.Image.prototype + * @param {Number} height + * @returns {Number} + * @example + * // get crop height
+ * var cropHeight = image.cropHeight();

+ * + * // set crop height
+ * image.cropHeight(20); + */ + + Kinetic.Collection.mapMethods(Kinetic.Image); +})(); +;(function() { + // constants + var AUTO = 'auto', + CANVAS = 'canvas', + CENTER = 'center', + CHANGE_KINETIC = 'Change.kinetic', + CONTEXT_2D = '2d', + DASH = '-', + EMPTY_STRING = '', + LEFT = 'left', + TEXT = 'text', + TEXT_UPPER = 'Text', + MIDDLE = 'middle', + NORMAL = 'normal', + PX_SPACE = 'px ', + SPACE = ' ', + RIGHT = 'right', + WORD = 'word', + CHAR = 'char', + NONE = 'none', + ATTR_CHANGE_LIST = ['fontFamily', 'fontSize', 'fontStyle', 'padding', 'align', 'lineHeight', 'text', 'width', 'height', 'wrap'], + + // cached variables + attrChangeListLen = ATTR_CHANGE_LIST.length, + dummyContext = document.createElement(CANVAS).getContext(CONTEXT_2D); + + /** + * Text constructor + * @constructor + * @memberof Kinetic + * @augments Kinetic.Shape + * @param {Object} config + * @param {String} [config.fontFamily] default is Arial + * @param {Number} [config.fontSize] in pixels. Default is 12 + * @param {String} [config.fontStyle] can be normal, bold, or italic. Default is normal + * @param {String} config.text + * @param {String} [config.align] can be left, center, or right + * @param {Number} [config.padding] + * @param {Number} [config.width] default is auto + * @param {Number} [config.height] default is auto + * @param {Number} [config.lineHeight] default is 1 + * @param {String} [config.wrap] can be word, char, or none. Default is word + * @param {String} [config.fill] fill color + * @param {Integer} [config.fillRed] set fill red component + * @param {Integer} [config.fillGreen] set fill green component + * @param {Integer} [config.fillBlue] set fill blue component + * @param {Integer} [config.fillAlpha] set fill alpha component + * @param {Image} [config.fillPatternImage] fill pattern image + * @param {Number} [config.fillPatternX] + * @param {Number} [config.fillPatternY] + * @param {Object} [config.fillPatternOffset] object with x and y component + * @param {Number} [config.fillPatternOffsetX] + * @param {Number} [config.fillPatternOffsetY] + * @param {Object} [config.fillPatternScale] object with x and y component + * @param {Number} [config.fillPatternScaleX] + * @param {Number} [config.fillPatternScaleY] + * @param {Number} [config.fillPatternRotation] + * @param {String} [config.fillPatternRepeat] can be "repeat", "repeat-x", "repeat-y", or "no-repeat". The default is "no-repeat" + * @param {Object} [config.fillLinearGradientStartPoint] object with x and y component + * @param {Number} [config.fillLinearGradientStartPointX] + * @param {Number} [config.fillLinearGradientStartPointY] + * @param {Object} [config.fillLinearGradientEndPoint] object with x and y component + * @param {Number} [config.fillLinearGradientEndPointX] + * @param {Number} [config.fillLinearGradientEndPointY] + * @param {Array} [config.fillLinearGradientColorStops] array of color stops + * @param {Object} [config.fillRadialGradientStartPoint] object with x and y component + * @param {Number} [config.fillRadialGradientStartPointX] + * @param {Number} [config.fillRadialGradientStartPointY] + * @param {Object} [config.fillRadialGradientEndPoint] object with x and y component + * @param {Number} [config.fillRadialGradientEndPointX] + * @param {Number} [config.fillRadialGradientEndPointY] + * @param {Number} [config.fillRadialGradientStartRadius] + * @param {Number} [config.fillRadialGradientEndRadius] + * @param {Array} [config.fillRadialGradientColorStops] array of color stops + * @param {Boolean} [config.fillEnabled] flag which enables or disables the fill. The default value is true + * @param {String} [config.fillPriority] can be color, linear-gradient, radial-graident, or pattern. The default value is color. The fillPriority property makes it really easy to toggle between different fill types. For example, if you want to toggle between a fill color style and a fill pattern style, simply set the fill property and the fillPattern properties, and then use setFillPriority('color') to render the shape with a color fill, or use setFillPriority('pattern') to render the shape with the pattern fill configuration + * @param {String} [config.stroke] stroke color + * @param {Integer} [config.strokeRed] set stroke red component + * @param {Integer} [config.strokeGreen] set stroke green component + * @param {Integer} [config.strokeBlue] set stroke blue component + * @param {Integer} [config.strokeAlpha] set stroke alpha component + * @param {Number} [config.strokeWidth] stroke width + * @param {Boolean} [config.strokeScaleEnabled] flag which enables or disables stroke scale. The default is true + * @param {Boolean} [config.strokeEnabled] flag which enables or disables the stroke. The default value is true + * @param {String} [config.lineJoin] can be miter, round, or bevel. The default + * is miter + * @param {String} [config.lineCap] can be butt, round, or sqare. The default + * is butt + * @param {String} [config.shadowColor] + * @param {Integer} [config.shadowColorRed] set shadow color red component + * @param {Integer} [config.shadowColorGreen] set shadow color green component + * @param {Integer} [config.shadowColorBlue] set shadow color blue component + * @param {Integer} [config.shadowColorAlpha] set shadow color alpha component + * @param {Number} [config.shadowBlur] + * @param {Object} [config.shadowOffset] object with x and y component + * @param {Number} [config.shadowOffsetX] + * @param {Number} [config.shadowOffsetY] + * @param {Number} [config.shadowOpacity] shadow opacity. Can be any real number + * between 0 and 1 + * @param {Boolean} [config.shadowEnabled] flag which enables or disables the shadow. The default value is true + * @param {Array} [config.dash] + * @param {Boolean} [config.dashEnabled] flag which enables or disables the dashArray. The default value is true + * @param {Number} [config.x] + * @param {Number} [config.y] + * @param {Number} [config.width] + * @param {Number} [config.height] + * @param {Boolean} [config.visible] + * @param {Boolean} [config.listening] whether or not the node is listening for events + * @param {String} [config.id] unique id + * @param {String} [config.name] non-unique name + * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 + * @param {Object} [config.scale] set scale + * @param {Number} [config.scaleX] set scale x + * @param {Number} [config.scaleY] set scale y + * @param {Number} [config.rotation] rotation in degrees + * @param {Object} [config.offset] offset from center point and rotation point + * @param {Number} [config.offsetX] set offset x + * @param {Number} [config.offsetY] set offset y + * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop + * the entire stage by dragging any portion of the stage + * @param {Function} [config.dragBoundFunc] + * @example + * var text = new Kinetic.Text({
+ * x: 10,
+ * y: 15,
+ * text: 'Simple Text',
+ * fontSize: 30,
+ * fontFamily: 'Calibri',
+ * fill: 'green'
+ * }); + */ + Kinetic.Text = function(config) { + this.___init(config); + }; + function _fillFunc(context) { + context.fillText(this.partialText, 0, 0); + } + function _strokeFunc(context) { + context.strokeText(this.partialText, 0, 0); + } + + Kinetic.Text.prototype = { + ___init: function(config) { + var that = this; + + if (config.width === undefined) { + config.width = AUTO; + } + if (config.height === undefined) { + config.height = AUTO; + } + + // call super constructor + Kinetic.Shape.call(this, config); + + this._fillFunc = _fillFunc; + this._strokeFunc = _strokeFunc; + this.className = TEXT_UPPER; + + // update text data for certain attr changes + for(var n = 0; n < attrChangeListLen; n++) { + this.on(ATTR_CHANGE_LIST[n] + CHANGE_KINETIC, that._setTextData); + } + + this._setTextData(); + this.sceneFunc(this._sceneFunc); + this.hitFunc(this._hitFunc); + }, + _sceneFunc: function(context) { + var p = this.getPadding(), + textHeight = this.getTextHeight(), + lineHeightPx = this.getLineHeight() * textHeight, + textArr = this.textArr, + textArrLen = textArr.length, + totalWidth = this.getWidth(), + n; + + context.setAttr('font', this._getContextFont()); + context.setAttr('textBaseline', MIDDLE); + context.setAttr('textAlign', LEFT); + context.save(); + context.translate(p, 0); + context.translate(0, p + textHeight / 2); + + // draw text lines + for(n = 0; n < textArrLen; n++) { + var obj = textArr[n], + text = obj.text, + width = obj.width; + + // horizontal alignment + context.save(); + if(this.getAlign() === RIGHT) { + context.translate(totalWidth - width - p * 2, 0); + } + else if(this.getAlign() === CENTER) { + context.translate((totalWidth - width - p * 2) / 2, 0); + } + + this.partialText = text; + context.fillStrokeShape(this); + context.restore(); + context.translate(0, lineHeightPx); + } + context.restore(); + }, + _hitFunc: function(context) { + var width = this.getWidth(), + height = this.getHeight(); + + context.beginPath(); + context.rect(0, 0, width, height); + context.closePath(); + context.fillStrokeShape(this); + }, + setText: function(text) { + var str = Kinetic.Util._isString(text) ? text : text.toString(); + this._setAttr(TEXT, str); + return this; + }, + /** + * get width of text area, which includes padding + * @method + * @memberof Kinetic.Text.prototype + * @returns {Number} + */ + getWidth: function() { + return this.attrs.width === AUTO ? this.getTextWidth() + this.getPadding() * 2 : this.attrs.width; + }, + /** + * get the height of the text area, which takes into account multi-line text, line heights, and padding + * @method + * @memberof Kinetic.Text.prototype + * @returns {Number} + */ + getHeight: function() { + return this.attrs.height === AUTO ? (this.getTextHeight() * this.textArr.length * this.getLineHeight()) + this.getPadding() * 2 : this.attrs.height; + }, + /** + * get text width + * @method + * @memberof Kinetic.Text.prototype + * @returns {Number} + */ + getTextWidth: function() { + return this.textWidth; + }, + /** + * get text height + * @method + * @memberof Kinetic.Text.prototype + * @returns {Number} + */ + getTextHeight: function() { + return this.textHeight; + }, + _getTextSize: function(text) { + var _context = dummyContext, + fontSize = this.getFontSize(), + metrics; + + _context.save(); + _context.font = this._getContextFont(); + + metrics = _context.measureText(text); + _context.restore(); + return { + width: metrics.width, + height: parseInt(fontSize, 10) + }; + }, + _getContextFont: function() { + return this.getFontStyle() + SPACE + this.getFontSize() + PX_SPACE + this.getFontFamily(); + }, + _addTextLine: function (line, width) { + return this.textArr.push({text: line, width: width}); + }, + _getTextWidth: function (text) { + return dummyContext.measureText(text).width; + }, + _setTextData: function () { + var lines = this.getText().split('\n'), + fontSize = +this.getFontSize(), + textWidth = 0, + lineHeightPx = this.getLineHeight() * fontSize, + width = this.attrs.width, + height = this.attrs.height, + fixedWidth = width !== AUTO, + fixedHeight = height !== AUTO, + padding = this.getPadding(), + maxWidth = width - padding * 2, + maxHeightPx = height - padding * 2, + currentHeightPx = 0, + wrap = this.getWrap(), + shouldWrap = wrap !== NONE, + wrapAtWord = wrap !== CHAR && shouldWrap; + + this.textArr = []; + dummyContext.save(); + dummyContext.font = this.getFontStyle() + SPACE + fontSize + PX_SPACE + this.getFontFamily(); + for (var i = 0, max = lines.length; i < max; ++i) { + var line = lines[i], + lineWidth = this._getTextWidth(line); + if (fixedWidth && lineWidth > maxWidth) { + /* + * if width is fixed and line does not fit entirely + * break the line into multiple fitting lines + */ + while (line.length > 0) { + /* + * use binary search to find the longest substring that + * that would fit in the specified width + */ + var low = 0, high = line.length, + match = '', matchWidth = 0; + while (low < high) { + var mid = (low + high) >>> 1, + substr = line.slice(0, mid + 1), + substrWidth = this._getTextWidth(substr); + if (substrWidth <= maxWidth) { + low = mid + 1; + match = substr; + matchWidth = substrWidth; + } else { + high = mid; + } + } + /* + * 'low' is now the index of the substring end + * 'match' is the substring + * 'matchWidth' is the substring width in px + */ + if (match) { + // a fitting substring was found + if (wrapAtWord) { + // try to find a space or dash where wrapping could be done + var wrapIndex = Math.max(match.lastIndexOf(SPACE), + match.lastIndexOf(DASH)) + 1; + if (wrapIndex > 0) { + // re-cut the substring found at the space/dash position + low = wrapIndex; + match = match.slice(0, low); + matchWidth = this._getTextWidth(match); + } + } + this._addTextLine(match, matchWidth); + currentHeightPx += lineHeightPx; + if (!shouldWrap || + (fixedHeight && currentHeightPx + lineHeightPx > maxHeightPx)) { + /* + * stop wrapping if wrapping is disabled or if adding + * one more line would overflow the fixed height + */ + break; + } + line = line.slice(low); + if (line.length > 0) { + // Check if the remaining text would fit on one line + lineWidth = this._getTextWidth(line); + if (lineWidth <= maxWidth) { + // if it does, add the line and break out of the loop + this._addTextLine(line, lineWidth); + currentHeightPx += lineHeightPx; + break; + } + } + } else { + // not even one character could fit in the element, abort + break; + } + } + } else { + // element width is automatically adjusted to max line width + this._addTextLine(line, lineWidth); + currentHeightPx += lineHeightPx; + textWidth = Math.max(textWidth, lineWidth); + } + // if element height is fixed, abort if adding one more line would overflow + if (fixedHeight && currentHeightPx + lineHeightPx > maxHeightPx) { + break; + } + } + dummyContext.restore(); + this.textHeight = fontSize; + this.textWidth = textWidth; + } + }; + Kinetic.Util.extend(Kinetic.Text, Kinetic.Shape); + + // add getters setters + Kinetic.Factory.addGetterSetter(Kinetic.Text, 'fontFamily', 'Arial'); + + /** + * get/set font family + * @name fontFamily + * @method + * @memberof Kinetic.Text.prototype + * @param {String} fontFamily + * @returns {String} + * @example + * // get font family
+ * var fontFamily = text.fontFamily();


+ * + * // set font family
+ * text.fontFamily('Arial'); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Text, 'fontSize', 12); + + /** + * get/set font size in pixels + * @name fontSize + * @method + * @memberof Kinetic.Text.prototype + * @param {Number} fontSize + * @returns {Number} + * @example + * // get font size
+ * var fontSize = text.fontSize();

+ * + * // set font size to 22px
+ * text.fontSize(22); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Text, 'fontStyle', NORMAL); + + /** + * set font style. Can be 'normal', 'italic', or 'bold'. 'normal' is the default. + * @name fontStyle + * @method + * @memberof Kinetic.Text.prototype + * @param {String} fontStyle + * @returns {String} + * @example + * // get font style
+ * var fontStyle = text.fontStyle();

+ * + * // set font style
+ * text.fontStyle('bold'); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Text, 'padding', 0); + + /** + * set padding + * @name padding + * @method + * @memberof Kinetic.Text.prototype + * @param {Number} padding + * @returns {Number} + * @example + * // get padding
+ * var padding = text.padding();

+ * + * // set padding to 10 pixels
+ * text.padding(10); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Text, 'align', LEFT); + + /** + * get/set horizontal align of text. Can be 'left', 'center', or 'right' + * @name align + * @method + * @memberof Kinetic.Text.prototype + * @param {String} align + * @returns {String} + * @example + * // get text align
+ * var align = text.align();

+ * + * // center text
+ * text.align('center');

+ * + * // align text to right
+ * text.align('right'); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Text, 'lineHeight', 1); + + /** + * get/set line height. The default is 1. + * @name lineHeight + * @method + * @memberof Kinetic.Text.prototype + * @param {Number} lineHeight + * @returns {Number} + * @example + * // get line height
+ * var lineHeight = text.lineHeight();


+ * + * // set the line height
+ * text.lineHeight(2); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Text, 'wrap', WORD); + + /** + * get/set wrap. Can be word, char, or none. Default is word. + * @name wrap + * @method + * @memberof Kinetic.Text.prototype + * @param {String} wrap + * @returns {String} + * @example + * // get wrap
+ * var wrap = text.wrap();

+ * + * // set wrap
+ * text.wrap('word'); + */ + + Kinetic.Factory.addGetter(Kinetic.Text, 'text', EMPTY_STRING); + Kinetic.Factory.addOverloadedGetterSetter(Kinetic.Text, 'text'); + + /** + * get/set text + * @name getText + * @method + * @memberof Kinetic.Text.prototype + * @param {String} text + * @returns {String} + * @example + * // get text
+ * var text = text.text();

+ * + * // set text
+ * text.text('Hello world!'); + */ + + Kinetic.Collection.mapMethods(Kinetic.Text); +})(); +;(function() { + /** + * Line constructor.  Lines are defined by an array of points and + * a tension + * @constructor + * @memberof Kinetic + * @augments Kinetic.Shape + * @param {Object} config + * @param {Array} config.points + * @param {Number} [config.tension] Higher values will result in a more curvy line. A value of 0 will result in no interpolation. + * The default is 0 + * @param {Boolean} [config.closed] defines whether or not the line shape is closed, creating a polygon or blob + * @param {String} [config.fill] fill color + * @param {Integer} [config.fillRed] set fill red component + * @param {Integer} [config.fillGreen] set fill green component + * @param {Integer} [config.fillBlue] set fill blue component + * @param {Integer} [config.fillAlpha] set fill alpha component + * @param {Image} [config.fillPatternImage] fill pattern image + * @param {Number} [config.fillPatternX] + * @param {Number} [config.fillPatternY] + * @param {Object} [config.fillPatternOffset] object with x and y component + * @param {Number} [config.fillPatternOffsetX] + * @param {Number} [config.fillPatternOffsetY] + * @param {Object} [config.fillPatternScale] object with x and y component + * @param {Number} [config.fillPatternScaleX] + * @param {Number} [config.fillPatternScaleY] + * @param {Number} [config.fillPatternRotation] + * @param {String} [config.fillPatternRepeat] can be "repeat", "repeat-x", "repeat-y", or "no-repeat". The default is "no-repeat" + * @param {Object} [config.fillLinearGradientStartPoint] object with x and y component + * @param {Number} [config.fillLinearGradientStartPointX] + * @param {Number} [config.fillLinearGradientStartPointY] + * @param {Object} [config.fillLinearGradientEndPoint] object with x and y component + * @param {Number} [config.fillLinearGradientEndPointX] + * @param {Number} [config.fillLinearGradientEndPointY] + * @param {Array} [config.fillLinearGradientColorStops] array of color stops + * @param {Object} [config.fillRadialGradientStartPoint] object with x and y component + * @param {Number} [config.fillRadialGradientStartPointX] + * @param {Number} [config.fillRadialGradientStartPointY] + * @param {Object} [config.fillRadialGradientEndPoint] object with x and y component + * @param {Number} [config.fillRadialGradientEndPointX] + * @param {Number} [config.fillRadialGradientEndPointY] + * @param {Number} [config.fillRadialGradientStartRadius] + * @param {Number} [config.fillRadialGradientEndRadius] + * @param {Array} [config.fillRadialGradientColorStops] array of color stops + * @param {Boolean} [config.fillEnabled] flag which enables or disables the fill. The default value is true + * @param {String} [config.fillPriority] can be color, linear-gradient, radial-graident, or pattern. The default value is color. The fillPriority property makes it really easy to toggle between different fill types. For example, if you want to toggle between a fill color style and a fill pattern style, simply set the fill property and the fillPattern properties, and then use setFillPriority('color') to render the shape with a color fill, or use setFillPriority('pattern') to render the shape with the pattern fill configuration + * @param {String} [config.stroke] stroke color + * @param {Integer} [config.strokeRed] set stroke red component + * @param {Integer} [config.strokeGreen] set stroke green component + * @param {Integer} [config.strokeBlue] set stroke blue component + * @param {Integer} [config.strokeAlpha] set stroke alpha component + * @param {Number} [config.strokeWidth] stroke width + * @param {Boolean} [config.strokeScaleEnabled] flag which enables or disables stroke scale. The default is true + * @param {Boolean} [config.strokeEnabled] flag which enables or disables the stroke. The default value is true + * @param {String} [config.lineJoin] can be miter, round, or bevel. The default + * is miter + * @param {String} [config.lineCap] can be butt, round, or sqare. The default + * is butt + * @param {String} [config.shadowColor] + * @param {Integer} [config.shadowColorRed] set shadow color red component + * @param {Integer} [config.shadowColorGreen] set shadow color green component + * @param {Integer} [config.shadowColorBlue] set shadow color blue component + * @param {Integer} [config.shadowColorAlpha] set shadow color alpha component + * @param {Number} [config.shadowBlur] + * @param {Object} [config.shadowOffset] object with x and y component + * @param {Number} [config.shadowOffsetX] + * @param {Number} [config.shadowOffsetY] + * @param {Number} [config.shadowOpacity] shadow opacity. Can be any real number + * between 0 and 1 + * @param {Boolean} [config.shadowEnabled] flag which enables or disables the shadow. The default value is true + * @param {Array} [config.dash] + * @param {Boolean} [config.dashEnabled] flag which enables or disables the dashArray. The default value is true + * @param {Number} [config.x] + * @param {Number} [config.y] + * @param {Number} [config.width] + * @param {Number} [config.height] + * @param {Boolean} [config.visible] + * @param {Boolean} [config.listening] whether or not the node is listening for events + * @param {String} [config.id] unique id + * @param {String} [config.name] non-unique name + * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 + * @param {Object} [config.scale] set scale + * @param {Number} [config.scaleX] set scale x + * @param {Number} [config.scaleY] set scale y + * @param {Number} [config.rotation] rotation in degrees + * @param {Object} [config.offset] offset from center point and rotation point + * @param {Number} [config.offsetX] set offset x + * @param {Number} [config.offsetY] set offset y + * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop + * the entire stage by dragging any portion of the stage + * @param {Function} [config.dragBoundFunc] + * @example + * var line = new Kinetic.Line({
+ * x: 100,
+ * y: 50,
+ * points: [73, 70, 340, 23, 450, 60, 500, 20],
+ * stroke: 'red',
+ * tension: 1
+ * }); + */ + Kinetic.Line = function(config) { + this.___init(config); + }; + + Kinetic.Line.prototype = { + ___init: function(config) { + var that = this; + // call super constructor + Kinetic.Shape.call(this, config); + this.className = 'Line'; + + this.on('pointsChange.kinetic tensionChange.kinetic closedChange.kinetic', function() { + this._clearCache('tensionPoints'); + }); + + this.sceneFunc(this._sceneFunc); + }, + _sceneFunc: function(context) { + var points = this.getPoints(), + length = points.length, + tension = this.getTension(), + closed = this.getClosed(), + tp, len, n, point; + + context.beginPath(); + context.moveTo(points[0], points[1]); + + // tension + if(tension !== 0 && length > 4) { + tp = this.getTensionPoints(); + len = tp.length; + n = closed ? 0 : 4; + + if (!closed) { + context.quadraticCurveTo(tp[0], tp[1], tp[2], tp[3]); + } + + while(n < len - 2) { + context.bezierCurveTo(tp[n++], tp[n++], tp[n++], tp[n++], tp[n++], tp[n++]); + } + + if (!closed) { + context.quadraticCurveTo(tp[len-2], tp[len-1], points[length-2], points[length-1]); + } + } + // no tension + else { + for(n = 2; n < length; n+=2) { + context.lineTo(points[n], points[n+1]); + } + } + + // closed e.g. polygons and blobs + if (closed) { + context.closePath(); + context.fillStrokeShape(this); + } + // open e.g. lines and splines + else { + context.strokeShape(this); + }; + }, + getTensionPoints: function() { + return this._getCache('tensionPoints', this._getTensionPoints); + }, + _getTensionPoints: function() { + if (this.getClosed()) { + return this._getTensionPointsClosed(); + } + else { + return Kinetic.Util._expandPoints(this.getPoints(), this.getTension()); + } + }, + _getTensionPointsClosed: function() { + var p = this.getPoints(), + len = p.length, + tension = this.getTension(), + util = Kinetic.Util, + firstControlPoints = util._getControlPoints( + p[len-2], + p[len-1], + p[0], + p[1], + p[2], + p[3], + tension + ), + lastControlPoints = util._getControlPoints( + p[len-4], + p[len-3], + p[len-2], + p[len-1], + p[0], + p[1], + tension + ), + middle = Kinetic.Util._expandPoints(p, tension), + tp = [ + firstControlPoints[2], + firstControlPoints[3] + ] + .concat(middle) + .concat([ + lastControlPoints[0], + lastControlPoints[1], + p[len-2], + p[len-1], + lastControlPoints[2], + lastControlPoints[3], + firstControlPoints[0], + firstControlPoints[1], + p[0], + p[1] + ]); + + return tp; + } + }; + Kinetic.Util.extend(Kinetic.Line, Kinetic.Shape); + + // add getters setters + Kinetic.Factory.addGetterSetter(Kinetic.Line, 'closed', false); + + /** + * get/set closed flag. The default is false + * @name closed + * @method + * @memberof Kinetic.Line.prototype + * @param {Boolean} closed + * @returns {Boolean} + * @example + * // get closed flag
+ * var closed = line.closed();

+ * + * // close the shape
+ * line.closed(true);

+ * + * // open the shape
+ * line.closed(false); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Line, 'tension', 0); + + /** + * get/set tension + * @name tension + * @method + * @memberof Kinetic.Line.prototype + * @param {Number} Higher values will result in a more curvy line. A value of 0 will result in no interpolation. + * The default is 0 + * @returns {Number} + * @example + * // get tension
+ * var tension = line.tension();

+ * + * // set tension
+ * line.tension(3); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Line, 'points'); + /** + * get/set points array + * @name points + * @method + * @memberof Kinetic.Line.prototype + * @param {Array} points + * @returns {Array} + * @example + * // get points
+ * var points = line.points();

+ * + * // set points
+ * line.points([10, 20, 30, 40, 50, 60]);

+ * + * // push a new point
+ * line.points(line.points().concat([70, 80])); + */ + + Kinetic.Collection.mapMethods(Kinetic.Line); +})();;(function() { + /** + * Sprite constructor + * @constructor + * @memberof Kinetic + * @augments Kinetic.Shape + * @param {Object} config + * @param {String} config.animation animation key + * @param {Object} config.animations animation map + * @param {Integer} [config.frameIndex] animation frame index + * @param {Image} config.image image object + * @param {String} [config.fill] fill color + * @param {Integer} [config.fillRed] set fill red component + * @param {Integer} [config.fillGreen] set fill green component + * @param {Integer} [config.fillBlue] set fill blue component + * @param {Integer} [config.fillAlpha] set fill alpha component + * @param {Image} [config.fillPatternImage] fill pattern image + * @param {Number} [config.fillPatternX] + * @param {Number} [config.fillPatternY] + * @param {Object} [config.fillPatternOffset] object with x and y component + * @param {Number} [config.fillPatternOffsetX] + * @param {Number} [config.fillPatternOffsetY] + * @param {Object} [config.fillPatternScale] object with x and y component + * @param {Number} [config.fillPatternScaleX] + * @param {Number} [config.fillPatternScaleY] + * @param {Number} [config.fillPatternRotation] + * @param {String} [config.fillPatternRepeat] can be "repeat", "repeat-x", "repeat-y", or "no-repeat". The default is "no-repeat" + * @param {Object} [config.fillLinearGradientStartPoint] object with x and y component + * @param {Number} [config.fillLinearGradientStartPointX] + * @param {Number} [config.fillLinearGradientStartPointY] + * @param {Object} [config.fillLinearGradientEndPoint] object with x and y component + * @param {Number} [config.fillLinearGradientEndPointX] + * @param {Number} [config.fillLinearGradientEndPointY] + * @param {Array} [config.fillLinearGradientColorStops] array of color stops + * @param {Object} [config.fillRadialGradientStartPoint] object with x and y component + * @param {Number} [config.fillRadialGradientStartPointX] + * @param {Number} [config.fillRadialGradientStartPointY] + * @param {Object} [config.fillRadialGradientEndPoint] object with x and y component + * @param {Number} [config.fillRadialGradientEndPointX] + * @param {Number} [config.fillRadialGradientEndPointY] + * @param {Number} [config.fillRadialGradientStartRadius] + * @param {Number} [config.fillRadialGradientEndRadius] + * @param {Array} [config.fillRadialGradientColorStops] array of color stops + * @param {Boolean} [config.fillEnabled] flag which enables or disables the fill. The default value is true + * @param {String} [config.fillPriority] can be color, linear-gradient, radial-graident, or pattern. The default value is color. The fillPriority property makes it really easy to toggle between different fill types. For example, if you want to toggle between a fill color style and a fill pattern style, simply set the fill property and the fillPattern properties, and then use setFillPriority('color') to render the shape with a color fill, or use setFillPriority('pattern') to render the shape with the pattern fill configuration + * @param {String} [config.stroke] stroke color + * @param {Integer} [config.strokeRed] set stroke red component + * @param {Integer} [config.strokeGreen] set stroke green component + * @param {Integer} [config.strokeBlue] set stroke blue component + * @param {Integer} [config.strokeAlpha] set stroke alpha component + * @param {Number} [config.strokeWidth] stroke width + * @param {Boolean} [config.strokeScaleEnabled] flag which enables or disables stroke scale. The default is true + * @param {Boolean} [config.strokeEnabled] flag which enables or disables the stroke. The default value is true + * @param {String} [config.lineJoin] can be miter, round, or bevel. The default + * is miter + * @param {String} [config.lineCap] can be butt, round, or sqare. The default + * is butt + * @param {String} [config.shadowColor] + * @param {Integer} [config.shadowColorRed] set shadow color red component + * @param {Integer} [config.shadowColorGreen] set shadow color green component + * @param {Integer} [config.shadowColorBlue] set shadow color blue component + * @param {Integer} [config.shadowColorAlpha] set shadow color alpha component + * @param {Number} [config.shadowBlur] + * @param {Object} [config.shadowOffset] object with x and y component + * @param {Number} [config.shadowOffsetX] + * @param {Number} [config.shadowOffsetY] + * @param {Number} [config.shadowOpacity] shadow opacity. Can be any real number + * between 0 and 1 + * @param {Boolean} [config.shadowEnabled] flag which enables or disables the shadow. The default value is true + * @param {Array} [config.dash] + * @param {Boolean} [config.dashEnabled] flag which enables or disables the dashArray. The default value is true + * @param {Number} [config.x] + * @param {Number} [config.y] + * @param {Number} [config.width] + * @param {Number} [config.height] + * @param {Boolean} [config.visible] + * @param {Boolean} [config.listening] whether or not the node is listening for events + * @param {String} [config.id] unique id + * @param {String} [config.name] non-unique name + * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 + * @param {Object} [config.scale] set scale + * @param {Number} [config.scaleX] set scale x + * @param {Number} [config.scaleY] set scale y + * @param {Number} [config.rotation] rotation in degrees + * @param {Object} [config.offset] offset from center point and rotation point + * @param {Number} [config.offsetX] set offset x + * @param {Number} [config.offsetY] set offset y + * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop + * the entire stage by dragging any portion of the stage + * @param {Function} [config.dragBoundFunc] + * @example + * var imageObj = new Image();
+ * imageObj.onload = function() {
+ * var sprite = new Kinetic.Sprite({
+ * x: 200,
+ * y: 100,
+ * image: imageObj,
+ * animation: 'standing',
+ * animations: {
+ * standing: [
+ * // x, y, width, height (6 frames)
+ * 0, 0, 49, 109,
+ * 52, 0, 49, 109,
+ * 105, 0, 49, 109,
+ * 158, 0, 49, 109,
+ * 210, 0, 49, 109,
+ * 262, 0, 49, 109
+ * ],
+ * kicking: [
+ * // x, y, width, height (6 frames)
+ * 0, 109, 45, 98,
+ * 45, 109, 45, 98,
+ * 95, 109, 63, 98,
+ * 156, 109, 70, 98,
+ * 229, 109, 60, 98,
+ * 287, 109, 41, 98
+ * ]
+ * },
+ * frameRate: 7,
+ * frameIndex: 0
+ * });
+ * };
+ * imageObj.src = '/path/to/image.jpg' + */ + Kinetic.Sprite = function(config) { + this.___init(config); + }; + + Kinetic.Sprite.prototype = { + ___init: function(config) { + // call super constructor + Kinetic.Shape.call(this, config); + this.className = 'Sprite'; + + this.anim = new Kinetic.Animation(); + this.on('animationChange.kinetic', function() { + // reset index when animation changes + this.frameIndex(0); + }); + + this.sceneFunc(this._sceneFunc); + this.hitFunc(this._hitFunc); + }, + _sceneFunc: function(context) { + var anim = this.getAnimation(), + index = this.frameIndex(), + ix4 = index * 4, + set = this.getAnimations()[anim], + x = set[ix4 + 0], + y = set[ix4 + 1], + width = set[ix4 + 2], + height = set[ix4 + 3], + image = this.getImage(); + + if(image) { + context.drawImage(image, x, y, width, height, 0, 0, width, height); + } + }, + _hitFunc: function(context) { + var anim = this.getAnimation(), + index = this.frameIndex(), + ix4 = index * 4, + set = this.getAnimations()[anim], + width = set[ix4 + 2], + height = set[ix4 + 3]; + + context.beginPath(); + context.rect(0, 0, width, height); + context.closePath(); + context.fillShape(this); + }, + _useBufferCanvas: function() { + return (this.hasShadow() || this.getAbsoluteOpacity() !== 1) && this.hasStroke(); + }, + /** + * start sprite animation + * @method + * @memberof Kinetic.Sprite.prototype + */ + start: function() { + var that = this; + var layer = this.getLayer(); + + /* + * animation object has no executable function because + * the updates are done with a fixed FPS with the setInterval + * below. The anim object only needs the layer reference for + * redraw + */ + this.anim.setLayers(layer); + + this.interval = setInterval(function() { + that._updateIndex(); + }, 1000 / this.getFrameRate()); + + this.anim.start(); + }, + /** + * stop sprite animation + * @method + * @memberof Kinetic.Sprite.prototype + */ + stop: function() { + this.anim.stop(); + clearInterval(this.interval); + }, + _updateIndex: function() { + var index = this.frameIndex(), + animation = this.getAnimation(), + animations = this.getAnimations(), + anim = animations[animation], + len = anim.length / 4; + + if(index < len - 1) { + this.frameIndex(index + 1); + } + else { + this.frameIndex(0); + } + } + }; + Kinetic.Util.extend(Kinetic.Sprite, Kinetic.Shape); + + // add getters setters + Kinetic.Factory.addGetterSetter(Kinetic.Sprite, 'animation'); + + /** + * get/set animation key + * @name animation + * @method + * @memberof Kinetic.Sprite.prototype + * @param {String} anim animation key + * @returns {String} + * @example + * // get animation key
+ * var animation = sprite.animation();

+ * + * // set animation key
+ * sprite.animation('kicking'); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Sprite, 'animations'); + + /** + * get/set animations map + * @name animations + * @method + * @memberof Kinetic.Sprite.prototype + * @param {Object} animations + * @returns {Object} + * @example + * // get animations map
+ * var animations = sprite.animations();

+ * + * // set animations map
+ * sprite.animations({
+ * standing: [
+ * // x, y, width, height (6 frames)
+ * 0, 0, 49, 109,
+ * 52, 0, 49, 109,
+ * 105, 0, 49, 109,
+ * 158, 0, 49, 109,
+ * 210, 0, 49, 109,
+ * 262, 0, 49, 109
+ * ],
+ * kicking: [
+ * // x, y, width, height (6 frames)
+ * 0, 109, 45, 98,
+ * 45, 109, 45, 98,
+ * 95, 109, 63, 98,
+ * 156, 109, 70, 98,
+ * 229, 109, 60, 98,
+ * 287, 109, 41, 98
+ * ]
+ * }); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Sprite, 'image'); + + /** + * get/set image + * @name image + * @method + * @memberof Kinetic.Sprite.prototype + * @param {Image} image + * @returns {Image} + * @example + * // get image + * var image = sprite.image();

+ * + * // set image
+ * sprite.image(imageObj); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Sprite, 'frameIndex', 0); + + /** + * set/set animation frame index + * @name frameIndex + * @method + * @memberof Kinetic.Sprite.prototype + * @param {Integer} frameIndex + * @returns {Integer} + * @example + * // get animation frame index
+ * var frameIndex = sprite.frameIndex();

+ * + * // set animation frame index
+ * sprite.frameIndex(3); + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Sprite, 'frameRate', 17); + + /** + * get/set frame rate in frames per second. Increase this number to make the sprite + * animation run faster, and decrease the number to make the sprite animation run slower + * The default is 17 frames per second + * @name frameRate + * @method + * @memberof Kinetic.Sprite.prototype + * @param {Integer} frameRate + * @returns {Integer} + * @example + * // get frame rate
+ * var frameRate = sprite.frameRate();

+ * + * // set frame rate to 2 frames per second
+ * sprite.frameRate(2); + */ + + Kinetic.Factory.backCompat(Kinetic.Sprite, { + index: 'frameIndex', + getIndex: 'getFrameIndex', + setIndex: 'setFrameIndex' + }); + + Kinetic.Collection.mapMethods(Kinetic.Sprite); +})(); +;(function () { + /** + * Path constructor. + * @author Jason Follas + * @constructor + * @memberof Kinetic + * @augments Kinetic.Shape + * @param {Object} config + * @param {String} config.data SVG data string + * @param {String} [config.fill] fill color + * @param {Integer} [config.fillRed] set fill red component + * @param {Integer} [config.fillGreen] set fill green component + * @param {Integer} [config.fillBlue] set fill blue component + * @param {Integer} [config.fillAlpha] set fill alpha component + * @param {Image} [config.fillPatternImage] fill pattern image + * @param {Number} [config.fillPatternX] + * @param {Number} [config.fillPatternY] + * @param {Object} [config.fillPatternOffset] object with x and y component + * @param {Number} [config.fillPatternOffsetX] + * @param {Number} [config.fillPatternOffsetY] + * @param {Object} [config.fillPatternScale] object with x and y component + * @param {Number} [config.fillPatternScaleX] + * @param {Number} [config.fillPatternScaleY] + * @param {Number} [config.fillPatternRotation] + * @param {String} [config.fillPatternRepeat] can be "repeat", "repeat-x", "repeat-y", or "no-repeat". The default is "no-repeat" + * @param {Object} [config.fillLinearGradientStartPoint] object with x and y component + * @param {Number} [config.fillLinearGradientStartPointX] + * @param {Number} [config.fillLinearGradientStartPointY] + * @param {Object} [config.fillLinearGradientEndPoint] object with x and y component + * @param {Number} [config.fillLinearGradientEndPointX] + * @param {Number} [config.fillLinearGradientEndPointY] + * @param {Array} [config.fillLinearGradientColorStops] array of color stops + * @param {Object} [config.fillRadialGradientStartPoint] object with x and y component + * @param {Number} [config.fillRadialGradientStartPointX] + * @param {Number} [config.fillRadialGradientStartPointY] + * @param {Object} [config.fillRadialGradientEndPoint] object with x and y component + * @param {Number} [config.fillRadialGradientEndPointX] + * @param {Number} [config.fillRadialGradientEndPointY] + * @param {Number} [config.fillRadialGradientStartRadius] + * @param {Number} [config.fillRadialGradientEndRadius] + * @param {Array} [config.fillRadialGradientColorStops] array of color stops + * @param {Boolean} [config.fillEnabled] flag which enables or disables the fill. The default value is true + * @param {String} [config.fillPriority] can be color, linear-gradient, radial-graident, or pattern. The default value is color. The fillPriority property makes it really easy to toggle between different fill types. For example, if you want to toggle between a fill color style and a fill pattern style, simply set the fill property and the fillPattern properties, and then use setFillPriority('color') to render the shape with a color fill, or use setFillPriority('pattern') to render the shape with the pattern fill configuration + * @param {String} [config.stroke] stroke color + * @param {Integer} [config.strokeRed] set stroke red component + * @param {Integer} [config.strokeGreen] set stroke green component + * @param {Integer} [config.strokeBlue] set stroke blue component + * @param {Integer} [config.strokeAlpha] set stroke alpha component + * @param {Number} [config.strokeWidth] stroke width + * @param {Boolean} [config.strokeScaleEnabled] flag which enables or disables stroke scale. The default is true + * @param {Boolean} [config.strokeEnabled] flag which enables or disables the stroke. The default value is true + * @param {String} [config.lineJoin] can be miter, round, or bevel. The default + * is miter + * @param {String} [config.lineCap] can be butt, round, or sqare. The default + * is butt + * @param {String} [config.shadowColor] + * @param {Integer} [config.shadowColorRed] set shadow color red component + * @param {Integer} [config.shadowColorGreen] set shadow color green component + * @param {Integer} [config.shadowColorBlue] set shadow color blue component + * @param {Integer} [config.shadowColorAlpha] set shadow color alpha component + * @param {Number} [config.shadowBlur] + * @param {Object} [config.shadowOffset] object with x and y component + * @param {Number} [config.shadowOffsetX] + * @param {Number} [config.shadowOffsetY] + * @param {Number} [config.shadowOpacity] shadow opacity. Can be any real number + * between 0 and 1 + * @param {Boolean} [config.shadowEnabled] flag which enables or disables the shadow. The default value is true + * @param {Array} [config.dash] + * @param {Boolean} [config.dashEnabled] flag which enables or disables the dashArray. The default value is true + * @param {Number} [config.x] + * @param {Number} [config.y] + * @param {Number} [config.width] + * @param {Number} [config.height] + * @param {Boolean} [config.visible] + * @param {Boolean} [config.listening] whether or not the node is listening for events + * @param {String} [config.id] unique id + * @param {String} [config.name] non-unique name + * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 + * @param {Object} [config.scale] set scale + * @param {Number} [config.scaleX] set scale x + * @param {Number} [config.scaleY] set scale y + * @param {Number} [config.rotation] rotation in degrees + * @param {Object} [config.offset] offset from center point and rotation point + * @param {Number} [config.offsetX] set offset x + * @param {Number} [config.offsetY] set offset y + * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop + * the entire stage by dragging any portion of the stage + * @param {Function} [config.dragBoundFunc] + * @example + * var path = new Kinetic.Path({
+ * x: 240,
+ * y: 40,
+ * data: 'M12.582,9.551C3.251,16.237,0.921,29.021,7.08,38.564l-2.36,1.689l4.893,2.262l4.893,2.262l-0.568-5.36l-0.567-5.359l-2.365,1.694c-4.657-7.375-2.83-17.185,4.352-22.33c7.451-5.338,17.817-3.625,23.156,3.824c5.337,7.449,3.625,17.813-3.821,23.152l2.857,3.988c9.617-6.893,11.827-20.277,4.935-29.896C35.591,4.87,22.204,2.658,12.582,9.551z',
+ * fill: 'green',
+ * scale: 2
+ * }); + */ + Kinetic.Path = function (config) { + this.___init(config); + }; + + Kinetic.Path.prototype = { + ___init: function (config) { + this.dataArray = []; + var that = this; + + // call super constructor + Kinetic.Shape.call(this, config); + this.className = 'Path'; + + this.dataArray = Kinetic.Path.parsePathData(this.getData()); + this.on('dataChange.kinetic', function () { + that.dataArray = Kinetic.Path.parsePathData(this.getData()); + }); + + this.sceneFunc(this._sceneFunc); + }, + _sceneFunc: function(context) { + var ca = this.dataArray, + closedPath = false; + + // context position + context.beginPath(); + for (var n = 0; n < ca.length; n++) { + var c = ca[n].command; + var p = ca[n].points; + switch (c) { + case 'L': + context.lineTo(p[0], p[1]); + break; + case 'M': + context.moveTo(p[0], p[1]); + break; + case 'C': + context.bezierCurveTo(p[0], p[1], p[2], p[3], p[4], p[5]); + break; + case 'Q': + context.quadraticCurveTo(p[0], p[1], p[2], p[3]); + break; + case 'A': + var cx = p[0], cy = p[1], rx = p[2], ry = p[3], theta = p[4], dTheta = p[5], psi = p[6], fs = p[7]; + + var r = (rx > ry) ? rx : ry; + var scaleX = (rx > ry) ? 1 : rx / ry; + var scaleY = (rx > ry) ? ry / rx : 1; + + context.translate(cx, cy); + context.rotate(psi); + context.scale(scaleX, scaleY); + context.arc(0, 0, r, theta, theta + dTheta, 1 - fs); + context.scale(1 / scaleX, 1 / scaleY); + context.rotate(-psi); + context.translate(-cx, -cy); + + break; + case 'z': + context.closePath(); + closedPath = true; + break; + } + } + + if (closedPath) { + context.fillStrokeShape(this); + } + else { + context.strokeShape(this); + } + } + }; + Kinetic.Util.extend(Kinetic.Path, Kinetic.Shape); + + Kinetic.Path.getLineLength = function(x1, y1, x2, y2) { + return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); + }; + Kinetic.Path.getPointOnLine = function(dist, P1x, P1y, P2x, P2y, fromX, fromY) { + if(fromX === undefined) { + fromX = P1x; + } + if(fromY === undefined) { + fromY = P1y; + } + + var m = (P2y - P1y) / ((P2x - P1x) + 0.00000001); + var run = Math.sqrt(dist * dist / (1 + m * m)); + if(P2x < P1x) + run *= -1; + var rise = m * run; + var pt; + + if (P2x === P1x) { // vertical line + pt = { + x: fromX, + y: fromY + rise + }; + } else if((fromY - P1y) / ((fromX - P1x) + 0.00000001) === m) { + pt = { + x: fromX + run, + y: fromY + rise + }; + } + else { + var ix, iy; + + var len = this.getLineLength(P1x, P1y, P2x, P2y); + if(len < 0.00000001) { + return undefined; + } + var u = (((fromX - P1x) * (P2x - P1x)) + ((fromY - P1y) * (P2y - P1y))); + u = u / (len * len); + ix = P1x + u * (P2x - P1x); + iy = P1y + u * (P2y - P1y); + + var pRise = this.getLineLength(fromX, fromY, ix, iy); + var pRun = Math.sqrt(dist * dist - pRise * pRise); + run = Math.sqrt(pRun * pRun / (1 + m * m)); + if(P2x < P1x) + run *= -1; + rise = m * run; + pt = { + x: ix + run, + y: iy + rise + }; + } + + return pt; + }; + + Kinetic.Path.getPointOnCubicBezier = function(pct, P1x, P1y, P2x, P2y, P3x, P3y, P4x, P4y) { + function CB1(t) { + return t * t * t; + } + function CB2(t) { + return 3 * t * t * (1 - t); + } + function CB3(t) { + return 3 * t * (1 - t) * (1 - t); + } + function CB4(t) { + return (1 - t) * (1 - t) * (1 - t); + } + var x = P4x * CB1(pct) + P3x * CB2(pct) + P2x * CB3(pct) + P1x * CB4(pct); + var y = P4y * CB1(pct) + P3y * CB2(pct) + P2y * CB3(pct) + P1y * CB4(pct); + + return { + x: x, + y: y + }; + }; + Kinetic.Path.getPointOnQuadraticBezier = function(pct, P1x, P1y, P2x, P2y, P3x, P3y) { + function QB1(t) { + return t * t; + } + function QB2(t) { + return 2 * t * (1 - t); + } + function QB3(t) { + return (1 - t) * (1 - t); + } + var x = P3x * QB1(pct) + P2x * QB2(pct) + P1x * QB3(pct); + var y = P3y * QB1(pct) + P2y * QB2(pct) + P1y * QB3(pct); + + return { + x: x, + y: y + }; + }; + Kinetic.Path.getPointOnEllipticalArc = function(cx, cy, rx, ry, theta, psi) { + var cosPsi = Math.cos(psi), sinPsi = Math.sin(psi); + var pt = { + x: rx * Math.cos(theta), + y: ry * Math.sin(theta) + }; + return { + x: cx + (pt.x * cosPsi - pt.y * sinPsi), + y: cy + (pt.x * sinPsi + pt.y * cosPsi) + }; + }; + /* + * get parsed data array from the data + * string. V, v, H, h, and l data are converted to + * L data for the purpose of high performance Path + * rendering + */ + Kinetic.Path.parsePathData = function(data) { + // Path Data Segment must begin with a moveTo + //m (x y)+ Relative moveTo (subsequent points are treated as lineTo) + //M (x y)+ Absolute moveTo (subsequent points are treated as lineTo) + //l (x y)+ Relative lineTo + //L (x y)+ Absolute LineTo + //h (x)+ Relative horizontal lineTo + //H (x)+ Absolute horizontal lineTo + //v (y)+ Relative vertical lineTo + //V (y)+ Absolute vertical lineTo + //z (closepath) + //Z (closepath) + //c (x1 y1 x2 y2 x y)+ Relative Bezier curve + //C (x1 y1 x2 y2 x y)+ Absolute Bezier curve + //q (x1 y1 x y)+ Relative Quadratic Bezier + //Q (x1 y1 x y)+ Absolute Quadratic Bezier + //t (x y)+ Shorthand/Smooth Relative Quadratic Bezier + //T (x y)+ Shorthand/Smooth Absolute Quadratic Bezier + //s (x2 y2 x y)+ Shorthand/Smooth Relative Bezier curve + //S (x2 y2 x y)+ Shorthand/Smooth Absolute Bezier curve + //a (rx ry x-axis-rotation large-arc-flag sweep-flag x y)+ Relative Elliptical Arc + //A (rx ry x-axis-rotation large-arc-flag sweep-flag x y)+ Absolute Elliptical Arc + + // return early if data is not defined + if(!data) { + return []; + } + + // command string + var cs = data; + + // command chars + var cc = ['m', 'M', 'l', 'L', 'v', 'V', 'h', 'H', 'z', 'Z', 'c', 'C', 'q', 'Q', 't', 'T', 's', 'S', 'a', 'A']; + // convert white spaces to commas + cs = cs.replace(new RegExp(' ', 'g'), ','); + // create pipes so that we can split the data + for(var n = 0; n < cc.length; n++) { + cs = cs.replace(new RegExp(cc[n], 'g'), '|' + cc[n]); + } + // create array + var arr = cs.split('|'); + var ca = []; + // init context point + var cpx = 0; + var cpy = 0; + for( n = 1; n < arr.length; n++) { + var str = arr[n]; + var c = str.charAt(0); + str = str.slice(1); + // remove ,- for consistency + str = str.replace(new RegExp(',-', 'g'), '-'); + // add commas so that it's easy to split + str = str.replace(new RegExp('-', 'g'), ',-'); + str = str.replace(new RegExp('e,-', 'g'), 'e-'); + var p = str.split(','); + if(p.length > 0 && p[0] === '') { + p.shift(); + } + // convert strings to floats + for(var i = 0; i < p.length; i++) { + p[i] = parseFloat(p[i]); + } + while(p.length > 0) { + if(isNaN(p[0]))// case for a trailing comma before next command + break; + + var cmd = null; + var points = []; + var startX = cpx, startY = cpy; + // Move var from within the switch to up here (jshint) + var prevCmd, ctlPtx, ctlPty; // Ss, Tt + var rx, ry, psi, fa, fs, x1, y1; // Aa + + + // convert l, H, h, V, and v to L + switch (c) { + + // Note: Keep the lineTo's above the moveTo's in this switch + case 'l': + cpx += p.shift(); + cpy += p.shift(); + cmd = 'L'; + points.push(cpx, cpy); + break; + case 'L': + cpx = p.shift(); + cpy = p.shift(); + points.push(cpx, cpy); + break; + + // Note: lineTo handlers need to be above this point + case 'm': + var dx = p.shift(); + var dy = p.shift(); + cpx += dx; + cpy += dy; + cmd = 'M'; + // After closing the path move the current position + // to the the first point of the path (if any). + if(ca.length>2 && ca[ca.length-1].command==='z'){ + for(var idx=ca.length-2;idx>=0;idx--){ + if(ca[idx].command==='M'){ + cpx=ca[idx].points[0]+dx; + cpy=ca[idx].points[1]+dy; + break; + } + } + } + points.push(cpx, cpy); + c = 'l'; + // subsequent points are treated as relative lineTo + break; + case 'M': + cpx = p.shift(); + cpy = p.shift(); + cmd = 'M'; + points.push(cpx, cpy); + c = 'L'; + // subsequent points are treated as absolute lineTo + break; + + case 'h': + cpx += p.shift(); + cmd = 'L'; + points.push(cpx, cpy); + break; + case 'H': + cpx = p.shift(); + cmd = 'L'; + points.push(cpx, cpy); + break; + case 'v': + cpy += p.shift(); + cmd = 'L'; + points.push(cpx, cpy); + break; + case 'V': + cpy = p.shift(); + cmd = 'L'; + points.push(cpx, cpy); + break; + case 'C': + points.push(p.shift(), p.shift(), p.shift(), p.shift()); + cpx = p.shift(); + cpy = p.shift(); + points.push(cpx, cpy); + break; + case 'c': + points.push(cpx + p.shift(), cpy + p.shift(), cpx + p.shift(), cpy + p.shift()); + cpx += p.shift(); + cpy += p.shift(); + cmd = 'C'; + points.push(cpx, cpy); + break; + case 'S': + ctlPtx = cpx, ctlPty = cpy; + prevCmd = ca[ca.length - 1]; + if(prevCmd.command === 'C') { + ctlPtx = cpx + (cpx - prevCmd.points[2]); + ctlPty = cpy + (cpy - prevCmd.points[3]); + } + points.push(ctlPtx, ctlPty, p.shift(), p.shift()); + cpx = p.shift(); + cpy = p.shift(); + cmd = 'C'; + points.push(cpx, cpy); + break; + case 's': + ctlPtx = cpx, ctlPty = cpy; + prevCmd = ca[ca.length - 1]; + if(prevCmd.command === 'C') { + ctlPtx = cpx + (cpx - prevCmd.points[2]); + ctlPty = cpy + (cpy - prevCmd.points[3]); + } + points.push(ctlPtx, ctlPty, cpx + p.shift(), cpy + p.shift()); + cpx += p.shift(); + cpy += p.shift(); + cmd = 'C'; + points.push(cpx, cpy); + break; + case 'Q': + points.push(p.shift(), p.shift()); + cpx = p.shift(); + cpy = p.shift(); + points.push(cpx, cpy); + break; + case 'q': + points.push(cpx + p.shift(), cpy + p.shift()); + cpx += p.shift(); + cpy += p.shift(); + cmd = 'Q'; + points.push(cpx, cpy); + break; + case 'T': + ctlPtx = cpx, ctlPty = cpy; + prevCmd = ca[ca.length - 1]; + if(prevCmd.command === 'Q') { + ctlPtx = cpx + (cpx - prevCmd.points[0]); + ctlPty = cpy + (cpy - prevCmd.points[1]); + } + cpx = p.shift(); + cpy = p.shift(); + cmd = 'Q'; + points.push(ctlPtx, ctlPty, cpx, cpy); + break; + case 't': + ctlPtx = cpx, ctlPty = cpy; + prevCmd = ca[ca.length - 1]; + if(prevCmd.command === 'Q') { + ctlPtx = cpx + (cpx - prevCmd.points[0]); + ctlPty = cpy + (cpy - prevCmd.points[1]); + } + cpx += p.shift(); + cpy += p.shift(); + cmd = 'Q'; + points.push(ctlPtx, ctlPty, cpx, cpy); + break; + case 'A': + rx = p.shift(), ry = p.shift(), psi = p.shift(), fa = p.shift(), fs = p.shift(); + x1 = cpx, y1 = cpy; cpx = p.shift(), cpy = p.shift(); + cmd = 'A'; + points = this.convertEndpointToCenterParameterization(x1, y1, cpx, cpy, fa, fs, rx, ry, psi); + break; + case 'a': + rx = p.shift(), ry = p.shift(), psi = p.shift(), fa = p.shift(), fs = p.shift(); + x1 = cpx, y1 = cpy; cpx += p.shift(), cpy += p.shift(); + cmd = 'A'; + points = this.convertEndpointToCenterParameterization(x1, y1, cpx, cpy, fa, fs, rx, ry, psi); + break; + } + + ca.push({ + command: cmd || c, + points: points, + start: { + x: startX, + y: startY + }, + pathLength: this.calcLength(startX, startY, cmd || c, points) + }); + } + + if(c === 'z' || c === 'Z') { + ca.push({ + command: 'z', + points: [], + start: undefined, + pathLength: 0 + }); + } + } + + return ca; + }; + Kinetic.Path.calcLength = function(x, y, cmd, points) { + var len, p1, p2; + var path = Kinetic.Path; + + switch (cmd) { + case 'L': + return path.getLineLength(x, y, points[0], points[1]); + case 'C': + // Approximates by breaking curve into 100 line segments + len = 0.0; + p1 = path.getPointOnCubicBezier(0, x, y, points[0], points[1], points[2], points[3], points[4], points[5]); + for( t = 0.01; t <= 1; t += 0.01) { + p2 = path.getPointOnCubicBezier(t, x, y, points[0], points[1], points[2], points[3], points[4], points[5]); + len += path.getLineLength(p1.x, p1.y, p2.x, p2.y); + p1 = p2; + } + return len; + case 'Q': + // Approximates by breaking curve into 100 line segments + len = 0.0; + p1 = path.getPointOnQuadraticBezier(0, x, y, points[0], points[1], points[2], points[3]); + for( t = 0.01; t <= 1; t += 0.01) { + p2 = path.getPointOnQuadraticBezier(t, x, y, points[0], points[1], points[2], points[3]); + len += path.getLineLength(p1.x, p1.y, p2.x, p2.y); + p1 = p2; + } + return len; + case 'A': + // Approximates by breaking curve into line segments + len = 0.0; + var start = points[4]; + // 4 = theta + var dTheta = points[5]; + // 5 = dTheta + var end = points[4] + dTheta; + var inc = Math.PI / 180.0; + // 1 degree resolution + if(Math.abs(start - end) < inc) { + inc = Math.abs(start - end); + } + // Note: for purpose of calculating arc length, not going to worry about rotating X-axis by angle psi + p1 = path.getPointOnEllipticalArc(points[0], points[1], points[2], points[3], start, 0); + if(dTheta < 0) {// clockwise + for( t = start - inc; t > end; t -= inc) { + p2 = path.getPointOnEllipticalArc(points[0], points[1], points[2], points[3], t, 0); + len += path.getLineLength(p1.x, p1.y, p2.x, p2.y); + p1 = p2; + } + } + else {// counter-clockwise + for( t = start + inc; t < end; t += inc) { + p2 = path.getPointOnEllipticalArc(points[0], points[1], points[2], points[3], t, 0); + len += path.getLineLength(p1.x, p1.y, p2.x, p2.y); + p1 = p2; + } + } + p2 = path.getPointOnEllipticalArc(points[0], points[1], points[2], points[3], end, 0); + len += path.getLineLength(p1.x, p1.y, p2.x, p2.y); + + return len; + } + + return 0; + }; + Kinetic.Path.convertEndpointToCenterParameterization = function(x1, y1, x2, y2, fa, fs, rx, ry, psiDeg) { + // Derived from: http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes + var psi = psiDeg * (Math.PI / 180.0); + var xp = Math.cos(psi) * (x1 - x2) / 2.0 + Math.sin(psi) * (y1 - y2) / 2.0; + var yp = -1 * Math.sin(psi) * (x1 - x2) / 2.0 + Math.cos(psi) * (y1 - y2) / 2.0; + + var lambda = (xp * xp) / (rx * rx) + (yp * yp) / (ry * ry); + + if(lambda > 1) { + rx *= Math.sqrt(lambda); + ry *= Math.sqrt(lambda); + } + + var f = Math.sqrt((((rx * rx) * (ry * ry)) - ((rx * rx) * (yp * yp)) - ((ry * ry) * (xp * xp))) / ((rx * rx) * (yp * yp) + (ry * ry) * (xp * xp))); + + if(fa == fs) { + f *= -1; + } + if(isNaN(f)) { + f = 0; + } + + var cxp = f * rx * yp / ry; + var cyp = f * -ry * xp / rx; + + var cx = (x1 + x2) / 2.0 + Math.cos(psi) * cxp - Math.sin(psi) * cyp; + var cy = (y1 + y2) / 2.0 + Math.sin(psi) * cxp + Math.cos(psi) * cyp; + + var vMag = function(v) { + return Math.sqrt(v[0] * v[0] + v[1] * v[1]); + }; + var vRatio = function(u, v) { + return (u[0] * v[0] + u[1] * v[1]) / (vMag(u) * vMag(v)); + }; + var vAngle = function(u, v) { + return (u[0] * v[1] < u[1] * v[0] ? -1 : 1) * Math.acos(vRatio(u, v)); + }; + var theta = vAngle([1, 0], [(xp - cxp) / rx, (yp - cyp) / ry]); + var u = [(xp - cxp) / rx, (yp - cyp) / ry]; + var v = [(-1 * xp - cxp) / rx, (-1 * yp - cyp) / ry]; + var dTheta = vAngle(u, v); + + if(vRatio(u, v) <= -1) { + dTheta = Math.PI; + } + if(vRatio(u, v) >= 1) { + dTheta = 0; + } + if(fs === 0 && dTheta > 0) { + dTheta = dTheta - 2 * Math.PI; + } + if(fs == 1 && dTheta < 0) { + dTheta = dTheta + 2 * Math.PI; + } + return [cx, cy, rx, ry, theta, dTheta, psi, fs]; + }; + // add getters setters + Kinetic.Factory.addGetterSetter(Kinetic.Path, 'data'); + + /** + * set SVG path data string. This method + * also automatically parses the data string + * into a data array. Currently supported SVG data: + * M, m, L, l, H, h, V, v, Q, q, T, t, C, c, S, s, A, a, Z, z + * @name setData + * @method + * @memberof Kinetic.Path.prototype + * @param {String} SVG path command string + */ + + /** + * get SVG path data string + * @name getData + * @method + * @memberof Kinetic.Path.prototype + */ + + Kinetic.Collection.mapMethods(Kinetic.Path); +})(); +;(function() { + var EMPTY_STRING = '', + CALIBRI = 'Calibri', + NORMAL = 'normal'; + + /** + * Path constructor. + * @author Jason Follas + * @constructor + * @memberof Kinetic + * @augments Kinetic.Shape + * @param {Object} config + * @param {String} [config.fontFamily] default is Calibri + * @param {Number} [config.fontSize] default is 12 + * @param {String} [config.fontStyle] can be normal, bold, or italic. Default is normal + * @param {String} config.text + * @param {String} config.data SVG data string + * @param {String} [config.fill] fill color + * @param {Integer} [config.fillRed] set fill red component + * @param {Integer} [config.fillGreen] set fill green component + * @param {Integer} [config.fillBlue] set fill blue component + * @param {Integer} [config.fillAlpha] set fill alpha component + * @param {Image} [config.fillPatternImage] fill pattern image + * @param {Number} [config.fillPatternX] + * @param {Number} [config.fillPatternY] + * @param {Object} [config.fillPatternOffset] object with x and y component + * @param {Number} [config.fillPatternOffsetX] + * @param {Number} [config.fillPatternOffsetY] + * @param {Object} [config.fillPatternScale] object with x and y component + * @param {Number} [config.fillPatternScaleX] + * @param {Number} [config.fillPatternScaleY] + * @param {Number} [config.fillPatternRotation] + * @param {String} [config.fillPatternRepeat] can be "repeat", "repeat-x", "repeat-y", or "no-repeat". The default is "no-repeat" + * @param {Object} [config.fillLinearGradientStartPoint] object with x and y component + * @param {Number} [config.fillLinearGradientStartPointX] + * @param {Number} [config.fillLinearGradientStartPointY] + * @param {Object} [config.fillLinearGradientEndPoint] object with x and y component + * @param {Number} [config.fillLinearGradientEndPointX] + * @param {Number} [config.fillLinearGradientEndPointY] + * @param {Array} [config.fillLinearGradientColorStops] array of color stops + * @param {Object} [config.fillRadialGradientStartPoint] object with x and y component + * @param {Number} [config.fillRadialGradientStartPointX] + * @param {Number} [config.fillRadialGradientStartPointY] + * @param {Object} [config.fillRadialGradientEndPoint] object with x and y component + * @param {Number} [config.fillRadialGradientEndPointX] + * @param {Number} [config.fillRadialGradientEndPointY] + * @param {Number} [config.fillRadialGradientStartRadius] + * @param {Number} [config.fillRadialGradientEndRadius] + * @param {Array} [config.fillRadialGradientColorStops] array of color stops + * @param {Boolean} [config.fillEnabled] flag which enables or disables the fill. The default value is true + * @param {String} [config.fillPriority] can be color, linear-gradient, radial-graident, or pattern. The default value is color. The fillPriority property makes it really easy to toggle between different fill types. For example, if you want to toggle between a fill color style and a fill pattern style, simply set the fill property and the fillPattern properties, and then use setFillPriority('color') to render the shape with a color fill, or use setFillPriority('pattern') to render the shape with the pattern fill configuration + * @param {String} [config.stroke] stroke color + * @param {Integer} [config.strokeRed] set stroke red component + * @param {Integer} [config.strokeGreen] set stroke green component + * @param {Integer} [config.strokeBlue] set stroke blue component + * @param {Integer} [config.strokeAlpha] set stroke alpha component + * @param {Number} [config.strokeWidth] stroke width + * @param {Boolean} [config.strokeScaleEnabled] flag which enables or disables stroke scale. The default is true + * @param {Boolean} [config.strokeEnabled] flag which enables or disables the stroke. The default value is true + * @param {String} [config.lineJoin] can be miter, round, or bevel. The default + * is miter + * @param {String} [config.lineCap] can be butt, round, or sqare. The default + * is butt + * @param {String} [config.shadowColor] + * @param {Integer} [config.shadowColorRed] set shadow color red component + * @param {Integer} [config.shadowColorGreen] set shadow color green component + * @param {Integer} [config.shadowColorBlue] set shadow color blue component + * @param {Integer} [config.shadowColorAlpha] set shadow color alpha component + * @param {Number} [config.shadowBlur] + * @param {Object} [config.shadowOffset] object with x and y component + * @param {Number} [config.shadowOffsetX] + * @param {Number} [config.shadowOffsetY] + * @param {Number} [config.shadowOpacity] shadow opacity. Can be any real number + * between 0 and 1 + * @param {Boolean} [config.shadowEnabled] flag which enables or disables the shadow. The default value is true + * @param {Array} [config.dash] + * @param {Boolean} [config.dashEnabled] flag which enables or disables the dashArray. The default value is true + * @param {Number} [config.x] + * @param {Number} [config.y] + * @param {Number} [config.width] + * @param {Number} [config.height] + * @param {Boolean} [config.visible] + * @param {Boolean} [config.listening] whether or not the node is listening for events + * @param {String} [config.id] unique id + * @param {String} [config.name] non-unique name + * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 + * @param {Object} [config.scale] set scale + * @param {Number} [config.scaleX] set scale x + * @param {Number} [config.scaleY] set scale y + * @param {Number} [config.rotation] rotation in degrees + * @param {Object} [config.offset] offset from center point and rotation point + * @param {Number} [config.offsetX] set offset x + * @param {Number} [config.offsetY] set offset y + * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop + * the entire stage by dragging any portion of the stage + * @param {Function} [config.dragBoundFunc] + * @example + * var textpath = new Kinetic.TextPath({
+ * x: 100,
+ * y: 50,
+ * fill: '#333',
+ * fontSize: '24',
+ * fontFamily: 'Arial',
+ * text: 'All the world\'s a stage, and all the men and women merely players.',
+ * data: 'M10,10 C0,0 10,150 100,100 S300,150 400,50'
+ * }); + */ + Kinetic.TextPath = function(config) { + this.___init(config); + }; + + function _fillFunc(context) { + context.fillText(this.partialText, 0, 0); + } + function _strokeFunc(context) { + context.strokeText(this.partialText, 0, 0); + } + + Kinetic.TextPath.prototype = { + ___init: function(config) { + var that = this; + this.dummyCanvas = document.createElement('canvas'); + this.dataArray = []; + + // call super constructor + Kinetic.Shape.call(this, config); + + // overrides + // TODO: shouldn't this be on the prototype? + this._fillFunc = _fillFunc; + this._strokeFunc = _strokeFunc; + this._fillFuncHit = _fillFunc; + this._strokeFuncHit = _strokeFunc; + + this.className = 'TextPath'; + + this.dataArray = Kinetic.Path.parsePathData(this.attrs.data); + this.on('dataChange.kinetic', function() { + that.dataArray = Kinetic.Path.parsePathData(this.attrs.data); + }); + + // update text data for certain attr changes + this.on('textChange.kinetic textStroke.kinetic textStrokeWidth.kinetic', that._setTextData); + that._setTextData(); + this.sceneFunc(this._sceneFunc); + }, + _sceneFunc: function(context) { + context.setAttr('font', this._getContextFont()); + context.setAttr('textBaseline', 'middle'); + context.setAttr('textAlign', 'left'); + context.save(); + + var glyphInfo = this.glyphInfo; + for(var i = 0; i < glyphInfo.length; i++) { + context.save(); + + var p0 = glyphInfo[i].p0; + + context.translate(p0.x, p0.y); + context.rotate(glyphInfo[i].rotation); + this.partialText = glyphInfo[i].text; + + context.fillStrokeShape(this); + context.restore(); + + //// To assist with debugging visually, uncomment following + // context.beginPath(); + // if (i % 2) + // context.strokeStyle = 'cyan'; + // else + // context.strokeStyle = 'green'; + // var p1 = glyphInfo[i].p1; + // context.moveTo(p0.x, p0.y); + // context.lineTo(p1.x, p1.y); + // context.stroke(); + } + context.restore(); + }, + /** + * get text width in pixels + * @method + * @memberof Kinetic.TextPath.prototype + */ + getTextWidth: function() { + return this.textWidth; + }, + /** + * get text height in pixels + * @method + * @memberof Kinetic.TextPath.prototype + */ + getTextHeight: function() { + return this.textHeight; + }, + /** + * set text + * @method + * @memberof Kinetic.TextPath.prototype + * @param {String} text + */ + setText: function(text) { + Kinetic.Text.prototype.setText.call(this, text); + }, + _getTextSize: function(text) { + var dummyCanvas = this.dummyCanvas; + var _context = dummyCanvas.getContext('2d'); + + _context.save(); + + _context.font = this._getContextFont(); + var metrics = _context.measureText(text); + + _context.restore(); + + return { + width: metrics.width, + height: parseInt(this.attrs.fontSize, 10) + }; + }, + _setTextData: function() { + + var that = this; + var size = this._getTextSize(this.attrs.text); + this.textWidth = size.width; + this.textHeight = size.height; + + this.glyphInfo = []; + + var charArr = this.attrs.text.split(''); + + var p0, p1, pathCmd; + + var pIndex = -1; + var currentT = 0; + + var getNextPathSegment = function() { + currentT = 0; + var pathData = that.dataArray; + + for(var i = pIndex + 1; i < pathData.length; i++) { + if(pathData[i].pathLength > 0) { + pIndex = i; + + return pathData[i]; + } + else if(pathData[i].command == 'M') { + p0 = { + x: pathData[i].points[0], + y: pathData[i].points[1] + }; + } + } + + return {}; + }; + var findSegmentToFitCharacter = function(c, before) { + + var glyphWidth = that._getTextSize(c).width; + + var currLen = 0; + var attempts = 0; + + p1 = undefined; + while(Math.abs(glyphWidth - currLen) / glyphWidth > 0.01 && attempts < 25) { + attempts++; + var cumulativePathLength = currLen; + while(pathCmd === undefined) { + pathCmd = getNextPathSegment(); + + if(pathCmd && cumulativePathLength + pathCmd.pathLength < glyphWidth) { + cumulativePathLength += pathCmd.pathLength; + pathCmd = undefined; + } + } + + if(pathCmd === {} || p0 === undefined) + return undefined; + + var needNewSegment = false; + + switch (pathCmd.command) { + case 'L': + if(Kinetic.Path.getLineLength(p0.x, p0.y, pathCmd.points[0], pathCmd.points[1]) > glyphWidth) { + p1 = Kinetic.Path.getPointOnLine(glyphWidth, p0.x, p0.y, pathCmd.points[0], pathCmd.points[1], p0.x, p0.y); + } + else + pathCmd = undefined; + break; + case 'A': + + var start = pathCmd.points[4]; + // 4 = theta + var dTheta = pathCmd.points[5]; + // 5 = dTheta + var end = pathCmd.points[4] + dTheta; + + if(currentT === 0) + currentT = start + 0.00000001; + // Just in case start is 0 + else if(glyphWidth > currLen) + currentT += (Math.PI / 180.0) * dTheta / Math.abs(dTheta); + else + currentT -= Math.PI / 360.0 * dTheta / Math.abs(dTheta); + + // Credit for bug fix: @therth https://github.com/ericdrowell/KineticJS/issues/249 + // Old code failed to render text along arc of this path: "M 50 50 a 150 50 0 0 1 250 50 l 50 0" + if(dTheta < 0 && currentT < end || dTheta >= 0 && currentT > end) { + currentT = end; + needNewSegment = true; + } + p1 = Kinetic.Path.getPointOnEllipticalArc(pathCmd.points[0], pathCmd.points[1], pathCmd.points[2], pathCmd.points[3], currentT, pathCmd.points[6]); + break; + case 'C': + if(currentT === 0) { + if(glyphWidth > pathCmd.pathLength) + currentT = 0.00000001; + else + currentT = glyphWidth / pathCmd.pathLength; + } + else if(glyphWidth > currLen) + currentT += (glyphWidth - currLen) / pathCmd.pathLength; + else + currentT -= (currLen - glyphWidth) / pathCmd.pathLength; + + if(currentT > 1.0) { + currentT = 1.0; + needNewSegment = true; + } + p1 = Kinetic.Path.getPointOnCubicBezier(currentT, pathCmd.start.x, pathCmd.start.y, pathCmd.points[0], pathCmd.points[1], pathCmd.points[2], pathCmd.points[3], pathCmd.points[4], pathCmd.points[5]); + break; + case 'Q': + if(currentT === 0) + currentT = glyphWidth / pathCmd.pathLength; + else if(glyphWidth > currLen) + currentT += (glyphWidth - currLen) / pathCmd.pathLength; + else + currentT -= (currLen - glyphWidth) / pathCmd.pathLength; + + if(currentT > 1.0) { + currentT = 1.0; + needNewSegment = true; + } + p1 = Kinetic.Path.getPointOnQuadraticBezier(currentT, pathCmd.start.x, pathCmd.start.y, pathCmd.points[0], pathCmd.points[1], pathCmd.points[2], pathCmd.points[3]); + break; + + } + + if(p1 !== undefined) { + currLen = Kinetic.Path.getLineLength(p0.x, p0.y, p1.x, p1.y); + } + + if(needNewSegment) { + needNewSegment = false; + pathCmd = undefined; + } + } + }; + for(var i = 0; i < charArr.length; i++) { + + // Find p1 such that line segment between p0 and p1 is approx. width of glyph + findSegmentToFitCharacter(charArr[i]); + + if(p0 === undefined || p1 === undefined) + break; + + var width = Kinetic.Path.getLineLength(p0.x, p0.y, p1.x, p1.y); + + // Note: Since glyphs are rendered one at a time, any kerning pair data built into the font will not be used. + // Can foresee having a rough pair table built in that the developer can override as needed. + + var kern = 0; + // placeholder for future implementation + + var midpoint = Kinetic.Path.getPointOnLine(kern + width / 2.0, p0.x, p0.y, p1.x, p1.y); + + var rotation = Math.atan2((p1.y - p0.y), (p1.x - p0.x)); + this.glyphInfo.push({ + transposeX: midpoint.x, + transposeY: midpoint.y, + text: charArr[i], + rotation: rotation, + p0: p0, + p1: p1 + }); + p0 = p1; + } + } + }; + + // map TextPath methods to Text + Kinetic.TextPath.prototype._getContextFont = Kinetic.Text.prototype._getContextFont; + + Kinetic.Util.extend(Kinetic.TextPath, Kinetic.Shape); + + // add setters and getters + Kinetic.Factory.addGetterSetter(Kinetic.TextPath, 'fontFamily', 'Arial'); + + /** + * set font family + * @name setFontFamily + * @method + * @memberof Kinetic.TextPath.prototype + * @param {String} fontFamily + */ + + /** + * get font family + * @name getFontFamily + * @method + * @memberof Kinetic.TextPath.prototype + */ + + Kinetic.Factory.addGetterSetter(Kinetic.TextPath, 'fontSize', 12); + + /** + * set font size + * @name setFontSize + * @method + * @memberof Kinetic.TextPath.prototype + * @param {int} fontSize + */ + + /** + * get font size + * @name getFontSize + * @method + * @memberof Kinetic.TextPath.prototype + */ + + Kinetic.Factory.addGetterSetter(Kinetic.TextPath, 'fontStyle', NORMAL); + + /** + * set font style. Can be 'normal', 'italic', or 'bold'. 'normal' is the default. + * @name setFontStyle + * @method + * @memberof Kinetic.TextPath.prototype + * @param {String} fontStyle + */ + + /** + * get font style + * @name getFontStyle + * @method + * @memberof Kinetic.TextPath.prototype + */ + + Kinetic.Factory.addGetter(Kinetic.TextPath, 'text', EMPTY_STRING); + + /** + * get text + * @name getText + * @method + * @memberof Kinetic.TextPath.prototype + */ + + Kinetic.Collection.mapMethods(Kinetic.TextPath); +})(); +;(function() { + /** + * RegularPolygon constructor.  Examples include triangles, squares, pentagons, hexagons, etc. + * @constructor + * @memberof Kinetic + * @augments Kinetic.Shape + * @param {Object} config + * @param {Number} config.sides + * @param {Number} config.radius + * @param {String} [config.fill] fill color + * @param {Integer} [config.fillRed] set fill red component + * @param {Integer} [config.fillGreen] set fill green component + * @param {Integer} [config.fillBlue] set fill blue component + * @param {Integer} [config.fillAlpha] set fill alpha component + * @param {Image} [config.fillPatternImage] fill pattern image + * @param {Number} [config.fillPatternX] + * @param {Number} [config.fillPatternY] + * @param {Object} [config.fillPatternOffset] object with x and y component + * @param {Number} [config.fillPatternOffsetX] + * @param {Number} [config.fillPatternOffsetY] + * @param {Object} [config.fillPatternScale] object with x and y component + * @param {Number} [config.fillPatternScaleX] + * @param {Number} [config.fillPatternScaleY] + * @param {Number} [config.fillPatternRotation] + * @param {String} [config.fillPatternRepeat] can be "repeat", "repeat-x", "repeat-y", or "no-repeat". The default is "no-repeat" + * @param {Object} [config.fillLinearGradientStartPoint] object with x and y component + * @param {Number} [config.fillLinearGradientStartPointX] + * @param {Number} [config.fillLinearGradientStartPointY] + * @param {Object} [config.fillLinearGradientEndPoint] object with x and y component + * @param {Number} [config.fillLinearGradientEndPointX] + * @param {Number} [config.fillLinearGradientEndPointY] + * @param {Array} [config.fillLinearGradientColorStops] array of color stops + * @param {Object} [config.fillRadialGradientStartPoint] object with x and y component + * @param {Number} [config.fillRadialGradientStartPointX] + * @param {Number} [config.fillRadialGradientStartPointY] + * @param {Object} [config.fillRadialGradientEndPoint] object with x and y component + * @param {Number} [config.fillRadialGradientEndPointX] + * @param {Number} [config.fillRadialGradientEndPointY] + * @param {Number} [config.fillRadialGradientStartRadius] + * @param {Number} [config.fillRadialGradientEndRadius] + * @param {Array} [config.fillRadialGradientColorStops] array of color stops + * @param {Boolean} [config.fillEnabled] flag which enables or disables the fill. The default value is true + * @param {String} [config.fillPriority] can be color, linear-gradient, radial-graident, or pattern. The default value is color. The fillPriority property makes it really easy to toggle between different fill types. For example, if you want to toggle between a fill color style and a fill pattern style, simply set the fill property and the fillPattern properties, and then use setFillPriority('color') to render the shape with a color fill, or use setFillPriority('pattern') to render the shape with the pattern fill configuration + * @param {String} [config.stroke] stroke color + * @param {Integer} [config.strokeRed] set stroke red component + * @param {Integer} [config.strokeGreen] set stroke green component + * @param {Integer} [config.strokeBlue] set stroke blue component + * @param {Integer} [config.strokeAlpha] set stroke alpha component + * @param {Number} [config.strokeWidth] stroke width + * @param {Boolean} [config.strokeScaleEnabled] flag which enables or disables stroke scale. The default is true + * @param {Boolean} [config.strokeEnabled] flag which enables or disables the stroke. The default value is true + * @param {String} [config.lineJoin] can be miter, round, or bevel. The default + * is miter + * @param {String} [config.lineCap] can be butt, round, or sqare. The default + * is butt + * @param {String} [config.shadowColor] + * @param {Integer} [config.shadowColorRed] set shadow color red component + * @param {Integer} [config.shadowColorGreen] set shadow color green component + * @param {Integer} [config.shadowColorBlue] set shadow color blue component + * @param {Integer} [config.shadowColorAlpha] set shadow color alpha component + * @param {Number} [config.shadowBlur] + * @param {Object} [config.shadowOffset] object with x and y component + * @param {Number} [config.shadowOffsetX] + * @param {Number} [config.shadowOffsetY] + * @param {Number} [config.shadowOpacity] shadow opacity. Can be any real number + * between 0 and 1 + * @param {Boolean} [config.shadowEnabled] flag which enables or disables the shadow. The default value is true + * @param {Array} [config.dash] + * @param {Boolean} [config.dashEnabled] flag which enables or disables the dashArray. The default value is true + * @param {Number} [config.x] + * @param {Number} [config.y] + * @param {Number} [config.width] + * @param {Number} [config.height] + * @param {Boolean} [config.visible] + * @param {Boolean} [config.listening] whether or not the node is listening for events + * @param {String} [config.id] unique id + * @param {String} [config.name] non-unique name + * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 + * @param {Object} [config.scale] set scale + * @param {Number} [config.scaleX] set scale x + * @param {Number} [config.scaleY] set scale y + * @param {Number} [config.rotation] rotation in degrees + * @param {Object} [config.offset] offset from center point and rotation point + * @param {Number} [config.offsetX] set offset x + * @param {Number} [config.offsetY] set offset y + * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop + * the entire stage by dragging any portion of the stage + * @param {Function} [config.dragBoundFunc] + * @example + * var hexagon = new Kinetic.RegularPolygon({
+ * x: 100,
+ * y: 200,
+ * sides: 6,
+ * radius: 70,
+ * fill: 'red',
+ * stroke: 'black',
+ * strokeWidth: 4
+ * }); + */ + Kinetic.RegularPolygon = function(config) { + this.___init(config); + }; + + Kinetic.RegularPolygon.prototype = { + ___init: function(config) { + // call super constructor + Kinetic.Shape.call(this, config); + this.className = 'RegularPolygon'; + this.sceneFunc(this._sceneFunc); + }, + _sceneFunc: function(context) { + var sides = this.attrs.sides, + radius = this.attrs.radius, + n, x, y; + + context.beginPath(); + context.moveTo(0, 0 - radius); + + for(n = 1; n < sides; n++) { + x = radius * Math.sin(n * 2 * Math.PI / sides); + y = -1 * radius * Math.cos(n * 2 * Math.PI / sides); + context.lineTo(x, y); + } + context.closePath(); + context.fillStrokeShape(this); + } + }; + Kinetic.Util.extend(Kinetic.RegularPolygon, Kinetic.Shape); + + // add getters setters + Kinetic.Factory.addGetterSetter(Kinetic.RegularPolygon, 'radius', 0); + + /** + * set radius + * @name setRadius + * @method + * @memberof Kinetic.RegularPolygon.prototype + * @param {Number} radius + */ + + /** + * get radius + * @name getRadius + * @method + * @memberof Kinetic.RegularPolygon.prototype + */ + + Kinetic.Factory.addGetterSetter(Kinetic.RegularPolygon, 'sides', 0); + + /** + * set number of sides + * @name setSides + * @method + * @memberof Kinetic.RegularPolygon.prototype + * @param {int} sides + */ + + /** + * get number of sides + * @name getSides + * @method + * @memberof Kinetic.RegularPolygon.prototype + */ + + Kinetic.Collection.mapMethods(Kinetic.RegularPolygon); +})(); +;(function() { + /** + * Star constructor + * @constructor + * @memberof Kinetic + * @augments Kinetic.Shape + * @param {Object} config + * @param {Integer} config.numPoints + * @param {Number} config.innerRadius + * @param {Number} config.outerRadius + * @param {String} [config.fill] fill color + * @param {Integer} [config.fillRed] set fill red component + * @param {Integer} [config.fillGreen] set fill green component + * @param {Integer} [config.fillBlue] set fill blue component + * @param {Integer} [config.fillAlpha] set fill alpha component + * @param {Image} [config.fillPatternImage] fill pattern image + * @param {Number} [config.fillPatternX] + * @param {Number} [config.fillPatternY] + * @param {Object} [config.fillPatternOffset] object with x and y component + * @param {Number} [config.fillPatternOffsetX] + * @param {Number} [config.fillPatternOffsetY] + * @param {Object} [config.fillPatternScale] object with x and y component + * @param {Number} [config.fillPatternScaleX] + * @param {Number} [config.fillPatternScaleY] + * @param {Number} [config.fillPatternRotation] + * @param {String} [config.fillPatternRepeat] can be "repeat", "repeat-x", "repeat-y", or "no-repeat". The default is "no-repeat" + * @param {Object} [config.fillLinearGradientStartPoint] object with x and y component + * @param {Number} [config.fillLinearGradientStartPointX] + * @param {Number} [config.fillLinearGradientStartPointY] + * @param {Object} [config.fillLinearGradientEndPoint] object with x and y component + * @param {Number} [config.fillLinearGradientEndPointX] + * @param {Number} [config.fillLinearGradientEndPointY] + * @param {Array} [config.fillLinearGradientColorStops] array of color stops + * @param {Object} [config.fillRadialGradientStartPoint] object with x and y component + * @param {Number} [config.fillRadialGradientStartPointX] + * @param {Number} [config.fillRadialGradientStartPointY] + * @param {Object} [config.fillRadialGradientEndPoint] object with x and y component + * @param {Number} [config.fillRadialGradientEndPointX] + * @param {Number} [config.fillRadialGradientEndPointY] + * @param {Number} [config.fillRadialGradientStartRadius] + * @param {Number} [config.fillRadialGradientEndRadius] + * @param {Array} [config.fillRadialGradientColorStops] array of color stops + * @param {Boolean} [config.fillEnabled] flag which enables or disables the fill. The default value is true + * @param {String} [config.fillPriority] can be color, linear-gradient, radial-graident, or pattern. The default value is color. The fillPriority property makes it really easy to toggle between different fill types. For example, if you want to toggle between a fill color style and a fill pattern style, simply set the fill property and the fillPattern properties, and then use setFillPriority('color') to render the shape with a color fill, or use setFillPriority('pattern') to render the shape with the pattern fill configuration + * @param {String} [config.stroke] stroke color + * @param {Integer} [config.strokeRed] set stroke red component + * @param {Integer} [config.strokeGreen] set stroke green component + * @param {Integer} [config.strokeBlue] set stroke blue component + * @param {Integer} [config.strokeAlpha] set stroke alpha component + * @param {Number} [config.strokeWidth] stroke width + * @param {Boolean} [config.strokeScaleEnabled] flag which enables or disables stroke scale. The default is true + * @param {Boolean} [config.strokeEnabled] flag which enables or disables the stroke. The default value is true + * @param {String} [config.lineJoin] can be miter, round, or bevel. The default + * is miter + * @param {String} [config.lineCap] can be butt, round, or sqare. The default + * is butt + * @param {String} [config.shadowColor] + * @param {Integer} [config.shadowColorRed] set shadow color red component + * @param {Integer} [config.shadowColorGreen] set shadow color green component + * @param {Integer} [config.shadowColorBlue] set shadow color blue component + * @param {Integer} [config.shadowColorAlpha] set shadow color alpha component + * @param {Number} [config.shadowBlur] + * @param {Object} [config.shadowOffset] object with x and y component + * @param {Number} [config.shadowOffsetX] + * @param {Number} [config.shadowOffsetY] + * @param {Number} [config.shadowOpacity] shadow opacity. Can be any real number + * between 0 and 1 + * @param {Boolean} [config.shadowEnabled] flag which enables or disables the shadow. The default value is true + * @param {Array} [config.dash] + * @param {Boolean} [config.dashEnabled] flag which enables or disables the dashArray. The default value is true + * @param {Number} [config.x] + * @param {Number} [config.y] + * @param {Number} [config.width] + * @param {Number} [config.height] + * @param {Boolean} [config.visible] + * @param {Boolean} [config.listening] whether or not the node is listening for events + * @param {String} [config.id] unique id + * @param {String} [config.name] non-unique name + * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 + * @param {Object} [config.scale] set scale + * @param {Number} [config.scaleX] set scale x + * @param {Number} [config.scaleY] set scale y + * @param {Number} [config.rotation] rotation in degrees + * @param {Object} [config.offset] offset from center point and rotation point + * @param {Number} [config.offsetX] set offset x + * @param {Number} [config.offsetY] set offset y + * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop + * the entire stage by dragging any portion of the stage + * @param {Function} [config.dragBoundFunc] + * @example + * var star = new Kinetic.Star({
+ * x: 100,
+ * y: 200,
+ * numPoints: 5,
+ * innerRadius: 70,
+ * outerRadius: 70,
+ * fill: 'red',
+ * stroke: 'black',
+ * strokeWidth: 4
+ * }); + */ + Kinetic.Star = function(config) { + this.___init(config); + }; + + Kinetic.Star.prototype = { + ___init: function(config) { + // call super constructor + Kinetic.Shape.call(this, config); + this.className = 'Star'; + this.sceneFunc(this._sceneFunc); + }, + _sceneFunc: function(context) { + var innerRadius = this.innerRadius(), + outerRadius = this.outerRadius(), + numPoints = this.numPoints(); + + context.beginPath(); + context.moveTo(0, 0 - outerRadius); + + for(var n = 1; n < numPoints * 2; n++) { + var radius = n % 2 === 0 ? outerRadius : innerRadius; + var x = radius * Math.sin(n * Math.PI / numPoints); + var y = -1 * radius * Math.cos(n * Math.PI / numPoints); + context.lineTo(x, y); + } + context.closePath(); + + context.fillStrokeShape(this); + } + }; + Kinetic.Util.extend(Kinetic.Star, Kinetic.Shape); + + // add getters setters + Kinetic.Factory.addGetterSetter(Kinetic.Star, 'numPoints', 5); + + /** + * set number of points + * @name setNumPoints + * @method + * @memberof Kinetic.Star.prototype + * @param {Integer} points + */ + + /** + * get number of points + * @name getNumPoints + * @method + * @memberof Kinetic.Star.prototype + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Star, 'innerRadius', 0); + + /** + * set inner radius + * @name setInnerRadius + * @method + * @memberof Kinetic.Star.prototype + * @param {Number} radius + */ + + /** + * get inner radius + * @name getInnerRadius + * @method + * @memberof Kinetic.Star.prototype + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Star, 'outerRadius', 0); + + /** + * set outer radius + * @name setOuterRadius + * @method + * @memberof Kinetic.Star.prototype + * @param {Number} radius + */ + + /** + * get outer radius + * @name getOuterRadius + * @method + * @memberof Kinetic.Star.prototype + */ + + Kinetic.Collection.mapMethods(Kinetic.Star); +})(); +;(function() { + // constants + var ATTR_CHANGE_LIST = ['fontFamily', 'fontSize', 'fontStyle', 'padding', 'lineHeight', 'text'], + CHANGE_KINETIC = 'Change.kinetic', + NONE = 'none', + UP = 'up', + RIGHT = 'right', + DOWN = 'down', + LEFT = 'left', + LABEL = 'Label', + + // cached variables + attrChangeListLen = ATTR_CHANGE_LIST.length; + + /** + * Label constructor.  Labels are groups that contain a Text and Tag shape + * @constructor + * @memberof Kinetic + * @param {Object} config + * @param {Number} [config.x] + * @param {Number} [config.y] + * @param {Number} [config.width] + * @param {Number} [config.height] + * @param {Boolean} [config.visible] + * @param {Boolean} [config.listening] whether or not the node is listening for events + * @param {String} [config.id] unique id + * @param {String} [config.name] non-unique name + * @param {Number} [config.opacity] determines node opacity. Can be any number between 0 and 1 + * @param {Object} [config.scale] set scale + * @param {Number} [config.scaleX] set scale x + * @param {Number} [config.scaleY] set scale y + * @param {Number} [config.rotation] rotation in degrees + * @param {Object} [config.offset] offset from center point and rotation point + * @param {Number} [config.offsetX] set offset x + * @param {Number} [config.offsetY] set offset y + * @param {Boolean} [config.draggable] makes the node draggable. When stages are draggable, you can drag and drop + * the entire stage by dragging any portion of the stage + * @param {Function} [config.dragBoundFunc] + * @example + * // create label + * var label = new Kinetic.Label({
+ * x: 100,
+ * y: 100,
+ * draggable: true
+ * });

+ * + * // add a tag to the label
+ * label.add(new Kinetic.Tag({
+ * fill: '#bbb',
+ * stroke: '#333',
+ * shadowColor: 'black',
+ * shadowBlur: 10,
+ * shadowOffset: [10, 10],
+ * shadowOpacity: 0.2,
+ * lineJoin: 'round',
+ * pointerDirection: 'up',
+ * pointerWidth: 20,
+ * pointerHeight: 20,
+ * cornerRadius: 5
+ * }));

+ * + * // add text to the label
+ * label.add(new Kinetic.Text({
+ * text: 'Hello World!',
+ * fontSize: 50,
+ * lineHeight: 1.2,
+ * padding: 10,
+ * fill: 'green'
+ * })); + */ + Kinetic.Label = function(config) { + this.____init(config); + }; + + Kinetic.Label.prototype = { + ____init: function(config) { + var that = this; + + this.className = LABEL; + Kinetic.Group.call(this, config); + + this.on('add.kinetic', function(evt) { + that._addListeners(evt.child); + that._sync(); + }); + }, + /** + * get Text shape for the label. You need to access the Text shape in order to update + * the text properties + * @name getText + * @method + * @memberof Kinetic.Label.prototype + */ + getText: function() { + return this.find('Text')[0]; + }, + /** + * get Tag shape for the label. You need to access the Tag shape in order to update + * the pointer properties and the corner radius + * @name getTag + * @method + * @memberof Kinetic.Label.prototype + */ + getTag: function() { + return this.find('Tag')[0]; + }, + _addListeners: function(text) { + var that = this, + n; + var func = function(){ + that._sync(); + }; + + // update text data for certain attr changes + for(n = 0; n < attrChangeListLen; n++) { + text.on(ATTR_CHANGE_LIST[n] + CHANGE_KINETIC, func); + } + }, + getWidth: function() { + return this.getText().getWidth(); + }, + getHeight: function() { + return this.getText().getHeight(); + }, + _sync: function() { + var text = this.getText(), + tag = this.getTag(), + width, height, pointerDirection, pointerWidth, x, y; + + if (text && tag) { + width = text.getWidth(), + height = text.getHeight(), + pointerDirection = tag.getPointerDirection(), + pointerWidth = tag.getPointerWidth(), + pointerHeight = tag.getPointerHeight(), + x = 0, + y = 0; + + switch(pointerDirection) { + case UP: + x = width / 2; + y = -1 * pointerHeight; + break; + case RIGHT: + x = width + pointerWidth; + y = height / 2; + break; + case DOWN: + x = width / 2; + y = height + pointerHeight; + break; + case LEFT: + x = -1 * pointerWidth; + y = height / 2; + break; + } + + tag.setAttrs({ + x: -1 * x, + y: -1 * y, + width: width, + height: height + }); + + text.setAttrs({ + x: -1 * x, + y: -1 * y + }); + } + } + }; + + Kinetic.Util.extend(Kinetic.Label, Kinetic.Group); + + Kinetic.Collection.mapMethods(Kinetic.Label); + + /** + * Tag constructor.  A Tag can be configured + * to have a pointer element that points up, right, down, or left + * @constructor + * @memberof Kinetic + * @param {Object} config + * @param {String} [config.pointerDirection] can be up, right, down, left, or none; the default + * is none. When a pointer is present, the positioning of the label is relative to the tip of the pointer. + * @param {Number} [config.pointerWidth] + * @param {Number} [config.pointerHeight] + * @param {Number} [config.cornerRadius] + */ + Kinetic.Tag = function(config) { + this.___init(config); + }; + + Kinetic.Tag.prototype = { + ___init: function(config) { + Kinetic.Shape.call(this, config); + this.className = 'Tag'; + this.sceneFunc(this._sceneFunc); + }, + _sceneFunc: function(context) { + var width = this.getWidth(), + height = this.getHeight(), + pointerDirection = this.getPointerDirection(), + pointerWidth = this.getPointerWidth(), + pointerHeight = this.getPointerHeight(), + cornerRadius = this.getCornerRadius(); + + context.beginPath(); + context.moveTo(0,0); + + if (pointerDirection === UP) { + context.lineTo((width - pointerWidth)/2, 0); + context.lineTo(width/2, -1 * pointerHeight); + context.lineTo((width + pointerWidth)/2, 0); + } + + context.lineTo(width, 0); + + if (pointerDirection === RIGHT) { + context.lineTo(width, (height - pointerHeight)/2); + context.lineTo(width + pointerWidth, height/2); + context.lineTo(width, (height + pointerHeight)/2); + } + + context.lineTo(width, height); + + if (pointerDirection === DOWN) { + context.lineTo((width + pointerWidth)/2, height); + context.lineTo(width/2, height + pointerHeight); + context.lineTo((width - pointerWidth)/2, height); + } + + context.lineTo(0, height); + + if (pointerDirection === LEFT) { + context.lineTo(0, (height + pointerHeight)/2); + context.lineTo(-1 * pointerWidth, height/2); + context.lineTo(0, (height - pointerHeight)/2); + } + + context.closePath(); + context.fillStrokeShape(this); + } + }; + + Kinetic.Util.extend(Kinetic.Tag, Kinetic.Shape); + Kinetic.Factory.addGetterSetter(Kinetic.Tag, 'pointerDirection', NONE); + + /** + * set pointer Direction + * @name setPointerDirection + * @method + * @memberof Kinetic.Tag.prototype + * @param {String} pointerDirection can be up, right, down, left, or none. The + * default is none + */ + + /** + * get pointer Direction + * @name getPointerDirection + * @method + * @memberof Kinetic.Tag.prototype + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Tag, 'pointerWidth', 0); + + /** + * set pointer width + * @name setPointerWidth + * @method + * @memberof Kinetic.Tag.prototype + * @param {Number} pointerWidth + */ + + /** + * get pointer width + * @name getPointerWidth + * @method + * @memberof Kinetic.Tag.prototype + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Tag, 'pointerHeight', 0); + + /** + * set pointer height + * @name setPointerHeight + * @method + * @memberof Kinetic.Tag.prototype + * @param {Number} pointerHeight + */ + + /** + * get pointer height + * @name getPointerHeight + * @method + * @memberof Kinetic.Tag.prototype + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Tag, 'cornerRadius', 0); + + /** + * set corner radius + * @name setCornerRadius + * @method + * @memberof Kinetic.Tag.prototype + * @param {Number} corner radius + */ + + /** + * get corner radius + * @name getCornerRadius + * @method + * @memberof Kinetic.Tag.prototype + */ + + Kinetic.Collection.mapMethods(Kinetic.Tag); +})(); diff --git a/kinetic-v5.0.1.min.js b/kinetic.min.js similarity index 100% rename from kinetic-v5.0.1.min.js rename to kinetic.min.js