Last active
December 29, 2021 00:04
-
-
Save chadsconway/cb1a162fe6a2dbc92e4463a25875d837 to your computer and use it in GitHub Desktop.
createjs buttons
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>EaselJS demo: Inheritance</title> | |
<link href="./demo.css" rel="stylesheet" type="text/css" /> | |
<script src="./easeljs-NEXT.js"></script> | |
<script src="./button.js"></script> | |
<script> | |
var stage, holder; | |
function init() { | |
stage = new createjs.Stage("demoCanvas"); | |
stage.enableMouseOver(); | |
var btn1 = stage.addChild(new Button("Hello!", "#F00")); | |
btn1.y = 20; | |
var btn2 = stage.addChild(new Button("Goodbye.", "#0F0")); | |
btn2.y = btn1.y + 50; | |
var btn3 = stage.addChild(new Button("Hello again!!", "#0FF")); | |
btn3.y = btn2.y + 50; | |
btn1.x = btn2.x = btn3.x = 20; | |
createjs.Ticker.on("tick", stage); | |
} | |
</script> | |
</head> | |
<body onload="init();"> | |
<canvas id="demoCanvas" width="300" height="200"> | |
alternate content | |
</canvas> | |
</body> | |
</html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
body { | |
margin: 0; | |
padding: 7px; | |
background-color: rgba(255, 255, 255, 0); | |
} | |
canvas { | |
border: solid 1px rgba(0, 0, 0, 0.05); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*! | |
* EaselJS | |
* Visit http://createjs.com/ for documentation, updates and examples. | |
* | |
* Copyright (c) 2010 gskinner.com, inc. | |
* | |
* 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. | |
*/ | |
//############################################################################## | |
// extend.js | |
//############################################################################## | |
this.createjs = this.createjs || {}; | |
/** | |
* @class Utility Methods | |
*/ | |
/** | |
* Sets up the prototype chain and constructor property for a new class. | |
* | |
* This should be called right after creating the class constructor. | |
* | |
* function MySubClass() {} | |
* createjs.extend(MySubClass, MySuperClass); | |
* MySubClass.prototype.doSomething = function() { } | |
* | |
* var foo = new MySubClass(); | |
* console.log(foo instanceof MySuperClass); // true | |
* console.log(foo.prototype.constructor === MySubClass); // true | |
* | |
* @method extend | |
* @param {Function} subclass The subclass. | |
* @param {Function} superclass The superclass to extend. | |
* @return {Function} Returns the subclass's new prototype. | |
*/ | |
createjs.extend = function (subclass, superclass) { | |
"use strict"; | |
function o() { | |
this.constructor = subclass; | |
} | |
o.prototype = superclass.prototype; | |
return (subclass.prototype = new o()); | |
}; | |
//############################################################################## | |
// promote.js | |
//############################################################################## | |
this.createjs = this.createjs || {}; | |
/** | |
* @class Utility Methods | |
*/ | |
/** | |
* Promotes any methods on the super class that were overridden, by creating an alias in the format `prefix_methodName`. | |
* It is recommended to use the super class's name as the prefix. | |
* An alias to the super class's constructor is always added in the format `prefix_constructor`. | |
* This allows the subclass to call super class methods without using `function.call`, providing better performance. | |
* | |
* For example, if `MySubClass` extends `MySuperClass`, and both define a `draw` method, then calling `promote(MySubClass, "MySuperClass")` | |
* would add a `MySuperClass_constructor` method to MySubClass and promote the `draw` method on `MySuperClass` to the | |
* prototype of `MySubClass` as `MySuperClass_draw`. | |
* | |
* This should be called after the class's prototype is fully defined. | |
* | |
* function ClassA(name) { | |
* this.name = name; | |
* } | |
* ClassA.prototype.greet = function() { | |
* return "Hello "+this.name; | |
* } | |
* | |
* function ClassB(name, punctuation) { | |
* this.ClassA_constructor(name); | |
* this.punctuation = punctuation; | |
* } | |
* createjs.extend(ClassB, ClassA); | |
* ClassB.prototype.greet = function() { | |
* return this.ClassA_greet()+this.punctuation; | |
* } | |
* createjs.promote(ClassB, "ClassA"); | |
* | |
* var foo = new ClassB("World", "!?!"); | |
* console.log(foo.greet()); // Hello World!?! | |
* | |
* @method promote | |
* @param {Function} subclass The class to promote super class methods on. | |
* @param {String} prefix The prefix to add to the promoted method names. Usually the name of the superclass. | |
* @return {Function} Returns the subclass. | |
*/ | |
createjs.promote = function (subclass, prefix) { | |
"use strict"; | |
var subP = subclass.prototype, | |
supP = | |
(Object.getPrototypeOf && Object.getPrototypeOf(subP)) || subP.__proto__; | |
if (supP) { | |
subP[(prefix += "_") + "constructor"] = supP.constructor; // constructor is not always innumerable | |
for (var n in supP) { | |
if (subP.hasOwnProperty(n) && typeof supP[n] == "function") { | |
subP[prefix + n] = supP[n]; | |
} | |
} | |
} | |
return subclass; | |
}; | |
//############################################################################## | |
// indexOf.js | |
//############################################################################## | |
this.createjs = this.createjs || {}; | |
/** | |
* @class Utility Methods | |
*/ | |
/** | |
* Finds the first occurrence of a specified value searchElement in the passed in array, and returns the index of | |
* that value. Returns -1 if value is not found. | |
* | |
* var i = createjs.indexOf(myArray, myElementToFind); | |
* | |
* @method indexOf | |
* @param {Array} array Array to search for searchElement | |
* @param searchElement Element to find in array. | |
* @return {Number} The first index of searchElement in array. | |
*/ | |
createjs.indexOf = function (array, searchElement) { | |
"use strict"; | |
for (var i = 0, l = array.length; i < l; i++) { | |
if (searchElement === array[i]) { | |
return i; | |
} | |
} | |
return -1; | |
}; | |
//############################################################################## | |
// UID.js | |
//############################################################################## | |
this.createjs = this.createjs || {}; | |
(function () { | |
"use strict"; | |
// constructor: | |
/** | |
* Global utility for generating sequential unique ID numbers. The UID class uses a static interface (ex. <code>UID.get()</code>) | |
* and should not be instantiated. | |
* @class UID | |
* @static | |
**/ | |
function UID() { | |
throw "UID cannot be instantiated"; | |
} | |
// private static properties: | |
/** | |
* @property _nextID | |
* @type Number | |
* @protected | |
**/ | |
UID._nextID = 0; | |
// public static methods: | |
/** | |
* Returns the next unique id. | |
* @method get | |
* @return {Number} The next unique id | |
* @static | |
**/ | |
UID.get = function () { | |
return UID._nextID++; | |
}; | |
createjs.UID = UID; | |
})(); | |
//############################################################################## | |
// deprecate.js | |
//############################################################################## | |
this.createjs = this.createjs || {}; | |
/** | |
* @class Utility Methods | |
*/ | |
/** | |
* Wraps deprecated methods so they still be used, but throw warnings to developers. | |
* | |
* obj.deprecatedMethod = createjs.deprecate("Old Method Name", obj._fallbackMethod); | |
* | |
* The recommended approach for deprecated properties is: | |
* | |
* try { | |
* Obj ect.defineProperties(object, { | |
* readyOnlyProp: { get: createjs.deprecate("readOnlyProp", function() { return this.alternateProp; }) }, | |
* readWriteProp: { | |
* get: createjs.deprecate("readOnlyProp", function() { return this.alternateProp; }), | |
* set: createjs.deprecate("readOnlyProp", function(val) { this.alternateProp = val; }) | |
* }); | |
* } catch (e) {} | |
* | |
* @method deprecate | |
* @param {Function} [fallbackMethod=null] A method to call when the deprecated method is used. See the example for how | |
* @param {String} [name=null] The name of the method or property to display in the console warning. | |
* to deprecate properties. | |
* @return {Function} If a fallbackMethod is supplied, returns a closure that will call the fallback method after | |
* logging the warning in the console. | |
*/ | |
createjs.deprecate = function (fallbackMethod, name) { | |
"use strict"; | |
return function () { | |
var msg = | |
"Deprecated property or method '" + name + "'. See docs for info."; | |
console && (console.warn ? console.warn(msg) : console.log(msg)); | |
return fallbackMethod && fallbackMethod.apply(this, arguments); | |
}; | |
}; | |
//############################################################################## | |
// Event.js | |
//############################################################################## | |
this.createjs = this.createjs || {}; | |
(function () { | |
"use strict"; | |
// constructor: | |
/** | |
* Contains properties and methods shared by all events for use with | |
* {{#crossLink "EventDispatcher"}}{{/crossLink}}. | |
* | |
* Note that Event objects are often reused, so you should never | |
* rely on an event object's state outside of the call stack it was received in. | |
* @class Event | |
* @param {String} type The event type. | |
* @param {Boolean} bubbles Indicates whether the event will bubble through the display list. | |
* @param {Boolean} cancelable Indicates whether the default behaviour of this event can be cancelled. | |
* @constructor | |
**/ | |
function Event(type, bubbles, cancelable) { | |
// public properties: | |
/** | |
* The type of event. | |
* @property type | |
* @type String | |
**/ | |
this.type = type; | |
/** | |
* The object that generated an event. | |
* @property target | |
* @type Object | |
* @default null | |
* @readonly | |
*/ | |
this.target = null; | |
/** | |
* The current target that a bubbling event is being dispatched from. For non-bubbling events, this will | |
* always be the same as target. For example, if childObj.parent = parentObj, and a bubbling event | |
* is generated from childObj, then a listener on parentObj would receive the event with | |
* target=childObj (the original target) and currentTarget=parentObj (where the listener was added). | |
* @property currentTarget | |
* @type Object | |
* @default null | |
* @readonly | |
*/ | |
this.currentTarget = null; | |
/** | |
* For bubbling events, this indicates the current event phase:<OL> | |
* <LI> capture phase: starting from the top parent to the target</LI> | |
* <LI> at target phase: currently being dispatched from the target</LI> | |
* <LI> bubbling phase: from the target to the top parent</LI> | |
* </OL> | |
* @property eventPhase | |
* @type Number | |
* @default 0 | |
* @readonly | |
*/ | |
this.eventPhase = 0; | |
/** | |
* Indicates whether the event will bubble through the display list. | |
* @property bubbles | |
* @type Boolean | |
* @default false | |
* @readonly | |
*/ | |
this.bubbles = !!bubbles; | |
/** | |
* Indicates whether the default behaviour of this event can be cancelled via | |
* {{#crossLink "Event/preventDefault"}}{{/crossLink}}. This is set via the Event constructor. | |
* @property cancelable | |
* @type Boolean | |
* @default false | |
* @readonly | |
*/ | |
this.cancelable = !!cancelable; | |
/** | |
* The epoch time at which this event was created. | |
* @property timeStamp | |
* @type Number | |
* @default 0 | |
* @readonly | |
*/ | |
this.timeStamp = new Date().getTime(); | |
/** | |
* Indicates if {{#crossLink "Event/preventDefault"}}{{/crossLink}} has been called | |
* on this event. | |
* @property defaultPrevented | |
* @type Boolean | |
* @default false | |
* @readonly | |
*/ | |
this.defaultPrevented = false; | |
/** | |
* Indicates if {{#crossLink "Event/stopPropagation"}}{{/crossLink}} or | |
* {{#crossLink "Event/stopImmediatePropagation"}}{{/crossLink}} has been called on this event. | |
* @property propagationStopped | |
* @type Boolean | |
* @default false | |
* @readonly | |
*/ | |
this.propagationStopped = false; | |
/** | |
* Indicates if {{#crossLink "Event/stopImmediatePropagation"}}{{/crossLink}} has been called | |
* on this event. | |
* @property immediatePropagationStopped | |
* @type Boolean | |
* @default false | |
* @readonly | |
*/ | |
this.immediatePropagationStopped = false; | |
/** | |
* Indicates if {{#crossLink "Event/remove"}}{{/crossLink}} has been called on this event. | |
* @property removed | |
* @type Boolean | |
* @default false | |
* @readonly | |
*/ | |
this.removed = false; | |
} | |
var p = Event.prototype; | |
// public methods: | |
/** | |
* Sets {{#crossLink "Event/defaultPrevented"}}{{/crossLink}} to true if the event is cancelable. | |
* Mirrors the DOM level 2 event standard. In general, cancelable events that have `preventDefault()` called will | |
* cancel the default behaviour associated with the event. | |
* @method preventDefault | |
**/ | |
p.preventDefault = function () { | |
this.defaultPrevented = this.cancelable && true; | |
}; | |
/** | |
* Sets {{#crossLink "Event/propagationStopped"}}{{/crossLink}} to true. | |
* Mirrors the DOM event standard. | |
* @method stopPropagation | |
**/ | |
p.stopPropagation = function () { | |
this.propagationStopped = true; | |
}; | |
/** | |
* Sets {{#crossLink "Event/propagationStopped"}}{{/crossLink}} and | |
* {{#crossLink "Event/immediatePropagationStopped"}}{{/crossLink}} to true. | |
* Mirrors the DOM event standard. | |
* @method stopImmediatePropagation | |
**/ | |
p.stopImmediatePropagation = function () { | |
this.immediatePropagationStopped = this.propagationStopped = true; | |
}; | |
/** | |
* Causes the active listener to be removed via removeEventListener(); | |
* | |
* myBtn.addEventListener("click", function(evt) { | |
* // do stuff... | |
* evt.remove(); // removes this listener. | |
* }); | |
* | |
* @method remove | |
**/ | |
p.remove = function () { | |
this.removed = true; | |
}; | |
/** | |
* Returns a clone of the Event instance. | |
* @method clone | |
* @return {Event} a clone of the Event instance. | |
**/ | |
p.clone = function () { | |
return new Event(this.type, this.bubbles, this.cancelable); | |
}; | |
/** | |
* Provides a chainable shortcut method for setting a number of properties on the instance. | |
* | |
* @method set | |
* @param {Object} props A generic object containing properties to copy to the instance. | |
* @return {Event} Returns the instance the method is called on (useful for chaining calls.) | |
* @chainable | |
*/ | |
p.set = function (props) { | |
for (var n in props) { | |
this[n] = props[n]; | |
} | |
return this; | |
}; | |
/** | |
* Returns a string representation of this object. | |
* @method toString | |
* @return {String} a string representation of the instance. | |
**/ | |
p.toString = function () { | |
return "[Event (type=" + this.type + ")]"; | |
}; | |
createjs.Event = Event; | |
})(); | |
//############################################################################## | |
// EventDispatcher.js | |
//############################################################################## | |
this.createjs = this.createjs || {}; | |
(function () { | |
"use strict"; | |
// constructor: | |
/** | |
* EventDispatcher provides methods for managing queues of event listeners and dispatching events. | |
* | |
* You can either extend EventDispatcher or mix its methods into an existing prototype or instance by using the | |
* EventDispatcher {{#crossLink "EventDispatcher/initialize"}}{{/crossLink}} method. | |
* | |
* Together with the CreateJS Event class, EventDispatcher provides an extended event model that is based on the | |
* DOM Level 2 event model, including addEventListener, removeEventListener, and dispatchEvent. It supports | |
* bubbling / capture, preventDefault, stopPropagation, stopImmediatePropagation, and handleEvent. | |
* | |
* EventDispatcher also exposes a {{#crossLink "EventDispatcher/on"}}{{/crossLink}} method, which makes it easier | |
* to create scoped listeners, listeners that only run once, and listeners with associated arbitrary data. The | |
* {{#crossLink "EventDispatcher/off"}}{{/crossLink}} method is merely an alias to | |
* {{#crossLink "EventDispatcher/removeEventListener"}}{{/crossLink}}. | |
* | |
* Another addition to the DOM Level 2 model is the {{#crossLink "EventDispatcher/removeAllEventListeners"}}{{/crossLink}} | |
* method, which can be used to listeners for all events, or listeners for a specific event. The Event object also | |
* includes a {{#crossLink "Event/remove"}}{{/crossLink}} method which removes the active listener. | |
* | |
* <h4>Example</h4> | |
* Add EventDispatcher capabilities to the "MyClass" class. | |
* | |
* EventDispatcher.initialize(MyClass.prototype); | |
* | |
* Add an event (see {{#crossLink "EventDispatcher/addEventListener"}}{{/crossLink}}). | |
* | |
* instance.addEventListener("eventName", handlerMethod); | |
* function handlerMethod(event) { | |
* console.log(event.target + " Was Clicked"); | |
* } | |
* | |
* <b>Maintaining proper scope</b><br /> | |
* Scope (ie. "this") can be be a challenge with events. Using the {{#crossLink "EventDispatcher/on"}}{{/crossLink}} | |
* method to subscribe to events simplifies this. | |
* | |
* instance.addEventListener("click", function(event) { | |
* console.log(instance == this); // false, scope is ambiguous. | |
* }); | |
* | |
* instance.on("click", function(event) { | |
* console.log(instance == this); // true, "on" uses dispatcher scope by default. | |
* }); | |
* | |
* If you want to use addEventListener instead, you may want to use function.bind() or a similar proxy to manage | |
* scope. | |
* | |
* <b>Browser support</b> | |
* The event model in CreateJS can be used separately from the suite in any project, however the inheritance model | |
* requires modern browsers (IE9+). | |
* | |
* | |
* @class EventDispatcher | |
* @constructor | |
**/ | |
function EventDispatcher() { | |
// private properties: | |
/** | |
* @protected | |
* @property _listeners | |
* @type Object | |
**/ | |
this._listeners = null; | |
/** | |
* @protected | |
* @property _captureListeners | |
* @type Object | |
**/ | |
this._captureListeners = null; | |
} | |
var p = EventDispatcher.prototype; | |
// static public methods: | |
/** | |
* Static initializer to mix EventDispatcher methods into a target object or prototype. | |
* | |
* EventDispatcher.initialize(MyClass.prototype); // add to the prototype of the class | |
* EventDispatcher.initialize(myObject); // add to a specific instance | |
* | |
* @method initialize | |
* @static | |
* @param {Object} target The target object to inject EventDispatcher methods into. This can be an instance or a | |
* prototype. | |
**/ | |
EventDispatcher.initialize = function (target) { | |
target.addEventListener = p.addEventListener; | |
target.on = p.on; | |
target.removeEventListener = target.off = p.removeEventListener; | |
target.removeAllEventListeners = p.removeAllEventListeners; | |
target.hasEventListener = p.hasEventListener; | |
target.dispatchEvent = p.dispatchEvent; | |
target._dispatchEvent = p._dispatchEvent; | |
target.willTrigger = p.willTrigger; | |
}; | |
// public methods: | |
/** | |
* Adds the specified event listener. Note that adding multiple listeners to the same function will result in | |
* multiple callbacks getting fired. | |
* | |
* <h4>Example</h4> | |
* | |
* displayObject.addEventListener("click", handleClick); | |
* function handleClick(event) { | |
* // Click happened. | |
* } | |
* | |
* @method addEventListener | |
* @param {String} type The string type of the event. | |
* @param {Function | Object} listener An object with a handleEvent method, or a function that will be called when | |
* the event is dispatched. | |
* @param {Boolean} [useCapture] For events that bubble, indicates whether to listen for the event in the capture or bubbling/target phase. | |
* @return {Function | Object} Returns the listener for chaining or assignment. | |
**/ | |
p.addEventListener = function (type, listener, useCapture) { | |
var listeners; | |
if (useCapture) { | |
listeners = this._captureListeners = this._captureListeners || {}; | |
} else { | |
listeners = this._listeners = this._listeners || {}; | |
} | |
var arr = listeners[type]; | |
if (arr) { | |
this.removeEventListener(type, listener, useCapture); | |
} | |
arr = listeners[type]; // remove may have deleted the array | |
if (!arr) { | |
listeners[type] = [listener]; | |
} else { | |
arr.push(listener); | |
} | |
return listener; | |
}; | |
/** | |
* A shortcut method for using addEventListener that makes it easier to specify an execution scope, have a listener | |
* only run once, associate arbitrary data with the listener, and remove the listener. | |
* | |
* This method works by creating an anonymous wrapper function and subscribing it with addEventListener. | |
* The wrapper function is returned for use with `removeEventListener` (or `off`). | |
* | |
* <b>IMPORTANT:</b> To remove a listener added with `on`, you must pass in the returned wrapper function as the listener, or use | |
* {{#crossLink "Event/remove"}}{{/crossLink}}. Likewise, each time you call `on` a NEW wrapper function is subscribed, so multiple calls | |
* to `on` with the same params will create multiple listeners. | |
* | |
* <h4>Example</h4> | |
* | |
* var listener = myBtn.on("click", handleClick, null, false, {count:3}); | |
* function handleClick(evt, data) { | |
* data.count -= 1; | |
* console.log(this == myBtn); // true - scope defaults to the dispatcher | |
* if (data.count == 0) { | |
* alert("clicked 3 times!"); | |
* myBtn.off("click", listener); | |
* // alternately: evt.remove(); | |
* } | |
* } | |
* | |
* @method on | |
* @param {String} type The string type of the event. | |
* @param {Function | Object} listener An object with a handleEvent method, or a function that will be called when | |
* the event is dispatched. | |
* @param {Object} [scope] The scope to execute the listener in. Defaults to the dispatcher/currentTarget for function listeners, and to the listener itself for object listeners (ie. using handleEvent). | |
* @param {Boolean} [once=false] If true, the listener will remove itself after the first time it is triggered. | |
* @param {*} [data] Arbitrary data that will be included as the second parameter when the listener is called. | |
* @param {Boolean} [useCapture=false] For events that bubble, indicates whether to listen for the event in the capture or bubbling/target phase. | |
* @return {Function} Returns the anonymous function that was created and assigned as the listener. This is needed to remove the listener later using .removeEventListener. | |
**/ | |
p.on = function (type, listener, scope, once, data, useCapture) { | |
if (listener.handleEvent) { | |
scope = scope || listener; | |
listener = listener.handleEvent; | |
} | |
scope = scope || this; | |
return this.addEventListener( | |
type, | |
function (evt) { | |
listener.call(scope, evt, data); | |
once && evt.remove(); | |
}, | |
useCapture | |
); | |
}; | |
/** | |
* Removes the specified event listener. | |
* | |
* <b>Important Note:</b> that you must pass the exact function reference used when the event was added. If a proxy | |
* function, or function closure is used as the callback, the proxy/closure reference must be used - a new proxy or | |
* closure will not work. | |
* | |
* <h4>Example</h4> | |
* | |
* displayObject.removeEventListener("click", handleClick); | |
* | |
* @method removeEventListener | |
* @param {String} type The string type of the event. | |
* @param {Function | Object} listener The listener function or object. | |
* @param {Boolean} [useCapture] For events that bubble, indicates whether to listen for the event in the capture or bubbling/target phase. | |
**/ | |
p.removeEventListener = function (type, listener, useCapture) { | |
var listeners = useCapture ? this._captureListeners : this._listeners; | |
if (!listeners) { | |
return; | |
} | |
var arr = listeners[type]; | |
if (!arr) { | |
return; | |
} | |
for (var i = 0, l = arr.length; i < l; i++) { | |
if (arr[i] == listener) { | |
if (l == 1) { | |
delete listeners[type]; | |
} // allows for faster checks. | |
else { | |
arr.splice(i, 1); | |
} | |
break; | |
} | |
} | |
}; | |
/** | |
* A shortcut to the removeEventListener method, with the same parameters and return value. This is a companion to the | |
* .on method. | |
* | |
* <b>IMPORTANT:</b> To remove a listener added with `on`, you must pass in the returned wrapper function as the listener. See | |
* {{#crossLink "EventDispatcher/on"}}{{/crossLink}} for an example. | |
* | |
* @method off | |
* @param {String} type The string type of the event. | |
* @param {Function | Object} listener The listener function or object. | |
* @param {Boolean} [useCapture] For events that bubble, indicates whether to listen for the event in the capture or bubbling/target phase. | |
**/ | |
p.off = p.removeEventListener; | |
/** | |
* Removes all listeners for the specified type, or all listeners of all types. | |
* | |
* <h4>Example</h4> | |
* | |
* // Remove all listeners | |
* displayObject.removeAllEventListeners(); | |
* | |
* // Remove all click listeners | |
* displayObject.removeAllEventListeners("click"); | |
* | |
* @method removeAllEventListeners | |
* @param {String} [type] The string type of the event. If omitted, all listeners for all types will be removed. | |
**/ | |
p.removeAllEventListeners = function (type) { | |
if (!type) { | |
this._listeners = this._captureListeners = null; | |
} else { | |
if (this._listeners) { | |
delete this._listeners[type]; | |
} | |
if (this._captureListeners) { | |
delete this._captureListeners[type]; | |
} | |
} | |
}; | |
/** | |
* Dispatches the specified event to all listeners. | |
* | |
* <h4>Example</h4> | |
* | |
* // Use a string event | |
* this.dispatchEvent("complete"); | |
* | |
* // Use an Event instance | |
* var event = new createjs.Event("progress"); | |
* this.dispatchEvent(event); | |
* | |
* @method dispatchEvent | |
* @param {Object | String | Event} eventObj An object with a "type" property, or a string type. | |
* While a generic object will work, it is recommended to use a CreateJS Event instance. If a string is used, | |
* dispatchEvent will construct an Event instance if necessary with the specified type. This latter approach can | |
* be used to avoid event object instantiation for non-bubbling events that may not have any listeners. | |
* @param {Boolean} [bubbles] Specifies the `bubbles` value when a string was passed to eventObj. | |
* @param {Boolean} [cancelable] Specifies the `cancelable` value when a string was passed to eventObj. | |
* @return {Boolean} Returns false if `preventDefault()` was called on a cancelable event, true otherwise. | |
**/ | |
p.dispatchEvent = function (eventObj, bubbles, cancelable) { | |
if (typeof eventObj == "string") { | |
// skip everything if there's no listeners and it doesn't bubble: | |
var listeners = this._listeners; | |
if (!bubbles && (!listeners || !listeners[eventObj])) { | |
return true; | |
} | |
eventObj = new createjs.Event(eventObj, bubbles, cancelable); | |
} else if (eventObj.target && eventObj.clone) { | |
// redispatching an active event object, so clone it: | |
eventObj = eventObj.clone(); | |
} | |
// TODO: it would be nice to eliminate this. Maybe in favour of evtObj instanceof Event? Or !!evtObj.createEvent | |
try { | |
eventObj.target = this; | |
} catch (e) {} // try/catch allows redispatching of native events | |
if (!eventObj.bubbles || !this.parent) { | |
this._dispatchEvent(eventObj, 2); | |
} else { | |
var top = this, | |
list = [top]; | |
while (top.parent) { | |
list.push((top = top.parent)); | |
} | |
var i, | |
l = list.length; | |
// capture & atTarget | |
for (i = l - 1; i >= 0 && !eventObj.propagationStopped; i--) { | |
list[i]._dispatchEvent(eventObj, 1 + (i == 0)); | |
} | |
// bubbling | |
for (i = 1; i < l && !eventObj.propagationStopped; i++) { | |
list[i]._dispatchEvent(eventObj, 3); | |
} | |
} | |
return !eventObj.defaultPrevented; | |
}; | |
/** | |
* Indicates whether there is at least one listener for the specified event type. | |
* @method hasEventListener | |
* @param {String} type The string type of the event. | |
* @return {Boolean} Returns true if there is at least one listener for the specified event. | |
**/ | |
p.hasEventListener = function (type) { | |
var listeners = this._listeners, | |
captureListeners = this._captureListeners; | |
return !!( | |
(listeners && listeners[type]) || | |
(captureListeners && captureListeners[type]) | |
); | |
}; | |
/** | |
* Indicates whether there is at least one listener for the specified event type on this object or any of its | |
* ancestors (parent, parent's parent, etc). A return value of true indicates that if a bubbling event of the | |
* specified type is dispatched from this object, it will trigger at least one listener. | |
* | |
* This is similar to {{#crossLink "EventDispatcher/hasEventListener"}}{{/crossLink}}, but it searches the entire | |
* event flow for a listener, not just this object. | |
* @method willTrigger | |
* @param {String} type The string type of the event. | |
* @return {Boolean} Returns `true` if there is at least one listener for the specified event. | |
**/ | |
p.willTrigger = function (type) { | |
var o = this; | |
while (o) { | |
if (o.hasEventListener(type)) { | |
return true; | |
} | |
o = o.parent; | |
} | |
return false; | |
}; | |
/** | |
* @method toString | |
* @return {String} a string representation of the instance. | |
**/ | |
p.toString = function () { | |
return "[EventDispatcher]"; | |
}; | |
// private methods: | |
/** | |
* @method _dispatchEvent | |
* @param {Object | Event} eventObj | |
* @param {Object} eventPhase | |
* @protected | |
**/ | |
p._dispatchEvent = function (eventObj, eventPhase) { | |
var l, | |
arr, | |
listeners = eventPhase <= 2 ? this._captureListeners : this._listeners; | |
if ( | |
eventObj && | |
listeners && | |
(arr = listeners[eventObj.type]) && | |
(l = arr.length) | |
) { | |
try { | |
eventObj.currentTarget = this; | |
} catch (e) {} | |
try { | |
eventObj.eventPhase = eventPhase | 0; | |
} catch (e) {} | |
eventObj.removed = false; | |
arr = arr.slice(); // to avoid issues with items being removed or added during the dispatch | |
for (var i = 0; i < l && !eventObj.immediatePropagationStopped; i++) { | |
var o = arr[i]; | |
if (o.handleEvent) { | |
o.handleEvent(eventObj); | |
} else { | |
o(eventObj); | |
} | |
if (eventObj.removed) { | |
this.off(eventObj.type, o, eventPhase == 1); | |
eventObj.removed = false; | |
} | |
} | |
} | |
if (eventPhase === 2) { | |
this._dispatchEvent(eventObj, 2.1); | |
} | |
}; | |
createjs.EventDispatcher = EventDispatcher; | |
})(); | |
//############################################################################## | |
// Ticker.js | |
//############################################################################## | |
this.createjs = this.createjs || {}; | |
(function () { | |
"use strict"; | |
// constructor: | |
/** | |
* The Ticker provides a centralized tick or heartbeat broadcast at a set interval. Listeners can subscribe to the tick | |
* event to be notified when a set time interval has elapsed. | |
* | |
* Note that the interval that the tick event is called is a target interval, and may be broadcast at a slower interval | |
* when under high CPU load. The Ticker class uses a static interface (ex. `Ticker.framerate = 30;`) and | |
* can not be instantiated. | |
* | |
* <h4>Example</h4> | |
* | |
* createjs.Ticker.addEventListener("tick", handleTick); | |
* function handleTick(event) { | |
* // Actions carried out each tick (aka frame) | |
* if (!event.paused) { | |
* // Actions carried out when the Ticker is not paused. | |
* } | |
* } | |
* | |
* @class Ticker | |
* @uses EventDispatcher | |
* @static | |
**/ | |
function Ticker() { | |
throw "Ticker cannot be instantiated."; | |
} | |
// constants: | |
/** | |
* In this mode, Ticker uses the requestAnimationFrame API, but attempts to synch the ticks to target framerate. It | |
* uses a simple heuristic that compares the time of the RAF return to the target time for the current frame and | |
* dispatches the tick when the time is within a certain threshold. | |
* | |
* This mode has a higher variance for time between frames than {{#crossLink "Ticker/TIMEOUT:property"}}{{/crossLink}}, | |
* but does not require that content be time based as with {{#crossLink "Ticker/RAF:property"}}{{/crossLink}} while | |
* gaining the benefits of that API (screen synch, background throttling). | |
* | |
* Variance is usually lowest for framerates that are a divisor of the RAF frequency. This is usually 60, so | |
* framerates of 10, 12, 15, 20, and 30 work well. | |
* | |
* Falls back to {{#crossLink "Ticker/TIMEOUT:property"}}{{/crossLink}} if the requestAnimationFrame API is not | |
* supported. | |
* @property RAF_SYNCHED | |
* @static | |
* @type {String} | |
* @default "synched" | |
* @readonly | |
**/ | |
Ticker.RAF_SYNCHED = "synched"; | |
/** | |
* In this mode, Ticker passes through the requestAnimationFrame heartbeat, ignoring the target framerate completely. | |
* Because requestAnimationFrame frequency is not deterministic, any content using this mode should be time based. | |
* You can leverage {{#crossLink "Ticker/getTime"}}{{/crossLink}} and the {{#crossLink "Ticker/tick:event"}}{{/crossLink}} | |
* event object's "delta" properties to make this easier. | |
* | |
* Falls back on {{#crossLink "Ticker/TIMEOUT:property"}}{{/crossLink}} if the requestAnimationFrame API is not | |
* supported. | |
* @property RAF | |
* @static | |
* @type {String} | |
* @default "raf" | |
* @readonly | |
**/ | |
Ticker.RAF = "raf"; | |
/** | |
* In this mode, Ticker uses the setTimeout API. This provides predictable, adaptive frame timing, but does not | |
* provide the benefits of requestAnimationFrame (screen synch, background throttling). | |
* @property TIMEOUT | |
* @static | |
* @type {String} | |
* @default "timeout" | |
* @readonly | |
**/ | |
Ticker.TIMEOUT = "timeout"; | |
// static events: | |
/** | |
* Dispatched each tick. The event will be dispatched to each listener even when the Ticker has been paused using | |
* {{#crossLink "Ticker/paused:property"}}{{/crossLink}}. | |
* | |
* <h4>Example</h4> | |
* | |
* createjs.Ticker.addEventListener("tick", handleTick); | |
* function handleTick(event) { | |
* console.log("Paused:", event.paused, event.delta); | |
* } | |
* | |
* @event tick | |
* @param {Object} target The object that dispatched the event. | |
* @param {String} type The event type. | |
* @param {Boolean} paused Indicates whether the ticker is currently paused. | |
* @param {Number} delta The time elapsed in ms since the last tick. | |
* @param {Number} time The total time in ms since Ticker was initialized. | |
* @param {Number} runTime The total time in ms that Ticker was not paused since it was initialized. For example, | |
* you could determine the amount of time that the Ticker has been paused since initialization with `time-runTime`. | |
* @since 0.6.0 | |
*/ | |
// public static properties: | |
/** | |
* Specifies the timing api (setTimeout or requestAnimationFrame) and mode to use. See | |
* {{#crossLink "Ticker/TIMEOUT:property"}}{{/crossLink}}, {{#crossLink "Ticker/RAF:property"}}{{/crossLink}}, and | |
* {{#crossLink "Ticker/RAF_SYNCHED:property"}}{{/crossLink}} for mode details. | |
* @property timingMode | |
* @static | |
* @type {String} | |
* @default Ticker.TIMEOUT | |
**/ | |
Ticker.timingMode = null; | |
/** | |
* Specifies a maximum value for the delta property in the tick event object. This is useful when building time | |
* based animations and systems to prevent issues caused by large time gaps caused by background tabs, system sleep, | |
* alert dialogs, or other blocking routines. Double the expected frame duration is often an effective value | |
* (ex. maxDelta=50 when running at 40fps). | |
* | |
* This does not impact any other values (ex. time, runTime, etc), so you may experience issues if you enable maxDelta | |
* when using both delta and other values. | |
* | |
* If 0, there is no maximum. | |
* @property maxDelta | |
* @static | |
* @type {number} | |
* @default 0 | |
*/ | |
Ticker.maxDelta = 0; | |
/** | |
* When the ticker is paused, all listeners will still receive a tick event, but the <code>paused</code> property | |
* of the event will be `true`. Also, while paused the `runTime` will not increase. See {{#crossLink "Ticker/tick:event"}}{{/crossLink}}, | |
* {{#crossLink "Ticker/getTime"}}{{/crossLink}}, and {{#crossLink "Ticker/getEventTime"}}{{/crossLink}} for more | |
* info. | |
* | |
* <h4>Example</h4> | |
* | |
* createjs.Ticker.addEventListener("tick", handleTick); | |
* createjs.Ticker.paused = true; | |
* function handleTick(event) { | |
* console.log(event.paused, | |
* createjs.Ticker.getTime(false), | |
* createjs.Ticker.getTime(true)); | |
* } | |
* | |
* @property paused | |
* @static | |
* @type {Boolean} | |
* @default false | |
**/ | |
Ticker.paused = false; | |
// mix-ins: | |
// EventDispatcher methods: | |
Ticker.removeEventListener = null; | |
Ticker.removeAllEventListeners = null; | |
Ticker.dispatchEvent = null; | |
Ticker.hasEventListener = null; | |
Ticker._listeners = null; | |
createjs.EventDispatcher.initialize(Ticker); // inject EventDispatcher methods. | |
Ticker._addEventListener = Ticker.addEventListener; | |
Ticker.addEventListener = function () { | |
!Ticker._inited && Ticker.init(); | |
return Ticker._addEventListener.apply(Ticker, arguments); | |
}; | |
// private static properties: | |
/** | |
* @property _inited | |
* @static | |
* @type {Boolean} | |
* @private | |
**/ | |
Ticker._inited = false; | |
/** | |
* @property _startTime | |
* @static | |
* @type {Number} | |
* @private | |
**/ | |
Ticker._startTime = 0; | |
/** | |
* @property _pausedTime | |
* @static | |
* @type {Number} | |
* @private | |
**/ | |
Ticker._pausedTime = 0; | |
/** | |
* The number of ticks that have passed | |
* @property _ticks | |
* @static | |
* @type {Number} | |
* @private | |
**/ | |
Ticker._ticks = 0; | |
/** | |
* The number of ticks that have passed while Ticker has been paused | |
* @property _pausedTicks | |
* @static | |
* @type {Number} | |
* @private | |
**/ | |
Ticker._pausedTicks = 0; | |
/** | |
* @property _interval | |
* @static | |
* @type {Number} | |
* @private | |
**/ | |
Ticker._interval = 50; | |
/** | |
* @property _lastTime | |
* @static | |
* @type {Number} | |
* @private | |
**/ | |
Ticker._lastTime = 0; | |
/** | |
* @property _times | |
* @static | |
* @type {Array} | |
* @private | |
**/ | |
Ticker._times = null; | |
/** | |
* @property _tickTimes | |
* @static | |
* @type {Array} | |
* @private | |
**/ | |
Ticker._tickTimes = null; | |
/** | |
* Stores the timeout or requestAnimationFrame id. | |
* @property _timerId | |
* @static | |
* @type {Number} | |
* @private | |
**/ | |
Ticker._timerId = null; | |
/** | |
* True if currently using requestAnimationFrame, false if using setTimeout. This may be different than timingMode | |
* if that property changed and a tick hasn't fired. | |
* @property _raf | |
* @static | |
* @type {Boolean} | |
* @private | |
**/ | |
Ticker._raf = true; | |
// static getter / setters: | |
/** | |
* Use the {{#crossLink "Ticker/interval:property"}}{{/crossLink}} property instead. | |
* @method _setInterval | |
* @private | |
* @static | |
* @param {Number} interval | |
**/ | |
Ticker._setInterval = function (interval) { | |
Ticker._interval = interval; | |
if (!Ticker._inited) { | |
return; | |
} | |
Ticker._setupTick(); | |
}; | |
// Ticker.setInterval is @deprecated. Remove for 1.1+ | |
Ticker.setInterval = createjs.deprecate( | |
Ticker._setInterval, | |
"Ticker.setInterval" | |
); | |
/** | |
* Use the {{#crossLink "Ticker/interval:property"}}{{/crossLink}} property instead. | |
* @method _getInterval | |
* @private | |
* @static | |
* @return {Number} | |
**/ | |
Ticker._getInterval = function () { | |
return Ticker._interval; | |
}; | |
// Ticker.getInterval is @deprecated. Remove for 1.1+ | |
Ticker.getInterval = createjs.deprecate( | |
Ticker._getInterval, | |
"Ticker.getInterval" | |
); | |
/** | |
* Use the {{#crossLink "Ticker/framerate:property"}}{{/crossLink}} property instead. | |
* @method _setFPS | |
* @private | |
* @static | |
* @param {Number} value | |
**/ | |
Ticker._setFPS = function (value) { | |
Ticker._setInterval(1000 / value); | |
}; | |
// Ticker.setFPS is @deprecated. Remove for 1.1+ | |
Ticker.setFPS = createjs.deprecate(Ticker._setFPS, "Ticker.setFPS"); | |
/** | |
* Use the {{#crossLink "Ticker/framerate:property"}}{{/crossLink}} property instead. | |
* @method _getFPS | |
* @static | |
* @private | |
* @return {Number} | |
**/ | |
Ticker._getFPS = function () { | |
return 1000 / Ticker._interval; | |
}; | |
// Ticker.getFPS is @deprecated. Remove for 1.1+ | |
Ticker.getFPS = createjs.deprecate(Ticker._getFPS, "Ticker.getFPS"); | |
/** | |
* Indicates the target time (in milliseconds) between ticks. Default is 50 (20 FPS). | |
* Note that actual time between ticks may be more than specified depending on CPU load. | |
* This property is ignored if the ticker is using the `RAF` timing mode. | |
* @property interval | |
* @static | |
* @type {Number} | |
**/ | |
/** | |
* Indicates the target frame rate in frames per second (FPS). Effectively just a shortcut to `interval`, where | |
* `framerate == 1000/interval`. | |
* @property framerate | |
* @static | |
* @type {Number} | |
**/ | |
try { | |
Object.defineProperties(Ticker, { | |
interval: { get: Ticker._getInterval, set: Ticker._setInterval }, | |
framerate: { get: Ticker._getFPS, set: Ticker._setFPS }, | |
}); | |
} catch (e) { | |
console.log(e); | |
} | |
// public static methods: | |
/** | |
* Starts the tick. This is called automatically when the first listener is added. | |
* @method init | |
* @static | |
**/ | |
Ticker.init = function () { | |
if (Ticker._inited) { | |
return; | |
} | |
Ticker._inited = true; | |
Ticker._times = []; | |
Ticker._tickTimes = []; | |
Ticker._startTime = Ticker._getTime(); | |
Ticker._times.push((Ticker._lastTime = 0)); | |
Ticker.interval = Ticker._interval; | |
}; | |
/** | |
* Stops the Ticker and removes all listeners. Use init() to restart the Ticker. | |
* @method reset | |
* @static | |
**/ | |
Ticker.reset = function () { | |
if (Ticker._raf) { | |
var f = | |
window.cancelAnimationFrame || | |
window.webkitCancelAnimationFrame || | |
window.mozCancelAnimationFrame || | |
window.oCancelAnimationFrame || | |
window.msCancelAnimationFrame; | |
f && f(Ticker._timerId); | |
} else { | |
clearTimeout(Ticker._timerId); | |
} | |
Ticker.removeAllEventListeners("tick"); | |
Ticker._timerId = Ticker._times = Ticker._tickTimes = null; | |
Ticker._startTime = | |
Ticker._lastTime = | |
Ticker._ticks = | |
Ticker._pausedTime = | |
0; | |
Ticker._inited = false; | |
}; | |
/** | |
* Returns the average time spent within a tick. This can vary significantly from the value provided by getMeasuredFPS | |
* because it only measures the time spent within the tick execution stack. | |
* | |
* Example 1: With a target FPS of 20, getMeasuredFPS() returns 20fps, which indicates an average of 50ms between | |
* the end of one tick and the end of the next. However, getMeasuredTickTime() returns 15ms. This indicates that | |
* there may be up to 35ms of "idle" time between the end of one tick and the start of the next. | |
* | |
* Example 2: With a target FPS of 30, {{#crossLink "Ticker/framerate:property"}}{{/crossLink}} returns 10fps, which | |
* indicates an average of 100ms between the end of one tick and the end of the next. However, {{#crossLink "Ticker/getMeasuredTickTime"}}{{/crossLink}} | |
* returns 20ms. This would indicate that something other than the tick is using ~80ms (another script, DOM | |
* rendering, etc). | |
* @method getMeasuredTickTime | |
* @static | |
* @param {Number} [ticks] The number of previous ticks over which to measure the average time spent in a tick. | |
* Defaults to the number of ticks per second. To get only the last tick's time, pass in 1. | |
* @return {Number} The average time spent in a tick in milliseconds. | |
**/ | |
Ticker.getMeasuredTickTime = function (ticks) { | |
var ttl = 0, | |
times = Ticker._tickTimes; | |
if (!times || times.length < 1) { | |
return -1; | |
} | |
// by default, calculate average for the past ~1 second: | |
ticks = Math.min(times.length, ticks || Ticker._getFPS() | 0); | |
for (var i = 0; i < ticks; i++) { | |
ttl += times[i]; | |
} | |
return ttl / ticks; | |
}; | |
/** | |
* Returns the actual frames / ticks per second. | |
* @method getMeasuredFPS | |
* @static | |
* @param {Number} [ticks] The number of previous ticks over which to measure the actual frames / ticks per second. | |
* Defaults to the number of ticks per second. | |
* @return {Number} The actual frames / ticks per second. Depending on performance, this may differ | |
* from the target frames per second. | |
**/ | |
Ticker.getMeasuredFPS = function (ticks) { | |
var times = Ticker._times; | |
if (!times || times.length < 2) { | |
return -1; | |
} | |
// by default, calculate fps for the past ~1 second: | |
ticks = Math.min(times.length - 1, ticks || Ticker._getFPS() | 0); | |
return 1000 / ((times[0] - times[ticks]) / ticks); | |
}; | |
/** | |
* Returns the number of milliseconds that have elapsed since Ticker was initialized via {{#crossLink "Ticker/init"}}. | |
* Returns -1 if Ticker has not been initialized. For example, you could use | |
* this in a time synchronized animation to determine the exact amount of time that has elapsed. | |
* @method getTime | |
* @static | |
* @param {Boolean} [runTime=false] If true only time elapsed while Ticker was not paused will be returned. | |
* If false, the value returned will be total time elapsed since the first tick event listener was added. | |
* @return {Number} Number of milliseconds that have elapsed since Ticker was initialized or -1. | |
**/ | |
Ticker.getTime = function (runTime) { | |
return Ticker._startTime | |
? Ticker._getTime() - (runTime ? Ticker._pausedTime : 0) | |
: -1; | |
}; | |
/** | |
* Similar to the {{#crossLink "Ticker/getTime"}}{{/crossLink}} method, but returns the time on the most recent {{#crossLink "Ticker/tick:event"}}{{/crossLink}} | |
* event object. | |
* @method getEventTime | |
* @static | |
* @param runTime {Boolean} [runTime=false] If true, the runTime property will be returned instead of time. | |
* @returns {number} The time or runTime property from the most recent tick event or -1. | |
*/ | |
Ticker.getEventTime = function (runTime) { | |
return Ticker._startTime | |
? (Ticker._lastTime || Ticker._startTime) - | |
(runTime ? Ticker._pausedTime : 0) | |
: -1; | |
}; | |
/** | |
* Returns the number of ticks that have been broadcast by Ticker. | |
* @method getTicks | |
* @static | |
* @param {Boolean} pauseable Indicates whether to include ticks that would have been broadcast | |
* while Ticker was paused. If true only tick events broadcast while Ticker is not paused will be returned. | |
* If false, tick events that would have been broadcast while Ticker was paused will be included in the return | |
* value. The default value is false. | |
* @return {Number} of ticks that have been broadcast. | |
**/ | |
Ticker.getTicks = function (pauseable) { | |
return Ticker._ticks - (pauseable ? Ticker._pausedTicks : 0); | |
}; | |
// private static methods: | |
/** | |
* @method _handleSynch | |
* @static | |
* @private | |
**/ | |
Ticker._handleSynch = function () { | |
Ticker._timerId = null; | |
Ticker._setupTick(); | |
// run if enough time has elapsed, with a little bit of flexibility to be early: | |
if (Ticker._getTime() - Ticker._lastTime >= (Ticker._interval - 1) * 0.97) { | |
Ticker._tick(); | |
} | |
}; | |
/** | |
* @method _handleRAF | |
* @static | |
* @private | |
**/ | |
Ticker._handleRAF = function () { | |
Ticker._timerId = null; | |
Ticker._setupTick(); | |
Ticker._tick(); | |
}; | |
/** | |
* @method _handleTimeout | |
* @static | |
* @private | |
**/ | |
Ticker._handleTimeout = function () { | |
Ticker._timerId = null; | |
Ticker._setupTick(); | |
Ticker._tick(); | |
}; | |
/** | |
* @method _setupTick | |
* @static | |
* @private | |
**/ | |
Ticker._setupTick = function () { | |
if (Ticker._timerId != null) { | |
return; | |
} // avoid duplicates | |
var mode = Ticker.timingMode; | |
if (mode == Ticker.RAF_SYNCHED || mode == Ticker.RAF) { | |
var f = | |
window.requestAnimationFrame || | |
window.webkitRequestAnimationFrame || | |
window.mozRequestAnimationFrame || | |
window.oRequestAnimationFrame || | |
window.msRequestAnimationFrame; | |
if (f) { | |
Ticker._timerId = f( | |
mode == Ticker.RAF ? Ticker._handleRAF : Ticker._handleSynch | |
); | |
Ticker._raf = true; | |
return; | |
} | |
} | |
Ticker._raf = false; | |
Ticker._timerId = setTimeout(Ticker._handleTimeout, Ticker._interval); | |
}; | |
/** | |
* @method _tick | |
* @static | |
* @private | |
**/ | |
Ticker._tick = function () { | |
var paused = Ticker.paused; | |
var time = Ticker._getTime(); | |
var elapsedTime = time - Ticker._lastTime; | |
Ticker._lastTime = time; | |
Ticker._ticks++; | |
if (paused) { | |
Ticker._pausedTicks++; | |
Ticker._pausedTime += elapsedTime; | |
} | |
if (Ticker.hasEventListener("tick")) { | |
var event = new createjs.Event("tick"); | |
var maxDelta = Ticker.maxDelta; | |
event.delta = maxDelta && elapsedTime > maxDelta ? maxDelta : elapsedTime; | |
event.paused = paused; | |
event.time = time; | |
event.runTime = time - Ticker._pausedTime; | |
Ticker.dispatchEvent(event); | |
} | |
Ticker._tickTimes.unshift(Ticker._getTime() - time); | |
while (Ticker._tickTimes.length > 100) { | |
Ticker._tickTimes.pop(); | |
} | |
Ticker._times.unshift(time); | |
while (Ticker._times.length > 100) { | |
Ticker._times.pop(); | |
} | |
}; | |
/** | |
* @method _getTime | |
* @static | |
* @private | |
**/ | |
var w = window, | |
now = | |
w.performance.now || | |
w.performance.mozNow || | |
w.performance.msNow || | |
w.performance.oNow || | |
w.performance.webkitNow; | |
Ticker._getTime = function () { | |
return ( | |
((now && now.call(w.performance)) || new Date().getTime()) - | |
Ticker._startTime | |
); | |
}; | |
createjs.Ticker = Ticker; | |
})(); | |
//############################################################################## | |
// VideoBuffer.js | |
//############################################################################## | |
this.createjs = this.createjs || {}; | |
(function () { | |
"use strict"; | |
// constructor: | |
/** | |
* When an HTML video seeks, including when looping, there is an indeterminate period before a new frame is available. | |
* This can result in the video blinking or flashing when it is drawn to a canvas. The VideoBuffer class resolves | |
* this issue by drawing each frame to an off-screen canvas and preserving the prior frame during a seek. | |
* | |
* var myBuffer = new createjs.VideoBuffer(myVideo); | |
* var myBitmap = new Bitmap(myBuffer); | |
* | |
* @class VideoBuffer | |
* @param {HTMLVideoElement} video The HTML video element to buffer. | |
* @constructor | |
**/ | |
function VideoBuffer(video) { | |
// private properties: | |
/** | |
* Used by Bitmap to determine when the video buffer is ready to be drawn. Not intended for general use. | |
* @property readyState | |
* @protected | |
* @type {Number} | |
* @default 0 | |
**/ | |
this.readyState = video.readyState; | |
/** | |
* @property _video | |
* @protected | |
* @type {HTMLVideoElement} | |
* @default 0 | |
**/ | |
this._video = video; | |
/** | |
* @property _canvas | |
* @protected | |
* @type {HTMLCanvasElement} | |
* @default 0 | |
**/ | |
this._canvas = null; | |
/** | |
* @property _lastTime | |
* @protected | |
* @type {Number} | |
* @default -1 | |
**/ | |
this._lastTime = -1; | |
if (this.readyState < 2) { | |
video.addEventListener("canplaythrough", this._videoReady.bind(this)); | |
} //once:true isn't supported everywhere, but its a non-critical optimization here. | |
} | |
var p = VideoBuffer.prototype; | |
// public methods: | |
/** | |
* Gets an HTML canvas element showing the current video frame, or the previous frame if in a seek / loop. | |
* Primarily for use by {{#crossLink "Bitmap"}}{{/crossLink}}. | |
* @method getImage | |
**/ | |
p.getImage = function () { | |
if (this.readyState < 2) { | |
return; | |
} | |
var canvas = this._canvas, | |
video = this._video; | |
if (!canvas) { | |
canvas = this._canvas = createjs.createCanvas | |
? createjs.createCanvas() | |
: document.createElement("canvas"); | |
canvas.width = video.videoWidth; | |
canvas.height = video.videoHeight; | |
} | |
if (video.readyState >= 2 && video.currentTime !== this._lastTime) { | |
var ctx = canvas.getContext("2d"); | |
ctx.clearRect(0, 0, canvas.width, canvas.height); | |
ctx.drawImage(video, 0, 0, canvas.width, canvas.height); | |
this._lastTime = video.currentTime; | |
} | |
return canvas; | |
}; | |
// private methods: | |
/** | |
* @method _videoReady | |
* @protected | |
**/ | |
p._videoReady = function () { | |
this.readyState = 2; | |
}; | |
createjs.VideoBuffer = VideoBuffer; | |
})(); | |
//############################################################################## | |
// MouseEvent.js | |
//############################################################################## | |
this.createjs = this.createjs || {}; | |
(function () { | |
"use strict"; | |
// constructor: | |
/** | |
* Passed as the parameter to all mouse/pointer/touch related events. For a listing of mouse events and their properties, | |
* see the {{#crossLink "DisplayObject"}}{{/crossLink}} and {{#crossLink "Stage"}}{{/crossLink}} event listings. | |
* @class MouseEvent | |
* @param {String} type The event type. | |
* @param {Boolean} bubbles Indicates whether the event will bubble through the display list. | |
* @param {Boolean} cancelable Indicates whether the default behaviour of this event can be cancelled. | |
* @param {Number} stageX The normalized x position relative to the stage. | |
* @param {Number} stageY The normalized y position relative to the stage. | |
* @param {MouseEvent} nativeEvent The native DOM event related to this mouse event. | |
* @param {Number} pointerID The unique id for the pointer. | |
* @param {Boolean} primary Indicates whether this is the primary pointer in a multitouch environment. | |
* @param {Number} rawX The raw x position relative to the stage. | |
* @param {Number} rawY The raw y position relative to the stage. | |
* @param {DisplayObject} relatedTarget The secondary target for the event. | |
* @extends Event | |
* @constructor | |
**/ | |
function MouseEvent( | |
type, | |
bubbles, | |
cancelable, | |
stageX, | |
stageY, | |
nativeEvent, | |
pointerID, | |
primary, | |
rawX, | |
rawY, | |
relatedTarget | |
) { | |
this.Event_constructor(type, bubbles, cancelable); | |
// public properties: | |
/** | |
* The normalized x position on the stage. This will always be within the range 0 to stage width. | |
* @property stageX | |
* @type Number | |
*/ | |
this.stageX = stageX; | |
/** | |
* The normalized y position on the stage. This will always be within the range 0 to stage height. | |
* @property stageY | |
* @type Number | |
**/ | |
this.stageY = stageY; | |
/** | |
* The raw x position relative to the stage. Normally this will be the same as the stageX value, unless | |
* stage.mouseMoveOutside is true and the pointer is outside of the stage bounds. | |
* @property rawX | |
* @type Number | |
*/ | |
this.rawX = rawX == null ? stageX : rawX; | |
/** | |
* The raw y position relative to the stage. Normally this will be the same as the stageY value, unless | |
* stage.mouseMoveOutside is true and the pointer is outside of the stage bounds. | |
* @property rawY | |
* @type Number | |
*/ | |
this.rawY = rawY == null ? stageY : rawY; | |
/** | |
* The native MouseEvent generated by the browser. The properties and API for this | |
* event may differ between browsers. This property will be null if the | |
* EaselJS property was not directly generated from a native MouseEvent. | |
* @property nativeEvent | |
* @type HtmlMouseEvent | |
* @default null | |
**/ | |
this.nativeEvent = nativeEvent; | |
/** | |
* The unique id for the pointer (touch point or cursor). This will be either -1 for the mouse, or the system | |
* supplied id value. | |
* @property pointerID | |
* @type {Number} | |
*/ | |
this.pointerID = pointerID; | |
/** | |
* Indicates whether this is the primary pointer in a multitouch environment. This will always be true for the mouse. | |
* For touch pointers, the first pointer in the current stack will be considered the primary pointer. | |
* @property primary | |
* @type {Boolean} | |
*/ | |
this.primary = !!primary; | |
/** | |
* The secondary target for the event, if applicable. This is used for mouseout/rollout | |
* events to indicate the object that the mouse entered from, mouseover/rollover for the object the mouse exited, | |
* and stagemousedown/stagemouseup events for the object that was the under the cursor, if any. | |
* | |
* Only valid interaction targets will be returned (ie. objects with mouse listeners or a cursor set). | |
* @property relatedTarget | |
* @type {DisplayObject} | |
*/ | |
this.relatedTarget = relatedTarget; | |
} | |
var p = createjs.extend(MouseEvent, createjs.Event); | |
// TODO: deprecated | |
// p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details. | |
// getter / setters: | |
/** | |
* Returns the x position of the mouse in the local coordinate system of the current target (ie. the dispatcher). | |
* @property localX | |
* @type {Number} | |
* @readonly | |
*/ | |
p._get_localX = function () { | |
return this.currentTarget.globalToLocal(this.rawX, this.rawY).x; | |
}; | |
/** | |
* Returns the y position of the mouse in the local coordinate system of the current target (ie. the dispatcher). | |
* @property localY | |
* @type {Number} | |
* @readonly | |
*/ | |
p._get_localY = function () { | |
return this.currentTarget.globalToLocal(this.rawX, this.rawY).y; | |
}; | |
/** | |
* Indicates whether the event was generated by a touch input (versus a mouse input). | |
* @property isTouch | |
* @type {Boolean} | |
* @readonly | |
*/ | |
p._get_isTouch = function () { | |
return this.pointerID !== -1; | |
}; | |
try { | |
Object.defineProperties(p, { | |
localX: { get: p._get_localX }, | |
localY: { get: p._get_localY }, | |
isTouch: { get: p._get_isTouch }, | |
}); | |
} catch (e) {} // TODO: use Log | |
// public methods: | |
/** | |
* Returns a clone of the MouseEvent instance. | |
* @method clone | |
* @return {MouseEvent} a clone of the MouseEvent instance. | |
**/ | |
p.clone = function () { | |
return new MouseEvent( | |
this.type, | |
this.bubbles, | |
this.cancelable, | |
this.stageX, | |
this.stageY, | |
this.nativeEvent, | |
this.pointerID, | |
this.primary, | |
this.rawX, | |
this.rawY | |
); | |
}; | |
/** | |
* Returns a string representation of this object. | |
* @method toString | |
* @return {String} a string representation of the instance. | |
**/ | |
p.toString = function () { | |
return ( | |
"[MouseEvent (type=" + | |
this.type + | |
" stageX=" + | |
this.stageX + | |
" stageY=" + | |
this.stageY + | |
")]" | |
); | |
}; | |
createjs.MouseEvent = createjs.promote(MouseEvent, "Event"); | |
})(); | |
//############################################################################## | |
// Matrix2D.js | |
//############################################################################## | |
this.createjs = this.createjs || {}; | |
(function () { | |
"use strict"; | |
// constructor: | |
/** | |
* Represents an affine transformation matrix, and provides tools for constructing and concatenating matrices. | |
* | |
* This matrix can be visualized as: | |
* | |
* [ a c tx | |
* b d ty | |
* 0 0 1 ] | |
* | |
* Note the locations of b and c. | |
* | |
* @class Matrix2D | |
* @param {Number} [a=1] Specifies the a property for the new matrix. | |
* @param {Number} [b=0] Specifies the b property for the new matrix. | |
* @param {Number} [c=0] Specifies the c property for the new matrix. | |
* @param {Number} [d=1] Specifies the d property for the new matrix. | |
* @param {Number} [tx=0] Specifies the tx property for the new matrix. | |
* @param {Number} [ty=0] Specifies the ty property for the new matrix. | |
* @constructor | |
**/ | |
function Matrix2D(a, b, c, d, tx, ty) { | |
this.setValues(a, b, c, d, tx, ty); | |
// public properties: | |
// assigned in the setValues method. | |
/** | |
* Position (0, 0) in a 3x3 affine transformation matrix. | |
* @property a | |
* @type Number | |
**/ | |
/** | |
* Position (0, 1) in a 3x3 affine transformation matrix. | |
* @property b | |
* @type Number | |
**/ | |
/** | |
* Position (1, 0) in a 3x3 affine transformation matrix. | |
* @property c | |
* @type Number | |
**/ | |
/** | |
* Position (1, 1) in a 3x3 affine transformation matrix. | |
* @property d | |
* @type Number | |
**/ | |
/** | |
* Position (2, 0) in a 3x3 affine transformation matrix. | |
* @property tx | |
* @type Number | |
**/ | |
/** | |
* Position (2, 1) in a 3x3 affine transformation matrix. | |
* @property ty | |
* @type Number | |
**/ | |
} | |
var p = Matrix2D.prototype; | |
// constants: | |
/** | |
* Multiplier for converting degrees to radians. Used internally by Matrix2D. | |
* @property DEG_TO_RAD | |
* @static | |
* @final | |
* @type Number | |
* @readonly | |
**/ | |
Matrix2D.DEG_TO_RAD = Math.PI / 180; | |
// static public properties: | |
/** | |
* An identity matrix, representing a null transformation. | |
* @property identity | |
* @static | |
* @type Matrix2D | |
* @readonly | |
**/ | |
Matrix2D.identity = null; // set at bottom of class definition. | |
// public methods: | |
/** | |
* Sets the specified values on this instance. | |
* @method setValues | |
* @param {Number} [a=1] Specifies the a property for the new matrix. | |
* @param {Number} [b=0] Specifies the b property for the new matrix. | |
* @param {Number} [c=0] Specifies the c property for the new matrix. | |
* @param {Number} [d=1] Specifies the d property for the new matrix. | |
* @param {Number} [tx=0] Specifies the tx property for the new matrix. | |
* @param {Number} [ty=0] Specifies the ty property for the new matrix. | |
* @return {Matrix2D} This instance. Useful for chaining method calls. | |
*/ | |
p.setValues = function (a, b, c, d, tx, ty) { | |
// don't forget to update docs in the constructor if these change: | |
this.a = a == null ? 1 : a; | |
this.b = b || 0; | |
this.c = c || 0; | |
this.d = d == null ? 1 : d; | |
this.tx = tx || 0; | |
this.ty = ty || 0; | |
return this; | |
}; | |
/** | |
* Appends the specified matrix properties to this matrix. All parameters are required. | |
* This is the equivalent of multiplying `(this matrix) * (specified matrix)`. | |
* @method append | |
* @param {Number} a | |
* @param {Number} b | |
* @param {Number} c | |
* @param {Number} d | |
* @param {Number} tx | |
* @param {Number} ty | |
* @return {Matrix2D} This matrix. Useful for chaining method calls. | |
**/ | |
p.append = function (a, b, c, d, tx, ty) { | |
var a1 = this.a; | |
var b1 = this.b; | |
var c1 = this.c; | |
var d1 = this.d; | |
if (a != 1 || b != 0 || c != 0 || d != 1) { | |
this.a = a1 * a + c1 * b; | |
this.b = b1 * a + d1 * b; | |
this.c = a1 * c + c1 * d; | |
this.d = b1 * c + d1 * d; | |
} | |
this.tx = a1 * tx + c1 * ty + this.tx; | |
this.ty = b1 * tx + d1 * ty + this.ty; | |
return this; | |
}; | |
/** | |
* Prepends the specified matrix properties to this matrix. | |
* This is the equivalent of multiplying `(specified matrix) * (this matrix)`. | |
* All parameters are required. | |
* @method prepend | |
* @param {Number} a | |
* @param {Number} b | |
* @param {Number} c | |
* @param {Number} d | |
* @param {Number} tx | |
* @param {Number} ty | |
* @return {Matrix2D} This matrix. Useful for chaining method calls. | |
**/ | |
p.prepend = function (a, b, c, d, tx, ty) { | |
var a1 = this.a; | |
var c1 = this.c; | |
var tx1 = this.tx; | |
this.a = a * a1 + c * this.b; | |
this.b = b * a1 + d * this.b; | |
this.c = a * c1 + c * this.d; | |
this.d = b * c1 + d * this.d; | |
this.tx = a * tx1 + c * this.ty + tx; | |
this.ty = b * tx1 + d * this.ty + ty; | |
return this; | |
}; | |
/** | |
* Appends the specified matrix to this matrix. | |
* This is the equivalent of multiplying `(this matrix) * (specified matrix)`. | |
* @method appendMatrix | |
* @param {Matrix2D} matrix | |
* @return {Matrix2D} This matrix. Useful for chaining method calls. | |
**/ | |
p.appendMatrix = function (matrix) { | |
return this.append( | |
matrix.a, | |
matrix.b, | |
matrix.c, | |
matrix.d, | |
matrix.tx, | |
matrix.ty | |
); | |
}; | |
/** | |
* Prepends the specified matrix to this matrix. | |
* This is the equivalent of multiplying `(specified matrix) * (this matrix)`. | |
* For example, you could calculate the combined transformation for a child object using: | |
* | |
* var o = myDisplayObject; | |
* var mtx = o.getMatrix(); | |
* while (o = o.parent) { | |
* // prepend each parent's transformation in turn: | |
* o.prependMatrix(o.getMatrix()); | |
* } | |
* @method prependMatrix | |
* @param {Matrix2D} matrix | |
* @return {Matrix2D} This matrix. Useful for chaining method calls. | |
**/ | |
p.prependMatrix = function (matrix) { | |
return this.prepend( | |
matrix.a, | |
matrix.b, | |
matrix.c, | |
matrix.d, | |
matrix.tx, | |
matrix.ty | |
); | |
}; | |
/** | |
* Generates matrix properties from the specified display object transform properties, and appends them to this matrix. | |
* For example, you can use this to generate a matrix representing the transformations of a display object: | |
* | |
* var mtx = new createjs.Matrix2D(); | |
* mtx.appendTransform(o.x, o.y, o.scaleX, o.scaleY, o.rotation); | |
* @method appendTransform | |
* @param {Number} x | |
* @param {Number} y | |
* @param {Number} scaleX | |
* @param {Number} scaleY | |
* @param {Number} rotation | |
* @param {Number} skewX | |
* @param {Number} skewY | |
* @param {Number} regX Optional. | |
* @param {Number} regY Optional. | |
* @return {Matrix2D} This matrix. Useful for chaining method calls. | |
**/ | |
p.appendTransform = function ( | |
x, | |
y, | |
scaleX, | |
scaleY, | |
rotation, | |
skewX, | |
skewY, | |
regX, | |
regY | |
) { | |
if (rotation % 360) { | |
var r = rotation * Matrix2D.DEG_TO_RAD; | |
var cos = Math.cos(r); | |
var sin = Math.sin(r); | |
} else { | |
cos = 1; | |
sin = 0; | |
} | |
if (skewX || skewY) { | |
// TODO: can this be combined into a single append operation? | |
skewX *= Matrix2D.DEG_TO_RAD; | |
skewY *= Matrix2D.DEG_TO_RAD; | |
this.append( | |
Math.cos(skewY), | |
Math.sin(skewY), | |
-Math.sin(skewX), | |
Math.cos(skewX), | |
x, | |
y | |
); | |
this.append( | |
cos * scaleX, | |
sin * scaleX, | |
-sin * scaleY, | |
cos * scaleY, | |
0, | |
0 | |
); | |
} else { | |
this.append( | |
cos * scaleX, | |
sin * scaleX, | |
-sin * scaleY, | |
cos * scaleY, | |
x, | |
y | |
); | |
} | |
if (regX || regY) { | |
// append the registration offset: | |
this.tx -= regX * this.a + regY * this.c; | |
this.ty -= regX * this.b + regY * this.d; | |
} | |
return this; | |
}; | |
/** | |
* Generates matrix properties from the specified display object transform properties, and prepends them to this matrix. | |
* For example, you could calculate the combined transformation for a child object using: | |
* | |
* var o = myDisplayObject; | |
* var mtx = new createjs.Matrix2D(); | |
* do { | |
* // prepend each parent's transformation in turn: | |
* mtx.prependTransform(o.x, o.y, o.scaleX, o.scaleY, o.rotation, o.skewX, o.skewY, o.regX, o.regY); | |
* } while (o = o.parent); | |
* | |
* Note that the above example would not account for {{#crossLink "DisplayObject/transformMatrix:property"}}{{/crossLink}} | |
* values. See {{#crossLink "Matrix2D/prependMatrix"}}{{/crossLink}} for an example that does. | |
* @method prependTransform | |
* @param {Number} x | |
* @param {Number} y | |
* @param {Number} scaleX | |
* @param {Number} scaleY | |
* @param {Number} rotation | |
* @param {Number} skewX | |
* @param {Number} skewY | |
* @param {Number} regX Optional. | |
* @param {Number} regY Optional. | |
* @return {Matrix2D} This matrix. Useful for chaining method calls. | |
**/ | |
p.prependTransform = function ( | |
x, | |
y, | |
scaleX, | |
scaleY, | |
rotation, | |
skewX, | |
skewY, | |
regX, | |
regY | |
) { | |
if (rotation % 360) { | |
var r = rotation * Matrix2D.DEG_TO_RAD; | |
var cos = Math.cos(r); | |
var sin = Math.sin(r); | |
} else { | |
cos = 1; | |
sin = 0; | |
} | |
if (regX || regY) { | |
// prepend the registration offset: | |
this.tx -= regX; | |
this.ty -= regY; | |
} | |
if (skewX || skewY) { | |
// TODO: can this be combined into a single prepend operation? | |
skewX *= Matrix2D.DEG_TO_RAD; | |
skewY *= Matrix2D.DEG_TO_RAD; | |
this.prepend( | |
cos * scaleX, | |
sin * scaleX, | |
-sin * scaleY, | |
cos * scaleY, | |
0, | |
0 | |
); | |
this.prepend( | |
Math.cos(skewY), | |
Math.sin(skewY), | |
-Math.sin(skewX), | |
Math.cos(skewX), | |
x, | |
y | |
); | |
} else { | |
this.prepend( | |
cos * scaleX, | |
sin * scaleX, | |
-sin * scaleY, | |
cos * scaleY, | |
x, | |
y | |
); | |
} | |
return this; | |
}; | |
/** | |
* Applies a clockwise rotation transformation to the matrix. | |
* @method rotate | |
* @param {Number} angle The angle to rotate by, in degrees. To use a value in radians, multiply it by `180/Math.PI`. | |
* @return {Matrix2D} This matrix. Useful for chaining method calls. | |
**/ | |
p.rotate = function (angle) { | |
angle = angle * Matrix2D.DEG_TO_RAD; | |
var cos = Math.cos(angle); | |
var sin = Math.sin(angle); | |
var a1 = this.a; | |
var b1 = this.b; | |
this.a = a1 * cos + this.c * sin; | |
this.b = b1 * cos + this.d * sin; | |
this.c = -a1 * sin + this.c * cos; | |
this.d = -b1 * sin + this.d * cos; | |
return this; | |
}; | |
/** | |
* Applies a skew transformation to the matrix. | |
* @method skew | |
* @param {Number} skewX The amount to skew horizontally in degrees. To use a value in radians, multiply it by `180/Math.PI`. | |
* @param {Number} skewY The amount to skew vertically in degrees. | |
* @return {Matrix2D} This matrix. Useful for chaining method calls. | |
*/ | |
p.skew = function (skewX, skewY) { | |
skewX = skewX * Matrix2D.DEG_TO_RAD; | |
skewY = skewY * Matrix2D.DEG_TO_RAD; | |
this.append( | |
Math.cos(skewY), | |
Math.sin(skewY), | |
-Math.sin(skewX), | |
Math.cos(skewX), | |
0, | |
0 | |
); | |
return this; | |
}; | |
/** | |
* Applies a scale transformation to the matrix. | |
* @method scale | |
* @param {Number} x The amount to scale horizontally. E.G. a value of 2 will double the size in the X direction, and 0.5 will halve it. | |
* @param {Number} y The amount to scale vertically. | |
* @return {Matrix2D} This matrix. Useful for chaining method calls. | |
**/ | |
p.scale = function (x, y) { | |
this.a *= x; | |
this.b *= x; | |
this.c *= y; | |
this.d *= y; | |
//this.tx *= x; | |
//this.ty *= y; | |
return this; | |
}; | |
/** | |
* Translates the matrix on the x and y axes. | |
* @method translate | |
* @param {Number} x | |
* @param {Number} y | |
* @return {Matrix2D} This matrix. Useful for chaining method calls. | |
**/ | |
p.translate = function (x, y) { | |
this.tx += this.a * x + this.c * y; | |
this.ty += this.b * x + this.d * y; | |
return this; | |
}; | |
/** | |
* Sets the properties of the matrix to those of an identity matrix (one that applies a null transformation). | |
* @method identity | |
* @return {Matrix2D} This matrix. Useful for chaining method calls. | |
**/ | |
p.identity = function () { | |
this.a = this.d = 1; | |
this.b = this.c = this.tx = this.ty = 0; | |
return this; | |
}; | |
/** | |
* Inverts the matrix, causing it to perform the opposite transformation. | |
* @method invert | |
* @return {Matrix2D} This matrix. Useful for chaining method calls. | |
**/ | |
p.invert = function () { | |
var a1 = this.a; | |
var b1 = this.b; | |
var c1 = this.c; | |
var d1 = this.d; | |
var tx1 = this.tx; | |
var n = a1 * d1 - b1 * c1; | |
this.a = d1 / n; | |
this.b = -b1 / n; | |
this.c = -c1 / n; | |
this.d = a1 / n; | |
this.tx = (c1 * this.ty - d1 * tx1) / n; | |
this.ty = -(a1 * this.ty - b1 * tx1) / n; | |
return this; | |
}; | |
/** | |
* Returns true if the matrix is an identity matrix. | |
* @method isIdentity | |
* @return {Boolean} | |
**/ | |
p.isIdentity = function () { | |
return ( | |
this.tx === 0 && | |
this.ty === 0 && | |
this.a === 1 && | |
this.b === 0 && | |
this.c === 0 && | |
this.d === 1 | |
); | |
}; | |
/** | |
* Returns true if this matrix is equal to the specified matrix (all property values are equal). | |
* @method equals | |
* @param {Matrix2D} matrix The matrix to compare. | |
* @return {Boolean} | |
**/ | |
p.equals = function (matrix) { | |
return ( | |
this.tx === matrix.tx && | |
this.ty === matrix.ty && | |
this.a === matrix.a && | |
this.b === matrix.b && | |
this.c === matrix.c && | |
this.d === matrix.d | |
); | |
}; | |
/** | |
* Transforms a point according to this matrix. | |
* @method transformPoint | |
* @param {Number} x The x component of the point to transform. | |
* @param {Number} y The y component of the point to transform. | |
* @param {Point | Object} [pt] An object to copy the result into. If omitted a generic object with x/y properties will be returned. | |
* @return {Point} This matrix. Useful for chaining method calls. | |
**/ | |
p.transformPoint = function (x, y, pt) { | |
pt = pt || {}; | |
pt.x = x * this.a + y * this.c + this.tx; | |
pt.y = x * this.b + y * this.d + this.ty; | |
return pt; | |
}; | |
/** | |
* Decomposes the matrix into transform properties (x, y, scaleX, scaleY, and rotation). Note that these values | |
* may not match the transform properties you used to generate the matrix, though they will produce the same visual | |
* results. | |
* @method decompose | |
* @param {Object} target The object to apply the transform properties to. If null, then a new object will be returned. | |
* @return {Object} The target, or a new generic object with the transform properties applied. | |
*/ | |
p.decompose = function (target) { | |
// TODO: it would be nice to be able to solve for whether the matrix can be decomposed into only scale/rotation even when scale is negative | |
if (target == null) { | |
target = {}; | |
} | |
target.x = this.tx; | |
target.y = this.ty; | |
target.scaleX = Math.sqrt(this.a * this.a + this.b * this.b); | |
target.scaleY = Math.sqrt(this.c * this.c + this.d * this.d); | |
var skewX = Math.atan2(-this.c, this.d); | |
var skewY = Math.atan2(this.b, this.a); | |
var delta = Math.abs(1 - skewX / skewY); | |
if (delta < 0.00001) { | |
// effectively identical, can use rotation: | |
target.rotation = skewY / Matrix2D.DEG_TO_RAD; | |
if (this.a < 0 && this.d >= 0) { | |
target.rotation += target.rotation <= 0 ? 180 : -180; | |
} | |
target.skewX = target.skewY = 0; | |
} else { | |
target.skewX = skewX / Matrix2D.DEG_TO_RAD; | |
target.skewY = skewY / Matrix2D.DEG_TO_RAD; | |
} | |
return target; | |
}; | |
/** | |
* Copies all properties from the specified matrix to this matrix. | |
* @method copy | |
* @param {Matrix2D} matrix The matrix to copy properties from. | |
* @return {Matrix2D} This matrix. Useful for chaining method calls. | |
*/ | |
p.copy = function (matrix) { | |
return this.setValues( | |
matrix.a, | |
matrix.b, | |
matrix.c, | |
matrix.d, | |
matrix.tx, | |
matrix.ty | |
); | |
}; | |
/** | |
* Returns a clone of the Matrix2D instance. | |
* @method clone | |
* @return {Matrix2D} a clone of the Matrix2D instance. | |
**/ | |
p.clone = function () { | |
return new Matrix2D(this.a, this.b, this.c, this.d, this.tx, this.ty); | |
}; | |
/** | |
* Returns a string representation of this object. | |
* @method toString | |
* @return {String} a string representation of the instance. | |
**/ | |
p.toString = function () { | |
return ( | |
"[Matrix2D (a=" + | |
this.a + | |
" b=" + | |
this.b + | |
" c=" + | |
this.c + | |
" d=" + | |
this.d + | |
" tx=" + | |
this.tx + | |
" ty=" + | |
this.ty + | |
")]" | |
); | |
}; | |
// this has to be populated after the class is defined: | |
Matrix2D.identity = new Matrix2D(); | |
createjs.Matrix2D = Matrix2D; | |
})(); | |
//############################################################################## | |
// DisplayProps.js | |
//############################################################################## | |
this.createjs = this.createjs || {}; | |
(function () { | |
"use strict"; | |
/** | |
* Used for calculating and encapsulating display related properties. | |
* @class DisplayProps | |
* @param {Number} [visible=true] Visible value. | |
* @param {Number} [alpha=1] Alpha value. | |
* @param {Number} [shadow=null] A Shadow instance or null. | |
* @param {Number} [compositeOperation=null] A compositeOperation value or null. | |
* @param {Number} [matrix] A transformation matrix. Defaults to a new identity matrix. | |
* @constructor | |
**/ | |
function DisplayProps(visible, alpha, shadow, compositeOperation, matrix) { | |
this.setValues(visible, alpha, shadow, compositeOperation, matrix); | |
// public properties: | |
// assigned in the setValues method. | |
/** | |
* Property representing the alpha that will be applied to a display object. | |
* @property alpha | |
* @type Number | |
**/ | |
/** | |
* Property representing the shadow that will be applied to a display object. | |
* @property shadow | |
* @type Shadow | |
**/ | |
/** | |
* Property representing the compositeOperation that will be applied to a display object. | |
* You can find a list of valid composite operations at: | |
* <a href="https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Compositing">https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Compositing</a> | |
* @property compositeOperation | |
* @type String | |
**/ | |
/** | |
* Property representing the value for visible that will be applied to a display object. | |
* @property visible | |
* @type Boolean | |
**/ | |
/** | |
* The transformation matrix that will be applied to a display object. | |
* @property matrix | |
* @type Matrix2D | |
**/ | |
} | |
var p = DisplayProps.prototype; | |
// initialization: | |
/** | |
* Reinitializes the instance with the specified values. | |
* @method setValues | |
* @param {Number} [visible=true] Visible value. | |
* @param {Number} [alpha=1] Alpha value. | |
* @param {Number} [shadow=null] A Shadow instance or null. | |
* @param {Number} [compositeOperation=null] A compositeOperation value or null. | |
* @param {Number} [matrix] A transformation matrix. Defaults to an identity matrix. | |
* @return {DisplayProps} This instance. Useful for chaining method calls. | |
* @chainable | |
*/ | |
p.setValues = function (visible, alpha, shadow, compositeOperation, matrix) { | |
this.visible = visible == null ? true : !!visible; | |
this.alpha = alpha == null ? 1 : alpha; | |
this.shadow = shadow; | |
this.compositeOperation = compositeOperation; | |
this.matrix = | |
matrix || | |
(this.matrix && this.matrix.identity()) || | |
new createjs.Matrix2D(); | |
return this; | |
}; | |
// public methods: | |
/** | |
* Appends the specified display properties. This is generally used to apply a child's properties its parent's. | |
* @method append | |
* @param {Boolean} visible desired visible value | |
* @param {Number} alpha desired alpha value | |
* @param {Shadow} shadow desired shadow value | |
* @param {String} compositeOperation desired composite operation value | |
* @param {Matrix2D} [matrix] a Matrix2D instance | |
* @return {DisplayProps} This instance. Useful for chaining method calls. | |
* @chainable | |
*/ | |
p.append = function (visible, alpha, shadow, compositeOperation, matrix) { | |
this.alpha *= alpha; | |
this.shadow = shadow || this.shadow; | |
this.compositeOperation = compositeOperation || this.compositeOperation; | |
this.visible = this.visible && visible; | |
matrix && this.matrix.appendMatrix(matrix); | |
return this; | |
}; | |
/** | |
* Prepends the specified display properties. This is generally used to apply a parent's properties to a child's. | |
* For example, to get the combined display properties that would be applied to a child, you could use: | |
* | |
* var o = myDisplayObject; | |
* var props = new createjs.DisplayProps(); | |
* do { | |
* // prepend each parent's props in turn: | |
* props.prepend(o.visible, o.alpha, o.shadow, o.compositeOperation, o.getMatrix()); | |
* } while (o = o.parent); | |
* | |
* @method prepend | |
* @param {Boolean} visible desired visible value | |
* @param {Number} alpha desired alpha value | |
* @param {Shadow} shadow desired shadow value | |
* @param {String} compositeOperation desired composite operation value | |
* @param {Matrix2D} [matrix] a Matrix2D instance | |
* @return {DisplayProps} This instance. Useful for chaining method calls. | |
* @chainable | |
*/ | |
p.prepend = function (visible, alpha, shadow, compositeOperation, matrix) { | |
this.alpha *= alpha; | |
this.shadow = this.shadow || shadow; | |
this.compositeOperation = this.compositeOperation || compositeOperation; | |
this.visible = this.visible && visible; | |
matrix && this.matrix.prependMatrix(matrix); | |
return this; | |
}; | |
/** | |
* Resets this instance and its matrix to default values. | |
* @method identity | |
* @return {DisplayProps} This instance. Useful for chaining method calls. | |
* @chainable | |
*/ | |
p.identity = function () { | |
this.visible = true; | |
this.alpha = 1; | |
this.shadow = this.compositeOperation = null; | |
this.matrix.identity(); | |
return this; | |
}; | |
/** | |
* Returns a clone of the DisplayProps instance. Clones the associated matrix. | |
* @method clone | |
* @return {DisplayProps} a clone of the DisplayProps instance. | |
**/ | |
p.clone = function () { | |
return new DisplayProps( | |
this.alpha, | |
this.shadow, | |
this.compositeOperation, | |
this.visible, | |
this.matrix.clone() | |
); | |
}; | |
// private methods: | |
createjs.DisplayProps = DisplayProps; | |
})(); | |
//############################################################################## | |
// Point.js | |
//############################################################################## | |
this.createjs = this.createjs || {}; | |
(function () { | |
"use strict"; | |
// constructor: | |
/** | |
* Represents a point on a 2 dimensional x / y coordinate system. | |
* | |
* <h4>Example</h4> | |
* | |
* var point = new createjs.Point(0, 100); | |
* | |
* @class Point | |
* @param {Number} [x=0] X position. | |
* @param {Number} [y=0] Y position. | |
* @constructor | |
**/ | |
function Point(x, y) { | |
this.setValues(x, y); | |
// public properties: | |
// assigned in the setValues method. | |
/** | |
* X position. | |
* @property x | |
* @type Number | |
**/ | |
/** | |
* Y position. | |
* @property y | |
* @type Number | |
**/ | |
} | |
var p = Point.prototype; | |
// public methods: | |
/** | |
* Sets the specified values on this instance. | |
* @method setValues | |
* @param {Number} [x=0] X position. | |
* @param {Number} [y=0] Y position. | |
* @return {Point} This instance. Useful for chaining method calls. | |
* @chainable | |
*/ | |
p.setValues = function (x, y) { | |
this.x = x || 0; | |
this.y = y || 0; | |
return this; | |
}; | |
/** | |
* Copies all properties from the specified point to this point. | |
* @method copy | |
* @param {Point} point The point to copy properties from. | |
* @return {Point} This point. Useful for chaining method calls. | |
* @chainable | |
*/ | |
p.copy = function (point) { | |
this.x = point.x; | |
this.y = point.y; | |
return this; | |
}; | |
/** | |
* Returns a clone of the Point instance. | |
* @method clone | |
* @return {Point} a clone of the Point instance. | |
**/ | |
p.clone = function () { | |
return new Point(this.x, this.y); | |
}; | |
/** | |
* Returns a string representation of this object. | |
* @method toString | |
* @return {String} a string representation of the instance. | |
**/ | |
p.toString = function () { | |
return "[Point (x=" + this.x + " y=" + this.y + ")]"; | |
}; | |
createjs.Point = Point; | |
})(); | |
//############################################################################## | |
// Rectangle.js | |
//############################################################################## | |
this.createjs = this.createjs || {}; | |
(function () { | |
"use strict"; | |
// constructor: | |
/** | |
* Represents a rectangle as defined by the points (x, y) and (x+width, y+height). | |
* | |
* <h4>Example</h4> | |
* | |
* var rect = new createjs.Rectangle(0, 0, 100, 100); | |
* | |
* @class Rectangle | |
* @param {Number} [x=0] X position. | |
* @param {Number} [y=0] Y position. | |
* @param {Number} [width=0] The width of the Rectangle. | |
* @param {Number} [height=0] The height of the Rectangle. | |
* @constructor | |
**/ | |
function Rectangle(x, y, width, height) { | |
this.setValues(x, y, width, height); | |
// public properties: | |
// assigned in the setValues method. | |
/** | |
* X position. | |
* @property x | |
* @type Number | |
**/ | |
/** | |
* Y position. | |
* @property y | |
* @type Number | |
**/ | |
/** | |
* Width. | |
* @property width | |
* @type Number | |
**/ | |
/** | |
* Height. | |
* @property height | |
* @type Number | |
**/ | |
} | |
var p = Rectangle.prototype; | |
// public methods: | |
/** | |
* Sets the specified values on this instance. | |
* @method setValues | |
* @param {Number} [x=0] X position. | |
* @param {Number} [y=0] Y position. | |
* @param {Number} [width=0] The width of the Rectangle. | |
* @param {Number} [height=0] The height of the Rectangle. | |
* @return {Rectangle} This instance. Useful for chaining method calls. | |
* @chainable | |
*/ | |
p.setValues = function (x, y, width, height) { | |
// don't forget to update docs in the constructor if these change: | |
this.x = x || 0; | |
this.y = y || 0; | |
this.width = width || 0; | |
this.height = height || 0; | |
return this; | |
}; | |
/** | |
* Extends the rectangle's bounds to include the described point or rectangle. | |
* @method extend | |
* @param {Number} x X position of the point or rectangle. | |
* @param {Number} y Y position of the point or rectangle. | |
* @param {Number} [width=0] The width of the rectangle. | |
* @param {Number} [height=0] The height of the rectangle. | |
* @return {Rectangle} This instance. Useful for chaining method calls. | |
* @chainable | |
*/ | |
p.extend = function (x, y, width, height) { | |
width = width || 0; | |
height = height || 0; | |
if (x + width > this.x + this.width) { | |
this.width = x + width - this.x; | |
} | |
if (y + height > this.y + this.height) { | |
this.height = y + height - this.y; | |
} | |
if (x < this.x) { | |
this.width += this.x - x; | |
this.x = x; | |
} | |
if (y < this.y) { | |
this.height += this.y - y; | |
this.y = y; | |
} | |
return this; | |
}; | |
/** | |
* Adds the specified padding to the rectangle's bounds. | |
* @method pad | |
* @param {Number} top | |
* @param {Number} left | |
* @param {Number} bottom | |
* @param {Number} right | |
* @return {Rectangle} This instance. Useful for chaining method calls. | |
* @chainable | |
*/ | |
p.pad = function (top, left, bottom, right) { | |
this.x -= left; | |
this.y -= top; | |
this.width += left + right; | |
this.height += top + bottom; | |
return this; | |
}; | |
/** | |
* Copies all properties from the specified rectangle to this rectangle. | |
* @method copy | |
* @param {Rectangle} rectangle The rectangle to copy properties from. | |
* @return {Rectangle} This rectangle. Useful for chaining method calls. | |
* @chainable | |
*/ | |
p.copy = function (rectangle) { | |
return this.setValues( | |
rectangle.x, | |
rectangle.y, | |
rectangle.width, | |
rectangle.height | |
); | |
}; | |
/** | |
* Returns true if this rectangle fully encloses the described point or rectangle. | |
* @method contains | |
* @param {Number} x X position of the point or rectangle. | |
* @param {Number} y Y position of the point or rectangle. | |
* @param {Number} [width=0] The width of the rectangle. | |
* @param {Number} [height=0] The height of the rectangle. | |
* @return {Boolean} True if the described point or rectangle is contained within this rectangle. | |
*/ | |
p.contains = function (x, y, width, height) { | |
width = width || 0; | |
height = height || 0; | |
return ( | |
x >= this.x && | |
x + width <= this.x + this.width && | |
y >= this.y && | |
y + height <= this.y + this.height | |
); | |
}; | |
/** | |
* Returns a new rectangle which contains this rectangle and the specified rectangle. | |
* @method union | |
* @param {Rectangle} rect The rectangle to calculate a union with. | |
* @return {Rectangle} A new rectangle describing the union. | |
*/ | |
p.union = function (rect) { | |
return this.clone().extend(rect.x, rect.y, rect.width, rect.height); | |
}; | |
/** | |
* Returns a new rectangle which describes the intersection (overlap) of this rectangle and the specified rectangle, | |
* or null if they do not intersect. | |
* @method intersection | |
* @param {Rectangle} rect The rectangle to calculate an intersection with. | |
* @return {Rectangle} A new rectangle describing the intersection or null. | |
*/ | |
p.intersection = function (rect) { | |
var x1 = rect.x, | |
y1 = rect.y, | |
x2 = x1 + rect.width, | |
y2 = y1 + rect.height; | |
if (this.x > x1) { | |
x1 = this.x; | |
} | |
if (this.y > y1) { | |
y1 = this.y; | |
} | |
if (this.x + this.width < x2) { | |
x2 = this.x + this.width; | |
} | |
if (this.y + this.height < y2) { | |
y2 = this.y + this.height; | |
} | |
return x2 <= x1 || y2 <= y1 | |
? null | |
: new Rectangle(x1, y1, x2 - x1, y2 - y1); | |
}; | |
/** | |
* Returns true if the specified rectangle intersects (has any overlap) with this rectangle. | |
* @method intersects | |
* @param {Rectangle} rect The rectangle to compare. | |
* @return {Boolean} True if the rectangles intersect. | |
*/ | |
p.intersects = function (rect) { | |
return ( | |
rect.x <= this.x + this.width && | |
this.x <= rect.x + rect.width && | |
rect.y <= this.y + this.height && | |
this.y <= rect.y + rect.height | |
); | |
}; | |
/** | |
* Returns true if the width or height are equal or less than 0. | |
* @method isEmpty | |
* @return {Boolean} True if the rectangle is empty. | |
*/ | |
p.isEmpty = function () { | |
return this.width <= 0 || this.height <= 0; | |
}; | |
/** | |
* Returns a clone of the Rectangle instance. | |
* @method clone | |
* @return {Rectangle} a clone of the Rectangle instance. | |
**/ | |
p.clone = function () { | |
return new Rectangle(this.x, this.y, this.width, this.height); | |
}; | |
/** | |
* Returns a string representation of this object. | |
* @method toString | |
* @return {String} a string representation of the instance. | |
**/ | |
p.toString = function () { | |
return ( | |
"[Rectangle (x=" + | |
this.x + | |
" y=" + | |
this.y + | |
" width=" + | |
this.width + | |
" height=" + | |
this.height + | |
")]" | |
); | |
}; | |
createjs.Rectangle = Rectangle; | |
})(); | |
//############################################################################## | |
// ButtonHelper.js | |
//############################################################################## | |
this.createjs = this.createjs || {}; | |
(function () { | |
"use strict"; | |
// constructor: | |
/** | |
* The ButtonHelper is a helper class to create interactive buttons from {{#crossLink "MovieClip"}}{{/crossLink}} or | |
* {{#crossLink "Sprite"}}{{/crossLink}} instances. This class will intercept mouse events from an object, and | |
* automatically call {{#crossLink "Sprite/gotoAndStop"}}{{/crossLink}} or {{#crossLink "Sprite/gotoAndPlay"}}{{/crossLink}}, | |
* to the respective animation labels, add a pointer cursor, and allows the user to define a hit state frame. | |
* | |
* The ButtonHelper instance does not need to be added to the stage, but a reference should be maintained to prevent | |
* garbage collection. | |
* | |
* Note that over states will not work unless you call {{#crossLink "Stage/enableMouseOver"}}{{/crossLink}}. | |
* | |
* <h4>Example</h4> | |
* | |
* var helper = new createjs.ButtonHelper(myInstance, "out", "over", "down", false, myInstance, "hit"); | |
* myInstance.addEventListener("click", handleClick); | |
* function handleClick(event) { | |
* // Click Happened. | |
* } | |
* | |
* @class ButtonHelper | |
* @param {Sprite|MovieClip} target The instance to manage. | |
* @param {String} [outLabel="out"] The label or animation to go to when the user rolls out of the button. | |
* @param {String} [overLabel="over"] The label or animation to go to when the user rolls over the button. | |
* @param {String} [downLabel="down"] The label or animation to go to when the user presses the button. | |
* @param {Boolean} [play=false] If the helper should call "gotoAndPlay" or "gotoAndStop" on the button when changing | |
* states. | |
* @param {DisplayObject} [hitArea] An optional item to use as the hit state for the button. If this is not defined, | |
* then the button's visible states will be used instead. Note that the same instance as the "target" argument can be | |
* used for the hitState. | |
* @param {String} [hitLabel] The label or animation on the hitArea instance that defines the hitArea bounds. If this is | |
* null, then the default state of the hitArea will be used. * | |
* @constructor | |
*/ | |
function ButtonHelper( | |
target, | |
outLabel, | |
overLabel, | |
downLabel, | |
play, | |
hitArea, | |
hitLabel | |
) { | |
if (!target.addEventListener) { | |
return; | |
} | |
// public properties: | |
/** | |
* The target for this button helper. | |
* @property target | |
* @type MovieClip | Sprite | |
* @readonly | |
**/ | |
this.target = target; | |
/** | |
* The label name or frame number to display when the user mouses out of the target. Defaults to "over". | |
* @property overLabel | |
* @type String | Number | |
**/ | |
this.overLabel = overLabel == null ? "over" : overLabel; | |
/** | |
* The label name or frame number to display when the user mouses over the target. Defaults to "out". | |
* @property outLabel | |
* @type String | Number | |
**/ | |
this.outLabel = outLabel == null ? "out" : outLabel; | |
/** | |
* The label name or frame number to display when the user presses on the target. Defaults to "down". | |
* @property downLabel | |
* @type String | Number | |
**/ | |
this.downLabel = downLabel == null ? "down" : downLabel; | |
/** | |
* If true, then ButtonHelper will call gotoAndPlay, if false, it will use gotoAndStop. Default is false. | |
* @property play | |
* @default false | |
* @type Boolean | |
**/ | |
this.play = play; | |
// private properties | |
/** | |
* @property _isPressed | |
* @type Boolean | |
* @protected | |
**/ | |
this._isPressed = false; | |
/** | |
* @property _isOver | |
* @type Boolean | |
* @protected | |
**/ | |
this._isOver = false; | |
/** | |
* @property _enabled | |
* @type Boolean | |
* @protected | |
**/ | |
this._enabled = false; | |
// setup: | |
target.mouseChildren = false; // prevents issues when children are removed from the display list when state changes. | |
this.enabled = true; | |
this.handleEvent({}); | |
if (hitArea) { | |
if (hitLabel) { | |
hitArea.actionsEnabled = false; | |
hitArea.gotoAndStop && hitArea.gotoAndStop(hitLabel); | |
} | |
target.hitArea = hitArea; | |
} | |
} | |
var p = ButtonHelper.prototype; | |
// getter / setters: | |
/** | |
* Use the {{#crossLink "ButtonHelper/enabled:property"}}{{/crossLink}} property instead. | |
* @method setEnabled | |
* @param {Boolean} value The enabled property to set the instance to. | |
* @[rptected | |
* @protected | |
**/ | |
p._setEnabled = function (value) { | |
if (value == this._enabled) { | |
return; | |
} | |
var o = this.target; | |
this._enabled = value; | |
if (value) { | |
o.cursor = "pointer"; | |
o.addEventListener("rollover", this); | |
o.addEventListener("rollout", this); | |
o.addEventListener("mousedown", this); | |
o.addEventListener("pressup", this); | |
if (o._reset) { | |
o.__reset = o._reset; | |
o._reset = this._reset; | |
} | |
} else { | |
o.cursor = null; | |
o.removeEventListener("rollover", this); | |
o.removeEventListener("rollout", this); | |
o.removeEventListener("mousedown", this); | |
o.removeEventListener("pressup", this); | |
if (o.__reset) { | |
o._reset = o.__reset; | |
delete o.__reset; | |
} | |
} | |
}; | |
// ButtonHelper.setEnabled is @deprecated. Remove for 1.1+ | |
p.setEnabled = createjs.deprecate(p._setEnabled, "ButtonHelper.setEnabled"); | |
/** | |
* Use the {{#crossLink "ButtonHelper/enabled:property"}}{{/crossLink}} property instead. | |
* @method getEnabled | |
* @protected | |
* @return {Boolean} | |
**/ | |
p._getEnabled = function () { | |
return this._enabled; | |
}; | |
// ButtonHelper.getEnabled is @deprecated. Remove for 1.1+ | |
p.getEnabled = createjs.deprecate(p._getEnabled, "ButtonHelper.getEnabled"); | |
/** | |
* Enables or disables the button functionality on the target. | |
* @property enabled | |
* @type {Boolean} | |
**/ | |
try { | |
Object.defineProperties(p, { | |
enabled: { get: p._getEnabled, set: p._setEnabled }, | |
}); | |
} catch (e) {} // TODO: use Log | |
// public methods: | |
/** | |
* Returns a string representation of this object. | |
* @method toString | |
* @return {String} a string representation of the instance. | |
**/ | |
p.toString = function () { | |
return "[ButtonHelper]"; | |
}; | |
// private methods: | |
/** | |
* @method handleEvent | |
* @param {Object} evt The mouse event to handle. | |
* @protected | |
**/ | |
p.handleEvent = function (evt) { | |
var label, | |
t = this.target, | |
type = evt.type; | |
if (type == "mousedown") { | |
this._isPressed = true; | |
label = this.downLabel; | |
} else if (type == "pressup") { | |
this._isPressed = false; | |
label = this._isOver ? this.overLabel : this.outLabel; | |
} else if (type == "rollover") { | |
this._isOver = true; | |
label = this._isPressed ? this.downLabel : this.overLabel; | |
} else { | |
// rollout and default | |
this._isOver = false; | |
label = this._isPressed ? this.overLabel : this.outLabel; | |
} | |
if (this.play) { | |
t.gotoAndPlay && t.gotoAndPlay(label); | |
} else { | |
t.gotoAndStop && t.gotoAndStop(label); | |
} | |
}; | |
/** | |
* Injected into target. Preserves the paused state through a reset. | |
* @method _reset | |
* @protected | |
**/ | |
p._reset = function () { | |
// TODO: explore better ways to handle this issue. This is hacky & disrupts object signatures. | |
var p = this.paused; | |
this.__reset(); | |
this.paused = p; | |
}; | |
createjs.ButtonHelper = ButtonHelper; | |
})(); | |
//############################################################################## | |
// Shadow.js | |
//############################################################################## | |
this.createjs = this.createjs || {}; | |
(function () { | |
"use strict"; | |
// constructor: | |
/** | |
* This class encapsulates the properties required to define a shadow to apply to a {{#crossLink "DisplayObject"}}{{/crossLink}} | |
* via its <code>shadow</code> property. | |
* | |
* <h4>Example</h4> | |
* | |
* myImage.shadow = new createjs.Shadow("#000000", 5, 5, 10); | |
* | |
* @class Shadow | |
* @constructor | |
* @param {String} color The color of the shadow. This can be any valid CSS color value. | |
* @param {Number} offsetX The x offset of the shadow in pixels. | |
* @param {Number} offsetY The y offset of the shadow in pixels. | |
* @param {Number} blur The size of the blurring effect. | |
**/ | |
function Shadow(color, offsetX, offsetY, blur) { | |
// public properties: | |
/** | |
* The color of the shadow. This can be any valid CSS color value. | |
* @property color | |
* @type String | |
* @default null | |
*/ | |
this.color = color || "black"; | |
/** The x offset of the shadow. | |
* @property offsetX | |
* @type Number | |
* @default 0 | |
*/ | |
this.offsetX = offsetX || 0; | |
/** The y offset of the shadow. | |
* @property offsetY | |
* @type Number | |
* @default 0 | |
*/ | |
this.offsetY = offsetY || 0; | |
/** The blur of the shadow. | |
* @property blur | |
* @type Number | |
* @default 0 | |
*/ | |
this.blur = blur || 0; | |
} | |
var p = Shadow.prototype; | |
// static public properties: | |
/** | |
* An identity shadow object (all properties are set to 0). | |
* @property identity | |
* @type Shadow | |
* @static | |
* @final | |
* @readonly | |
**/ | |
Shadow.identity = new Shadow("transparent", 0, 0, 0); | |
// public methods: | |
/** | |
* Returns a string representation of this object. | |
* @method toString | |
* @return {String} a string representation of the instance. | |
**/ | |
p.toString = function () { | |
return "[Shadow]"; | |
}; | |
/** | |
* Returns a clone of this Shadow instance. | |
* @method clone | |
* @return {Shadow} A clone of the current Shadow instance. | |
**/ | |
p.clone = function () { | |
return new Shadow(this.color, this.offsetX, this.offsetY, this.blur); | |
}; | |
createjs.Shadow = Shadow; | |
})(); | |
//############################################################################## | |
// SpriteSheet.js | |
//############################################################################## | |
this.createjs = this.createjs || {}; | |
(function () { | |
"use strict"; | |
// constructor: | |
/** | |
* Encapsulates the properties and methods associated with a sprite sheet. A sprite sheet is a series of images (usually | |
* animation frames) combined into a larger image (or images). For example, an animation consisting of eight 100x100 | |
* images could be combined into a single 400x200 sprite sheet (4 frames across by 2 high). | |
* | |
* The data passed to the SpriteSheet constructor defines: | |
* <ol> | |
* <li> The source image or images to use.</li> | |
* <li> The positions of individual image frames.</li> | |
* <li> Sequences of frames that form named animations. Optional.</li> | |
* <li> The target playback framerate. Optional.</li> | |
* </ol> | |
* <h3>SpriteSheet Format</h3> | |
* SpriteSheets are an object with two required properties (`images` and `frames`), and two optional properties | |
* (`framerate` and `animations`). This makes them easy to define in javascript code, or in JSON. | |
* | |
* <h4>images</h4> | |
* An array of source images. Images can be either an HTMlimage | |
* instance, or a uri to an image. The former is recommended to control preloading. | |
* | |
* images: [image1, "path/to/image2.png"], | |
* | |
* <h4>frames</h4> | |
* Defines the individual frames. There are two supported formats for frame data: | |
* When all of the frames are the same size (in a grid), use an object with `width`, `height`, `regX`, `regY`, | |
* and `count` properties. | |
* | |
* <ul> | |
* <li>`width` & `height` are required and specify the dimensions of the frames</li> | |
* <li>`regX` & `regY` indicate the registration point or "origin" of the frames</li> | |
* <li>`spacing` indicate the spacing between frames</li> | |
* <li>`margin` specify the margin around the image(s)</li> | |
* <li>`count` allows you to specify the total number of frames in the spritesheet; if omitted, this will | |
* be calculated based on the dimensions of the source images and the frames. Frames will be assigned | |
* indexes based on their position in the source images (left to right, top to bottom).</li> | |
* </ul> | |
* | |
* frames: {width:64, height:64, count:20, regX: 32, regY:64, spacing:0, margin:0} | |
* | |
* If the frames are of different sizes, use an array of frame definitions. Each definition is itself an array | |
* with 4 required and 3 optional entries, in the order: | |
* | |
* <ul> | |
* <li>The first four, `x`, `y`, `width`, and `height` are required and define the frame rectangle.</li> | |
* <li>The fifth, `imageIndex`, specifies the index of the source image (defaults to 0)</li> | |
* <li>The last two, `regX` and `regY` specify the registration point of the frame</li> | |
* </ul> | |
* | |
* frames: [ | |
* // x, y, width, height, imageIndex*, regX*, regY* | |
* [64, 0, 96, 64], | |
* [0, 0, 64, 64, 1, 32, 32] | |
* // etc. | |
* ] | |
* | |
* <h4>animations</h4> | |
* Optional. An object defining sequences of frames to play as named animations. Each property corresponds to an | |
* animation of the same name. Each animation must specify the frames to play, and may | |
* also include a relative playback `speed` (ex. 2 would playback at double speed, 0.5 at half), and | |
* the name of the `next` animation to sequence to after it completes. | |
* | |
* There are three formats supported for defining the frames in an animation, which can be mixed and matched as appropriate: | |
* <ol> | |
* <li>for a single frame animation, you can simply specify the frame index | |
* | |
* animations: { | |
* sit: 7 | |
* } | |
* | |
* </li> | |
* <li> | |
* for an animation of consecutive frames, you can use an array with two required, and two optional entries | |
* in the order: `start`, `end`, `next`, and `speed`. This will play the frames from start to end inclusive. | |
* | |
* animations: { | |
* // start, end, next*, speed* | |
* run: [0, 8], | |
* jump: [9, 12, "run", 2] | |
* } | |
* | |
* </li> | |
* <li> | |
* for non-consecutive frames, you can use an object with a `frames` property defining an array of frame | |
* indexes to play in order. The object can also specify `next` and `speed` properties. | |
* | |
* animations: { | |
* walk: { | |
* frames: [1,2,3,3,2,1] | |
* }, | |
* shoot: { | |
* frames: [1,4,5,6], | |
* next: "walk", | |
* speed: 0.5 | |
* } | |
* } | |
* | |
* </li> | |
* </ol> | |
* <strong>Note:</strong> the `speed` property was added in EaselJS 0.7.0. Earlier versions had a `frequency` | |
* property instead, which was the inverse of `speed`. For example, a value of "4" would be 1/4 normal speed in | |
* earlier versions, but is 4x normal speed in EaselJS 0.7.0+. | |
* | |
* <h4>framerate</h4> | |
* Optional. Indicates the default framerate to play this spritesheet at in frames per second. See | |
* {{#crossLink "SpriteSheet/framerate:property"}}{{/crossLink}} for more information. | |
* | |
* framerate: 20 | |
* | |
* Note that the Sprite framerate will only work if the stage update method is provided with the {{#crossLink "Ticker/tick:event"}}{{/crossLink}} | |
* event generated by the {{#crossLink "Ticker"}}{{/crossLink}}. | |
* | |
* createjs.Ticker.on("tick", handleTick); | |
* function handleTick(event) { | |
* stage.update(event); | |
* } | |
* | |
* <h3>Example</h3> | |
* To define a simple sprite sheet, with a single image "sprites.jpg" arranged in a regular 50x50 grid with three | |
* animations: "stand" showing the first frame, "run" looping frame 1-5 inclusive, and "jump" playing frame 6-8 and | |
* sequencing back to run. | |
* | |
* var data = { | |
* images: ["sprites.jpg"], | |
* frames: {width:50, height:50}, | |
* animations: { | |
* stand:0, | |
* run:[1,5], | |
* jump:[6,8,"run"] | |
* } | |
* }; | |
* var spriteSheet = new createjs.SpriteSheet(data); | |
* var animation = new createjs.Sprite(spriteSheet, "run"); | |
* | |
* <h3>Generating SpriteSheet Images</h3> | |
* Spritesheets can be created manually by combining images in PhotoShop, and specifying the frame size or | |
* coordinates manually, however there are a number of tools that facilitate this. | |
* <ul> | |
* <li>Exporting SpriteSheets or HTML5 content from Adobe Flash/Animate supports the EaselJS SpriteSheet format.</li> | |
* <li>The popular <a href="https://www.codeandweb.com/texturepacker/easeljs" target="_blank">Texture Packer</a> has | |
* EaselJS support. | |
* <li>SWF animations in Adobe Flash/Animate can be exported to SpriteSheets using <a href="http://createjs.com/zoe" target="_blank">Zoë</a></li> | |
* </ul> | |
* | |
* <h3>Cross Origin Issues</h3> | |
* <strong>Warning:</strong> Images loaded cross-origin will throw cross-origin security errors when interacted with | |
* using: | |
* <ul> | |
* <li>a mouse</li> | |
* <li>methods such as {{#crossLink "Container/getObjectUnderPoint"}}{{/crossLink}}</li> | |
* <li>Filters (see {{#crossLink "Filter"}}{{/crossLink}})</li> | |
* <li>caching (see {{#crossLink "DisplayObject/cache"}}{{/crossLink}})</li> | |
* </ul> | |
* You can get around this by setting `crossOrigin` property on your images before passing them to EaselJS, or | |
* setting the `crossOrigin` property on PreloadJS' LoadQueue or LoadItems. | |
* | |
* var image = new Image(); | |
* img.crossOrigin="Anonymous"; | |
* img.src = "http://server-with-CORS-support.com/path/to/image.jpg"; | |
* | |
* If you pass string paths to SpriteSheets, they will not work cross-origin. The server that stores the image must | |
* support cross-origin requests, or this will not work. For more information, check out | |
* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS" target="_blank">CORS overview on MDN</a>. | |
* | |
* @class SpriteSheet | |
* @constructor | |
* @param {Object} data An object describing the SpriteSheet data. | |
* @extends EventDispatcher | |
**/ | |
function SpriteSheet(data) { | |
this.EventDispatcher_constructor(); | |
// public properties: | |
/** | |
* Indicates whether all images are finished loading. | |
* @property complete | |
* @type Boolean | |
* @readonly | |
**/ | |
this.complete = true; | |
/** | |
* Specifies the framerate to use by default for Sprite instances using the SpriteSheet. See the Sprite class | |
* {{#crossLink "Sprite/framerate:property"}}{{/crossLink}} for more information. | |
* @property framerate | |
* @type Number | |
**/ | |
this.framerate = 0; | |
// private properties: | |
/** | |
* @property _animations | |
* @protected | |
* @type Array | |
**/ | |
this._animations = null; | |
/** | |
* @property _frames | |
* @protected | |
* @type Array | |
**/ | |
this._frames = null; | |
/** | |
* @property _images | |
* @protected | |
* @type Array | |
**/ | |
this._images = null; | |
/** | |
* @property _data | |
* @protected | |
* @type Object | |
**/ | |
this._data = null; | |
/** | |
* @property _loadCount | |
* @protected | |
* @type Number | |
**/ | |
this._loadCount = 0; | |
// only used for simple frame defs: | |
/** | |
* @property _frameHeight | |
* @protected | |
* @type Number | |
**/ | |
this._frameHeight = 0; | |
/** | |
* @property _frameWidth | |
* @protected | |
* @type Number | |
**/ | |
this._frameWidth = 0; | |
/** | |
* @property _numFrames | |
* @protected | |
* @type Number | |
**/ | |
this._numFrames = 0; | |
/** | |
* @property _regX | |
* @protected | |
* @type Number | |
**/ | |
this._regX = 0; | |
/** | |
* @property _regY | |
* @protected | |
* @type Number | |
**/ | |
this._regY = 0; | |
/** | |
* @property _spacing | |
* @protected | |
* @type Number | |
**/ | |
this._spacing = 0; | |
/** | |
* @property _margin | |
* @protected | |
* @type Number | |
**/ | |
this._margin = 0; | |
// setup: | |
this._parseData(data); | |
} | |
var p = createjs.extend(SpriteSheet, createjs.EventDispatcher); | |
// TODO: deprecated | |
// p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details. | |
// events: | |
/** | |
* Dispatched when all images are loaded. Note that this only fires if the images | |
* were not fully loaded when the sprite sheet was initialized. You should check the complete property | |
* to prior to adding a listener. Ex. | |
* | |
* var sheet = new createjs.SpriteSheet(data); | |
* if (!sheet.complete) { | |
* // not preloaded, listen for the complete event: | |
* sheet.addEventListener("complete", handler); | |
* } | |
* | |
* @event complete | |
* @param {Object} target The object that dispatched the event. | |
* @param {String} type The event type. | |
* @since 0.6.0 | |
*/ | |
/** | |
* Dispatched when getFrame is called with a valid frame index. This is primarily intended for use by {{#crossLink "SpriteSheetBuilder"}}{{/crossLink}} | |
* when doing on-demand rendering. | |
* @event getframe | |
* @param {Number} index The frame index. | |
* @param {Object} frame The frame object that getFrame will return. | |
*/ | |
/** | |
* Dispatched when an image encounters an error. A SpriteSheet will dispatch an error event for each image that | |
* encounters an error, and will still dispatch a {{#crossLink "SpriteSheet/complete:event"}}{{/crossLink}} | |
* event once all images are finished processing, even if an error is encountered. | |
* @event error | |
* @param {String} src The source of the image that failed to load. | |
* @since 0.8.2 | |
*/ | |
// getter / setters: | |
/** | |
* Use the {{#crossLink "SpriteSheet/animations:property"}}{{/crossLink}} property instead. | |
* @method _getAnimations | |
* @protected | |
* @return {Array} | |
**/ | |
p._getAnimations = function () { | |
return this._animations.slice(); | |
}; | |
// SpriteSheet.getAnimations is @deprecated. Remove for 1.1+ | |
p.getAnimations = createjs.deprecate( | |
p._getAnimations, | |
"SpriteSheet.getAnimations" | |
); | |
/** | |
* Returns an array of all available animation names available on this sprite sheet as strings. | |
* @property animations | |
* @type {Array} | |
* @readonly | |
**/ | |
try { | |
Object.defineProperties(p, { | |
animations: { get: p._getAnimations }, | |
}); | |
} catch (e) {} | |
// public methods: | |
/** | |
* Returns the total number of frames in the specified animation, or in the whole sprite | |
* sheet if the animation param is omitted. Returns 0 if the spritesheet relies on calculated frame counts, and | |
* the images have not been fully loaded. | |
* @method getNumFrames | |
* @param {String} animation The name of the animation to get a frame count for. | |
* @return {Number} The number of frames in the animation, or in the entire sprite sheet if the animation param is omitted. | |
*/ | |
p.getNumFrames = function (animation) { | |
if (animation == null) { | |
return this._frames ? this._frames.length : this._numFrames || 0; | |
} else { | |
var data = this._data[animation]; | |
if (data == null) { | |
return 0; | |
} else { | |
return data.frames.length; | |
} | |
} | |
}; | |
/** | |
* Returns an object defining the specified animation. The returned object contains:<UL> | |
* <li>frames: an array of the frame ids in the animation</li> | |
* <li>speed: the playback speed for this animation</li> | |
* <li>name: the name of the animation</li> | |
* <li>next: the default animation to play next. If the animation loops, the name and next property will be the | |
* same.</li> | |
* </UL> | |
* @method getAnimation | |
* @param {String} name The name of the animation to get. | |
* @return {Object} a generic object with frames, speed, name, and next properties. | |
**/ | |
p.getAnimation = function (name) { | |
return this._data[name]; | |
}; | |
/** | |
* Returns an object specifying the image and source rect of the specified frame. The returned object has:<UL> | |
* <li>an image property holding a reference to the image object in which the frame is found</li> | |
* <li>a rect property containing a Rectangle instance which defines the boundaries for the frame within that | |
* image.</li> | |
* <li> A regX and regY property corresponding to the regX/Y values for the frame. | |
* </UL> | |
* @method getFrame | |
* @param {Number} frameIndex The index of the frame. | |
* @return {Object} a generic object with image and rect properties. Returns null if the frame does not exist. | |
**/ | |
p.getFrame = function (frameIndex) { | |
var frame; | |
if (this._frames && (frame = this._frames[frameIndex])) { | |
return frame; | |
} | |
return null; | |
}; | |
/** | |
* Returns a {{#crossLink "Rectangle"}}{{/crossLink}} instance defining the bounds of the specified frame relative | |
* to the origin. For example, a 90 x 70 frame with a regX of 50 and a regY of 40 would return: | |
* | |
* [x=-50, y=-40, width=90, height=70] | |
* | |
* @method getFrameBounds | |
* @param {Number} frameIndex The index of the frame. | |
* @param {Rectangle} [rectangle] A Rectangle instance to copy the values into. By default a new instance is created. | |
* @return {Rectangle} A Rectangle instance. Returns null if the frame does not exist, or the image is not fully loaded. | |
**/ | |
p.getFrameBounds = function (frameIndex, rectangle) { | |
var frame = this.getFrame(frameIndex); | |
return frame | |
? (rectangle || new createjs.Rectangle()).setValues( | |
-frame.regX, | |
-frame.regY, | |
frame.rect.width, | |
frame.rect.height | |
) | |
: null; | |
}; | |
/** | |
* Returns a string representation of this object. | |
* @method toString | |
* @return {String} a string representation of the instance. | |
**/ | |
p.toString = function () { | |
return "[SpriteSheet]"; | |
}; | |
/** | |
* SpriteSheet cannot be cloned. A SpriteSheet can be shared by multiple Sprite instances without cloning it. | |
* @method clone | |
**/ | |
p.clone = function () { | |
throw "SpriteSheet cannot be cloned."; | |
}; | |
// private methods: | |
/** | |
* @method _parseData | |
* @param {Object} data An object describing the SpriteSheet data. | |
* @protected | |
**/ | |
p._parseData = function (data) { | |
var i, l, o, a; | |
if (data == null) { | |
return; | |
} | |
this.framerate = data.framerate || 0; | |
// parse images: | |
if (data.images && (l = data.images.length) > 0) { | |
a = this._images = []; | |
for (i = 0; i < l; i++) { | |
var img = data.images[i]; | |
if (typeof img == "string") { | |
var src = img; | |
img = document.createElement("img"); | |
img.src = src; | |
} | |
a.push(img); | |
if (!img.getContext && !img.naturalWidth) { | |
this._loadCount++; | |
this.complete = false; | |
(function (o, src) { | |
img.onload = function () { | |
o._handleImageLoad(src); | |
}; | |
})(this, src); | |
(function (o, src) { | |
img.onerror = function () { | |
o._handleImageError(src); | |
}; | |
})(this, src); | |
} | |
} | |
} | |
// parse frames: | |
if (data.frames == null) { | |
// nothing | |
} else if (Array.isArray(data.frames)) { | |
this._frames = []; | |
a = data.frames; | |
for (i = 0, l = a.length; i < l; i++) { | |
var arr = a[i]; | |
this._frames.push({ | |
image: this._images[arr[4] ? arr[4] : 0], | |
rect: new createjs.Rectangle(arr[0], arr[1], arr[2], arr[3]), | |
regX: arr[5] || 0, | |
regY: arr[6] || 0, | |
}); | |
} | |
} else { | |
o = data.frames; | |
this._frameWidth = o.width; | |
this._frameHeight = o.height; | |
this._regX = o.regX || 0; | |
this._regY = o.regY || 0; | |
this._spacing = o.spacing || 0; | |
this._margin = o.margin || 0; | |
this._numFrames = o.count; | |
if (this._loadCount == 0) { | |
this._calculateFrames(); | |
} | |
} | |
// parse animations: | |
this._animations = []; | |
if ((o = data.animations) != null) { | |
this._data = {}; | |
var name; | |
for (name in o) { | |
var anim = { name: name }; | |
var obj = o[name]; | |
if (typeof obj == "number") { | |
// single frame | |
a = anim.frames = [obj]; | |
} else if (Array.isArray(obj)) { | |
// simple | |
if (obj.length == 1) { | |
anim.frames = [obj[0]]; | |
} else { | |
anim.speed = obj[3]; | |
anim.next = obj[2]; | |
a = anim.frames = []; | |
for (i = obj[0]; i <= obj[1]; i++) { | |
a.push(i); | |
} | |
} | |
} else { | |
// complex | |
anim.speed = obj.speed; | |
anim.next = obj.next; | |
var frames = obj.frames; | |
a = anim.frames = | |
typeof frames == "number" ? [frames] : frames.slice(0); | |
} | |
if (anim.next === true || anim.next === undefined) { | |
anim.next = name; | |
} // loop | |
if (anim.next === false || (a.length < 2 && anim.next == name)) { | |
anim.next = null; | |
} // stop | |
if (!anim.speed) { | |
anim.speed = 1; | |
} | |
this._animations.push(name); | |
this._data[name] = anim; | |
} | |
} | |
}; | |
/** | |
* @method _handleImageLoad | |
* @protected | |
**/ | |
p._handleImageLoad = function (src) { | |
if (--this._loadCount == 0) { | |
this._calculateFrames(); | |
this.complete = true; | |
this.dispatchEvent("complete"); | |
} | |
}; | |
/** | |
* @method _handleImageError | |
* @protected | |
*/ | |
p._handleImageError = function (src) { | |
var errorEvent = new createjs.Event("error"); | |
errorEvent.src = src; | |
this.dispatchEvent(errorEvent); | |
// Complete is still dispatched. | |
if (--this._loadCount == 0) { | |
this.dispatchEvent("complete"); | |
} | |
}; | |
/** | |
* @method _calculateFrames | |
* @protected | |
**/ | |
p._calculateFrames = function () { | |
if (this._frames || this._frameWidth == 0) { | |
return; | |
} | |
this._frames = []; | |
var maxFrames = this._numFrames || 100000; // if we go over this, something is wrong. | |
var frameCount = 0, | |
frameWidth = this._frameWidth, | |
frameHeight = this._frameHeight; | |
var spacing = this._spacing, | |
margin = this._margin; | |
imgLoop: for (var i = 0, imgs = this._images; i < imgs.length; i++) { | |
var img = imgs[i], | |
imgW = img.width || img.naturalWidth, | |
imgH = img.height || img.naturalHeight; | |
var y = margin; | |
while (y <= imgH - margin - frameHeight) { | |
var x = margin; | |
while (x <= imgW - margin - frameWidth) { | |
if (frameCount >= maxFrames) { | |
break imgLoop; | |
} | |
frameCount++; | |
this._frames.push({ | |
image: img, | |
rect: new createjs.Rectangle(x, y, frameWidth, frameHeight), | |
regX: this._regX, | |
regY: this._regY, | |
}); | |
x += frameWidth + spacing; | |
} | |
y += frameHeight + spacing; | |
} | |
} | |
this._numFrames = frameCount; | |
}; | |
createjs.SpriteSheet = createjs.promote(SpriteSheet, "EventDispatcher"); | |
})(); | |
//############################################################################## | |
// Graphics.js | |
//############################################################################## | |
this.createjs = this.createjs || {}; | |
(function () { | |
"use strict"; | |
// constructor: | |
/** | |
* The Graphics class exposes an easy to use API for generating vector drawing instructions and drawing them to a | |
* specified context. Note that you can use Graphics without any dependency on the EaselJS framework by calling {{#crossLink "Graphics/draw"}}{{/crossLink}} | |
* directly, or it can be used with the {{#crossLink "Shape"}}{{/crossLink}} object to draw vector graphics within the | |
* context of an EaselJS display list. | |
* | |
* There are two approaches to working with Graphics object: calling methods on a Graphics instance (the "Graphics API"), or | |
* instantiating Graphics command objects and adding them to the graphics queue via {{#crossLink "Graphics/append"}}{{/crossLink}}. | |
* The former abstracts the latter, simplifying beginning and ending paths, fills, and strokes. | |
* | |
* var g = new createjs.Graphics(); | |
* g.setStrokeStyle(1); | |
* g.beginStroke("#000000"); | |
* g.beginFill("red"); | |
* g.drawCircle(0,0,30); | |
* | |
* All drawing methods in Graphics return the Graphics instance, so they can be chained together. For example, | |
* the following line of code would generate the instructions to draw a rectangle with a red stroke and blue fill: | |
* | |
* myGraphics.beginStroke("red").beginFill("blue").drawRect(20, 20, 100, 50); | |
* | |
* Each graphics API call generates a command object (see below). The last command to be created can be accessed via | |
* {{#crossLink "Graphics/command:property"}}{{/crossLink}}: | |
* | |
* var fillCommand = myGraphics.beginFill("red").command; | |
* // ... later, update the fill style/color: | |
* fillCommand.style = "blue"; | |
* // or change it to a bitmap fill: | |
* fillCommand.bitmap(myImage); | |
* | |
* For more direct control of rendering, you can instantiate and append command objects to the graphics queue directly. In this case, you | |
* need to manage path creation manually, and ensure that fill/stroke is applied to a defined path: | |
* | |
* // start a new path. Graphics.beginCmd is a reusable BeginPath instance: | |
* myGraphics.append(createjs.Graphics.beginCmd); | |
* // we need to define the path before applying the fill: | |
* var circle = new createjs.Graphics.Circle(0,0,30); | |
* myGraphics.append(circle); | |
* // fill the path we just defined: | |
* var fill = new createjs.Graphics.Fill("red"); | |
* myGraphics.append(fill); | |
* | |
* These approaches can be used together, for example to insert a custom command: | |
* | |
* myGraphics.beginFill("red"); | |
* var customCommand = new CustomSpiralCommand(etc); | |
* myGraphics.append(customCommand); | |
* myGraphics.beginFill("blue"); | |
* myGraphics.drawCircle(0, 0, 30); | |
* | |
* See {{#crossLink "Graphics/append"}}{{/crossLink}} for more info on creating custom commands. | |
* | |
* <h4>Tiny API</h4> | |
* The Graphics class also includes a "tiny API", which is one or two-letter methods that are shortcuts for all of the | |
* Graphics methods. These methods are great for creating compact instructions, and is used by the Toolkit for CreateJS | |
* to generate readable code. All tiny methods are marked as protected, so you can view them by enabling protected | |
* descriptions in the docs. | |
* | |
* <table> | |
* <tr><td><b>Tiny</b></td><td><b>Method</b></td><td><b>Tiny</b></td><td><b>Method</b></td></tr> | |
* <tr><td>mt</td><td>{{#crossLink "Graphics/moveTo"}}{{/crossLink}} </td> | |
* <td>lt</td> <td>{{#crossLink "Graphics/lineTo"}}{{/crossLink}}</td></tr> | |
* <tr><td>a/at</td><td>{{#crossLink "Graphics/arc"}}{{/crossLink}} / {{#crossLink "Graphics/arcTo"}}{{/crossLink}} </td> | |
* <td>bt</td><td>{{#crossLink "Graphics/bezierCurveTo"}}{{/crossLink}} </td></tr> | |
* <tr><td>qt</td><td>{{#crossLink "Graphics/quadraticCurveTo"}}{{/crossLink}} (also curveTo)</td> | |
* <td>r</td><td>{{#crossLink "Graphics/rect"}}{{/crossLink}} </td></tr> | |
* <tr><td>cp</td><td>{{#crossLink "Graphics/closePath"}}{{/crossLink}} </td> | |
* <td>c</td><td>{{#crossLink "Graphics/clear"}}{{/crossLink}} </td></tr> | |
* <tr><td>f</td><td>{{#crossLink "Graphics/beginFill"}}{{/crossLink}} </td> | |
* <td>lf</td><td>{{#crossLink "Graphics/beginLinearGradientFill"}}{{/crossLink}} </td></tr> | |
* <tr><td>rf</td><td>{{#crossLink "Graphics/beginRadialGradientFill"}}{{/crossLink}} </td> | |
* <td>bf</td><td>{{#crossLink "Graphics/beginBitmapFill"}}{{/crossLink}} </td></tr> | |
* <tr><td>ef</td><td>{{#crossLink "Graphics/endFill"}}{{/crossLink}} </td> | |
* <td>ss / sd</td><td>{{#crossLink "Graphics/setStrokeStyle"}}{{/crossLink}} / {{#crossLink "Graphics/setStrokeDash"}}{{/crossLink}} </td></tr> | |
* <tr><td>s</td><td>{{#crossLink "Graphics/beginStroke"}}{{/crossLink}} </td> | |
* <td>ls</td><td>{{#crossLink "Graphics/beginLinearGradientStroke"}}{{/crossLink}} </td></tr> | |
* <tr><td>rs</td><td>{{#crossLink "Graphics/beginRadialGradientStroke"}}{{/crossLink}} </td> | |
* <td>bs</td><td>{{#crossLink "Graphics/beginBitmapStroke"}}{{/crossLink}} </td></tr> | |
* <tr><td>es</td><td>{{#crossLink "Graphics/endStroke"}}{{/crossLink}} </td> | |
* <td>dr</td><td>{{#crossLink "Graphics/drawRect"}}{{/crossLink}} </td></tr> | |
* <tr><td>rr</td><td>{{#crossLink "Graphics/drawRoundRect"}}{{/crossLink}} </td> | |
* <td>rc</td><td>{{#crossLink "Graphics/drawRoundRectComplex"}}{{/crossLink}} </td></tr> | |
* <tr><td>dc</td><td>{{#crossLink "Graphics/drawCircle"}}{{/crossLink}} </td> | |
* <td>de</td><td>{{#crossLink "Graphics/drawEllipse"}}{{/crossLink}} </td></tr> | |
* <tr><td>dp</td><td>{{#crossLink "Graphics/drawPolyStar"}}{{/crossLink}} </td> | |
* <td>p</td><td>{{#crossLink "Graphics/decodePath"}}{{/crossLink}} </td></tr> | |
* </table> | |
* | |
* Here is the above example, using the tiny API instead. | |
* | |
* myGraphics.s("red").f("blue").r(20, 20, 100, 50); | |
* | |
* @class Graphics | |
* @constructor | |
**/ | |
function Graphics() { | |
// public properties | |
/** | |
* Holds a reference to the last command that was created or appended. For example, you could retain a reference | |
* to a Fill command in order to dynamically update the color later by using: | |
* | |
* var myFill = myGraphics.beginFill("red").command; | |
* // update color later: | |
* myFill.style = "yellow"; | |
* | |
* @property command | |
* @type Object | |
**/ | |
this.command = null; | |
// private properties | |
/** | |
* @property _stroke | |
* @protected | |
* @type {Stroke} | |
**/ | |
this._stroke = null; | |
/** | |
* @property _strokeStyle | |
* @protected | |
* @type {StrokeStyle} | |
**/ | |
this._strokeStyle = null; | |
/** | |
* @property _oldStrokeStyle | |
* @protected | |
* @type {StrokeStyle} | |
**/ | |
this._oldStrokeStyle = null; | |
/** | |
* @property _strokeDash | |
* @protected | |
* @type {StrokeDash} | |
**/ | |
this._strokeDash = null; | |
/** | |
* @property _oldStrokeDash | |
* @protected | |
* @type {StrokeDash} | |
**/ | |
this._oldStrokeDash = null; | |
/** | |
* @property _strokeIgnoreScale | |
* @protected | |
* @type Boolean | |
**/ | |
this._strokeIgnoreScale = false; | |
/** | |
* @property _fill | |
* @protected | |
* @type {Fill} | |
**/ | |
this._fill = null; | |
/** | |
* @property _instructions | |
* @protected | |
* @type {Array} | |
**/ | |
this._instructions = []; | |
/** | |
* Indicates the last instruction index that was committed. | |
* @property _commitIndex | |
* @protected | |
* @type {Number} | |
**/ | |
this._commitIndex = 0; | |
/** | |
* Uncommitted instructions. | |
* @property _activeInstructions | |
* @protected | |
* @type {Array} | |
**/ | |
this._activeInstructions = []; | |
/** | |
* This indicates that there have been changes to the activeInstruction list since the last updateInstructions call. | |
* @property _dirty | |
* @protected | |
* @type {Boolean} | |
* @default false | |
**/ | |
this._dirty = false; | |
/** | |
* Index to draw from if a store operation has happened. | |
* @property _storeIndex | |
* @protected | |
* @type {Number} | |
* @default 0 | |
**/ | |
this._storeIndex = 0; | |
// setup: | |
this.clear(); | |
} | |
var p = Graphics.prototype; | |
var G = Graphics; // shortcut | |
// static public methods: | |
/** | |
* Returns a CSS compatible color string based on the specified RGB numeric color values in the format | |
* "rgba(255,255,255,1.0)", or if alpha is null then in the format "rgb(255,255,255)". For example, | |
* | |
* createjs.Graphics.getRGB(50, 100, 150, 0.5); | |
* // Returns "rgba(50,100,150,0.5)" | |
* | |
* It also supports passing a single hex color value as the first param, and an optional alpha value as the second | |
* param. For example, | |
* | |
* createjs.Graphics.getRGB(0xFF00FF, 0.2); | |
* // Returns "rgba(255,0,255,0.2)" | |
* | |
* @method getRGB | |
* @static | |
* @param {Number} r The red component for the color, between 0 and 0xFF (255). | |
* @param {Number} g The green component for the color, between 0 and 0xFF (255). | |
* @param {Number} b The blue component for the color, between 0 and 0xFF (255). | |
* @param {Number} [alpha] The alpha component for the color where 0 is fully transparent and 1 is fully opaque. | |
* @return {String} A CSS compatible color string based on the specified RGB numeric color values in the format | |
* "rgba(255,255,255,1.0)", or if alpha is null then in the format "rgb(255,255,255)". | |
**/ | |
Graphics.getRGB = function (r, g, b, alpha) { | |
if (r != null && b == null) { | |
alpha = g; | |
b = r & 0xff; | |
g = (r >> 8) & 0xff; | |
r = (r >> 16) & 0xff; | |
} | |
if (alpha == null) { | |
return "rgb(" + r + "," + g + "," + b + ")"; | |
} else { | |
return "rgba(" + r + "," + g + "," + b + "," + alpha + ")"; | |
} | |
}; | |
/** | |
* Returns a CSS compatible color string based on the specified HSL numeric color values in the format "hsla(360,100,100,1.0)", | |
* or if alpha is null then in the format "hsl(360,100,100)". | |
* | |
* createjs.Graphics.getHSL(150, 100, 70); | |
* // Returns "hsl(150,100,70)" | |
* | |
* @method getHSL | |
* @static | |
* @param {Number} hue The hue component for the color, between 0 and 360. | |
* @param {Number} saturation The saturation component for the color, between 0 and 100. | |
* @param {Number} lightness The lightness component for the color, between 0 and 100. | |
* @param {Number} [alpha] The alpha component for the color where 0 is fully transparent and 1 is fully opaque. | |
* @return {String} A CSS compatible color string based on the specified HSL numeric color values in the format | |
* "hsla(360,100,100,1.0)", or if alpha is null then in the format "hsl(360,100,100)". | |
**/ | |
Graphics.getHSL = function (hue, saturation, lightness, alpha) { | |
if (alpha == null) { | |
return "hsl(" + (hue % 360) + "," + saturation + "%," + lightness + "%)"; | |
} else { | |
return ( | |
"hsla(" + | |
(hue % 360) + | |
"," + | |
saturation + | |
"%," + | |
lightness + | |
"%," + | |
alpha + | |
")" | |
); | |
} | |
}; | |
// static properties: | |
/** | |
* A reusable instance of {{#crossLink "Graphics/BeginPath"}}{{/crossLink}} to avoid | |
* unnecessary instantiation. | |
* @property beginCmd | |
* @type {Graphics.BeginPath} | |
* @static | |
**/ | |
// defined at the bottom of this file. | |
/** | |
* Map of Base64 characters to values. Used by {{#crossLink "Graphics/decodePath"}}{{/crossLink}}. | |
* @property BASE_64 | |
* @static | |
* @final | |
* @readonly | |
* @type {Object} | |
**/ | |
Graphics.BASE_64 = { | |
A: 0, | |
B: 1, | |
C: 2, | |
D: 3, | |
E: 4, | |
F: 5, | |
G: 6, | |
H: 7, | |
I: 8, | |
J: 9, | |
K: 10, | |
L: 11, | |
M: 12, | |
N: 13, | |
O: 14, | |
P: 15, | |
Q: 16, | |
R: 17, | |
S: 18, | |
T: 19, | |
U: 20, | |
V: 21, | |
W: 22, | |
X: 23, | |
Y: 24, | |
Z: 25, | |
a: 26, | |
b: 27, | |
c: 28, | |
d: 29, | |
e: 30, | |
f: 31, | |
g: 32, | |
h: 33, | |
i: 34, | |
j: 35, | |
k: 36, | |
l: 37, | |
m: 38, | |
n: 39, | |
o: 40, | |
p: 41, | |
q: 42, | |
r: 43, | |
s: 44, | |
t: 45, | |
u: 46, | |
v: 47, | |
w: 48, | |
x: 49, | |
y: 50, | |
z: 51, | |
0: 52, | |
1: 53, | |
2: 54, | |
3: 55, | |
4: 56, | |
5: 57, | |
6: 58, | |
7: 59, | |
8: 60, | |
9: 61, | |
"+": 62, | |
"/": 63, | |
}; | |
/** | |
* Maps numeric values for the caps parameter of {{#crossLink "Graphics/setStrokeStyle"}}{{/crossLink}} to | |
* corresponding string values. This is primarily for use with the tiny API. The mappings are as follows: 0 to | |
* "butt", 1 to "round", and 2 to "square". | |
* For example, to set the line caps to "square": | |
* | |
* myGraphics.ss(16, 2); | |
* | |
* @property STROKE_CAPS_MAP | |
* @static | |
* @final | |
* @readonly | |
* @type {Array} | |
**/ | |
Graphics.STROKE_CAPS_MAP = ["butt", "round", "square"]; | |
/** | |
* Maps numeric values for the joints parameter of {{#crossLink "Graphics/setStrokeStyle"}}{{/crossLink}} to | |
* corresponding string values. This is primarily for use with the tiny API. The mappings are as follows: 0 to | |
* "miter", 1 to "round", and 2 to "bevel". | |
* For example, to set the line joints to "bevel": | |
* | |
* myGraphics.ss(16, 0, 2); | |
* | |
* @property STROKE_JOINTS_MAP | |
* @static | |
* @final | |
* @readonly | |
* @type {Array} | |
**/ | |
Graphics.STROKE_JOINTS_MAP = ["miter", "round", "bevel"]; | |
/** | |
* @property _ctx | |
* @static | |
* @protected | |
* @type {CanvasRenderingContext2D} | |
**/ | |
var canvas = createjs.createCanvas | |
? createjs.createCanvas() | |
: document.createElement("canvas"); | |
if (canvas.getContext) { | |
Graphics._ctx = canvas.getContext("2d"); | |
canvas.width = canvas.height = 1; | |
} | |
// getter / setters: | |
/** | |
* Use the {{#crossLink "Graphics/instructions:property"}}{{/crossLink}} property instead. | |
* @method _getInstructions | |
* @protected | |
* @return {Array} The instructions array, useful for chaining | |
**/ | |
p._getInstructions = function () { | |
this._updateInstructions(); | |
return this._instructions; | |
}; | |
// Graphics.getInstructions is @deprecated. Remove for 1.1+ | |
p.getInstructions = createjs.deprecate( | |
p._getInstructions, | |
"Graphics.getInstructions" | |
); | |
/** | |
* Returns the graphics instructions array. Each entry is a graphics command object (ex. Graphics.Fill, Graphics.Rect) | |
* Modifying the returned array directly is not recommended, and is likely to result in unexpected behaviour. | |
* | |
* This property is mainly intended for introspection of the instructions (ex. for graphics export). | |
* @property instructions | |
* @type {Array} | |
* @readonly | |
**/ | |
try { | |
Object.defineProperties(p, { | |
instructions: { get: p._getInstructions }, | |
}); | |
} catch (e) {} | |
// public methods: | |
/** | |
* Returns true if this Graphics instance has no drawing commands. | |
* @method isEmpty | |
* @return {Boolean} Returns true if this Graphics instance has no drawing commands. | |
**/ | |
p.isEmpty = function () { | |
return !(this._instructions.length || this._activeInstructions.length); | |
}; | |
/** | |
* Draws the display object into the specified context ignoring its visible, alpha, shadow, and transform. | |
* Returns true if the draw was handled (useful for overriding functionality). | |
* | |
* NOTE: This method is mainly for internal use, though it may be useful for advanced uses. | |
* @method draw | |
* @param {CanvasRenderingContext2D} ctx The canvas 2D context object to draw into. | |
* @param {Object} data Optional data that is passed to graphics command exec methods. When called from a Shape instance, the shape passes itself as the data parameter. This can be used by custom graphic commands to insert contextual data. | |
**/ | |
p.draw = function (ctx, data) { | |
this._updateInstructions(); | |
var instr = this._instructions; | |
for (var i = this._storeIndex, l = instr.length; i < l; i++) { | |
instr[i].exec(ctx, data); | |
} | |
}; | |
/** | |
* Draws only the path described for this Graphics instance, skipping any non-path instructions, including fill and | |
* stroke descriptions. Used for <code>DisplayObject.mask</code> to draw the clipping path, for example. | |
* | |
* NOTE: This method is mainly for internal use, though it may be useful for advanced uses. | |
* @method drawAsPath | |
* @param {CanvasRenderingContext2D} ctx The canvas 2D context object to draw into. | |
**/ | |
p.drawAsPath = function (ctx) { | |
this._updateInstructions(); | |
var instr, | |
instrs = this._instructions; | |
for (var i = this._storeIndex, l = instrs.length; i < l; i++) { | |
// the first command is always a beginPath command. | |
if ((instr = instrs[i]).path !== false) { | |
instr.exec(ctx); | |
} | |
} | |
}; | |
// public methods that map directly to context 2D calls: | |
/** | |
* Moves the drawing point to the specified position. A tiny API method "mt" also exists. | |
* @method moveTo | |
* @param {Number} x The x coordinate the drawing point should move to. | |
* @param {Number} y The y coordinate the drawing point should move to. | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls). | |
* @chainable | |
**/ | |
p.moveTo = function (x, y) { | |
return this.append(new G.MoveTo(x, y), true); | |
}; | |
/** | |
* Draws a line from the current drawing point to the specified position, which become the new current drawing | |
* point. Note that you *must* call {{#crossLink "Graphics/moveTo"}}{{/crossLink}} before the first `lineTo()`. | |
* A tiny API method "lt" also exists. | |
* | |
* For detailed information, read the | |
* <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#complex-shapes-(paths)"> | |
* whatwg spec</a>. | |
* @method lineTo | |
* @param {Number} x The x coordinate the drawing point should draw to. | |
* @param {Number} y The y coordinate the drawing point should draw to. | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
**/ | |
p.lineTo = function (x, y) { | |
return this.append(new G.LineTo(x, y)); | |
}; | |
/** | |
* Draws an arc with the specified control points and radius. For detailed information, read the | |
* <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-arcto"> | |
* whatwg spec</a>. A tiny API method "at" also exists. | |
* @method arcTo | |
* @param {Number} x1 | |
* @param {Number} y1 | |
* @param {Number} x2 | |
* @param {Number} y2 | |
* @param {Number} radius | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
**/ | |
p.arcTo = function (x1, y1, x2, y2, radius) { | |
return this.append(new G.ArcTo(x1, y1, x2, y2, radius)); | |
}; | |
/** | |
* Draws an arc defined by the radius, startAngle and endAngle arguments, centered at the position (x, y). For | |
* example, to draw a full circle with a radius of 20 centered at (100, 100): | |
* | |
* arc(100, 100, 20, 0, Math.PI*2); | |
* | |
* For detailed information, read the | |
* <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-arc">whatwg spec</a>. | |
* A tiny API method "a" also exists. | |
* @method arc | |
* @param {Number} x | |
* @param {Number} y | |
* @param {Number} radius | |
* @param {Number} startAngle Measured in radians. | |
* @param {Number} endAngle Measured in radians. | |
* @param {Boolean} anticlockwise | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
**/ | |
p.arc = function (x, y, radius, startAngle, endAngle, anticlockwise) { | |
return this.append( | |
new G.Arc(x, y, radius, startAngle, endAngle, anticlockwise) | |
); | |
}; | |
/** | |
* Draws a quadratic curve from the current drawing point to (x, y) using the control point (cpx, cpy). For detailed | |
* information, read the <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-quadraticcurveto"> | |
* whatwg spec</a>. A tiny API method "qt" also exists. | |
* @method quadraticCurveTo | |
* @param {Number} cpx | |
* @param {Number} cpy | |
* @param {Number} x | |
* @param {Number} y | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
**/ | |
p.quadraticCurveTo = function (cpx, cpy, x, y) { | |
return this.append(new G.QuadraticCurveTo(cpx, cpy, x, y)); | |
}; | |
/** | |
* Draws a bezier curve from the current drawing point to (x, y) using the control points (cp1x, cp1y) and (cp2x, | |
* cp2y). For detailed information, read the | |
* <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-beziercurveto"> | |
* whatwg spec</a>. A tiny API method "bt" also exists. | |
* @method bezierCurveTo | |
* @param {Number} cp1x | |
* @param {Number} cp1y | |
* @param {Number} cp2x | |
* @param {Number} cp2y | |
* @param {Number} x | |
* @param {Number} y | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
**/ | |
p.bezierCurveTo = function (cp1x, cp1y, cp2x, cp2y, x, y) { | |
return this.append(new G.BezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)); | |
}; | |
/** | |
* Draws a rectangle at (x, y) with the specified width and height using the current fill and/or stroke. | |
* For detailed information, read the | |
* <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-rect"> | |
* whatwg spec</a>. A tiny API method "r" also exists. | |
* @method rect | |
* @param {Number} x | |
* @param {Number} y | |
* @param {Number} w Width of the rectangle | |
* @param {Number} h Height of the rectangle | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
**/ | |
p.rect = function (x, y, w, h) { | |
return this.append(new G.Rect(x, y, w, h)); | |
}; | |
/** | |
* Closes the current path, effectively drawing a line from the current drawing point to the first drawing point specified | |
* since the fill or stroke was last set. A tiny API method "cp" also exists. | |
* @method closePath | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
**/ | |
p.closePath = function () { | |
return this._activeInstructions.length | |
? this.append(new G.ClosePath()) | |
: this; | |
}; | |
// public methods that roughly map to Adobe Flash/Animate graphics APIs: | |
/** | |
* Clears all drawing instructions, effectively resetting this Graphics instance. Any line and fill styles will need | |
* to be redefined to draw shapes following a clear call. A tiny API method "c" also exists. | |
* @method clear | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
**/ | |
p.clear = function () { | |
this._instructions.length = | |
this._activeInstructions.length = | |
this._commitIndex = | |
0; | |
this._strokeStyle = | |
this._oldStrokeStyle = | |
this._stroke = | |
this._fill = | |
this._strokeDash = | |
this._oldStrokeDash = | |
null; | |
this._dirty = this._strokeIgnoreScale = false; | |
return this; | |
}; | |
/** | |
* Begins a fill with the specified color. This ends the current sub-path. A tiny API method "f" also exists. | |
* @method beginFill | |
* @param {String} color A CSS compatible color value (ex. "red", "#FF0000", or "rgba(255,0,0,0.5)"). Setting to | |
* null will result in no fill. | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
**/ | |
p.beginFill = function (color) { | |
return this._setFill(color ? new G.Fill(color) : null); | |
}; | |
/** | |
* Begins a linear gradient fill defined by the line (x0, y0) to (x1, y1). This ends the current sub-path. For | |
* example, the following code defines a black to white vertical gradient ranging from 20px to 120px, and draws a | |
* square to display it: | |
* | |
* myGraphics.beginLinearGradientFill(["#000","#FFF"], [0, 1], 0, 20, 0, 120).drawRect(20, 20, 120, 120); | |
* | |
* A tiny API method "lf" also exists. | |
* @method beginLinearGradientFill | |
* @param {Array} colors An array of CSS compatible color values. For example, ["#F00","#00F"] would define a gradient | |
* drawing from red to blue. | |
* @param {Array} ratios An array of gradient positions which correspond to the colors. For example, [0.1, 0.9] would draw | |
* the first color to 10% then interpolating to the second color at 90%. | |
* @param {Number} x0 The position of the first point defining the line that defines the gradient direction and size. | |
* @param {Number} y0 The position of the first point defining the line that defines the gradient direction and size. | |
* @param {Number} x1 The position of the second point defining the line that defines the gradient direction and size. | |
* @param {Number} y1 The position of the second point defining the line that defines the gradient direction and size. | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
**/ | |
p.beginLinearGradientFill = function (colors, ratios, x0, y0, x1, y1) { | |
return this._setFill( | |
new G.Fill().linearGradient(colors, ratios, x0, y0, x1, y1) | |
); | |
}; | |
/** | |
* Begins a radial gradient fill. This ends the current sub-path. For example, the following code defines a red to | |
* blue radial gradient centered at (100, 100), with a radius of 50, and draws a circle to display it: | |
* | |
* myGraphics.beginRadialGradientFill(["#F00","#00F"], [0, 1], 100, 100, 0, 100, 100, 50).drawCircle(100, 100, 50); | |
* | |
* A tiny API method "rf" also exists. | |
* @method beginRadialGradientFill | |
* @param {Array} colors An array of CSS compatible color values. For example, ["#F00","#00F"] would define | |
* a gradient drawing from red to blue. | |
* @param {Array} ratios An array of gradient positions which correspond to the colors. For example, [0.1, | |
* 0.9] would draw the first color to 10% then interpolating to the second color at 90%. | |
* @param {Number} x0 Center position of the inner circle that defines the gradient. | |
* @param {Number} y0 Center position of the inner circle that defines the gradient. | |
* @param {Number} r0 Radius of the inner circle that defines the gradient. | |
* @param {Number} x1 Center position of the outer circle that defines the gradient. | |
* @param {Number} y1 Center position of the outer circle that defines the gradient. | |
* @param {Number} r1 Radius of the outer circle that defines the gradient. | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
**/ | |
p.beginRadialGradientFill = function ( | |
colors, | |
ratios, | |
x0, | |
y0, | |
r0, | |
x1, | |
y1, | |
r1 | |
) { | |
return this._setFill( | |
new G.Fill().radialGradient(colors, ratios, x0, y0, r0, x1, y1, r1) | |
); | |
}; | |
/** | |
* Begins a pattern fill using the specified image. This ends the current sub-path. A tiny API method "bf" also | |
* exists. | |
* @method beginBitmapFill | |
* @param {HTMLImageElement | HTMLCanvasElement | HTMLVideoElement} image The Image, Canvas, or Video object to use | |
* as the pattern. Must be loaded prior to creating a bitmap fill, or the fill will be empty. | |
* @param {String} repetition Optional. Indicates whether to repeat the image in the fill area. One of "repeat", | |
* "repeat-x", "repeat-y", or "no-repeat". Defaults to "repeat". Note that Firefox does not support "repeat-x" or | |
* "repeat-y" (latest tests were in FF 20.0), and will default to "repeat". | |
* @param {Matrix2D} matrix Optional. Specifies a transformation matrix for the bitmap fill. This transformation | |
* will be applied relative to the parent transform. | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
**/ | |
p.beginBitmapFill = function (image, repetition, matrix) { | |
return this._setFill(new G.Fill(null, matrix).bitmap(image, repetition)); | |
}; | |
/** | |
* Ends the current sub-path, and begins a new one with no fill. Functionally identical to <code>beginFill(null)</code>. | |
* A tiny API method "ef" also exists. | |
* @method endFill | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
**/ | |
p.endFill = function () { | |
return this.beginFill(); | |
}; | |
/** | |
* Sets the stroke style. Like all drawing methods, this can be chained, so you can define | |
* the stroke style and color in a single line of code like so: | |
* | |
* myGraphics.setStrokeStyle(8,"round").beginStroke("#F00"); | |
* | |
* A tiny API method "ss" also exists. | |
* @method setStrokeStyle | |
* @param {Number} thickness The width of the stroke. | |
* @param {String | Number} [caps=0] Indicates the type of caps to use at the end of lines. One of butt, | |
* round, or square. Defaults to "butt". Also accepts the values 0 (butt), 1 (round), and 2 (square) for use with | |
* the tiny API. | |
* @param {String | Number} [joints=0] Specifies the type of joints that should be used where two lines meet. | |
* One of bevel, round, or miter. Defaults to "miter". Also accepts the values 0 (miter), 1 (round), and 2 (bevel) | |
* for use with the tiny API. | |
* @param {Number} [miterLimit=10] If joints is set to "miter", then you can specify a miter limit ratio which | |
* controls at what point a mitered joint will be clipped. | |
* @param {Boolean} [ignoreScale=false] If true, the stroke will be drawn at the specified thickness regardless | |
* of active transformations. | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
**/ | |
p.setStrokeStyle = function ( | |
thickness, | |
caps, | |
joints, | |
miterLimit, | |
ignoreScale | |
) { | |
this._updateInstructions(true); | |
this._strokeStyle = this.command = new G.StrokeStyle( | |
thickness, | |
caps, | |
joints, | |
miterLimit, | |
ignoreScale | |
); | |
// ignoreScale lives on Stroke, not StrokeStyle, so we do a little trickery: | |
if (this._stroke) { | |
this._stroke.ignoreScale = ignoreScale; | |
} | |
this._strokeIgnoreScale = ignoreScale; | |
return this; | |
}; | |
/** | |
* Sets or clears the stroke dash pattern. | |
* | |
* myGraphics.setStrokeDash([20, 10], 0); | |
* | |
* A tiny API method `sd` also exists. | |
* @method setStrokeDash | |
* @param {Array} [segments] An array specifying the dash pattern, alternating between line and gap. | |
* For example, `[20,10]` would create a pattern of 20 pixel lines with 10 pixel gaps between them. | |
* Passing null or an empty array will clear the existing stroke dash. | |
* @param {Number} [offset=0] The offset of the dash pattern. For example, you could increment this value to create a "marching ants" effect. | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
**/ | |
p.setStrokeDash = function (segments, offset) { | |
this._updateInstructions(true); | |
this._strokeDash = this.command = new G.StrokeDash(segments, offset); | |
return this; | |
}; | |
/** | |
* Begins a stroke with the specified color. This ends the current sub-path. A tiny API method "s" also exists. | |
* @method beginStroke | |
* @param {String} color A CSS compatible color value (ex. "#FF0000", "red", or "rgba(255,0,0,0.5)"). Setting to | |
* null will result in no stroke. | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
**/ | |
p.beginStroke = function (color) { | |
return this._setStroke(color ? new G.Stroke(color) : null); | |
}; | |
/** | |
* Begins a linear gradient stroke defined by the line (x0, y0) to (x1, y1). This ends the current sub-path. For | |
* example, the following code defines a black to white vertical gradient ranging from 20px to 120px, and draws a | |
* square to display it: | |
* | |
* myGraphics.setStrokeStyle(10). | |
* beginLinearGradientStroke(["#000","#FFF"], [0, 1], 0, 20, 0, 120).drawRect(20, 20, 120, 120); | |
* | |
* A tiny API method "ls" also exists. | |
* @method beginLinearGradientStroke | |
* @param {Array} colors An array of CSS compatible color values. For example, ["#F00","#00F"] would define | |
* a gradient drawing from red to blue. | |
* @param {Array} ratios An array of gradient positions which correspond to the colors. For example, [0.1, | |
* 0.9] would draw the first color to 10% then interpolating to the second color at 90%. | |
* @param {Number} x0 The position of the first point defining the line that defines the gradient direction and size. | |
* @param {Number} y0 The position of the first point defining the line that defines the gradient direction and size. | |
* @param {Number} x1 The position of the second point defining the line that defines the gradient direction and size. | |
* @param {Number} y1 The position of the second point defining the line that defines the gradient direction and size. | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
**/ | |
p.beginLinearGradientStroke = function (colors, ratios, x0, y0, x1, y1) { | |
return this._setStroke( | |
new G.Stroke().linearGradient(colors, ratios, x0, y0, x1, y1) | |
); | |
}; | |
/** | |
* Begins a radial gradient stroke. This ends the current sub-path. For example, the following code defines a red to | |
* blue radial gradient centered at (100, 100), with a radius of 50, and draws a rectangle to display it: | |
* | |
* myGraphics.setStrokeStyle(10) | |
* .beginRadialGradientStroke(["#F00","#00F"], [0, 1], 100, 100, 0, 100, 100, 50) | |
* .drawRect(50, 90, 150, 110); | |
* | |
* A tiny API method "rs" also exists. | |
* @method beginRadialGradientStroke | |
* @param {Array} colors An array of CSS compatible color values. For example, ["#F00","#00F"] would define | |
* a gradient drawing from red to blue. | |
* @param {Array} ratios An array of gradient positions which correspond to the colors. For example, [0.1, | |
* 0.9] would draw the first color to 10% then interpolating to the second color at 90%, then draw the second color | |
* to 100%. | |
* @param {Number} x0 Center position of the inner circle that defines the gradient. | |
* @param {Number} y0 Center position of the inner circle that defines the gradient. | |
* @param {Number} r0 Radius of the inner circle that defines the gradient. | |
* @param {Number} x1 Center position of the outer circle that defines the gradient. | |
* @param {Number} y1 Center position of the outer circle that defines the gradient. | |
* @param {Number} r1 Radius of the outer circle that defines the gradient. | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
**/ | |
p.beginRadialGradientStroke = function ( | |
colors, | |
ratios, | |
x0, | |
y0, | |
r0, | |
x1, | |
y1, | |
r1 | |
) { | |
return this._setStroke( | |
new G.Stroke().radialGradient(colors, ratios, x0, y0, r0, x1, y1, r1) | |
); | |
}; | |
/** | |
* Begins a pattern fill using the specified image. This ends the current sub-path. Note that unlike bitmap fills, | |
* strokes do not currently support a matrix parameter due to limitations in the canvas API. A tiny API method "bs" | |
* also exists. | |
* @method beginBitmapStroke | |
* @param {HTMLImageElement | HTMLCanvasElement | HTMLVideoElement} image The Image, Canvas, or Video object to use | |
* as the pattern. Must be loaded prior to creating a bitmap fill, or the fill will be empty. | |
* @param {String} [repetition=repeat] Optional. Indicates whether to repeat the image in the fill area. One of | |
* "repeat", "repeat-x", "repeat-y", or "no-repeat". Defaults to "repeat". | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
**/ | |
p.beginBitmapStroke = function (image, repetition) { | |
// NOTE: matrix is not supported for stroke because transforms on strokes also affect the drawn stroke width. | |
return this._setStroke(new G.Stroke().bitmap(image, repetition)); | |
}; | |
/** | |
* Ends the current sub-path, and begins a new one with no stroke. Functionally identical to <code>beginStroke(null)</code>. | |
* A tiny API method "es" also exists. | |
* @method endStroke | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
**/ | |
p.endStroke = function () { | |
return this.beginStroke(); | |
}; | |
/** | |
* Maps the familiar ActionScript <code>curveTo()</code> method to the functionally similar {{#crossLink "Graphics/quadraticCurveTo"}}{{/crossLink}} | |
* method. | |
* @method curveTo | |
* @param {Number} cpx | |
* @param {Number} cpy | |
* @param {Number} x | |
* @param {Number} y | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
**/ | |
p.curveTo = p.quadraticCurveTo; | |
/** | |
* | |
* Maps the familiar ActionScript <code>drawRect()</code> method to the functionally similar {{#crossLink "Graphics/rect"}}{{/crossLink}} | |
* method. | |
* @method drawRect | |
* @param {Number} x | |
* @param {Number} y | |
* @param {Number} w Width of the rectangle | |
* @param {Number} h Height of the rectangle | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
**/ | |
p.drawRect = p.rect; | |
/** | |
* Draws a rounded rectangle with all corners with the specified radius. | |
* @method drawRoundRect | |
* @param {Number} x | |
* @param {Number} y | |
* @param {Number} w | |
* @param {Number} h | |
* @param {Number} radius Corner radius. | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
**/ | |
p.drawRoundRect = function (x, y, w, h, radius) { | |
return this.drawRoundRectComplex( | |
x, | |
y, | |
w, | |
h, | |
radius, | |
radius, | |
radius, | |
radius | |
); | |
}; | |
/** | |
* Draws a rounded rectangle with different corner radii. Supports positive and negative corner radii. A tiny API | |
* method "rc" also exists. | |
* @method drawRoundRectComplex | |
* @param {Number} x The horizontal coordinate to draw the round rect. | |
* @param {Number} y The vertical coordinate to draw the round rect. | |
* @param {Number} w The width of the round rect. | |
* @param {Number} h The height of the round rect. | |
* @param {Number} radiusTL Top left corner radius. | |
* @param {Number} radiusTR Top right corner radius. | |
* @param {Number} radiusBR Bottom right corner radius. | |
* @param {Number} radiusBL Bottom left corner radius. | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
**/ | |
p.drawRoundRectComplex = function ( | |
x, | |
y, | |
w, | |
h, | |
radiusTL, | |
radiusTR, | |
radiusBR, | |
radiusBL | |
) { | |
return this.append( | |
new G.RoundRect(x, y, w, h, radiusTL, radiusTR, radiusBR, radiusBL) | |
); | |
}; | |
/** | |
* Draws a circle with the specified radius at (x, y). | |
* | |
* var g = new createjs.Graphics(); | |
* g.setStrokeStyle(1); | |
* g.beginStroke(createjs.Graphics.getRGB(0,0,0)); | |
* g.beginFill(createjs.Graphics.getRGB(255,0,0)); | |
* g.drawCircle(0,0,3); | |
* | |
* var s = new createjs.Shape(g); | |
* s.x = 100; | |
* s.y = 100; | |
* | |
* stage.addChild(s); | |
* stage.update(); | |
* | |
* A tiny API method "dc" also exists. | |
* @method drawCircle | |
* @param {Number} x x coordinate center point of circle. | |
* @param {Number} y y coordinate center point of circle. | |
* @param {Number} radius Radius of circle. | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
**/ | |
p.drawCircle = function (x, y, radius) { | |
return this.append(new G.Circle(x, y, radius)); | |
}; | |
/** | |
* Draws an ellipse (oval) with a specified width (w) and height (h). Similar to {{#crossLink "Graphics/drawCircle"}}{{/crossLink}}, | |
* except the width and height can be different. A tiny API method "de" also exists. | |
* @method drawEllipse | |
* @param {Number} x The left coordinate point of the ellipse. Note that this is different from {{#crossLink "Graphics/drawCircle"}}{{/crossLink}} | |
* which draws from center. | |
* @param {Number} y The top coordinate point of the ellipse. Note that this is different from {{#crossLink "Graphics/drawCircle"}}{{/crossLink}} | |
* which draws from the center. | |
* @param {Number} w The height (horizontal diameter) of the ellipse. The horizontal radius will be half of this | |
* number. | |
* @param {Number} h The width (vertical diameter) of the ellipse. The vertical radius will be half of this number. | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
**/ | |
p.drawEllipse = function (x, y, w, h) { | |
return this.append(new G.Ellipse(x, y, w, h)); | |
}; | |
/** | |
* Draws a star if pointSize is greater than 0, or a regular polygon if pointSize is 0 with the specified number of | |
* points. For example, the following code will draw a familiar 5 pointed star shape centered at 100, 100 and with a | |
* radius of 50: | |
* | |
* myGraphics.beginFill("#FF0").drawPolyStar(100, 100, 50, 5, 0.6, -90); | |
* // Note: -90 makes the first point vertical | |
* | |
* A tiny API method "dp" also exists. | |
* | |
* @method drawPolyStar | |
* @param {Number} x Position of the center of the shape. | |
* @param {Number} y Position of the center of the shape. | |
* @param {Number} radius The outer radius of the shape. | |
* @param {Number} sides The number of points on the star or sides on the polygon. | |
* @param {Number} pointSize The depth or "pointy-ness" of the star points. A pointSize of 0 will draw a regular | |
* polygon (no points), a pointSize of 1 will draw nothing because the points are infinitely pointy. | |
* @param {Number} angle The angle of the first point / corner. For example a value of 0 will draw the first point | |
* directly to the right of the center. | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
**/ | |
p.drawPolyStar = function (x, y, radius, sides, pointSize, angle) { | |
return this.append(new G.PolyStar(x, y, radius, sides, pointSize, angle)); | |
}; | |
/** | |
* Appends a graphics command object to the graphics queue. Command objects expose an "exec" method | |
* that accepts two parameters: the Context2D to operate on, and an arbitrary data object passed into | |
* {{#crossLink "Graphics/draw"}}{{/crossLink}}. The latter will usually be the Shape instance that called draw. | |
* | |
* This method is used internally by Graphics methods, such as drawCircle, but can also be used directly to insert | |
* built-in or custom graphics commands. For example: | |
* | |
* // attach data to our shape, so we can access it during the draw: | |
* myShape.color = "red"; | |
* | |
* // append a Circle command object: | |
* myShape.graphics.append(new createjs.Graphics.Circle(50, 50, 30)); | |
* | |
* // append a custom command object with an exec method that sets the fill style | |
* // based on the shape's data, and then fills the circle. | |
* myShape.graphics.append({exec:function(ctx, shape) { | |
* ctx.fillStyle = shape.color; | |
* ctx.fill(); | |
* }}); | |
* | |
* @method append | |
* @param {Object} command A graphics command object exposing an "exec" method. | |
* @param {boolean} clean The clean param is primarily for internal use. A value of true indicates that a command does not generate a path that should be stroked or filled. | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
**/ | |
p.append = function (command, clean) { | |
this._activeInstructions.push(command); | |
this.command = command; | |
if (!clean) { | |
this._dirty = true; | |
} | |
return this; | |
}; | |
/** | |
* Decodes a compact encoded path string into a series of draw instructions. | |
* This format is not intended to be human readable, and is meant for use by authoring tools. | |
* The format uses a base64 character set, with each character representing 6 bits, to define a series of draw | |
* commands. | |
* | |
* Each command is comprised of a single "header" character followed by a variable number of alternating x and y | |
* position values. Reading the header bits from left to right (most to least significant): bits 1 to 3 specify the | |
* type of operation (0-moveTo, 1-lineTo, 2-quadraticCurveTo, 3-bezierCurveTo, 4-closePath, 5-7 unused). Bit 4 | |
* indicates whether position values use 12 bits (2 characters) or 18 bits (3 characters), with a one indicating the | |
* latter. Bits 5 and 6 are currently unused. | |
* | |
* Following the header is a series of 0 (closePath), 2 (moveTo, lineTo), 4 (quadraticCurveTo), or 6 (bezierCurveTo) | |
* parameters. These parameters are alternating x/y positions represented by 2 or 3 characters (as indicated by the | |
* 4th bit in the command char). These characters consist of a 1 bit sign (1 is negative, 0 is positive), followed | |
* by an 11 (2 char) or 17 (3 char) bit integer value. All position values are in tenths of a pixel. Except in the | |
* case of move operations which are absolute, this value is a delta from the previous x or y position (as | |
* appropriate). | |
* | |
* For example, the string "A3cAAMAu4AAA" represents a line starting at -150,0 and ending at 150,0. | |
* <br />A - bits 000000. First 3 bits (000) indicate a moveTo operation. 4th bit (0) indicates 2 chars per | |
* parameter. | |
* <br />n0 - 110111011100. Absolute x position of -150.0px. First bit indicates a negative value, remaining bits | |
* indicate 1500 tenths of a pixel. | |
* <br />AA - 000000000000. Absolute y position of 0. | |
* <br />I - 001100. First 3 bits (001) indicate a lineTo operation. 4th bit (1) indicates 3 chars per parameter. | |
* <br />Au4 - 000000101110111000. An x delta of 300.0px, which is added to the previous x value of -150.0px to | |
* provide an absolute position of +150.0px. | |
* <br />AAA - 000000000000000000. A y delta value of 0. | |
* | |
* A tiny API method "p" also exists. | |
* @method decodePath | |
* @param {String} str The path string to decode. | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
**/ | |
p.decodePath = function (str) { | |
var instructions = [ | |
this.moveTo, | |
this.lineTo, | |
this.quadraticCurveTo, | |
this.bezierCurveTo, | |
this.closePath, | |
]; | |
var paramCount = [2, 2, 4, 6, 0]; | |
var i = 0, | |
l = str.length; | |
var params = []; | |
var x = 0, | |
y = 0; | |
var base64 = Graphics.BASE_64; | |
while (i < l) { | |
var c = str.charAt(i); | |
var n = base64[c]; | |
var fi = n >> 3; // highest order bits 1-3 code for operation. | |
var f = instructions[fi]; | |
// check that we have a valid instruction & that the unused bits are empty: | |
if (!f || n & 3) { | |
throw "bad path data (@" + i + "): " + c; | |
} | |
var pl = paramCount[fi]; | |
if (!fi) { | |
x = y = 0; | |
} // move operations reset the position. | |
params.length = 0; | |
i++; | |
var charCount = ((n >> 2) & 1) + 2; // 4th header bit indicates number size for this operation. | |
for (var p = 0; p < pl; p++) { | |
var num = base64[str.charAt(i)]; | |
var sign = num >> 5 ? -1 : 1; | |
num = ((num & 31) << 6) | base64[str.charAt(i + 1)]; | |
if (charCount == 3) { | |
num = (num << 6) | base64[str.charAt(i + 2)]; | |
} | |
num = (sign * num) / 10; | |
if (p % 2) { | |
x = num += x; | |
} else { | |
y = num += y; | |
} | |
params[p] = num; | |
i += charCount; | |
} | |
f.apply(this, params); | |
} | |
return this; | |
}; | |
/** | |
* Stores all graphics commands so they won't be executed in future draws. Calling store() a second time adds to | |
* the existing store. This also affects `drawAsPath()`. | |
* | |
* This is useful in cases where you are creating vector graphics in an iterative manner (ex. generative art), so | |
* that only new graphics need to be drawn (which can provide huge performance benefits), but you wish to retain all | |
* of the vector instructions for later use (ex. scaling, modifying, or exporting). | |
* | |
* Note that calling store() will force the active path (if any) to be ended in a manner similar to changing | |
* the fill or stroke. | |
* | |
* For example, consider a application where the user draws lines with the mouse. As each line segment (or collection of | |
* segments) are added to a Shape, it can be rasterized using {{#crossLink "DisplayObject/updateCache"}}{{/crossLink}}, | |
* and then stored, so that it can be redrawn at a different scale when the application is resized, or exported to SVG. | |
* | |
* // set up cache: | |
* myShape.cache(0,0,500,500,scale); | |
* | |
* // when the user drags, draw a new line: | |
* myShape.graphics.moveTo(oldX,oldY).lineTo(newX,newY); | |
* // then draw it into the existing cache: | |
* myShape.updateCache("source-over"); | |
* // store the new line, so it isn't redrawn next time: | |
* myShape.store(); | |
* | |
* // then, when the window resizes, we can re-render at a different scale: | |
* // first, unstore all our lines: | |
* myShape.unstore(); | |
* // then cache using the new scale: | |
* myShape.cache(0,0,500,500,newScale); | |
* // finally, store the existing commands again: | |
* myShape.store(); | |
* | |
* @method store | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
**/ | |
p.store = function () { | |
this._updateInstructions(true); | |
this._storeIndex = this._instructions.length; | |
return this; | |
}; | |
/** | |
* Unstores any graphics commands that were previously stored using {{#crossLink "Graphics/store"}}{{/crossLink}} | |
* so that they will be executed in subsequent draw calls. | |
* | |
* @method unstore | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
**/ | |
p.unstore = function () { | |
this._storeIndex = 0; | |
return this; | |
}; | |
/** | |
* Returns a clone of this Graphics instance. Note that the individual command objects are not cloned. | |
* @method clone | |
* @return {Graphics} A clone of the current Graphics instance. | |
**/ | |
p.clone = function () { | |
var o = new Graphics(); | |
o.command = this.command; | |
o._stroke = this._stroke; | |
o._strokeStyle = this._strokeStyle; | |
o._strokeDash = this._strokeDash; | |
o._strokeIgnoreScale = this._strokeIgnoreScale; | |
o._fill = this._fill; | |
o._instructions = this._instructions.slice(); | |
o._commitIndex = this._commitIndex; | |
o._activeInstructions = this._activeInstructions.slice(); | |
o._dirty = this._dirty; | |
o._storeIndex = this._storeIndex; | |
return o; | |
}; | |
/** | |
* Returns a string representation of this object. | |
* @method toString | |
* @return {String} a string representation of the instance. | |
**/ | |
p.toString = function () { | |
return "[Graphics]"; | |
}; | |
// tiny API: | |
/** | |
* Shortcut to moveTo. | |
* @method mt | |
* @param {Number} x The x coordinate the drawing point should move to. | |
* @param {Number} y The y coordinate the drawing point should move to. | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls). | |
* @chainable | |
* @protected | |
**/ | |
p.mt = p.moveTo; | |
/** | |
* Shortcut to lineTo. | |
* @method lt | |
* @param {Number} x The x coordinate the drawing point should draw to. | |
* @param {Number} y The y coordinate the drawing point should draw to. | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
* @protected | |
**/ | |
p.lt = p.lineTo; | |
/** | |
* Shortcut to arcTo. | |
* @method at | |
* @param {Number} x1 | |
* @param {Number} y1 | |
* @param {Number} x2 | |
* @param {Number} y2 | |
* @param {Number} radius | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
* @protected | |
**/ | |
p.at = p.arcTo; | |
/** | |
* Shortcut to bezierCurveTo. | |
* @method bt | |
* @param {Number} cp1x | |
* @param {Number} cp1y | |
* @param {Number} cp2x | |
* @param {Number} cp2y | |
* @param {Number} x | |
* @param {Number} y | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
* @protected | |
**/ | |
p.bt = p.bezierCurveTo; | |
/** | |
* Shortcut to quadraticCurveTo / curveTo. | |
* @method qt | |
* @param {Number} cpx | |
* @param {Number} cpy | |
* @param {Number} x | |
* @param {Number} y | |
* @protected | |
* @chainable | |
**/ | |
p.qt = p.quadraticCurveTo; | |
/** | |
* Shortcut to arc. | |
* @method a | |
* @param {Number} x | |
* @param {Number} y | |
* @param {Number} radius | |
* @param {Number} startAngle Measured in radians. | |
* @param {Number} endAngle Measured in radians. | |
* @param {Boolean} anticlockwise | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @protected | |
* @chainable | |
**/ | |
p.a = p.arc; | |
/** | |
* Shortcut to rect. | |
* @method r | |
* @param {Number} x | |
* @param {Number} y | |
* @param {Number} w Width of the rectangle | |
* @param {Number} h Height of the rectangle | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
* @protected | |
**/ | |
p.r = p.rect; | |
/** | |
* Shortcut to closePath. | |
* @method cp | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
* @protected | |
**/ | |
p.cp = p.closePath; | |
/** | |
* Shortcut to clear. | |
* @method c | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
* @protected | |
**/ | |
p.c = p.clear; | |
/** | |
* Shortcut to beginFill. | |
* @method f | |
* @param {String} color A CSS compatible color value (ex. "red", "#FF0000", or "rgba(255,0,0,0.5)"). Setting to | |
* null will result in no fill. | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
* @protected | |
**/ | |
p.f = p.beginFill; | |
/** | |
* Shortcut to beginLinearGradientFill. | |
* @method lf | |
* @param {Array} colors An array of CSS compatible color values. For example, ["#F00","#00F"] would define a gradient | |
* drawing from red to blue. | |
* @param {Array} ratios An array of gradient positions which correspond to the colors. For example, [0.1, 0.9] would draw | |
* the first color to 10% then interpolating to the second color at 90%. | |
* @param {Number} x0 The position of the first point defining the line that defines the gradient direction and size. | |
* @param {Number} y0 The position of the first point defining the line that defines the gradient direction and size. | |
* @param {Number} x1 The position of the second point defining the line that defines the gradient direction and size. | |
* @param {Number} y1 The position of the second point defining the line that defines the gradient direction and size. | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
* @protected | |
**/ | |
p.lf = p.beginLinearGradientFill; | |
/** | |
* Shortcut to beginRadialGradientFill. | |
* @method rf | |
* @param {Array} colors An array of CSS compatible color values. For example, ["#F00","#00F"] would define | |
* a gradient drawing from red to blue. | |
* @param {Array} ratios An array of gradient positions which correspond to the colors. For example, [0.1, | |
* 0.9] would draw the first color to 10% then interpolating to the second color at 90%. | |
* @param {Number} x0 Center position of the inner circle that defines the gradient. | |
* @param {Number} y0 Center position of the inner circle that defines the gradient. | |
* @param {Number} r0 Radius of the inner circle that defines the gradient. | |
* @param {Number} x1 Center position of the outer circle that defines the gradient. | |
* @param {Number} y1 Center position of the outer circle that defines the gradient. | |
* @param {Number} r1 Radius of the outer circle that defines the gradient. | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
* @protected | |
**/ | |
p.rf = p.beginRadialGradientFill; | |
/** | |
* Shortcut to beginBitmapFill. | |
* @method bf | |
* @param {HTMLImageElement | HTMLCanvasElement | HTMLVideoElement} image The Image, Canvas, or Video object to use | |
* as the pattern. | |
* @param {String} repetition Optional. Indicates whether to repeat the image in the fill area. One of "repeat", | |
* "repeat-x", "repeat-y", or "no-repeat". Defaults to "repeat". Note that Firefox does not support "repeat-x" or | |
* "repeat-y" (latest tests were in FF 20.0), and will default to "repeat". | |
* @param {Matrix2D} matrix Optional. Specifies a transformation matrix for the bitmap fill. This transformation | |
* will be applied relative to the parent transform. | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
* @protected | |
**/ | |
p.bf = p.beginBitmapFill; | |
/** | |
* Shortcut to endFill. | |
* @method ef | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
* @protected | |
**/ | |
p.ef = p.endFill; | |
/** | |
* Shortcut to setStrokeStyle. | |
* @method ss | |
* @param {Number} thickness The width of the stroke. | |
* @param {String | Number} [caps=0] Indicates the type of caps to use at the end of lines. One of butt, | |
* round, or square. Defaults to "butt". Also accepts the values 0 (butt), 1 (round), and 2 (square) for use with | |
* the tiny API. | |
* @param {String | Number} [joints=0] Specifies the type of joints that should be used where two lines meet. | |
* One of bevel, round, or miter. Defaults to "miter". Also accepts the values 0 (miter), 1 (round), and 2 (bevel) | |
* for use with the tiny API. | |
* @param {Number} [miterLimit=10] If joints is set to "miter", then you can specify a miter limit ratio which | |
* controls at what point a mitered joint will be clipped. | |
* @param {Boolean} [ignoreScale=false] If true, the stroke will be drawn at the specified thickness regardless | |
* of active transformations. | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
* @protected | |
**/ | |
p.ss = p.setStrokeStyle; | |
/** | |
* Shortcut to setStrokeDash. | |
* @method sd | |
* @param {Array} [segments] An array specifying the dash pattern, alternating between line and gap. | |
* For example, [20,10] would create a pattern of 20 pixel lines with 10 pixel gaps between them. | |
* Passing null or an empty array will clear any existing dash. | |
* @param {Number} [offset=0] The offset of the dash pattern. For example, you could increment this value to create a "marching ants" effect. | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
* @protected | |
**/ | |
p.sd = p.setStrokeDash; | |
/** | |
* Shortcut to beginStroke. | |
* @method s | |
* @param {String} color A CSS compatible color value (ex. "#FF0000", "red", or "rgba(255,0,0,0.5)"). Setting to | |
* null will result in no stroke. | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
* @protected | |
**/ | |
p.s = p.beginStroke; | |
/** | |
* Shortcut to beginLinearGradientStroke. | |
* @method ls | |
* @param {Array} colors An array of CSS compatible color values. For example, ["#F00","#00F"] would define | |
* a gradient drawing from red to blue. | |
* @param {Array} ratios An array of gradient positions which correspond to the colors. For example, [0.1, | |
* 0.9] would draw the first color to 10% then interpolating to the second color at 90%. | |
* @param {Number} x0 The position of the first point defining the line that defines the gradient direction and size. | |
* @param {Number} y0 The position of the first point defining the line that defines the gradient direction and size. | |
* @param {Number} x1 The position of the second point defining the line that defines the gradient direction and size. | |
* @param {Number} y1 The position of the second point defining the line that defines the gradient direction and size. | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
* @protected | |
**/ | |
p.ls = p.beginLinearGradientStroke; | |
/** | |
* Shortcut to beginRadialGradientStroke. | |
* @method rs | |
* @param {Array} colors An array of CSS compatible color values. For example, ["#F00","#00F"] would define | |
* a gradient drawing from red to blue. | |
* @param {Array} ratios An array of gradient positions which correspond to the colors. For example, [0.1, | |
* 0.9] would draw the first color to 10% then interpolating to the second color at 90%, then draw the second color | |
* to 100%. | |
* @param {Number} x0 Center position of the inner circle that defines the gradient. | |
* @param {Number} y0 Center position of the inner circle that defines the gradient. | |
* @param {Number} r0 Radius of the inner circle that defines the gradient. | |
* @param {Number} x1 Center position of the outer circle that defines the gradient. | |
* @param {Number} y1 Center position of the outer circle that defines the gradient. | |
* @param {Number} r1 Radius of the outer circle that defines the gradient. | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
* @protected | |
**/ | |
p.rs = p.beginRadialGradientStroke; | |
/** | |
* Shortcut to beginBitmapStroke. | |
* @method bs | |
* @param {HTMLImageElement | HTMLCanvasElement | HTMLVideoElement} image The Image, Canvas, or Video object to use | |
* as the pattern. | |
* @param {String} [repetition=repeat] Optional. Indicates whether to repeat the image in the fill area. One of | |
* "repeat", "repeat-x", "repeat-y", or "no-repeat". Defaults to "repeat". | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
* @protected | |
**/ | |
p.bs = p.beginBitmapStroke; | |
/** | |
* Shortcut to endStroke. | |
* @method es | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
* @protected | |
**/ | |
p.es = p.endStroke; | |
/** | |
* Shortcut to drawRect. | |
* @method dr | |
* @param {Number} x | |
* @param {Number} y | |
* @param {Number} w Width of the rectangle | |
* @param {Number} h Height of the rectangle | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
* @protected | |
**/ | |
p.dr = p.drawRect; | |
/** | |
* Shortcut to drawRoundRect. | |
* @method rr | |
* @param {Number} x | |
* @param {Number} y | |
* @param {Number} w | |
* @param {Number} h | |
* @param {Number} radius Corner radius. | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
* @protected | |
**/ | |
p.rr = p.drawRoundRect; | |
/** | |
* Shortcut to drawRoundRectComplex. | |
* @method rc | |
* @param {Number} x The horizontal coordinate to draw the round rect. | |
* @param {Number} y The vertical coordinate to draw the round rect. | |
* @param {Number} w The width of the round rect. | |
* @param {Number} h The height of the round rect. | |
* @param {Number} radiusTL Top left corner radius. | |
* @param {Number} radiusTR Top right corner radius. | |
* @param {Number} radiusBR Bottom right corner radius. | |
* @param {Number} radiusBL Bottom left corner radius. | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
* @protected | |
**/ | |
p.rc = p.drawRoundRectComplex; | |
/** | |
* Shortcut to drawCircle. | |
* @method dc | |
* @param {Number} x x coordinate center point of circle. | |
* @param {Number} y y coordinate center point of circle. | |
* @param {Number} radius Radius of circle. | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
* @protected | |
**/ | |
p.dc = p.drawCircle; | |
/** | |
* Shortcut to drawEllipse. | |
* @method de | |
* @param {Number} x The left coordinate point of the ellipse. Note that this is different from {{#crossLink "Graphics/drawCircle"}}{{/crossLink}} | |
* which draws from center. | |
* @param {Number} y The top coordinate point of the ellipse. Note that this is different from {{#crossLink "Graphics/drawCircle"}}{{/crossLink}} | |
* which draws from the center. | |
* @param {Number} w The height (horizontal diameter) of the ellipse. The horizontal radius will be half of this | |
* number. | |
* @param {Number} h The width (vertical diameter) of the ellipse. The vertical radius will be half of this number. | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
* @protected | |
**/ | |
p.de = p.drawEllipse; | |
/** | |
* Shortcut to drawPolyStar. | |
* @method dp | |
* @param {Number} x Position of the center of the shape. | |
* @param {Number} y Position of the center of the shape. | |
* @param {Number} radius The outer radius of the shape. | |
* @param {Number} sides The number of points on the star or sides on the polygon. | |
* @param {Number} pointSize The depth or "pointy-ness" of the star points. A pointSize of 0 will draw a regular | |
* polygon (no points), a pointSize of 1 will draw nothing because the points are infinitely pointy. | |
* @param {Number} angle The angle of the first point / corner. For example a value of 0 will draw the first point | |
* directly to the right of the center. | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
* @protected | |
**/ | |
p.dp = p.drawPolyStar; | |
/** | |
* Shortcut to decodePath. | |
* @method p | |
* @param {String} str The path string to decode. | |
* @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) | |
* @chainable | |
* @protected | |
**/ | |
p.p = p.decodePath; | |
// private methods: | |
/** | |
* @method _updateInstructions | |
* @param commit | |
* @protected | |
**/ | |
p._updateInstructions = function (commit) { | |
var instr = this._instructions, | |
active = this._activeInstructions, | |
commitIndex = this._commitIndex; | |
if (this._dirty && active.length) { | |
instr.length = commitIndex; // remove old, uncommitted commands | |
instr.push(Graphics.beginCmd); | |
var l = active.length, | |
ll = instr.length; | |
instr.length = ll + l; | |
for (var i = 0; i < l; i++) { | |
instr[i + ll] = active[i]; | |
} | |
if (this._fill) { | |
instr.push(this._fill); | |
} | |
if (this._stroke) { | |
// doesn't need to be re-applied if it hasn't changed. | |
if (this._strokeDash !== this._oldStrokeDash) { | |
instr.push(this._strokeDash); | |
} | |
if (this._strokeStyle !== this._oldStrokeStyle) { | |
instr.push(this._strokeStyle); | |
} | |
if (commit) { | |
this._oldStrokeStyle = this._strokeStyle; | |
this._oldStrokeDash = this._strokeDash; | |
} | |
instr.push(this._stroke); | |
} | |
this._dirty = false; | |
} | |
if (commit) { | |
active.length = 0; | |
this._commitIndex = instr.length; | |
} | |
}; | |
/** | |
* @method _setFill | |
* @param fill | |
* @protected | |
**/ | |
p._setFill = function (fill) { | |
this._updateInstructions(true); | |
this.command = this._fill = fill; | |
return this; | |
}; | |
/** | |
* @method _setStroke | |
* @param stroke | |
* @protected | |
**/ | |
p._setStroke = function (stroke) { | |
this._updateInstructions(true); | |
if ((this.command = this._stroke = stroke)) { | |
stroke.ignoreScale = this._strokeIgnoreScale; | |
} | |
return this; | |
}; | |
// Command Objects: | |
/** | |
* @namespace Graphics | |
*/ | |
/** | |
* Graphics command object. See {{#crossLink "Graphics/lineTo"}}{{/crossLink}} and {{#crossLink "Graphics/append"}}{{/crossLink}} for more information. See {{#crossLink "Graphics"}}{{/crossLink}} and {{#crossLink "Graphics/append"}}{{/crossLink}} for more information. | |
* @class LineTo | |
* @constructor | |
* @param {Number} x | |
* @param {Number} y | |
**/ | |
/** | |
* @property x | |
* @type Number | |
*/ | |
/** | |
* @property y | |
* @type Number | |
*/ | |
/** | |
* Execute the Graphics command in the provided Canvas context. | |
* @method exec | |
* @param {CanvasRenderingContext2D} ctx The canvas rendering context | |
*/ | |
( | |
G.LineTo = function (x, y) { | |
this.x = x; | |
this.y = y; | |
} | |
).prototype.exec = function (ctx) { | |
ctx.lineTo(this.x, this.y); | |
}; | |
/** | |
* Graphics command object. See {{#crossLink "Graphics/moveTo"}}{{/crossLink}} and {{#crossLink "Graphics/append"}}{{/crossLink}} for more information. | |
* @class MoveTo | |
* @constructor | |
* @param {Number} x | |
* @param {Number} y | |
**/ | |
/** | |
* @property x | |
* @type Number | |
*/ | |
/** | |
* @property y | |
* @type Number | |
*/ | |
/** | |
* @method exec | |
* @param {CanvasRenderingContext2D} ctx | |
*/ | |
( | |
G.MoveTo = function (x, y) { | |
this.x = x; | |
this.y = y; | |
} | |
).prototype.exec = function (ctx) { | |
ctx.moveTo(this.x, this.y); | |
}; | |
/** | |
* Graphics command object. See {{#crossLink "Graphics/arcTo"}}{{/crossLink}} and {{#crossLink "Graphics/append"}}{{/crossLink}} for more information. | |
* @class ArcTo | |
* @constructor | |
* @param {Number} x1 | |
* @param {Number} y1 | |
* @param {Number} x2 | |
* @param {Number} y2 | |
* @param {Number} radius | |
**/ | |
/** | |
* @property x1 | |
* @type Number | |
*/ | |
/** | |
* @property y1 | |
* @type Number | |
*/ | |
/** | |
* @property x2 | |
* @type Number | |
*/ | |
/** | |
* @property y2 | |
* @type Number | |
*/ | |
/** | |
* @property radius | |
* @type Number | |
*/ | |
/** | |
* Execute the Graphics command in the provided Canvas context. | |
* @method exec | |
* @param {CanvasRenderingContext2D} ctx The canvas rendering context | |
*/ | |
( | |
G.ArcTo = function (x1, y1, x2, y2, radius) { | |
this.x1 = x1; | |
this.y1 = y1; | |
this.x2 = x2; | |
this.y2 = y2; | |
this.radius = radius; | |
} | |
).prototype.exec = function (ctx) { | |
ctx.arcTo(this.x1, this.y1, this.x2, this.y2, this.radius); | |
}; | |
/** | |
* Graphics command object. See {{#crossLink "Graphics/arc"}}{{/crossLink}} and {{#crossLink "Graphics/append"}}{{/crossLink}} for more information. | |
* @class Arc | |
* @constructor | |
* @param {Number} x | |
* @param {Number} y | |
* @param {Number} radius | |
* @param {Number} startAngle | |
* @param {Number} endAngle | |
* @param {Number} anticlockwise | |
**/ | |
/** | |
* @property x | |
* @type Number | |
*/ | |
/** | |
* @property y | |
* @type Number | |
*/ | |
/** | |
* @property radius | |
* @type Number | |
*/ | |
/** | |
* @property startAngle | |
* @type Number | |
*/ | |
/** | |
* @property endAngle | |
* @type Number | |
*/ | |
/** | |
* @property anticlockwise | |
* @type Number | |
*/ | |
/** | |
* Execute the Graphics command in the provided Canvas context. | |
* @method exec | |
* @param {CanvasRenderingContext2D} ctx The canvas rendering context | |
*/ | |
( | |
G.Arc = function (x, y, radius, startAngle, endAngle, anticlockwise) { | |
this.x = x; | |
this.y = y; | |
this.radius = radius; | |
this.startAngle = startAngle; | |
this.endAngle = endAngle; | |
this.anticlockwise = !!anticlockwise; | |
} | |
).prototype.exec = function (ctx) { | |
ctx.arc( | |
this.x, | |
this.y, | |
this.radius, | |
this.startAngle, | |
this.endAngle, | |
this.anticlockwise | |
); | |
}; | |
/** | |
* Graphics command object. See {{#crossLink "Graphics/quadraticCurveTo"}}{{/crossLink}} and {{#crossLink "Graphics/append"}}{{/crossLink}} for more information. | |
* @class QuadraticCurveTo | |
* @constructor | |
* @param {Number} cpx | |
* @param {Number} cpy | |
* @param {Number} x | |
* @param {Number} y | |
**/ | |
/** | |
* @property cpx | |
* @type Number | |
*/ | |
/** | |
* @property cpy | |
* @type Number | |
*/ | |
/** | |
* @property x | |
* @type Number | |
*/ | |
/** | |
* @property y | |
* @type Number | |
*/ | |
/** | |
* Execute the Graphics command in the provided Canvas context. | |
* @method exec | |
* @param {CanvasRenderingContext2D} ctx The canvas rendering context | |
*/ | |
( | |
G.QuadraticCurveTo = function (cpx, cpy, x, y) { | |
this.cpx = cpx; | |
this.cpy = cpy; | |
this.x = x; | |
this.y = y; | |
} | |
).prototype.exec = function (ctx) { | |
ctx.quadraticCurveTo(this.cpx, this.cpy, this.x, this.y); | |
}; | |
/** | |
* Graphics command object. See {{#crossLink "Graphics/bezierCurveTo"}}{{/crossLink}} and {{#crossLink "Graphics/append"}}{{/crossLink}} for more information. | |
* @class BezierCurveTo | |
* @constructor | |
* @param {Number} cp1x | |
* @param {Number} cp1y | |
* @param {Number} cp2x | |
* @param {Number} cp2y | |
* @param {Number} x | |
* @param {Number} y | |
**/ | |
/** | |
* @property cp1x | |
* @type Number | |
*/ | |
/** | |
* @property cp1y | |
* @type Number | |
*/ | |
/** | |
* @property cp2x | |
* @type Number | |
*/ | |
/** | |
* @property cp2y | |
* @type Number | |
*/ | |
/** | |
* @property x | |
* @type Number | |
*/ | |
/** | |
* @property y | |
* @type Number | |
*/ | |
/** | |
* Execute the Graphics command in the provided Canvas context. | |
* @method exec | |
* @param {CanvasRenderingContext2D} ctx The canvas rendering context | |
*/ | |
( | |
G.BezierCurveTo = function (cp1x, cp1y, cp2x, cp2y, x, y) { | |
this.cp1x = cp1x; | |
this.cp1y = cp1y; | |
this.cp2x = cp2x; | |
this.cp2y = cp2y; | |
this.x = x; | |
this.y = y; | |
} | |
).prototype.exec = function (ctx) { | |
ctx.bezierCurveTo( | |
this.cp1x, | |
this.cp1y, | |
this.cp2x, | |
this.cp2y, | |
this.x, | |
this.y | |
); | |
}; | |
/** | |
* Graphics command object. See {{#crossLink "Graphics/rect"}}{{/crossLink}} and {{#crossLink "Graphics/append"}}{{/crossLink}} for more information. | |
* @class Rect | |
* @constructor | |
* @param {Number} x | |
* @param {Number} y | |
* @param {Number} w | |
* @param {Number} h | |
**/ | |
/** | |
* @property x | |
* @type Number | |
*/ | |
/** | |
* @property y | |
* @type Number | |
*/ | |
/** | |
* @property w | |
* @type Number | |
*/ | |
/** | |
* @property h | |
* @type Number | |
*/ | |
/** | |
* Execute the Graphics command in the provided Canvas context. | |
* @method exec | |
* @param {CanvasRenderingContext2D} ctx The canvas rendering context | |
*/ | |
( | |
G.Rect = function (x, y, w, h) { | |
this.x = x; | |
this.y = y; | |
this.w = w; | |
this.h = h; | |
} | |
).prototype.exec = function (ctx) { | |
ctx.rect(this.x, this.y, this.w, this.h); | |
}; | |
/** | |
* Graphics command object. See {{#crossLink "Graphics/closePath"}}{{/crossLink}} and {{#crossLink "Graphics/append"}}{{/crossLink}} for more information. | |
* @class ClosePath | |
* @constructor | |
**/ | |
/** | |
* Execute the Graphics command in the provided Canvas context. | |
* @method exec | |
* @param {CanvasRenderingContext2D} ctx The canvas rendering context | |
*/ | |
(G.ClosePath = function () {}).prototype.exec = function (ctx) { | |
ctx.closePath(); | |
}; | |
/** | |
* Graphics command object to begin a new path. See {{#crossLink "Graphics"}}{{/crossLink}} and {{#crossLink "Graphics/append"}}{{/crossLink}} for more information. | |
* @class BeginPath | |
* @constructor | |
**/ | |
/** | |
* Execute the Graphics command in the provided Canvas context. | |
* @method exec | |
* @param {CanvasRenderingContext2D} ctx The canvas rendering context | |
*/ | |
(G.BeginPath = function () {}).prototype.exec = function (ctx) { | |
ctx.beginPath(); | |
}; | |
/** | |
* Graphics command object. See {{#crossLink "Graphics/beginFill"}}{{/crossLink}} and {{#crossLink "Graphics/append"}}{{/crossLink}} for more information. | |
* @class Fill | |
* @constructor | |
* @param {Object} style A valid Context2D fillStyle. | |
* @param {Matrix2D} matrix | |
**/ | |
/** | |
* A valid Context2D fillStyle. | |
* @property style | |
* @type Object | |
*/ | |
/** | |
* @property matrix | |
* @type Matrix2D | |
*/ | |
/** | |
* Execute the Graphics command in the provided Canvas context. | |
* @method exec | |
* @param {CanvasRenderingContext2D} ctx The canvas rendering context | |
*/ | |
p = (G.Fill = function (style, matrix) { | |
this.style = style; | |
this.matrix = matrix; | |
}).prototype; | |
p.exec = function (ctx) { | |
if (!this.style) { | |
return; | |
} | |
ctx.fillStyle = this.style; | |
var mtx = this.matrix; | |
if (mtx) { | |
ctx.save(); | |
ctx.transform(mtx.a, mtx.b, mtx.c, mtx.d, mtx.tx, mtx.ty); | |
} | |
ctx.fill(); | |
if (mtx) { | |
ctx.restore(); | |
} | |
}; | |
/** | |
* Creates a linear gradient style and assigns it to {{#crossLink "Fill/style:property"}}{{/crossLink}}. | |
* See {{#crossLink "Graphics/beginLinearGradientFill"}}{{/crossLink}} for more information. | |
* @method linearGradient | |
* @param {Array} colors | |
* | |
* @param {Array} ratios | |
* @param {Number} x0 | |
* @param {Number} y0 | |
* @param {Number} x1 | |
* @param {Number} y1 | |
* @return {Fill} Returns this Fill object for chaining or assignment. | |
*/ | |
p.linearGradient = function (colors, ratios, x0, y0, x1, y1) { | |
var o = (this.style = Graphics._ctx.createLinearGradient(x0, y0, x1, y1)); | |
for (var i = 0, l = colors.length; i < l; i++) { | |
o.addColorStop(ratios[i], colors[i]); | |
} | |
o.props = { | |
colors: colors, | |
ratios: ratios, | |
x0: x0, | |
y0: y0, | |
x1: x1, | |
y1: y1, | |
type: "linear", | |
}; | |
return this; | |
}; | |
/** | |
* Creates a radial gradient style and assigns it to {{#crossLink "Fill/style:property"}}{{/crossLink}}. | |
* See {{#crossLink "Graphics/beginRadialGradientFill"}}{{/crossLink}} for more information. | |
* @method radialGradient | |
* @param {Array} colors | |
* @param {Array} ratios | |
* @param {Number} x0 | |
* @param {Number} y0 | |
* @param {Number} r0 | |
* @param {Number} x1 | |
* @param {Number} y1 | |
* @param {Number} r1 | |
* @return {Fill} Returns this Fill object for chaining or assignment. | |
*/ | |
p.radialGradient = function (colors, ratios, x0, y0, r0, x1, y1, r1) { | |
var o = (this.style = Graphics._ctx.createRadialGradient( | |
x0, | |
y0, | |
r0, | |
x1, | |
y1, | |
r1 | |
)); | |
for (var i = 0, l = colors.length; i < l; i++) { | |
o.addColorStop(ratios[i], colors[i]); | |
} | |
o.props = { | |
colors: colors, | |
ratios: ratios, | |
x0: x0, | |
y0: y0, | |
r0: r0, | |
x1: x1, | |
y1: y1, | |
r1: r1, | |
type: "radial", | |
}; | |
return this; | |
}; | |
/** | |
* Creates a bitmap fill style and assigns it to the {{#crossLink "Fill/style:property"}}{{/crossLink}}. | |
* See {{#crossLink "Graphics/beginBitmapFill"}}{{/crossLink}} for more information. | |
* @method bitmap | |
* @param {HTMLImageElement | HTMLCanvasElement | HTMLVideoElement} image Must be loaded prior to creating a bitmap fill, or the fill will be empty. | |
* @param {String} [repetition] One of: repeat, repeat-x, repeat-y, or no-repeat. | |
* @return {Fill} Returns this Fill object for chaining or assignment. | |
*/ | |
p.bitmap = function (image, repetition) { | |
if (image.naturalWidth || image.getContext || image.readyState >= 2) { | |
var o = (this.style = Graphics._ctx.createPattern( | |
image, | |
repetition || "" | |
)); | |
o.props = { image: image, repetition: repetition, type: "bitmap" }; | |
} | |
return this; | |
}; | |
p.path = false; | |
/** | |
* Graphics command object. See {{#crossLink "Graphics/beginStroke"}}{{/crossLink}} and {{#crossLink "Graphics/append"}}{{/crossLink}} for more information. | |
* @class Stroke | |
* @constructor | |
* @param {Object} style A valid Context2D fillStyle. | |
* @param {Boolean} ignoreScale | |
**/ | |
/** | |
* A valid Context2D strokeStyle. | |
* @property style | |
* @type Object | |
*/ | |
/** | |
* @property ignoreScale | |
* @type Boolean | |
*/ | |
/** | |
* Execute the Graphics command in the provided Canvas context. | |
* @method exec | |
* @param {CanvasRenderingContext2D} ctx The canvas rendering context | |
*/ | |
p = (G.Stroke = function (style, ignoreScale) { | |
this.style = style; | |
this.ignoreScale = ignoreScale; | |
}).prototype; | |
p.exec = function (ctx) { | |
if (!this.style) { | |
return; | |
} | |
ctx.strokeStyle = this.style; | |
if (this.ignoreScale) { | |
ctx.save(); | |
ctx.setTransform(1, 0, 0, 1, 0, 0); | |
} | |
ctx.stroke(); | |
if (this.ignoreScale) { | |
ctx.restore(); | |
} | |
}; | |
/** | |
* Creates a linear gradient style and assigns it to {{#crossLink "Stroke/style:property"}}{{/crossLink}}. | |
* See {{#crossLink "Graphics/beginLinearGradientStroke"}}{{/crossLink}} for more information. | |
* @method linearGradient | |
* @param {Array} colors | |
* @param {Array} ratios | |
* @param {Number} x0 | |
* @param {Number} y0 | |
* @param {Number} x1 | |
* @param {Number} y1 | |
* @return {Fill} Returns this Stroke object for chaining or assignment. | |
*/ | |
p.linearGradient = G.Fill.prototype.linearGradient; | |
/** | |
* Creates a radial gradient style and assigns it to {{#crossLink "Stroke/style:property"}}{{/crossLink}}. | |
* See {{#crossLink "Graphics/beginRadialGradientStroke"}}{{/crossLink}} for more information. | |
* @method radialGradient | |
* @param {Array} colors | |
* @param {Array} ratios | |
* @param {Number} x0 | |
* @param {Number} y0 | |
* @param {Number} r0 | |
* @param {Number} x1 | |
* @param {Number} y1 | |
* @param {Number} r1 | |
* @return {Fill} Returns this Stroke object for chaining or assignment. | |
*/ | |
p.radialGradient = G.Fill.prototype.radialGradient; | |
/** | |
* Creates a bitmap fill style and assigns it to {{#crossLink "Stroke/style:property"}}{{/crossLink}}. | |
* See {{#crossLink "Graphics/beginBitmapStroke"}}{{/crossLink}} for more information. | |
* @method bitmap | |
* @param {HTMLImageElement} image | |
* @param {String} [repetition] One of: repeat, repeat-x, repeat-y, or no-repeat. | |
* @return {Fill} Returns this Stroke object for chaining or assignment. | |
*/ | |
p.bitmap = G.Fill.prototype.bitmap; | |
p.path = false; | |
/** | |
* Graphics command object. See {{#crossLink "Graphics/setStrokeStyle"}}{{/crossLink}} and {{#crossLink "Graphics/append"}}{{/crossLink}} for more information. | |
* @class StrokeStyle | |
* @constructor | |
* @param {Number} width | |
* @param {String} [caps=butt] | |
* @param {String} [joints=miter] | |
* @param {Number} [miterLimit=10] | |
* @param {Boolean} [ignoreScale=false] | |
**/ | |
/** | |
* @property width | |
* @type Number | |
*/ | |
/** | |
* One of: butt, round, square | |
* @property caps | |
* @type String | |
*/ | |
/** | |
* One of: round, bevel, miter | |
* @property joints | |
* @type String | |
*/ | |
/** | |
* @property miterLimit | |
* @type Number | |
*/ | |
/** | |
* Execute the Graphics command in the provided Canvas context. | |
* @method exec | |
* @param {CanvasRenderingContext2D} ctx The canvas rendering context | |
*/ | |
p = (G.StrokeStyle = function (width, caps, joints, miterLimit, ignoreScale) { | |
this.width = width; | |
this.caps = caps; | |
this.joints = joints; | |
this.miterLimit = miterLimit; | |
this.ignoreScale = ignoreScale; | |
}).prototype; | |
p.exec = function (ctx) { | |
ctx.lineWidth = this.width == null ? "1" : this.width; | |
ctx.lineCap = | |
this.caps == null | |
? "butt" | |
: isNaN(this.caps) | |
? this.caps | |
: Graphics.STROKE_CAPS_MAP[this.caps]; | |
ctx.lineJoin = | |
this.joints == null | |
? "miter" | |
: isNaN(this.joints) | |
? this.joints | |
: Graphics.STROKE_JOINTS_MAP[this.joints]; | |
ctx.miterLimit = this.miterLimit == null ? "10" : this.miterLimit; | |
ctx.ignoreScale = this.ignoreScale == null ? false : this.ignoreScale; | |
}; | |
p.path = false; | |
/** | |
* Graphics command object. See {{#crossLink "Graphics/setStrokeDash"}}{{/crossLink}} and {{#crossLink "Graphics/append"}}{{/crossLink}} for more information. | |
* @class StrokeDash | |
* @constructor | |
* @param {Array} [segments] | |
* @param {Number} [offset=0] | |
**/ | |
/** | |
* @property segments | |
* @type Array | |
*/ | |
/** | |
* @property offset | |
* @type Number | |
*/ | |
/** | |
* Execute the Graphics command in the provided Canvas context. | |
* @method exec | |
* @param {CanvasRenderingContext2D} ctx The canvas rendering context | |
*/ | |
( | |
G.StrokeDash = function (segments, offset) { | |
this.segments = segments; | |
this.offset = offset || 0; | |
} | |
).prototype.exec = function (ctx) { | |
if (ctx.setLineDash) { | |
// feature detection. | |
ctx.setLineDash(this.segments || G.StrokeDash.EMPTY_SEGMENTS); // instead of [] to reduce churn. | |
ctx.lineDashOffset = this.offset || 0; | |
} | |
}; | |
/** | |
* The default value for segments (ie. no dash). | |
* @property EMPTY_SEGMENTS | |
* @static | |
* @final | |
* @readonly | |
* @protected | |
* @type {Array} | |
**/ | |
G.StrokeDash.EMPTY_SEGMENTS = []; | |
/** | |
* Graphics command object. See {{#crossLink "Graphics/drawRoundRectComplex"}}{{/crossLink}} and {{#crossLink "Graphics/append"}}{{/crossLink}} for more information. | |
* @class RoundRect | |
* @constructor | |
* @param {Number} x | |
* @param {Number} y | |
* @param {Number} w | |
* @param {Number} h | |
* @param {Number} radiusTL | |
* @param {Number} radiusTR | |
* @param {Number} radiusBR | |
* @param {Number} radiusBL | |
**/ | |
/** | |
* @property x | |
* @type Number | |
*/ | |
/** | |
* @property y | |
* @type Number | |
*/ | |
/** | |
* @property w | |
* @type Number | |
*/ | |
/** | |
* @property h | |
* @type Number | |
*/ | |
/** | |
* @property radiusTL | |
* @type Number | |
*/ | |
/** | |
* @property radiusTR | |
* @type Number | |
*/ | |
/** | |
* @property radiusBR | |
* @type Number | |
*/ | |
/** | |
* @property radiusBL | |
* @type Number | |
*/ | |
/** | |
* Execute the Graphics command in the provided Canvas context. | |
* @method exec | |
* @param {CanvasRenderingContext2D} ctx The canvas rendering context | |
*/ | |
( | |
G.RoundRect = function ( | |
x, | |
y, | |
w, | |
h, | |
radiusTL, | |
radiusTR, | |
radiusBR, | |
radiusBL | |
) { | |
this.x = x; | |
this.y = y; | |
this.w = w; | |
this.h = h; | |
this.radiusTL = radiusTL; | |
this.radiusTR = radiusTR; | |
this.radiusBR = radiusBR; | |
this.radiusBL = radiusBL; | |
} | |
).prototype.exec = function (ctx) { | |
var max = (w < h ? w : h) / 2; | |
var mTL = 0, | |
mTR = 0, | |
mBR = 0, | |
mBL = 0; | |
var x = this.x, | |
y = this.y, | |
w = this.w, | |
h = this.h; | |
var rTL = this.radiusTL, | |
rTR = this.radiusTR, | |
rBR = this.radiusBR, | |
rBL = this.radiusBL; | |
if (rTL < 0) { | |
rTL *= mTL = -1; | |
} | |
if (rTL > max) { | |
rTL = max; | |
} | |
if (rTR < 0) { | |
rTR *= mTR = -1; | |
} | |
if (rTR > max) { | |
rTR = max; | |
} | |
if (rBR < 0) { | |
rBR *= mBR = -1; | |
} | |
if (rBR > max) { | |
rBR = max; | |
} | |
if (rBL < 0) { | |
rBL *= mBL = -1; | |
} | |
if (rBL > max) { | |
rBL = max; | |
} | |
ctx.moveTo(x + w - rTR, y); | |
ctx.arcTo(x + w + rTR * mTR, y - rTR * mTR, x + w, y + rTR, rTR); | |
ctx.lineTo(x + w, y + h - rBR); | |
ctx.arcTo(x + w + rBR * mBR, y + h + rBR * mBR, x + w - rBR, y + h, rBR); | |
ctx.lineTo(x + rBL, y + h); | |
ctx.arcTo(x - rBL * mBL, y + h + rBL * mBL, x, y + h - rBL, rBL); | |
ctx.lineTo(x, y + rTL); | |
ctx.arcTo(x - rTL * mTL, y - rTL * mTL, x + rTL, y, rTL); | |
ctx.closePath(); | |
}; | |
/** | |
* Graphics command object. See {{#crossLink "Graphics/drawCircle"}}{{/crossLink}} and {{#crossLink "Graphics/append"}}{{/crossLink}} for more information. | |
* @class Circle | |
* @constructor | |
* @param {Number} x | |
* @param {Number} y | |
* @param {Number} radius | |
**/ | |
/** | |
* @property x | |
* @type Number | |
*/ | |
/** | |
* @property y | |
* @type Number | |
*/ | |
/** | |
* @property radius | |
* @type Number | |
*/ | |
/** | |
* Execute the Graphics command in the provided Canvas context. | |
* @method exec | |
* @param {CanvasRenderingContext2D} ctx The canvas rendering context | |
*/ | |
( | |
G.Circle = function (x, y, radius) { | |
this.x = x; | |
this.y = y; | |
this.radius = radius; | |
} | |
).prototype.exec = function (ctx) { | |
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2); | |
}; | |
/** | |
* Graphics command object. See {{#crossLink "Graphics/drawEllipse"}}{{/crossLink}} and {{#crossLink "Graphics/append"}}{{/crossLink}} for more information. | |
* @class Ellipse | |
* @constructor | |
* @param {Number} x | |
* @param {Number} y | |
* @param {Number} w | |
* @param {Number} h | |
**/ | |
/** | |
* @property x | |
* @type Number | |
*/ | |
/** | |
* @property y | |
* @type Number | |
*/ | |
/** | |
* @property w | |
* @type Number | |
*/ | |
/** | |
* @property h | |
* @type Number | |
*/ | |
/** | |
* Execute the Graphics command in the provided Canvas context. | |
* @method exec | |
* @param {CanvasRenderingContext2D} ctx The canvas rendering context | |
*/ | |
( | |
G.Ellipse = function (x, y, w, h) { | |
this.x = x; | |
this.y = y; | |
this.w = w; | |
this.h = h; | |
} | |
).prototype.exec = function (ctx) { | |
var x = this.x, | |
y = this.y; | |
var w = this.w, | |
h = this.h; | |
var k = 0.5522848; | |
var ox = (w / 2) * k; | |
var oy = (h / 2) * k; | |
var xe = x + w; | |
var ye = y + h; | |
var xm = x + w / 2; | |
var ym = y + h / 2; | |
ctx.moveTo(x, ym); | |
ctx.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); | |
ctx.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); | |
ctx.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); | |
ctx.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); | |
}; | |
/** | |
* Graphics command object. See {{#crossLink "Graphics/drawPolyStar"}}{{/crossLink}} and {{#crossLink "Graphics/append"}}{{/crossLink}} for more information. | |
* @class PolyStar | |
* @constructor | |
* @param {Number} x | |
* @param {Number} y | |
* @param {Number} radius | |
* @param {Number} sides | |
* @param {Number} pointSize | |
* @param {Number} angle | |
**/ | |
/** | |
* @property x | |
* @type Number | |
*/ | |
/** | |
* @property y | |
* @type Number | |
*/ | |
/** | |
* @property radius | |
* @type Number | |
*/ | |
/** | |
* @property sides | |
* @type Number | |
*/ | |
/** | |
* @property pointSize | |
* @type Number | |
*/ | |
/** | |
* @property angle | |
* @type Number | |
*/ | |
/** | |
* Execute the Graphics command in the provided Canvas context. | |
* @method exec | |
* @param {CanvasRenderingContext2D} ctx The canvas rendering context | |
*/ | |
( | |
G.PolyStar = function (x, y, radius, sides, pointSize, angle) { | |
this.x = x; | |
this.y = y; | |
this.radius = radius; | |
this.sides = sides; | |
this.pointSize = pointSize; | |
this.angle = angle; | |
} | |
).prototype.exec = function (ctx) { | |
var x = this.x, | |
y = this.y; | |
var radius = this.radius; | |
var angle = ((this.angle || 0) / 180) * Math.PI; | |
var sides = this.sides; | |
var ps = 1 - (this.pointSize || 0); | |
var a = Math.PI / sides; | |
ctx.moveTo(x + Math.cos(angle) * radius, y + Math.sin(angle) * radius); | |
for (var i = 0; i < sides; i++) { | |
angle += a; | |
if (ps != 1) { | |
ctx.lineTo( | |
x + Math.cos(angle) * radius * ps, | |
y + Math.sin(angle) * radius * ps | |
); | |
} | |
angle += a; | |
ctx.lineTo(x + Math.cos(angle) * radius, y + Math.sin(angle) * radius); | |
} | |
ctx.closePath(); | |
}; | |
// docced above. | |
Graphics.beginCmd = new G.BeginPath(); // so we don't have to instantiate multiple instances. | |
createjs.Graphics = Graphics; | |
})(); | |
//############################################################################## | |
// DisplayObject.js | |
//############################################################################## | |
this.createjs = this.createjs || {}; | |
(function () { | |
"use strict"; | |
// constructor: | |
/** | |
* DisplayObject is an abstract class that should not be constructed directly. Instead construct subclasses such as | |
* {{#crossLink "Container"}}{{/crossLink}}, {{#crossLink "Bitmap"}}{{/crossLink}}, and {{#crossLink "Shape"}}{{/crossLink}}. | |
* DisplayObject is the base class for all display classes in the EaselJS library. It defines the core properties and | |
* methods that are shared between all display objects, such as transformation properties (x, y, scaleX, scaleY, etc), | |
* caching, and mouse handlers. | |
* @class DisplayObject | |
* @extends EventDispatcher | |
* @constructor | |
**/ | |
function DisplayObject() { | |
this.EventDispatcher_constructor(); | |
// public properties: | |
/** | |
* The alpha (transparency) for this display object. 0 is fully transparent, 1 is fully opaque. | |
* @property alpha | |
* @type {Number} | |
* @default 1 | |
**/ | |
this.alpha = 1; | |
/** | |
* If a cache is active, this returns the canvas that holds the image of this display object. See {{#crossLink "DisplayObject/cache:method"}}{{/crossLink}} | |
* for more information. Use this to display the result of a cache. This will be a HTMLCanvasElement unless special cache rules have been deliberately enabled for this cache. | |
* @property cacheCanvas | |
* @type {HTMLCanvasElement | Object} | |
* @default null | |
* @readonly | |
**/ | |
this.cacheCanvas = null; | |
/** | |
* If a cache has been made, this returns the class that is managing the cacheCanvas and its properties. See {{#crossLink "BitmapCache"}}{{/crossLink}} | |
* for more information. Use this to control, inspect, and change the cache. In special circumstances this may be a modified or subclassed BitmapCache. | |
* @property bitmapCache | |
* @type {BitmapCache} | |
* @default null | |
* @readonly | |
**/ | |
this.bitmapCache = null; | |
/** | |
* Unique ID for this display object. Makes display objects easier for some uses. | |
* @property id | |
* @type {Number} | |
* @default -1 | |
**/ | |
this.id = createjs.UID.get(); | |
/** | |
* Indicates whether to include this object when running mouse interactions. Setting this to `false` for children | |
* of a {{#crossLink "Container"}}{{/crossLink}} will cause events on the Container to not fire when that child is | |
* clicked. Setting this property to `false` does not prevent the {{#crossLink "Container/getObjectsUnderPoint"}}{{/crossLink}} | |
* method from returning the child. | |
* | |
* <strong>Note:</strong> In EaselJS 0.7.0, the mouseEnabled property will not work properly with nested Containers. Please | |
* check out the latest NEXT version in <a href="https://github.com/CreateJS/EaselJS/tree/master/lib">GitHub</a> for an updated version with this issue resolved. The fix will be | |
* provided in the next release of EaselJS. | |
* @property mouseEnabled | |
* @type {Boolean} | |
* @default true | |
**/ | |
this.mouseEnabled = true; | |
/** | |
* If false, the tick will not run on this display object (or its children). This can provide some performance benefits. | |
* In addition to preventing the "tick" event from being dispatched, it will also prevent tick related updates | |
* on some display objects (ex. Sprite & MovieClip frame advancing, and DOMElement display properties). | |
* @property tickEnabled | |
* @type Boolean | |
* @default true | |
**/ | |
this.tickEnabled = true; | |
/** | |
* An optional name for this display object. Included in {{#crossLink "DisplayObject/toString"}}{{/crossLink}} . Useful for | |
* debugging. | |
* @property name | |
* @type {String} | |
* @default null | |
**/ | |
this.name = null; | |
/** | |
* A reference to the {{#crossLink "Container"}}{{/crossLink}} or {{#crossLink "Stage"}}{{/crossLink}} object that | |
* contains this display object, or null if it has not been added | |
* to one. | |
* @property parent | |
* @final | |
* @type {Container} | |
* @default null | |
* @readonly | |
**/ | |
this.parent = null; | |
/** | |
* The left offset for this display object's registration point. For example, to make a 100x100px Bitmap rotate | |
* around its center, you would set regX and {{#crossLink "DisplayObject/regY:property"}}{{/crossLink}} to 50. | |
* Cached object's registration points should be set based on pre-cache conditions, not cached size. | |
* @property regX | |
* @type {Number} | |
* @default 0 | |
**/ | |
this.regX = 0; | |
/** | |
* The y offset for this display object's registration point. For example, to make a 100x100px Bitmap rotate around | |
* its center, you would set {{#crossLink "DisplayObject/regX:property"}}{{/crossLink}} and regY to 50. | |
* Cached object's registration points should be set based on pre-cache conditions, not cached size. | |
* @property regY | |
* @type {Number} | |
* @default 0 | |
**/ | |
this.regY = 0; | |
/** | |
* The rotation in degrees for this display object. | |
* @property rotation | |
* @type {Number} | |
* @default 0 | |
**/ | |
this.rotation = 0; | |
/** | |
* The factor to stretch this display object horizontally. For example, setting scaleX to 2 will stretch the display | |
* object to twice its nominal width. To horizontally flip an object, set the scale to a negative number. | |
* @property scaleX | |
* @type {Number} | |
* @default 1 | |
**/ | |
this.scaleX = 1; | |
/** | |
* The factor to stretch this display object vertically. For example, setting scaleY to 0.5 will stretch the display | |
* object to half its nominal height. To vertically flip an object, set the scale to a negative number. | |
* @property scaleY | |
* @type {Number} | |
* @default 1 | |
**/ | |
this.scaleY = 1; | |
/** | |
* The factor to skew this display object horizontally. | |
* @property skewX | |
* @type {Number} | |
* @default 0 | |
**/ | |
this.skewX = 0; | |
/** | |
* The factor to skew this display object vertically. | |
* @property skewY | |
* @type {Number} | |
* @default 0 | |
**/ | |
this.skewY = 0; | |
/** | |
* A shadow object that defines the shadow to render on this display object. Set to `null` to remove a shadow. If | |
* null, this property is inherited from the parent container. | |
* @property shadow | |
* @type {Shadow} | |
* @default null | |
**/ | |
this.shadow = null; | |
/** | |
* Indicates whether this display object should be rendered to the canvas and included when running the Stage | |
* {{#crossLink "Stage/getObjectsUnderPoint"}}{{/crossLink}} method. | |
* @property visible | |
* @type {Boolean} | |
* @default true | |
**/ | |
this.visible = true; | |
/** | |
* The x (horizontal) position of the display object, relative to its parent. | |
* @property x | |
* @type {Number} | |
* @default 0 | |
**/ | |
this.x = 0; | |
/** The y (vertical) position of the display object, relative to its parent. | |
* @property y | |
* @type {Number} | |
* @default 0 | |
**/ | |
this.y = 0; | |
/** | |
* If set, defines the transformation for this display object, overriding all other transformation properties | |
* (x, y, rotation, scale, skew). | |
* @property transformMatrix | |
* @type {Matrix2D} | |
* @default null | |
**/ | |
this.transformMatrix = null; | |
/** | |
* The composite operation indicates how the pixels of this display object will be composited with the elements | |
* behind it. If `null`, this property is inherited from the parent container. For more information, read the | |
* <a href="https://html.spec.whatwg.org/multipage/scripting.html#dom-context-2d-globalcompositeoperation"> | |
* whatwg spec on compositing</a>. For a list of supported compositeOperation value, visit | |
* <a href="https://drafts.fxtf.org/compositing/">the W3C draft on Compositing and Blending</a>. | |
* @property compositeOperation | |
* @type {String} | |
* @default null | |
**/ | |
this.compositeOperation = null; | |
/** | |
* Indicates whether the display object should be drawn to a whole pixel when | |
* {{#crossLink "Stage/snapToPixelEnabled"}}{{/crossLink}} is true. To enable/disable snapping on whole | |
* categories of display objects, set this value on the prototype (Ex. Text.prototype.snapToPixel = true). | |
* @property snapToPixel | |
* @type {Boolean} | |
* @default true | |
**/ | |
this.snapToPixel = true; | |
/** | |
* An array of Filter objects to apply to this display object. Filters are only applied / updated when {{#crossLink "cache"}}{{/crossLink}} | |
* or {{#crossLink "updateCache"}}{{/crossLink}} is called on the display object, and only apply to the area that is | |
* cached. | |
* @property filters | |
* @type {Array} | |
* @default null | |
**/ | |
this.filters = null; | |
/** | |
* A Shape instance that defines a vector mask (clipping path) for this display object. The shape's transformation | |
* will be applied relative to the display object's parent coordinates (as if it were a child of the parent). | |
* @property mask | |
* @type {Shape} | |
* @default null | |
*/ | |
this.mask = null; | |
/** | |
* A display object that will be tested when checking mouse interactions or testing {{#crossLink "Container/getObjectsUnderPoint"}}{{/crossLink}}. | |
* The hit area will have its transformation applied relative to this display object's coordinate space (as though | |
* the hit test object were a child of this display object and relative to its regX/Y). The hitArea will be tested | |
* using only its own `alpha` value regardless of the alpha value on the target display object, or the target's | |
* ancestors (parents). | |
* | |
* If set on a {{#crossLink "Container"}}{{/crossLink}}, children of the Container will not receive mouse events. | |
* This is similar to setting {{#crossLink "mouseChildren"}}{{/crossLink}} to false. | |
* | |
* Note that hitArea is NOT currently used by the `hitTest()` method, nor is it supported for {{#crossLink "Stage"}}{{/crossLink}}. | |
* @property hitArea | |
* @type {DisplayObject} | |
* @default null | |
*/ | |
this.hitArea = null; | |
/** | |
* A CSS cursor (ex. "pointer", "help", "text", etc) that will be displayed when the user hovers over this display | |
* object. You must enable mouseover events using the {{#crossLink "Stage/enableMouseOver"}}{{/crossLink}} method to | |
* use this property. Setting a non-null cursor on a Container will override the cursor set on its descendants. | |
* @property cursor | |
* @type {String} | |
* @default null | |
*/ | |
this.cursor = null; | |
// private properties: | |
/** | |
* Moved to {{#crossLink "BitmapCache"}}{{/crossLink}} | |
* @property _cacheScale | |
* @protected | |
* @type {Number} | |
* @default 1 | |
* @deprecated | |
**/ | |
/** | |
* Moved to {{#crossLink "BitmapCache"}}{{/crossLink}} | |
* @property _cacheDataURLID | |
* @protected | |
* @type {Number} | |
* @default 0 | |
* @deprecated | |
*/ | |
/** | |
* Moved to {{#crossLink "BitmapCache"}}{{/crossLink}} | |
* @property _cacheDataURL | |
* @protected | |
* @type {String} | |
* @default null | |
* @deprecated | |
*/ | |
/** | |
* @property _props | |
* @protected | |
* @type {DisplayObject} | |
* @default null | |
**/ | |
this._props = new createjs.DisplayProps(); | |
/** | |
* @property _rectangle | |
* @protected | |
* @type {Rectangle} | |
* @default null | |
**/ | |
this._rectangle = new createjs.Rectangle(); | |
/** | |
* @property _bounds | |
* @protected | |
* @type {Rectangle} | |
* @default null | |
**/ | |
this._bounds = null; | |
/** | |
* Where StageGL should look for required display properties, matters only for leaf display objects. Containers | |
* or cached objects won't use this property, it's for native display of terminal elements. | |
* @property _webGLRenderStyle | |
* @protected | |
* @type {number} | |
* @default 0 | |
*/ | |
this._webGLRenderStyle = DisplayObject._StageGL_NONE; | |
} | |
var p = createjs.extend(DisplayObject, createjs.EventDispatcher); | |
// static properties: | |
/** | |
* Listing of mouse event names. Used in _hasMouseEventListener. | |
* @property _MOUSE_EVENTS | |
* @protected | |
* @static | |
* @type {Array} | |
**/ | |
DisplayObject._MOUSE_EVENTS = [ | |
"click", | |
"dblclick", | |
"mousedown", | |
"mouseout", | |
"mouseover", | |
"pressmove", | |
"pressup", | |
"rollout", | |
"rollover", | |
]; | |
/** | |
* Suppresses errors generated when using features like hitTest, mouse events, and {{#crossLink "getObjectsUnderPoint"}}{{/crossLink}} | |
* with cross domain content. | |
* @property suppressCrossDomainErrors | |
* @static | |
* @type {Boolean} | |
* @default false | |
**/ | |
DisplayObject.suppressCrossDomainErrors = false; | |
/** | |
* @property _snapToPixelEnabled | |
* @protected | |
* @static | |
* @type {Boolean} | |
* @default false | |
**/ | |
DisplayObject._snapToPixelEnabled = false; // stage.snapToPixelEnabled is temporarily copied here during a draw to provide global access. | |
/** | |
* Enum like property for determining StageGL render lookup, i.e. where to expect properties. | |
* @property _StageGL_NONE | |
* @protected | |
* @static | |
* @type {number} | |
*/ | |
DisplayObject._StageGL_NONE = 0; | |
/** | |
* Enum like property for determining StageGL render lookup, i.e. where to expect properties. | |
* @property _StageGL_SPRITE | |
* @protected | |
* @static | |
* @type {number} | |
*/ | |
DisplayObject._StageGL_SPRITE = 1; | |
/** | |
* Enum like property for determining StageGL render lookup, i.e. where to expect properties. | |
* @property _StageGL_BITMAP | |
* @protected | |
* @static | |
* @type {number} | |
*/ | |
DisplayObject._StageGL_BITMAP = 2; | |
/** | |
* @property _hitTestCanvas | |
* @type {HTMLCanvasElement | Object} | |
* @static | |
* @protected | |
**/ | |
/** | |
* @property _hitTestContext | |
* @type {CanvasRenderingContext2D} | |
* @static | |
* @protected | |
**/ | |
var canvas = createjs.createCanvas | |
? createjs.createCanvas() | |
: document.createElement("canvas"); // prevent errors on load in browsers without canvas. | |
if (canvas.getContext) { | |
DisplayObject._hitTestCanvas = canvas; | |
DisplayObject._hitTestContext = canvas.getContext("2d"); | |
canvas.width = canvas.height = 1; | |
} | |
// events: | |
/** | |
* Dispatched when the user presses their left mouse button over the display object. See the | |
* {{#crossLink "MouseEvent"}}{{/crossLink}} class for a listing of event properties. | |
* @event mousedown | |
* @since 0.6.0 | |
*/ | |
/** | |
* Dispatched when the user presses their left mouse button and then releases it while over the display object. | |
* See the {{#crossLink "MouseEvent"}}{{/crossLink}} class for a listing of event properties. | |
* @event click | |
* @since 0.6.0 | |
*/ | |
/** | |
* Dispatched when the user double clicks their left mouse button over this display object. | |
* See the {{#crossLink "MouseEvent"}}{{/crossLink}} class for a listing of event properties. | |
* @event dblclick | |
* @since 0.6.0 | |
*/ | |
/** | |
* Dispatched when the user's mouse enters this display object. This event must be enabled using | |
* {{#crossLink "Stage/enableMouseOver"}}{{/crossLink}}. See also {{#crossLink "DisplayObject/rollover:event"}}{{/crossLink}}. | |
* See the {{#crossLink "MouseEvent"}}{{/crossLink}} class for a listing of event properties. | |
* @event mouseover | |
* @since 0.6.0 | |
*/ | |
/** | |
* Dispatched when the user's mouse leaves this display object. This event must be enabled using | |
* {{#crossLink "Stage/enableMouseOver"}}{{/crossLink}}. See also {{#crossLink "DisplayObject/rollout:event"}}{{/crossLink}}. | |
* See the {{#crossLink "MouseEvent"}}{{/crossLink}} class for a listing of event properties. | |
* @event mouseout | |
* @since 0.6.0 | |
*/ | |
/** | |
* This event is similar to {{#crossLink "DisplayObject/mouseover:event"}}{{/crossLink}}, with the following | |
* differences: it does not bubble, and it considers {{#crossLink "Container"}}{{/crossLink}} instances as an | |
* aggregate of their content. | |
* | |
* For example, myContainer contains two overlapping children: shapeA and shapeB. The user moves their mouse over | |
* shapeA and then directly on to shapeB. With a listener for {{#crossLink "mouseover:event"}}{{/crossLink}} on | |
* myContainer, two events would be received, each targeting a child element:<OL> | |
* <LI>when the mouse enters shapeA (target=shapeA)</LI> | |
* <LI>when the mouse enters shapeB (target=shapeB)</LI> | |
* </OL> | |
* However, with a listener for "rollover" instead, only a single event is received when the mouse first enters | |
* the aggregate myContainer content (target=myContainer). | |
* | |
* This event must be enabled using {{#crossLink "Stage/enableMouseOver"}}{{/crossLink}}. | |
* See the {{#crossLink "MouseEvent"}}{{/crossLink}} class for a listing of event properties. | |
* @event rollover | |
* @since 0.7.0 | |
*/ | |
/** | |
* This event is similar to {{#crossLink "DisplayObject/mouseout:event"}}{{/crossLink}}, with the following | |
* differences: it does not bubble, and it considers {{#crossLink "Container"}}{{/crossLink}} instances as an | |
* aggregate of their content. | |
* | |
* For example, myContainer contains two overlapping children: shapeA and shapeB. The user moves their mouse over | |
* shapeA, then directly on to shapeB, then off both. With a listener for {{#crossLink "mouseout:event"}}{{/crossLink}} | |
* on myContainer, two events would be received, each targeting a child element:<OL> | |
* <LI>when the mouse leaves shapeA (target=shapeA)</LI> | |
* <LI>when the mouse leaves shapeB (target=shapeB)</LI> | |
* </OL> | |
* However, with a listener for "rollout" instead, only a single event is received when the mouse leaves | |
* the aggregate myContainer content (target=myContainer). | |
* | |
* This event must be enabled using {{#crossLink "Stage/enableMouseOver"}}{{/crossLink}}. | |
* See the {{#crossLink "MouseEvent"}}{{/crossLink}} class for a listing of event properties. | |
* @event rollout | |
* @since 0.7.0 | |
*/ | |
/** | |
* After a {{#crossLink "DisplayObject/mousedown:event"}}{{/crossLink}} occurs on a display object, a pressmove | |
* event will be generated on that object whenever the mouse moves until the mouse press is released. This can be | |
* useful for dragging and similar operations. | |
* | |
* **Please note** that if the initial mouse target from a `mousedown` event is removed from the stage after being pressed | |
* (e.g. during a `pressmove` event), a `pressmove` event is still generated. However since it is no longer in the | |
* display list, the event can not bubble. This means that previous ancestors (parent containers) will not receive | |
* the event, and therefore can not re-dispatch it. If you intend to listen for `{{#crossLink "DisplayObject/pressup:event"}}{{/crossLink}}` | |
* or `pressmove` on a dynamic object (such as a {{#crossLink "MovieClip"}}{{/crossLink}} or {{#crossLink "Container"}}{{/crossLink}}), | |
* then ensure you set {{#crossLink "Container/mouseChildren:property"}}{{/crossLink}} to `false`. | |
* @event pressmove | |
* @since 0.7.0 | |
*/ | |
/** | |
* After a {{#crossLink "DisplayObject/mousedown:event"}}{{/crossLink}} occurs on a display object, a pressup event | |
* will be generated on that object when that mouse press is released. This can be useful for dragging and similar | |
* operations. | |
* | |
* **Please note** that if the initial mouse target from a `mousedown` event is removed from the stage after being pressed | |
* (e.g. during a `pressmove` event), a `pressup` event is still generated. However since it is no longer in the | |
* display list, the event can not bubble. This means that previous ancestors (parent containers) will not receive | |
* the event, and therefore can not re-dispatch it. If you intend to listen for `{{#crossLink "DisplayObject/pressmove:event"}}{{/crossLink}}` | |
* or `pressup` on a dynamic object (such as a {{#crossLink "MovieClip"}}{{/crossLink}} or {{#crossLink "Container"}}{{/crossLink}}), | |
* then ensure you set {{#crossLink "Container/mouseChildren:property"}}{{/crossLink}} to `false`. | |
* @event pressup | |
* @since 0.7.0 | |
*/ | |
/** | |
* Dispatched when the display object is added to a parent container. | |
* @event added | |
*/ | |
/** | |
* Dispatched when the display object is removed from its parent container. | |
* @event removed | |
*/ | |
/** | |
* Dispatched on each display object on a stage whenever the stage updates. This occurs immediately before the | |
* rendering (draw) pass. When {{#crossLink "Stage/update"}}{{/crossLink}} is called, first all display objects on | |
* the stage dispatch the tick event, then all of the display objects are drawn to stage. Children will have their | |
* {{#crossLink "tick:event"}}{{/crossLink}} event dispatched in order of their depth prior to the event being | |
* dispatched on their parent. | |
* @event tick | |
* @param {Object} target The object that dispatched the event. | |
* @param {String} type The event type. | |
* @param {Array} params An array containing any arguments that were passed to the Stage.update() method. For | |
* example if you called stage.update("hello"), then the params would be ["hello"]. | |
* @since 0.6.0 | |
*/ | |
// getter / setters: | |
/** | |
* Use the {{#crossLink "DisplayObject/stage:property"}}{{/crossLink}} property instead. | |
* @method _getStage | |
* @protected | |
* @return {Stage} | |
**/ | |
p._getStage = function () { | |
// uses dynamic access to avoid circular dependencies; | |
var o = this, | |
_Stage = createjs["Stage"]; | |
while (o.parent) { | |
o = o.parent; | |
} | |
if (o instanceof _Stage) { | |
return o; | |
} | |
return null; | |
}; | |
// DisplayObject.getStage is @deprecated. Remove for 1.1+ | |
p.getStage = createjs.deprecate(p._getStage, "DisplayObject.getStage"); | |
/** | |
* Returns the Stage instance that this display object will be rendered on, or null if it has not been added to one. | |
* @property stage | |
* @type {Stage} | |
* @readonly | |
**/ | |
/** | |
* Returns an ID number that uniquely identifies the current cache for this display object. This can be used to | |
* determine if the cache has changed since a previous check. | |
* Moved to {{#crossLink "BitmapCache"}}{{/crossLink}} | |
* @property cacheID | |
* @deprecated | |
* @type {Number} | |
* @default 0 | |
*/ | |
/** | |
* Set both the {{#crossLink "DisplayObject/scaleX:property"}}{{/crossLink}} and the {{#crossLink "DisplayObject/scaleY"}}{{/crossLink}} | |
* property to the same value. Note that when you get the value, if the `scaleX` and `scaleY` are different values, | |
* it will return only the `scaleX`. | |
* @property scaleX | |
* @type {Number} | |
* @default 1 | |
*/ | |
try { | |
Object.defineProperties(p, { | |
stage: { get: p._getStage }, | |
cacheID: { | |
get: function () { | |
return this.bitmapCache && this.bitmapCache.cacheID; | |
}, | |
set: function (a) { | |
this.bitmapCache && (this.bitmapCache.cacheID = a); | |
}, | |
}, | |
scale: { | |
get: function () { | |
return this.scaleX; | |
}, | |
set: function (scale) { | |
this.scaleX = this.scaleY = scale; | |
}, | |
}, | |
}); | |
} catch (e) {} | |
// public methods: | |
/** | |
* Returns true or false indicating whether the display object would be visible if drawn to a canvas. | |
* This does not account for whether it would be visible within the boundaries of the stage. | |
* | |
* NOTE: This method is mainly for internal use, though it may be useful for advanced uses. | |
* @method isVisible | |
* @return {Boolean} Boolean indicating whether the display object would be visible if drawn to a canvas | |
**/ | |
p.isVisible = function () { | |
return !!( | |
this.visible && | |
this.alpha > 0 && | |
this.scaleX != 0 && | |
this.scaleY != 0 | |
); | |
}; | |
/** | |
* Draws the display object into the specified context ignoring its visible, alpha, shadow, and transform. | |
* Returns <code>true</code> if the draw was handled (useful for overriding functionality). | |
* | |
* NOTE: This method is mainly for internal use, though it may be useful for advanced uses. | |
* @method draw | |
* @param {CanvasRenderingContext2D} ctx The canvas 2D context object to draw into. | |
* @param {Boolean} [ignoreCache=false] Indicates whether the draw operation should ignore any current cache. For example, | |
* used for drawing the cache (to prevent it from simply drawing an existing cache back into itself). | |
* @return {Boolean} | |
**/ | |
p.draw = function (ctx, ignoreCache) { | |
var cache = this.bitmapCache; | |
if (cache && !ignoreCache) { | |
return cache.draw(ctx); | |
} | |
return false; | |
}; | |
/** | |
* Applies this display object's transformation, alpha, globalCompositeOperation, clipping path (mask), and shadow | |
* to the specified context. This is typically called prior to {{#crossLink "DisplayObject/draw"}}{{/crossLink}}. | |
* @method updateContext | |
* @param {CanvasRenderingContext2D} ctx The canvas 2D to update. | |
**/ | |
p.updateContext = function (ctx) { | |
var o = this, | |
mask = o.mask, | |
mtx = o._props.matrix; | |
if (mask && mask.graphics && !mask.graphics.isEmpty()) { | |
mask.getMatrix(mtx); | |
ctx.transform(mtx.a, mtx.b, mtx.c, mtx.d, mtx.tx, mtx.ty); | |
mask.graphics.drawAsPath(ctx); | |
ctx.clip(); | |
mtx.invert(); | |
ctx.transform(mtx.a, mtx.b, mtx.c, mtx.d, mtx.tx, mtx.ty); | |
} | |
this.getMatrix(mtx); | |
var tx = mtx.tx, | |
ty = mtx.ty; | |
if (DisplayObject._snapToPixelEnabled && o.snapToPixel) { | |
tx = (tx + (tx < 0 ? -0.5 : 0.5)) | 0; | |
ty = (ty + (ty < 0 ? -0.5 : 0.5)) | 0; | |
} | |
ctx.transform(mtx.a, mtx.b, mtx.c, mtx.d, tx, ty); | |
ctx.globalAlpha *= o.alpha; | |
if (o.compositeOperation) { | |
ctx.globalCompositeOperation = o.compositeOperation; | |
} | |
if (o.shadow) { | |
this._applyShadow(ctx, o.shadow); | |
} | |
}; | |
/** | |
* Draws the display object into a new element, which is then used for subsequent draws. Intended for complex content | |
* that does not change frequently (ex. a Container with many children that do not move, or a complex vector Shape), | |
* this can provide for much faster rendering because the content does not need to be re-rendered each tick. The | |
* cached display object can be moved, rotated, faded, etc freely, however if its content changes, you must manually | |
* update the cache by calling <code>updateCache()</code> again. You must specify the cached area via the x, y, w, | |
* and h parameters. This defines the rectangle that will be rendered and cached using this display object's coordinates. | |
* | |
* <h4>Example</h4> | |
* For example if you defined a Shape that drew a circle at 0, 0 with a radius of 25: | |
* | |
* var shape = new createjs.Shape(); | |
* shape.graphics.beginFill("#ff0000").drawCircle(0, 0, 25); | |
* shape.cache(-25, -25, 50, 50); | |
* | |
* Note that filters need to be defined <em>before</em> the cache is applied or you will have to call updateCache after | |
* application. Check out the {{#crossLink "Filter"}}{{/crossLink}} class for more information. Some filters | |
* (ex. BlurFilter) may not work as expected in conjunction with the scale param. | |
* | |
* Usually, the resulting cacheCanvas will have the dimensions width * scale, height * scale, however some filters (ex. BlurFilter) | |
* will add padding to the canvas dimensions. | |
* | |
* In previous versions caching was handled on DisplayObject but has since been moved to {{#crossLink "BitmapCache"}}{{/crossLink}}. | |
* This allows for easier interaction and alternate cache methods like WebGL with {{#crossLink "StageGL"}}{{/crossLink}}. | |
* For more information on the options object, see the BitmapCache {{#crossLink "BitmapCache/define"}}{{/crossLink}}. | |
* | |
* @method cache | |
* @param {Number} x The x coordinate origin for the cache region. | |
* @param {Number} y The y coordinate origin for the cache region. | |
* @param {Number} width The width of the cache region. | |
* @param {Number} height The height of the cache region. | |
* @param {Number} [scale=1] The scale at which the cache will be created. For example, if you cache a vector shape using | |
* myShape.cache(0,0,100,100,2) then the resulting cacheCanvas will be 200x200 px. This lets you scale and rotate | |
* cached elements with greater fidelity. Default is 1. | |
* @param {Object} [options=undefined] Specify additional parameters for the cache logic | |
**/ | |
p.cache = function (x, y, width, height, scale, options) { | |
if (!this.bitmapCache) { | |
this.bitmapCache = new createjs.BitmapCache(); | |
} | |
this.bitmapCache.define(this, x, y, width, height, scale, options); | |
}; | |
/** | |
* Redraws the display object to its cache. Calling updateCache without an active cache will throw an error. | |
* If compositeOperation is null the current cache will be cleared prior to drawing. Otherwise the display object | |
* will be drawn over the existing cache using the specified compositeOperation. | |
* | |
* <h4>Example</h4> | |
* Clear the current graphics of a cached shape, draw some new instructions, and then update the cache. The new line | |
* will be drawn on top of the old one. | |
* | |
* // Not shown: Creating the shape, and caching it. | |
* shapeInstance.clear(); | |
* shapeInstance.setStrokeStyle(3).beginStroke("#ff0000").moveTo(100, 100).lineTo(200,200); | |
* shapeInstance.updateCache(); | |
* | |
* In previous versions caching was handled on DisplayObject but has since been moved to {{#crossLink "BitmapCache"}}{{/crossLink}}. | |
* This allows for easier interaction and alternate cache methods like WebGL and {{#crossLink "StageGL"}}{{/crossLink}}. | |
* | |
* @method updateCache | |
* @param {String} compositeOperation The compositeOperation to use, or null to clear the cache and redraw it. | |
* <a href="https://html.spec.whatwg.org/multipage/scripting.html#dom-context-2d-globalcompositeoperation"> | |
* whatwg spec on compositing</a>. | |
**/ | |
p.updateCache = function (compositeOperation) { | |
if (!this.bitmapCache) { | |
throw "cache() must be called before updateCache()"; | |
} | |
this.bitmapCache.update(compositeOperation); | |
}; | |
/** | |
* Clears the current cache. See {{#crossLink "DisplayObject/cache"}}{{/crossLink}} for more information. | |
* @method uncache | |
**/ | |
p.uncache = function () { | |
if (this.bitmapCache) { | |
this.bitmapCache.release(); | |
this.bitmapCache = undefined; | |
} | |
}; | |
/** | |
* Returns a data URL for the cache, or null if this display object is not cached. | |
* Only generated if the cache has changed, otherwise returns last result. | |
* @method getCacheDataURL | |
* @return {String} The image data url for the cache. | |
**/ | |
p.getCacheDataURL = function () { | |
return this.bitmapCache ? this.bitmapCache.getDataURL() : null; | |
}; | |
/** | |
* Transforms the specified x and y position from the coordinate space of the display object | |
* to the global (stage) coordinate space. For example, this could be used to position an HTML label | |
* over a specific point on a nested display object. Returns a Point instance with x and y properties | |
* correlating to the transformed coordinates on the stage. | |
* | |
* <h4>Example</h4> | |
* | |
* displayObject.x = 300; | |
* displayObject.y = 200; | |
* stage.addChild(displayObject); | |
* var point = displayObject.localToGlobal(100, 100); | |
* // Results in x=400, y=300 | |
* | |
* @method localToGlobal | |
* @param {Number} x The x position in the source display object to transform. | |
* @param {Number} y The y position in the source display object to transform. | |
* @param {Point | Object} [pt] An object to copy the result into. If omitted a new Point object with x/y properties will be returned. | |
* @return {Point} A Point instance with x and y properties correlating to the transformed coordinates | |
* on the stage. | |
**/ | |
p.localToGlobal = function (x, y, pt) { | |
return this.getConcatenatedMatrix(this._props.matrix).transformPoint( | |
x, | |
y, | |
pt || new createjs.Point() | |
); | |
}; | |
/** | |
* Transforms the specified x and y position from the global (stage) coordinate space to the | |
* coordinate space of the display object. For example, this could be used to determine | |
* the current mouse position within the display object. Returns a Point instance with x and y properties | |
* correlating to the transformed position in the display object's coordinate space. | |
* | |
* <h4>Example</h4> | |
* | |
* displayObject.x = 300; | |
* displayObject.y = 200; | |
* stage.addChild(displayObject); | |
* var point = displayObject.globalToLocal(100, 100); | |
* // Results in x=-200, y=-100 | |
* | |
* @method globalToLocal | |
* @param {Number} x The x position on the stage to transform. | |
* @param {Number} y The y position on the stage to transform. | |
* @param {Point | Object} [pt] An object to copy the result into. If omitted a new Point object with x/y properties will be returned. | |
* @return {Point} A Point instance with x and y properties correlating to the transformed position in the | |
* display object's coordinate space. | |
**/ | |
p.globalToLocal = function (x, y, pt) { | |
return this.getConcatenatedMatrix(this._props.matrix) | |
.invert() | |
.transformPoint(x, y, pt || new createjs.Point()); | |
}; | |
/** | |
* Transforms the specified x and y position from the coordinate space of this display object to the coordinate | |
* space of the target display object. Returns a Point instance with x and y properties correlating to the | |
* transformed position in the target's coordinate space. Effectively the same as using the following code with | |
* {{#crossLink "DisplayObject/localToGlobal"}}{{/crossLink}} and {{#crossLink "DisplayObject/globalToLocal"}}{{/crossLink}}. | |
* | |
* var pt = this.localToGlobal(x, y); | |
* pt = target.globalToLocal(pt.x, pt.y); | |
* | |
* @method localToLocal | |
* @param {Number} x The x position in the source display object to transform. | |
* @param {Number} y The y position on the source display object to transform. | |
* @param {DisplayObject} target The target display object to which the coordinates will be transformed. | |
* @param {Point | Object} [pt] An object to copy the result into. If omitted a new Point object with x/y properties will be returned. | |
* @return {Point} Returns a Point instance with x and y properties correlating to the transformed position | |
* in the target's coordinate space. | |
**/ | |
p.localToLocal = function (x, y, target, pt) { | |
pt = this.localToGlobal(x, y, pt); | |
return target.globalToLocal(pt.x, pt.y, pt); | |
}; | |
/** | |
* Shortcut method to quickly set the transform properties on the display object. All parameters are optional. | |
* Omitted parameters will have the default value set. | |
* | |
* <h4>Example</h4> | |
* | |
* displayObject.setTransform(100, 100, 2, 2); | |
* | |
* @method setTransform | |
* @param {Number} [x=0] The horizontal translation (x position) in pixels | |
* @param {Number} [y=0] The vertical translation (y position) in pixels | |
* @param {Number} [scaleX=1] The horizontal scale, as a percentage of 1 | |
* @param {Number} [scaleY=1] the vertical scale, as a percentage of 1 | |
* @param {Number} [rotation=0] The rotation, in degrees | |
* @param {Number} [skewX=0] The horizontal skew factor | |
* @param {Number} [skewY=0] The vertical skew factor | |
* @param {Number} [regX=0] The horizontal registration point in pixels | |
* @param {Number} [regY=0] The vertical registration point in pixels | |
* @return {DisplayObject} Returns this instance. Useful for chaining commands. | |
* @chainable | |
*/ | |
p.setTransform = function ( | |
x, | |
y, | |
scaleX, | |
scaleY, | |
rotation, | |
skewX, | |
skewY, | |
regX, | |
regY | |
) { | |
this.x = x || 0; | |
this.y = y || 0; | |
this.scaleX = scaleX == null ? 1 : scaleX; | |
this.scaleY = scaleY == null ? 1 : scaleY; | |
this.rotation = rotation || 0; | |
this.skewX = skewX || 0; | |
this.skewY = skewY || 0; | |
this.regX = regX || 0; | |
this.regY = regY || 0; | |
return this; | |
}; | |
/** | |
* Returns a matrix based on this object's current transform. | |
* @method getMatrix | |
* @param {Matrix2D} matrix Optional. A Matrix2D object to populate with the calculated values. If null, a new | |
* Matrix object is returned. | |
* @return {Matrix2D} A matrix representing this display object's transform. | |
**/ | |
p.getMatrix = function (matrix) { | |
var o = this, | |
mtx = (matrix && matrix.identity()) || new createjs.Matrix2D(); | |
return o.transformMatrix | |
? mtx.copy(o.transformMatrix) | |
: mtx.appendTransform( | |
o.x, | |
o.y, | |
o.scaleX, | |
o.scaleY, | |
o.rotation, | |
o.skewX, | |
o.skewY, | |
o.regX, | |
o.regY | |
); | |
}; | |
/** | |
* Generates a Matrix2D object representing the combined transform of the display object and all of its | |
* parent Containers up to the highest level ancestor (usually the {{#crossLink "Stage"}}{{/crossLink}}). This can | |
* be used to transform positions between coordinate spaces, such as with {{#crossLink "DisplayObject/localToGlobal"}}{{/crossLink}} | |
* and {{#crossLink "DisplayObject/globalToLocal"}}{{/crossLink}}. | |
* @method getConcatenatedMatrix | |
* @param {Matrix2D} [matrix] A {{#crossLink "Matrix2D"}}{{/crossLink}} object to populate with the calculated values. | |
* If null, a new Matrix2D object is returned. | |
* @return {Matrix2D} The combined matrix. | |
**/ | |
p.getConcatenatedMatrix = function (matrix) { | |
var o = this, | |
mtx = this.getMatrix(matrix); | |
while ((o = o.parent)) { | |
mtx.prependMatrix(o.getMatrix(o._props.matrix)); | |
} | |
return mtx; | |
}; | |
/** | |
* Generates a DisplayProps object representing the combined display properties of the object and all of its | |
* parent Containers up to the highest level ancestor (usually the {{#crossLink "Stage"}}{{/crossLink}}). | |
* @method getConcatenatedDisplayProps | |
* @param {DisplayProps} [props] A {{#crossLink "DisplayProps"}}{{/crossLink}} object to populate with the calculated values. | |
* If null, a new DisplayProps object is returned. | |
* @return {DisplayProps} The combined display properties. | |
**/ | |
p.getConcatenatedDisplayProps = function (props) { | |
props = props ? props.identity() : new createjs.DisplayProps(); | |
var o = this, | |
mtx = o.getMatrix(props.matrix); | |
do { | |
props.prepend(o.visible, o.alpha, o.shadow, o.compositeOperation); | |
// we do this to avoid problems with the matrix being used for both operations when o._props.matrix is passed in as the props param. | |
// this could be simplified (ie. just done as part of the prepend above) if we switched to using a pool. | |
if (o != this) { | |
mtx.prependMatrix(o.getMatrix(o._props.matrix)); | |
} | |
} while ((o = o.parent)); | |
return props; | |
}; | |
/** | |
* Tests whether the display object intersects the specified point in local coordinates (ie. draws a pixel with alpha > 0 at | |
* the specified position). This ignores the alpha, shadow, hitArea, mask, and compositeOperation of the display object. | |
* | |
* <h4>Example</h4> | |
* | |
* stage.addEventListener("stagemousedown", handleMouseDown); | |
* function handleMouseDown(event) { | |
* var hit = myShape.hitTest(event.stageX, event.stageY); | |
* } | |
* | |
* Please note that shape-to-shape collision is not currently supported by EaselJS. | |
* @method hitTest | |
* @param {Number} x The x position to check in the display object's local coordinates. | |
* @param {Number} y The y position to check in the display object's local coordinates. | |
* @return {Boolean} A Boolean indicating whether a visible portion of the DisplayObject intersect the specified | |
* local Point. | |
*/ | |
p.hitTest = function (x, y) { | |
var ctx = DisplayObject._hitTestContext; | |
ctx.setTransform(1, 0, 0, 1, -x, -y); | |
this.draw(ctx); | |
var hit = this._testHit(ctx); | |
ctx.setTransform(1, 0, 0, 1, 0, 0); | |
ctx.clearRect(0, 0, 2, 2); | |
return hit; | |
}; | |
/** | |
* Provides a chainable shortcut method for setting a number of properties on the instance. | |
* | |
* <h4>Example</h4> | |
* | |
* var myGraphics = new createjs.Graphics().beginFill("#ff0000").drawCircle(0, 0, 25); | |
* var shape = stage.addChild(new Shape()).set({graphics:myGraphics, x:100, y:100, alpha:0.5}); | |
* | |
* @method set | |
* @param {Object} props A generic object containing properties to copy to the DisplayObject instance. | |
* @return {DisplayObject} Returns the instance the method is called on (useful for chaining calls.) | |
* @chainable | |
*/ | |
p.set = function (props) { | |
for (var n in props) { | |
this[n] = props[n]; | |
} | |
return this; | |
}; | |
/** | |
* Returns a rectangle representing this object's bounds in its local coordinate system (ie. with no transformation). | |
* Objects that have been cached will return the bounds of the cache. | |
* | |
* Not all display objects can calculate their own bounds (ex. Shape). For these objects, you can use | |
* {{#crossLink "DisplayObject/setBounds"}}{{/crossLink}} so that they are included when calculating Container | |
* bounds. | |
* | |
* <table> | |
* <tr><td><b>All</b></td><td> | |
* All display objects support setting bounds manually using setBounds(). Likewise, display objects that | |
* have been cached using cache() will return the bounds of their cache. Manual and cache bounds will override | |
* the automatic calculations listed below. | |
* </td></tr> | |
* <tr><td><b>Bitmap</b></td><td> | |
* Returns the width and height of the sourceRect (if specified) or image, extending from (x=0,y=0). | |
* </td></tr> | |
* <tr><td><b>Sprite</b></td><td> | |
* Returns the bounds of the current frame. May have non-zero x/y if a frame registration point was specified | |
* in the spritesheet data. See also {{#crossLink "SpriteSheet/getFrameBounds"}}{{/crossLink}} | |
* </td></tr> | |
* <tr><td><b>Container</b></td><td> | |
* Returns the aggregate (combined) bounds of all children that return a non-null value from getBounds(). | |
* </td></tr> | |
* <tr><td><b>Shape</b></td><td> | |
* Does not currently support automatic bounds calculations. Use setBounds() to manually define bounds. | |
* </td></tr> | |
* <tr><td><b>Text</b></td><td> | |
* Returns approximate bounds. Horizontal values (x/width) are quite accurate, but vertical values (y/height) are | |
* not, especially when using textBaseline values other than "top". | |
* </td></tr> | |
* <tr><td><b>BitmapText</b></td><td> | |
* Returns approximate bounds. Values will be more accurate if spritesheet frame registration points are close | |
* to (x=0,y=0). | |
* </td></tr> | |
* </table> | |
* | |
* Bounds can be expensive to calculate for some objects (ex. text, or containers with many children), and | |
* are recalculated each time you call getBounds(). You can prevent recalculation on static objects by setting the | |
* bounds explicitly: | |
* | |
* var bounds = obj.getBounds(); | |
* obj.setBounds(bounds.x, bounds.y, bounds.width, bounds.height); | |
* // getBounds will now use the set values, instead of recalculating | |
* | |
* To reduce memory impact, the returned Rectangle instance may be reused internally; clone the instance or copy its | |
* values if you need to retain it. | |
* | |
* var myBounds = obj.getBounds().clone(); | |
* // OR: | |
* myRect.copy(obj.getBounds()); | |
* | |
* @method getBounds | |
* @return {Rectangle} A Rectangle instance representing the bounds, or null if bounds are not available for this | |
* object. | |
**/ | |
p.getBounds = function () { | |
if (this._bounds) { | |
return this._rectangle.copy(this._bounds); | |
} | |
var cacheCanvas = this.cacheCanvas; | |
if (cacheCanvas) { | |
var scale = this._cacheScale; | |
return this._rectangle.setValues( | |
this._cacheOffsetX, | |
this._cacheOffsetY, | |
cacheCanvas.width / scale, | |
cacheCanvas.height / scale | |
); | |
} | |
return null; | |
}; | |
/** | |
* Returns a rectangle representing this object's bounds in its parent's coordinate system (ie. with transformations applied). | |
* Objects that have been cached will return the transformed bounds of the cache. | |
* | |
* Not all display objects can calculate their own bounds (ex. Shape). For these objects, you can use | |
* {{#crossLink "DisplayObject/setBounds"}}{{/crossLink}} so that they are included when calculating Container | |
* bounds. | |
* | |
* To reduce memory impact, the returned Rectangle instance may be reused internally; clone the instance or copy its | |
* values if you need to retain it. | |
* | |
* Container instances calculate aggregate bounds for all children that return bounds via getBounds. | |
* @method getTransformedBounds | |
* @return {Rectangle} A Rectangle instance representing the bounds, or null if bounds are not available for this object. | |
**/ | |
p.getTransformedBounds = function () { | |
return this._getBounds(); | |
}; | |
/** | |
* Allows you to manually specify the bounds of an object that either cannot calculate their own bounds (ex. Shape & | |
* Text) for future reference, or so the object can be included in Container bounds. Manually set bounds will always | |
* override calculated bounds. | |
* | |
* The bounds should be specified in the object's local (untransformed) coordinates. For example, a Shape instance | |
* with a 25px radius circle centered at 0,0 would have bounds of (-25, -25, 50, 50). | |
* @method setBounds | |
* @param {Number} x The x origin of the bounds. Pass null to remove the manual bounds. | |
* @param {Number} y The y origin of the bounds. | |
* @param {Number} width The width of the bounds. | |
* @param {Number} height The height of the bounds. | |
**/ | |
p.setBounds = function (x, y, width, height) { | |
if (x == null) { | |
this._bounds = x; | |
return; | |
} | |
this._bounds = (this._bounds || new createjs.Rectangle()).setValues( | |
x, | |
y, | |
width, | |
height | |
); | |
}; | |
/** | |
* Returns a clone of this DisplayObject. Some properties that are specific to this instance's current context are | |
* reverted to their defaults (for example .parent). Caches are not maintained across clones, and some elements | |
* are copied by reference (masks, individual filter instances, hit area) | |
* @method clone | |
* @return {DisplayObject} A clone of the current DisplayObject instance. | |
**/ | |
p.clone = function () { | |
return this._cloneProps(new DisplayObject()); | |
}; | |
/** | |
* Returns a string representation of this object. | |
* @method toString | |
* @return {String} a string representation of the instance. | |
**/ | |
p.toString = function () { | |
return "[DisplayObject (name=" + this.name + ")]"; | |
}; | |
// private methods: | |
/** | |
* Called before the object gets drawn and is a chance to ensure the display state of the object is correct. | |
* Mostly used by {{#crossLink "MovieClip"}}{{/crossLink}} and {{#crossLink "BitmapText"}}{{/crossLink}} to | |
* correct their internal state and children prior to being drawn. | |
* | |
* Is manually called via draw in a {{#crossLink "Stage"}}{{/crossLink}} but is automatically called when | |
* present in a {{#crossLink "StageGL"}}{{/crossLink}} instance. | |
* | |
* @method _updateState | |
* @default null | |
*/ | |
p._updateState = null; | |
// separated so it can be used more easily in subclasses: | |
/** | |
* @method _cloneProps | |
* @param {DisplayObject} o The DisplayObject instance which will have properties from the current DisplayObject | |
* instance copied into. | |
* @return {DisplayObject} o | |
* @protected | |
**/ | |
p._cloneProps = function (o) { | |
o.alpha = this.alpha; | |
o.mouseEnabled = this.mouseEnabled; | |
o.tickEnabled = this.tickEnabled; | |
o.name = this.name; | |
o.regX = this.regX; | |
o.regY = this.regY; | |
o.rotation = this.rotation; | |
o.scaleX = this.scaleX; | |
o.scaleY = this.scaleY; | |
o.shadow = this.shadow; | |
o.skewX = this.skewX; | |
o.skewY = this.skewY; | |
o.visible = this.visible; | |
o.x = this.x; | |
o.y = this.y; | |
o.compositeOperation = this.compositeOperation; | |
o.snapToPixel = this.snapToPixel; | |
o.filters = this.filters == null ? null : this.filters.slice(0); | |
o.mask = this.mask; | |
o.hitArea = this.hitArea; | |
o.cursor = this.cursor; | |
o._bounds = this._bounds; | |
return o; | |
}; | |
/** | |
* @method _applyShadow | |
* @protected | |
* @param {CanvasRenderingContext2D} ctx | |
* @param {Shadow} shadow | |
**/ | |
p._applyShadow = function (ctx, shadow) { | |
shadow = shadow || Shadow.identity; | |
ctx.shadowColor = shadow.color; | |
ctx.shadowOffsetX = shadow.offsetX; | |
ctx.shadowOffsetY = shadow.offsetY; | |
ctx.shadowBlur = shadow.blur; | |
}; | |
/** | |
* @method _tick | |
* @param {Object} evtObj An event object that will be dispatched to all tick listeners. This object is reused between dispatchers to reduce construction & GC costs. | |
* @protected | |
**/ | |
p._tick = function (evtObj) { | |
// because tick can be really performance sensitive, check for listeners before calling dispatchEvent. | |
var ls = this._listeners; | |
if (ls && ls["tick"]) { | |
// reset & reuse the event object to avoid construction / GC costs: | |
evtObj.target = null; | |
evtObj.propagationStopped = evtObj.immediatePropagationStopped = false; | |
this.dispatchEvent(evtObj); | |
} | |
}; | |
/** | |
* @method _testHit | |
* @protected | |
* @param {CanvasRenderingContext2D} ctx | |
* @return {Boolean} | |
**/ | |
p._testHit = function (ctx) { | |
try { | |
var hit = ctx.getImageData(0, 0, 1, 1).data[3] > 1; | |
} catch (e) { | |
if (!DisplayObject.suppressCrossDomainErrors) { | |
throw "An error has occurred. This is most likely due to security restrictions on reading canvas pixel data with local or cross-domain images."; | |
} | |
} | |
return hit; | |
}; | |
/** | |
* @method _getBounds | |
* @param {Matrix2D} matrix | |
* @param {Boolean} ignoreTransform If true, does not apply this object's transform. | |
* @return {Rectangle} | |
* @protected | |
**/ | |
p._getBounds = function (matrix, ignoreTransform) { | |
return this._transformBounds(this.getBounds(), matrix, ignoreTransform); | |
}; | |
/** | |
* @method _transformBounds | |
* @param {Rectangle} bounds | |
* @param {Matrix2D} matrix | |
* @param {Boolean} ignoreTransform | |
* @return {Rectangle} | |
* @protected | |
**/ | |
p._transformBounds = function (bounds, matrix, ignoreTransform) { | |
if (!bounds) { | |
return bounds; | |
} | |
var x = bounds.x, | |
y = bounds.y, | |
width = bounds.width, | |
height = bounds.height, | |
mtx = this._props.matrix; | |
mtx = ignoreTransform ? mtx.identity() : this.getMatrix(mtx); | |
if (x || y) { | |
mtx.appendTransform(0, 0, 1, 1, 0, 0, 0, -x, -y); | |
} // TODO: simplify this. | |
if (matrix) { | |
mtx.prependMatrix(matrix); | |
} | |
var x_a = width * mtx.a, | |
x_b = width * mtx.b; | |
var y_c = height * mtx.c, | |
y_d = height * mtx.d; | |
var tx = mtx.tx, | |
ty = mtx.ty; | |
var minX = tx, | |
maxX = tx, | |
minY = ty, | |
maxY = ty; | |
if ((x = x_a + tx) < minX) { | |
minX = x; | |
} else if (x > maxX) { | |
maxX = x; | |
} | |
if ((x = x_a + y_c + tx) < minX) { | |
minX = x; | |
} else if (x > maxX) { | |
maxX = x; | |
} | |
if ((x = y_c + tx) < minX) { | |
minX = x; | |
} else if (x > maxX) { | |
maxX = x; | |
} | |
if ((y = x_b + ty) < minY) { | |
minY = y; | |
} else if (y > maxY) { | |
maxY = y; | |
} | |
if ((y = x_b + y_d + ty) < minY) { | |
minY = y; | |
} else if (y > maxY) { | |
maxY = y; | |
} | |
if ((y = y_d + ty) < minY) { | |
minY = y; | |
} else if (y > maxY) { | |
maxY = y; | |
} | |
return bounds.setValues(minX, minY, maxX - minX, maxY - minY); | |
}; | |
/** | |
* Indicates whether the display object has any mouse event listeners or a cursor. | |
* @method _isMouseOpaque | |
* @return {Boolean} | |
* @protected | |
**/ | |
p._hasMouseEventListener = function () { | |
var evts = DisplayObject._MOUSE_EVENTS; | |
for (var i = 0, l = evts.length; i < l; i++) { | |
if (this.hasEventListener(evts[i])) { | |
return true; | |
} | |
} | |
return !!this.cursor; | |
}; | |
createjs.DisplayObject = createjs.promote(DisplayObject, "EventDispatcher"); | |
})(); | |
//############################################################################## | |
// Container.js | |
//############################################################################## | |
this.createjs = this.createjs || {}; | |
(function () { | |
"use strict"; | |
// constructor: | |
/** | |
* A Container is a nestable display list that allows you to work with compound display elements. For example you could | |
* group arm, leg, torso and head {{#crossLink "Bitmap"}}{{/crossLink}} instances together into a Person Container, and | |
* transform them as a group, while still being able to move the individual parts relative to each other. Children of | |
* containers have their <code>transform</code> and <code>alpha</code> properties concatenated with their parent | |
* Container. | |
* | |
* For example, a {{#crossLink "Shape"}}{{/crossLink}} with x=100 and alpha=0.5, placed in a Container with <code>x=50</code> | |
* and <code>alpha=0.7</code> will be rendered to the canvas at <code>x=150</code> and <code>alpha=0.35</code>. | |
* Containers have some overhead, so you generally shouldn't create a Container to hold a single child. | |
* | |
* <h4>Example</h4> | |
* | |
* var container = new createjs.Container(); | |
* container.addChild(bitmapInstance, shapeInstance); | |
* container.x = 100; | |
* | |
* @class Container | |
* @extends DisplayObject | |
* @constructor | |
**/ | |
function Container() { | |
this.DisplayObject_constructor(); | |
// public properties: | |
/** | |
* The array of children in the display list. You should usually use the child management methods such as | |
* {{#crossLink "Container/addChild"}}{{/crossLink}}, {{#crossLink "Container/removeChild"}}{{/crossLink}}, | |
* {{#crossLink "Container/swapChildren"}}{{/crossLink}}, etc, rather than accessing this directly, but it is | |
* included for advanced uses. | |
* @property children | |
* @type Array | |
* @default null | |
**/ | |
this.children = []; | |
/** | |
* Indicates whether the children of this container are independently enabled for mouse/pointer interaction. | |
* If false, the children will be aggregated under the container - for example, a click on a child shape would | |
* trigger a click event on the container. | |
* @property mouseChildren | |
* @type Boolean | |
* @default true | |
**/ | |
this.mouseChildren = true; | |
/** | |
* If false, the tick will not be propagated to children of this Container. This can provide some performance benefits. | |
* In addition to preventing the "tick" event from being dispatched, it will also prevent tick related updates | |
* on some display objects (ex. Sprite & MovieClip frame advancing, DOMElement visibility handling). | |
* @property tickChildren | |
* @type Boolean | |
* @default true | |
**/ | |
this.tickChildren = true; | |
} | |
var p = createjs.extend(Container, createjs.DisplayObject); | |
// getter / setters: | |
/** | |
* Use the {{#crossLink "Container/numChildren:property"}}{{/crossLink}} property instead. | |
* @method _getNumChildren | |
* @protected | |
* @return {Number} | |
**/ | |
p._getNumChildren = function () { | |
return this.children.length; | |
}; | |
// Container.getNumChildren is @deprecated. Remove for 1.1+ | |
p.getNumChildren = createjs.deprecate( | |
p._getNumChildren, | |
"Container.getNumChildren" | |
); | |
/** | |
* Returns the number of children in the container. | |
* @property numChildren | |
* @type {Number} | |
* @readonly | |
**/ | |
try { | |
Object.defineProperties(p, { | |
numChildren: { get: p._getNumChildren }, | |
}); | |
} catch (e) {} | |
// public methods: | |
/** | |
* Constructor alias for backwards compatibility. This method will be removed in future versions. | |
* Subclasses should be updated to use {{#crossLink "Utility Methods/extends"}}{{/crossLink}}. | |
* @method initialize | |
* @deprecated in favour of `createjs.promote()` | |
**/ | |
p.initialize = Container; // TODO: deprecated. | |
/** | |
* Returns true or false indicating whether the display object would be visible if drawn to a canvas. | |
* This does not account for whether it would be visible within the boundaries of the stage. | |
* | |
* NOTE: This method is mainly for internal use, though it may be useful for advanced uses. | |
* @method isVisible | |
* @return {Boolean} Boolean indicating whether the display object would be visible if drawn to a canvas | |
**/ | |
p.isVisible = function () { | |
var hasContent = this.cacheCanvas || this.children.length; | |
return !!( | |
this.visible && | |
this.alpha > 0 && | |
this.scaleX != 0 && | |
this.scaleY != 0 && | |
hasContent | |
); | |
}; | |
/** | |
* Draws the display object into the specified context ignoring its visible, alpha, shadow, and transform. | |
* Returns true if the draw was handled (useful for overriding functionality). | |
* | |
* NOTE: This method is mainly for internal use, though it may be useful for advanced uses. | |
* @method draw | |
* @param {CanvasRenderingContext2D} ctx The canvas 2D context object to draw into. | |
* @param {Boolean} [ignoreCache=false] Indicates whether the draw operation should ignore any current cache. | |
* For example, used for drawing the cache (to prevent it from simply drawing an existing cache back | |
* into itself). | |
**/ | |
p.draw = function (ctx, ignoreCache) { | |
if (this.DisplayObject_draw(ctx, ignoreCache)) { | |
return true; | |
} | |
// this ensures we don't have issues with display list changes that occur during a draw: | |
var list = this.children.slice(); | |
for (var i = 0, l = list.length; i < l; i++) { | |
var child = list[i]; | |
if (!child.isVisible()) { | |
continue; | |
} | |
// draw the child: | |
ctx.save(); | |
child.updateContext(ctx); | |
child.draw(ctx); | |
ctx.restore(); | |
} | |
return true; | |
}; | |
/** | |
* Adds a child to the top of the display list. | |
* | |
* <h4>Example</h4> | |
* | |
* container.addChild(bitmapInstance); | |
* | |
* You can also add multiple children at once: | |
* | |
* container.addChild(bitmapInstance, shapeInstance, textInstance); | |
* | |
* @method addChild | |
* @param {DisplayObject} child The display object to add. | |
* @return {DisplayObject} The child that was added, or the last child if multiple children were added. | |
**/ | |
p.addChild = function (child) { | |
if (child == null) { | |
return child; | |
} | |
var l = arguments.length; | |
if (l > 1) { | |
for (var i = 0; i < l; i++) { | |
this.addChild(arguments[i]); | |
} | |
return arguments[l - 1]; | |
} | |
// Note: a lot of duplication with addChildAt, but push is WAY faster than splice. | |
var par = child.parent, | |
silent = par === this; | |
par && par._removeChildAt(createjs.indexOf(par.children, child), silent); | |
child.parent = this; | |
this.children.push(child); | |
if (!silent) { | |
child.dispatchEvent("added"); | |
} | |
return child; | |
}; | |
/** | |
* Adds a child to the display list at the specified index, bumping children at equal or greater indexes up one, and | |
* setting its parent to this Container. | |
* | |
* <h4>Example</h4> | |
* | |
* addChildAt(child1, index); | |
* | |
* You can also add multiple children, such as: | |
* | |
* addChildAt(child1, child2, ..., index); | |
* | |
* The index must be between 0 and numChildren. For example, to add myShape under otherShape in the display list, | |
* you could use: | |
* | |
* container.addChildAt(myShape, container.getChildIndex(otherShape)); | |
* | |
* This would also bump otherShape's index up by one. Fails silently if the index is out of range. | |
* | |
* @method addChildAt | |
* @param {DisplayObject} child The display object to add. | |
* @param {Number} index The index to add the child at. | |
* @return {DisplayObject} Returns the last child that was added, or the last child if multiple children were added. | |
**/ | |
p.addChildAt = function (child, index) { | |
var l = arguments.length; | |
var indx = arguments[l - 1]; // can't use the same name as the index param or it replaces arguments[1] | |
if (indx < 0 || indx > this.children.length) { | |
return arguments[l - 2]; | |
} | |
if (l > 2) { | |
for (var i = 0; i < l - 1; i++) { | |
this.addChildAt(arguments[i], indx + i); | |
} | |
return arguments[l - 2]; | |
} | |
var par = child.parent, | |
silent = par === this; | |
par && par._removeChildAt(createjs.indexOf(par.children, child), silent); | |
child.parent = this; | |
this.children.splice(index, 0, child); | |
if (!silent) { | |
child.dispatchEvent("added"); | |
} | |
return child; | |
}; | |
/** | |
* Removes the specified child from the display list. Note that it is faster to use removeChildAt() if the index is | |
* already known. | |
* | |
* <h4>Example</h4> | |
* | |
* container.removeChild(child); | |
* | |
* You can also remove multiple children: | |
* | |
* removeChild(child1, child2, ...); | |
* | |
* Returns true if the child (or children) was removed, or false if it was not in the display list. | |
* @method removeChild | |
* @param {DisplayObject} child The child to remove. | |
* @return {Boolean} true if the child (or children) was removed, or false if it was not in the display list. | |
**/ | |
p.removeChild = function (child) { | |
var l = arguments.length; | |
if (l > 1) { | |
var good = true; | |
for (var i = 0; i < l; i++) { | |
good = good && this.removeChild(arguments[i]); | |
} | |
return good; | |
} | |
return this._removeChildAt(createjs.indexOf(this.children, child)); | |
}; | |
/** | |
* Removes the child at the specified index from the display list, and sets its parent to null. | |
* | |
* <h4>Example</h4> | |
* | |
* container.removeChildAt(2); | |
* | |
* You can also remove multiple children: | |
* | |
* container.removeChild(2, 7, ...) | |
* | |
* Returns true if the child (or children) was removed, or false if any index was out of range. | |
* @method removeChildAt | |
* @param {Number} index The index of the child to remove. | |
* @return {Boolean} true if the child (or children) was removed, or false if any index was out of range. | |
**/ | |
p.removeChildAt = function (index) { | |
var l = arguments.length; | |
if (l > 1) { | |
var a = []; | |
for (var i = 0; i < l; i++) { | |
a[i] = arguments[i]; | |
} | |
a.sort(function (a, b) { | |
return b - a; | |
}); | |
var good = true; | |
for (var i = 0; i < l; i++) { | |
good = good && this._removeChildAt(a[i]); | |
} | |
return good; | |
} | |
return this._removeChildAt(index); | |
}; | |
/** | |
* Removes all children from the display list. | |
* | |
* <h4>Example</h4> | |
* | |
* container.removeAllChildren(); | |
* | |
* @method removeAllChildren | |
**/ | |
p.removeAllChildren = function () { | |
var kids = this.children; | |
while (kids.length) { | |
this._removeChildAt(0); | |
} | |
}; | |
/** | |
* Returns the child at the specified index. | |
* | |
* <h4>Example</h4> | |
* | |
* container.getChildAt(2); | |
* | |
* @method getChildAt | |
* @param {Number} index The index of the child to return. | |
* @return {DisplayObject} The child at the specified index. Returns null if there is no child at the index. | |
**/ | |
p.getChildAt = function (index) { | |
return this.children[index]; | |
}; | |
/** | |
* Returns the child with the specified name. | |
* @method getChildByName | |
* @param {String} name The name of the child to return. | |
* @return {DisplayObject} The child with the specified name. | |
**/ | |
p.getChildByName = function (name) { | |
var kids = this.children; | |
for (var i = 0, l = kids.length; i < l; i++) { | |
if (kids[i].name == name) { | |
return kids[i]; | |
} | |
} | |
return null; | |
}; | |
/** | |
* Performs an array sort operation on the child list. | |
* | |
* <h4>Example: Display children with a higher y in front.</h4> | |
* | |
* var sortFunction = function(obj1, obj2, options) { | |
* if (obj1.y > obj2.y) { return 1; } | |
* if (obj1.y < obj2.y) { return -1; } | |
* return 0; | |
* } | |
* container.sortChildren(sortFunction); | |
* | |
* @method sortChildren | |
* @param {Function} sortFunction the function to use to sort the child list. See JavaScript's <code>Array.sort</code> | |
* documentation for details. | |
**/ | |
p.sortChildren = function (sortFunction) { | |
this.children.sort(sortFunction); | |
}; | |
/** | |
* Returns the index of the specified child in the display list, or -1 if it is not in the display list. | |
* | |
* <h4>Example</h4> | |
* | |
* var index = container.getChildIndex(child); | |
* | |
* @method getChildIndex | |
* @param {DisplayObject} child The child to return the index of. | |
* @return {Number} The index of the specified child. -1 if the child is not found. | |
**/ | |
p.getChildIndex = function (child) { | |
return createjs.indexOf(this.children, child); | |
}; | |
/** | |
* Swaps the children at the specified indexes. Fails silently if either index is out of range. | |
* @method swapChildrenAt | |
* @param {Number} index1 | |
* @param {Number} index2 | |
**/ | |
p.swapChildrenAt = function (index1, index2) { | |
var kids = this.children; | |
var o1 = kids[index1]; | |
var o2 = kids[index2]; | |
if (!o1 || !o2) { | |
return; | |
} | |
kids[index1] = o2; | |
kids[index2] = o1; | |
}; | |
/** | |
* Swaps the specified children's depth in the display list. Fails silently if either child is not a child of this | |
* Container. | |
* @method swapChildren | |
* @param {DisplayObject} child1 | |
* @param {DisplayObject} child2 | |
**/ | |
p.swapChildren = function (child1, child2) { | |
var kids = this.children; | |
var index1, index2; | |
for (var i = 0, l = kids.length; i < l; i++) { | |
if (kids[i] == child1) { | |
index1 = i; | |
} | |
if (kids[i] == child2) { | |
index2 = i; | |
} | |
if (index1 != null && index2 != null) { | |
break; | |
} | |
} | |
if (i == l) { | |
return; | |
} // TODO: throw error? | |
kids[index1] = child2; | |
kids[index2] = child1; | |
}; | |
/** | |
* Changes the depth of the specified child. Fails silently if the child is not a child of this container, or the index is out of range. | |
* @param {DisplayObject} child | |
* @param {Number} index | |
* @method setChildIndex | |
**/ | |
p.setChildIndex = function (child, index) { | |
var kids = this.children, | |
l = kids.length; | |
if (child.parent != this || index < 0 || index >= l) { | |
return; | |
} | |
for (var i = 0; i < l; i++) { | |
if (kids[i] == child) { | |
break; | |
} | |
} | |
if (i == l || i == index) { | |
return; | |
} | |
kids.splice(i, 1); | |
kids.splice(index, 0, child); | |
}; | |
/** | |
* Returns true if the specified display object either is this container or is a descendent (child, grandchild, etc) | |
* of this container. | |
* @method contains | |
* @param {DisplayObject} child The DisplayObject to be checked. | |
* @return {Boolean} true if the specified display object either is this container or is a descendent. | |
**/ | |
p.contains = function (child) { | |
while (child) { | |
if (child == this) { | |
return true; | |
} | |
child = child.parent; | |
} | |
return false; | |
}; | |
/** | |
* Tests whether the display object intersects the specified local point (ie. draws a pixel with alpha > 0 at the | |
* specified position). This ignores the alpha, shadow and compositeOperation of the display object, and all | |
* transform properties including regX/Y. | |
* @method hitTest | |
* @param {Number} x The x position to check in the display object's local coordinates. | |
* @param {Number} y The y position to check in the display object's local coordinates. | |
* @return {Boolean} A Boolean indicating whether there is a visible section of a DisplayObject that overlaps the specified | |
* coordinates. | |
**/ | |
p.hitTest = function (x, y) { | |
// TODO: optimize to use the fast cache check where possible. | |
return this.getObjectUnderPoint(x, y) != null; | |
}; | |
/** | |
* Returns an array of all display objects under the specified coordinates that are in this container's display | |
* list. This routine ignores any display objects with {{#crossLink "DisplayObject/mouseEnabled:property"}}{{/crossLink}} | |
* set to `false`. The array will be sorted in order of visual depth, with the top-most display object at index 0. | |
* This uses shape based hit detection, and can be an expensive operation to run, so it is best to use it carefully. | |
* For example, if testing for objects under the mouse, test on tick (instead of on {{#crossLink "DisplayObject/mousemove:event"}}{{/crossLink}}), | |
* and only if the mouse's position has changed. | |
* | |
* <ul> | |
* <li>By default (mode=0) this method evaluates all display objects.</li> | |
* <li>By setting the `mode` parameter to `1`, the {{#crossLink "DisplayObject/mouseEnabled:property"}}{{/crossLink}} | |
* and {{#crossLink "mouseChildren:property"}}{{/crossLink}} properties will be respected.</li> | |
* <li>Setting the `mode` to `2` additionally excludes display objects that do not have active mouse event | |
* listeners or a {{#crossLink "DisplayObject:cursor:property"}}{{/crossLink}} property. That is, only objects | |
* that would normally intercept mouse interaction will be included. This can significantly improve performance | |
* in some cases by reducing the number of display objects that need to be tested.</li> | |
* </li> | |
* | |
* This method accounts for both {{#crossLink "DisplayObject/hitArea:property"}}{{/crossLink}} and {{#crossLink "DisplayObject/mask:property"}}{{/crossLink}}. | |
* @method getObjectsUnderPoint | |
* @param {Number} x The x position in the container to test. | |
* @param {Number} y The y position in the container to test. | |
* @param {Number} [mode=0] The mode to use to determine which display objects to include. 0-all, 1-respect mouseEnabled/mouseChildren, 2-only mouse opaque objects. | |
* @return {Array} An Array of DisplayObjects under the specified coordinates. | |
**/ | |
p.getObjectsUnderPoint = function (x, y, mode) { | |
var arr = []; | |
var pt = this.localToGlobal(x, y); | |
this._getObjectsUnderPoint(pt.x, pt.y, arr, mode > 0, mode == 1); | |
return arr; | |
}; | |
/** | |
* Similar to {{#crossLink "Container/getObjectsUnderPoint"}}{{/crossLink}}, but returns only the top-most display | |
* object. This runs significantly faster than <code>getObjectsUnderPoint()</code>, but is still potentially an expensive | |
* operation. See {{#crossLink "Container/getObjectsUnderPoint"}}{{/crossLink}} for more information. | |
* @method getObjectUnderPoint | |
* @param {Number} x The x position in the container to test. | |
* @param {Number} y The y position in the container to test. | |
* @param {Number} mode The mode to use to determine which display objects to include. 0-all, 1-respect mouseEnabled/mouseChildren, 2-only mouse opaque objects. | |
* @return {DisplayObject} The top-most display object under the specified coordinates. | |
**/ | |
p.getObjectUnderPoint = function (x, y, mode) { | |
var pt = this.localToGlobal(x, y); | |
return this._getObjectsUnderPoint(pt.x, pt.y, null, mode > 0, mode == 1); | |
}; | |
/** | |
* Docced in superclass. | |
*/ | |
p.getBounds = function () { | |
return this._getBounds(null, true); | |
}; | |
/** | |
* Docced in superclass. | |
*/ | |
p.getTransformedBounds = function () { | |
return this._getBounds(); | |
}; | |
/** | |
* Returns a clone of this Container. Some properties that are specific to this instance's current context are | |
* reverted to their defaults (for example .parent). | |
* @method clone | |
* @param {Boolean} [recursive=false] If true, all of the descendants of this container will be cloned recursively. If false, the | |
* properties of the container will be cloned, but the new instance will not have any children. | |
* @return {Container} A clone of the current Container instance. | |
**/ | |
p.clone = function (recursive) { | |
var o = this._cloneProps(new Container()); | |
if (recursive) { | |
this._cloneChildren(o); | |
} | |
return o; | |
}; | |
/** | |
* Returns a string representation of this object. | |
* @method toString | |
* @return {String} a string representation of the instance. | |
**/ | |
p.toString = function () { | |
return "[Container (name=" + this.name + ")]"; | |
}; | |
// private methods: | |
/** | |
* @method _tick | |
* @param {Object} evtObj An event object that will be dispatched to all tick listeners. This object is reused between dispatchers to reduce construction & GC costs. | |
* @protected | |
**/ | |
p._tick = function (evtObj) { | |
if (this.tickChildren) { | |
for (var i = this.children.length - 1; i >= 0; i--) { | |
var child = this.children[i]; | |
if (child.tickEnabled && child._tick) { | |
child._tick(evtObj); | |
} | |
} | |
} | |
this.DisplayObject__tick(evtObj); | |
}; | |
/** | |
* Recursively clones all children of this container, and adds them to the target container. | |
* @method cloneChildren | |
* @protected | |
* @param {Container} o The target container. | |
**/ | |
p._cloneChildren = function (o) { | |
if (o.children.length) { | |
o.removeAllChildren(); | |
} | |
var arr = o.children; | |
for (var i = 0, l = this.children.length; i < l; i++) { | |
var clone = this.children[i].clone(true); | |
clone.parent = o; | |
arr.push(clone); | |
} | |
}; | |
/** | |
* Removes the child at the specified index from the display list, and sets its parent to null. | |
* Used by `removeChildAt`, `addChild`, and `addChildAt`. | |
* @method removeChildAt | |
* @protected | |
* @param {Number} index The index of the child to remove. | |
* @param {Boolean} [silent] Prevents dispatch of `removed` event if true. | |
* @return {Boolean} true if the child (or children) was removed, or false if any index was out of range. | |
**/ | |
p._removeChildAt = function (index, silent) { | |
if (index < 0 || index > this.children.length - 1) { | |
return false; | |
} | |
var child = this.children[index]; | |
if (child) { | |
child.parent = null; | |
} | |
this.children.splice(index, 1); | |
if (!silent) { | |
child.dispatchEvent("removed"); | |
} | |
return true; | |
}; | |
/** | |
* @method _getObjectsUnderPoint | |
* @param {Number} x | |
* @param {Number} y | |
* @param {Array} arr | |
* @param {Boolean} mouse If true, it will respect mouse interaction properties like mouseEnabled, mouseChildren, and active listeners. | |
* @param {Boolean} activeListener If true, there is an active mouse event listener on a parent object. | |
* @param {Number} currentDepth Indicates the current depth of the search. | |
* @return {DisplayObject} | |
* @protected | |
**/ | |
p._getObjectsUnderPoint = function ( | |
x, | |
y, | |
arr, | |
mouse, | |
activeListener, | |
currentDepth | |
) { | |
currentDepth = currentDepth || 0; | |
if (!currentDepth && !this._testMask(this, x, y)) { | |
return null; | |
} | |
var mtx, | |
ctx = createjs.DisplayObject._hitTestContext; | |
activeListener = activeListener || (mouse && this._hasMouseEventListener()); | |
// draw children one at a time, and check if we get a hit: | |
var children = this.children, | |
l = children.length; | |
for (var i = l - 1; i >= 0; i--) { | |
var child = children[i]; | |
var hitArea = child.hitArea; | |
if ( | |
!child.visible || | |
(!hitArea && !child.isVisible()) || | |
(mouse && !child.mouseEnabled) | |
) { | |
continue; | |
} | |
if (!hitArea && !this._testMask(child, x, y)) { | |
continue; | |
} | |
// if a child container has a hitArea then we only need to check its hitAre2a, so we can treat it as a normal DO: | |
if (!hitArea && child instanceof Container) { | |
var result = child._getObjectsUnderPoint( | |
x, | |
y, | |
arr, | |
mouse, | |
activeListener, | |
currentDepth + 1 | |
); | |
if (!arr && result) { | |
return mouse && !this.mouseChildren ? this : result; | |
} | |
} else { | |
if (mouse && !activeListener && !child._hasMouseEventListener()) { | |
continue; | |
} | |
// TODO: can we pass displayProps forward, to avoid having to calculate this backwards every time? It's kind of a mixed bag. When we're only hunting for DOs with event listeners, it may not make sense. | |
var props = child.getConcatenatedDisplayProps(child._props); | |
mtx = props.matrix; | |
if (hitArea) { | |
mtx.appendMatrix(hitArea.getMatrix(hitArea._props.matrix)); | |
props.alpha = hitArea.alpha; | |
} | |
ctx.globalAlpha = props.alpha; | |
ctx.setTransform(mtx.a, mtx.b, mtx.c, mtx.d, mtx.tx - x, mtx.ty - y); | |
(hitArea || child).draw(ctx); | |
if (!this._testHit(ctx)) { | |
continue; | |
} | |
ctx.setTransform(1, 0, 0, 1, 0, 0); | |
ctx.clearRect(0, 0, 2, 2); | |
if (arr) { | |
arr.push(child); | |
} else { | |
return mouse && !this.mouseChildren ? this : child; | |
} | |
} | |
} | |
return null; | |
}; | |
/** | |
* @method _testMask | |
* @param {DisplayObject} target | |
* @param {Number} x | |
* @param {Number} y | |
* @return {Boolean} Indicates whether the x/y is within the masked region. | |
* @protected | |
**/ | |
p._testMask = function (target, x, y) { | |
var mask = target.mask; | |
if (!mask || !mask.graphics || mask.graphics.isEmpty()) { | |
return true; | |
} | |
var mtx = this._props.matrix, | |
parent = target.parent; | |
mtx = parent ? parent.getConcatenatedMatrix(mtx) : mtx.identity(); | |
mtx = mask.getMatrix(mask._props.matrix).prependMatrix(mtx); | |
var ctx = createjs.DisplayObject._hitTestContext; | |
ctx.setTransform(mtx.a, mtx.b, mtx.c, mtx.d, mtx.tx - x, mtx.ty - y); | |
// draw the mask as a solid fill: | |
mask.graphics.drawAsPath(ctx); | |
ctx.fillStyle = "#000"; | |
ctx.fill(); | |
if (!this._testHit(ctx)) { | |
return false; | |
} | |
ctx.setTransform(1, 0, 0, 1, 0, 0); | |
ctx.clearRect(0, 0, 2, 2); | |
return true; | |
}; | |
/** | |
* @method _getBounds | |
* @param {Matrix2D} matrix | |
* @param {Boolean} ignoreTransform If true, does not apply this object's transform. | |
* @return {Rectangle} | |
* @protected | |
**/ | |
p._getBounds = function (matrix, ignoreTransform) { | |
var bounds = this.DisplayObject_getBounds(); | |
if (bounds) { | |
return this._transformBounds(bounds, matrix, ignoreTransform); | |
} | |
var mtx = this._props.matrix; | |
mtx = ignoreTransform ? mtx.identity() : this.getMatrix(mtx); | |
if (matrix) { | |
mtx.prependMatrix(matrix); | |
} | |
var l = this.children.length, | |
rect = null; | |
for (var i = 0; i < l; i++) { | |
var child = this.children[i]; | |
if (!child.visible || !(bounds = child._getBounds(mtx))) { | |
continue; | |
} | |
if (rect) { | |
rect.extend(bounds.x, bounds.y, bounds.width, bounds.height); | |
} else { | |
rect = bounds.clone(); | |
} | |
} | |
return rect; | |
}; | |
createjs.Container = createjs.promote(Container, "DisplayObject"); | |
})(); | |
//############################################################################## | |
// Stage.js | |
//############################################################################## | |
this.createjs = this.createjs || {}; | |
(function () { | |
"use strict"; | |
// constructor: | |
/** | |
* A stage is the root level {{#crossLink "Container"}}{{/crossLink}} for a display list. Each time its {{#crossLink "Stage/tick"}}{{/crossLink}} | |
* method is called, it will render its display list to its target canvas. | |
* | |
* <h4>Example</h4> | |
* This example creates a stage, adds a child to it, then uses {{#crossLink "Ticker"}}{{/crossLink}} to update the child | |
* and redraw the stage using {{#crossLink "Stage/update"}}{{/crossLink}}. | |
* | |
* var stage = new createjs.Stage("canvasElementId"); | |
* var image = new createjs.Bitmap("imagePath.png"); | |
* stage.addChild(image); | |
* createjs.Ticker.addEventListener("tick", handleTick); | |
* function handleTick(event) { | |
* image.x += 10; | |
* stage.update(); | |
* } | |
* | |
* @class Stage | |
* @extends Container | |
* @constructor | |
* @param {HTMLCanvasElement | String | Object} canvas A canvas object that the Stage will render to, or the string id | |
* of a canvas object in the current document. | |
**/ | |
function Stage(canvas) { | |
this.Container_constructor(); | |
// public properties: | |
/** | |
* Indicates whether the stage should automatically clear the canvas before each render. You can set this to <code>false</code> | |
* to manually control clearing (for generative art, or when pointing multiple stages at the same canvas for | |
* example). | |
* | |
* <h4>Example</h4> | |
* | |
* var stage = new createjs.Stage("canvasId"); | |
* stage.autoClear = false; | |
* | |
* @property autoClear | |
* @type Boolean | |
* @default true | |
**/ | |
this.autoClear = true; | |
/** | |
* The canvas the stage will render to. Multiple stages can share a single canvas, but you must disable autoClear for all but the | |
* first stage that will be ticked (or they will clear each other's render). | |
* | |
* When changing the canvas property you must disable the events on the old canvas, and enable events on the | |
* new canvas or mouse events will not work as expected. For example: | |
* | |
* myStage.enableDOMEvents(false); | |
* myStage.canvas = anotherCanvas; | |
* myStage.enableDOMEvents(true); | |
* | |
* @property canvas | |
* @type HTMLCanvasElement | Object | |
**/ | |
this.canvas = | |
typeof canvas == "string" ? document.getElementById(canvas) : canvas; | |
/** | |
* The current mouse X position on the canvas. If the mouse leaves the canvas, this will indicate the most recent | |
* position over the canvas, and mouseInBounds will be set to false. | |
* @property mouseX | |
* @type Number | |
* @readonly | |
**/ | |
this.mouseX = 0; | |
/** | |
* The current mouse Y position on the canvas. If the mouse leaves the canvas, this will indicate the most recent | |
* position over the canvas, and mouseInBounds will be set to false. | |
* @property mouseY | |
* @type Number | |
* @readonly | |
**/ | |
this.mouseY = 0; | |
/** | |
* Specifies the area of the stage to affect when calling update. This can be use to selectively | |
* re-draw specific regions of the canvas. If null, the whole canvas area is drawn. | |
* @property drawRect | |
* @type {Rectangle} | |
*/ | |
this.drawRect = null; | |
/** | |
* Indicates whether display objects should be rendered on whole pixels. You can set the | |
* {{#crossLink "DisplayObject/snapToPixel"}}{{/crossLink}} property of | |
* display objects to false to enable/disable this behaviour on a per instance basis. | |
* @property snapToPixelEnabled | |
* @type Boolean | |
* @default false | |
**/ | |
this.snapToPixelEnabled = false; | |
/** | |
* Indicates whether the mouse is currently within the bounds of the canvas. | |
* @property mouseInBounds | |
* @type Boolean | |
* @default false | |
**/ | |
this.mouseInBounds = false; | |
/** | |
* If true, tick callbacks will be called on all display objects on the stage prior to rendering to the canvas. | |
* @property tickOnUpdate | |
* @type Boolean | |
* @default true | |
**/ | |
this.tickOnUpdate = true; | |
/** | |
* If true, mouse move events will continue to be called when the mouse leaves the target canvas. See | |
* {{#crossLink "Stage/mouseInBounds:property"}}{{/crossLink}}, and {{#crossLink "MouseEvent"}}{{/crossLink}} | |
* x/y/rawX/rawY. | |
* @property mouseMoveOutside | |
* @type Boolean | |
* @default false | |
**/ | |
this.mouseMoveOutside = false; | |
/** | |
* Prevents selection of other elements in the html page if the user clicks and drags, or double clicks on the canvas. | |
* This works by calling `preventDefault()` on any mousedown events (or touch equivalent) originating on the canvas. | |
* @property preventSelection | |
* @type Boolean | |
* @default true | |
**/ | |
this.preventSelection = true; | |
/** | |
* The hitArea property is not supported for Stage. | |
* @property hitArea | |
* @type {DisplayObject} | |
* @default null | |
*/ | |
// private properties: | |
/** | |
* Holds objects with data for each active pointer id. Each object has the following properties: | |
* x, y, event, target, overTarget, overX, overY, inBounds, posEvtObj (native event that last updated position) | |
* @property _pointerData | |
* @type {Object} | |
* @private | |
*/ | |
this._pointerData = {}; | |
/** | |
* Number of active pointers. | |
* @property _pointerCount | |
* @type {Object} | |
* @private | |
*/ | |
this._pointerCount = 0; | |
/** | |
* The ID of the primary pointer. | |
* @property _primaryPointerID | |
* @type {Object} | |
* @private | |
*/ | |
this._primaryPointerID = null; | |
/** | |
* @property _mouseOverIntervalID | |
* @protected | |
* @type Number | |
**/ | |
this._mouseOverIntervalID = null; | |
/** | |
* @property _nextStage | |
* @protected | |
* @type Stage | |
**/ | |
this._nextStage = null; | |
/** | |
* @property _prevStage | |
* @protected | |
* @type Stage | |
**/ | |
this._prevStage = null; | |
// initialize: | |
this.enableDOMEvents(true); | |
} | |
var p = createjs.extend(Stage, createjs.Container); | |
// events: | |
/** | |
* Dispatched when the user moves the mouse over the canvas. | |
* See the {{#crossLink "MouseEvent"}}{{/crossLink}} class for a listing of event properties. | |
* @event stagemousemove | |
* @since 0.6.0 | |
*/ | |
/** | |
* Dispatched when the user presses their left mouse button on the canvas. See the {{#crossLink "MouseEvent"}}{{/crossLink}} | |
* class for a listing of event properties. | |
* @event stagemousedown | |
* @since 0.6.0 | |
*/ | |
/** | |
* Dispatched when the user the user presses somewhere on the stage, then releases the mouse button anywhere that the page can detect it (this varies slightly between browsers). | |
* You can use {{#crossLink "Stage/mouseInBounds:property"}}{{/crossLink}} to check whether the mouse is currently within the stage bounds. | |
* See the {{#crossLink "MouseEvent"}}{{/crossLink}} class for a listing of event properties. | |
* @event stagemouseup | |
* @since 0.6.0 | |
*/ | |
/** | |
* Dispatched when the mouse moves from within the canvas area (mouseInBounds == true) to outside it (mouseInBounds == false). | |
* This is currently only dispatched for mouse input (not touch). See the {{#crossLink "MouseEvent"}}{{/crossLink}} | |
* class for a listing of event properties. | |
* @event mouseleave | |
* @since 0.7.0 | |
*/ | |
/** | |
* Dispatched when the mouse moves into the canvas area (mouseInBounds == false) from outside it (mouseInBounds == true). | |
* This is currently only dispatched for mouse input (not touch). See the {{#crossLink "MouseEvent"}}{{/crossLink}} | |
* class for a listing of event properties. | |
* @event mouseenter | |
* @since 0.7.0 | |
*/ | |
/** | |
* Dispatched each update immediately before the tick event is propagated through the display list. | |
* You can call preventDefault on the event object to cancel propagating the tick event. | |
* @event tickstart | |
* @since 0.7.0 | |
*/ | |
/** | |
* Dispatched each update immediately after the tick event is propagated through the display list. Does not fire if | |
* tickOnUpdate is false. Precedes the "drawstart" event. | |
* @event tickend | |
* @since 0.7.0 | |
*/ | |
/** | |
* Dispatched each update immediately before the canvas is cleared and the display list is drawn to it. | |
* You can call preventDefault on the event object to cancel the draw. | |
* @event drawstart | |
* @since 0.7.0 | |
*/ | |
/** | |
* Dispatched each update immediately after the display list is drawn to the canvas and the canvas context is restored. | |
* @event drawend | |
* @since 0.7.0 | |
*/ | |
// getter / setters: | |
/** | |
* Specifies a target stage that will have mouse / touch interactions relayed to it after this stage handles them. | |
* This can be useful in cases where you have multiple layered canvases and want user interactions | |
* events to pass through. For example, this would relay mouse events from topStage to bottomStage: | |
* | |
* topStage.nextStage = bottomStage; | |
* | |
* To disable relaying, set nextStage to null. | |
* | |
* MouseOver, MouseOut, RollOver, and RollOut interactions are also passed through using the mouse over settings | |
* of the top-most stage, but are only processed if the target stage has mouse over interactions enabled. | |
* Considerations when using roll over in relay targets:<OL> | |
* <LI> The top-most (first) stage must have mouse over interactions enabled (via enableMouseOver)</LI> | |
* <LI> All stages that wish to participate in mouse over interaction must enable them via enableMouseOver</LI> | |
* <LI> All relay targets will share the frequency value of the top-most stage</LI> | |
* </OL> | |
* To illustrate, in this example the targetStage would process mouse over interactions at 10hz (despite passing | |
* 30 as it's desired frequency): | |
* topStage.nextStage = targetStage; | |
* topStage.enableMouseOver(10); | |
* targetStage.enableMouseOver(30); | |
* | |
* If the target stage's canvas is completely covered by this stage's canvas, you may also want to disable its | |
* DOM events using: | |
* | |
* targetStage.enableDOMEvents(false); | |
* | |
* @property nextStage | |
* @type {Stage} | |
**/ | |
p._get_nextStage = function () { | |
return this._nextStage; | |
}; | |
p._set_nextStage = function (value) { | |
if (this._nextStage) { | |
this._nextStage._prevStage = null; | |
} | |
if (value) { | |
value._prevStage = this; | |
} | |
this._nextStage = value; | |
}; | |
try { | |
Object.defineProperties(p, { | |
nextStage: { get: p._get_nextStage, set: p._set_nextStage }, | |
}); | |
} catch (e) {} // TODO: use Log | |
// public methods: | |
/** | |
* Each time the update method is called, the stage will call {{#crossLink "Stage/tick"}}{{/crossLink}} | |
* unless {{#crossLink "Stage/tickOnUpdate:property"}}{{/crossLink}} is set to false, | |
* and then render the display list to the canvas. | |
* | |
* @method update | |
* @param {Object} [props] Props object to pass to `tick()`. Should usually be a {{#crossLink "Ticker"}}{{/crossLink}} event object, or similar object with a delta property. | |
**/ | |
p.update = function (props) { | |
if (!this.canvas) { | |
return; | |
} | |
if (this.tickOnUpdate) { | |
this.tick(props); | |
} | |
if (this.dispatchEvent("drawstart", false, true) === false) { | |
return; | |
} | |
createjs.DisplayObject._snapToPixelEnabled = this.snapToPixelEnabled; | |
var r = this.drawRect, | |
ctx = this.canvas.getContext("2d"); | |
ctx.setTransform(1, 0, 0, 1, 0, 0); | |
if (this.autoClear) { | |
if (r) { | |
ctx.clearRect(r.x, r.y, r.width, r.height); | |
} else { | |
ctx.clearRect(0, 0, this.canvas.width + 1, this.canvas.height + 1); | |
} | |
} | |
ctx.save(); | |
if (this.drawRect) { | |
ctx.beginPath(); | |
ctx.rect(r.x, r.y, r.width, r.height); | |
ctx.clip(); | |
} | |
this.updateContext(ctx); | |
this.draw(ctx, false); | |
ctx.restore(); | |
this.dispatchEvent("drawend"); | |
}; | |
/** | |
* Propagates a tick event through the display list. This is automatically called by {{#crossLink "Stage/update"}}{{/crossLink}} | |
* unless {{#crossLink "Stage/tickOnUpdate:property"}}{{/crossLink}} is set to false. | |
* | |
* If a props object is passed to `tick()`, then all of its properties will be copied to the event object that is | |
* propagated to listeners. | |
* | |
* Some time-based features in EaselJS (for example {{#crossLink "Sprite/framerate"}}{{/crossLink}} require that | |
* a {{#crossLink "Ticker/tick:event"}}{{/crossLink}} event object (or equivalent object with a delta property) be | |
* passed as the `props` parameter to `tick()`. For example: | |
* | |
* Ticker.on("tick", handleTick); | |
* function handleTick(evtObj) { | |
* // clone the event object from Ticker, and add some custom data to it: | |
* var evt = evtObj.clone().set({greeting:"hello", name:"world"}); | |
* | |
* // pass it to stage.update(): | |
* myStage.update(evt); // subsequently calls tick() with the same param | |
* } | |
* | |
* // ... | |
* myDisplayObject.on("tick", handleDisplayObjectTick); | |
* function handleDisplayObjectTick(evt) { | |
* console.log(evt.delta); // the delta property from the Ticker tick event object | |
* console.log(evt.greeting, evt.name); // custom data: "hello world" | |
* } | |
* | |
* @method tick | |
* @param {Object} [props] An object with properties that should be copied to the event object. Should usually be a Ticker event object, or similar object with a delta property. | |
**/ | |
p.tick = function (props) { | |
if ( | |
!this.tickEnabled || | |
this.dispatchEvent("tickstart", false, true) === false | |
) { | |
return; | |
} | |
var evtObj = new createjs.Event("tick"); | |
if (props) { | |
for (var n in props) { | |
if (props.hasOwnProperty(n)) { | |
evtObj[n] = props[n]; | |
} | |
} | |
} | |
this._tick(evtObj); | |
this.dispatchEvent("tickend"); | |
}; | |
/** | |
* Default event handler that calls the Stage {{#crossLink "Stage/update"}}{{/crossLink}} method when a {{#crossLink "DisplayObject/tick:event"}}{{/crossLink}} | |
* event is received. This allows you to register a Stage instance as a event listener on {{#crossLink "Ticker"}}{{/crossLink}} | |
* directly, using: | |
* | |
* Ticker.addEventListener("tick", myStage); | |
* | |
* Note that if you subscribe to ticks using this pattern, then the tick event object will be passed through to | |
* display object tick handlers, instead of <code>delta</code> and <code>paused</code> parameters. | |
* @property handleEvent | |
* @type Function | |
**/ | |
p.handleEvent = function (evt) { | |
if (evt.type == "tick") { | |
this.update(evt); | |
} | |
}; | |
/** | |
* Clears the target canvas. Useful if {{#crossLink "Stage/autoClear:property"}}{{/crossLink}} is set to `false`. | |
* @method clear | |
**/ | |
p.clear = function () { | |
if (!this.canvas) { | |
return; | |
} | |
var ctx = this.canvas.getContext("2d"); | |
ctx.setTransform(1, 0, 0, 1, 0, 0); | |
ctx.clearRect(0, 0, this.canvas.width + 1, this.canvas.height + 1); | |
}; | |
/** | |
* Returns a data url that contains a Base64-encoded image of the contents of the stage. The returned data url can | |
* be specified as the src value of an image element. | |
* @method toDataURL | |
* @param {String} [backgroundColor] The background color to be used for the generated image. Any valid CSS color | |
* value is allowed. The default value is a transparent background. | |
* @param {String} [mimeType="image/png"] The MIME type of the image format to be create. The default is "image/png". If an unknown MIME type | |
* is passed in, or if the browser does not support the specified MIME type, the default value will be used. | |
* @return {String} a Base64 encoded image. | |
**/ | |
p.toDataURL = function (backgroundColor, mimeType) { | |
var data, | |
ctx = this.canvas.getContext("2d"), | |
w = this.canvas.width, | |
h = this.canvas.height; | |
if (backgroundColor) { | |
data = ctx.getImageData(0, 0, w, h); | |
var compositeOperation = ctx.globalCompositeOperation; | |
ctx.globalCompositeOperation = "destination-over"; | |
ctx.fillStyle = backgroundColor; | |
ctx.fillRect(0, 0, w, h); | |
} | |
var dataURL = this.canvas.toDataURL(mimeType || "image/png"); | |
if (backgroundColor) { | |
ctx.putImageData(data, 0, 0); | |
ctx.globalCompositeOperation = compositeOperation; | |
} | |
return dataURL; | |
}; | |
/** | |
* Enables or disables (by passing a frequency of 0) mouse over ({{#crossLink "DisplayObject/mouseover:event"}}{{/crossLink}} | |
* and {{#crossLink "DisplayObject/mouseout:event"}}{{/crossLink}}) and roll over events ({{#crossLink "DisplayObject/rollover:event"}}{{/crossLink}} | |
* and {{#crossLink "DisplayObject/rollout:event"}}{{/crossLink}}) for this stage's display list. These events can | |
* be expensive to generate, so they are disabled by default. The frequency of the events can be controlled | |
* independently of mouse move events via the optional `frequency` parameter. | |
* | |
* <h4>Example</h4> | |
* | |
* var stage = new createjs.Stage("canvasId"); | |
* stage.enableMouseOver(10); // 10 updates per second | |
* | |
* @method enableMouseOver | |
* @param {Number} [frequency=20] Optional param specifying the maximum number of times per second to broadcast | |
* mouse over/out events. Set to 0 to disable mouse over events completely. Maximum is 50. A lower frequency is less | |
* responsive, but uses less CPU. | |
**/ | |
p.enableMouseOver = function (frequency) { | |
if (this._mouseOverIntervalID) { | |
clearInterval(this._mouseOverIntervalID); | |
this._mouseOverIntervalID = null; | |
if (frequency == 0) { | |
this._testMouseOver(true); | |
} | |
} | |
if (frequency == null) { | |
frequency = 20; | |
} else if (frequency <= 0) { | |
return; | |
} | |
var o = this; | |
this._mouseOverIntervalID = setInterval(function () { | |
o._testMouseOver(); | |
}, 1000 / Math.min(50, frequency)); | |
}; | |
/** | |
* Enables or disables the event listeners that stage adds to DOM elements (window, document and canvas). It is good | |
* practice to disable events when disposing of a Stage instance, otherwise the stage will continue to receive | |
* events from the page. | |
* | |
* When changing the canvas property you must disable the events on the old canvas, and enable events on the | |
* new canvas or mouse events will not work as expected. For example: | |
* | |
* myStage.enableDOMEvents(false); | |
* myStage.canvas = anotherCanvas; | |
* myStage.enableDOMEvents(true); | |
* | |
* @method enableDOMEvents | |
* @param {Boolean} [enable=true] Indicates whether to enable or disable the events. Default is true. | |
**/ | |
p.enableDOMEvents = function (enable) { | |
if (enable == null) { | |
enable = true; | |
} | |
var n, | |
o, | |
ls = this._eventListeners; | |
if (!enable && ls) { | |
for (n in ls) { | |
o = ls[n]; | |
o.t.removeEventListener(n, o.f, false); | |
} | |
this._eventListeners = null; | |
} else if (enable && !ls && this.canvas) { | |
var t = window.addEventListener ? window : document; | |
var _this = this; | |
ls = this._eventListeners = {}; | |
ls["mouseup"] = { | |
t: t, | |
f: function (e) { | |
_this._handleMouseUp(e); | |
}, | |
}; | |
ls["mousemove"] = { | |
t: t, | |
f: function (e) { | |
_this._handleMouseMove(e); | |
}, | |
}; | |
ls["dblclick"] = { | |
t: this.canvas, | |
f: function (e) { | |
_this._handleDoubleClick(e); | |
}, | |
}; | |
ls["mousedown"] = { | |
t: this.canvas, | |
f: function (e) { | |
_this._handleMouseDown(e); | |
}, | |
}; | |
for (n in ls) { | |
o = ls[n]; | |
o.t.addEventListener(n, o.f, false); | |
} | |
} | |
}; | |
/** | |
* Stage instances cannot be cloned. | |
* @method clone | |
**/ | |
p.clone = function () { | |
throw "Stage cannot be cloned."; | |
}; | |
/** | |
* Returns a string representation of this object. | |
* @method toString | |
* @return {String} a string representation of the instance. | |
**/ | |
p.toString = function () { | |
return "[Stage (name=" + this.name + ")]"; | |
}; | |
// private methods: | |
/** | |
* @method _getElementRect | |
* @protected | |
* @param {HTMLElement} e | |
**/ | |
p._getElementRect = function (e) { | |
var bounds; | |
try { | |
bounds = e.getBoundingClientRect(); | |
} catch (err) { | |
// this can fail on disconnected DOM elements in IE9 | |
bounds = { | |
top: e.offsetTop, | |
left: e.offsetLeft, | |
width: e.offsetWidth, | |
height: e.offsetHeight, | |
}; | |
} | |
var offX = | |
(window.pageXOffset || document.scrollLeft || 0) - | |
(document.clientLeft || document.body.clientLeft || 0); | |
var offY = | |
(window.pageYOffset || document.scrollTop || 0) - | |
(document.clientTop || document.body.clientTop || 0); | |
var styles = window.getComputedStyle | |
? getComputedStyle(e, null) | |
: e.currentStyle; // IE <9 compatibility. | |
var padL = parseInt(styles.paddingLeft) + parseInt(styles.borderLeftWidth); | |
var padT = parseInt(styles.paddingTop) + parseInt(styles.borderTopWidth); | |
var padR = | |
parseInt(styles.paddingRight) + parseInt(styles.borderRightWidth); | |
var padB = | |
parseInt(styles.paddingBottom) + parseInt(styles.borderBottomWidth); | |
// note: in some browsers bounds properties are read only. | |
return { | |
left: bounds.left + offX + padL, | |
right: bounds.right + offX - padR, | |
top: bounds.top + offY + padT, | |
bottom: bounds.bottom + offY - padB, | |
}; | |
}; | |
/** | |
* @method _getPointerData | |
* @protected | |
* @param {Number} id | |
**/ | |
p._getPointerData = function (id) { | |
var data = this._pointerData[id]; | |
if (!data) { | |
data = this._pointerData[id] = { x: 0, y: 0 }; | |
} | |
return data; | |
}; | |
/** | |
* @method _handleMouseMove | |
* @protected | |
* @param {MouseEvent} e | |
**/ | |
p._handleMouseMove = function (e) { | |
if (!e) { | |
e = window.event; | |
} | |
this._handlePointerMove(-1, e, e.pageX, e.pageY); | |
}; | |
/** | |
* @method _handlePointerMove | |
* @protected | |
* @param {Number} id | |
* @param {Event} e | |
* @param {Number} pageX | |
* @param {Number} pageY | |
* @param {Stage} owner Indicates that the event has already been captured & handled by the indicated stage. | |
**/ | |
p._handlePointerMove = function (id, e, pageX, pageY, owner) { | |
if (this._prevStage && owner === undefined) { | |
return; | |
} // redundant listener. | |
if (!this.canvas) { | |
return; | |
} | |
var nextStage = this._nextStage, | |
o = this._getPointerData(id); | |
var inBounds = o.inBounds; | |
this._updatePointerPosition(id, e, pageX, pageY); | |
if (inBounds || o.inBounds || this.mouseMoveOutside) { | |
if (id === -1 && o.inBounds == !inBounds) { | |
this._dispatchMouseEvent( | |
this, | |
inBounds ? "mouseleave" : "mouseenter", | |
false, | |
id, | |
o, | |
e | |
); | |
} | |
this._dispatchMouseEvent(this, "stagemousemove", false, id, o, e); | |
this._dispatchMouseEvent(o.target, "pressmove", true, id, o, e); | |
} | |
nextStage && nextStage._handlePointerMove(id, e, pageX, pageY, null); | |
}; | |
/** | |
* @method _updatePointerPosition | |
* @protected | |
* @param {Number} id | |
* @param {Event} e | |
* @param {Number} pageX | |
* @param {Number} pageY | |
**/ | |
p._updatePointerPosition = function (id, e, pageX, pageY) { | |
var rect = this._getElementRect(this.canvas); | |
pageX -= rect.left; | |
pageY -= rect.top; | |
var w = this.canvas.width; | |
var h = this.canvas.height; | |
pageX /= (rect.right - rect.left) / w; | |
pageY /= (rect.bottom - rect.top) / h; | |
var o = this._getPointerData(id); | |
if ( | |
(o.inBounds = | |
pageX >= 0 && pageY >= 0 && pageX <= w - 1 && pageY <= h - 1) | |
) { | |
o.x = pageX; | |
o.y = pageY; | |
} else if (this.mouseMoveOutside) { | |
o.x = pageX < 0 ? 0 : pageX > w - 1 ? w - 1 : pageX; | |
o.y = pageY < 0 ? 0 : pageY > h - 1 ? h - 1 : pageY; | |
} | |
o.posEvtObj = e; | |
o.rawX = pageX; | |
o.rawY = pageY; | |
if (id === this._primaryPointerID || id === -1) { | |
this.mouseX = o.x; | |
this.mouseY = o.y; | |
this.mouseInBounds = o.inBounds; | |
} | |
}; | |
/** | |
* @method _handleMouseUp | |
* @protected | |
* @param {MouseEvent} e | |
**/ | |
p._handleMouseUp = function (e) { | |
this._handlePointerUp(-1, e, false); | |
}; | |
/** | |
* @method _handlePointerUp | |
* @protected | |
* @param {Number} id | |
* @param {Event} e | |
* @param {Boolean} clear | |
* @param {Stage} owner Indicates that the event has already been captured & handled by the indicated stage. | |
**/ | |
p._handlePointerUp = function (id, e, clear, owner) { | |
var nextStage = this._nextStage, | |
o = this._getPointerData(id); | |
if (this._prevStage && owner === undefined) { | |
return; | |
} // redundant listener. | |
var target = null, | |
oTarget = o.target; | |
if (!owner && (oTarget || nextStage)) { | |
target = this._getObjectsUnderPoint(o.x, o.y, null, true); | |
} | |
if (o.down) { | |
this._dispatchMouseEvent(this, "stagemouseup", false, id, o, e, target); | |
o.down = false; | |
} | |
if (target == oTarget) { | |
this._dispatchMouseEvent(oTarget, "click", true, id, o, e); | |
} | |
this._dispatchMouseEvent(oTarget, "pressup", true, id, o, e); | |
if (clear) { | |
if (id == this._primaryPointerID) { | |
this._primaryPointerID = null; | |
} | |
delete this._pointerData[id]; | |
} else { | |
o.target = null; | |
} | |
nextStage && | |
nextStage._handlePointerUp(id, e, clear, owner || (target && this)); | |
}; | |
/** | |
* @method _handleMouseDown | |
* @protected | |
* @param {MouseEvent} e | |
**/ | |
p._handleMouseDown = function (e) { | |
this._handlePointerDown(-1, e, e.pageX, e.pageY); | |
}; | |
/** | |
* @method _handlePointerDown | |
* @protected | |
* @param {Number} id | |
* @param {Event} e | |
* @param {Number} pageX | |
* @param {Number} pageY | |
* @param {Stage} owner Indicates that the event has already been captured & handled by the indicated stage. | |
**/ | |
p._handlePointerDown = function (id, e, pageX, pageY, owner) { | |
if (this.preventSelection) { | |
e.preventDefault(); | |
} | |
if (this._primaryPointerID == null || id === -1) { | |
this._primaryPointerID = id; | |
} // mouse always takes over. | |
if (pageY != null) { | |
this._updatePointerPosition(id, e, pageX, pageY); | |
} | |
var target = null, | |
nextStage = this._nextStage, | |
o = this._getPointerData(id); | |
if (!owner) { | |
target = o.target = this._getObjectsUnderPoint(o.x, o.y, null, true); | |
} | |
if (o.inBounds) { | |
this._dispatchMouseEvent(this, "stagemousedown", false, id, o, e, target); | |
o.down = true; | |
} | |
this._dispatchMouseEvent(target, "mousedown", true, id, o, e); | |
nextStage && | |
nextStage._handlePointerDown( | |
id, | |
e, | |
pageX, | |
pageY, | |
owner || (target && this) | |
); | |
}; | |
/** | |
* @method _testMouseOver | |
* @param {Boolean} clear If true, clears the mouseover / rollover (ie. no target) | |
* @param {Stage} owner Indicates that the event has already been captured & handled by the indicated stage. | |
* @param {Stage} eventTarget The stage that the cursor is actively over. | |
* @protected | |
**/ | |
p._testMouseOver = function (clear, owner, eventTarget) { | |
if (this._prevStage && owner === undefined) { | |
return; | |
} // redundant listener. | |
var nextStage = this._nextStage; | |
if (!this._mouseOverIntervalID) { | |
// not enabled for mouseover, but should still relay the event. | |
nextStage && nextStage._testMouseOver(clear, owner, eventTarget); | |
return; | |
} | |
var o = this._getPointerData(-1); | |
// only update if the mouse position has changed. This provides a lot of optimization, but has some trade-offs. | |
if ( | |
!o || | |
(!clear && | |
this.mouseX == this._mouseOverX && | |
this.mouseY == this._mouseOverY && | |
this.mouseInBounds) | |
) { | |
return; | |
} | |
var e = o.posEvtObj; | |
var isEventTarget = eventTarget || (e && e.target == this.canvas); | |
var target = null, | |
common = -1, | |
cursor = "", | |
t, | |
i, | |
l; | |
if (!owner && (clear || (this.mouseInBounds && isEventTarget))) { | |
target = this._getObjectsUnderPoint(this.mouseX, this.mouseY, null, true); | |
this._mouseOverX = this.mouseX; | |
this._mouseOverY = this.mouseY; | |
} | |
var oldList = this._mouseOverTarget || []; | |
var oldTarget = oldList[oldList.length - 1]; | |
var list = (this._mouseOverTarget = []); | |
// generate ancestor list and check for cursor: | |
t = target; | |
while (t) { | |
list.unshift(t); | |
if (!cursor) { | |
cursor = t.cursor; | |
} | |
t = t.parent; | |
} | |
this.canvas.style.cursor = cursor; | |
if (!owner && eventTarget) { | |
eventTarget.canvas.style.cursor = cursor; | |
} | |
// find common ancestor: | |
for (i = 0, l = list.length; i < l; i++) { | |
if (list[i] != oldList[i]) { | |
break; | |
} | |
common = i; | |
} | |
if (oldTarget != target) { | |
this._dispatchMouseEvent(oldTarget, "mouseout", true, -1, o, e, target); | |
} | |
for (i = oldList.length - 1; i > common; i--) { | |
this._dispatchMouseEvent(oldList[i], "rollout", false, -1, o, e, target); | |
} | |
for (i = list.length - 1; i > common; i--) { | |
this._dispatchMouseEvent(list[i], "rollover", false, -1, o, e, oldTarget); | |
} | |
if (oldTarget != target) { | |
this._dispatchMouseEvent(target, "mouseover", true, -1, o, e, oldTarget); | |
} | |
nextStage && | |
nextStage._testMouseOver( | |
clear, | |
owner || (target && this), | |
eventTarget || (isEventTarget && this) | |
); | |
}; | |
/** | |
* @method _handleDoubleClick | |
* @protected | |
* @param {MouseEvent} e | |
* @param {Stage} owner Indicates that the event has already been captured & handled by the indicated stage. | |
**/ | |
p._handleDoubleClick = function (e, owner) { | |
var target = null, | |
nextStage = this._nextStage, | |
o = this._getPointerData(-1); | |
if (!owner) { | |
target = this._getObjectsUnderPoint(o.x, o.y, null, true); | |
this._dispatchMouseEvent(target, "dblclick", true, -1, o, e); | |
} | |
nextStage && nextStage._handleDoubleClick(e, owner || (target && this)); | |
}; | |
/** | |
* @method _dispatchMouseEvent | |
* @protected | |
* @param {DisplayObject} target | |
* @param {String} type | |
* @param {Boolean} bubbles | |
* @param {Number} pointerId | |
* @param {Object} o | |
* @param {MouseEvent} [nativeEvent] | |
* @param {DisplayObject} [relatedTarget] | |
**/ | |
p._dispatchMouseEvent = function ( | |
target, | |
type, | |
bubbles, | |
pointerId, | |
o, | |
nativeEvent, | |
relatedTarget | |
) { | |
// TODO: might be worth either reusing MouseEvent instances, or adding a willTrigger method to avoid GC. | |
if (!target || (!bubbles && !target.hasEventListener(type))) { | |
return; | |
} | |
/* | |
// TODO: account for stage transformations? | |
this._mtx = this.getConcatenatedMatrix(this._mtx).invert(); | |
var pt = this._mtx.transformPoint(o.x, o.y); | |
var evt = new createjs.MouseEvent(type, bubbles, false, pt.x, pt.y, nativeEvent, pointerId, pointerId==this._primaryPointerID || pointerId==-1, o.rawX, o.rawY); | |
*/ | |
var evt = new createjs.MouseEvent( | |
type, | |
bubbles, | |
false, | |
o.x, | |
o.y, | |
nativeEvent, | |
pointerId, | |
pointerId === this._primaryPointerID || pointerId === -1, | |
o.rawX, | |
o.rawY, | |
relatedTarget | |
); | |
target.dispatchEvent(evt); | |
}; | |
createjs.Stage = createjs.promote(Stage, "Container"); | |
})(); | |
//############################################################################## | |
// StageGL.js | |
//############################################################################## | |
this.createjs = this.createjs || {}; | |
/* | |
* README IF EDITING: | |
* Terminology for developers: | |
* | |
* Vertex: a point that help defines a shape, 3 per triangle. Usually has an x,y,z but can have more/less info. | |
* Vertex Property: a piece of information attached to the vertex like a vector3 containing x,y,z | |
* Index/Indices: used in groups of 3 to define a triangle, points to vertices by their index in an array (some render | |
* modes do not use these) | |
* Card: a group of 2 triangles used to display a rectangular image | |
* U/V: common names for the [0-1] texture co-ordinates on an image | |
* Batch: a single call to the renderer, best done as little as possible so multiple cards are put into a single batch | |
* Buffer: WebGL array data | |
* Program/Shader: For every vertex we run the Vertex shader. The results are used per pixel by the Fragment shader. When | |
* combined and paired these are a shader "program" | |
* Texture: WebGL representation of image data and associated extra information | |
* Slot: A space on the GPU into which textures can be loaded for use in a batch, using "ActiveTexture" switches texture slot. | |
*/ | |
(function () { | |
"use strict"; | |
/** | |
* A StageGL instance is the root level {{#crossLink "Container"}}{{/crossLink}} for an WebGL-optimized display list, | |
* which is used in place of the usual {{#crossLink "Stage"}}{{/crossLink}}. This class should behave identically to | |
* a {{#crossLink "Stage"}}{{/crossLink}} except for WebGL-specific functionality. | |
* | |
* Each time the {{#crossLink "Stage/tick"}}{{/crossLink}} method is called, the display list is rendered to the | |
* target <canvas/> instance, ignoring non-WebGL-compatible display objects. On devices and browsers that don't | |
* support WebGL, content will automatically be rendered to canvas 2D context instead. | |
* | |
* <h4>Limitations</h4> | |
* - {{#crossLink "Shape"}}{{/crossLink}}, {{#crossLink "Shadow"}}{{/crossLink}}, and {{#crossLink "Text"}}{{/crossLink}} | |
* are not rendered when added to the display list. | |
* - To display something StageGL cannot render, {{#crossLink "displayObject/cache"}}{{/crossLink}} the object. | |
* Caches can be rendered regardless of source. | |
* - Images are wrapped as a webGL "Texture". Each graphics card has a limit to its concurrent Textures, too many | |
* Textures will noticeably slow performance. | |
* - Each cache counts as an individual Texture. As such {{#crossLink "SpriteSheet"}}{{/crossLink}} and | |
* {{#crossLink "SpriteSheetBuilder"}}{{/crossLink}} are recommended practices to help keep texture counts low. | |
* - To use any image node (DOM Image/Canvas Element) between multiple StageGL instances it must be a | |
* {{#crossLink "Bitmap/clone"}}{{/crossLink}}, otherwise the GPU texture loading and tracking will get confused. | |
* - to avoid an up/down scaled render you must call {{#crossLink "StageGL/updateViewport"}}{{/crossLink}} if you | |
* resize your canvas after making a StageGL instance, this will properly size the WebGL context stored in memory. | |
* - Best performance in demanding scenarios will come from manual management of texture memory, but it is handled | |
* automatically by default. See {{#crossLink "StageGL/releaseTexture"}}{{/crossLink}} for details. | |
* | |
* <h4>Example</h4> | |
* This example creates a StageGL instance, adds a child to it, then uses the EaselJS {{#crossLink "Ticker"}}{{/crossLink}} | |
* to update the child and redraw the stage. | |
* | |
* var stage = new createjs.StageGL("canvasElementId"); | |
* | |
* var image = new createjs.Bitmap("imagePath.png"); | |
* stage.addChild(image); | |
* | |
* createjs.Ticker.on("tick", handleTick); | |
* | |
* function handleTick(event) { | |
* image.x += 10; | |
* stage.update(); | |
* } | |
* | |
* <h4>Notes</h4> | |
* - StageGL is not currently included in the minified version of EaselJS. | |
* - {{#crossLink "SpriteContainer"}}{{/crossLink}} (the previous approach to WebGL with EaselJS) has been deprecated. | |
* - Earlier versions of WebGL support in EaselJS (SpriteStage and SpriteContainer) had hard limitations on images | |
* per container, which have been solved. | |
* | |
* @class StageGL | |
* @extends Stage | |
* @constructor | |
* @param {HTMLCanvasElement | String | Object} canvas A canvas object that StageGL will render to, or the string id | |
* of a canvas object in the current DOM. | |
* @param {Object} options All the option parameters in a reference object, some are not supported by some browsers. | |
* @param {Boolean} [options.preserveBuffer=false] If `true`, the canvas is NOT auto-cleared by WebGL (the spec | |
* discourages setting this to `true`). This is useful if you want persistent draw effects. | |
* @param {Boolean} [options.antialias=false] Specifies whether or not the browser's WebGL implementation should try | |
* to perform anti-aliasing. This will also enable linear pixel sampling on power-of-two textures (smoother images). | |
* @param {Boolean} [options.transparent=false] If `true`, the canvas is transparent. This is <strong>very</strong> | |
* expensive, and should be used with caution. | |
* @param {Boolean} [options.premultiply=false] Alters color handling. If `true`, this assumes the shader must | |
* account for pre-multiplied alpha. This can help avoid visual halo effects with some assets, but may also cause | |
* problems with other assets. | |
* @param {Integer} [options.autoPurge=1200] How often the system should automatically dump unused textures with | |
* `purgeTextures(autoPurge)` every `autoPurge/2` draws. See {{#crossLink "StageGL/purgeTextures"}}{{/crossLink}} for more | |
* information. | |
*/ | |
function StageGL(canvas, options) { | |
this.Stage_constructor(canvas); | |
if (options !== undefined) { | |
if (typeof options !== "object") { | |
throw "Invalid options object"; | |
} | |
var premultiply = options.premultiply; | |
var transparent = options.transparent; | |
var antialias = options.antialias; | |
var preserveBuffer = options.preserveBuffer; | |
var autoPurge = options.autoPurge; | |
} | |
// public properties: | |
/** | |
* Console log potential issues and problems. This is designed to have <em>minimal</em> performance impact, so | |
* if extensive debugging information is required, this may be inadequate. See {{#crossLink "WebGLInspector"}}{{/crossLink}} | |
* @property vocalDebug | |
* @type {Boolean} | |
* @default false | |
*/ | |
this.vocalDebug = false; | |
// private properties: | |
/** | |
* Specifies whether or not the canvas is auto-cleared by WebGL. The WebGL spec discourages `true`. | |
* If true, the canvas is NOT auto-cleared by WebGL. Used when the canvas context is created and requires | |
* context re-creation to update. | |
* @property _preserveBuffer | |
* @protected | |
* @type {Boolean} | |
* @default false | |
*/ | |
this._preserveBuffer = preserveBuffer || false; | |
/** | |
* Specifies whether or not the browser's WebGL implementation should try to perform anti-aliasing. | |
* @property _antialias | |
* @protected | |
* @type {Boolean} | |
* @default false | |
*/ | |
this._antialias = antialias || false; | |
/** | |
* Specifies whether or not the browser's WebGL implementation should be transparent. | |
* @property _transparent | |
* @protected | |
* @type {Boolean} | |
* @default false | |
*/ | |
this._transparent = transparent || false; | |
/** | |
* Specifies whether or not StageGL is handling colours as premultiplied alpha. | |
* @property _premultiply | |
* @protected | |
* @type {Boolean} | |
* @default false | |
*/ | |
this._premultiply = premultiply || false; | |
/** | |
* Internal value of {{#crossLink "StageGL/autoPurge"}}{{/crossLink}} | |
* @property _autoPurge | |
* @protected | |
* @type {Integer} | |
* @default null | |
*/ | |
this._autoPurge = undefined; | |
this.autoPurge = autoPurge; //getter/setter handles setting the real value and validating | |
/** | |
* The width in px of the drawing surface saved in memory. | |
* @property _viewportWidth | |
* @protected | |
* @type {Number} | |
* @default 0 | |
*/ | |
this._viewportWidth = 0; | |
/** | |
* The height in px of the drawing surface saved in memory. | |
* @property _viewportHeight | |
* @protected | |
* @type {Number} | |
* @default 0 | |
*/ | |
this._viewportHeight = 0; | |
/** | |
* A 2D projection matrix used to convert WebGL's viewspace into canvas co-ordinates. Regular canvas display | |
* uses Top-Left values of [0,0] where WebGL uses a Center [0,0] Top-Right [1,1] (euclidean) system. | |
* @property _projectionMatrix | |
* @protected | |
* @type {Float32Array} | |
* @default null | |
*/ | |
this._projectionMatrix = null; | |
/** | |
* The current WebGL canvas context. Often shorthanded to just "gl" in many parts of the code. | |
* @property _webGLContext | |
* @protected | |
* @type {WebGLRenderingContext} | |
* @default null | |
*/ | |
this._webGLContext = null; | |
/** | |
* The color to use when the WebGL canvas has been cleared. May appear as a background color. Defaults to grey. | |
* @property _clearColor | |
* @protected | |
* @type {Object} | |
* @default {r: 0.50, g: 0.50, b: 0.50, a: 0.00} | |
*/ | |
this._clearColor = { r: 0.5, g: 0.5, b: 0.5, a: 0.0 }; | |
/** | |
* The maximum number of cards (aka a single sprite) that can be drawn in one draw call. Use getter/setters to | |
* modify otherwise internal buffers may be incorrect sizes. | |
* @property _maxCardsPerBatch | |
* @protected | |
* @type {Number} | |
* @default StageGL.DEFAULT_MAX_BATCH_SIZE (10000) | |
*/ | |
this._maxCardsPerBatch = StageGL.DEFAULT_MAX_BATCH_SIZE; //TODO: write getter/setters for this | |
/** | |
* The shader program used to draw the current batch. | |
* @property _activeShader | |
* @protected | |
* @type {WebGLProgram} | |
* @default null | |
*/ | |
this._activeShader = null; | |
/** | |
* The vertex position data for the current draw call. | |
* @property _vertices | |
* @protected | |
* @type {Float32Array} | |
* @default null | |
*/ | |
this._vertices = null; | |
/** | |
* The WebGL buffer attached to {{#crossLink "StageGL/_vertices:property"}}{{/crossLink}}. | |
* @property _vertexPositionBuffer | |
* @protected | |
* @type {WebGLBuffer} | |
* @default null | |
*/ | |
this._vertexPositionBuffer = null; | |
/** | |
* The vertex U/V data for the current draw call. | |
* @property _uvs | |
* @protected | |
* @type {Float32Array} | |
* @default null | |
*/ | |
this._uvs = null; | |
/** | |
* The WebGL buffer attached to {{#crossLink "StageGL/_uvs:property"}}{{/crossLink}}. | |
* @property _uvPositionBuffer | |
* @protected | |
* @type {WebGLBuffer} | |
* @default null | |
*/ | |
this._uvPositionBuffer = null; | |
/** | |
* The vertex indices data for the current draw call. | |
* @property _indices | |
* @protected | |
* @type {Float32Array} | |
* @default null | |
*/ | |
this._indices = null; | |
/** | |
* The WebGL buffer attached to {{#crossLink "StageGL/_indices:property"}}{{/crossLink}}. | |
* @property _textureIndexBuffer | |
* @protected | |
* @type {WebGLBuffer} | |
* @default null | |
*/ | |
this._textureIndexBuffer = null; | |
/** | |
* The vertices data for the current draw call. | |
* @property _alphas | |
* @protected | |
* @type {Float32Array} | |
* @default null | |
*/ | |
this._alphas = null; | |
/** | |
* The WebGL buffer attached to {{#crossLink "StageGL/_alphas:property"}}{{/crossLink}}. | |
* @property _alphaBuffer | |
* @protected | |
* @type {WebGLBuffer} | |
* @default null | |
*/ | |
this._alphaBuffer = null; | |
/** | |
* An index based lookup of every WebGL Texture currently in use. | |
* @property _drawTexture | |
* @protected | |
* @type {Array} | |
*/ | |
this._textureDictionary = []; | |
/** | |
* A string based lookup hash of which index a texture is stored at in the dictionary. The lookup string is | |
* often the src url. | |
* @property _textureIDs | |
* @protected | |
* @type {Object} | |
*/ | |
this._textureIDs = {}; | |
/** | |
* An array of all the textures currently loaded into the GPU. The index in the array matches the GPU index. | |
* @property _batchTextures | |
* @protected | |
* @type {Array} | |
*/ | |
this._batchTextures = []; | |
/** | |
* An array of all the simple filler textures used to prevent issues with missing textures in a batch. | |
* @property _baseTextures | |
* @protected | |
* @type {Array} | |
*/ | |
this._baseTextures = []; | |
/** | |
* The number of concurrent textures the GPU can handle. This value is dynamically set from WebGL during initialization | |
* via `gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS)`. The WebGL spec states that the lowest guaranteed value is 8, | |
* but it could be higher. Do not set this value higher than the value returned by the GPU. Setting it lower will | |
* probably reduce performance, but may be advisable to reserve slots for custom filter work. | |
* NOTE: Can also act as a length for {{#crossLink "StageGL/_batchTextures:property"}}. | |
* @property _batchTextureCount | |
* @protected | |
* @type {Number} | |
* @default 8 | |
*/ | |
this._batchTextureCount = 8; | |
/** | |
* The location at which the last texture was inserted into a GPU slot in {{#crossLink "StageGL/_batchTextures:property"}}{{/crossLink}}. | |
* Manual control of this variable can yield improvements in performance by intelligently replacing textures | |
* inside a batch to reduce texture re-load. It is impossible to write automated general use code, as it requires | |
* display list look ahead inspection and/or render foreknowledge. | |
* @property _lastTextureInsert | |
* @protected | |
* @type {Number} | |
* @default -1 | |
*/ | |
this._lastTextureInsert = -1; | |
/** | |
* The current batch being drawn, A batch consists of a call to `drawElements` on the GPU. Many of these calls | |
* can occur per draw. | |
* @property _batchId | |
* @protected | |
* @type {Number} | |
* @default 0 | |
*/ | |
this._batchID = 0; | |
/** | |
* The current draw being performed, may contain multiple batches. Comparing to {{#crossLink "StageGL/_batchID:property"}}{{/crossLink}} | |
* can reveal batching efficiency. | |
* @property _drawID | |
* @protected | |
* @type {Number} | |
* @default 0 | |
*/ | |
this._drawID = 0; | |
/** | |
* Used to prevent textures in certain GPU slots from being replaced by an insert. | |
* @property _slotBlackList | |
* @protected | |
* @type {Array} | |
*/ | |
this._slotBlacklist = []; | |
/** | |
* Used to prevent nested draw calls from accidentally overwriting drawing information by tracking depth. | |
* @property _isDrawing | |
* @protected | |
* @type {Number} | |
* @default 0 | |
*/ | |
this._isDrawing = 0; | |
/** | |
* Used to ensure every canvas used as a texture source has a unique ID. | |
* @property _lastTrackedCanvas | |
* @protected | |
* @type {Number} | |
* @default 0 | |
*/ | |
this._lastTrackedCanvas = 0; | |
/** | |
* Controls whether final rendering output of a {{#crossLink "cacheDraw"}}{{/crossLink}} is the canvas or a render | |
* texture. See the {{#crossLink "cache"}}{{/crossLink}} function modifications for full implications and discussion. | |
* @property isCacheControlled | |
* @protected | |
* @type {Boolean} | |
* @default false | |
* @todo LM: is this supposed to be _isCacheControlled since its private? | |
*/ | |
this.isCacheControlled = false; | |
/** | |
* Used to counter-position the object being cached so it aligns with the cache surface. Additionally ensures | |
* that all rendering starts with a top level container. | |
* @property _cacheContainer | |
* @protected | |
* @type {Container} | |
* @default An instance of an EaselJS Container. | |
*/ | |
this._cacheContainer = new createjs.Container(); | |
// and begin | |
this._initializeWebGL(); | |
} | |
var p = createjs.extend(StageGL, createjs.Stage); | |
// static methods: | |
/** | |
* Calculate the U/V co-ordinate based info for sprite frames. Instead of pixel count it uses a 0-1 space. Also includes | |
* the ability to get info back for a specific frame, or only calculate that one frame. | |
* | |
* //generate UV rects for all entries | |
* StageGL.buildUVRects( spriteSheetA ); | |
* //generate all, fetch the first | |
* var firstFrame = StageGL.buildUVRects( spriteSheetB, 0 ); | |
* //generate the rect for just a single frame for performance's sake | |
* var newFrame = StageGL.buildUVRects( dynamicSpriteSheet, newFrameIndex, true ); | |
* | |
* NOTE: This method is mainly for internal use, though it may be useful for advanced uses. | |
* @method buildUVRects | |
* @param {SpriteSheet} spritesheet The spritesheet to find the frames on | |
* @param {int} [target=-1] The index of the frame to return | |
* @param {Boolean} [onlyTarget=false] Whether "target" is the only frame that gets calculated | |
* @static | |
* @return {Object} the target frame if supplied and present or a generic frame {t, l, b, r} | |
*/ | |
StageGL.buildUVRects = function (spritesheet, target, onlyTarget) { | |
if (!spritesheet || !spritesheet._frames) { | |
return null; | |
} | |
if (target === undefined) { | |
target = -1; | |
} | |
if (onlyTarget === undefined) { | |
onlyTarget = false; | |
} | |
var start = target != -1 && onlyTarget ? target : 0; | |
var end = | |
target != -1 && onlyTarget ? target + 1 : spritesheet._frames.length; | |
for (var i = start; i < end; i++) { | |
var f = spritesheet._frames[i]; | |
if (f.uvRect || f.image.width <= 0 || f.image.height <= 0) { | |
continue; | |
} | |
var r = f.rect; | |
f.uvRect = { | |
t: r.y / f.image.height, | |
l: r.x / f.image.width, | |
b: (r.y + r.height) / f.image.height, | |
r: (r.x + r.width) / f.image.width, | |
}; | |
} | |
return ( | |
spritesheet._frames[target != -1 ? target : 0].uvRect || { | |
t: 0, | |
l: 0, | |
b: 1, | |
r: 1, | |
} | |
); | |
}; | |
/** | |
* Test a context to see if it has WebGL enabled on it. | |
* @method isWebGLActive | |
* @param {CanvasContext} ctx The context to test | |
* @static | |
* @return {Boolean} Whether WebGL is enabled | |
*/ | |
StageGL.isWebGLActive = function (ctx) { | |
return ( | |
ctx && | |
ctx instanceof WebGLRenderingContext && | |
typeof WebGLRenderingContext !== "undefined" | |
); | |
}; | |
// static properties: | |
/** | |
* The number of properties defined per vertex (x, y, textureU, textureV, textureIndex, alpha) | |
* @property VERTEX_PROPERTY_COUNT | |
* @static | |
* @final | |
* @type {Number} | |
* @default 6 | |
* @readonly | |
*/ | |
StageGL.VERTEX_PROPERTY_COUNT = 6; | |
/** | |
* The number of triangle indices it takes to form a Card. 3 per triangle, 2 triangles. | |
* @property INDICIES_PER_CARD | |
* @static | |
* @final | |
* @type {Number} | |
* @default 6 | |
* @readonly | |
*/ | |
StageGL.INDICIES_PER_CARD = 6; | |
/** | |
* The default value for the maximum number of cards we want to process in a batch. See | |
* {{#crossLink "StageGL/WEBGL_MAX_INDEX_NUM:property"}}{{/crossLink}} for a hard limit. | |
* @property DEFAULT_MAX_BATCH_SIZE | |
* @static | |
* @final | |
* @type {Number} | |
* @default 10000 | |
* @readonly | |
*/ | |
StageGL.DEFAULT_MAX_BATCH_SIZE = 10000; | |
/** | |
* The maximum size WebGL allows for element index numbers. Uses a 16 bit unsigned integer. It takes 6 indices to | |
* make a unique card. | |
* @property WEBGL_MAX_INDEX_NUM | |
* @static | |
* @final | |
* @type {Number} | |
* @default 65536 | |
* @readonly | |
*/ | |
StageGL.WEBGL_MAX_INDEX_NUM = Math.pow(2, 16); | |
/** | |
* Default U/V rect for dealing with full coverage from an image source. | |
* @property UV_RECT | |
* @static | |
* @final | |
* @type {Object} | |
* @default {t:0, l:0, b:1, r:1} | |
* @readonly | |
*/ | |
StageGL.UV_RECT = { t: 0, l: 0, b: 1, r: 1 }; | |
try { | |
/** | |
* Vertex positions for a card that covers the entire render. Used with render targets primarily. | |
* @property COVER_VERT | |
* @static | |
* @final | |
* @type {Float32Array} | |
* @readonly | |
*/ | |
StageGL.COVER_VERT = new Float32Array([ | |
-1, | |
1, //TL | |
1, | |
1, //TR | |
-1, | |
-1, //BL | |
1, | |
1, //TR | |
1, | |
-1, //BR | |
-1, | |
-1, //BL | |
]); | |
/** | |
* U/V for {{#crossLink "StageGL/COVER_VERT:property"}}{{/crossLink}}. | |
* @property COVER_UV | |
* @static | |
* @final | |
* @type {Float32Array} | |
* @readonly | |
*/ | |
StageGL.COVER_UV = new Float32Array([ | |
0, | |
0, //TL | |
1, | |
0, //TR | |
0, | |
1, //BL | |
1, | |
0, //TR | |
1, | |
1, //BR | |
0, | |
1, //BL | |
]); | |
/** | |
* Flipped U/V for {{#crossLink "StageGL:COVER_VERT:property"}}{{/crossLink}}. | |
* @property COVER_UV_FLIP | |
* @static | |
* @final | |
* @type {Float32Array} | |
* @readonly | |
*/ | |
StageGL.COVER_UV_FLIP = new Float32Array([ | |
0, | |
1, //TL | |
1, | |
1, //TR | |
0, | |
0, //BL | |
1, | |
1, //TR | |
1, | |
0, //BR | |
0, | |
0, //BL | |
]); | |
} catch (e) { | |
/* Breaking in older browsers, but those browsers wont run StageGL so no recovery or warning needed */ | |
} | |
/** | |
* Portion of the shader that contains the "varying" properties required in both vertex and fragment shaders. The | |
* regular shader is designed to render all expected objects. Shader code may contain templates that are replaced | |
* pre-compile. | |
* @property REGULAR_VARYING_HEADER | |
* @static | |
* @final | |
* @type {String} | |
* @readonly | |
*/ | |
StageGL.REGULAR_VARYING_HEADER = | |
"precision mediump float;" + | |
"varying vec2 vTextureCoord;" + | |
"varying lowp float indexPicker;" + | |
"varying lowp float alphaValue;"; | |
/** | |
* Actual full header for the vertex shader. Includes the varying header. The regular shader is designed to render | |
* all expected objects. Shader code may contain templates that are replaced pre-compile. | |
* @property REGULAR_VERTEX_HEADER | |
* @static | |
* @final | |
* @type {String} | |
* @readonly | |
*/ | |
StageGL.REGULAR_VERTEX_HEADER = | |
StageGL.REGULAR_VARYING_HEADER + | |
"attribute vec2 vertexPosition;" + | |
"attribute vec2 uvPosition;" + | |
"attribute lowp float textureIndex;" + | |
"attribute lowp float objectAlpha;" + | |
"uniform mat4 pMatrix;"; | |
/** | |
* Actual full header for the fragment shader. Includes the varying header. The regular shader is designed to render | |
* all expected objects. Shader code may contain templates that are replaced pre-compile. | |
* @property REGULAR_FRAGMENT_HEADER | |
* @static | |
* @final | |
* @type {String} | |
* @readonly | |
*/ | |
StageGL.REGULAR_FRAGMENT_HEADER = | |
StageGL.REGULAR_VARYING_HEADER + "uniform sampler2D uSampler[{{count}}];"; | |
/** | |
* Body of the vertex shader. The regular shader is designed to render all expected objects. Shader code may contain | |
* templates that are replaced pre-compile. | |
* @property REGULAR_VERTEX_BODY | |
* @static | |
* @final | |
* @type {String} | |
* @readonly | |
*/ | |
StageGL.REGULAR_VERTEX_BODY = | |
"void main(void) {" + | |
//DHG TODO: This doesn't work. Must be something wrong with the hand built matrix see js... bypass for now | |
//vertexPosition, round if flag | |
//"gl_Position = pMatrix * vec4(vertexPosition.x, vertexPosition.y, 0.0, 1.0);" + | |
"gl_Position = vec4(" + | |
"(vertexPosition.x * pMatrix[0][0]) + pMatrix[3][0]," + | |
"(vertexPosition.y * pMatrix[1][1]) + pMatrix[3][1]," + | |
"pMatrix[3][2]," + | |
"1.0" + | |
");" + | |
"alphaValue = objectAlpha;" + | |
"indexPicker = textureIndex;" + | |
"vTextureCoord = uvPosition;" + | |
"}"; | |
/** | |
* Body of the fragment shader. The regular shader is designed to render all expected objects. Shader code may | |
* contain templates that are replaced pre-compile. | |
* @property REGULAR_FRAGMENT_BODY | |
* @static | |
* @final | |
* @type {String} | |
* @readonly | |
*/ | |
StageGL.REGULAR_FRAGMENT_BODY = | |
"void main(void) {" + | |
"vec4 color = vec4(1.0, 0.0, 0.0, 1.0);" + | |
"if (indexPicker <= 0.5) {" + | |
"color = texture2D(uSampler[0], vTextureCoord);" + | |
"{{alternates}}" + | |
"}" + | |
"{{fragColor}}" + | |
"}"; | |
StageGL.REGULAR_FRAG_COLOR_NORMAL = | |
"gl_FragColor = vec4(color.rgb, color.a * alphaValue);"; | |
StageGL.REGULAR_FRAG_COLOR_PREMULTIPLY = | |
"if(color.a > 0.0035) {" + // 1/255 = 0.0039, so ignore any value below 1 because it's probably noise | |
"gl_FragColor = vec4(color.rgb/color.a, color.a * alphaValue);" + | |
"} else {" + | |
"gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);" + | |
"}"; | |
//TODO: DHG: a real particle shader | |
/** | |
* @property PARTICLE_VERTEX_BODY | |
* @todo | |
* @final | |
* @static | |
* @type {String} | |
* @readonly | |
*/ | |
StageGL.PARTICLE_VERTEX_BODY = StageGL.REGULAR_VERTEX_BODY; | |
/** | |
* @property PARTICLE_FRAGMENT_BODY | |
* @todo | |
* @final | |
* @static | |
* @type {String} | |
* @readonly | |
*/ | |
StageGL.PARTICLE_FRAGMENT_BODY = StageGL.REGULAR_FRAGMENT_BODY; | |
/** | |
* Portion of the shader that contains the "varying" properties required in both vertex and fragment shaders. The | |
* cover shader is designed to be a simple vertex/uv only texture render that covers the render surface. Shader | |
* code may contain templates that are replaced pre-compile. | |
* @property COVER_VARYING_HEADER | |
* @static | |
* @final | |
* @type {String} | |
* @readonly | |
*/ | |
StageGL.COVER_VARYING_HEADER = | |
"precision mediump float;" + | |
"varying highp vec2 vRenderCoord;" + | |
"varying highp vec2 vTextureCoord;"; | |
/** | |
* Actual full header for the vertex shader. Includes the varying header. The cover shader is designed to be a | |
* simple vertex/uv only texture render that covers the render surface. Shader code may contain templates that are | |
* replaced pre-compile. | |
* @property COVER_VERTEX_HEADER | |
* @static | |
* @final | |
* @type {String} | |
* @readonly | |
*/ | |
StageGL.COVER_VERTEX_HEADER = | |
StageGL.COVER_VARYING_HEADER + | |
"attribute vec2 vertexPosition;" + | |
"attribute vec2 uvPosition;" + | |
"uniform float uUpright;"; | |
/** | |
* Actual full header for the fragment shader. Includes the varying header. The cover shader is designed to be a | |
* simple vertex/uv only texture render that covers the render surface. Shader code may contain templates that are | |
* replaced pre-compile. | |
* @property COVER_FRAGMENT_HEADER | |
* @static | |
* @final | |
* @type {String} | |
* @readonly | |
*/ | |
StageGL.COVER_FRAGMENT_HEADER = | |
StageGL.COVER_VARYING_HEADER + "uniform sampler2D uSampler;"; | |
/** | |
* Body of the vertex shader. The cover shader is designed to be a simple vertex/uv only texture render that covers | |
* the render surface. Shader code may contain templates that are replaced pre-compile. | |
* @property COVER_VERTEX_BODY | |
* @static | |
* @final | |
* @type {String} | |
* @readonly | |
*/ | |
StageGL.COVER_VERTEX_BODY = | |
"void main(void) {" + | |
"gl_Position = vec4(vertexPosition.x, vertexPosition.y, 0.0, 1.0);" + | |
"vRenderCoord = uvPosition;" + | |
"vTextureCoord = vec2(uvPosition.x, abs(uUpright - uvPosition.y));" + | |
"}"; | |
/** | |
* Body of the fragment shader. The cover shader is designed to be a simple vertex/uv only texture render that | |
* covers the render surface. Shader code may contain templates that are replaced pre-compile. | |
* @property COVER_FRAGMENT_BODY | |
* @static | |
* @final | |
* @type {String} | |
* @readonly | |
*/ | |
StageGL.COVER_FRAGMENT_BODY = | |
"void main(void) {" + | |
"vec4 color = texture2D(uSampler, vRenderCoord);" + | |
"gl_FragColor = color;" + | |
"}"; | |
// events: | |
/** | |
* Dispatched each update immediately before the canvas is cleared and the display list is drawn to it. You can call | |
* {{#crossLink "Event/preventDefault"}}{{/crossLink}} on the event to cancel the draw. | |
* @event drawstart | |
*/ | |
/** | |
* Dispatched each update immediately after the display list is drawn to the canvas and the canvas context is restored. | |
* @event drawend | |
*/ | |
// getter / setters: | |
p._get_isWebGL = function () { | |
return !!this._webGLContext; | |
}; | |
p._set_autoPurge = function (value) { | |
value = isNaN(value) ? 1200 : value; | |
if (value != -1) { | |
value = value < 10 ? 10 : value; | |
} | |
this._autoPurge = value; | |
}; | |
p._get_autoPurge = function () { | |
return Number(this._autoPurge); | |
}; | |
try { | |
Object.defineProperties(p, { | |
/** | |
* Indicates whether WebGL is being used for rendering. For example, this would be `false` if WebGL is not | |
* supported in the browser. | |
* @property isWebGL | |
* @type {Boolean} | |
* @readonly | |
*/ | |
isWebGL: { get: p._get_isWebGL }, | |
/** | |
* Specifies whether or not StageGL is automatically purging unused textures. Higher numbers purge less | |
* often. Values below 10 are upgraded to 10, and -1 disables this feature. | |
* @property autoPurge | |
* @protected | |
* @type {Integer} | |
* @default 1000 | |
*/ | |
autoPurge: { get: p._get_autoPurge, set: p._set_autoPurge }, | |
}); | |
} catch (e) {} // TODO: use Log | |
// constructor methods: | |
/** | |
* Create and properly initialize the WebGL instance. | |
* @method _initializeWebGL | |
* @protected | |
* @return {WebGLRenderingContext} | |
*/ | |
p._initializeWebGL = function () { | |
if (this.canvas) { | |
if (!this._webGLContext || this._webGLContext.canvas !== this.canvas) { | |
// A context hasn't been defined yet, | |
// OR the defined context belongs to a different canvas, so reinitialize. | |
// defaults and options | |
var options = { | |
depth: false, // Disable the depth buffer as it isn't used. | |
alpha: this._transparent, // Make the canvas background transparent. | |
stencil: true, | |
antialias: this._antialias, | |
premultipliedAlpha: this._premultiply, // Assume the drawing buffer contains colors with premultiplied alpha. | |
preserveDrawingBuffer: this._preserveBuffer, | |
}; | |
var gl = (this._webGLContext = this._fetchWebGLContext( | |
this.canvas, | |
options | |
)); | |
if (!gl) { | |
return null; | |
} | |
this.updateSimultaneousTextureCount( | |
gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS) | |
); | |
this._maxTextureSlots = gl.getParameter( | |
gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS | |
); | |
this._createBuffers(gl); | |
this._initTextures(gl); | |
gl.disable(gl.DEPTH_TEST); | |
gl.enable(gl.BLEND); | |
gl.blendFuncSeparate( | |
gl.SRC_ALPHA, | |
gl.ONE_MINUS_SRC_ALPHA, | |
gl.ONE, | |
gl.ONE_MINUS_SRC_ALPHA | |
); | |
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, this._premultiply); | |
//gl.pixelStorei(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.NONE); | |
this._webGLContext.clearColor( | |
this._clearColor.r, | |
this._clearColor.g, | |
this._clearColor.b, | |
this._clearColor.a | |
); | |
this.updateViewport( | |
this._viewportWidth || this.canvas.width, | |
this._viewportHeight || this.canvas.height | |
); | |
} | |
} else { | |
this._webGLContext = null; | |
} | |
return this._webGLContext; | |
}; | |
// public methods: | |
/** | |
* Docced in superclass | |
*/ | |
p.update = function (props) { | |
if (!this.canvas) { | |
return; | |
} | |
if (this.tickOnUpdate) { | |
this.tick(props); | |
} | |
this.dispatchEvent("drawstart"); | |
if (this.autoClear) { | |
this.clear(); | |
} | |
if (this._webGLContext) { | |
// Use WebGL. | |
this._batchDraw(this, this._webGLContext); | |
if ( | |
this._autoPurge != -1 && | |
!(this._drawID % ((this._autoPurge / 2) | 0)) | |
) { | |
this.purgeTextures(this._autoPurge); | |
} | |
} else { | |
// Use 2D. | |
var ctx = this.canvas.getContext("2d"); | |
ctx.save(); | |
this.updateContext(ctx); | |
this.draw(ctx, false); | |
ctx.restore(); | |
} | |
this.dispatchEvent("drawend"); | |
}; | |
/** | |
* Docced in superclass | |
*/ | |
p.clear = function () { | |
if (!this.canvas) { | |
return; | |
} | |
if (StageGL.isWebGLActive(this._webGLContext)) { | |
var gl = this._webGLContext; | |
var cc = this._clearColor; | |
var adjust = this._transparent ? cc.a : 1.0; | |
// Use WebGL settings; adjust for pre multiplied alpha appropriate to scenario | |
this._webGLContext.clearColor( | |
cc.r * adjust, | |
cc.g * adjust, | |
cc.b * adjust, | |
adjust | |
); | |
gl.clear(gl.COLOR_BUFFER_BIT); | |
this._webGLContext.clearColor(cc.r, cc.g, cc.b, cc.a); | |
} else { | |
// Use 2D. | |
this.Stage_clear(); | |
} | |
}; | |
/** | |
* Draws the stage into the supplied context if possible. Many WebGL properties only exist on their context. As such | |
* you cannot share contexts among many StageGLs and each context requires a unique StageGL instance. Contexts that | |
* don't match the context managed by this StageGL will be treated as a 2D context. | |
* | |
* NOTE: This method is mainly for internal use, though it may be useful for advanced uses. | |
* @method draw | |
* @param {CanvasRenderingContext2D | WebGLRenderingContext} context The context object to draw into. | |
* @param {Boolean} [ignoreCache=false] Indicates whether the draw operation should ignore any current cache. For | |
* example, used for drawing the cache (to prevent it from simply drawing an existing cache back into itself). | |
* @return {Boolean} If the draw was handled by this function | |
*/ | |
p.draw = function (context, ignoreCache) { | |
if ( | |
context === this._webGLContext && | |
StageGL.isWebGLActive(this._webGLContext) | |
) { | |
var gl = this._webGLContext; | |
this._batchDraw(this, gl, ignoreCache); | |
return true; | |
} else { | |
return this.Stage_draw(context, ignoreCache); | |
} | |
}; | |
/** | |
* Draws the target into the correct context, be it a canvas or Render Texture using WebGL. | |
* | |
* NOTE: This method is mainly for internal use, though it may be useful for advanced uses. | |
* @method cacheDraw | |
* @param {DisplayObject} target The object we're drawing into cache. | |
* For example, used for drawing the cache (to prevent it from simply drawing an existing cache back into itself). | |
* @param {Array} filters The filters we're drawing into cache. | |
* @param {BitmapCache} manager The BitmapCache instance looking after the cache | |
* @return {Boolean} If the draw was handled by this function | |
*/ | |
p.cacheDraw = function (target, filters, manager) { | |
if (StageGL.isWebGLActive(this._webGLContext)) { | |
var gl = this._webGLContext; | |
this._cacheDraw(gl, target, filters, manager); | |
return true; | |
} else { | |
return false; | |
} | |
}; | |
/** | |
* Blocks, or frees a texture "slot" on the GPU. Can be useful if you are overflowing textures. When overflowing | |
* textures they are re-uploaded to the GPU every time they're encountered, this can be expensive with large textures. | |
* By blocking the slot you reduce available slots, potentially increasing draw calls, but mostly you prevent a | |
* texture being re-uploaded if it would have moved slots due to overflow. | |
* | |
* NOTE: This method is mainly for internal use, though it may be useful for advanced uses. | |
* For example, block the slot a background image is stored in so there is less re-loading of that image. | |
* @method protectTextureSlot | |
* @param {Number} id The slot to be affected | |
* @param {Boolean} [lock=false] Whether this slot is the one being locked. | |
*/ | |
p.protectTextureSlot = function (id, lock) { | |
if (id > this._maxTextureSlots || id < 0) { | |
throw "Slot outside of acceptable range"; | |
} | |
this._slotBlacklist[id] = !!lock; | |
}; | |
/** | |
* Render textures can't draw into themselves so any item being used for renderTextures needs two to alternate between. | |
* This function creates, gets, and toggles the render surface between the two. | |
* | |
* NOTE: This method is mainly for internal use, though it may be useful for advanced uses. | |
* @method getTargetRenderTexture | |
* @param {DisplayObject} target The object associated with the render textures, usually a cached object. | |
* @param {Number} w The width to create the texture at. | |
* @param {Number} h The height to create the texture at. | |
* @return {Objet} | |
* @todo fill in return type | |
*/ | |
p.getTargetRenderTexture = function (target, w, h) { | |
var result, | |
toggle = false; | |
var gl = this._webGLContext; | |
if (target.__lastRT !== undefined && target.__lastRT === target.__rtA) { | |
toggle = true; | |
} | |
if (!toggle) { | |
if (target.__rtA === undefined) { | |
target.__rtA = this.getRenderBufferTexture(w, h); | |
} else { | |
if (w != target.__rtA._width || h != target.__rtA._height) { | |
this.resizeTexture(target.__rtA, w, h); | |
} | |
this.setTextureParams(gl); | |
} | |
result = target.__rtA; | |
} else { | |
if (target.__rtB === undefined) { | |
target.__rtB = this.getRenderBufferTexture(w, h); | |
} else { | |
if (w != target.__rtB._width || h != target.__rtB._height) { | |
this.resizeTexture(target.__rtB, w, h); | |
} | |
this.setTextureParams(gl); | |
} | |
result = target.__rtB; | |
} | |
if (!result) { | |
throw "Problems creating render textures, known causes include using too much VRAM by not releasing WebGL texture instances"; | |
} | |
target.__lastRT = result; | |
return result; | |
}; | |
/** | |
* For every image encountered StageGL registers and tracks it automatically. This tracking can cause memory leaks | |
* if not purged. StageGL, by default, automatically purges them. This does have a cost and may unfortunately find | |
* false positives. This function is for manual management of this memory instead of the automatic system controlled | |
* by the {{#crossLink "StageGL/autoPurge:property"}}{{/crossLink}} property. | |
* | |
* This function will recursively remove all textures found on the object, its children, cache, etc. It will uncache | |
* objects and remove any texture it finds REGARDLESS of whether it is currently in use elsewhere. It is up to the | |
* developer to ensure that a texture in use is not removed. | |
* | |
* Textures in use, or to be used again shortly, should not be removed. This is simply for performance reasons. | |
* Removing a texture in use will cause the texture to have to be re-uploaded slowing rendering. | |
* @method releaseTexture | |
* @param {DisplayObject | Texture | Image | Canvas} item An object that used the texture to be discarded. | |
*/ | |
p.releaseTexture = function (item) { | |
var i, l; | |
if (!item) { | |
return; | |
} | |
// this is a container object | |
if (item.children) { | |
for (i = 0, l = item.children.length; i < l; i++) { | |
this.releaseTexture(item.children[i]); | |
} | |
} | |
// this has a cache canvas | |
if (item.cacheCanvas) { | |
item.uncache(); | |
} | |
var foundImage = undefined; | |
if (item._storeID !== undefined) { | |
// this is a texture itself | |
if (item === this._textureDictionary[item._storeID]) { | |
this._killTextureObject(item); | |
item._storeID = undefined; | |
return; | |
} | |
// this is an image or canvas | |
foundImage = item; | |
} else if (item._webGLRenderStyle === 2) { | |
// this is a Bitmap class | |
foundImage = item.image; | |
} else if (item._webGLRenderStyle === 1) { | |
// this is a SpriteSheet, we can't tell which image we used from the list easily so remove them all! | |
for (i = 0, l = item.spriteSheet._images.length; i < l; i++) { | |
this.releaseTexture(item.spriteSheet._images[i]); | |
} | |
return; | |
} | |
// did we find anything | |
if (foundImage === undefined) { | |
if (this.vocalDebug) { | |
console.log("No associated texture found on release"); | |
} | |
return; | |
} | |
// remove it | |
this._killTextureObject(this._textureDictionary[foundImage._storeID]); | |
foundImage._storeID = undefined; | |
}; | |
/** | |
* Similar to {{#crossLink "releaseTexture"}}{{/crossLink}}, but this function differs by searching for textures to | |
* release. It works by assuming that it can purge any texture which was last used more than "count" draw calls ago. | |
* Because this process is unaware of the objects and whether they may be used on your stage, false positives can | |
* occur. It is recommended to manually manage your memory with {{#crossLink "StageGL/releaseTexture"}}{{/crossLink}}, | |
* however, there are many use cases where this is simpler and error-free. This process is also run by default under | |
* the hood to prevent leaks. To disable it see the {{#crossLink "StageGL/autoPurge:property"}}{{/crossLink}} property. | |
* @method purgeTextures | |
* @param {Number} [count=100] How many renders ago the texture was last used | |
*/ | |
p.purgeTextures = function (count) { | |
if (count == undefined) { | |
count = 100; | |
} | |
var dict = this._textureDictionary; | |
var l = dict.length; | |
for (var i = 0; i < l; i++) { | |
var item = dict[i]; | |
if (!item) { | |
continue; | |
} | |
if (item._drawID + count <= this._drawID) { | |
// use draw not batch as draw is more indicative of time | |
this._killTextureObject(item); | |
} | |
} | |
}; | |
/** | |
* Try to set the max textures the system can handle. It should default to the hardware maximum, and lower values | |
* may limit performance. Some devices have been known to mis-report their max textures, or you may need a standard | |
* baseline cross devices for testing. Barring the previous suggestions, there is little need to call this function | |
* as the library will automatically try to find the best value. | |
* | |
* NOTE: This method is mainly for internal use, though it may be useful for advanced uses. | |
* @method updateSimultaneousTextureCount | |
* @param {Number} [count=1] The number of textures intended for simultaneous loading. | |
*/ | |
p.updateSimultaneousTextureCount = function (count) { | |
// TODO: DHG: make sure API works in all instances, may be some issues with buffers etc I haven't foreseen | |
var gl = this._webGLContext; | |
var success = false; | |
if (count < 1 || isNaN(count)) { | |
count = 1; | |
} | |
this._batchTextureCount = count; | |
while (!success) { | |
try { | |
this._activeShader = this._fetchShaderProgram(gl); | |
success = true; | |
} catch (e) { | |
if (this._batchTextureCount == 1) { | |
throw "Cannot compile shader " + e; | |
} | |
this._batchTextureCount -= 4; | |
if (this._batchTextureCount < 1) { | |
this._batchTextureCount = 1; | |
} | |
if (this.vocalDebug) { | |
console.log( | |
"Reducing desired texture count due to errors: " + | |
this._batchTextureCount | |
); | |
} | |
} | |
} | |
}; | |
/** | |
* Update the WebGL viewport. Note that this does <strong>not</strong> update the canvas element's width/height, but | |
* the render surface's instead. This is necessary after manually resizing the canvas element on the DOM to avoid a | |
* up/down scaled render. | |
* @method updateViewport | |
* @param {Integer} width The width of the render surface in pixels. | |
* @param {Integer} height The height of the render surface in pixels. | |
*/ | |
p.updateViewport = function (width, height) { | |
this._viewportWidth = width | 0; | |
this._viewportHeight = height | 0; | |
var gl = this._webGLContext; | |
if (gl) { | |
gl.viewport(0, 0, this._viewportWidth, this._viewportHeight); | |
// WebGL works with a -1,1 space on its screen. It also follows Y-Up | |
// we need to flip the y, scale and then translate the co-ordinates to match this | |
// additionally we offset into they Y so the polygons are inside the camera's "clipping" plane | |
this._projectionMatrix = new Float32Array([ | |
2 / this._viewportWidth, | |
0, | |
0, | |
0, | |
0, | |
-2 / this._viewportHeight, | |
1, | |
0, | |
0, | |
0, | |
1, | |
0, | |
-1, | |
1, | |
0.1, | |
0, | |
]); | |
// create the flipped version for use with render texture flipping | |
// DHG: this would be a slice/clone but some platforms don't offer them for Float32Array | |
this._projectionMatrixFlip = new Float32Array([ | |
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |
]); | |
this._projectionMatrixFlip.set(this._projectionMatrix); | |
this._projectionMatrixFlip[5] *= -1; | |
this._projectionMatrixFlip[13] *= -1; | |
} | |
}; | |
/** | |
* Fetches the shader compiled and set up to work with the provided filter/object. The shader is compiled on first | |
* use and returned on subsequent calls. | |
* @method getFilterShader | |
* @param {Filter|Object} filter The object which will provide the information needed to construct the filter shader. | |
* @return {WebGLProgram} | |
*/ | |
p.getFilterShader = function (filter) { | |
if (!filter) { | |
filter = this; | |
} | |
var gl = this._webGLContext; | |
var targetShader = this._activeShader; | |
if (filter._builtShader) { | |
targetShader = filter._builtShader; | |
if (filter.shaderParamSetup) { | |
gl.useProgram(targetShader); | |
filter.shaderParamSetup(gl, this, targetShader); | |
} | |
} else { | |
try { | |
targetShader = this._fetchShaderProgram( | |
gl, | |
"filter", | |
filter.VTX_SHADER_BODY, | |
filter.FRAG_SHADER_BODY, | |
filter.shaderParamSetup && filter.shaderParamSetup.bind(filter) | |
); | |
filter._builtShader = targetShader; | |
targetShader._name = filter.toString(); | |
} catch (e) { | |
console && console.log("SHADER SWITCH FAILURE", e); | |
} | |
} | |
return targetShader; | |
}; | |
/** | |
* Returns a base texture that has no image or data loaded. Not intended for loading images. It may return `null` | |
* in some error cases, and trying to use a "null" texture can cause renders to fail. | |
* @method getBaseTexture | |
* @param {uint} [w=1] The width of the texture in pixels, defaults to 1 | |
* @param {uint} [h=1] The height of the texture in pixels, defaults to 1 | |
*/ | |
p.getBaseTexture = function (w, h) { | |
var width = Math.ceil(w > 0 ? w : 1) || 1; | |
var height = Math.ceil(h > 0 ? h : 1) || 1; | |
var gl = this._webGLContext; | |
var texture = gl.createTexture(); | |
this.resizeTexture(texture, width, height); | |
this.setTextureParams(gl, false); | |
return texture; | |
}; | |
/** | |
* Resizes a supplied texture element. May generate invalid textures in some error cases such as; when the texture | |
* is too large, when an out of texture memory error occurs, or other error scenarios. Trying to use an invalid texture | |
* can cause renders to hard stop on some devices. Check the WebGL bound texture after running this function. | |
* | |
* NOTE: The supplied texture must have been made with the WebGL "texImage2D" function, all default APIs in StageGL | |
* use this, so this note only matters for library developers and plugins. | |
* | |
* @protected | |
* @method resizeTexture | |
* @param {WebGLTexture} texture The GL Texture to be modified. | |
* @param {uint} [width=1] The width of the texture in pixels, defaults to 1 | |
* @param {uint} [height=1] The height of the texture in pixels, defaults to 1 | |
*/ | |
p.resizeTexture = function (texture, width, height) { | |
var gl = this._webGLContext; | |
gl.bindTexture(gl.TEXTURE_2D, texture); | |
gl.texImage2D( | |
gl.TEXTURE_2D, // target | |
0, // level of detail | |
gl.RGBA, // internal format | |
width, | |
height, | |
0, // width, height, border (only for array/null sourced textures) | |
gl.RGBA, // format (match internal format) | |
gl.UNSIGNED_BYTE, // type of texture(pixel color depth) | |
null // image data, we can do null because we're doing array data | |
); | |
texture.width = width; | |
texture.height = height; | |
}; | |
/** | |
* Returns a base texture (see {{#crossLink "StageGL/getBaseTexture"}}{{/crossLink}}) for details. Also includes an | |
* attached and linked render buffer in `texture._frameBuffer`. RenderTextures can be thought of as an internal | |
* canvas on the GPU that can be drawn to. | |
* @method getRenderBufferTexture | |
* @param {Number} w The width of the texture in pixels. | |
* @param {Number} h The height of the texture in pixels. | |
* @return {Texture} the basic texture instance with a render buffer property. | |
*/ | |
p.getRenderBufferTexture = function (w, h) { | |
var gl = this._webGLContext; | |
// get the texture | |
var renderTexture = this.getBaseTexture(w, h); | |
if (!renderTexture) { | |
return null; | |
} | |
// get the frame buffer | |
var frameBuffer = gl.createFramebuffer(); | |
if (!frameBuffer) { | |
return null; | |
} | |
// set its width and height for spoofing as an image | |
renderTexture.width = w; | |
renderTexture.height = h; | |
// attach frame buffer to texture and provide cross links to look up each other | |
gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer); | |
gl.framebufferTexture2D( | |
gl.FRAMEBUFFER, | |
gl.COLOR_ATTACHMENT0, | |
gl.TEXTURE_2D, | |
renderTexture, | |
0 | |
); | |
frameBuffer._renderTexture = renderTexture; | |
renderTexture._frameBuffer = frameBuffer; | |
// these keep track of themselves simply to reduce complexity of some lookup code | |
renderTexture._storeID = this._textureDictionary.length; | |
this._textureDictionary[renderTexture._storeID] = renderTexture; | |
gl.bindFramebuffer(gl.FRAMEBUFFER, null); | |
return renderTexture; | |
}; | |
/** | |
* Common utility function used to apply the correct texture processing parameters for the bound texture. | |
* @method setTextureParams | |
* @param {WebGLRenderingContext} gl The canvas WebGL context object to draw into. | |
* @param {Boolean} [isPOT=false] Marks whether the texture is "Power of Two", this may allow better quality AA. | |
*/ | |
p.setTextureParams = function (gl, isPOT) { | |
if (isPOT && this._antialias) { | |
//non POT linear works in some devices, but performance is NOT good, investigate | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); | |
} else { | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); | |
} | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); | |
}; | |
/** | |
* Changes the webGL clear, aka "background" color to the provided value. A transparent clear is recommended, as | |
* non-transparent colours may create undesired boxes around some visuals. | |
* | |
* The clear color will also be used for filters and other "render textures". The stage background will ignore the | |
* transparency value and display a solid color normally. For the stage to recognize and use transparency it must be | |
* created with the transparent flag set to `true` (see {{#crossLink "StageGL/constructor"}}{{/crossLink}})). | |
* | |
* Using "transparent white" to demonstrate, the valid data formats are as follows: | |
* <ul> | |
* <li>"#FFF"</li> | |
* <li>"#FFFFFF"</li> | |
* <li>"#FFFFFF00"</li> | |
* <li>"rgba(255,255,255,0.0)"</li> | |
* </ul> | |
* @method setClearColor | |
* @param {String|int} [color=0x00000000] The new color to use as the background | |
*/ | |
p.setClearColor = function (color) { | |
var r, g, b, a, output; | |
if (typeof color == "string") { | |
if (color.indexOf("#") == 0) { | |
if (color.length == 4) { | |
color = | |
"#" + | |
color.charAt(1) + | |
color.charAt(1) + | |
color.charAt(2) + | |
color.charAt(2) + | |
color.charAt(3) + | |
color.charAt(3); | |
} | |
r = Number("0x" + color.slice(1, 3)) / 255; | |
g = Number("0x" + color.slice(3, 5)) / 255; | |
b = Number("0x" + color.slice(5, 7)) / 255; | |
a = Number("0x" + color.slice(7, 9)) / 255; | |
} else if (color.indexOf("rgba(") == 0) { | |
output = color.slice(5, -1).split(","); | |
r = Number(output[0]) / 255; | |
g = Number(output[1]) / 255; | |
b = Number(output[2]) / 255; | |
a = Number(output[3]); | |
} | |
} else { | |
// >>> is an unsigned shift which is what we want as 0x80000000 and up are negative values | |
r = ((color & 0xff000000) >>> 24) / 255; | |
g = ((color & 0x00ff0000) >>> 16) / 255; | |
b = ((color & 0x0000ff00) >>> 8) / 255; | |
a = (color & 0x000000ff) / 255; | |
} | |
this._clearColor.r = r || 0; | |
this._clearColor.g = g || 0; | |
this._clearColor.b = b || 0; | |
this._clearColor.a = a || 0; | |
if (!this._webGLContext) { | |
return; | |
} | |
this._webGLContext.clearColor( | |
this._clearColor.r, | |
this._clearColor.g, | |
this._clearColor.b, | |
this._clearColor.a | |
); | |
}; | |
/** | |
* docced in subclass | |
*/ | |
p.toString = function () { | |
return "[StageGL (name=" + this.name + ")]"; | |
}; | |
// private methods: | |
/** | |
* Sets up and returns the WebGL context for the canvas. May return undefined in error scenarios. These can include | |
* situations where the canvas element already has a context, 2D or GL. | |
* @param {Canvas} canvas The DOM canvas element to attach to | |
* @param {Object} options The options to be handed into the WebGL object, see WebGL spec | |
* @method _fetchWebGLContext | |
* @protected | |
* @return {WebGLRenderingContext} The WebGL context, may return undefined in error scenarios | |
*/ | |
p._fetchWebGLContext = function (canvas, options) { | |
var gl; | |
try { | |
gl = | |
canvas.getContext("webgl", options) || | |
canvas.getContext("experimental-webgl", options); | |
} catch (e) { | |
// don't do anything in catch, null check will handle it | |
} | |
if (!gl) { | |
var msg = "Could not initialize WebGL"; | |
console.error ? console.error(msg) : console.log(msg); | |
} else { | |
gl.viewportWidth = canvas.width; | |
gl.viewportHeight = canvas.height; | |
} | |
return gl; | |
}; | |
/** | |
* Create the completed Shader Program from the vertex and fragment shaders. Allows building of custom shaders for | |
* filters. Once compiled, shaders are saved so. If the Shader code requires dynamic alterations re-run this function | |
* to generate a new shader. | |
* @method _fetchShaderProgram | |
* @param {WebGLRenderingContext} gl The canvas WebGL context object to draw into. | |
* @param {String} [shaderName="regular"] Working values: "regular", "override", and "filter". Which type of shader to build. | |
* Filter and override both accept the custom params. Regular and override have all features. Filter is a special case reduced feature shader meant to be over-ridden. | |
* @param {String} [customVTX] Extra vertex shader information to replace a regular draw, see | |
* {{#crossLink "StageGL/COVER_VERTEX_BODY"}}{{/crossLink}} for default and {{#crossLink "Filter"}}{{/crossLink}} for examples. | |
* @param {String} [customFRAG] Extra fragment shader information to replace a regular draw, see | |
* {{#crossLink "StageGL/COVER_FRAGMENT_BODY"}}{{/crossLink}} for default and {{#crossLink "Filter"}}{{/crossLink}} for examples. | |
* @param {Function} [shaderParamSetup] Function to run so custom shader parameters can get applied for the render. | |
* @protected | |
* @return {WebGLProgram} The compiled and linked shader | |
*/ | |
p._fetchShaderProgram = function ( | |
gl, | |
shaderName, | |
customVTX, | |
customFRAG, | |
shaderParamSetup | |
) { | |
gl.useProgram(null); // safety to avoid collisions | |
// build the correct shader string out of the right headers and bodies | |
var targetFrag, targetVtx; | |
switch (shaderName) { | |
case "filter": | |
targetVtx = | |
StageGL.COVER_VERTEX_HEADER + | |
(customVTX || StageGL.COVER_VERTEX_BODY); | |
targetFrag = | |
StageGL.COVER_FRAGMENT_HEADER + | |
(customFRAG || StageGL.COVER_FRAGMENT_BODY); | |
break; | |
case "particle": //TODO | |
targetVtx = | |
StageGL.REGULAR_VERTEX_HEADER + StageGL.PARTICLE_VERTEX_BODY; | |
targetFrag = | |
StageGL.REGULAR_FRAGMENT_HEADER + StageGL.PARTICLE_FRAGMENT_BODY; | |
break; | |
case "override": | |
targetVtx = | |
StageGL.REGULAR_VERTEX_HEADER + | |
(customVTX || StageGL.REGULAR_VERTEX_BODY); | |
targetFrag = | |
StageGL.REGULAR_FRAGMENT_HEADER + | |
(customFRAG || StageGL.REGULAR_FRAGMENT_BODY); | |
break; | |
case "regular": | |
default: | |
targetVtx = StageGL.REGULAR_VERTEX_HEADER + StageGL.REGULAR_VERTEX_BODY; | |
targetFrag = | |
StageGL.REGULAR_FRAGMENT_HEADER + StageGL.REGULAR_FRAGMENT_BODY; | |
break; | |
} | |
// create the separate vars | |
var vertexShader = this._createShader(gl, gl.VERTEX_SHADER, targetVtx); | |
var fragmentShader = this._createShader(gl, gl.FRAGMENT_SHADER, targetFrag); | |
// link them together | |
var shaderProgram = gl.createProgram(); | |
gl.attachShader(shaderProgram, vertexShader); | |
gl.attachShader(shaderProgram, fragmentShader); | |
gl.linkProgram(shaderProgram); | |
shaderProgram._type = shaderName; | |
// check compile status | |
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { | |
gl.useProgram(this._activeShader); | |
throw gl.getProgramInfoLog(shaderProgram); | |
} | |
// set up the parameters on the shader | |
gl.useProgram(shaderProgram); | |
switch (shaderName) { | |
case "filter": | |
// get the places in memory the shader is stored so we can feed information into them | |
// then save it off on the shader because it's so tied to the shader itself | |
shaderProgram.vertexPositionAttribute = gl.getAttribLocation( | |
shaderProgram, | |
"vertexPosition" | |
); | |
gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute); | |
shaderProgram.uvPositionAttribute = gl.getAttribLocation( | |
shaderProgram, | |
"uvPosition" | |
); | |
gl.enableVertexAttribArray(shaderProgram.uvPositionAttribute); | |
shaderProgram.samplerUniform = gl.getUniformLocation( | |
shaderProgram, | |
"uSampler" | |
); | |
gl.uniform1i(shaderProgram.samplerUniform, 0); | |
shaderProgram.uprightUniform = gl.getUniformLocation( | |
shaderProgram, | |
"uUpright" | |
); | |
gl.uniform1f(shaderProgram.uprightUniform, 0); | |
// if there's some custom attributes be sure to hook them up | |
if (shaderParamSetup) { | |
shaderParamSetup(gl, this, shaderProgram); | |
} | |
break; | |
case "override": | |
case "particle": | |
case "regular": | |
default: | |
// get the places in memory the shader is stored so we can feed information into them | |
// then save it off on the shader because it's so tied to the shader itself | |
shaderProgram.vertexPositionAttribute = gl.getAttribLocation( | |
shaderProgram, | |
"vertexPosition" | |
); | |
gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute); | |
shaderProgram.uvPositionAttribute = gl.getAttribLocation( | |
shaderProgram, | |
"uvPosition" | |
); | |
gl.enableVertexAttribArray(shaderProgram.uvPositionAttribute); | |
shaderProgram.textureIndexAttribute = gl.getAttribLocation( | |
shaderProgram, | |
"textureIndex" | |
); | |
gl.enableVertexAttribArray(shaderProgram.textureIndexAttribute); | |
shaderProgram.alphaAttribute = gl.getAttribLocation( | |
shaderProgram, | |
"objectAlpha" | |
); | |
gl.enableVertexAttribArray(shaderProgram.alphaAttribute); | |
var samplers = []; | |
for (var i = 0; i < this._batchTextureCount; i++) { | |
samplers[i] = i; | |
} | |
shaderProgram.samplerData = samplers; | |
shaderProgram.samplerUniform = gl.getUniformLocation( | |
shaderProgram, | |
"uSampler" | |
); | |
gl.uniform1iv(shaderProgram.samplerUniform, samplers); | |
shaderProgram.pMatrixUniform = gl.getUniformLocation( | |
shaderProgram, | |
"pMatrix" | |
); | |
break; | |
} | |
gl.useProgram(this._activeShader); | |
return shaderProgram; | |
}; | |
/** | |
* Creates a shader from the specified string replacing templates. Template items are defined via `{{` `key` `}}``. | |
* @method _createShader | |
* @param {WebGLRenderingContext} gl The canvas WebGL context object to draw into. | |
* @param {Number} type The type of shader to create. gl.VERTEX_SHADER | gl.FRAGMENT_SHADER | |
* @param {String} str The definition for the shader. | |
* @return {WebGLShader} | |
* @protected | |
*/ | |
p._createShader = function (gl, type, str) { | |
// inject the static number | |
str = str.replace(/{{count}}/g, this._batchTextureCount); | |
// resolve issue with no dynamic samplers by creating correct samplers in if else chain | |
// TODO: WebGL 2.0 does not need this support | |
var insert = ""; | |
for (var i = 1; i < this._batchTextureCount; i++) { | |
insert += | |
"} else if (indexPicker <= " + | |
i + | |
".5) { color = texture2D(uSampler[" + | |
i + | |
"], vTextureCoord);"; | |
} | |
str = str.replace(/{{alternates}}/g, insert); | |
str = str.replace( | |
/{{fragColor}}/g, | |
this._premultiply | |
? StageGL.REGULAR_FRAG_COLOR_PREMULTIPLY | |
: StageGL.REGULAR_FRAG_COLOR_NORMAL | |
); | |
// actually compile the shader | |
var shader = gl.createShader(type); | |
gl.shaderSource(shader, str); | |
gl.compileShader(shader); | |
// check compile status | |
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { | |
throw gl.getShaderInfoLog(shader); | |
} | |
return shader; | |
}; | |
/** | |
* Sets up the necessary vertex property buffers, including position and U/V. | |
* @method _createBuffers | |
* @param {WebGLRenderingContext} gl | |
* @protected | |
*/ | |
p._createBuffers = function (gl) { | |
var groupCount = this._maxCardsPerBatch * StageGL.INDICIES_PER_CARD; | |
var groupSize, i, l; | |
// INFO: | |
// all buffers are created using this pattern | |
// create a WebGL buffer | |
// attach it to context | |
// figure out how many parts it has to an entry | |
// fill it with empty data to reserve the memory | |
// attach the empty data to the GPU | |
// track the sizes on the buffer object | |
// INFO: | |
// a single buffer may be optimal in some situations and would be approached like this, | |
// currently not implemented due to lack of need and potential complications with drawCover | |
// var vertexBuffer = this._vertexBuffer = gl.createBuffer(); | |
// gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); | |
// groupSize = 2 + 2 + 1 + 1; //x/y, u/v, index, alpha | |
// var vertexData = this._vertexData = new Float32Array(groupCount * groupSize); | |
// for (i=0; i<vertexData.length; i+=groupSize) { | |
// vertexData[i+0] = vertexData[i+1] = 0; | |
// vertexData[i+2] = vertexData[i+3] = 0.5; | |
// vertexData[i+4] = 0; | |
// vertexData[i+5] = 1; | |
// } | |
// vertexBuffer.itemSize = groupSize; | |
// vertexBuffer.numItems = groupCount; | |
// TODO bechmark and test using unified buffer | |
// the actual position information | |
var vertexPositionBuffer = (this._vertexPositionBuffer = gl.createBuffer()); | |
gl.bindBuffer(gl.ARRAY_BUFFER, vertexPositionBuffer); | |
groupSize = 2; | |
var vertices = (this._vertices = new Float32Array(groupCount * groupSize)); | |
for (i = 0, l = vertices.length; i < l; i += groupSize) { | |
vertices[i] = vertices[i + 1] = 0; | |
} | |
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.DYNAMIC_DRAW); | |
vertexPositionBuffer.itemSize = groupSize; | |
vertexPositionBuffer.numItems = groupCount; | |
// where on the texture it gets its information | |
var uvPositionBuffer = (this._uvPositionBuffer = gl.createBuffer()); | |
gl.bindBuffer(gl.ARRAY_BUFFER, uvPositionBuffer); | |
groupSize = 2; | |
var uvs = (this._uvs = new Float32Array(groupCount * groupSize)); | |
for (i = 0, l = uvs.length; i < l; i += groupSize) { | |
uvs[i] = uvs[i + 1] = 0; | |
} | |
gl.bufferData(gl.ARRAY_BUFFER, uvs, gl.DYNAMIC_DRAW); | |
uvPositionBuffer.itemSize = groupSize; | |
uvPositionBuffer.numItems = groupCount; | |
// what texture it should use | |
var textureIndexBuffer = (this._textureIndexBuffer = gl.createBuffer()); | |
gl.bindBuffer(gl.ARRAY_BUFFER, textureIndexBuffer); | |
groupSize = 1; | |
var indices = (this._indices = new Float32Array(groupCount * groupSize)); | |
for (i = 0, l = indices.length; i < l; i++) { | |
indices[i] = 0; | |
} | |
gl.bufferData(gl.ARRAY_BUFFER, indices, gl.DYNAMIC_DRAW); | |
textureIndexBuffer.itemSize = groupSize; | |
textureIndexBuffer.numItems = groupCount; | |
// what alpha it should have | |
var alphaBuffer = (this._alphaBuffer = gl.createBuffer()); | |
gl.bindBuffer(gl.ARRAY_BUFFER, alphaBuffer); | |
groupSize = 1; | |
var alphas = (this._alphas = new Float32Array(groupCount * groupSize)); | |
for (i = 0, l = alphas.length; i < l; i++) { | |
alphas[i] = 1; | |
} | |
gl.bufferData(gl.ARRAY_BUFFER, alphas, gl.DYNAMIC_DRAW); | |
alphaBuffer.itemSize = groupSize; | |
alphaBuffer.numItems = groupCount; | |
}; | |
/** | |
* Do all the setup steps for textures in the system. | |
* @method _initTextures | |
* @protected | |
*/ | |
p._initTextures = function () { | |
//TODO: DHG: add a cleanup routine in here in case this happens mid stream | |
// reset counters | |
this._lastTextureInsert = -1; | |
// clear containers | |
this._textureDictionary = []; | |
this._textureIDs = {}; | |
this._baseTextures = []; | |
this._batchTextures = []; | |
// fill in blanks as it helps the renderer be stable while textures are loading and reduces need for safety code | |
for (var i = 0; i < this._batchTextureCount; i++) { | |
var tex = this.getBaseTexture(); | |
this._baseTextures[i] = this._batchTextures[i] = tex; | |
if (!tex) { | |
throw "Problems creating basic textures, known causes include using too much VRAM by not releasing WebGL texture instances"; | |
} | |
} | |
}; | |
/** | |
* Load a specific texture, accounting for potential delay, as it might not be preloaded. | |
* @method _loadTextureImage | |
* @param {WebGLRenderingContext} gl | |
* @param {Image} image Actual image to be loaded | |
* @return {WebGLTexture} The resulting Texture object | |
* @protected | |
*/ | |
p._loadTextureImage = function (gl, image) { | |
var src = image.src; | |
if (!src) { | |
// one time canvas property setup | |
image._isCanvas = true; | |
src = image.src = "canvas_" + this._lastTrackedCanvas++; | |
} | |
// put the texture into our storage system | |
var storeID = this._textureIDs[src]; | |
if (storeID === undefined) { | |
storeID = this._textureIDs[src] = this._textureDictionary.length; | |
} | |
if (this._textureDictionary[storeID] === undefined) { | |
this._textureDictionary[storeID] = this.getBaseTexture(); | |
} | |
var texture = this._textureDictionary[storeID]; | |
if (texture) { | |
// get texture params all set up | |
texture._batchID = this._batchID; | |
texture._storeID = storeID; | |
texture._imageData = image; | |
this._insertTextureInBatch(gl, texture); | |
// get the data into the texture or wait for it to load | |
image._storeID = storeID; | |
if (image.complete || image.naturalWidth || image._isCanvas) { | |
// is it already loaded | |
this._updateTextureImageData(gl, image); | |
} else { | |
image.addEventListener( | |
"load", | |
this._updateTextureImageData.bind(this, gl, image) | |
); | |
} | |
} else { | |
// we really really should have a texture, try to recover the error by using a saved empty texture so we don't crash | |
var msg = | |
"Problem creating desired texture, known causes include using too much VRAM by not releasing WebGL texture instances"; | |
(console.error && console.error(msg)) || console.log(msg); | |
texture = this._baseTextures[0]; | |
texture._batchID = this._batchID; | |
texture._storeID = -1; | |
texture._imageData = texture; | |
this._insertTextureInBatch(gl, texture); | |
} | |
return texture; | |
}; | |
/** | |
* Necessary to upload the actual image data to the GPU. Without this the texture will be blank. Called automatically | |
* in most cases due to loading and caching APIs. Flagging an image source with `_invalid = true` will trigger this | |
* next time the image is rendered. | |
* @param {WebGLRenderingContext} gl | |
* @param {Image | Canvas} image The image data to be uploaded | |
* @protected | |
*/ | |
p._updateTextureImageData = function (gl, image) { | |
// the bitwise & is intentional, cheap exponent 2 check | |
var isNPOT = | |
image.width & (image.width - 1) || image.height & (image.height - 1); | |
var texture = this._textureDictionary[image._storeID]; | |
gl.activeTexture(gl.TEXTURE0 + texture._activeIndex); | |
gl.bindTexture(gl.TEXTURE_2D, texture); | |
texture.isPOT = !isNPOT; | |
this.setTextureParams(gl, texture.isPOT); | |
try { | |
gl.texImage2D( | |
gl.TEXTURE_2D, | |
0, | |
gl.RGBA, | |
gl.RGBA, | |
gl.UNSIGNED_BYTE, | |
image | |
); | |
} catch (e) { | |
var errString = | |
"\nAn error has occurred. This is most likely due to security restrictions on WebGL images with local or cross-domain origins"; | |
if (console.error) { | |
//TODO: LM: I recommend putting this into a log function internally, since you do it so often, and each is implemented differently. | |
console.error(errString); | |
console.error(e); | |
} else if (console) { | |
console.log(errString); | |
console.log(e); | |
} | |
} | |
image._invalid = false; | |
texture._w = image.width; | |
texture._h = image.height; | |
if (this.vocalDebug) { | |
if (isNPOT) { | |
console.warn("NPOT(Non Power of Two) Texture: " + image.src); | |
} | |
if ( | |
image.width > gl.MAX_TEXTURE_SIZE || | |
image.height > gl.MAX_TEXTURE_SIZE | |
) { | |
console && | |
console.error( | |
"Oversized Texture: " + | |
image.width + | |
"x" + | |
image.height + | |
" vs " + | |
gl.MAX_TEXTURE_SIZE + | |
"max" | |
); | |
} | |
} | |
}; | |
/** | |
* Adds the texture to a spot in the current batch, forcing a draw if no spots are free. | |
* @method _insertTextureInBatch | |
* @param {WebGLRenderingContext} gl The canvas WebGL context object to draw into. | |
* @param {WebGLTexture} texture The texture to be inserted. | |
* @protected | |
*/ | |
p._insertTextureInBatch = function (gl, texture) { | |
// if it wasn't used last batch | |
if (this._batchTextures[texture._activeIndex] !== texture) { | |
// we've got to find it a a spot. | |
var found = -1; | |
var start = (this._lastTextureInsert + 1) % this._batchTextureCount; | |
var look = start; | |
do { | |
if ( | |
this._batchTextures[look]._batchID != this._batchID && | |
!this._slotBlacklist[look] | |
) { | |
found = look; | |
break; | |
} | |
look = (look + 1) % this._batchTextureCount; | |
} while (look !== start); | |
// we couldn't find anywhere for it go, meaning we're maxed out | |
if (found === -1) { | |
this.batchReason = "textureOverflow"; | |
this._drawBuffers(gl); // <------------------------------------------------------------------------ | |
this.batchCardCount = 0; | |
found = start; | |
} | |
// lets put it into that spot | |
this._batchTextures[found] = texture; | |
texture._activeIndex = found; | |
var image = texture._imageData; | |
if (image && image._invalid && texture._drawID !== undefined) { | |
this._updateTextureImageData(gl, image); | |
} else { | |
gl.activeTexture(gl.TEXTURE0 + found); | |
gl.bindTexture(gl.TEXTURE_2D, texture); | |
this.setTextureParams(gl); | |
} | |
this._lastTextureInsert = found; | |
} else { | |
var image = texture._imageData; | |
if (texture._storeID != undefined && image && image._invalid) { | |
this._updateTextureImageData(gl, image); | |
} | |
} | |
texture._drawID = this._drawID; | |
texture._batchID = this._batchID; | |
}; | |
/** | |
* Remove and clean the texture, expects a texture and is inflexible. Mostly for internal use, recommended to call | |
* {{#crossLink "StageGL/releaseTexture"}}{{/crossLink}} instead as it will call this with the correct texture object(s). | |
* Note: Testing shows this may not happen immediately, have to wait frames for WebGL to have actually adjust memory. | |
* @method _killTextureObject | |
* @param {Texture} tex The texture to be cleaned out | |
* @protected | |
*/ | |
p._killTextureObject = function (tex) { | |
if (!tex) { | |
return; | |
} | |
var gl = this._webGLContext; | |
// remove linkage | |
if (tex._storeID !== undefined && tex._storeID >= 0) { | |
this._textureDictionary[tex._storeID] = undefined; | |
for (var n in this._textureIDs) { | |
if (this._textureIDs[n] == tex._storeID) { | |
delete this._textureIDs[n]; | |
} | |
} | |
if (tex._imageData) { | |
tex._imageData._storeID = undefined; | |
} | |
tex._imageData = tex._storeID = undefined; | |
} | |
// make sure to drop it out of an active slot | |
if ( | |
tex._activeIndex !== undefined && | |
this._batchTextures[tex._activeIndex] === tex | |
) { | |
this._batchTextures[tex._activeIndex] = | |
this._baseTextures[tex._activeIndex]; | |
} | |
// remove buffers if present | |
try { | |
if (tex._frameBuffer) { | |
gl.deleteFramebuffer(tex._frameBuffer); | |
} | |
tex._frameBuffer = undefined; | |
} catch (e) { | |
/* suppress delete errors because it's already gone or didn't need deleting probably */ | |
if (this.vocalDebug) { | |
console.log(e); | |
} | |
} | |
// remove entry | |
try { | |
gl.deleteTexture(tex); | |
} catch (e) { | |
/* suppress delete errors because it's already gone or didn't need deleting probably */ | |
if (this.vocalDebug) { | |
console.log(e); | |
} | |
} | |
}; | |
/** | |
* Store or restore current batch textures into a backup array | |
* @method _backupBatchTextures | |
* @param {Boolean} restore Perform a restore instead of a store. | |
* @param {Array} [target=this._backupTextures] Where to perform the backup, defaults to internal backup. | |
* @protected | |
*/ | |
p._backupBatchTextures = function (restore, target) { | |
var gl = this._webGLContext; | |
if (!this._backupTextures) { | |
this._backupTextures = []; | |
} | |
if (target === undefined) { | |
target = this._backupTextures; | |
} | |
for (var i = 0; i < this._batchTextureCount; i++) { | |
gl.activeTexture(gl.TEXTURE0 + i); | |
if (restore) { | |
this._batchTextures[i] = target[i]; | |
} else { | |
target[i] = this._batchTextures[i]; | |
this._batchTextures[i] = this._baseTextures[i]; | |
} | |
gl.bindTexture(gl.TEXTURE_2D, this._batchTextures[i]); | |
this.setTextureParams(gl, this._batchTextures[i].isPOT); | |
} | |
if (restore && target === this._backupTextures) { | |
this._backupTextures = []; | |
} | |
}; | |
/** | |
* Begin the drawing process for a regular render. | |
* @method _batchDraw | |
* @param {WebGLRenderingContext} gl The canvas WebGL context object to draw into. | |
* @param {Stage || Container} sceneGraph {{#crossLink "Container"}}{{/crossLink}} object with all that needs to rendered, preferably a Stage. | |
* @param {Boolean} ignoreCache | |
* @protected | |
*/ | |
p._batchDraw = function (sceneGraph, gl, ignoreCache) { | |
if (this._isDrawing > 0) { | |
this._drawBuffers(gl); | |
} | |
this._isDrawing++; | |
this._drawID++; | |
this.batchCardCount = 0; | |
this.depth = 0; | |
this._appendToBatchGroup( | |
sceneGraph, | |
gl, | |
new createjs.Matrix2D(), | |
this.alpha, | |
ignoreCache | |
); | |
this.batchReason = "drawFinish"; | |
this._drawBuffers(gl); // <-------------------------------------------------------- | |
this._isDrawing--; | |
}; | |
/** | |
* Perform the drawing process to fill a specific cache texture, including applying filters. | |
* @method _cacheDraw | |
* @param {DisplayObject} target The object we're drawing into the cache. For example, used for drawing the cache | |
* (to prevent it from simply drawing an existing cache back into itself). | |
* @param {Array} filters The filters we're drawing into cache. | |
* @param {BitmapCache} manager The BitmapCache instance looking after the cache | |
* @protected | |
*/ | |
p._cacheDraw = function (gl, target, filters, manager) { | |
/* | |
Implicitly there are 4 modes to this function: filtered-sameContext, filtered-uniqueContext, sameContext, uniqueContext. | |
Each situation must be handled slightly differently as 'sameContext' or 'uniqueContext' define how the output works, | |
one drawing directly into the main context and the other drawing into a stored renderTexture respectively. | |
When the draw is a 'filtered' draw, the filters are applied sequentially and will draw into saved textures repeatedly. | |
Once the final filter is done the final output is treated depending upon whether it is a same or unique context. | |
The internal complexity comes from reducing over-draw, shared code, and issues like textures needing to be flipped | |
sometimes when written to render textures. | |
*/ | |
var renderTexture; | |
var shaderBackup = this._activeShader; | |
var blackListBackup = this._slotBlacklist; | |
var lastTextureSlot = this._maxTextureSlots - 1; | |
var wBackup = this._viewportWidth, | |
hBackup = this._viewportHeight; | |
// protect the last slot so that we have somewhere to bind the renderTextures so it doesn't get upset | |
this.protectTextureSlot(lastTextureSlot, true); | |
// create offset container for drawing item | |
var mtx = target.getMatrix(); | |
mtx = mtx.clone(); | |
mtx.scale(1 / manager.scale, 1 / manager.scale); | |
mtx = mtx.invert(); | |
mtx.translate( | |
(-manager.offX / manager.scale) * target.scaleX, | |
(-manager.offY / manager.scale) * target.scaleY | |
); | |
var container = this._cacheContainer; | |
container.children = [target]; | |
container.transformMatrix = mtx; | |
this._backupBatchTextures(false); | |
if (filters && filters.length) { | |
this._drawFilters(target, filters, manager); | |
} else { | |
// is this for another stage or mine? | |
if (this.isCacheControlled) { | |
// draw item to canvas I -> C | |
gl.clear(gl.COLOR_BUFFER_BIT); | |
this._batchDraw(container, gl, true); | |
} else { | |
gl.activeTexture(gl.TEXTURE0 + lastTextureSlot); | |
target.cacheCanvas = this.getTargetRenderTexture( | |
target, | |
manager._drawWidth, | |
manager._drawHeight | |
); | |
renderTexture = target.cacheCanvas; | |
// draw item to render texture I -> T | |
gl.bindFramebuffer(gl.FRAMEBUFFER, renderTexture._frameBuffer); | |
this.updateViewport(manager._drawWidth, manager._drawHeight); | |
this._projectionMatrix = this._projectionMatrixFlip; | |
gl.clear(gl.COLOR_BUFFER_BIT); | |
this._batchDraw(container, gl, true); | |
gl.bindFramebuffer(gl.FRAMEBUFFER, null); | |
this.updateViewport(wBackup, hBackup); | |
} | |
} | |
this._backupBatchTextures(true); | |
this.protectTextureSlot(lastTextureSlot, false); | |
this._activeShader = shaderBackup; | |
this._slotBlacklist = blackListBackup; | |
}; | |
/** | |
* Sub portion of _cacheDraw, split off for readability. Do not call independently. | |
* @method _drawFilters | |
* @param {DisplayObject} target The object we're drawing with a filter. | |
* @param {Array} filters The filters we're drawing into cache. | |
* @param {BitmapCache} manager The BitmapCache instance looking after the cache | |
*/ | |
p._drawFilters = function (target, filters, manager) { | |
var gl = this._webGLContext; | |
var renderTexture; | |
var lastTextureSlot = this._maxTextureSlots - 1; | |
var wBackup = this._viewportWidth, | |
hBackup = this._viewportHeight; | |
var container = this._cacheContainer; | |
var filterCount = filters.length; | |
// we don't know which texture slot we're dealing with previously and we need one out of the way | |
// once we're using that slot activate it so when we make and bind our RenderTexture it's safe there | |
gl.activeTexture(gl.TEXTURE0 + lastTextureSlot); | |
renderTexture = this.getTargetRenderTexture( | |
target, | |
manager._drawWidth, | |
manager._drawHeight | |
); | |
// draw item to render texture I -> T | |
gl.bindFramebuffer(gl.FRAMEBUFFER, renderTexture._frameBuffer); | |
this.updateViewport(manager._drawWidth, manager._drawHeight); | |
gl.clear(gl.COLOR_BUFFER_BIT); | |
this._batchDraw(container, gl, true); | |
// bind the result texture to slot 0 as all filters and cover draws assume original content is in slot 0 | |
gl.activeTexture(gl.TEXTURE0); | |
gl.bindTexture(gl.TEXTURE_2D, renderTexture); | |
this.setTextureParams(gl); | |
var flipY = false; | |
var i = 0, | |
filter = filters[i]; | |
do { | |
// this is safe because we wouldn't be in apply filters without a filter count of at least 1 | |
// swap to correct shader | |
this._activeShader = this.getFilterShader(filter); | |
if (!this._activeShader) { | |
continue; | |
} | |
// now the old result is stored in slot 0, make a new render texture | |
gl.activeTexture(gl.TEXTURE0 + lastTextureSlot); | |
renderTexture = this.getTargetRenderTexture( | |
target, | |
manager._drawWidth, | |
manager._drawHeight | |
); | |
gl.bindFramebuffer(gl.FRAMEBUFFER, renderTexture._frameBuffer); | |
// draw result to render texture R -> T | |
gl.viewport(0, 0, manager._drawWidth, manager._drawHeight); | |
gl.clear(gl.COLOR_BUFFER_BIT); | |
this._drawCover(gl, flipY); | |
// bind the result texture to slot 0 as all filters and cover draws assume original content is in slot 0 | |
gl.activeTexture(gl.TEXTURE0); | |
gl.bindTexture(gl.TEXTURE_2D, renderTexture); | |
this.setTextureParams(gl); | |
// use flipping to keep things upright, things already cancel out on a single filter | |
// this needs to be here as multiPass is not accurate to _this_ frame until after shader acquisition | |
if (filterCount > 1 || filters[0]._multiPass) { | |
flipY = !flipY; | |
} | |
// work through the multipass if it's there, otherwise move on | |
filter = filter._multiPass !== null ? filter._multiPass : filters[++i]; | |
} while (filter); | |
// is this for another stage or mine | |
if (this.isCacheControlled) { | |
gl.bindFramebuffer(gl.FRAMEBUFFER, null); | |
this.updateViewport(wBackup, hBackup); | |
// draw result to canvas R -> C | |
this._activeShader = this.getFilterShader(this); | |
gl.clear(gl.COLOR_BUFFER_BIT); | |
this._drawCover(gl, flipY); | |
} else { | |
//TODO: DHG: this is less than ideal. A flipped initial render for this circumstance might help. Adjust the perspective matrix? | |
if (flipY) { | |
gl.activeTexture(gl.TEXTURE0 + lastTextureSlot); | |
renderTexture = this.getTargetRenderTexture( | |
target, | |
manager._drawWidth, | |
manager._drawHeight | |
); | |
gl.bindFramebuffer(gl.FRAMEBUFFER, renderTexture._frameBuffer); | |
this._activeShader = this.getFilterShader(this); | |
gl.viewport(0, 0, manager._drawWidth, manager._drawHeight); | |
gl.clear(gl.COLOR_BUFFER_BIT); | |
this._drawCover(gl, !flipY); | |
} | |
gl.bindFramebuffer(gl.FRAMEBUFFER, null); | |
this.updateViewport(wBackup, hBackup); | |
// make sure the last texture is the active thing to draw | |
target.cacheCanvas = renderTexture; | |
} | |
}; | |
/** | |
* Add all the contents of a container to the pending buffers, called recursively on each container. This may | |
* trigger a draw if a buffer runs out of space. This is the main workforce of the render loop. | |
* @method _appendToBatchGroup | |
* @param {Container} container The {{#crossLink "Container"}}{{/crossLink}} that contains everything to be drawn. | |
* @param {WebGLRenderingContext} gl The canvas WebGL context object to draw into. | |
* @param {Matrix2D} concatMtx The effective (concatenated) transformation matrix when beginning this container | |
* @param {Number} concatAlpha The effective (concatenated) alpha when beginning this container | |
* @param {Boolean} ignoreCache Don't use an element's cache during this draw | |
* @protected | |
*/ | |
p._appendToBatchGroup = function ( | |
container, | |
gl, | |
concatMtx, | |
concatAlpha, | |
ignoreCache | |
) { | |
// sort out shared properties | |
if (!container._glMtx) { | |
container._glMtx = new createjs.Matrix2D(); | |
} | |
var cMtx = container._glMtx; | |
cMtx.copy(concatMtx); | |
if (container.transformMatrix) { | |
cMtx.appendMatrix(container.transformMatrix); | |
} else { | |
cMtx.appendTransform( | |
container.x, | |
container.y, | |
container.scaleX, | |
container.scaleY, | |
container.rotation, | |
container.skewX, | |
container.skewY, | |
container.regX, | |
container.regY | |
); | |
} | |
// sub components of figuring out the position an object holds | |
var subL, subT, subR, subB; | |
// actually apply its data to the buffers | |
var l = container.children.length; | |
for (var i = 0; i < l; i++) { | |
var item = container.children[i]; | |
if (!(item.visible && concatAlpha)) { | |
continue; | |
} | |
if (!item.cacheCanvas || ignoreCache) { | |
if (item._updateState) { | |
item._updateState(); | |
} | |
if (item.children) { | |
this._appendToBatchGroup(item, gl, cMtx, item.alpha * concatAlpha); | |
continue; | |
} | |
} | |
// check for overflowing batch, if yes then force a render | |
// TODO: DHG: consider making this polygon count dependant for things like vector draws | |
if (this.batchCardCount + 1 > this._maxCardsPerBatch) { | |
this.batchReason = "vertexOverflow"; | |
this._drawBuffers(gl); // <------------------------------------------------------------ | |
this.batchCardCount = 0; | |
} | |
// keep track of concatenated position | |
if (!item._glMtx) { | |
item._glMtx = new createjs.Matrix2D(); | |
} | |
var iMtx = item._glMtx; | |
iMtx.copy(cMtx); | |
if (item.transformMatrix) { | |
iMtx.appendMatrix(item.transformMatrix); | |
} else { | |
iMtx.appendTransform( | |
item.x, | |
item.y, | |
item.scaleX, | |
item.scaleY, | |
item.rotation, | |
item.skewX, | |
item.skewY, | |
item.regX, | |
item.regY | |
); | |
} | |
var uvRect, texIndex, image, frame, texture, src; | |
var useCache = item.cacheCanvas && !ignoreCache; | |
if (item._webGLRenderStyle === 2 || useCache) { | |
// BITMAP / Cached Canvas | |
image = (ignoreCache ? false : item.cacheCanvas) || item.image; | |
} else if (item._webGLRenderStyle === 1) { | |
// SPRITE | |
frame = item.spriteSheet.getFrame(item.currentFrame); //TODO: Faster way? | |
if (frame === null) { | |
continue; | |
} | |
image = frame.image; | |
} else { | |
// MISC (DOM objects render themselves later) | |
continue; | |
} | |
var uvs = this._uvs; | |
var vertices = this._vertices; | |
var texI = this._indices; | |
var alphas = this._alphas; | |
// calculate texture | |
if (!image) { | |
continue; | |
} | |
if (image._storeID === undefined) { | |
// this texture is new to us so load it and add it to the batch | |
texture = this._loadTextureImage(gl, image); | |
this._insertTextureInBatch(gl, texture); | |
} else { | |
// fetch the texture (render textures know how to look themselves up to simplify this logic) | |
texture = this._textureDictionary[image._storeID]; | |
if (!texture) { | |
if (this.vocalDebug) { | |
console.log( | |
"Texture should not be looked up while not being stored." | |
); | |
} | |
continue; | |
} | |
// put it in the batch if needed | |
if (texture._batchID !== this._batchID) { | |
this._insertTextureInBatch(gl, texture); | |
} | |
} | |
texIndex = texture._activeIndex; | |
if (item._webGLRenderStyle === 2 || useCache) { | |
// BITMAP / Cached Canvas | |
if (!useCache && item.sourceRect) { | |
// calculate uvs | |
if (!item._uvRect) { | |
item._uvRect = {}; | |
} | |
src = item.sourceRect; | |
uvRect = item._uvRect; | |
uvRect.t = src.y / image.height; | |
uvRect.l = src.x / image.width; | |
uvRect.b = (src.y + src.height) / image.height; | |
uvRect.r = (src.x + src.width) / image.width; | |
// calculate vertices | |
subL = 0; | |
subT = 0; | |
subR = src.width + subL; | |
subB = src.height + subT; | |
} else { | |
// calculate uvs | |
uvRect = StageGL.UV_RECT; | |
// calculate vertices | |
if (useCache) { | |
src = item.bitmapCache; | |
subL = src.x + src._filterOffX / src.scale; | |
subT = src.y + src._filterOffY / src.scale; | |
subR = src._drawWidth / src.scale + subL; | |
subB = src._drawHeight / src.scale + subT; | |
} else { | |
subL = 0; | |
subT = 0; | |
subR = image.width + subL; | |
subB = image.height + subT; | |
} | |
} | |
} else if (item._webGLRenderStyle === 1) { | |
// SPRITE | |
var rect = frame.rect; | |
// calculate uvs | |
uvRect = frame.uvRect; | |
if (!uvRect) { | |
uvRect = StageGL.buildUVRects( | |
item.spriteSheet, | |
item.currentFrame, | |
false | |
); | |
} | |
// calculate vertices | |
subL = -frame.regX; | |
subT = -frame.regY; | |
subR = rect.width - frame.regX; | |
subB = rect.height - frame.regY; | |
} | |
// These must be calculated here else a forced draw might happen after they're set | |
var offV1 = this.batchCardCount * StageGL.INDICIES_PER_CARD; // offset for 1 component vectors | |
var offV2 = offV1 * 2; // offset for 2 component vectors | |
//DHG: See Matrix2D.transformPoint for why this math specifically | |
// apply vertices | |
vertices[offV2] = subL * iMtx.a + subT * iMtx.c + iMtx.tx; | |
vertices[offV2 + 1] = subL * iMtx.b + subT * iMtx.d + iMtx.ty; | |
vertices[offV2 + 2] = subL * iMtx.a + subB * iMtx.c + iMtx.tx; | |
vertices[offV2 + 3] = subL * iMtx.b + subB * iMtx.d + iMtx.ty; | |
vertices[offV2 + 4] = subR * iMtx.a + subT * iMtx.c + iMtx.tx; | |
vertices[offV2 + 5] = subR * iMtx.b + subT * iMtx.d + iMtx.ty; | |
vertices[offV2 + 6] = vertices[offV2 + 2]; | |
vertices[offV2 + 7] = vertices[offV2 + 3]; | |
vertices[offV2 + 8] = vertices[offV2 + 4]; | |
vertices[offV2 + 9] = vertices[offV2 + 5]; | |
vertices[offV2 + 10] = subR * iMtx.a + subB * iMtx.c + iMtx.tx; | |
vertices[offV2 + 11] = subR * iMtx.b + subB * iMtx.d + iMtx.ty; | |
// apply uvs | |
uvs[offV2] = uvRect.l; | |
uvs[offV2 + 1] = uvRect.t; | |
uvs[offV2 + 2] = uvRect.l; | |
uvs[offV2 + 3] = uvRect.b; | |
uvs[offV2 + 4] = uvRect.r; | |
uvs[offV2 + 5] = uvRect.t; | |
uvs[offV2 + 6] = uvRect.l; | |
uvs[offV2 + 7] = uvRect.b; | |
uvs[offV2 + 8] = uvRect.r; | |
uvs[offV2 + 9] = uvRect.t; | |
uvs[offV2 + 10] = uvRect.r; | |
uvs[offV2 + 11] = uvRect.b; | |
// apply texture | |
texI[offV1] = | |
texI[offV1 + 1] = | |
texI[offV1 + 2] = | |
texI[offV1 + 3] = | |
texI[offV1 + 4] = | |
texI[offV1 + 5] = | |
texIndex; | |
// apply alpha | |
alphas[offV1] = | |
alphas[offV1 + 1] = | |
alphas[offV1 + 2] = | |
alphas[offV1 + 3] = | |
alphas[offV1 + 4] = | |
alphas[offV1 + 5] = | |
item.alpha * concatAlpha; | |
this.batchCardCount++; | |
} | |
}; | |
/** | |
* Draws all the currently defined cards in the buffer to the render surface. | |
* @method _drawBuffers | |
* @param {WebGLRenderingContext} gl The canvas WebGL context object to draw into. | |
* @protected | |
*/ | |
p._drawBuffers = function (gl) { | |
if (this.batchCardCount <= 0) { | |
return; | |
} // prevents error logs on stages filled with un-renederable content. | |
if (this.vocalDebug) { | |
console.log( | |
"Draw[" + this._drawID + ":" + this._batchID + "] : " + this.batchReason | |
); | |
} | |
var shaderProgram = this._activeShader; | |
var vertexPositionBuffer = this._vertexPositionBuffer; | |
var textureIndexBuffer = this._textureIndexBuffer; | |
var uvPositionBuffer = this._uvPositionBuffer; | |
var alphaBuffer = this._alphaBuffer; | |
gl.useProgram(shaderProgram); | |
gl.bindBuffer(gl.ARRAY_BUFFER, vertexPositionBuffer); | |
gl.vertexAttribPointer( | |
shaderProgram.vertexPositionAttribute, | |
vertexPositionBuffer.itemSize, | |
gl.FLOAT, | |
false, | |
0, | |
0 | |
); | |
gl.bufferSubData(gl.ARRAY_BUFFER, 0, this._vertices); | |
gl.bindBuffer(gl.ARRAY_BUFFER, textureIndexBuffer); | |
gl.vertexAttribPointer( | |
shaderProgram.textureIndexAttribute, | |
textureIndexBuffer.itemSize, | |
gl.FLOAT, | |
false, | |
0, | |
0 | |
); | |
gl.bufferSubData(gl.ARRAY_BUFFER, 0, this._indices); | |
gl.bindBuffer(gl.ARRAY_BUFFER, uvPositionBuffer); | |
gl.vertexAttribPointer( | |
shaderProgram.uvPositionAttribute, | |
uvPositionBuffer.itemSize, | |
gl.FLOAT, | |
false, | |
0, | |
0 | |
); | |
gl.bufferSubData(gl.ARRAY_BUFFER, 0, this._uvs); | |
gl.bindBuffer(gl.ARRAY_BUFFER, alphaBuffer); | |
gl.vertexAttribPointer( | |
shaderProgram.alphaAttribute, | |
alphaBuffer.itemSize, | |
gl.FLOAT, | |
false, | |
0, | |
0 | |
); | |
gl.bufferSubData(gl.ARRAY_BUFFER, 0, this._alphas); | |
gl.uniformMatrix4fv( | |
shaderProgram.pMatrixUniform, | |
gl.FALSE, | |
this._projectionMatrix | |
); | |
for (var i = 0; i < this._batchTextureCount; i++) { | |
var texture = this._batchTextures[i]; | |
gl.activeTexture(gl.TEXTURE0 + i); | |
gl.bindTexture(gl.TEXTURE_2D, texture); | |
this.setTextureParams(gl, texture.isPOT); | |
} | |
gl.drawArrays( | |
gl.TRIANGLES, | |
0, | |
this.batchCardCount * StageGL.INDICIES_PER_CARD | |
); | |
this._batchID++; | |
}; | |
/** | |
* Draws a card that covers the entire render surface. Mainly used for filters. | |
* @method _drawBuffers | |
* @param {WebGLRenderingContext} gl The canvas WebGL context object to draw into. | |
* @param {Boolean} flipY Covers are used for things like RenderTextures and because of 3D vs Canvas space this can | |
* end up meaning the `y` space sometimes requires flipping in the render. | |
* @protected | |
*/ | |
p._drawCover = function (gl, flipY) { | |
if (this._isDrawing > 0) { | |
this._drawBuffers(gl); | |
} | |
if (this.vocalDebug) { | |
console.log( | |
"Draw[" + this._drawID + ":" + this._batchID + "] : " + "Cover" | |
); | |
} | |
var shaderProgram = this._activeShader; | |
var vertexPositionBuffer = this._vertexPositionBuffer; | |
var uvPositionBuffer = this._uvPositionBuffer; | |
gl.clear(gl.COLOR_BUFFER_BIT); | |
gl.useProgram(shaderProgram); | |
gl.bindBuffer(gl.ARRAY_BUFFER, vertexPositionBuffer); | |
gl.vertexAttribPointer( | |
shaderProgram.vertexPositionAttribute, | |
vertexPositionBuffer.itemSize, | |
gl.FLOAT, | |
false, | |
0, | |
0 | |
); | |
gl.bufferSubData(gl.ARRAY_BUFFER, 0, StageGL.COVER_VERT); | |
gl.bindBuffer(gl.ARRAY_BUFFER, uvPositionBuffer); | |
gl.vertexAttribPointer( | |
shaderProgram.uvPositionAttribute, | |
uvPositionBuffer.itemSize, | |
gl.FLOAT, | |
false, | |
0, | |
0 | |
); | |
gl.bufferSubData( | |
gl.ARRAY_BUFFER, | |
0, | |
flipY ? StageGL.COVER_UV_FLIP : StageGL.COVER_UV | |
); | |
gl.uniform1i(shaderProgram.samplerUniform, 0); | |
gl.uniform1f(shaderProgram.uprightUniform, flipY ? 0 : 1); | |
gl.drawArrays(gl.TRIANGLES, 0, StageGL.INDICIES_PER_CARD); | |
}; | |
createjs.StageGL = createjs.promote(StageGL, "Stage"); | |
})(); | |
//############################################################################## | |
// Bitmap.js | |
//############################################################################## | |
this.createjs = this.createjs || {}; | |
(function () { | |
/** | |
* A Bitmap represents an Image, Canvas, or Video in the display list. A Bitmap can be instantiated using an existing | |
* HTML element, or a string. | |
* | |
* <h4>Example</h4> | |
* | |
* var bitmap = new createjs.Bitmap("imagePath.jpg"); | |
* | |
* <strong>Notes:</strong> | |
* <ol> | |
* <li>When using a video source that may loop or seek, use a {{#crossLink "VideoBuffer"}}{{/crossLink}} object to | |
* prevent blinking / flashing. | |
* <li>When a string path or image tag that is not yet loaded is used, the stage may need to be redrawn before it | |
* will be displayed.</li> | |
* <li>Bitmaps with an SVG source currently will not respect an alpha value other than 0 or 1. To get around this, | |
* the Bitmap can be cached.</li> | |
* <li>Bitmaps with an SVG source will taint the canvas with cross-origin data, which prevents interactivity. This | |
* happens in all browsers except recent Firefox builds.</li> | |
* <li>Images loaded cross-origin will throw cross-origin security errors when interacted with using a mouse, using | |
* methods such as `getObjectUnderPoint`, or using filters, or caching. You can get around this by setting | |
* `crossOrigin` flags on your images before passing them to EaselJS, eg: `img.crossOrigin="Anonymous";`</li> | |
* </ol> | |
* | |
* @class Bitmap | |
* @extends DisplayObject | |
* @constructor | |
* @param {CanvasImageSource | String | Object} imageOrUri The source image to display. This can be a CanvasImageSource | |
* (image, video, canvas), an object with a `getImage` method that returns a CanvasImageSource, or a string URL to an image. | |
* If the latter, a new Image instance with the URL as its src will be used. | |
**/ | |
function Bitmap(imageOrUri) { | |
this.DisplayObject_constructor(); | |
// public properties: | |
/** | |
* The source image to display. This can be a CanvasImageSource | |
* (image, video, canvas), an object with a `getImage` method that returns a CanvasImageSource, or a string URL to an image. | |
* If the latter, a new Image instance with the URL as its src will be used. | |
* @property image | |
* @type CanvasImageSource | Object | |
**/ | |
if (typeof imageOrUri == "string") { | |
this.image = document.createElement("img"); | |
this.image.src = imageOrUri; | |
} else { | |
this.image = imageOrUri; | |
} | |
/** | |
* Specifies an area of the source image to draw. If omitted, the whole image will be drawn. | |
* Notes: | |
* <ul> | |
* <li>that video sources must have a width / height set to work correctly with `sourceRect`</li> | |
* <li>Cached objects will ignore the `sourceRect` property</li> | |
* </ul> | |
* @property sourceRect | |
* @type Rectangle | |
* @default null | |
*/ | |
this.sourceRect = null; | |
// private properties: | |
/** | |
* Docced in superclass. | |
*/ | |
this._webGLRenderStyle = createjs.DisplayObject._StageGL_BITMAP; | |
} | |
var p = createjs.extend(Bitmap, createjs.DisplayObject); | |
// public methods: | |
/** | |
* Constructor alias for backwards compatibility. This method will be removed in future versions. | |
* Subclasses should be updated to use {{#crossLink "Utility Methods/extends"}}{{/crossLink}}. | |
* @method initialize | |
* @deprecated in favour of `createjs.promote()` | |
**/ | |
p.initialize = Bitmap; // TODO: deprecated. | |
/** | |
* Returns true or false indicating whether the display object would be visible if drawn to a canvas. | |
* This does not account for whether it would be visible within the boundaries of the stage. | |
* | |
* NOTE: This method is mainly for internal use, though it may be useful for advanced uses. | |
* @method isVisible | |
* @return {Boolean} Boolean indicating whether the display object would be visible if drawn to a canvas | |
**/ | |
p.isVisible = function () { | |
var image = this.image; | |
var hasContent = | |
this.cacheCanvas || | |
(image && | |
(image.naturalWidth || image.getContext || image.readyState >= 2)); | |
return !!( | |
this.visible && | |
this.alpha > 0 && | |
this.scaleX != 0 && | |
this.scaleY != 0 && | |
hasContent | |
); | |
}; | |
/** | |
* Draws the display object into the specified context ignoring its visible, alpha, shadow, and transform. | |
* Returns true if the draw was handled (useful for overriding functionality). | |
* | |
* NOTE: This method is mainly for internal use, though it may be useful for advanced uses. | |
* @method draw | |
* @param {CanvasRenderingContext2D} ctx The canvas 2D context object to draw into. | |
* @param {Boolean} [ignoreCache=false] Indicates whether the draw operation should ignore any current cache. | |
* For example, used for drawing the cache (to prevent it from simply drawing an existing cache back | |
* into itself). | |
* @return {Boolean} | |
**/ | |
p.draw = function (ctx, ignoreCache) { | |
if (this.DisplayObject_draw(ctx, ignoreCache)) { | |
return true; | |
} | |
var img = this.image, | |
rect = this.sourceRect; | |
if (img.getImage) { | |
img = img.getImage(); | |
} | |
if (!img) { | |
return true; | |
} | |
if (rect) { | |
// some browsers choke on out of bound values, so we'll fix them: | |
var x1 = rect.x, | |
y1 = rect.y, | |
x2 = x1 + rect.width, | |
y2 = y1 + rect.height, | |
x = 0, | |
y = 0, | |
w = img.width, | |
h = img.height; | |
if (x1 < 0) { | |
x -= x1; | |
x1 = 0; | |
} | |
if (x2 > w) { | |
x2 = w; | |
} | |
if (y1 < 0) { | |
y -= y1; | |
y1 = 0; | |
} | |
if (y2 > h) { | |
y2 = h; | |
} | |
ctx.drawImage(img, x1, y1, x2 - x1, y2 - y1, x, y, x2 - x1, y2 - y1); | |
} else { | |
ctx.drawImage(img, 0, 0); | |
} | |
return true; | |
}; | |
//Note, the doc sections below document using the specified APIs (from DisplayObject) from | |
//Bitmap. This is why they have no method implementations. | |
/** | |
* Because the content of a Bitmap is already in a simple format, cache is unnecessary for Bitmap instances. | |
* You should <b>not</b> cache Bitmap instances as it can degrade performance. | |
* | |
* <strong>However: If you want to use a filter on a Bitmap, you <em>MUST</em> cache it, or it will not work.</strong> | |
* To see the API for caching, please visit the DisplayObject {{#crossLink "DisplayObject/cache"}}{{/crossLink}} | |
* method. | |
* @method cache | |
**/ | |
/** | |
* Because the content of a Bitmap is already in a simple format, cache is unnecessary for Bitmap instances. | |
* You should <b>not</b> cache Bitmap instances as it can degrade performance. | |
* | |
* <strong>However: If you want to use a filter on a Bitmap, you <em>MUST</em> cache it, or it will not work.</strong> | |
* To see the API for caching, please visit the DisplayObject {{#crossLink "DisplayObject/cache"}}{{/crossLink}} | |
* method. | |
* @method updateCache | |
**/ | |
/** | |
* Because the content of a Bitmap is already in a simple format, cache is unnecessary for Bitmap instances. | |
* You should <b>not</b> cache Bitmap instances as it can degrade performance. | |
* | |
* <strong>However: If you want to use a filter on a Bitmap, you <em>MUST</em> cache it, or it will not work.</strong> | |
* To see the API for caching, please visit the DisplayObject {{#crossLink "DisplayObject/cache"}}{{/crossLink}} | |
* method. | |
* @method uncache | |
**/ | |
/** | |
* Docced in superclass. | |
*/ | |
p.getBounds = function () { | |
var rect = this.DisplayObject_getBounds(); | |
if (rect) { | |
return rect; | |
} | |
var image = this.image, | |
o = this.sourceRect || image; | |
var hasContent = | |
image && | |
(image.naturalWidth || image.getContext || image.readyState >= 2); | |
return hasContent | |
? this._rectangle.setValues(0, 0, o.width, o.height) | |
: null; | |
}; | |
/** | |
* Returns a clone of the Bitmap instance. | |
* @method clone | |
* @param {Boolean} node Whether the underlying dom element should be cloned as well. | |
* @return {Bitmap} a clone of the Bitmap instance. | |
**/ | |
p.clone = function (node) { | |
var image = this.image; | |
if (image && node) { | |
image = image.cloneNode(); | |
} | |
var o = new Bitmap(image); | |
if (this.sourceRect) { | |
o.sourceRect = this.sourceRect.clone(); | |
} | |
this._cloneProps(o); | |
return o; | |
}; | |
/** | |
* Returns a string representation of this object. | |
* @method toString | |
* @return {String} a string representation of the instance. | |
**/ | |
p.toString = function () { | |
return "[Bitmap (name=" + this.name + ")]"; | |
}; | |
createjs.Bitmap = createjs.promote(Bitmap, "DisplayObject"); | |
})(); | |
//############################################################################## | |
// Sprite.js | |
//############################################################################## | |
this.createjs = this.createjs || {}; | |
(function () { | |
"use strict"; | |
// constructor: | |
/** | |
* Displays a frame or sequence of frames (ie. an animation) from a SpriteSheet instance. A sprite sheet is a series of | |
* images (usually animation frames) combined into a single image. For example, an animation consisting of 8 100x100 | |
* images could be combined into a 400x200 sprite sheet (4 frames across by 2 high). You can display individual frames, | |
* play frames as an animation, and even sequence animations together. | |
* | |
* See the {{#crossLink "SpriteSheet"}}{{/crossLink}} class for more information on setting up frames and animations. | |
* | |
* <h4>Example</h4> | |
* | |
* var instance = new createjs.Sprite(spriteSheet); | |
* instance.gotoAndStop("frameName"); | |
* | |
* Until {{#crossLink "Sprite/gotoAndStop"}}{{/crossLink}} or {{#crossLink "Sprite/gotoAndPlay"}}{{/crossLink}} is called, | |
* only the first defined frame defined in the sprite sheet will be displayed. | |
* | |
* @class Sprite | |
* @extends DisplayObject | |
* @constructor | |
* @param {SpriteSheet} spriteSheet The SpriteSheet instance to play back. This includes the source image(s), frame | |
* dimensions, and frame data. See {{#crossLink "SpriteSheet"}}{{/crossLink}} for more information. | |
* @param {String|Number} [frameOrAnimation] The frame number or animation to play initially. | |
**/ | |
function Sprite(spriteSheet, frameOrAnimation) { | |
this.DisplayObject_constructor(); | |
// public properties: | |
/** | |
* The frame index that will be drawn when draw is called. Note that with some {{#crossLink "SpriteSheet"}}{{/crossLink}} | |
* definitions, this will advance non-sequentially. This will always be an integer value. | |
* @property currentFrame | |
* @type {Number} | |
* @default 0 | |
* @readonly | |
**/ | |
this.currentFrame = 0; | |
/** | |
* Returns the name of the currently playing animation. | |
* @property currentAnimation | |
* @type {String} | |
* @final | |
* @readonly | |
**/ | |
this.currentAnimation = null; | |
/** | |
* Prevents the animation from advancing each tick automatically. For example, you could create a sprite | |
* sheet of icons, set paused to true, and display the appropriate icon by setting <code>currentFrame</code>. | |
* @property paused | |
* @type {Boolean} | |
* @default false | |
**/ | |
this.paused = true; | |
/** | |
* The SpriteSheet instance to play back. This includes the source image, frame dimensions, and frame | |
* data. See {{#crossLink "SpriteSheet"}}{{/crossLink}} for more information. | |
* @property spriteSheet | |
* @type {SpriteSheet} | |
* @readonly | |
**/ | |
this.spriteSheet = spriteSheet; | |
/** | |
* Specifies the current frame index within the currently playing animation. When playing normally, this will increase | |
* from 0 to n-1, where n is the number of frames in the current animation. | |
* | |
* This could be a non-integer value if | |
* using time-based playback (see {{#crossLink "Sprite/framerate"}}{{/crossLink}}, or if the animation's speed is | |
* not an integer. | |
* @property currentAnimationFrame | |
* @type {Number} | |
* @default 0 | |
**/ | |
this.currentAnimationFrame = 0; | |
/** | |
* By default Sprite instances advance one frame per tick. Specifying a framerate for the Sprite (or its related | |
* SpriteSheet) will cause it to advance based on elapsed time between ticks as appropriate to maintain the target | |
* framerate. | |
* | |
* For example, if a Sprite with a framerate of 10 is placed on a Stage being updated at 40fps, then the Sprite will | |
* advance roughly one frame every 4 ticks. This will not be exact, because the time between each tick will | |
* vary slightly between frames. | |
* | |
* This feature is dependent on the tick event object (or an object with an appropriate "delta" property) being | |
* passed into {{#crossLink "Stage/update"}}{{/crossLink}}. | |
* @property framerate | |
* @type {Number} | |
* @default 0 | |
**/ | |
this.framerate = 0; | |
// private properties: | |
/** | |
* Current animation object. | |
* @property _animation | |
* @protected | |
* @type {Object} | |
* @default null | |
**/ | |
this._animation = null; | |
/** | |
* Current frame index. | |
* @property _currentFrame | |
* @protected | |
* @type {Number} | |
* @default null | |
**/ | |
this._currentFrame = null; | |
/** | |
* Skips the next auto advance. Used by gotoAndPlay to avoid immediately jumping to the next frame | |
* @property _skipAdvance | |
* @protected | |
* @type {Boolean} | |
* @default false | |
**/ | |
this._skipAdvance = false; | |
/** | |
* Docced in superclass. | |
*/ | |
this._webGLRenderStyle = createjs.DisplayObject._StageGL_SPRITE; | |
if (frameOrAnimation != null) { | |
this.gotoAndPlay(frameOrAnimation); | |
} | |
} | |
var p = createjs.extend(Sprite, createjs.DisplayObject); | |
/** | |
* Constructor alias for backwards compatibility. This method will be removed in future versions. | |
* Subclasses should be updated to use {{#crossLink "Utility Methods/extends"}}{{/crossLink}}. | |
* @method initialize | |
* @deprecated in favour of `createjs.promote()` | |
**/ | |
p.initialize = Sprite; // TODO: Deprecated. This is for backwards support of Flash/Animate spritesheet export. | |
// events: | |
/** | |
* Dispatched when an animation reaches its ends. | |
* @event animationend | |
* @param {Object} target The object that dispatched the event. | |
* @param {String} type The event type. | |
* @param {String} name The name of the animation that just ended. | |
* @param {String} next The name of the next animation that will be played, or null. This will be the same as name if the animation is looping. | |
* @since 0.6.0 | |
*/ | |
/** | |
* Dispatched any time the current frame changes. For example, this could be due to automatic advancement on a tick, | |
* or calling gotoAndPlay() or gotoAndStop(). | |
* @event change | |
* @param {Object} target The object that dispatched the event. | |
* @param {String} type The event type. | |
*/ | |
// public methods: | |
/** | |
* Returns true or false indicating whether the display object would be visible if drawn to a canvas. | |
* This does not account for whether it would be visible within the boundaries of the stage. | |
* NOTE: This method is mainly for internal use, though it may be useful for advanced uses. | |
* @method isVisible | |
* @return {Boolean} Boolean indicating whether the display object would be visible if drawn to a canvas | |
**/ | |
p.isVisible = function () { | |
var hasContent = this.cacheCanvas || this.spriteSheet.complete; | |
return !!( | |
this.visible && | |
this.alpha > 0 && | |
this.scaleX != 0 && | |
this.scaleY != 0 && | |
hasContent | |
); | |
}; | |
/** | |
* Draws the display object into the specified context ignoring its visible, alpha, shadow, and transform. | |
* Returns true if the draw was handled (useful for overriding functionality). | |
* NOTE: This method is mainly for internal use, though it may be useful for advanced uses. | |
* @method draw | |
* @param {CanvasRenderingContext2D} ctx The canvas 2D context object to draw into. | |
* @param {Boolean} ignoreCache Indicates whether the draw operation should ignore any current cache. | |
* For example, used for drawing the cache (to prevent it from simply drawing an existing cache back | |
* into itself). | |
**/ | |
p.draw = function (ctx, ignoreCache) { | |
if (this.DisplayObject_draw(ctx, ignoreCache)) { | |
return true; | |
} | |
this._normalizeFrame(); | |
var o = this.spriteSheet.getFrame(this._currentFrame | 0); | |
if (!o) { | |
return false; | |
} | |
var rect = o.rect; | |
if (rect.width && rect.height) { | |
ctx.drawImage( | |
o.image, | |
rect.x, | |
rect.y, | |
rect.width, | |
rect.height, | |
-o.regX, | |
-o.regY, | |
rect.width, | |
rect.height | |
); | |
} | |
return true; | |
}; | |
//Note, the doc sections below document using the specified APIs (from DisplayObject) from | |
//Bitmap. This is why they have no method implementations. | |
/** | |
* Because the content of a Sprite is already in a raster format, cache is unnecessary for Sprite instances. | |
* You should not cache Sprite instances as it can degrade performance. | |
* @method cache | |
**/ | |
/** | |
* Because the content of a Sprite is already in a raster format, cache is unnecessary for Sprite instances. | |
* You should not cache Sprite instances as it can degrade performance. | |
* @method updateCache | |
**/ | |
/** | |
* Because the content of a Sprite is already in a raster format, cache is unnecessary for Sprite instances. | |
* You should not cache Sprite instances as it can degrade performance. | |
* @method uncache | |
**/ | |
/** | |
* Play (unpause) the current animation. The Sprite will be paused if either {{#crossLink "Sprite/stop"}}{{/crossLink}} | |
* or {{#crossLink "Sprite/gotoAndStop"}}{{/crossLink}} is called. Single frame animations will remain | |
* unchanged. | |
* @method play | |
**/ | |
p.play = function () { | |
this.paused = false; | |
}; | |
/** | |
* Stop playing a running animation. The Sprite will be playing if {{#crossLink "Sprite/gotoAndPlay"}}{{/crossLink}} | |
* is called. Note that calling {{#crossLink "Sprite/gotoAndPlay"}}{{/crossLink}} or {{#crossLink "Sprite/play"}}{{/crossLink}} | |
* will resume playback. | |
* @method stop | |
**/ | |
p.stop = function () { | |
this.paused = true; | |
}; | |
/** | |
* Sets paused to false and plays the specified animation name, named frame, or frame number. | |
* @method gotoAndPlay | |
* @param {String|Number} frameOrAnimation The frame number or animation name that the playhead should move to | |
* and begin playing. | |
**/ | |
p.gotoAndPlay = function (frameOrAnimation) { | |
this.paused = false; | |
this._skipAdvance = true; | |
this._goto(frameOrAnimation); | |
}; | |
/** | |
* Sets paused to true and seeks to the specified animation name, named frame, or frame number. | |
* @method gotoAndStop | |
* @param {String|Number} frameOrAnimation The frame number or animation name that the playhead should move to | |
* and stop. | |
**/ | |
p.gotoAndStop = function (frameOrAnimation) { | |
this.paused = true; | |
this._goto(frameOrAnimation); | |
}; | |
/** | |
* Advances the playhead. This occurs automatically each tick by default. | |
* @param [time] {Number} The amount of time in ms to advance by. Only applicable if framerate is set on the Sprite | |
* or its SpriteSheet. | |
* @method advance | |
*/ | |
p.advance = function (time) { | |
var fps = this.framerate || this.spriteSheet.framerate; | |
var t = fps && time != null ? time / (1000 / fps) : 1; | |
this._normalizeFrame(t); | |
}; | |
/** | |
* Returns a {{#crossLink "Rectangle"}}{{/crossLink}} instance defining the bounds of the current frame relative to | |
* the origin. For example, a 90 x 70 frame with <code>regX=50</code> and <code>regY=40</code> would return a | |
* rectangle with [x=-50, y=-40, width=90, height=70]. This ignores transformations on the display object. | |
* | |
* Also see the SpriteSheet {{#crossLink "SpriteSheet/getFrameBounds"}}{{/crossLink}} method. | |
* @method getBounds | |
* @return {Rectangle} A Rectangle instance. Returns null if the frame does not exist, or the image is not fully | |
* loaded. | |
**/ | |
p.getBounds = function () { | |
// TODO: should this normalizeFrame? | |
return ( | |
this.DisplayObject_getBounds() || | |
this.spriteSheet.getFrameBounds(this.currentFrame, this._rectangle) | |
); | |
}; | |
/** | |
* Returns a clone of the Sprite instance. Note that the same SpriteSheet is shared between cloned | |
* instances. | |
* @method clone | |
* @return {Sprite} a clone of the Sprite instance. | |
**/ | |
p.clone = function () { | |
return this._cloneProps(new Sprite(this.spriteSheet)); | |
}; | |
/** | |
* Returns a string representation of this object. | |
* @method toString | |
* @return {String} a string representation of the instance. | |
**/ | |
p.toString = function () { | |
return "[Sprite (name=" + this.name + ")]"; | |
}; | |
// private methods: | |
/** | |
* @method _cloneProps | |
* @param {Sprite} o | |
* @return {Sprite} o | |
* @protected | |
**/ | |
p._cloneProps = function (o) { | |
this.DisplayObject__cloneProps(o); | |
o.currentFrame = this.currentFrame; | |
o.currentAnimation = this.currentAnimation; | |
o.paused = this.paused; | |
o.currentAnimationFrame = this.currentAnimationFrame; | |
o.framerate = this.framerate; | |
o._animation = this._animation; | |
o._currentFrame = this._currentFrame; | |
o._skipAdvance = this._skipAdvance; | |
return o; | |
}; | |
/** | |
* Advances the <code>currentFrame</code> if paused is not true. This is called automatically when the {{#crossLink "Stage"}}{{/crossLink}} | |
* ticks. | |
* @param {Object} evtObj An event object that will be dispatched to all tick listeners. This object is reused between dispatchers to reduce construction & GC costs. | |
* @protected | |
* @method _tick | |
**/ | |
p._tick = function (evtObj) { | |
if (!this.paused) { | |
if (!this._skipAdvance) { | |
this.advance(evtObj && evtObj.delta); | |
} | |
this._skipAdvance = false; | |
} | |
this.DisplayObject__tick(evtObj); | |
}; | |
/** | |
* Normalizes the current frame, advancing animations and dispatching callbacks as appropriate. | |
* @protected | |
* @method _normalizeFrame | |
**/ | |
p._normalizeFrame = function (frameDelta) { | |
frameDelta = frameDelta || 0; | |
var animation = this._animation; | |
var paused = this.paused; | |
var frame = this._currentFrame; | |
var l; | |
if (animation) { | |
var speed = animation.speed || 1; | |
var animFrame = this.currentAnimationFrame; | |
l = animation.frames.length; | |
if (animFrame + frameDelta * speed >= l) { | |
var next = animation.next; | |
if (this._dispatchAnimationEnd(animation, frame, paused, next, l - 1)) { | |
// something changed in the event stack, so we shouldn't make any more changes here. | |
return; | |
} else if (next) { | |
// sequence. Automatically calls _normalizeFrame again with the remaining frames. | |
return this._goto(next, frameDelta - (l - animFrame) / speed); | |
} else { | |
// end. | |
this.paused = true; | |
animFrame = animation.frames.length - 1; | |
} | |
} else { | |
animFrame += frameDelta * speed; | |
} | |
this.currentAnimationFrame = animFrame; | |
this._currentFrame = animation.frames[animFrame | 0]; | |
} else { | |
frame = this._currentFrame += frameDelta; | |
l = this.spriteSheet.getNumFrames(); | |
if (frame >= l && l > 0) { | |
if (!this._dispatchAnimationEnd(animation, frame, paused, l - 1)) { | |
// looped. | |
if ((this._currentFrame -= l) >= l) { | |
return this._normalizeFrame(); | |
} | |
} | |
} | |
} | |
frame = this._currentFrame | 0; | |
if (this.currentFrame != frame) { | |
this.currentFrame = frame; | |
this.dispatchEvent("change"); | |
} | |
}; | |
/** | |
* Dispatches the "animationend" event. Returns true if a handler changed the animation (ex. calling {{#crossLink "Sprite/stop"}}{{/crossLink}}, | |
* {{#crossLink "Sprite/gotoAndPlay"}}{{/crossLink}}, etc.) | |
* @property _dispatchAnimationEnd | |
* @private | |
* @type {Function} | |
**/ | |
p._dispatchAnimationEnd = function (animation, frame, paused, next, end) { | |
var name = animation ? animation.name : null; | |
if (this.hasEventListener("animationend")) { | |
var evt = new createjs.Event("animationend"); | |
evt.name = name; | |
evt.next = next; | |
this.dispatchEvent(evt); | |
} | |
// did the animation get changed in the event stack?: | |
var changed = this._animation != animation || this._currentFrame != frame; | |
// if the animation hasn't changed, but the sprite was paused, then we want to stick to the last frame: | |
if (!changed && !paused && this.paused) { | |
this.currentAnimationFrame = end; | |
changed = true; | |
} | |
return changed; | |
}; | |
/** | |
* Moves the playhead to the specified frame number or animation. | |
* @method _goto | |
* @param {String|Number} frameOrAnimation The frame number or animation that the playhead should move to. | |
* @param {Boolean} [frame] The frame of the animation to go to. Defaults to 0. | |
* @protected | |
**/ | |
p._goto = function (frameOrAnimation, frame) { | |
this.currentAnimationFrame = 0; | |
if (isNaN(frameOrAnimation)) { | |
var data = this.spriteSheet.getAnimation(frameOrAnimation); | |
if (data) { | |
this._animation = data; | |
this.currentAnimation = frameOrAnimation; | |
this._normalizeFrame(frame); | |
} | |
} else { | |
this.currentAnimation = this._animation = null; | |
this._currentFrame = frameOrAnimation; | |
this._normalizeFrame(); | |
} | |
}; | |
createjs.Sprite = createjs.promote(Sprite, "DisplayObject"); | |
})(); | |
//############################################################################## | |
// Shape.js | |
//############################################################################## | |
this.createjs = this.createjs || {}; | |
(function () { | |
"use strict"; | |
// constructor: | |
/** | |
* A Shape allows you to display vector art in the display list. It composites a {{#crossLink "Graphics"}}{{/crossLink}} | |
* instance which exposes all of the vector drawing methods. The Graphics instance can be shared between multiple Shape | |
* instances to display the same vector graphics with different positions or transforms. | |
* | |
* If the vector art will not | |
* change between draws, you may want to use the {{#crossLink "DisplayObject/cache"}}{{/crossLink}} method to reduce the | |
* rendering cost. | |
* | |
* <h4>Example</h4> | |
* | |
* var graphics = new createjs.Graphics().beginFill("#ff0000").drawRect(0, 0, 100, 100); | |
* var shape = new createjs.Shape(graphics); | |
* | |
* //Alternatively use can also use the graphics property of the Shape class to renderer the same as above. | |
* var shape = new createjs.Shape(); | |
* shape.graphics.beginFill("#ff0000").drawRect(0, 0, 100, 100); | |
* | |
* @class Shape | |
* @extends DisplayObject | |
* @constructor | |
* @param {Graphics} graphics Optional. The graphics instance to display. If null, a new Graphics instance will be created. | |
**/ | |
function Shape(graphics) { | |
this.DisplayObject_constructor(); | |
// public properties: | |
/** | |
* The graphics instance to display. | |
* @property graphics | |
* @type Graphics | |
**/ | |
this.graphics = graphics ? graphics : new createjs.Graphics(); | |
} | |
var p = createjs.extend(Shape, createjs.DisplayObject); | |
// TODO: deprecated | |
// p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details. | |
// public methods: | |
/** | |
* Returns true or false indicating whether the Shape would be visible if drawn to a canvas. | |
* This does not account for whether it would be visible within the boundaries of the stage. | |
* NOTE: This method is mainly for internal use, though it may be useful for advanced uses. | |
* @method isVisible | |
* @return {Boolean} Boolean indicating whether the Shape would be visible if drawn to a canvas | |
**/ | |
p.isVisible = function () { | |
var hasContent = | |
this.cacheCanvas || (this.graphics && !this.graphics.isEmpty()); | |
return !!( | |
this.visible && | |
this.alpha > 0 && | |
this.scaleX != 0 && | |
this.scaleY != 0 && | |
hasContent | |
); | |
}; | |
/** | |
* Draws the Shape into the specified context ignoring its visible, alpha, shadow, and transform. Returns true if | |
* the draw was handled (useful for overriding functionality). | |
* | |
* <i>NOTE: This method is mainly for internal use, though it may be useful for advanced uses.</i> | |
* @method draw | |
* @param {CanvasRenderingContext2D} ctx The canvas 2D context object to draw into. | |
* @param {Boolean} [ignoreCache=false] Indicates whether the draw operation should ignore any current cache. For example, | |
* used for drawing the cache (to prevent it from simply drawing an existing cache back into itself). | |
* @return {Boolean} | |
**/ | |
p.draw = function (ctx, ignoreCache) { | |
if (this.DisplayObject_draw(ctx, ignoreCache)) { | |
return true; | |
} | |
this.graphics.draw(ctx, this); | |
return true; | |
}; | |
/** | |
* Returns a clone of this Shape. Some properties that are specific to this instance's current context are reverted to | |
* their defaults (for example .parent). | |
* @method clone | |
* @param {Boolean} recursive If true, this Shape's {{#crossLink "Graphics"}}{{/crossLink}} instance will also be | |
* cloned. If false, the Graphics instance will be shared with the new Shape. | |
**/ | |
p.clone = function (recursive) { | |
var g = recursive && this.graphics ? this.graphics.clone() : this.graphics; | |
return this._cloneProps(new Shape(g)); | |
}; | |
/** | |
* Returns a string representation of this object. | |
* @method toString | |
* @return {String} a string representation of the instance. | |
**/ | |
p.toString = function () { | |
return "[Shape (name=" + this.name + ")]"; | |
}; | |
createjs.Shape = createjs.promote(Shape, "DisplayObject"); | |
})(); | |
//############################################################################## | |
// Text.js | |
//############################################################################## | |
this.createjs = this.createjs || {}; | |
(function () { | |
"use strict"; | |
// constructor: | |
/** | |
* Display one or more lines of dynamic text (not user editable) in the display list. Line wrapping support (using the | |
* lineWidth) is very basic, wrapping on spaces and tabs only. Note that as an alternative to Text, you can position HTML | |
* text above or below the canvas relative to items in the display list using the {{#crossLink "DisplayObject/localToGlobal"}}{{/crossLink}} | |
* method, or using {{#crossLink "DOMElement"}}{{/crossLink}}. | |
* | |
* <b>Please note that Text does not support HTML text, and can only display one font style at a time.</b> To use | |
* multiple font styles, you will need to create multiple text instances, and position them manually. | |
* | |
* <h4>Example</h4> | |
* | |
* var text = new createjs.Text("Hello World", "20px Arial", "#ff7700"); | |
* text.x = 100; | |
* text.textBaseline = "alphabetic"; | |
* | |
* CreateJS Text supports web fonts (the same rules as Canvas). The font must be loaded and supported by the browser | |
* before it can be displayed. | |
* | |
* <strong>Note:</strong> Text can be expensive to generate, so cache instances where possible. Be aware that not all | |
* browsers will render Text exactly the same. | |
* @class Text | |
* @extends DisplayObject | |
* @constructor | |
* @param {String} [text] The text to display. | |
* @param {String} [font] The font style to use. Any valid value for the CSS font attribute is acceptable (ex. "bold | |
* 36px Arial"). | |
* @param {String} [color] The color to draw the text in. Any valid value for the CSS color attribute is acceptable (ex. | |
* "#F00", "red", or "#FF0000"). | |
**/ | |
function Text(text, font, color) { | |
this.DisplayObject_constructor(); | |
// public properties: | |
/** | |
* The text to display. | |
* @property text | |
* @type String | |
**/ | |
this.text = text; | |
/** | |
* The font style to use. Any valid value for the CSS font attribute is acceptable (ex. "bold 36px Arial"). | |
* @property font | |
* @type String | |
**/ | |
this.font = font; | |
/** | |
* The color to draw the text in. Any valid value for the CSS color attribute is acceptable (ex. "#F00"). Default is "#000". | |
* It will also accept valid canvas fillStyle values. | |
* @property color | |
* @type String | |
**/ | |
this.color = color; | |
/** | |
* The horizontal text alignment. Any of "start", "end", "left", "right", and "center". For detailed | |
* information view the | |
* <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#text-styles"> | |
* whatwg spec</a>. Default is "left". | |
* @property textAlign | |
* @type String | |
**/ | |
this.textAlign = "left"; | |
/** | |
* The vertical alignment point on the font. Any of "top", "hanging", "middle", "alphabetic", "ideographic", or | |
* "bottom". For detailed information view the <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#text-styles"> | |
* whatwg spec</a>. Default is "top". | |
* @property textBaseline | |
* @type String | |
*/ | |
this.textBaseline = "top"; | |
/** | |
* The maximum width to draw the text. If maxWidth is specified (not null), the text will be condensed or | |
* shrunk to make it fit in this width. For detailed information view the | |
* <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#text-styles"> | |
* whatwg spec</a>. | |
* @property maxWidth | |
* @type Number | |
*/ | |
this.maxWidth = null; | |
/** | |
* If greater than 0, the text will be drawn as a stroke (outline) of the specified width. | |
* @property outline | |
* @type Number | |
**/ | |
this.outline = 0; | |
/** | |
* Indicates the line height (vertical distance between baselines) for multi-line text. If null or 0, | |
* the value of getMeasuredLineHeight is used. | |
* @property lineHeight | |
* @type Number | |
**/ | |
this.lineHeight = 0; | |
/** | |
* Indicates the maximum width for a line of text before it is wrapped to multiple lines. If null, | |
* the text will not be wrapped. | |
* @property lineWidth | |
* @type Number | |
**/ | |
this.lineWidth = null; | |
} | |
var p = createjs.extend(Text, createjs.DisplayObject); | |
// TODO: deprecated | |
// p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details. | |
// static properties: | |
/** | |
* @property _workingContext | |
* @type CanvasRenderingContext2D | |
* @private | |
**/ | |
var canvas = createjs.createCanvas | |
? createjs.createCanvas() | |
: document.createElement("canvas"); | |
if (canvas.getContext) { | |
Text._workingContext = canvas.getContext("2d"); | |
canvas.width = canvas.height = 1; | |
} | |
// constants: | |
/** | |
* Lookup table for the ratio to offset bounds x calculations based on the textAlign property. | |
* @property H_OFFSETS | |
* @type Object | |
* @protected | |
* @static | |
**/ | |
Text.H_OFFSETS = { start: 0, left: 0, center: -0.5, end: -1, right: -1 }; | |
/** | |
* Lookup table for the ratio to offset bounds y calculations based on the textBaseline property. | |
* @property H_OFFSETS | |
* @type Object | |
* @protected | |
* @static | |
**/ | |
Text.V_OFFSETS = { | |
top: 0, | |
hanging: -0.01, | |
middle: -0.4, | |
alphabetic: -0.8, | |
ideographic: -0.85, | |
bottom: -1, | |
}; | |
// public methods: | |
/** | |
* Returns true or false indicating whether the display object would be visible if drawn to a canvas. | |
* This does not account for whether it would be visible within the boundaries of the stage. | |
* NOTE: This method is mainly for internal use, though it may be useful for advanced uses. | |
* @method isVisible | |
* @return {Boolean} Whether the display object would be visible if drawn to a canvas | |
**/ | |
p.isVisible = function () { | |
var hasContent = | |
this.cacheCanvas || (this.text != null && this.text !== ""); | |
return !!( | |
this.visible && | |
this.alpha > 0 && | |
this.scaleX != 0 && | |
this.scaleY != 0 && | |
hasContent | |
); | |
}; | |
/** | |
* Draws the Text into the specified context ignoring its visible, alpha, shadow, and transform. | |
* Returns true if the draw was handled (useful for overriding functionality). | |
* NOTE: This method is mainly for internal use, though it may be useful for advanced uses. | |
* @method draw | |
* @param {CanvasRenderingContext2D} ctx The canvas 2D context object to draw into. | |
* @param {Boolean} ignoreCache Indicates whether the draw operation should ignore any current cache. | |
* For example, used for drawing the cache (to prevent it from simply drawing an existing cache back | |
* into itself). | |
**/ | |
p.draw = function (ctx, ignoreCache) { | |
if (this.DisplayObject_draw(ctx, ignoreCache)) { | |
return true; | |
} | |
var col = this.color || "#000"; | |
if (this.outline) { | |
ctx.strokeStyle = col; | |
ctx.lineWidth = this.outline * 1; | |
} else { | |
ctx.fillStyle = col; | |
} | |
this._drawText(this._prepContext(ctx)); | |
return true; | |
}; | |
/** | |
* Returns the measured, untransformed width of the text without wrapping. Use getBounds for a more robust value. | |
* @method getMeasuredWidth | |
* @return {Number} The measured, untransformed width of the text. | |
**/ | |
p.getMeasuredWidth = function () { | |
return this._getMeasuredWidth(this.text); | |
}; | |
/** | |
* Returns an approximate line height of the text, ignoring the lineHeight property. This is based on the measured | |
* width of a "M" character multiplied by 1.2, which provides an approximate line height for most fonts. | |
* @method getMeasuredLineHeight | |
* @return {Number} an approximate line height of the text, ignoring the lineHeight property. This is | |
* based on the measured width of a "M" character multiplied by 1.2, which approximates em for most fonts. | |
**/ | |
p.getMeasuredLineHeight = function () { | |
return this._getMeasuredWidth("M") * 1.2; | |
}; | |
/** | |
* Returns the approximate height of multi-line text by multiplying the number of lines against either the | |
* <code>lineHeight</code> (if specified) or {{#crossLink "Text/getMeasuredLineHeight"}}{{/crossLink}}. Note that | |
* this operation requires the text flowing logic to run, which has an associated CPU cost. | |
* @method getMeasuredHeight | |
* @return {Number} The approximate height of the untransformed multi-line text. | |
**/ | |
p.getMeasuredHeight = function () { | |
return this._drawText(null, {}).height; | |
}; | |
/** | |
* Docced in superclass. | |
*/ | |
p.getBounds = function () { | |
var rect = this.DisplayObject_getBounds(); | |
if (rect) { | |
return rect; | |
} | |
if (this.text == null || this.text === "") { | |
return null; | |
} | |
var o = this._drawText(null, {}); | |
var w = this.maxWidth && this.maxWidth < o.width ? this.maxWidth : o.width; | |
var x = w * Text.H_OFFSETS[this.textAlign || "left"]; | |
var lineHeight = this.lineHeight || this.getMeasuredLineHeight(); | |
var y = lineHeight * Text.V_OFFSETS[this.textBaseline || "top"]; | |
return this._rectangle.setValues(x, y, w, o.height); | |
}; | |
/** | |
* Returns an object with width, height, and lines properties. The width and height are the visual width and height | |
* of the drawn text. The lines property contains an array of strings, one for | |
* each line of text that will be drawn, accounting for line breaks and wrapping. These strings have trailing | |
* whitespace removed. | |
* @method getMetrics | |
* @return {Object} An object with width, height, and lines properties. | |
**/ | |
p.getMetrics = function () { | |
var o = { lines: [] }; | |
o.lineHeight = this.lineHeight || this.getMeasuredLineHeight(); | |
o.vOffset = o.lineHeight * Text.V_OFFSETS[this.textBaseline || "top"]; | |
return this._drawText(null, o, o.lines); | |
}; | |
/** | |
* Returns a clone of the Text instance. | |
* @method clone | |
* @return {Text} a clone of the Text instance. | |
**/ | |
p.clone = function () { | |
return this._cloneProps(new Text(this.text, this.font, this.color)); | |
}; | |
/** | |
* Returns a string representation of this object. | |
* @method toString | |
* @return {String} a string representation of the instance. | |
**/ | |
p.toString = function () { | |
return ( | |
"[Text (text=" + | |
(this.text.length > 20 ? this.text.substr(0, 17) + "..." : this.text) + | |
")]" | |
); | |
}; | |
// private methods: | |
/** | |
* @method _cloneProps | |
* @param {Text} o | |
* @protected | |
* @return {Text} o | |
**/ | |
p._cloneProps = function (o) { | |
this.DisplayObject__cloneProps(o); | |
o.textAlign = this.textAlign; | |
o.textBaseline = this.textBaseline; | |
o.maxWidth = this.maxWidth; | |
o.outline = this.outline; | |
o.lineHeight = this.lineHeight; | |
o.lineWidth = this.lineWidth; | |
return o; | |
}; | |
/** | |
* @method _getWorkingContext | |
* @param {CanvasRenderingContext2D} ctx | |
* @return {CanvasRenderingContext2D} | |
* @protected | |
**/ | |
p._prepContext = function (ctx) { | |
ctx.font = this.font || "10px sans-serif"; | |
ctx.textAlign = this.textAlign || "left"; | |
ctx.textBaseline = this.textBaseline || "top"; | |
ctx.lineJoin = "miter"; | |
ctx.miterLimit = 2.5; | |
return ctx; | |
}; | |
/** | |
* Draws multiline text. | |
* @method _drawText | |
* @param {CanvasRenderingContext2D} ctx | |
* @param {Object} o | |
* @param {Array} lines | |
* @return {Object} | |
* @protected | |
**/ | |
p._drawText = function (ctx, o, lines) { | |
var paint = !!ctx; | |
if (!paint) { | |
ctx = Text._workingContext; | |
ctx.save(); | |
this._prepContext(ctx); | |
} | |
var lineHeight = this.lineHeight || this.getMeasuredLineHeight(); | |
var maxW = 0, | |
count = 0; | |
var hardLines = String(this.text).split(/(?:\r\n|\r|\n)/); | |
for (var i = 0, l = hardLines.length; i < l; i++) { | |
var str = hardLines[i]; | |
var w = null; | |
if ( | |
this.lineWidth != null && | |
(w = ctx.measureText(str).width) > this.lineWidth | |
) { | |
// text wrapping: | |
var words = str.split(/(\s)/); | |
str = words[0]; | |
w = ctx.measureText(str).width; | |
for (var j = 1, jl = words.length; j < jl; j += 2) { | |
// Line needs to wrap: | |
var wordW = ctx.measureText(words[j] + words[j + 1]).width; | |
if (w + wordW > this.lineWidth) { | |
if (paint) { | |
this._drawTextLine(ctx, str, count * lineHeight); | |
} | |
if (lines) { | |
lines.push(str); | |
} | |
if (w > maxW) { | |
maxW = w; | |
} | |
str = words[j + 1]; | |
w = ctx.measureText(str).width; | |
count++; | |
} else { | |
str += words[j] + words[j + 1]; | |
w += wordW; | |
} | |
} | |
} | |
if (paint) { | |
this._drawTextLine(ctx, str, count * lineHeight); | |
} | |
if (lines) { | |
lines.push(str); | |
} | |
if (o && w == null) { | |
w = ctx.measureText(str).width; | |
} | |
if (w > maxW) { | |
maxW = w; | |
} | |
count++; | |
} | |
if (o) { | |
o.width = maxW; | |
o.height = count * lineHeight; | |
} | |
if (!paint) { | |
ctx.restore(); | |
} | |
return o; | |
}; | |
/** | |
* @method _drawTextLine | |
* @param {CanvasRenderingContext2D} ctx | |
* @param {String} text | |
* @param {Number} y | |
* @protected | |
**/ | |
p._drawTextLine = function (ctx, text, y) { | |
// Chrome 17 will fail to draw the text if the last param is included but null, so we feed it a large value instead: | |
if (this.outline) { | |
ctx.strokeText(text, 0, y, this.maxWidth || 0xffff); | |
} else { | |
ctx.fillText(text, 0, y, this.maxWidth || 0xffff); | |
} | |
}; | |
/** | |
* @method _getMeasuredWidth | |
* @param {String} text | |
* @protected | |
**/ | |
p._getMeasuredWidth = function (text) { | |
var ctx = Text._workingContext; | |
ctx.save(); | |
var w = this._prepContext(ctx).measureText(text).width; | |
ctx.restore(); | |
return w; | |
}; | |
createjs.Text = createjs.promote(Text, "DisplayObject"); | |
})(); | |
//############################################################################## | |
// BitmapText.js | |
//############################################################################## | |
this.createjs = this.createjs || {}; | |
(function () { | |
"use strict"; | |
// constructor: | |
/** | |
* Displays text using bitmap glyphs defined in a sprite sheet. Multi-line text is supported using new line characters, | |
* but automatic wrapping is not supported. See the {{#crossLink "BitmapText/spriteSheet:property"}}{{/crossLink}} | |
* property for more information on defining glyphs. | |
* | |
* <strong>Important:</strong> While BitmapText extends Container, it is not designed to be used as one. | |
* As such, methods like addChild and removeChild are disabled. | |
* | |
* | |
* @class BitmapText | |
* @extends DisplayObject | |
* @param {String} [text=""] The text to display. | |
* @param {SpriteSheet} [spriteSheet=null] The spritesheet that defines the character glyphs. | |
* @constructor | |
**/ | |
function BitmapText(text, spriteSheet) { | |
this.Container_constructor(); | |
// public properties: | |
/** | |
* The text to display. | |
* @property text | |
* @type String | |
* @default "" | |
**/ | |
this.text = text || ""; | |
/** | |
* A SpriteSheet instance that defines the glyphs for this bitmap text. Each glyph/character | |
* should have a single frame animation defined in the sprite sheet named the same as | |
* corresponding character. For example, the following animation definition: | |
* | |
* "A": {frames: [0]} | |
* | |
* would indicate that the frame at index 0 of the spritesheet should be drawn for the "A" character. The short form | |
* is also acceptable: | |
* | |
* "A": 0 | |
* | |
* Note that if a character in the text is not found in the sprite sheet, it will also | |
* try to use the alternate case (upper or lower). | |
* | |
* See SpriteSheet for more information on defining sprite sheet data. | |
* @property spriteSheet | |
* @type SpriteSheet | |
* @default null | |
**/ | |
this.spriteSheet = spriteSheet; | |
/** | |
* The height of each line of text. If 0, then it will use a line height calculated | |
* by checking for the height of the "1", "T", or "L" character (in that order). If | |
* those characters are not defined, it will use the height of the first frame of the | |
* sprite sheet. | |
* @property lineHeight | |
* @type Number | |
* @default 0 | |
**/ | |
this.lineHeight = 0; | |
/** | |
* This spacing (in pixels) will be added after each character in the output. | |
* @property letterSpacing | |
* @type Number | |
* @default 0 | |
**/ | |
this.letterSpacing = 0; | |
/** | |
* If a space character is not defined in the sprite sheet, then empty pixels equal to | |
* spaceWidth will be inserted instead. If 0, then it will use a value calculated | |
* by checking for the width of the "1", "l", "E", or "A" character (in that order). If | |
* those characters are not defined, it will use the width of the first frame of the | |
* sprite sheet. | |
* @property spaceWidth | |
* @type Number | |
* @default 0 | |
**/ | |
this.spaceWidth = 0; | |
// private properties: | |
/** | |
* @property _oldProps | |
* @type Object | |
* @protected | |
**/ | |
this._oldProps = { | |
text: 0, | |
spriteSheet: 0, | |
lineHeight: 0, | |
letterSpacing: 0, | |
spaceWidth: 0, | |
}; | |
/** | |
* Used to track the object which this class attached listeners to, helps optimize listener attachment. | |
* @property _oldStage | |
* @type Stage | |
* @protected | |
*/ | |
this._oldStage = null; | |
/** | |
* The event listener proxy triggered drawing draw for special circumstances. | |
* @property _drawAction | |
* @type function | |
* @protected | |
*/ | |
this._drawAction = null; | |
} | |
var p = createjs.extend(BitmapText, createjs.Container); | |
// static properties: | |
/** | |
* BitmapText uses Sprite instances to draw text. To reduce the creation and destruction of instances (and thus garbage collection), it maintains | |
* an internal object pool of sprite instances to reuse. Increasing this value can cause more sprites to be | |
* retained, slightly increasing memory use, but reducing instantiation. | |
* @property maxPoolSize | |
* @type Number | |
* @static | |
* @default 100 | |
**/ | |
BitmapText.maxPoolSize = 100; | |
/** | |
* Sprite object pool. | |
* @type {Array} | |
* @static | |
* @private | |
*/ | |
BitmapText._spritePool = []; | |
// public methods: | |
/** | |
* Docced in superclass. | |
**/ | |
p.draw = function (ctx, ignoreCache) { | |
if (this.DisplayObject_draw(ctx, ignoreCache)) { | |
return; | |
} | |
this._updateState(); | |
this.Container_draw(ctx, ignoreCache); | |
}; | |
/** | |
* Docced in superclass. | |
**/ | |
p.getBounds = function () { | |
this._updateText(); | |
return this.Container_getBounds(); | |
}; | |
/** | |
* Returns true or false indicating whether the display object would be visible if drawn to a canvas. | |
* This does not account for whether it would be visible within the boundaries of the stage. | |
* NOTE: This method is mainly for internal use, though it may be useful for advanced uses. | |
* @method isVisible | |
* @return {Boolean} Boolean indicating whether the display object would be visible if drawn to a canvas | |
**/ | |
p.isVisible = function () { | |
var hasContent = | |
this.cacheCanvas || | |
(this.spriteSheet && this.spriteSheet.complete && this.text); | |
return !!( | |
this.visible && | |
this.alpha > 0 && | |
this.scaleX !== 0 && | |
this.scaleY !== 0 && | |
hasContent | |
); | |
}; | |
p.clone = function () { | |
return this._cloneProps(new BitmapText(this.text, this.spriteSheet)); | |
}; | |
/** | |
* <strong>Disabled in BitmapText.</strong> | |
* @method addChild | |
**/ | |
/** | |
* <strong>Disabled in BitmapText.</strong> | |
* @method addChildAt | |
**/ | |
/** | |
* <strong>Disabled in BitmapText.</strong> | |
* @method removeChild | |
**/ | |
/** | |
* <strong>Disabled in BitmapText.</strong> | |
* @method removeChildAt | |
**/ | |
/** | |
* <strong>Disabled in BitmapText.</strong> | |
* @method removeAllChildren | |
**/ | |
p.addChild = | |
p.addChildAt = | |
p.removeChild = | |
p.removeChildAt = | |
p.removeAllChildren = | |
function () {}; | |
// private methods: | |
/** | |
* Docced in superclass. | |
**/ | |
p._updateState = function () { | |
this._updateText(); | |
}; | |
/** | |
* @method _cloneProps | |
* @param {BitmapText} o | |
* @return {BitmapText} o | |
* @protected | |
**/ | |
p._cloneProps = function (o) { | |
this.Container__cloneProps(o); | |
o.lineHeight = this.lineHeight; | |
o.letterSpacing = this.letterSpacing; | |
o.spaceWidth = this.spaceWidth; | |
return o; | |
}; | |
/** | |
* @method _getFrameIndex | |
* @param {String} character | |
* @param {SpriteSheet} spriteSheet | |
* @return {Number} | |
* @protected | |
**/ | |
p._getFrameIndex = function (character, spriteSheet) { | |
var c, | |
o = spriteSheet.getAnimation(character); | |
if (!o) { | |
character != (c = character.toUpperCase()) || | |
character != (c = character.toLowerCase()) || | |
(c = null); | |
if (c) { | |
o = spriteSheet.getAnimation(c); | |
} | |
} | |
return o && o.frames[0]; | |
}; | |
/** | |
* @method _getFrame | |
* @param {String} character | |
* @param {SpriteSheet} spriteSheet | |
* @return {Object} | |
* @protected | |
**/ | |
p._getFrame = function (character, spriteSheet) { | |
var index = this._getFrameIndex(character, spriteSheet); | |
return index == null ? index : spriteSheet.getFrame(index); | |
}; | |
/** | |
* @method _getLineHeight | |
* @param {SpriteSheet} ss | |
* @return {Number} | |
* @protected | |
**/ | |
p._getLineHeight = function (ss) { | |
var frame = | |
this._getFrame("1", ss) || | |
this._getFrame("T", ss) || | |
this._getFrame("L", ss) || | |
ss.getFrame(0); | |
return frame ? frame.rect.height : 1; | |
}; | |
/** | |
* @method _getSpaceWidth | |
* @param {SpriteSheet} ss | |
* @return {Number} | |
* @protected | |
**/ | |
p._getSpaceWidth = function (ss) { | |
var frame = | |
this._getFrame("1", ss) || | |
this._getFrame("l", ss) || | |
this._getFrame("e", ss) || | |
this._getFrame("a", ss) || | |
ss.getFrame(0); | |
return frame ? frame.rect.width : 1; | |
}; | |
/** | |
* @method _updateText | |
* @protected | |
**/ | |
p._updateText = function () { | |
var x = 0, | |
y = 0, | |
o = this._oldProps, | |
change = false, | |
spaceW = this.spaceWidth, | |
lineH = this.lineHeight, | |
ss = this.spriteSheet; | |
var pool = BitmapText._spritePool, | |
kids = this.children, | |
childIndex = 0, | |
numKids = kids.length, | |
sprite; | |
for (var n in o) { | |
if (o[n] != this[n]) { | |
o[n] = this[n]; | |
change = true; | |
} | |
} | |
if (!change) { | |
return; | |
} | |
var hasSpace = !!this._getFrame(" ", ss); | |
if (!hasSpace && !spaceW) { | |
spaceW = this._getSpaceWidth(ss); | |
} | |
if (!lineH) { | |
lineH = this._getLineHeight(ss); | |
} | |
for (var i = 0, l = this.text.length; i < l; i++) { | |
var character = this.text.charAt(i); | |
if (character == " " && !hasSpace) { | |
x += spaceW; | |
continue; | |
} else if (character == "\n" || character == "\r") { | |
if (character == "\r" && this.text.charAt(i + 1) == "\n") { | |
i++; | |
} // crlf | |
x = 0; | |
y += lineH; | |
continue; | |
} | |
var index = this._getFrameIndex(character, ss); | |
if (index == null) { | |
continue; | |
} | |
if (childIndex < numKids) { | |
sprite = kids[childIndex]; | |
} else { | |
kids.push((sprite = pool.length ? pool.pop() : new createjs.Sprite())); | |
sprite.parent = this; | |
numKids++; | |
} | |
sprite.spriteSheet = ss; | |
sprite.gotoAndStop(index); | |
sprite.x = x; | |
sprite.y = y; | |
childIndex++; | |
x += sprite.getBounds().width + this.letterSpacing; | |
} | |
while (numKids > childIndex) { | |
// faster than removeChild. | |
pool.push((sprite = kids.pop())); | |
sprite.parent = null; | |
numKids--; | |
} | |
if (pool.length > BitmapText.maxPoolSize) { | |
pool.length = BitmapText.maxPoolSize; | |
} | |
}; | |
createjs.BitmapText = createjs.promote(BitmapText, "Container"); | |
})(); | |
//############################################################################## | |
// MovieClip.js | |
//############################################################################## | |
this.createjs = this.createjs || {}; | |
(function () { | |
"use strict"; | |
// constructor: | |
/** | |
* The MovieClip class associates a TweenJS Timeline with an EaselJS {{#crossLink "Container"}}{{/crossLink}}. It allows | |
* you to create objects which encapsulate timeline animations, state changes, and synched actions. The MovieClip | |
* class has been included in the EaselJS minified file since 0.7.0. | |
* | |
* Currently MovieClip only works properly if it is tick based (as opposed to time based) though some concessions have | |
* been made to support time-based timelines in the future. | |
* | |
* <h4>Example</h4> | |
* This example animates two shapes back and forth. The grey shape starts on the left, but we jump to a mid-point in | |
* the animation using {{#crossLink "MovieClip/gotoAndPlay"}}{{/crossLink}}. | |
* | |
* var stage = new createjs.Stage("canvas"); | |
* createjs.Ticker.addEventListener("tick", stage); | |
* | |
* var mc = new createjs.MovieClip({loop:-1, labels:{myLabel:20}}); | |
* stage.addChild(mc); | |
* | |
* var child1 = new createjs.Shape( | |
* new createjs.Graphics().beginFill("#999999") | |
* .drawCircle(30,30,30)); | |
* var child2 = new createjs.Shape( | |
* new createjs.Graphics().beginFill("#5a9cfb") | |
* .drawCircle(30,30,30)); | |
* | |
* mc.timeline.addTween( | |
* createjs.Tween.get(child1) | |
* .to({x:0}).to({x:60}, 50).to({x:0}, 50)); | |
* mc.timeline.addTween( | |
* createjs.Tween.get(child2) | |
* .to({x:60}).to({x:0}, 50).to({x:60}, 50)); | |
* | |
* mc.gotoAndPlay("start"); | |
* | |
* It is recommended to use <code>tween.to()</code> to animate and set properties (use no duration to have it set | |
* immediately), and the <code>tween.wait()</code> method to create delays between animations. Note that using the | |
* <code>tween.set()</code> method to affect properties will likely not provide the desired result. | |
* | |
* @class MovieClip | |
* @main MovieClip | |
* @param {Object} [props] The configuration properties to apply to this instance (ex. `{mode:MovieClip.SYNCHED}`). | |
* Supported props for the MovieClip are listed below. These props are set on the corresponding instance properties except where | |
* specified.<UL> | |
* <LI> `mode`</LI> | |
* <LI> `startPosition`</LI> | |
* <LI> `frameBounds`</LI> | |
* </UL> | |
* | |
* This object will also be passed into the Timeline instance associated with this MovieClip. See the documentation | |
* for Timeline for a list of supported props (ex. `paused`, `labels`, `loop`, `reversed`, etc.) | |
* @extends Container | |
* @constructor | |
**/ | |
function MovieClip(props) { | |
this.Container_constructor(); | |
!MovieClip.inited && MovieClip.init(); // static init | |
var mode, startPosition, loop, labels; | |
// handle old params (mode, startPosition, loop, labels): | |
// TODO: deprecated param handling: | |
if (props instanceof String || arguments.length > 1) { | |
mode = props; | |
startPosition = arguments[1]; | |
loop = arguments[2]; | |
labels = arguments[3]; | |
if (loop == null) { | |
loop = -1; | |
} | |
props = null; | |
} else if (props) { | |
mode = props.mode; | |
startPosition = props.startPosition; | |
loop = props.loop; | |
labels = props.labels; | |
} | |
if (!props) { | |
props = { labels: labels }; | |
} | |
// public properties: | |
/** | |
* Controls how this MovieClip advances its time. Must be one of 0 (INDEPENDENT), 1 (SINGLE_FRAME), or 2 (SYNCHED). | |
* See each constant for a description of the behaviour. | |
* @property mode | |
* @type String | |
* @default null | |
**/ | |
this.mode = mode || MovieClip.INDEPENDENT; | |
/** | |
* Specifies what the first frame to play in this movieclip, or the only frame to display if mode is SINGLE_FRAME. | |
* @property startPosition | |
* @type Number | |
* @default 0 | |
*/ | |
this.startPosition = startPosition || 0; | |
/** | |
* Specifies how many times this MovieClip should loop. A value of -1 indicates it should loop indefinitely. A value of | |
* 1 would cause it to loop once (ie. play a total of twice). | |
* @property loop | |
* @type Number | |
* @default -1 | |
*/ | |
this.loop = loop === true ? -1 : loop || 0; | |
/** | |
* The current frame of the movieclip. | |
* @property currentFrame | |
* @type Number | |
* @default 0 | |
* @readonly | |
*/ | |
this.currentFrame = 0; | |
/** | |
* If true, the MovieClip's position will not advance when ticked. | |
* @property paused | |
* @type Boolean | |
* @default false | |
*/ | |
this.paused = props.paused || false; | |
/** | |
* If true, actions in this MovieClip's tweens will be run when the playhead advances. | |
* @property actionsEnabled | |
* @type Boolean | |
* @default true | |
*/ | |
this.actionsEnabled = true; | |
/** | |
* If true, the MovieClip will automatically be reset to its first frame whenever the timeline adds | |
* it back onto the display list. This only applies to MovieClip instances with mode=INDEPENDENT. | |
* <br><br> | |
* For example, if you had a character animation with a "body" child MovieClip instance | |
* with different costumes on each frame, you could set body.autoReset = false, so that | |
* you can manually change the frame it is on, without worrying that it will be reset | |
* automatically. | |
* @property autoReset | |
* @type Boolean | |
* @default true | |
*/ | |
this.autoReset = true; | |
/** | |
* An array of bounds for each frame in the MovieClip. This is mainly intended for tool output. | |
* @property frameBounds | |
* @type Array | |
* @default null | |
*/ | |
this.frameBounds = this.frameBounds || props.frameBounds; // frameBounds are set on the prototype in Animate. | |
/** | |
* By default MovieClip instances advance one frame per tick. Specifying a framerate for the MovieClip | |
* will cause it to advance based on elapsed time between ticks as appropriate to maintain the target | |
* framerate. | |
* | |
* For example, if a MovieClip with a framerate of 10 is placed on a Stage being updated at 40fps, then the MovieClip will | |
* advance roughly one frame every 4 ticks. This will not be exact, because the time between each tick will | |
* vary slightly between frames. | |
* | |
* This feature is dependent on the tick event object (or an object with an appropriate "delta" property) being | |
* passed into {{#crossLink "Stage/update"}}{{/crossLink}}. | |
* @property framerate | |
* @type {Number} | |
* @default null | |
**/ | |
this.framerate = null; | |
// set up the needed props for Timeline: | |
props.useTicks = props.paused = true; | |
/** | |
* The TweenJS Timeline that is associated with this MovieClip. This is created automatically when the MovieClip | |
* instance is initialized. Animations are created by adding <a href="http://tweenjs.com">TweenJS</a> Tween | |
* instances to the timeline. | |
* | |
* <h4>Example</h4> | |
* | |
* var tween = createjs.Tween.get(target).to({x:0}).to({x:100}, 30); | |
* var mc = new createjs.MovieClip(); | |
* mc.timeline.addTween(tween); | |
* | |
* Elements can be added and removed from the timeline by toggling an "_off" property | |
* using the <code>tweenInstance.to()</code> method. Note that using <code>Tween.set</code> is not recommended to | |
* create MovieClip animations. The following example will toggle the target off on frame 0, and then back on for | |
* frame 1. You can use the "visible" property to achieve the same effect. | |
* | |
* var tween = createjs.Tween.get(target).to({_off:false}) | |
* .wait(1).to({_off:true}) | |
* .wait(1).to({_off:false}); | |
* | |
* @property timeline | |
* @type Timeline | |
* @default null | |
*/ | |
this.timeline = new createjs.Timeline(props); | |
// private properties: | |
/** | |
* @property _synchOffset | |
* @type Number | |
* @default 0 | |
* @private | |
*/ | |
this._synchOffset = 0; | |
/** | |
* @property _rawPosition | |
* @type Number | |
* @default -1 | |
* @private | |
*/ | |
this._rawPosition = -1; // TODO: evaluate using a ._reset Boolean prop instead of -1. | |
/** | |
* @property _bound_resolveState | |
* @type Function | |
* @private | |
*/ | |
this._bound_resolveState = this._resolveState.bind(this); | |
/** | |
* The time remaining from the previous tick, only applicable when .framerate is set. | |
* @property _t | |
* @type Number | |
* @private | |
*/ | |
this._t = 0; | |
/** | |
* List of display objects that are actively being managed by the MovieClip. | |
* @property _managed | |
* @type Object | |
* @private | |
*/ | |
this._managed = {}; | |
} | |
var p = createjs.extend(MovieClip, createjs.Container); | |
// constants: | |
/** | |
* The MovieClip will advance independently of its parent, even if its parent is paused. | |
* This is the default mode. | |
* @property INDEPENDENT | |
* @static | |
* @type String | |
* @default "independent" | |
* @readonly | |
**/ | |
MovieClip.INDEPENDENT = "independent"; | |
/** | |
* The MovieClip will only display a single frame (as determined by the startPosition property). | |
* @property SINGLE_FRAME | |
* @static | |
* @type String | |
* @default "single" | |
* @readonly | |
**/ | |
MovieClip.SINGLE_FRAME = "single"; | |
/** | |
* The MovieClip will be advanced only when its parent advances and will be synched to the position of | |
* the parent MovieClip. | |
* @property SYNCHED | |
* @static | |
* @type String | |
* @default "synched" | |
* @readonly | |
**/ | |
MovieClip.SYNCHED = "synched"; | |
// static properties: | |
MovieClip.inited = false; | |
// static methods: | |
MovieClip.init = function () { | |
if (MovieClip.inited) { | |
return; | |
} | |
// plugins introduce some overhead to Tween, so we only install this if an MC is instantiated. | |
MovieClipPlugin.install(); | |
MovieClip.inited = true; | |
}; | |
// getter / setters: | |
/** | |
* Use the {{#crossLink "MovieClip/labels:property"}}{{/crossLink}} property instead. | |
* @method _getLabels | |
* @protected | |
* @return {Array} | |
**/ | |
p._getLabels = function () { | |
return this.timeline.getLabels(); | |
}; | |
// MovieClip.getLabels is @deprecated. Remove for 1.1+ | |
p.getLabels = createjs.deprecate(p._getLabels, "MovieClip.getLabels"); | |
/** | |
* Use the {{#crossLink "MovieClip/currentLabel:property"}}{{/crossLink}} property instead. | |
* @method _getCurrentLabel | |
* @protected | |
* @return {String} | |
**/ | |
p._getCurrentLabel = function () { | |
return this.timeline.currentLabel; | |
}; | |
// MovieClip.getCurrentLabel is @deprecated. Remove for 1.1+ | |
p.getCurrentLabel = createjs.deprecate( | |
p._getCurrentLabel, | |
"MovieClip.getCurrentLabel" | |
); | |
/** | |
* Use the {{#crossLink "MovieClip/duration:property"}}{{/crossLink}} property instead. | |
* @method _getDuration | |
* @protected | |
* @return {Number} | |
**/ | |
p._getDuration = function () { | |
return this.timeline.duration; | |
}; | |
// MovieClip.getDuration is @deprecated. Remove for 1.1+ | |
p.getDuration = createjs.deprecate(p._getDuration, "MovieClip.getDuration"); | |
/** | |
* Returns an array of objects with label and position (aka frame) properties, sorted by position. | |
* @property labels | |
* @type {Array} | |
* @readonly | |
**/ | |
/** | |
* Returns the name of the label on or immediately before the current frame. | |
* @property currentLabel | |
* @type {String} | |
* @readonly | |
**/ | |
/** | |
* Returns the duration of this MovieClip in seconds or ticks. | |
* @property totalFrames | |
* @type {Number} | |
* @readonly | |
**/ | |
/** | |
* Returns the duration of this MovieClip in seconds or ticks. | |
* @property duration | |
* @type {Number} | |
* @readonly | |
**/ | |
try { | |
Object.defineProperties(p, { | |
labels: { get: p._getLabels }, | |
currentLabel: { get: p._getCurrentLabel }, | |
totalFrames: { get: p._getDuration }, | |
duration: { get: p._getDuration }, | |
// TODO: can we just proxy .currentFrame to tl.position as well? Ditto for .loop (or just remove entirely). | |
}); | |
} catch (e) {} | |
// public methods: | |
/** | |
* Constructor alias for backwards compatibility. This method will be removed in future versions. | |
* Subclasses should be updated to use {{#crossLink "Utility Methods/extends"}}{{/crossLink}}. | |
* @method initialize | |
* @deprecated in favour of `createjs.promote()` | |
**/ | |
p.initialize = MovieClip; // TODO: Deprecated. This is for backwards support of Adobe Flash/Animate | |
/** | |
* Returns true or false indicating whether the display object would be visible if drawn to a canvas. | |
* This does not account for whether it would be visible within the boundaries of the stage. | |
* NOTE: This method is mainly for internal use, though it may be useful for advanced uses. | |
* @method isVisible | |
* @return {Boolean} Boolean indicating whether the display object would be visible if drawn to a canvas | |
**/ | |
p.isVisible = function () { | |
// children are placed in draw, so we can't determine if we have content. | |
return !!( | |
this.visible && | |
this.alpha > 0 && | |
this.scaleX != 0 && | |
this.scaleY != 0 | |
); | |
}; | |
/** | |
* Draws the display object into the specified context ignoring its visible, alpha, shadow, and transform. | |
* Returns true if the draw was handled (useful for overriding functionality). | |
* NOTE: This method is mainly for internal use, though it may be useful for advanced uses. | |
* @method draw | |
* @param {CanvasRenderingContext2D} ctx The canvas 2D context object to draw into. | |
* @param {Boolean} ignoreCache Indicates whether the draw operation should ignore any current cache. | |
* For example, used for drawing the cache (to prevent it from simply drawing an existing cache back | |
* into itself). | |
**/ | |
p.draw = function (ctx, ignoreCache) { | |
// draw to cache first: | |
if (this.DisplayObject_draw(ctx, ignoreCache)) { | |
return true; | |
} | |
this._updateState(); | |
this.Container_draw(ctx, ignoreCache); | |
return true; | |
}; | |
/** | |
* Sets paused to false. | |
* @method play | |
**/ | |
p.play = function () { | |
this.paused = false; | |
}; | |
/** | |
* Sets paused to true. | |
* @method stop | |
**/ | |
p.stop = function () { | |
this.paused = true; | |
}; | |
/** | |
* Advances this movie clip to the specified position or label and sets paused to false. | |
* @method gotoAndPlay | |
* @param {String|Number} positionOrLabel The animation name or frame number to go to. | |
**/ | |
p.gotoAndPlay = function (positionOrLabel) { | |
this.paused = false; | |
this._goto(positionOrLabel); | |
}; | |
/** | |
* Advances this movie clip to the specified position or label and sets paused to true. | |
* @method gotoAndStop | |
* @param {String|Number} positionOrLabel The animation or frame name to go to. | |
**/ | |
p.gotoAndStop = function (positionOrLabel) { | |
this.paused = true; | |
this._goto(positionOrLabel); | |
}; | |
/** | |
* Advances the playhead. This occurs automatically each tick by default. | |
* @param [time] {Number} The amount of time in ms to advance by. Only applicable if framerate is set. | |
* @method advance | |
*/ | |
p.advance = function (time) { | |
var independent = MovieClip.INDEPENDENT; | |
if (this.mode !== independent) { | |
return; | |
} // update happens in draw for synched clips | |
// if this MC doesn't have a framerate, hunt ancestors for one: | |
var o = this, | |
fps = o.framerate; | |
while ((o = o.parent) && fps === null) { | |
if (o.mode === independent) { | |
fps = o._framerate; | |
} | |
} | |
this._framerate = fps; | |
if (this.paused) { | |
return; | |
} | |
// calculate how many frames to advance: | |
var t = | |
fps !== null && fps !== -1 && time !== null | |
? time / (1000 / fps) + this._t | |
: 1; | |
var frames = t | 0; | |
this._t = t - frames; // leftover time, save to add to next advance. | |
while (frames--) { | |
this._updateTimeline(this._rawPosition + 1, false); | |
} | |
}; | |
/** | |
* MovieClip instances cannot be cloned. | |
* @method clone | |
**/ | |
p.clone = function () { | |
// TODO: add support for this? Need to clone the Timeline & retarget tweens - pretty complex. | |
throw "MovieClip cannot be cloned."; | |
}; | |
/** | |
* Returns a string representation of this object. | |
* @method toString | |
* @return {String} a string representation of the instance. | |
**/ | |
p.toString = function () { | |
return "[MovieClip (name=" + this.name + ")]"; | |
}; | |
// private methods: | |
/** | |
* Docced in superclass. | |
**/ | |
p._updateState = function () { | |
if (this._rawPosition === -1 || this.mode !== MovieClip.INDEPENDENT) { | |
this._updateTimeline(-1); | |
} | |
}; | |
/** | |
* @method _tick | |
* @param {Object} evtObj An event object that will be dispatched to all tick listeners. This object is reused between dispatchers to reduce construction & GC costs. | |
* function. | |
* @protected | |
**/ | |
p._tick = function (evtObj) { | |
this.advance(evtObj && evtObj.delta); | |
this.Container__tick(evtObj); | |
}; | |
/** | |
* @method _goto | |
* @param {String|Number} positionOrLabel The animation name or frame number to go to. | |
* @protected | |
**/ | |
p._goto = function (positionOrLabel) { | |
var pos = this.timeline.resolve(positionOrLabel); | |
if (pos == null) { | |
return; | |
} | |
this._t = 0; | |
this._updateTimeline(pos, true); | |
}; | |
/** | |
* @method _reset | |
* @private | |
**/ | |
p._reset = function () { | |
this._rawPosition = -1; | |
this._t = this.currentFrame = 0; | |
this.paused = false; | |
}; | |
/** | |
* @method _updateTimeline | |
* @param {Boolean} jump Indicates whether this update is due to jumping (via gotoAndXX) to a new position. | |
* @protected | |
**/ | |
p._updateTimeline = function (rawPosition, jump) { | |
var synced = this.mode !== MovieClip.INDEPENDENT, | |
tl = this.timeline; | |
if (synced) { | |
rawPosition = | |
this.startPosition + | |
(this.mode === MovieClip.SINGLE_FRAME ? 0 : this._synchOffset); | |
} | |
if (rawPosition < 0) { | |
rawPosition = 0; | |
} | |
if (this._rawPosition === rawPosition && !synced) { | |
return; | |
} | |
this._rawPosition = rawPosition; | |
// update timeline position, ignoring actions if this is a graphic. | |
tl.loop = this.loop; // TODO: should we maintain this on MovieClip, or just have it on timeline? | |
tl.setPosition( | |
rawPosition, | |
synced || !this.actionsEnabled, | |
jump, | |
this._bound_resolveState | |
); | |
}; | |
/** | |
* Renders position 0 without running actions or updating _rawPosition. | |
* Primarily used by Animate CC to build out the first frame in the constructor of MC symbols. | |
* NOTE: not tested when run after the MC advances past the first frame. | |
* @method _renderFirstFrame | |
* @protected | |
**/ | |
p._renderFirstFrame = function () { | |
var tl = this.timeline, | |
pos = tl.rawPosition; | |
tl.setPosition(0, true, true, this._bound_resolveState); | |
tl.rawPosition = pos; | |
}; | |
/** | |
* Runs via a callback after timeline property updates and before actions. | |
* @method _resolveState | |
* @protected | |
**/ | |
p._resolveState = function () { | |
var tl = this.timeline; | |
this.currentFrame = tl.position; | |
for (var n in this._managed) { | |
this._managed[n] = 1; | |
} | |
var tweens = tl.tweens; | |
for (var i = 0, l = tweens.length; i < l; i++) { | |
var tween = tweens[i], | |
target = tween.target; | |
if (target === this || tween.passive) { | |
continue; | |
} // TODO: this assumes the actions tween from Animate has `this` as the target. There's likely a better approach. | |
var offset = tween._stepPosition; | |
if (target instanceof createjs.DisplayObject) { | |
// motion tween. | |
this._addManagedChild(target, offset); | |
} else { | |
// state tween. | |
this._setState(target.state, offset); | |
} | |
} | |
var kids = this.children; | |
for (i = kids.length - 1; i >= 0; i--) { | |
var id = kids[i].id; | |
if (this._managed[id] === 1) { | |
this.removeChildAt(i); | |
delete this._managed[id]; | |
} | |
} | |
}; | |
/** | |
* @method _setState | |
* @param {Array} state | |
* @param {Number} offset | |
* @protected | |
**/ | |
p._setState = function (state, offset) { | |
if (!state) { | |
return; | |
} | |
for (var i = state.length - 1; i >= 0; i--) { | |
var o = state[i]; | |
var target = o.t; | |
var props = o.p; | |
for (var n in props) { | |
target[n] = props[n]; | |
} | |
this._addManagedChild(target, offset); | |
} | |
}; | |
/** | |
* Adds a child to the timeline, and sets it up as a managed child. | |
* @method _addManagedChild | |
* @param {MovieClip} child The child MovieClip to manage | |
* @param {Number} offset | |
* @private | |
**/ | |
p._addManagedChild = function (child, offset) { | |
if (child._off) { | |
return; | |
} | |
this.addChildAt(child, 0); | |
if (child instanceof MovieClip) { | |
child._synchOffset = offset; | |
// TODO: this does not precisely match Adobe Flash/Animate, which loses track of the clip if it is renamed or removed from the timeline, which causes it to reset. | |
// TODO: should also reset when MovieClip loops, though that will be a bit tricky to detect. | |
if ( | |
child.mode === MovieClip.INDEPENDENT && | |
child.autoReset && | |
!this._managed[child.id] | |
) { | |
child._reset(); | |
} | |
} | |
this._managed[child.id] = 2; | |
}; | |
/** | |
* @method _getBounds | |
* @param {Matrix2D} matrix | |
* @param {Boolean} ignoreTransform | |
* @return {Rectangle} | |
* @protected | |
**/ | |
p._getBounds = function (matrix, ignoreTransform) { | |
var bounds = this.DisplayObject_getBounds(); | |
if (!bounds) { | |
if (this.frameBounds) { | |
bounds = this._rectangle.copy(this.frameBounds[this.currentFrame]); | |
} | |
} | |
if (bounds) { | |
return this._transformBounds(bounds, matrix, ignoreTransform); | |
} | |
return this.Container__getBounds(matrix, ignoreTransform); | |
}; | |
createjs.MovieClip = createjs.promote(MovieClip, "Container"); | |
// MovieClipPlugin for TweenJS: | |
/** | |
* This plugin works with <a href="http://tweenjs.com" target="_blank">TweenJS</a> to prevent the startPosition | |
* property from tweening. | |
* @private | |
* @class MovieClipPlugin | |
* @constructor | |
**/ | |
function MovieClipPlugin() { | |
throw "MovieClipPlugin cannot be instantiated."; | |
} | |
/** | |
* @property priority | |
* @type {Number} | |
* @static | |
* @readonly | |
**/ | |
MovieClipPlugin.priority = 100; // very high priority, should run first | |
/** | |
* @property ID | |
* @type {String} | |
* @static | |
* @readonly | |
**/ | |
MovieClipPlugin.ID = "MovieClip"; | |
/** | |
* @method install | |
* @static | |
**/ | |
MovieClipPlugin.install = function () { | |
createjs.Tween._installPlugin(MovieClipPlugin); | |
}; | |
/** | |
* @method init | |
* @param {Tween} tween | |
* @param {String} prop | |
* @param {*} value | |
* @static | |
**/ | |
MovieClipPlugin.init = function (tween, prop, value) { | |
if (prop === "startPosition" && tween.target instanceof MovieClip) { | |
tween._addPlugin(MovieClipPlugin); | |
} | |
}; | |
/** | |
* @method step | |
* @param {Tween} tween | |
* @param {TweenStep} step | |
* @param {Object} props | |
* @static | |
**/ | |
MovieClipPlugin.step = function (tween, step, props) {}; | |
/** | |
* @method change | |
* @param {Tween} tween | |
* @param {TweenStep} step | |
* @param {*} value | |
* @param {Number} ratio | |
* @param {Object} end | |
* @return {*} | |
* @static | |
*/ | |
MovieClipPlugin.change = function (tween, step, prop, value, ratio, end) { | |
if (prop === "startPosition") { | |
return ratio === 1 ? step.props[prop] : step.prev.props[prop]; | |
} | |
}; | |
})(); | |
//############################################################################## | |
// SpriteSheetUtils.js | |
//############################################################################## | |
this.createjs = this.createjs || {}; | |
(function () { | |
"use strict"; | |
// constructor: | |
/** | |
* The SpriteSheetUtils class is a collection of static methods for working with {{#crossLink "SpriteSheet"}}{{/crossLink}}s. | |
* A sprite sheet is a series of images (usually animation frames) combined into a single image on a regular grid. For | |
* example, an animation consisting of 8 100x100 images could be combined into a 400x200 sprite sheet (4 frames across | |
* by 2 high). The SpriteSheetUtils class uses a static interface and should not be instantiated. | |
* @class SpriteSheetUtils | |
* @static | |
**/ | |
function SpriteSheetUtils() { | |
throw "SpriteSheetUtils cannot be instantiated"; | |
} | |
// private static properties: | |
/** | |
* @property _workingCanvas | |
* @static | |
* @type HTMLCanvasElement | Object | |
* @protected | |
*/ | |
/** | |
* @property _workingContext | |
* @static | |
* @type CanvasRenderingContext2D | |
* @protected | |
*/ | |
var canvas = createjs.createCanvas | |
? createjs.createCanvas() | |
: document.createElement("canvas"); | |
if (canvas.getContext) { | |
SpriteSheetUtils._workingCanvas = canvas; | |
SpriteSheetUtils._workingContext = canvas.getContext("2d"); | |
canvas.width = canvas.height = 1; | |
} | |
// public static methods: | |
/** | |
* Returns a single frame of the specified sprite sheet as a new PNG image. An example of when this may be useful is | |
* to use a spritesheet frame as the source for a bitmap fill. | |
* | |
* <strong>WARNING:</strong> In almost all cases it is better to display a single frame using a {{#crossLink "Sprite"}}{{/crossLink}} | |
* with a {{#crossLink "Sprite/gotoAndStop"}}{{/crossLink}} call than it is to slice out a frame using this | |
* method and display it with a Bitmap instance. You can also crop an image using the {{#crossLink "Bitmap/sourceRect"}}{{/crossLink}} | |
* property of {{#crossLink "Bitmap"}}{{/crossLink}}. | |
* | |
* The extractFrame method may cause cross-domain warnings since it accesses pixels directly on the canvas. | |
* @method extractFrame | |
* @static | |
* @param {SpriteSheet} spriteSheet The SpriteSheet instance to extract a frame from. | |
* @param {Number|String} frameOrAnimation The frame number or animation name to extract. If an animation | |
* name is specified, only the first frame of the animation will be extracted. | |
* @return {HTMLImageElement} a single frame of the specified sprite sheet as a new PNG image. | |
*/ | |
SpriteSheetUtils.extractFrame = function (spriteSheet, frameOrAnimation) { | |
if (isNaN(frameOrAnimation)) { | |
frameOrAnimation = spriteSheet.getAnimation(frameOrAnimation).frames[0]; | |
} | |
var data = spriteSheet.getFrame(frameOrAnimation); | |
if (!data) { | |
return null; | |
} | |
var r = data.rect; | |
var canvas = SpriteSheetUtils._workingCanvas; | |
canvas.width = r.width; | |
canvas.height = r.height; | |
SpriteSheetUtils._workingContext.drawImage( | |
data.image, | |
r.x, | |
r.y, | |
r.width, | |
r.height, | |
0, | |
0, | |
r.width, | |
r.height | |
); | |
var img = document.createElement("img"); | |
img.src = canvas.toDataURL("image/png"); | |
return img; | |
}; | |
// SpriteSheetUtils.addFlippedFrames is @deprecated. Remove for 1.1+ | |
SpriteSheetUtils.addFlippedFrames = createjs.deprecate( | |
null, | |
"SpriteSheetUtils.addFlippedFrames" | |
); | |
// SpriteSheetUtils.addFlippedFrames is @deprecated. Remove for 1.1+ | |
SpriteSheetUtils.mergeAlpha = createjs.deprecate( | |
null, | |
"SpriteSheetUtils.mergeAlpha" | |
); | |
// private static methods: | |
SpriteSheetUtils._flip = function (spriteSheet, count, h, v) { | |
var imgs = spriteSheet._images; | |
var canvas = SpriteSheetUtils._workingCanvas; | |
var ctx = SpriteSheetUtils._workingContext; | |
var il = imgs.length / count; | |
for (var i = 0; i < il; i++) { | |
var src = imgs[i]; | |
src.__tmp = i; // a bit hacky, but faster than doing indexOf below. | |
ctx.setTransform(1, 0, 0, 1, 0, 0); | |
ctx.clearRect(0, 0, canvas.width + 1, canvas.height + 1); | |
canvas.width = src.width; | |
canvas.height = src.height; | |
ctx.setTransform( | |
h ? -1 : 1, | |
0, | |
0, | |
v ? -1 : 1, | |
h ? src.width : 0, | |
v ? src.height : 0 | |
); | |
ctx.drawImage(src, 0, 0); | |
var img = document.createElement("img"); | |
img.src = canvas.toDataURL("image/png"); | |
// work around a strange bug in Safari: | |
img.width = src.width || src.naturalWidth; | |
img.height = src.height || src.naturalHeight; | |
imgs.push(img); | |
} | |
var frames = spriteSheet._frames; | |
var fl = frames.length / count; | |
for (i = 0; i < fl; i++) { | |
src = frames[i]; | |
var rect = src.rect.clone(); | |
img = imgs[src.image.__tmp + il * count]; | |
var frame = { image: img, rect: rect, regX: src.regX, regY: src.regY }; | |
if (h) { | |
rect.x = (img.width || img.naturalWidth) - rect.x - rect.width; // update rect | |
frame.regX = rect.width - src.regX; // update registration point | |
} | |
if (v) { | |
rect.y = (img.height || img.naturalHeight) - rect.y - rect.height; // update rect | |
frame.regY = rect.height - src.regY; // update registration point | |
} | |
frames.push(frame); | |
} | |
var sfx = "_" + (h ? "h" : "") + (v ? "v" : ""); | |
var names = spriteSheet._animations; | |
var data = spriteSheet._data; | |
var al = names.length / count; | |
for (i = 0; i < al; i++) { | |
var name = names[i]; | |
src = data[name]; | |
var anim = { | |
name: name + sfx, | |
speed: src.speed, | |
next: src.next, | |
frames: [], | |
}; | |
if (src.next) { | |
anim.next += sfx; | |
} | |
frames = src.frames; | |
for (var j = 0, l = frames.length; j < l; j++) { | |
anim.frames.push(frames[j] + fl * count); | |
} | |
data[anim.name] = anim; | |
names.push(anim.name); | |
} | |
}; | |
createjs.SpriteSheetUtils = SpriteSheetUtils; | |
})(); | |
//############################################################################## | |
// SpriteSheetBuilder.js | |
//############################################################################## | |
this.createjs = this.createjs || {}; | |
(function () { | |
"use strict"; | |
// constructor: | |
/** | |
* The SpriteSheetBuilder allows you to generate {{#crossLink "SpriteSheet"}}{{/crossLink}} instances at run time | |
* from any display object. This can allow you to maintain your assets as vector graphics (for low file size), and | |
* render them at run time as SpriteSheets for better performance. | |
* | |
* SpriteSheets can be built either synchronously, or asynchronously, so that large SpriteSheets can be generated | |
* without locking the UI. | |
* | |
* Note that the "images" used in the generated SpriteSheet are actually canvas elements, and that they will be | |
* sized to the nearest power of 2 up to the value of {{#crossLink "SpriteSheetBuilder/maxWidth:property"}}{{/crossLink}} | |
* or {{#crossLink "SpriteSheetBuilder/maxHeight:property"}}{{/crossLink}}. | |
* @class SpriteSheetBuilder | |
* @param {Number} [framerate=0] The {{#crossLink "SpriteSheet/framerate:property"}}{{/crossLink}} of | |
* {{#crossLink "SpriteSheet"}}{{/crossLink}} instances that are created. | |
* @extends EventDispatcher | |
* @constructor | |
**/ | |
function SpriteSheetBuilder(framerate) { | |
this.EventDispatcher_constructor(); | |
// public properties: | |
/** | |
* The maximum width for the images (not individual frames) in the generated SpriteSheet. It is recommended to | |
* use a power of 2 for this value (ex. 1024, 2048, 4096). If the frames cannot all fit within the max | |
* dimensions, then additional images will be created as needed. | |
* @property maxWidth | |
* @type Number | |
* @default 2048 | |
*/ | |
this.maxWidth = 2048; | |
/** | |
* The maximum height for the images (not individual frames) in the generated SpriteSheet. It is recommended to | |
* use a power of 2 for this value (ex. 1024, 2048, 4096). If the frames cannot all fit within the max | |
* dimensions, then additional images will be created as needed. | |
* @property maxHeight | |
* @type Number | |
* @default 2048 | |
**/ | |
this.maxHeight = 2048; | |
/** | |
* The SpriteSheet that was generated. This will be null before a build is completed successfully. | |
* @property spriteSheet | |
* @type SpriteSheet | |
**/ | |
this.spriteSheet = null; | |
/** | |
* The scale to apply when drawing all frames to the SpriteSheet. This is multiplied against any scale specified | |
* in the addFrame call. This can be used, for example, to generate a SpriteSheet at run time that is tailored | |
* to the a specific device resolution (ex. tablet vs mobile). | |
* @property scale | |
* @type Number | |
* @default 1 | |
**/ | |
this.scale = 1; | |
/** | |
* The padding to use between frames. This is helpful to preserve antialiasing on drawn vector content. | |
* @property padding | |
* @type Number | |
* @default 1 | |
**/ | |
this.padding = 1; | |
/** | |
* A number from 0.01 to 0.99 that indicates what percentage of time the builder can use. This can be | |
* thought of as the number of seconds per second the builder will use. For example, with a timeSlice value of 0.3, | |
* the builder will run 20 times per second, using approximately 15ms per build (30% of available time, or 0.3s per second). | |
* Defaults to 0.3. | |
* @property timeSlice | |
* @type Number | |
* @default 0.3 | |
**/ | |
this.timeSlice = 0.3; | |
/** | |
* A value between 0 and 1 that indicates the progress of a build, or -1 if a build has not | |
* been initiated. | |
* @property progress | |
* @type Number | |
* @default -1 | |
* @readonly | |
*/ | |
this.progress = -1; | |
/** | |
* A {{#crossLink "SpriteSheet/framerate:property"}}{{/crossLink}} value that will be passed to new {{#crossLink "SpriteSheet"}}{{/crossLink}} instances that are | |
* created. If no framerate is specified (or it is 0), then SpriteSheets will use the {{#crossLink "Ticker"}}{{/crossLink}} | |
* framerate. | |
* @property framerate | |
* @type Number | |
* @default 0 | |
*/ | |
this.framerate = framerate || 0; | |
// private properties: | |
/** | |
* @property _frames | |
* @protected | |
* @type Array | |
**/ | |
this._frames = []; | |
/** | |
* @property _animations | |
* @protected | |
* @type Array | |
**/ | |
this._animations = {}; | |
/** | |
* @property _data | |
* @protected | |
* @type Array | |
**/ | |
this._data = null; | |
/** | |
* @property _nextFrameIndex | |
* @protected | |
* @type Number | |
**/ | |
this._nextFrameIndex = 0; | |
/** | |
* @property _index | |
* @protected | |
* @type Number | |
**/ | |
this._index = 0; | |
/** | |
* @property _timerID | |
* @protected | |
* @type Number | |
**/ | |
this._timerID = null; | |
/** | |
* @property _scale | |
* @protected | |
* @type Number | |
**/ | |
this._scale = 1; | |
} | |
var p = createjs.extend(SpriteSheetBuilder, createjs.EventDispatcher); | |
// constants: | |
SpriteSheetBuilder.ERR_DIMENSIONS = | |
"frame dimensions exceed max spritesheet dimensions"; | |
SpriteSheetBuilder.ERR_RUNNING = "a build is already running"; | |
// events: | |
/** | |
* Dispatched when a build completes. | |
* @event complete | |
* @param {Object} target The object that dispatched the event. | |
* @param {String} type The event type. | |
* @since 0.6.0 | |
*/ | |
/** | |
* Dispatched when an asynchronous build has progress. | |
* @event progress | |
* @param {Object} target The object that dispatched the event. | |
* @param {String} type The event type. | |
* @param {Number} progress The current progress value (0-1). | |
* @since 0.6.0 | |
*/ | |
// public methods: | |
/** | |
* Adds a frame to the {{#crossLink "SpriteSheet"}}{{/crossLink}}. Note that the frame will not be drawn until you | |
* call {{#crossLink "SpriteSheetBuilder/build"}}{{/crossLink}} method. The optional setup params allow you to have | |
* a function run immediately before the draw occurs. For example, this allows you to add a single source multiple | |
* times, but manipulate it or its children to change it to generate different frames. | |
* | |
* Note that the source's transformations (x, y, scale, rotate, alpha) will be ignored, except for regX/Y. To apply | |
* transforms to a source object and have them captured in the SpriteSheet, simply place it into a {{#crossLink "Container"}}{{/crossLink}} | |
* and pass in the Container as the source. | |
* @method addFrame | |
* @param {DisplayObject} source The source {{#crossLink "DisplayObject"}}{{/crossLink}} to draw as the frame. | |
* @param {Rectangle} [sourceRect] A {{#crossLink "Rectangle"}}{{/crossLink}} defining the portion of the | |
* source to draw to the frame. If not specified, it will look for a `getBounds` method, bounds property, or | |
* `nominalBounds` property on the source to use. If one is not found, the frame will be skipped. | |
* @param {Number} [scale=1] Optional. The scale to draw this frame at. Default is 1. | |
* @param {Function} [setupFunction] A function to call immediately before drawing this frame. It will be called with two parameters: the source, and setupData. | |
* @param {Object} [setupData] Arbitrary setup data to pass to setupFunction as the second parameter. | |
* @return {Number} The index of the frame that was just added, or null if a sourceRect could not be determined. | |
**/ | |
p.addFrame = function (source, sourceRect, scale, setupFunction, setupData) { | |
if (this._data) { | |
throw SpriteSheetBuilder.ERR_RUNNING; | |
} | |
var rect = sourceRect || source.bounds || source.nominalBounds; | |
if (!rect && source.getBounds) { | |
rect = source.getBounds(); | |
} | |
if (!rect) { | |
return null; | |
} | |
scale = scale || 1; | |
return ( | |
this._frames.push({ | |
source: source, | |
sourceRect: rect, | |
scale: scale, | |
funct: setupFunction, | |
data: setupData, | |
index: this._frames.length, | |
height: rect.height * scale, | |
}) - 1 | |
); | |
}; | |
/** | |
* Adds an animation that will be included in the created {{#crossLink "SpriteSheet"}}{{/crossLink}}. | |
* @method addAnimation | |
* @param {String} name The name for the animation. | |
* @param {Array} frames An array of frame indexes that comprise the animation. Ex. [3,6,5] would describe an animation | |
* that played frame indexes 3, 6, and 5 in that order. | |
* @param {String} [next] Specifies the name of the animation to continue to after this animation ends. You can | |
* also pass false to have the animation stop when it ends. By default it will loop to the start of the same animation. | |
* @param {Number} [speed] Specifies a frame advance speed for this animation. For example, a value of 0.5 would | |
* cause the animation to advance every second tick. Note that earlier versions used `frequency` instead, which had | |
* the opposite effect. | |
**/ | |
p.addAnimation = function (name, frames, next, speed) { | |
if (this._data) { | |
throw SpriteSheetBuilder.ERR_RUNNING; | |
} | |
this._animations[name] = { frames: frames, next: next, speed: speed }; | |
}; | |
/** | |
* This will take a {{#crossLink "MovieClip"}}{{/crossLink}} instance, and add its frames and labels to this | |
* builder. Labels will be added as an animation running from the label index to the next label. For example, if | |
* there is a label named "foo" at frame 0 and a label named "bar" at frame 10, in a MovieClip with 15 frames, it | |
* will add an animation named "foo" that runs from frame index 0 to 9, and an animation named "bar" that runs from | |
* frame index 10 to 14. | |
* | |
* Note that this will iterate through the full MovieClip with {{#crossLink "MovieClip/actionsEnabled:property"}}{{/crossLink}} | |
* set to `false`, ending on the last frame. | |
* @method addMovieClip | |
* @param {MovieClip} source The source MovieClip instance to add to the SpriteSheet. | |
* @param {Rectangle} [sourceRect] A {{#crossLink "Rectangle"}}{{/crossLink}} defining the portion of the source to | |
* draw to the frame. If not specified, it will look for a {{#crossLink "DisplayObject/getBounds"}}{{/crossLink}} | |
* method, `frameBounds` Array, `bounds` property, or `nominalBounds` property on the source to use. If one is not | |
* found, the MovieClip will be skipped. | |
* @param {Number} [scale=1] The scale to draw the movie clip at. | |
* @param {Function} [setupFunction] A function to call immediately before drawing each frame. It will be called | |
* with three parameters: the source, setupData, and the frame index. | |
* @param {Object} [setupData] Arbitrary setup data to pass to setupFunction as the second parameter. | |
* @param {Function} [labelFunction] This method will be called for each MovieClip label that is added with four | |
* parameters: the label name, the source MovieClip instance, the starting frame index (in the movieclip timeline) | |
* and the end index. It must return a new name for the label/animation, or `false` to exclude the label. | |
**/ | |
p.addMovieClip = function ( | |
source, | |
sourceRect, | |
scale, | |
setupFunction, | |
setupData, | |
labelFunction | |
) { | |
if (this._data) { | |
throw SpriteSheetBuilder.ERR_RUNNING; | |
} | |
var rects = source.frameBounds; | |
var rect = sourceRect || source.bounds || source.nominalBounds; | |
if (!rect && source.getBounds) { | |
rect = source.getBounds(); | |
} | |
if (!rect && !rects) { | |
return; | |
} | |
var i, | |
l, | |
baseFrameIndex = this._frames.length; | |
var duration = source.timeline.duration; | |
for (i = 0; i < duration; i++) { | |
var r = rects && rects[i] ? rects[i] : rect; | |
this.addFrame(source, r, scale, this._setupMovieClipFrame, { | |
i: i, | |
f: setupFunction, | |
d: setupData, | |
}); | |
} | |
var labels = source.timeline._labels; | |
var lbls = []; | |
for (var n in labels) { | |
lbls.push({ index: labels[n], label: n }); | |
} | |
if (lbls.length) { | |
lbls.sort(function (a, b) { | |
return a.index - b.index; | |
}); | |
for (i = 0, l = lbls.length; i < l; i++) { | |
var label = lbls[i].label; | |
var start = baseFrameIndex + lbls[i].index; | |
var end = baseFrameIndex + (i == l - 1 ? duration : lbls[i + 1].index); | |
var frames = []; | |
for (var j = start; j < end; j++) { | |
frames.push(j); | |
} | |
if (labelFunction) { | |
label = labelFunction(label, source, start, end); | |
if (!label) { | |
continue; | |
} | |
} | |
this.addAnimation(label, frames, true); // for now, this loops all animations. | |
} | |
} | |
}; | |
/** | |
* Builds a {{#crossLink "SpriteSheet"}}{{/crossLink}} instance based on the current frames. | |
* @method build | |
* @return {SpriteSheet} The created SpriteSheet instance, or null if a build is already running or an error | |
* occurred. | |
**/ | |
p.build = function () { | |
if (this._data) { | |
throw SpriteSheetBuilder.ERR_RUNNING; | |
} | |
this._startBuild(); | |
while (this._drawNext()) {} | |
this._endBuild(); | |
return this.spriteSheet; | |
}; | |
/** | |
* Asynchronously builds a {{#crossLink "SpriteSheet"}}{{/crossLink}} instance based on the current frames. It will | |
* run 20 times per second, using an amount of time defined by `timeSlice`. When it is complete it will call the | |
* specified callback. | |
* @method buildAsync | |
* @param {Number} [timeSlice] Sets the timeSlice property on this instance. | |
**/ | |
p.buildAsync = function (timeSlice) { | |
if (this._data) { | |
throw SpriteSheetBuilder.ERR_RUNNING; | |
} | |
this.timeSlice = timeSlice; | |
this._startBuild(); | |
var _this = this; | |
this._timerID = setTimeout(function () { | |
_this._run(); | |
}, 50 - Math.max(0.01, Math.min(0.99, this.timeSlice || 0.3)) * 50); | |
}; | |
/** | |
* Stops the current asynchronous build. | |
* @method stopAsync | |
**/ | |
p.stopAsync = function () { | |
clearTimeout(this._timerID); | |
this._data = null; | |
}; | |
/** | |
* SpriteSheetBuilder instances cannot be cloned. | |
* @method clone | |
**/ | |
p.clone = function () { | |
throw "SpriteSheetBuilder cannot be cloned."; | |
}; | |
/** | |
* Returns a string representation of this object. | |
* @method toString | |
* @return {String} a string representation of the instance. | |
**/ | |
p.toString = function () { | |
return "[SpriteSheetBuilder]"; | |
}; | |
// private methods: | |
/** | |
* @method _startBuild | |
* @protected | |
**/ | |
p._startBuild = function () { | |
var pad = this.padding || 0; | |
this.progress = 0; | |
this.spriteSheet = null; | |
this._index = 0; | |
this._scale = this.scale; | |
var dataFrames = []; | |
this._data = { | |
images: [], | |
frames: dataFrames, | |
framerate: this.framerate, | |
animations: this._animations, // TODO: should we "clone" _animations in case someone adds more animations after a build? | |
}; | |
var frames = this._frames.slice(); | |
frames.sort(function (a, b) { | |
return a.height <= b.height ? -1 : 1; | |
}); | |
if (frames[frames.length - 1].height + pad * 2 > this.maxHeight) { | |
throw SpriteSheetBuilder.ERR_DIMENSIONS; | |
} | |
var y = 0, | |
x = 0; | |
var img = 0; | |
while (frames.length) { | |
var o = this._fillRow(frames, y, img, dataFrames, pad); | |
if (o.w > x) { | |
x = o.w; | |
} | |
y += o.h; | |
if (!o.h || !frames.length) { | |
var canvas = createjs.createCanvas | |
? createjs.createCanvas() | |
: document.createElement("canvas"); | |
canvas.width = this._getSize(x, this.maxWidth); | |
canvas.height = this._getSize(y, this.maxHeight); | |
this._data.images[img] = canvas; | |
if (!o.h) { | |
x = y = 0; | |
img++; | |
} | |
} | |
} | |
}; | |
/** | |
* @method _setupMovieClipFrame | |
* @protected | |
* @return {Number} The width & height of the row. | |
**/ | |
p._setupMovieClipFrame = function (source, data) { | |
var ae = source.actionsEnabled; | |
source.actionsEnabled = false; | |
source.gotoAndStop(data.i); | |
source.actionsEnabled = ae; | |
data.f && data.f(source, data.d, data.i); | |
}; | |
/** | |
* @method _getSize | |
* @protected | |
* @return {Number} The width & height of the row. | |
**/ | |
p._getSize = function (size, max) { | |
var pow = 4; | |
while (Math.pow(2, ++pow) < size) {} | |
return Math.min(max, Math.pow(2, pow)); | |
}; | |
/** | |
* @method _fillRow | |
* @param {Array} frames | |
* @param {Number} y | |
* @param {HTMLImageElement} img | |
* @param {Object} dataFrames | |
* @param {Number} pad | |
* @protected | |
* @return {Number} The width & height of the row. | |
**/ | |
p._fillRow = function (frames, y, img, dataFrames, pad) { | |
var w = this.maxWidth; | |
var maxH = this.maxHeight; | |
y += pad; | |
var h = maxH - y; | |
var x = pad; | |
var height = 0; | |
for (var i = frames.length - 1; i >= 0; i--) { | |
var frame = frames[i]; | |
var sc = this._scale * frame.scale; | |
var rect = frame.sourceRect; | |
var source = frame.source; | |
var rx = Math.floor(sc * rect.x - pad); | |
var ry = Math.floor(sc * rect.y - pad); | |
var rh = Math.ceil(sc * rect.height + pad * 2); | |
var rw = Math.ceil(sc * rect.width + pad * 2); | |
if (rw > w) { | |
throw SpriteSheetBuilder.ERR_DIMENSIONS; | |
} | |
if (rh > h || x + rw > w) { | |
continue; | |
} | |
frame.img = img; | |
frame.rect = new createjs.Rectangle(x, y, rw, rh); | |
height = height || rh; | |
frames.splice(i, 1); | |
dataFrames[frame.index] = [ | |
x, | |
y, | |
rw, | |
rh, | |
img, | |
Math.round(-rx + sc * source.regX - pad), | |
Math.round(-ry + sc * source.regY - pad), | |
]; | |
x += rw; | |
} | |
return { w: x, h: height }; | |
}; | |
/** | |
* @method _endBuild | |
* @protected | |
**/ | |
p._endBuild = function () { | |
this.spriteSheet = new createjs.SpriteSheet(this._data); | |
this._data = null; | |
this.progress = 1; | |
this.dispatchEvent("complete"); | |
}; | |
/** | |
* @method _run | |
* @protected | |
**/ | |
p._run = function () { | |
var ts = Math.max(0.01, Math.min(0.99, this.timeSlice || 0.3)) * 50; | |
var t = new Date().getTime() + ts; | |
var complete = false; | |
while (t > new Date().getTime()) { | |
if (!this._drawNext()) { | |
complete = true; | |
break; | |
} | |
} | |
if (complete) { | |
this._endBuild(); | |
} else { | |
var _this = this; | |
this._timerID = setTimeout(function () { | |
_this._run(); | |
}, 50 - ts); | |
} | |
var p = (this.progress = this._index / this._frames.length); | |
if (this.hasEventListener("progress")) { | |
var evt = new createjs.Event("progress"); | |
evt.progress = p; | |
this.dispatchEvent(evt); | |
} | |
}; | |
/** | |
* @method _drawNext | |
* @protected | |
* @return Boolean Returns false if this is the last draw. | |
**/ | |
p._drawNext = function () { | |
var frame = this._frames[this._index]; | |
var sc = frame.scale * this._scale; | |
var rect = frame.rect; | |
var sourceRect = frame.sourceRect; | |
var canvas = this._data.images[frame.img]; | |
var ctx = canvas.getContext("2d"); | |
frame.funct && frame.funct(frame.source, frame.data); | |
ctx.save(); | |
ctx.beginPath(); | |
ctx.rect(rect.x, rect.y, rect.width, rect.height); | |
ctx.clip(); | |
ctx.translate( | |
Math.ceil(rect.x - sourceRect.x * sc), | |
Math.ceil(rect.y - sourceRect.y * sc) | |
); | |
ctx.scale(sc, sc); | |
frame.source.draw(ctx); // display object will draw itself. | |
ctx.restore(); | |
return ++this._index < this._frames.length; | |
}; | |
createjs.SpriteSheetBuilder = createjs.promote( | |
SpriteSheetBuilder, | |
"EventDispatcher" | |
); | |
})(); | |
//############################################################################## | |
// DOMElement.js | |
//############################################################################## | |
this.createjs = this.createjs || {}; | |
(function () { | |
"use strict"; | |
// constructor: | |
/** | |
* <b>This class is still experimental, and more advanced use is likely to be buggy. Please report bugs.</b> | |
* | |
* A DOMElement allows you to associate a HTMLElement with the display list. It will be transformed | |
* within the DOM as though it is child of the {{#crossLink "Container"}}{{/crossLink}} it is added to. However, it is | |
* not rendered to canvas, and as such will retain whatever z-index it has relative to the canvas (ie. it will be | |
* drawn in front of or behind the canvas). | |
* | |
* The position of a DOMElement is relative to their parent node in the DOM. It is recommended that | |
* the DOM Object be added to a div that also contains the canvas so that they share the same position | |
* on the page. | |
* | |
* DOMElement is useful for positioning HTML elements over top of canvas content, and for elements | |
* that you want to display outside the bounds of the canvas. For example, a tooltip with rich HTML | |
* content. | |
* | |
* <h4>Mouse Interaction</h4> | |
* | |
* DOMElement instances are not full EaselJS display objects, and do not participate in EaselJS mouse | |
* events or support methods like hitTest. To get mouse events from a DOMElement, you must instead add handlers to | |
* the htmlElement (note, this does not support EventDispatcher) | |
* | |
* var domElement = new createjs.DOMElement(htmlElement); | |
* domElement.htmlElement.onclick = function() { | |
* console.log("clicked"); | |
* } | |
* | |
* <strong>Important:</strong> This class needs to be notified it is about to be drawn, this will happen automatically | |
* if you call stage.update, calling stage.draw or disabling tickEnabled will miss important steps and it will render | |
* stale information. | |
* | |
* @class DOMElement | |
* @extends DisplayObject | |
* @constructor | |
* @param {HTMLElement} htmlElement A reference or id for the DOM element to manage. | |
*/ | |
function DOMElement(htmlElement) { | |
this.DisplayObject_constructor(); | |
if (typeof htmlElement == "string") { | |
htmlElement = document.getElementById(htmlElement); | |
} | |
this.mouseEnabled = false; | |
var style = htmlElement.style; | |
style.position = "absolute"; | |
style.transformOrigin = | |
style.WebkitTransformOrigin = | |
style.msTransformOrigin = | |
style.MozTransformOrigin = | |
style.OTransformOrigin = | |
"0% 0%"; | |
// public properties: | |
/** | |
* The DOM object to manage. | |
* @property htmlElement | |
* @type HTMLElement | |
*/ | |
this.htmlElement = htmlElement; | |
// private properties: | |
/** | |
* @property _oldMtx | |
* @type Matrix2D | |
* @protected | |
*/ | |
this._oldProps = null; | |
/** | |
* Used to track the object which this class attached listeners to, helps optimize listener attachment. | |
* @property _oldStage | |
* @type Stage | |
* @protected | |
*/ | |
this._oldStage = null; | |
/** | |
* The event listener proxy triggered drawing draw for special circumstances. | |
* @property _drawAction | |
* @type function | |
* @protected | |
*/ | |
this._drawAction = null; | |
} | |
var p = createjs.extend(DOMElement, createjs.DisplayObject); | |
// TODO: deprecated | |
// p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details. | |
// public methods: | |
/** | |
* Returns true or false indicating whether the display object would be visible if drawn to a canvas. | |
* This does not account for whether it would be visible within the boundaries of the stage. | |
* NOTE: This method is mainly for internal use, though it may be useful for advanced uses. | |
* @method isVisible | |
* @return {Boolean} Boolean indicating whether the display object would be visible if drawn to a canvas | |
*/ | |
p.isVisible = function () { | |
return this.htmlElement != null; | |
}; | |
/** | |
* Draws the display object into the specified context ignoring its visible, alpha, shadow, and transform. | |
* Returns true if the draw was handled (useful for overriding functionality). | |
* NOTE: This method is mainly for internal use, though it may be useful for advanced uses. | |
* @method draw | |
* @param {CanvasRenderingContext2D} ctx The canvas 2D context object to draw into. | |
* @param {Boolean} ignoreCache Indicates whether the draw operation should ignore any current cache. | |
* For example, used for drawing the cache (to prevent it from simply drawing an existing cache back | |
* into itself). | |
* @return {Boolean} | |
*/ | |
p.draw = function (ctx, ignoreCache) { | |
// this relies on the _tick method because draw isn't called if the parent is not visible. | |
// the actual update happens in _handleDrawEnd | |
return true; | |
}; | |
/** | |
* Not applicable to DOMElement. | |
* @method cache | |
*/ | |
p.cache = function () {}; | |
/** | |
* Not applicable to DOMElement. | |
* @method uncache | |
*/ | |
p.uncache = function () {}; | |
/** | |
* Not applicable to DOMElement. | |
* @method updateCache | |
*/ | |
p.updateCache = function () {}; | |
/** | |
* Not applicable to DOMElement. | |
* @method hitTest | |
*/ | |
p.hitTest = function () {}; | |
/** | |
* Not applicable to DOMElement. | |
* @method localToGlobal | |
*/ | |
p.localToGlobal = function () {}; | |
/** | |
* Not applicable to DOMElement. | |
* @method globalToLocal | |
*/ | |
p.globalToLocal = function () {}; | |
/** | |
* Not applicable to DOMElement. | |
* @method localToLocal | |
*/ | |
p.localToLocal = function () {}; | |
/** | |
* DOMElement cannot be cloned. Throws an error. | |
* @method clone | |
*/ | |
p.clone = function () { | |
throw "DOMElement cannot be cloned."; | |
}; | |
/** | |
* Returns a string representation of this object. | |
* @method toString | |
* @return {String} a string representation of the instance. | |
*/ | |
p.toString = function () { | |
return "[DOMElement (name=" + this.name + ")]"; | |
}; | |
/** | |
* Interaction events should be added to `htmlElement`, and not the DOMElement instance, since DOMElement instances | |
* are not full EaselJS display objects and do not participate in EaselJS mouse events. | |
* @event click | |
*/ | |
/** | |
* Interaction events should be added to `htmlElement`, and not the DOMElement instance, since DOMElement instances | |
* are not full EaselJS display objects and do not participate in EaselJS mouse events. | |
* @event dblClick | |
*/ | |
/** | |
* Interaction events should be added to `htmlElement`, and not the DOMElement instance, since DOMElement instances | |
* are not full EaselJS display objects and do not participate in EaselJS mouse events. | |
* @event mousedown | |
*/ | |
/** | |
* The HTMLElement can listen for the mouseover event, not the DOMElement instance. | |
* Since DOMElement instances are not full EaselJS display objects and do not participate in EaselJS mouse events. | |
* @event mouseover | |
*/ | |
/** | |
* Not applicable to DOMElement. | |
* @event tick | |
*/ | |
// private methods: | |
/** | |
* @method _tick | |
* @param {Object} evtObj An event object that will be dispatched to all tick listeners. This object is reused between dispatchers to reduce construction & GC costs. | |
* function. | |
* @protected | |
*/ | |
p._tick = function (evtObj) { | |
var stage = this.stage; | |
if (stage && stage !== this._oldStage) { | |
this._drawAction && stage.off("drawend", this._drawAction); | |
this._drawAction = stage.on("drawend", this._handleDrawEnd, this); | |
this._oldStage = stage; | |
} | |
this.DisplayObject__tick(evtObj); | |
}; | |
/** | |
* @method _handleDrawEnd | |
* @param {Event} evt | |
* @protected | |
*/ | |
p._handleDrawEnd = function (evt) { | |
var o = this.htmlElement; | |
if (!o) { | |
return; | |
} | |
var style = o.style; | |
var props = this.getConcatenatedDisplayProps(this._props), | |
mtx = props.matrix; | |
var visibility = props.visible ? "visible" : "hidden"; | |
if (visibility != style.visibility) { | |
style.visibility = visibility; | |
} | |
if (!props.visible) { | |
return; | |
} | |
var oldProps = this._oldProps, | |
oldMtx = oldProps && oldProps.matrix; | |
var n = 10000; // precision | |
if (!oldMtx || !oldMtx.equals(mtx)) { | |
var str = | |
"matrix(" + | |
((mtx.a * n) | 0) / n + | |
"," + | |
((mtx.b * n) | 0) / n + | |
"," + | |
((mtx.c * n) | 0) / n + | |
"," + | |
((mtx.d * n) | 0) / n + | |
"," + | |
((mtx.tx + 0.5) | 0); | |
style.transform = | |
style.WebkitTransform = | |
style.OTransform = | |
style.msTransform = | |
str + "," + ((mtx.ty + 0.5) | 0) + ")"; | |
style.MozTransform = str + "px," + ((mtx.ty + 0.5) | 0) + "px)"; | |
if (!oldProps) { | |
oldProps = this._oldProps = new createjs.DisplayProps(true, null); | |
} | |
oldProps.matrix.copy(mtx); | |
} | |
if (oldProps.alpha != props.alpha) { | |
style.opacity = "" + ((props.alpha * n) | 0) / n; | |
oldProps.alpha = props.alpha; | |
} | |
}; | |
createjs.DOMElement = createjs.promote(DOMElement, "DisplayObject"); | |
})(); | |
//############################################################################## | |
// Filter.js | |
//############################################################################## | |
this.createjs = this.createjs || {}; | |
(function () { | |
"use strict"; | |
// constructor: | |
/** | |
* Base class that all filters should inherit from. Filters need to be applied to objects that have been cached using | |
* the {{#crossLink "DisplayObject/cache"}}{{/crossLink}} method. If an object changes, please cache it again, or use | |
* {{#crossLink "DisplayObject/updateCache"}}{{/crossLink}}. Note that the filters must be applied before caching. | |
* | |
* <h4>Example</h4> | |
* | |
* myInstance.filters = [ | |
* new createjs.ColorFilter(0, 0, 0, 1, 255, 0, 0), | |
* new createjs.BlurFilter(5, 5, 10) | |
* ]; | |
* myInstance.cache(0,0, 100, 100); | |
* | |
* Note that each filter can implement a {{#crossLink "Filter/getBounds"}}{{/crossLink}} method, which returns the | |
* margins that need to be applied in order to fully display the filter. For example, the {{#crossLink "BlurFilter"}}{{/crossLink}} | |
* will cause an object to feather outwards, resulting in a margin around the shape. | |
* | |
* <h4>EaselJS Filters</h4> | |
* EaselJS comes with a number of pre-built filters: | |
* <ul><li>{{#crossLink "AlphaMapFilter"}}{{/crossLink}} : Map a greyscale image to the alpha channel of a display object</li> | |
* <li>{{#crossLink "AlphaMaskFilter"}}{{/crossLink}}: Map an image's alpha channel to the alpha channel of a display object</li> | |
* <li>{{#crossLink "BlurFilter"}}{{/crossLink}}: Apply vertical and horizontal blur to a display object</li> | |
* <li>{{#crossLink "ColorFilter"}}{{/crossLink}}: Color transform a display object</li> | |
* <li>{{#crossLink "ColorMatrixFilter"}}{{/crossLink}}: Transform an image using a {{#crossLink "ColorMatrix"}}{{/crossLink}}</li> | |
* </ul> | |
* | |
* @class Filter | |
* @constructor | |
**/ | |
function Filter() { | |
/** | |
* A flag stating that this filter uses a context draw mode and cannot be batched into imageData processing. | |
* @property usesContext | |
* @type {boolean} | |
* @default false | |
*/ | |
this.usesContext = false; | |
/** | |
* Another filter that is required to act as part of this filter and created and managed under the hood. | |
* @private | |
* @property _multiPass | |
* @type {Filter} | |
* @default null | |
*/ | |
this._multiPass = null; | |
/** | |
* Pre-processed template shader code. It will be parsed before being fed in into the shader compiler. | |
* This should be based upon StageGL.SHADER_VERTEX_BODY_REGULAR | |
* @property VTX_SHADER | |
* @virtual | |
* @type {String} | |
* @readonly | |
*/ | |
this.VTX_SHADER_BODY = null; | |
/** | |
* Pre-processed template shader code. It will be parsed before being fed in into the shader compiler. | |
* This should be based upon StageGL.SHADER_FRAGMENT_BODY_REGULAR | |
* @property FRAG_SHADER | |
* @virtual | |
* @type {String} | |
* @readonly | |
*/ | |
this.FRAG_SHADER_BODY = null; | |
} | |
var p = Filter.prototype; | |
// public methods: | |
/** | |
* Provides padding values for this filter. That is, how much the filter will extend the visual bounds of an object it is applied to. | |
* @method getBounds | |
* @param {Rectangle} [rect] If specified, the provided Rectangle instance will be expanded by the padding amounts and returned. | |
* @return {Rectangle} If a `rect` param was provided, it is returned. If not, either a new rectangle with the padding values, or null if no padding is required for this filter. | |
**/ | |
p.getBounds = function (rect) { | |
return rect; | |
}; | |
/** | |
* Assign any unique uniforms or other setup functionality here. | |
* @method shaderParamSetup | |
* @virtual | |
* @param {WebGLContext} gl The context associated with the stage performing the render. | |
* @param {StageGL} stage The stage instance that will be rendering. | |
* @param {ShaderProgram} shaderProgram The compiled shader that is going to be used to perform the render. | |
*/ | |
p.shaderParamSetup = function (gl, stage, shaderProgram) {}; | |
/** | |
* Applies the filter to the specified context. | |
* @method applyFilter | |
* @param {CanvasRenderingContext2D} ctx The 2D context to use as the source. | |
* @param {Number} x The x position to use for the source rect. | |
* @param {Number} y The y position to use for the source rect. | |
* @param {Number} width The width to use for the source rect. | |
* @param {Number} height The height to use for the source rect. | |
* @param {CanvasRenderingContext2D} [targetCtx] The 2D context to draw the result to. Defaults to the context passed to ctx. | |
* @param {Number} [targetX] The x position to draw the result to. Defaults to the value passed to x. | |
* @param {Number} [targetY] The y position to draw the result to. Defaults to the value passed to y. | |
* @return {Boolean} If the filter was applied successfully. | |
**/ | |
p.applyFilter = function ( | |
ctx, | |
x, | |
y, | |
width, | |
height, | |
targetCtx, | |
targetX, | |
targetY | |
) { | |
// this is the default behaviour because most filters access pixel data. It is overridden when not needed. | |
targetCtx = targetCtx || ctx; | |
if (targetX == null) { | |
targetX = x; | |
} | |
if (targetY == null) { | |
targetY = y; | |
} | |
try { | |
var imageData = ctx.getImageData(x, y, width, height); | |
} catch (e) { | |
return false; | |
} | |
if (this._applyFilter(imageData)) { | |
targetCtx.putImageData(imageData, targetX, targetY); | |
return true; | |
} | |
return false; | |
}; | |
/** | |
* Returns a string representation of this object. | |
* @method toString | |
* @return {String} a string representation of the instance. | |
**/ | |
p.toString = function () { | |
return "[Filter]"; | |
}; | |
/** | |
* Returns a clone of this Filter instance. | |
* @method clone | |
* @return {Filter} A clone of the current Filter instance. | |
**/ | |
p.clone = function () { | |
return new Filter(); | |
}; | |
// private methods: | |
/** | |
* @method _applyFilter | |
* @param {ImageData} imageData Target ImageData instance. | |
* @return {Boolean} | |
**/ | |
p._applyFilter = function (imageData) { | |
return true; | |
}; | |
createjs.Filter = Filter; | |
})(); | |
//############################################################################## | |
// BitmapCache.js | |
//############################################################################## | |
this.createjs = this.createjs || {}; | |
(function () { | |
"use strict"; | |
// constructor: | |
/** | |
* The BitmapCache is an internal representation of all the cache properties and logic required in order to "cache" | |
* an object. This information and functionality used to be located on a {{#crossLink "DisplayObject/cache"}}{{/crossLink}} | |
* method in {{#crossLink "DisplayObject"}}{{/crossLink}}, but was moved to its own class. | |
* | |
* Caching in this context is purely visual, and will render the DisplayObject out into an image to be used instead | |
* of the object. The actual cache itself is still stored on the target with the {{#crossLink "DisplayObject/cacheCanvas:property"}}{{/crossLink}}. | |
* Working with a singular image like a {{#crossLink "Bitmap"}}{{/crossLink}} there is little benefit to performing | |
* a cache as it is already a single image. Caching is best done on containers containing multiple complex parts that | |
* do not move often, so that rendering the image instead will improve overall rendering speed. A cached object will | |
* not visually update until explicitly told to do so with a call to update, much like a Stage. If a cache is being | |
* updated every frame it is likely not improving rendering performance. Cache are best used when updates will be sparse. | |
* | |
* Caching is also a co-requisite for applying filters to prevent expensive filters running constantly without need, | |
* and to physically enable some effects. The BitmapCache is also responsible for applying filters to objects and | |
* reads each {{#crossLink "Filter"}}{{/crossLink}} due to this relationship. Real-time Filters are not recommended | |
* performance wise when dealing with a Context2D canvas. For best performance and to still allow for some visual | |
* effects use a compositeOperation when possible. | |
* @class BitmapCache | |
* @constructor | |
**/ | |
function BitmapCache() { | |
// public: | |
/** | |
* Width of the cache relative to the target object. | |
* @property width | |
* @protected | |
* @type {Number} | |
* @default undefined | |
**/ | |
this.width = undefined; | |
/** | |
* Height of the cache relative to the target object. | |
* @property height | |
* @protected | |
* @type {Number} | |
* @default undefined | |
* @todo Should the width and height be protected? | |
**/ | |
this.height = undefined; | |
/** | |
* Horizontal position of the cache relative to the target's origin. | |
* @property x | |
* @protected | |
* @type {Number} | |
* @default undefined | |
**/ | |
this.x = undefined; | |
/** | |
* Vertical position of the cache relative to target's origin. | |
* @property y | |
* @protected | |
* @type {Number} | |
* @default undefined | |
**/ | |
this.y = undefined; | |
/** | |
* The internal scale of the cache image, does not affects display size. This is useful to both increase and | |
* decrease render quality. Objects with increased scales are more likely to look good when scaled up or rotated. | |
* Objects with decreased scales can save on rendering performance. | |
* @property scale | |
* @protected | |
* @type {Number} | |
* @default 1 | |
**/ | |
this.scale = 1; | |
/** | |
* The x offset used for drawing into the cache itself, accounts for both transforms applied. | |
* @property offX | |
* @protected | |
* @type {Number} | |
* @default 0 | |
**/ | |
this.offX = 0; | |
/** | |
* The y offset used for drawing into the cache itself, accounts for both transforms applied. | |
* @property offY | |
* @protected | |
* @type {Number} | |
* @default 0 | |
**/ | |
this.offY = 0; | |
/** | |
* Track how many times the cache has been updated, mostly used for preventing duplicate cacheURLs. | |
* This can be useful to see if a cache has been updated. | |
* @property cacheID | |
* @type {Number} | |
* @default 0 | |
**/ | |
this.cacheID = 0; | |
// protected: | |
/** | |
* The relative offset of the filter's x position, used for drawing the cache onto its container. | |
* Re-calculated every update call before drawing. | |
* @property _filterOffY | |
* @protected | |
* @type {Number} | |
* @default 0 | |
**/ | |
this._filterOffX = 0; | |
/** | |
* The relative offset of the filter's y position, used for drawing the cache onto its container. | |
* Re-calculated every update call before drawing. | |
* @property _filterOffY | |
* @protected | |
* @type {Number} | |
* @default 0 | |
**/ | |
this._filterOffY = 0; | |
/** | |
* The cacheID when a DataURL was requested. | |
* @property _cacheDataURLID | |
* @protected | |
* @type {Number} | |
* @default 0 | |
**/ | |
this._cacheDataURLID = 0; | |
/** | |
* The cache's DataURL, generated on-demand using the getter. | |
* @property _cacheDataURL | |
* @protected | |
* @type {String} | |
* @default null | |
**/ | |
this._cacheDataURL = null; | |
/** | |
* Internal tracking of final bounding width, approximately width*scale; however, filters can complicate the actual value. | |
* @property _drawWidth | |
* @protected | |
* @type {Number} | |
* @default 0 | |
**/ | |
this._drawWidth = 0; | |
/** | |
* Internal tracking of final bounding height, approximately height*scale; however, filters can complicate the actual value. | |
* @property _drawHeight | |
* @protected | |
* @type {Number} | |
* @default 0 | |
**/ | |
this._drawHeight = 0; | |
} | |
var p = BitmapCache.prototype; | |
/** | |
* Returns the bounds that surround all applied filters, relies on each filter to describe how it changes bounds. | |
* @method getFilterBounds | |
* @param {DisplayObject} target The object to check the filter bounds for. | |
* @param {Rectangle} [output=null] Optional parameter, if provided then calculated bounds will be applied to that object. | |
* @return {Rectangle} bounds object representing the bounds with filters. | |
* @static | |
**/ | |
BitmapCache.getFilterBounds = function (target, output) { | |
if (!output) { | |
output = new createjs.Rectangle(); | |
} | |
var filters = target.filters; | |
var filterCount = filters && filters.length; | |
if (!!filterCount <= 0) { | |
return output; | |
} | |
for (var i = 0; i < filterCount; i++) { | |
var f = filters[i]; | |
if (!f || !f.getBounds) { | |
continue; | |
} | |
var test = f.getBounds(); | |
if (!test) { | |
continue; | |
} | |
if (i == 0) { | |
output.setValues(test.x, test.y, test.width, test.height); | |
} else { | |
output.extend(test.x, test.y, test.width, test.height); | |
} | |
} | |
return output; | |
}; | |
// public methods: | |
/** | |
* Returns a string representation of this object. | |
* @method toString | |
* @return {String} a string representation of the instance. | |
**/ | |
p.toString = function () { | |
return "[BitmapCache]"; | |
}; | |
/** | |
* Actually create the correct cache surface and properties associated with it. Caching and it's benefits are discussed | |
* by the {{#crossLink "DisplayObject/cache"}}{{/crossLink}} function and this class description. Here are the detailed | |
* specifics of how to use the options object. | |
* | |
* - If options.useGL is set to "new" a StageGL is created and contained on this for use when rendering the cache. | |
* - If options.useGL is set to "stage" if the current stage is a StageGL it will be used. If not then it will default to "new". | |
* - If options.useGL is a StageGL instance it will not create one but use the one provided. | |
* - If options.useGL is undefined a Context 2D cache will be performed. | |
* | |
* This means you can use any combination of StageGL and 2D with either, neither, or both the stage and cache being | |
* WebGL. Using "new" with a StageGL display list is highly unrecommended, but still an option. It should be avoided | |
* due to negative performance reasons and the Image loading limitation noted in the class complications above. | |
* | |
* When "options.useGL" is set to the parent stage of the target and WebGL, performance is increased by using | |
* "RenderTextures" instead of canvas elements. These are internal Textures on the graphics card stored in the GPU. | |
* Because they are no longer canvases you cannot perform operations you could with a regular canvas. The benefit | |
* is that this avoids the slowdown of copying the texture back and forth from the GPU to a Canvas element. | |
* This means "stage" is the recommended option when available. | |
* | |
* A StageGL cache does not infer the ability to draw objects a StageGL cannot currently draw, i.e. do not use a | |
* WebGL context cache when caching a Shape, Text, etc. | |
* <h4>WebGL cache with a 2D context</h4> | |
* | |
* var stage = new createjs.Stage(); | |
* var bmp = new createjs.Bitmap(src); | |
* bmp.cache(0, 0, bmp.width, bmp.height, 1, {gl: "new"}); // no StageGL to use, so make one | |
* | |
* var shape = new createjs.Shape(); | |
* shape.graphics.clear().fill("red").drawRect(0,0,20,20); | |
* shape.cache(0, 0, 20, 20, 1); // cannot use WebGL cache | |
* | |
* <h4>WebGL cache with a WebGL context</h4> | |
* | |
* var stageGL = new createjs.StageGL(); | |
* var bmp = new createjs.Bitmap(src); | |
* bmp.cache(0, 0, bmp.width, bmp.height, 1, {gl: "stage"}); // use our StageGL to cache | |
* | |
* var shape = new createjs.Shape(); | |
* shape.graphics.clear().fill("red").drawRect(0,0,20,20); | |
* shape.cache(0, 0, 20, 20, 1); // cannot use WebGL cache | |
* | |
* You may wish to create your own StageGL instance to control factors like clear color, transparency, AA, and | |
* others. If you do, pass a new instance in instead of "true", the library will automatically set the | |
* {{#crossLink "StageGL/isCacheControlled"}}{{/crossLink}} to true on your instance. This will trigger it to behave | |
* correctly, and not assume your main context is WebGL. | |
* | |
* @public | |
* @method BitmapCache.cache | |
* @param {Number} x The x coordinate origin for the cache region. | |
* @param {Number} y The y coordinate origin for the cache region. | |
* @param {Number} width The width of the cache region. | |
* @param {Number} height The height of the cache region. | |
* @param {Number} [scale=1] The scale at which the cache will be created. For example, if you cache a vector shape | |
* using myShape.cache(0,0,100,100,2) then the resulting cacheCanvas will be 200x200 px. This lets you scale and | |
* rotate cached elements with greater fidelity. Default is 1. | |
* @param {Object} [options=undefined] Specify additional parameters for the cache logic | |
* @param {undefined|"new"|"stage"|StageGL} [options.useGL=undefined] Select whether to use context 2D, or WebGL rendering, and | |
* whether to make a new stage instance or use an existing one. See above for extensive details on use. | |
* @for BitmapCache | |
*/ | |
p.define = function (target, x, y, width, height, scale, options) { | |
if (!target) { | |
throw "No symbol to cache"; | |
} | |
this._options = options; | |
this.target = target; | |
this.width = width >= 1 ? width : 1; | |
this.height = height >= 1 ? height : 1; | |
this.x = x || 0; | |
this.y = y || 0; | |
this.scale = scale || 1; | |
this.update(); | |
}; | |
/** | |
* Directly called via {{#crossLink "DisplayObject/updateCache:method"}}{{/crossLink}}, but also internally. This | |
* has the dual responsibility of making sure the surface is ready to be drawn to, and performing the draw. For | |
* full details of each behaviour, check the protected functions {{#crossLink "BitmapCache/_updateSurface"}}{{/crossLink}} | |
* and {{#crossLink "BitmapCache/_drawToCache"}}{{/crossLink}} respectively. | |
* @method update | |
* @param {String} [compositeOperation=null] The DisplayObject this cache is linked to. | |
**/ | |
p.update = function (compositeOperation) { | |
if (!this.target) { | |
throw "define() must be called before update()"; | |
} | |
var filterBounds = BitmapCache.getFilterBounds(this.target); | |
var surface = this.target.cacheCanvas; | |
this._drawWidth = Math.ceil(this.width * this.scale) + filterBounds.width; | |
this._drawHeight = | |
Math.ceil(this.height * this.scale) + filterBounds.height; | |
if ( | |
!surface || | |
this._drawWidth != surface.width || | |
this._drawHeight != surface.height | |
) { | |
this._updateSurface(); | |
} | |
this._filterOffX = filterBounds.x; | |
this._filterOffY = filterBounds.y; | |
this.offX = this.x * this.scale + this._filterOffX; | |
this.offY = this.y * this.scale + this._filterOffY; | |
this._drawToCache(compositeOperation); | |
this.cacheID = this.cacheID ? this.cacheID + 1 : 1; | |
}; | |
/** | |
* Reset and release all the properties and memory associated with this cache. | |
* @method release | |
**/ | |
p.release = function () { | |
if (this._webGLCache) { | |
// if it isn't cache controlled clean up after yourself | |
if (!this._webGLCache.isCacheControlled) { | |
if (this.__lastRT) { | |
this.__lastRT = undefined; | |
} | |
if (this.__rtA) { | |
this._webGLCache._killTextureObject(this.__rtA); | |
} | |
if (this.__rtB) { | |
this._webGLCache._killTextureObject(this.__rtB); | |
} | |
if (this.target && this.target.cacheCanvas) { | |
this._webGLCache._killTextureObject(this.target.cacheCanvas); | |
} | |
} | |
// set the context to none and let the garbage collector get the rest when the canvas itself gets removed | |
this._webGLCache = false; | |
} else { | |
var stage = this.target.stage; | |
if (stage instanceof createjs.StageGL) { | |
stage.releaseTexture(this.target.cacheCanvas); | |
} | |
} | |
this.target = this.target.cacheCanvas = null; | |
this.cacheID = this._cacheDataURLID = this._cacheDataURL = undefined; | |
this.width = this.height = this.x = this.y = this.offX = this.offY = 0; | |
this.scale = 1; | |
}; | |
/** | |
* Returns a data URL for the cache, or `null` if this display object is not cached. | |
* Uses {{#crossLink "BitmapCache/cacheID:property"}}{{/crossLink}} to ensure a new data URL is not generated if the | |
* cache has not changed. | |
* @method getCacheDataURL | |
* @return {String} The image data url for the cache. | |
**/ | |
p.getCacheDataURL = function () { | |
var cacheCanvas = this.target && this.target.cacheCanvas; | |
if (!cacheCanvas) { | |
return null; | |
} | |
if (this.cacheID != this._cacheDataURLID) { | |
this._cacheDataURLID = this.cacheID; | |
this._cacheDataURL = cacheCanvas.toDataURL | |
? cacheCanvas.toDataURL() | |
: null; // incase function is | |
} | |
return this._cacheDataURL; | |
}; | |
/** | |
* Use context2D drawing commands to display the cache canvas being used. | |
* @method draw | |
* @param {CanvasRenderingContext2D} ctx The context to draw into. | |
* @return {Boolean} Whether the draw was handled successfully. | |
**/ | |
p.draw = function (ctx) { | |
if (!this.target) { | |
return false; | |
} | |
ctx.drawImage( | |
this.target.cacheCanvas, | |
this.x + this._filterOffX / this.scale, | |
this.y + this._filterOffY / this.scale, | |
this._drawWidth / this.scale, | |
this._drawHeight / this.scale | |
); | |
return true; | |
}; | |
// private methods: | |
/** | |
* Create or resize the invisible canvas/surface that is needed for the display object(s) to draw to, | |
* and in turn be used in their stead when drawing. The surface is resized to the size defined | |
* by the width and height, factoring in scaling and filters. Adjust them to adjust the output size. | |
* @method _updateSurface | |
* @protected | |
**/ | |
p._updateSurface = function () { | |
if (!this._options || !this._options.useGL) { | |
var surface = this.target.cacheCanvas; | |
// create it if it's missing | |
if (!surface) { | |
surface = this.target.cacheCanvas = createjs.createCanvas | |
? createjs.createCanvas() | |
: document.createElement("canvas"); | |
} | |
// now size it | |
surface.width = this._drawWidth; | |
surface.height = this._drawHeight; | |
return; | |
} | |
// create it if it's missing | |
if (!this._webGLCache) { | |
if (this._options.useGL === "stage") { | |
if (!(this.target.stage && this.target.stage.isWebGL)) { | |
var error = | |
"Cannot use 'stage' for cache because the object's parent stage is "; | |
error += this.target.stage | |
? "non WebGL." | |
: "not set, please addChild to the correct stage."; | |
throw error; | |
} | |
this.target.cacheCanvas = true; // will be replaced with RenderTexture, temporary positive value for old "isCached" checks | |
this._webGLCache = this.target.stage; | |
} else if (this._options.useGL === "new") { | |
this.target.cacheCanvas = document.createElement("canvas"); // we can turn off autopurge because we wont be making textures here | |
this._webGLCache = new createjs.StageGL(this.target.cacheCanvas, { | |
antialias: true, | |
transparent: true, | |
autoPurge: -1, | |
}); | |
this._webGLCache.isCacheControlled = true; // use this flag to control stage sizing and final output | |
} else if (this._options.useGL instanceof createjs.StageGL) { | |
this.target.cacheCanvas = true; // will be replaced with RenderTexture, temporary positive value for old "isCached" checks | |
this._webGLCache = this._options.useGL; | |
this._webGLCache.isCacheControlled = true; // use this flag to control stage sizing and final output | |
} else { | |
throw ( | |
"Invalid option provided to useGL, expected ['stage', 'new', StageGL, undefined], got " + | |
this._options.useGL | |
); | |
} | |
} | |
// now size render surfaces | |
var surface = this.target.cacheCanvas; | |
var stageGL = this._webGLCache; | |
// if we have a dedicated stage we've gotta size it | |
if (stageGL.isCacheControlled) { | |
surface.width = this._drawWidth; | |
surface.height = this._drawHeight; | |
stageGL.updateViewport(this._drawWidth, this._drawHeight); | |
} | |
if (this.target.filters) { | |
// with filters we can't tell how many we'll need but the most we'll ever need is two, so make them now | |
stageGL.getTargetRenderTexture( | |
this.target, | |
this._drawWidth, | |
this._drawHeight | |
); | |
stageGL.getTargetRenderTexture( | |
this.target, | |
this._drawWidth, | |
this._drawHeight | |
); | |
} else { | |
// without filters then we only need one RenderTexture, and that's only if its not a dedicated stage | |
if (!stageGL.isCacheControlled) { | |
stageGL.getTargetRenderTexture( | |
this.target, | |
this._drawWidth, | |
this._drawHeight | |
); | |
} | |
} | |
}; | |
/** | |
* Perform the cache draw out for context 2D now that the setup properties have been performed. | |
* @method _drawToCache | |
* @protected | |
**/ | |
p._drawToCache = function (compositeOperation) { | |
var surface = this.target.cacheCanvas; | |
var target = this.target; | |
var webGL = this._webGLCache; | |
if (webGL) { | |
//TODO: auto split blur into an x/y pass | |
webGL.cacheDraw(target, target.filters, this); | |
// we may of swapped around which element the surface is, so we re-fetch it | |
surface = this.target.cacheCanvas; | |
surface.width = this._drawWidth; | |
surface.height = this._drawHeight; | |
} else { | |
var ctx = surface.getContext("2d"); | |
if (!compositeOperation) { | |
ctx.clearRect(0, 0, this._drawWidth + 1, this._drawHeight + 1); | |
} | |
ctx.save(); | |
ctx.globalCompositeOperation = compositeOperation; | |
ctx.setTransform( | |
this.scale, | |
0, | |
0, | |
this.scale, | |
-this._filterOffX, | |
-this._filterOffY | |
); | |
ctx.translate(-this.x, -this.y); | |
target.draw(ctx, true); | |
ctx.restore(); | |
if (target.filters && target.filters.length) { | |
this._applyFilters(ctx); | |
} | |
} | |
surface._invalid = true; | |
}; | |
/** | |
* Work through every filter and apply its individual visual transformation. | |
* @method _applyFilters | |
* @protected | |
**/ | |
p._applyFilters = function (ctx) { | |
var filters = this.target.filters; | |
var w = this._drawWidth; | |
var h = this._drawHeight; | |
var data; | |
var i = 0, | |
filter = filters[i]; | |
do { | |
// this is safe because we wouldn't be in apply filters without a filter count of at least 1 | |
if (filter.usesContext) { | |
if (data) { | |
ctx.putImageData(data, 0, 0); | |
data = null; | |
} | |
filter.applyFilter(ctx, 0, 0, w, h); | |
} else { | |
if (!data) { | |
data = ctx.getImageData(0, 0, w, h); | |
} | |
filter._applyFilter(data); | |
} | |
// work through the multipass if it's there, otherwise move on | |
filter = filter._multiPass !== null ? filter._multiPass : filters[++i]; | |
} while (filter); | |
//done | |
if (data) { | |
ctx.putImageData(data, 0, 0); | |
} | |
}; | |
createjs.BitmapCache = BitmapCache; | |
})(); | |
//############################################################################## | |
// BlurFilter.js | |
//############################################################################## | |
this.createjs = this.createjs || {}; | |
(function () { | |
"use strict"; | |
// constructor: | |
/** | |
* Applies a box blur to DisplayObjects in context 2D and a Gaussian blur in webgl. Note that this filter is fairly | |
* intensive, particularly if the quality is set higher than 1. | |
* | |
* <h4>Example</h4> | |
* This example creates a red circle, and then applies a 5 pixel blur to it. It uses the {{#crossLink "Filter/getBounds"}}{{/crossLink}} | |
* method to account for the spread that the blur causes. | |
* | |
* var shape = new createjs.Shape().set({x:100,y:100}); | |
* shape.graphics.beginFill("#ff0000").drawCircle(0,0,50); | |
* | |
* var blurFilter = new createjs.BlurFilter(5, 5, 1); | |
* shape.filters = [blurFilter]; | |
* var bounds = blurFilter.getBounds(); | |
* | |
* shape.cache(-50+bounds.x, -50+bounds.y, 100+bounds.width, 100+bounds.height); | |
* | |
* See {{#crossLink "Filter"}}{{/crossLink}} for an more information on applying filters. | |
* @class BlurFilter | |
* @extends Filter | |
* @constructor | |
* @param {Number} [blurX=0] The horizontal blur radius in pixels. | |
* @param {Number} [blurY=0] The vertical blur radius in pixels. | |
* @param {Number} [quality=1] The number of blur iterations. | |
**/ | |
function BlurFilter(blurX, blurY, quality) { | |
this.Filter_constructor(); | |
// public properties: | |
/** | |
* Horizontal blur radius in pixels | |
* @property blurX | |
* @default 0 | |
* @type Number | |
**/ | |
this._blurX = blurX; | |
this._blurXTable = []; | |
this._lastBlurX = null; | |
/** | |
* Vertical blur radius in pixels | |
* @property blurY | |
* @default 0 | |
* @type Number | |
**/ | |
this._blurY = blurY; | |
this._blurYTable = []; | |
this._lastBlurY = null; | |
/** | |
* Number of blur iterations. For example, a value of 1 will produce a rough blur. A value of 2 will produce a | |
* smoother blur, but take twice as long to run. | |
* @property quality | |
* @default 1 | |
* @type Number | |
**/ | |
this._quality; | |
this._lastQuality = null; | |
/** | |
* This is a template to generate the shader for {{#crossLink FRAG_SHADER_BODY}}{{/crossLink}} | |
*/ | |
this.FRAG_SHADER_TEMPLATE = | |
"uniform float xWeight[{{blurX}}];" + | |
"uniform float yWeight[{{blurY}}];" + | |
"uniform vec2 textureOffset;" + | |
"void main(void) {" + | |
"vec4 color = vec4(0.0);" + | |
"float xAdj = ({{blurX}}.0-1.0)/2.0;" + | |
"float yAdj = ({{blurY}}.0-1.0)/2.0;" + | |
"vec2 sampleOffset;" + | |
"for(int i=0; i<{{blurX}}; i++) {" + | |
"for(int j=0; j<{{blurY}}; j++) {" + | |
"sampleOffset = vRenderCoord + (textureOffset * vec2(float(i)-xAdj, float(j)-yAdj));" + | |
"color += texture2D(uSampler, sampleOffset) * (xWeight[i] * yWeight[j]);" + | |
"}" + | |
"}" + | |
"gl_FragColor = color.rgba;" + | |
"}"; | |
// update the filter using the setters | |
if (isNaN(quality) || quality < 1) { | |
quality = 1; | |
} | |
this.setQuality(quality | 0); | |
} | |
var p = createjs.extend(BlurFilter, createjs.Filter); | |
// TODO: deprecated | |
// p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details. | |
p.getBlurX = function () { | |
return this._blurX; | |
}; | |
p.getBlurY = function () { | |
return this._blurY; | |
}; | |
p.setBlurX = function (value) { | |
if (isNaN(value) || value < 0) { | |
value = 0; | |
} | |
this._blurX = value; | |
}; | |
p.setBlurY = function (value) { | |
if (isNaN(value) || value < 0) { | |
value = 0; | |
} | |
this._blurY = value; | |
}; | |
p.getQuality = function () { | |
return this._quality; | |
}; | |
p.setQuality = function (value) { | |
if (isNaN(value) || value < 0) { | |
value = 0; | |
} | |
this._quality = value | 0; | |
}; | |
p._getShader = function () { | |
var xChange = this._lastBlurX !== this._blurX; | |
var yChange = this._lastBlurY !== this._blurY; | |
var qChange = this._lastQuality !== this._quality; | |
if (xChange || yChange || qChange) { | |
if (xChange || qChange) { | |
this._blurXTable = this._getTable(this._blurX * this._quality); | |
} | |
if (yChange || qChange) { | |
this._blurYTable = this._getTable(this._blurY * this._quality); | |
} | |
this._updateShader(); | |
this._lastBlurX = this._blurX; | |
this._lastBlurY = this._blurY; | |
this._lastQuality = this._quality; | |
return undefined; // force a rebuild | |
} | |
return this._compiledShader; | |
}; | |
p._setShader = function () { | |
this._compiledShader; | |
}; | |
try { | |
Object.defineProperties(p, { | |
blurX: { get: p.getBlurX, set: p.setBlurX }, | |
blurY: { get: p.getBlurY, set: p.setBlurY }, | |
quality: { get: p.getQuality, set: p.setQuality }, | |
_builtShader: { get: p._getShader, set: p._setShader }, | |
}); | |
} catch (e) { | |
console.log(e); | |
} | |
/** | |
* Internal lookup function to create gaussian distribution. | |
* @method _getTable | |
* @param {Number} spread How many steps in the curve. | |
* @return {Array<Number>} An array with Math.ceil(spread*2) entries with appropriately distributed weights. | |
*/ | |
p._getTable = function (spread) { | |
var EDGE = 4.2; | |
if (spread <= 1) { | |
return [1]; | |
} | |
var result = []; | |
var count = Math.ceil(spread * 2); | |
count += count % 2 ? 0 : 1; | |
var adjust = (count / 2) | 0; | |
for (var i = -adjust; i <= adjust; i++) { | |
var x = (i / adjust) * EDGE; | |
result.push( | |
(1 / Math.sqrt(2 * Math.PI)) * Math.pow(Math.E, -(Math.pow(x, 2) / 4)) | |
); | |
} | |
var factor = result.reduce(function (a, b) { | |
return a + b; | |
}); | |
return result.map(function (currentValue, index, array) { | |
return currentValue / factor; | |
}); | |
}; | |
/** | |
* Internal update function to create shader properties. | |
* @method _updateShader | |
*/ | |
p._updateShader = function () { | |
if (this._blurX === undefined || this._blurY === undefined) { | |
return; | |
} | |
var result = this.FRAG_SHADER_TEMPLATE; | |
result = result.replace( | |
/\{\{blurX\}\}/g, | |
this._blurXTable.length.toFixed(0) | |
); | |
result = result.replace( | |
/\{\{blurY\}\}/g, | |
this._blurYTable.length.toFixed(0) | |
); | |
this.FRAG_SHADER_BODY = result; | |
}; | |
/** docced in super class **/ | |
p.shaderParamSetup = function (gl, stage, shaderProgram) { | |
// load the normalized gaussian weight tables | |
gl.uniform1fv( | |
gl.getUniformLocation(shaderProgram, "xWeight"), | |
this._blurXTable | |
); | |
gl.uniform1fv( | |
gl.getUniformLocation(shaderProgram, "yWeight"), | |
this._blurYTable | |
); | |
// what is the size of a single pixel in -1, 1 (webGL) space | |
gl.uniform2f( | |
gl.getUniformLocation(shaderProgram, "textureOffset"), | |
2 / (stage._viewportWidth * this._quality), | |
2 / (stage._viewportHeight * this._quality) | |
); | |
}; | |
// constants: | |
/** | |
* Array of multiply values for blur calculations. | |
* @property MUL_TABLE | |
* @type Array | |
* @protected | |
* @static | |
**/ | |
BlurFilter.MUL_TABLE = [ | |
1, 171, 205, 293, 57, 373, 79, 137, 241, 27, 391, 357, 41, 19, 283, 265, | |
497, 469, 443, 421, 25, 191, 365, 349, 335, 161, 155, 149, 9, 278, 269, 261, | |
505, 245, 475, 231, 449, 437, 213, 415, 405, 395, 193, 377, 369, 361, 353, | |
345, 169, 331, 325, 319, 313, 307, 301, 37, 145, 285, 281, 69, 271, 267, | |
263, 259, 509, 501, 493, 243, 479, 118, 465, 459, 113, 446, 55, 435, 429, | |
423, 209, 413, 51, 403, 199, 393, 97, 3, 379, 375, 371, 367, 363, 359, 355, | |
351, 347, 43, 85, 337, 333, 165, 327, 323, 5, 317, 157, 311, 77, 305, 303, | |
75, 297, 294, 73, 289, 287, 71, 141, 279, 277, 275, 68, 135, 67, 133, 33, | |
262, 260, 129, 511, 507, 503, 499, 495, 491, 61, 121, 481, 477, 237, 235, | |
467, 232, 115, 457, 227, 451, 7, 445, 221, 439, 218, 433, 215, 427, 425, | |
211, 419, 417, 207, 411, 409, 203, 202, 401, 399, 396, 197, 49, 389, 387, | |
385, 383, 95, 189, 47, 187, 93, 185, 23, 183, 91, 181, 45, 179, 89, 177, 11, | |
175, 87, 173, 345, 343, 341, 339, 337, 21, 167, 83, 331, 329, 327, 163, 81, | |
323, 321, 319, 159, 79, 315, 313, 39, 155, 309, 307, 153, 305, 303, 151, 75, | |
299, 149, 37, 295, 147, 73, 291, 145, 289, 287, 143, 285, 71, 141, 281, 35, | |
279, 139, 69, 275, 137, 273, 17, 271, 135, 269, 267, 133, 265, 33, 263, 131, | |
261, 130, 259, 129, 257, 1, | |
]; | |
/** | |
* Array of shift values for blur calculations. | |
* @property SHG_TABLE | |
* @type Array | |
* @protected | |
* @static | |
**/ | |
BlurFilter.SHG_TABLE = [ | |
0, 9, 10, 11, 9, 12, 10, 11, 12, 9, 13, 13, 10, 9, 13, 13, 14, 14, 14, 14, | |
10, 13, 14, 14, 14, 13, 13, 13, 9, 14, 14, 14, 15, 14, 15, 14, 15, 15, 14, | |
15, 15, 15, 14, 15, 15, 15, 15, 15, 14, 15, 15, 15, 15, 15, 15, 12, 14, 15, | |
15, 13, 15, 15, 15, 15, 16, 16, 16, 15, 16, 14, 16, 16, 14, 16, 13, 16, 16, | |
16, 15, 16, 13, 16, 15, 16, 14, 9, 16, 16, 16, 16, 16, 16, 16, 16, 16, 13, | |
14, 16, 16, 15, 16, 16, 10, 16, 15, 16, 14, 16, 16, 14, 16, 16, 14, 16, 16, | |
14, 15, 16, 16, 16, 14, 15, 14, 15, 13, 16, 16, 15, 17, 17, 17, 17, 17, 17, | |
14, 15, 17, 17, 16, 16, 17, 16, 15, 17, 16, 17, 11, 17, 16, 17, 16, 17, 16, | |
17, 17, 16, 17, 17, 16, 17, 17, 16, 16, 17, 17, 17, 16, 14, 17, 17, 17, 17, | |
15, 16, 14, 16, 15, 16, 13, 16, 15, 16, 14, 16, 15, 16, 12, 16, 15, 16, 17, | |
17, 17, 17, 17, 13, 16, 15, 17, 17, 17, 16, 15, 17, 17, 17, 16, 15, 17, 17, | |
14, 16, 17, 17, 16, 17, 17, 16, 15, 17, 16, 14, 17, 16, 15, 17, 16, 17, 17, | |
16, 17, 15, 16, 17, 14, 17, 16, 15, 17, 16, 17, 13, 17, 16, 17, 17, 16, 17, | |
14, 17, 16, 17, 16, 17, 16, 17, 9, | |
]; | |
// public methods: | |
/** docced in super class **/ | |
p.getBounds = function (rect) { | |
var x = this.blurX | 0, | |
y = this.blurY | 0; | |
if (x <= 0 && y <= 0) { | |
return rect; | |
} | |
var q = Math.pow(this.quality, 0.2); | |
return (rect || new createjs.Rectangle()).pad( | |
y * q + 1, | |
x * q + 1, | |
y * q + 1, | |
x * q + 1 | |
); | |
}; | |
/** docced in super class **/ | |
p.clone = function () { | |
return new BlurFilter(this.blurX, this.blurY, this.quality); | |
}; | |
/** docced in super class **/ | |
p.toString = function () { | |
return "[BlurFilter]"; | |
}; | |
// private methods: | |
/** docced in super class **/ | |
p._applyFilter = function (imageData) { | |
var radiusX = this._blurX >> 1; | |
if (isNaN(radiusX) || radiusX < 0) return false; | |
var radiusY = this._blurY >> 1; | |
if (isNaN(radiusY) || radiusY < 0) return false; | |
if (radiusX == 0 && radiusY == 0) return false; | |
var iterations = this.quality; | |
if (isNaN(iterations) || iterations < 1) iterations = 1; | |
iterations |= 0; | |
if (iterations > 3) iterations = 3; | |
if (iterations < 1) iterations = 1; | |
var px = imageData.data; | |
var x = 0, | |
y = 0, | |
i = 0, | |
p = 0, | |
yp = 0, | |
yi = 0, | |
yw = 0, | |
r = 0, | |
g = 0, | |
b = 0, | |
a = 0, | |
pr = 0, | |
pg = 0, | |
pb = 0, | |
pa = 0; | |
var divx = (radiusX + radiusX + 1) | 0; | |
var divy = (radiusY + radiusY + 1) | 0; | |
var w = imageData.width | 0; | |
var h = imageData.height | 0; | |
var w1 = (w - 1) | 0; | |
var h1 = (h - 1) | 0; | |
var rxp1 = (radiusX + 1) | 0; | |
var ryp1 = (radiusY + 1) | 0; | |
var ssx = { r: 0, b: 0, g: 0, a: 0 }; | |
var sx = ssx; | |
for (i = 1; i < divx; i++) { | |
sx = sx.n = { r: 0, b: 0, g: 0, a: 0 }; | |
} | |
sx.n = ssx; | |
var ssy = { r: 0, b: 0, g: 0, a: 0 }; | |
var sy = ssy; | |
for (i = 1; i < divy; i++) { | |
sy = sy.n = { r: 0, b: 0, g: 0, a: 0 }; | |
} | |
sy.n = ssy; | |
var si = null; | |
var mtx = BlurFilter.MUL_TABLE[radiusX] | 0; | |
var stx = BlurFilter.SHG_TABLE[radiusX] | 0; | |
var mty = BlurFilter.MUL_TABLE[radiusY] | 0; | |
var sty = BlurFilter.SHG_TABLE[radiusY] | 0; | |
while (iterations-- > 0) { | |
yw = yi = 0; | |
var ms = mtx; | |
var ss = stx; | |
for (y = h; --y > -1; ) { | |
r = rxp1 * (pr = px[yi | 0]); | |
g = rxp1 * (pg = px[(yi + 1) | 0]); | |
b = rxp1 * (pb = px[(yi + 2) | 0]); | |
a = rxp1 * (pa = px[(yi + 3) | 0]); | |
sx = ssx; | |
for (i = rxp1; --i > -1; ) { | |
sx.r = pr; | |
sx.g = pg; | |
sx.b = pb; | |
sx.a = pa; | |
sx = sx.n; | |
} | |
for (i = 1; i < rxp1; i++) { | |
p = (yi + ((w1 < i ? w1 : i) << 2)) | 0; | |
r += sx.r = px[p]; | |
g += sx.g = px[p + 1]; | |
b += sx.b = px[p + 2]; | |
a += sx.a = px[p + 3]; | |
sx = sx.n; | |
} | |
si = ssx; | |
for (x = 0; x < w; x++) { | |
px[yi++] = (r * ms) >>> ss; | |
px[yi++] = (g * ms) >>> ss; | |
px[yi++] = (b * ms) >>> ss; | |
px[yi++] = (a * ms) >>> ss; | |
p = (yw + ((p = x + radiusX + 1) < w1 ? p : w1)) << 2; | |
r -= si.r - (si.r = px[p]); | |
g -= si.g - (si.g = px[p + 1]); | |
b -= si.b - (si.b = px[p + 2]); | |
a -= si.a - (si.a = px[p + 3]); | |
si = si.n; | |
} | |
yw += w; | |
} | |
ms = mty; | |
ss = sty; | |
for (x = 0; x < w; x++) { | |
yi = (x << 2) | 0; | |
r = (ryp1 * (pr = px[yi])) | 0; | |
g = (ryp1 * (pg = px[(yi + 1) | 0])) | 0; | |
b = (ryp1 * (pb = px[(yi + 2) | 0])) | 0; | |
a = (ryp1 * (pa = px[(yi + 3) | 0])) | 0; | |
sy = ssy; | |
for (i = 0; i < ryp1; i++) { | |
sy.r = pr; | |
sy.g = pg; | |
sy.b = pb; | |
sy.a = pa; | |
sy = sy.n; | |
} | |
yp = w; | |
for (i = 1; i <= radiusY; i++) { | |
yi = (yp + x) << 2; | |
r += sy.r = px[yi]; | |
g += sy.g = px[yi + 1]; | |
b += sy.b = px[yi + 2]; | |
a += sy.a = px[yi + 3]; | |
sy = sy.n; | |
if (i < h1) { | |
yp += w; | |
} | |
} | |
yi = x; | |
si = ssy; | |
if (iterations > 0) { | |
for (y = 0; y < h; y++) { | |
p = yi << 2; | |
px[p + 3] = pa = (a * ms) >>> ss; | |
if (pa > 0) { | |
px[p] = (r * ms) >>> ss; | |
px[p + 1] = (g * ms) >>> ss; | |
px[p + 2] = (b * ms) >>> ss; | |
} else { | |
px[p] = px[p + 1] = px[p + 2] = 0; | |
} | |
p = (x + ((p = y + ryp1) < h1 ? p : h1) * w) << 2; | |
r -= si.r - (si.r = px[p]); | |
g -= si.g - (si.g = px[p + 1]); | |
b -= si.b - (si.b = px[p + 2]); | |
a -= si.a - (si.a = px[p + 3]); | |
si = si.n; | |
yi += w; | |
} | |
} else { | |
for (y = 0; y < h; y++) { | |
p = yi << 2; | |
px[p + 3] = pa = (a * ms) >>> ss; | |
if (pa > 0) { | |
pa = 255 / pa; | |
px[p] = ((r * ms) >>> ss) * pa; | |
px[p + 1] = ((g * ms) >>> ss) * pa; | |
px[p + 2] = ((b * ms) >>> ss) * pa; | |
} else { | |
px[p] = px[p + 1] = px[p + 2] = 0; | |
} | |
p = (x + ((p = y + ryp1) < h1 ? p : h1) * w) << 2; | |
r -= si.r - (si.r = px[p]); | |
g -= si.g - (si.g = px[p + 1]); | |
b -= si.b - (si.b = px[p + 2]); | |
a -= si.a - (si.a = px[p + 3]); | |
si = si.n; | |
yi += w; | |
} | |
} | |
} | |
} | |
return true; | |
}; | |
createjs.BlurFilter = createjs.promote(BlurFilter, "Filter"); | |
})(); | |
//############################################################################## | |
// AlphaMapFilter.js | |
//############################################################################## | |
this.createjs = this.createjs || {}; | |
(function () { | |
"use strict"; | |
// constructor: | |
/** | |
* Applies a greyscale alpha map image (or canvas) to the target, such that the alpha channel of the result will | |
* be copied from the red channel of the map, and the RGB channels will be copied from the target. | |
* | |
* Generally, it is recommended that you use {{#crossLink "AlphaMaskFilter"}}{{/crossLink}}, because it has much | |
* better performance. | |
* | |
* <h4>Example</h4> | |
* This example draws a red->blue box, caches it, and then uses the cache canvas as an alpha map on a 100x100 image. | |
* | |
* var box = new createjs.Shape(); | |
* box.graphics.beginLinearGradientFill(["#ff0000", "#0000ff"], [0, 1], 0, 0, 0, 100) | |
* box.graphics.drawRect(0, 0, 100, 100); | |
* box.cache(0, 0, 100, 100); | |
* | |
* var bmp = new createjs.Bitmap("path/to/image.jpg"); | |
* bmp.filters = [ | |
* new createjs.AlphaMapFilter(box.cacheCanvas) | |
* ]; | |
* bmp.cache(0, 0, 100, 100); | |
* stage.addChild(bmp); | |
* | |
* See {{#crossLink "Filter"}}{{/crossLink}} for more information on applying filters. | |
* @class AlphaMapFilter | |
* @extends Filter | |
* @constructor | |
* @param {HTMLImageElement|HTMLCanvasElement} alphaMap The greyscale image (or canvas) to use as the alpha value for the | |
* result. This should be exactly the same dimensions as the target. | |
**/ | |
function AlphaMapFilter(alphaMap) { | |
this.Filter_constructor(); | |
// public properties: | |
/** | |
* The greyscale image (or canvas) to use as the alpha value for the result. This should be exactly the same | |
* dimensions as the target. | |
* @property alphaMap | |
* @type HTMLImageElement|HTMLCanvasElement | |
**/ | |
this.alphaMap = alphaMap; | |
// private properties: | |
/** | |
* @property _alphaMap | |
* @protected | |
* @type HTMLImageElement|HTMLCanvasElement | |
**/ | |
this._alphaMap = null; | |
/** | |
* @property _mapData | |
* @protected | |
* @type Uint8ClampedArray | |
**/ | |
this._mapData = null; | |
this._mapTexture = null; | |
this.FRAG_SHADER_BODY = | |
"uniform sampler2D uAlphaSampler;" + | |
"void main(void) {" + | |
"vec4 color = texture2D(uSampler, vRenderCoord);" + | |
"vec4 alphaMap = texture2D(uAlphaSampler, vTextureCoord);" + | |
// some image formats can have transparent white rgba(1,1,1, 0) when put on the GPU, this means we need a slight tweak | |
// using ceil ensure that the colour will be used so long as it exists but pure transparency will be treated black | |
"gl_FragColor = vec4(color.rgb, color.a * (alphaMap.r * ceil(alphaMap.a)));" + | |
"}"; | |
} | |
var p = createjs.extend(AlphaMapFilter, createjs.Filter); | |
// TODO: deprecated | |
// p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details. | |
/** docced in super class **/ | |
p.shaderParamSetup = function (gl, stage, shaderProgram) { | |
if (!this._mapTexture) { | |
this._mapTexture = gl.createTexture(); | |
} | |
gl.activeTexture(gl.TEXTURE1); | |
gl.bindTexture(gl.TEXTURE_2D, this._mapTexture); | |
stage.setTextureParams(gl); | |
gl.texImage2D( | |
gl.TEXTURE_2D, | |
0, | |
gl.RGBA, | |
gl.RGBA, | |
gl.UNSIGNED_BYTE, | |
this.alphaMap | |
); | |
gl.uniform1i(gl.getUniformLocation(shaderProgram, "uAlphaSampler"), 1); | |
}; | |
// public methods: | |
/** docced in super class **/ | |
p.clone = function () { | |
var o = new AlphaMapFilter(this.alphaMap); | |
o._alphaMap = this._alphaMap; | |
o._mapData = this._mapData; | |
return o; | |
}; | |
/** docced in super class **/ | |
p.toString = function () { | |
return "[AlphaMapFilter]"; | |
}; | |
// private methods: | |
/** docced in super class **/ | |
p._applyFilter = function (imageData) { | |
if (!this.alphaMap) { | |
return true; | |
} | |
if (!this._prepAlphaMap()) { | |
return false; | |
} | |
// TODO: update to support scenarios where the target has different dimensions. | |
var data = imageData.data; | |
var map = this._mapData; | |
for (var i = 0, l = data.length; i < l; i += 4) { | |
data[i + 3] = map[i] || 0; | |
} | |
return true; | |
}; | |
/** | |
* @method _prepAlphaMap | |
* @protected | |
**/ | |
p._prepAlphaMap = function () { | |
if (!this.alphaMap) { | |
return false; | |
} | |
if (this.alphaMap == this._alphaMap && this._mapData) { | |
return true; | |
} | |
this._mapData = null; | |
var map = (this._alphaMap = this.alphaMap); | |
var canvas = map; | |
var ctx; | |
if (map instanceof HTMLCanvasElement) { | |
ctx = canvas.getContext("2d"); | |
} else { | |
canvas = createjs.createCanvas | |
? createjs.createCanvas() | |
: document.createElement("canvas"); | |
canvas.width = map.width; | |
canvas.height = map.height; | |
ctx = canvas.getContext("2d"); | |
ctx.drawImage(map, 0, 0); | |
} | |
try { | |
var imgData = ctx.getImageData(0, 0, map.width, map.height); | |
} catch (e) { | |
//if (!this.suppressCrossDomainErrors) throw new Error("unable to access local image data: " + e); | |
return false; | |
} | |
this._mapData = imgData.data; | |
return true; | |
}; | |
createjs.AlphaMapFilter = createjs.promote(AlphaMapFilter, "Filter"); | |
})(); | |
//############################################################################## | |
// AlphaMaskFilter.js | |
//############################################################################## | |
this.createjs = this.createjs || {}; | |
(function () { | |
"use strict"; | |
// constructor: | |
/** | |
* Applies the alpha from the mask image (or canvas) to the target, such that the alpha channel of the result will | |
* be derived from the mask, and the RGB channels will be copied from the target. This can be used, for example, to | |
* apply an alpha mask to a display object. This can also be used to combine a JPG compressed RGB image with a PNG32 | |
* alpha mask, which can result in a much smaller file size than a single PNG32 containing ARGB. | |
* | |
* <b>IMPORTANT NOTE: This filter currently does not support the targetCtx, or targetX/Y parameters correctly.</b> | |
* | |
* <h4>Example</h4> | |
* This example draws a gradient box, then caches it and uses the "cacheCanvas" as the alpha mask on a 100x100 image. | |
* | |
* var box = new createjs.Shape(); | |
* box.graphics.beginLinearGradientFill(["#000000", "rgba(0, 0, 0, 0)"], [0, 1], 0, 0, 100, 100) | |
* box.graphics.drawRect(0, 0, 100, 100); | |
* box.cache(0, 0, 100, 100); | |
* | |
* var bmp = new createjs.Bitmap("path/to/image.jpg"); | |
* bmp.filters = [ | |
* new createjs.AlphaMaskFilter(box.cacheCanvas) | |
* ]; | |
* bmp.cache(0, 0, 100, 100); | |
* | |
* See {{#crossLink "Filter"}}{{/crossLink}} for more information on applying filters. | |
* @class AlphaMaskFilter | |
* @extends Filter | |
* @constructor | |
* @param {HTMLImageElement|HTMLCanvasElement} mask | |
**/ | |
function AlphaMaskFilter(mask) { | |
this.Filter_constructor(); | |
// public properties: | |
/** | |
* The image (or canvas) to use as the mask. | |
* @property mask | |
* @type HTMLImageElement|HTMLCanvasElement | |
**/ | |
this.mask = mask; | |
/** docced in super class **/ | |
this.usesContext = true; | |
this.FRAG_SHADER_BODY = | |
"uniform sampler2D uAlphaSampler;" + | |
"void main(void) {" + | |
"vec4 color = texture2D(uSampler, vRenderCoord);" + | |
"vec4 alphaMap = texture2D(uAlphaSampler, vTextureCoord);" + | |
"gl_FragColor = vec4(color.rgb, color.a * alphaMap.a);" + | |
"}"; | |
} | |
var p = createjs.extend(AlphaMaskFilter, createjs.Filter); | |
// TODO: deprecated | |
// p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details. | |
/** docced in super class **/ | |
p.shaderParamSetup = function (gl, stage, shaderProgram) { | |
if (!this._mapTexture) { | |
this._mapTexture = gl.createTexture(); | |
} | |
gl.activeTexture(gl.TEXTURE1); | |
gl.bindTexture(gl.TEXTURE_2D, this._mapTexture); | |
stage.setTextureParams(gl); | |
gl.texImage2D( | |
gl.TEXTURE_2D, | |
0, | |
gl.RGBA, | |
gl.RGBA, | |
gl.UNSIGNED_BYTE, | |
this.mask | |
); | |
gl.uniform1i(gl.getUniformLocation(shaderProgram, "uAlphaSampler"), 1); | |
}; | |
// public methods: | |
/** | |
* Applies the filter to the specified context. | |
* | |
* <strong>IMPORTANT NOTE: This filter currently does not support the targetCtx, or targetX/Y parameters | |
* correctly.</strong> | |
* @method applyFilter | |
* @param {CanvasRenderingContext2D} ctx The 2D context to use as the source. | |
* @param {Number} x The x position to use for the source rect. | |
* @param {Number} y The y position to use for the source rect. | |
* @param {Number} width The width to use for the source rect. | |
* @param {Number} height The height to use for the source rect. | |
* @param {CanvasRenderingContext2D} [targetCtx] NOT SUPPORTED IN THIS FILTER. The 2D context to draw the result to. Defaults to the context passed to ctx. | |
* @param {Number} [targetX] NOT SUPPORTED IN THIS FILTER. The x position to draw the result to. Defaults to the value passed to x. | |
* @param {Number} [targetY] NOT SUPPORTED IN THIS FILTER. The y position to draw the result to. Defaults to the value passed to y. | |
* @return {Boolean} If the filter was applied successfully. | |
**/ | |
p.applyFilter = function ( | |
ctx, | |
x, | |
y, | |
width, | |
height, | |
targetCtx, | |
targetX, | |
targetY | |
) { | |
if (!this.mask) { | |
return true; | |
} | |
targetCtx = targetCtx || ctx; | |
if (targetX == null) { | |
targetX = x; | |
} | |
if (targetY == null) { | |
targetY = y; | |
} | |
targetCtx.save(); | |
if (ctx != targetCtx) { | |
// TODO: support targetCtx and targetX/Y | |
// clearRect, then draw the ctx in? | |
return false; | |
} | |
targetCtx.globalCompositeOperation = "destination-in"; | |
targetCtx.drawImage(this.mask, targetX, targetY); | |
targetCtx.restore(); | |
return true; | |
}; | |
/** docced in super class **/ | |
p.clone = function () { | |
return new AlphaMaskFilter(this.mask); | |
}; | |
/** docced in super class **/ | |
p.toString = function () { | |
return "[AlphaMaskFilter]"; | |
}; | |
createjs.AlphaMaskFilter = createjs.promote(AlphaMaskFilter, "Filter"); | |
})(); | |
//############################################################################## | |
// ColorFilter.js | |
//############################################################################## | |
this.createjs = this.createjs || {}; | |
(function () { | |
"use strict"; | |
// constructor: | |
/** | |
* Applies a color transform to DisplayObjects. | |
* | |
* <h4>Example</h4> | |
* This example draws a red circle, and then transforms it to Blue. This is accomplished by multiplying all the channels | |
* to 0 (except alpha, which is set to 1), and then adding 255 to the blue channel. | |
* | |
* var shape = new createjs.Shape().set({x:100,y:100}); | |
* shape.graphics.beginFill("#ff0000").drawCircle(0,0,50); | |
* | |
* shape.filters = [ | |
* new createjs.ColorFilter(0,0,0,1, 0,0,255,0) | |
* ]; | |
* shape.cache(-50, -50, 100, 100); | |
* | |
* See {{#crossLink "Filter"}}{{/crossLink}} for an more information on applying filters. | |
* @class ColorFilter | |
* @param {Number} [redMultiplier=1] The amount to multiply against the red channel. This is a range between 0 and 1. | |
* @param {Number} [greenMultiplier=1] The amount to multiply against the green channel. This is a range between 0 and 1. | |
* @param {Number} [blueMultiplier=1] The amount to multiply against the blue channel. This is a range between 0 and 1. | |
* @param {Number} [alphaMultiplier=1] The amount to multiply against the alpha channel. This is a range between 0 and 1. | |
* @param {Number} [redOffset=0] The amount to add to the red channel after it has been multiplied. This is a range | |
* between -255 and 255. | |
* @param {Number} [greenOffset=0] The amount to add to the green channel after it has been multiplied. This is a range | |
* between -255 and 255. | |
* @param {Number} [blueOffset=0] The amount to add to the blue channel after it has been multiplied. This is a range | |
* between -255 and 255. | |
* @param {Number} [alphaOffset=0] The amount to add to the alpha channel after it has been multiplied. This is a range | |
* between -255 and 255. | |
* @constructor | |
* @extends Filter | |
**/ | |
function ColorFilter( | |
redMultiplier, | |
greenMultiplier, | |
blueMultiplier, | |
alphaMultiplier, | |
redOffset, | |
greenOffset, | |
blueOffset, | |
alphaOffset | |
) { | |
this.Filter_constructor(); | |
// public properties: | |
/** | |
* Red channel multiplier. | |
* @property redMultiplier | |
* @type Number | |
**/ | |
this.redMultiplier = redMultiplier != null ? redMultiplier : 1; | |
/** | |
* Green channel multiplier. | |
* @property greenMultiplier | |
* @type Number | |
**/ | |
this.greenMultiplier = greenMultiplier != null ? greenMultiplier : 1; | |
/** | |
* Blue channel multiplier. | |
* @property blueMultiplier | |
* @type Number | |
**/ | |
this.blueMultiplier = blueMultiplier != null ? blueMultiplier : 1; | |
/** | |
* Alpha channel multiplier. | |
* @property alphaMultiplier | |
* @type Number | |
**/ | |
this.alphaMultiplier = alphaMultiplier != null ? alphaMultiplier : 1; | |
/** | |
* Red channel offset (added to value). | |
* @property redOffset | |
* @type Number | |
**/ | |
this.redOffset = redOffset || 0; | |
/** | |
* Green channel offset (added to value). | |
* @property greenOffset | |
* @type Number | |
**/ | |
this.greenOffset = greenOffset || 0; | |
/** | |
* Blue channel offset (added to value). | |
* @property blueOffset | |
* @type Number | |
**/ | |
this.blueOffset = blueOffset || 0; | |
/** | |
* Alpha channel offset (added to value). | |
* @property alphaOffset | |
* @type Number | |
**/ | |
this.alphaOffset = alphaOffset || 0; | |
this.FRAG_SHADER_BODY = | |
"uniform vec4 uColorMultiplier;" + | |
"uniform vec4 uColorOffset;" + | |
"void main(void) {" + | |
"vec4 color = texture2D(uSampler, vRenderCoord);" + | |
"gl_FragColor = (color * uColorMultiplier) + uColorOffset;" + | |
"}"; | |
} | |
var p = createjs.extend(ColorFilter, createjs.Filter); | |
// TODO: deprecated | |
// p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details. | |
// public methods: | |
/** docced in super class **/ | |
p.shaderParamSetup = function (gl, stage, shaderProgram) { | |
gl.uniform4f( | |
gl.getUniformLocation(shaderProgram, "uColorMultiplier"), | |
this.redMultiplier, | |
this.greenMultiplier, | |
this.blueMultiplier, | |
this.alphaMultiplier | |
); | |
gl.uniform4f( | |
gl.getUniformLocation(shaderProgram, "uColorOffset"), | |
this.redOffset / 255, | |
this.greenOffset / 255, | |
this.blueOffset / 255, | |
this.alphaOffset / 255 | |
); | |
}; | |
/** docced in super class **/ | |
p.toString = function () { | |
return "[ColorFilter]"; | |
}; | |
/** docced in super class **/ | |
p.clone = function () { | |
return new ColorFilter( | |
this.redMultiplier, | |
this.greenMultiplier, | |
this.blueMultiplier, | |
this.alphaMultiplier, | |
this.redOffset, | |
this.greenOffset, | |
this.blueOffset, | |
this.alphaOffset | |
); | |
}; | |
// private methods: | |
/** docced in super class **/ | |
p._applyFilter = function (imageData) { | |
var data = imageData.data; | |
var l = data.length; | |
for (var i = 0; i < l; i += 4) { | |
data[i] = data[i] * this.redMultiplier + this.redOffset; | |
data[i + 1] = data[i + 1] * this.greenMultiplier + this.greenOffset; | |
data[i + 2] = data[i + 2] * this.blueMultiplier + this.blueOffset; | |
data[i + 3] = data[i + 3] * this.alphaMultiplier + this.alphaOffset; | |
} | |
return true; | |
}; | |
createjs.ColorFilter = createjs.promote(ColorFilter, "Filter"); | |
})(); | |
//############################################################################## | |
// ColorMatrix.js | |
//############################################################################## | |
this.createjs = this.createjs || {}; | |
(function () { | |
"use strict"; | |
// constructor: | |
/** | |
* Provides helper functions for assembling a matrix for use with the {{#crossLink "ColorMatrixFilter"}}{{/crossLink}}. | |
* Most methods return the instance to facilitate chained calls. | |
* | |
* <h4>Example</h4> | |
* | |
* myColorMatrix.adjustHue(20).adjustBrightness(50); | |
* | |
* See {{#crossLink "Filter"}}{{/crossLink}} for an example of how to apply filters, or {{#crossLink "ColorMatrixFilter"}}{{/crossLink}} | |
* for an example of how to use ColorMatrix to change a DisplayObject's color. | |
* @class ColorMatrix | |
* @param {Number} brightness | |
* @param {Number} contrast | |
* @param {Number} saturation | |
* @param {Number} hue | |
* @constructor | |
**/ | |
function ColorMatrix(brightness, contrast, saturation, hue) { | |
this.setColor(brightness, contrast, saturation, hue); | |
} | |
var p = ColorMatrix.prototype; | |
// constants: | |
/** | |
* Array of delta values for contrast calculations. | |
* @property DELTA_INDEX | |
* @type Array | |
* @protected | |
* @static | |
**/ | |
ColorMatrix.DELTA_INDEX = [ | |
0, 0.01, 0.02, 0.04, 0.05, 0.06, 0.07, 0.08, 0.1, 0.11, 0.12, 0.14, 0.15, | |
0.16, 0.17, 0.18, 0.2, 0.21, 0.22, 0.24, 0.25, 0.27, 0.28, 0.3, 0.32, 0.34, | |
0.36, 0.38, 0.4, 0.42, 0.44, 0.46, 0.48, 0.5, 0.53, 0.56, 0.59, 0.62, 0.65, | |
0.68, 0.71, 0.74, 0.77, 0.8, 0.83, 0.86, 0.89, 0.92, 0.95, 0.98, 1.0, 1.06, | |
1.12, 1.18, 1.24, 1.3, 1.36, 1.42, 1.48, 1.54, 1.6, 1.66, 1.72, 1.78, 1.84, | |
1.9, 1.96, 2.0, 2.12, 2.25, 2.37, 2.5, 2.62, 2.75, 2.87, 3.0, 3.2, 3.4, 3.6, | |
3.8, 4.0, 4.3, 4.7, 4.9, 5.0, 5.5, 6.0, 6.5, 6.8, 7.0, 7.3, 7.5, 7.8, 8.0, | |
8.4, 8.7, 9.0, 9.4, 9.6, 9.8, 10.0, | |
]; | |
/** | |
* Identity matrix values. | |
* @property IDENTITY_MATRIX | |
* @type Array | |
* @protected | |
* @static | |
**/ | |
ColorMatrix.IDENTITY_MATRIX = [ | |
1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, | |
]; | |
/** | |
* The constant length of a color matrix. | |
* @property LENGTH | |
* @type Number | |
* @protected | |
* @static | |
**/ | |
ColorMatrix.LENGTH = ColorMatrix.IDENTITY_MATRIX.length; | |
// public methods: | |
/** | |
* Resets the instance with the specified values. | |
* @method setColor | |
* @param {Number} brightness | |
* @param {Number} contrast | |
* @param {Number} saturation | |
* @param {Number} hue | |
* @return {ColorMatrix} The ColorMatrix instance the method is called on (useful for chaining calls.) | |
* @chainable | |
*/ | |
p.setColor = function (brightness, contrast, saturation, hue) { | |
return this.reset().adjustColor(brightness, contrast, saturation, hue); | |
}; | |
/** | |
* Resets the matrix to identity values. | |
* @method reset | |
* @return {ColorMatrix} The ColorMatrix instance the method is called on (useful for chaining calls.) | |
* @chainable | |
*/ | |
p.reset = function () { | |
return this.copy(ColorMatrix.IDENTITY_MATRIX); | |
}; | |
/** | |
* Shortcut method to adjust brightness, contrast, saturation and hue. | |
* Equivalent to calling adjustHue(hue), adjustContrast(contrast), | |
* adjustBrightness(brightness), adjustSaturation(saturation), in that order. | |
* @method adjustColor | |
* @param {Number} brightness | |
* @param {Number} contrast | |
* @param {Number} saturation | |
* @param {Number} hue | |
* @return {ColorMatrix} The ColorMatrix instance the method is called on (useful for chaining calls.) | |
* @chainable | |
**/ | |
p.adjustColor = function (brightness, contrast, saturation, hue) { | |
this.adjustHue(hue); | |
this.adjustContrast(contrast); | |
this.adjustBrightness(brightness); | |
return this.adjustSaturation(saturation); | |
}; | |
/** | |
* Adjusts the brightness of pixel color by adding the specified value to the red, green and blue channels. | |
* Positive values will make the image brighter, negative values will make it darker. | |
* @method adjustBrightness | |
* @param {Number} value A value between -255 & 255 that will be added to the RGB channels. | |
* @return {ColorMatrix} The ColorMatrix instance the method is called on (useful for chaining calls.) | |
* @chainable | |
**/ | |
p.adjustBrightness = function (value) { | |
if (value == 0 || isNaN(value)) { | |
return this; | |
} | |
value = this._cleanValue(value, 255); | |
this._multiplyMatrix([ | |
1, | |
0, | |
0, | |
0, | |
value, | |
0, | |
1, | |
0, | |
0, | |
value, | |
0, | |
0, | |
1, | |
0, | |
value, | |
0, | |
0, | |
0, | |
1, | |
0, | |
0, | |
0, | |
0, | |
0, | |
1, | |
]); | |
return this; | |
}; | |
/** | |
* Adjusts the contrast of pixel color. | |
* Positive values will increase contrast, negative values will decrease contrast. | |
* @method adjustContrast | |
* @param {Number} value A value between -100 & 100. | |
* @return {ColorMatrix} The ColorMatrix instance the method is called on (useful for chaining calls.) | |
* @chainable | |
**/ | |
p.adjustContrast = function (value) { | |
if (value == 0 || isNaN(value)) { | |
return this; | |
} | |
value = this._cleanValue(value, 100); | |
var x; | |
if (value < 0) { | |
x = 127 + (value / 100) * 127; | |
} else { | |
x = value % 1; | |
if (x == 0) { | |
x = ColorMatrix.DELTA_INDEX[value]; | |
} else { | |
x = | |
ColorMatrix.DELTA_INDEX[value << 0] * (1 - x) + | |
ColorMatrix.DELTA_INDEX[(value << 0) + 1] * x; // use linear interpolation for more granularity. | |
} | |
x = x * 127 + 127; | |
} | |
this._multiplyMatrix([ | |
x / 127, | |
0, | |
0, | |
0, | |
0.5 * (127 - x), | |
0, | |
x / 127, | |
0, | |
0, | |
0.5 * (127 - x), | |
0, | |
0, | |
x / 127, | |
0, | |
0.5 * (127 - x), | |
0, | |
0, | |
0, | |
1, | |
0, | |
0, | |
0, | |
0, | |
0, | |
1, | |
]); | |
return this; | |
}; | |
/** | |
* Adjusts the color saturation of the pixel. | |
* Positive values will increase saturation, negative values will decrease saturation (trend towards greyscale). | |
* @method adjustSaturation | |
* @param {Number} value A value between -100 & 100. | |
* @return {ColorMatrix} The ColorMatrix instance the method is called on (useful for chaining calls.) | |
* @chainable | |
**/ | |
p.adjustSaturation = function (value) { | |
if (value == 0 || isNaN(value)) { | |
return this; | |
} | |
value = this._cleanValue(value, 100); | |
var x = 1 + (value > 0 ? (3 * value) / 100 : value / 100); | |
var lumR = 0.3086; | |
var lumG = 0.6094; | |
var lumB = 0.082; | |
this._multiplyMatrix([ | |
lumR * (1 - x) + x, | |
lumG * (1 - x), | |
lumB * (1 - x), | |
0, | |
0, | |
lumR * (1 - x), | |
lumG * (1 - x) + x, | |
lumB * (1 - x), | |
0, | |
0, | |
lumR * (1 - x), | |
lumG * (1 - x), | |
lumB * (1 - x) + x, | |
0, | |
0, | |
0, | |
0, | |
0, | |
1, | |
0, | |
0, | |
0, | |
0, | |
0, | |
1, | |
]); | |
return this; | |
}; | |
/** | |
* Adjusts the hue of the pixel color. | |
* @method adjustHue | |
* @param {Number} value A value between -180 & 180. | |
* @return {ColorMatrix} The ColorMatrix instance the method is called on (useful for chaining calls.) | |
* @chainable | |
**/ | |
p.adjustHue = function (value) { | |
if (value == 0 || isNaN(value)) { | |
return this; | |
} | |
value = (this._cleanValue(value, 180) / 180) * Math.PI; | |
var cosVal = Math.cos(value); | |
var sinVal = Math.sin(value); | |
var lumR = 0.213; | |
var lumG = 0.715; | |
var lumB = 0.072; | |
this._multiplyMatrix([ | |
lumR + cosVal * (1 - lumR) + sinVal * -lumR, | |
lumG + cosVal * -lumG + sinVal * -lumG, | |
lumB + cosVal * -lumB + sinVal * (1 - lumB), | |
0, | |
0, | |
lumR + cosVal * -lumR + sinVal * 0.143, | |
lumG + cosVal * (1 - lumG) + sinVal * 0.14, | |
lumB + cosVal * -lumB + sinVal * -0.283, | |
0, | |
0, | |
lumR + cosVal * -lumR + sinVal * -(1 - lumR), | |
lumG + cosVal * -lumG + sinVal * lumG, | |
lumB + cosVal * (1 - lumB) + sinVal * lumB, | |
0, | |
0, | |
0, | |
0, | |
0, | |
1, | |
0, | |
0, | |
0, | |
0, | |
0, | |
1, | |
]); | |
return this; | |
}; | |
/** | |
* Concatenates (multiplies) the specified matrix with this one. | |
* @method concat | |
* @param {Array} matrix An array or ColorMatrix instance. | |
* @return {ColorMatrix} The ColorMatrix instance the method is called on (useful for chaining calls.) | |
* @chainable | |
**/ | |
p.concat = function (matrix) { | |
matrix = this._fixMatrix(matrix); | |
if (matrix.length != ColorMatrix.LENGTH) { | |
return this; | |
} | |
this._multiplyMatrix(matrix); | |
return this; | |
}; | |
/** | |
* Returns a clone of this ColorMatrix. | |
* @method clone | |
* @return {ColorMatrix} A clone of this ColorMatrix. | |
**/ | |
p.clone = function () { | |
return new ColorMatrix().copy(this); | |
}; | |
/** | |
* Return a length 25 (5x5) array instance containing this matrix's values. | |
* @method toArray | |
* @return {Array} An array holding this matrix's values. | |
**/ | |
p.toArray = function () { | |
var arr = []; | |
for (var i = 0, l = ColorMatrix.LENGTH; i < l; i++) { | |
arr[i] = this[i]; | |
} | |
return arr; | |
}; | |
/** | |
* Copy the specified matrix's values to this matrix. | |
* @method copy | |
* @param {Array} matrix An array or ColorMatrix instance. | |
* @return {ColorMatrix} The ColorMatrix instance the method is called on (useful for chaining calls.) | |
* @chainable | |
**/ | |
p.copy = function (matrix) { | |
var l = ColorMatrix.LENGTH; | |
for (var i = 0; i < l; i++) { | |
this[i] = matrix[i]; | |
} | |
return this; | |
}; | |
/** | |
* Returns a string representation of this object. | |
* @method toString | |
* @return {String} a string representation of the instance. | |
**/ | |
p.toString = function () { | |
return "[ColorMatrix]"; | |
}; | |
// private methods: | |
/** | |
* @method _multiplyMatrix | |
* @param {Array} matrix | |
* @protected | |
**/ | |
p._multiplyMatrix = function (matrix) { | |
var i, | |
j, | |
k, | |
col = []; | |
for (i = 0; i < 5; i++) { | |
for (j = 0; j < 5; j++) { | |
col[j] = this[j + i * 5]; | |
} | |
for (j = 0; j < 5; j++) { | |
var val = 0; | |
for (k = 0; k < 5; k++) { | |
val += matrix[j + k * 5] * col[k]; | |
} | |
this[j + i * 5] = val; | |
} | |
} | |
}; | |
/** | |
* Make sure values are within the specified range, hue has a limit of 180, brightness is 255, others are 100. | |
* @method _cleanValue | |
* @param {Number} value The raw number | |
* @param {Number} limit The maximum that the number can be. The minimum is the limit * -1. | |
* @protected | |
**/ | |
p._cleanValue = function (value, limit) { | |
return Math.min(limit, Math.max(-limit, value)); | |
}; | |
/** | |
* Makes sure matrixes are 5x5 (25 long). | |
* @method _fixMatrix | |
* @param {Array} matrix | |
* @protected | |
**/ | |
p._fixMatrix = function (matrix) { | |
if (matrix instanceof ColorMatrix) { | |
matrix = matrix.toArray(); | |
} | |
if (matrix.length < ColorMatrix.LENGTH) { | |
matrix = matrix | |
.slice(0, matrix.length) | |
.concat( | |
ColorMatrix.IDENTITY_MATRIX.slice(matrix.length, ColorMatrix.LENGTH) | |
); | |
} else if (matrix.length > ColorMatrix.LENGTH) { | |
matrix = matrix.slice(0, ColorMatrix.LENGTH); | |
} | |
return matrix; | |
}; | |
createjs.ColorMatrix = ColorMatrix; | |
})(); | |
//############################################################################## | |
// ColorMatrixFilter.js | |
//############################################################################## | |
this.createjs = this.createjs || {}; | |
(function () { | |
"use strict"; | |
// constructor: | |
/** | |
* Allows you to carry out complex color operations such as modifying saturation, brightness, or inverting. See the | |
* {{#crossLink "ColorMatrix"}}{{/crossLink}} for more information on changing colors. For an easier color transform, | |
* consider the {{#crossLink "ColorFilter"}}{{/crossLink}}. | |
* | |
* <h4>Example</h4> | |
* This example creates a red circle, inverts its hue, and then saturates it to brighten it up. | |
* | |
* var shape = new createjs.Shape().set({x:100,y:100}); | |
* shape.graphics.beginFill("#ff0000").drawCircle(0,0,50); | |
* | |
* var matrix = new createjs.ColorMatrix().adjustHue(180).adjustSaturation(100); | |
* shape.filters = [ | |
* new createjs.ColorMatrixFilter(matrix) | |
* ]; | |
* | |
* shape.cache(-50, -50, 100, 100); | |
* | |
* See {{#crossLink "Filter"}}{{/crossLink}} for an more information on applying filters. | |
* @class ColorMatrixFilter | |
* @constructor | |
* @extends Filter | |
* @param {Array | ColorMatrix} matrix A 4x5 matrix describing the color operation to perform. See also the {{#crossLink "ColorMatrix"}}{{/crossLink}} | |
* class. | |
**/ | |
function ColorMatrixFilter(matrix) { | |
this.Filter_constructor(); | |
// public properties: | |
/** | |
* A 4x5 matrix describing the color operation to perform. See also the {{#crossLink "ColorMatrix"}}{{/crossLink}} | |
* @property matrix | |
* @type Array | ColorMatrix | |
**/ | |
this.matrix = matrix; | |
this.FRAG_SHADER_BODY = | |
"uniform mat4 uColorMatrix;" + | |
"uniform vec4 uColorMatrixOffset;" + | |
"void main(void) {" + | |
"vec4 color = texture2D(uSampler, vRenderCoord);" + | |
"mat4 m = uColorMatrix;" + | |
"vec4 newColor = vec4(0,0,0,0);" + | |
"newColor.r = color.r*m[0][0] + color.g*m[0][1] + color.b*m[0][2] + color.a*m[0][3];" + | |
"newColor.g = color.r*m[1][0] + color.g*m[1][1] + color.b*m[1][2] + color.a*m[1][3];" + | |
"newColor.b = color.r*m[2][0] + color.g*m[2][1] + color.b*m[2][2] + color.a*m[2][3];" + | |
"newColor.a = color.r*m[3][0] + color.g*m[3][1] + color.b*m[3][2] + color.a*m[3][3];" + | |
"gl_FragColor = newColor + uColorMatrixOffset;" + | |
"}"; | |
} | |
var p = createjs.extend(ColorMatrixFilter, createjs.Filter); | |
// TODO: deprecated | |
// p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details. | |
/** docced in super class **/ | |
p.shaderParamSetup = function (gl, stage, shaderProgram) { | |
var mat = this.matrix; | |
var colorMatrix = new Float32Array([ | |
mat[0], | |
mat[1], | |
mat[2], | |
mat[3], | |
mat[5], | |
mat[6], | |
mat[7], | |
mat[8], | |
mat[10], | |
mat[11], | |
mat[12], | |
mat[13], | |
mat[15], | |
mat[16], | |
mat[17], | |
mat[18], | |
]); | |
gl.uniformMatrix4fv( | |
gl.getUniformLocation(shaderProgram, "uColorMatrix"), | |
false, | |
colorMatrix | |
); | |
gl.uniform4f( | |
gl.getUniformLocation(shaderProgram, "uColorMatrixOffset"), | |
mat[4] / 255, | |
mat[9] / 255, | |
mat[14] / 255, | |
mat[19] / 255 | |
); | |
}; | |
// public methods: | |
/** docced in super class **/ | |
p.toString = function () { | |
return "[ColorMatrixFilter]"; | |
}; | |
/** docced in super class **/ | |
p.clone = function () { | |
return new ColorMatrixFilter(this.matrix); | |
}; | |
// private methods: | |
/** docced in super class **/ | |
p._applyFilter = function (imageData) { | |
var data = imageData.data; | |
var l = data.length; | |
var r, g, b, a; | |
var mtx = this.matrix; | |
var m0 = mtx[0], | |
m1 = mtx[1], | |
m2 = mtx[2], | |
m3 = mtx[3], | |
m4 = mtx[4]; | |
var m5 = mtx[5], | |
m6 = mtx[6], | |
m7 = mtx[7], | |
m8 = mtx[8], | |
m9 = mtx[9]; | |
var m10 = mtx[10], | |
m11 = mtx[11], | |
m12 = mtx[12], | |
m13 = mtx[13], | |
m14 = mtx[14]; | |
var m15 = mtx[15], | |
m16 = mtx[16], | |
m17 = mtx[17], | |
m18 = mtx[18], | |
m19 = mtx[19]; | |
for (var i = 0; i < l; i += 4) { | |
r = data[i]; | |
g = data[i + 1]; | |
b = data[i + 2]; | |
a = data[i + 3]; | |
data[i] = r * m0 + g * m1 + b * m2 + a * m3 + m4; // red | |
data[i + 1] = r * m5 + g * m6 + b * m7 + a * m8 + m9; // green | |
data[i + 2] = r * m10 + g * m11 + b * m12 + a * m13 + m14; // blue | |
data[i + 3] = r * m15 + g * m16 + b * m17 + a * m18 + m19; // alpha | |
} | |
return true; | |
}; | |
createjs.ColorMatrixFilter = createjs.promote(ColorMatrixFilter, "Filter"); | |
})(); | |
//############################################################################## | |
// Touch.js | |
//############################################################################## | |
this.createjs = this.createjs || {}; | |
(function () { | |
"use strict"; | |
// constructor: | |
/** | |
* Global utility for working with multi-touch enabled devices in EaselJS. Currently supports W3C Touch API (iOS and | |
* modern Android browser) and the Pointer API (IE), including ms-prefixed events in IE10, and unprefixed in IE11. | |
* | |
* Ensure that you {{#crossLink "Touch/disable"}}{{/crossLink}} touch when cleaning up your application. You do not have | |
* to check if touch is supported to enable it, as it will fail gracefully if it is not supported. | |
* | |
* <h4>Example</h4> | |
* | |
* var stage = new createjs.Stage("canvasId"); | |
* createjs.Touch.enable(stage); | |
* | |
* <strong>Note:</strong> It is important to disable Touch on a stage that you are no longer using: | |
* | |
* createjs.Touch.disable(stage); | |
* | |
* @class Touch | |
* @static | |
**/ | |
function Touch() { | |
throw "Touch cannot be instantiated"; | |
} | |
// public static methods: | |
/** | |
* Returns `true` if touch is supported in the current browser. | |
* @method isSupported | |
* @return {Boolean} Indicates whether touch is supported in the current browser. | |
* @static | |
**/ | |
Touch.isSupported = function () { | |
return !!( | |
"ontouchstart" in window || // iOS & Android | |
(window.navigator["msPointerEnabled"] && | |
window.navigator["msMaxTouchPoints"] > 0) || // IE10 | |
(window.navigator["pointerEnabled"] && | |
window.navigator["maxTouchPoints"] > 0) | |
); // IE11+ | |
}; | |
/** | |
* Enables touch interaction for the specified EaselJS {{#crossLink "Stage"}}{{/crossLink}}. Currently supports iOS | |
* (and compatible browsers, such as modern Android browsers), and IE10/11. Supports both single touch and | |
* multi-touch modes. Extends the EaselJS {{#crossLink "MouseEvent"}}{{/crossLink}} model, but without support for | |
* double click or over/out events. See the MouseEvent {{#crossLink "MouseEvent/pointerId:property"}}{{/crossLink}} | |
* for more information. | |
* @method enable | |
* @param {Stage} stage The {{#crossLink "Stage"}}{{/crossLink}} to enable touch on. | |
* @param {Boolean} [singleTouch=false] If `true`, only a single touch will be active at a time. | |
* @param {Boolean} [allowDefault=false] If `true`, then default gesture actions (ex. scrolling, zooming) will be | |
* allowed when the user is interacting with the target canvas. | |
* @return {Boolean} Returns `true` if touch was successfully enabled on the target stage. | |
* @static | |
**/ | |
Touch.enable = function (stage, singleTouch, allowDefault) { | |
if (!stage || !stage.canvas || !Touch.isSupported()) { | |
return false; | |
} | |
if (stage.__touch) { | |
return true; | |
} | |
// inject required properties on stage: | |
stage.__touch = { | |
pointers: {}, | |
multitouch: !singleTouch, | |
preventDefault: !allowDefault, | |
count: 0, | |
}; | |
// note that in the future we may need to disable the standard mouse event model before adding | |
// these to prevent duplicate calls. It doesn't seem to be an issue with iOS devices though. | |
if ("ontouchstart" in window) { | |
Touch._IOS_enable(stage); | |
} else if ( | |
window.navigator["msPointerEnabled"] || | |
window.navigator["pointerEnabled"] | |
) { | |
Touch._IE_enable(stage); | |
} | |
return true; | |
}; | |
/** | |
* Removes all listeners that were set up when calling `Touch.enable()` on a stage. | |
* @method disable | |
* @param {Stage} stage The {{#crossLink "Stage"}}{{/crossLink}} to disable touch on. | |
* @static | |
**/ | |
Touch.disable = function (stage) { | |
if (!stage) { | |
return; | |
} | |
if ("ontouchstart" in window) { | |
Touch._IOS_disable(stage); | |
} else if ( | |
window.navigator["msPointerEnabled"] || | |
window.navigator["pointerEnabled"] | |
) { | |
Touch._IE_disable(stage); | |
} | |
delete stage.__touch; | |
}; | |
// Private static methods: | |
/** | |
* @method _IOS_enable | |
* @protected | |
* @param {Stage} stage | |
* @static | |
**/ | |
Touch._IOS_enable = function (stage) { | |
var canvas = stage.canvas; | |
var f = (stage.__touch.f = function (e) { | |
Touch._IOS_handleEvent(stage, e); | |
}); | |
canvas.addEventListener("touchstart", f, false); | |
canvas.addEventListener("touchmove", f, false); | |
canvas.addEventListener("touchend", f, false); | |
canvas.addEventListener("touchcancel", f, false); | |
}; | |
/** | |
* @method _IOS_disable | |
* @protected | |
* @param {Stage} stage | |
* @static | |
**/ | |
Touch._IOS_disable = function (stage) { | |
var canvas = stage.canvas; | |
if (!canvas) { | |
return; | |
} | |
var f = stage.__touch.f; | |
canvas.removeEventListener("touchstart", f, false); | |
canvas.removeEventListener("touchmove", f, false); | |
canvas.removeEventListener("touchend", f, false); | |
canvas.removeEventListener("touchcancel", f, false); | |
}; | |
/** | |
* @method _IOS_handleEvent | |
* @param {Stage} stage | |
* @param {Object} e The event to handle | |
* @protected | |
* @static | |
**/ | |
Touch._IOS_handleEvent = function (stage, e) { | |
if (!stage) { | |
return; | |
} | |
if (stage.__touch.preventDefault) { | |
e.preventDefault && e.preventDefault(); | |
} | |
var touches = e.changedTouches; | |
var type = e.type; | |
for (var i = 0, l = touches.length; i < l; i++) { | |
var touch = touches[i]; | |
var id = touch.identifier; | |
if (touch.target != stage.canvas) { | |
continue; | |
} | |
if (type == "touchstart") { | |
this._handleStart(stage, id, e, touch.pageX, touch.pageY); | |
} else if (type == "touchmove") { | |
this._handleMove(stage, id, e, touch.pageX, touch.pageY); | |
} else if (type == "touchend" || type == "touchcancel") { | |
this._handleEnd(stage, id, e); | |
} | |
} | |
}; | |
/** | |
* @method _IE_enable | |
* @protected | |
* @param {Stage} stage | |
* @static | |
**/ | |
Touch._IE_enable = function (stage) { | |
var canvas = stage.canvas; | |
var f = (stage.__touch.f = function (e) { | |
Touch._IE_handleEvent(stage, e); | |
}); | |
if (window.navigator["pointerEnabled"] === undefined) { | |
canvas.addEventListener("MSPointerDown", f, false); | |
window.addEventListener("MSPointerMove", f, false); | |
window.addEventListener("MSPointerUp", f, false); | |
window.addEventListener("MSPointerCancel", f, false); | |
if (stage.__touch.preventDefault) { | |
canvas.style.msTouchAction = "none"; | |
} | |
} else { | |
canvas.addEventListener("pointerdown", f, false); | |
window.addEventListener("pointermove", f, false); | |
window.addEventListener("pointerup", f, false); | |
window.addEventListener("pointercancel", f, false); | |
if (stage.__touch.preventDefault) { | |
canvas.style.touchAction = "none"; | |
} | |
} | |
stage.__touch.activeIDs = {}; | |
}; | |
/** | |
* @method _IE_disable | |
* @protected | |
* @param {Stage} stage | |
* @static | |
**/ | |
Touch._IE_disable = function (stage) { | |
var f = stage.__touch.f; | |
if (window.navigator["pointerEnabled"] === undefined) { | |
window.removeEventListener("MSPointerMove", f, false); | |
window.removeEventListener("MSPointerUp", f, false); | |
window.removeEventListener("MSPointerCancel", f, false); | |
if (stage.canvas) { | |
stage.canvas.removeEventListener("MSPointerDown", f, false); | |
} | |
} else { | |
window.removeEventListener("pointermove", f, false); | |
window.removeEventListener("pointerup", f, false); | |
window.removeEventListener("pointercancel", f, false); | |
if (stage.canvas) { | |
stage.canvas.removeEventListener("pointerdown", f, false); | |
} | |
} | |
}; | |
/** | |
* @method _IE_handleEvent | |
* @param {Stage} stage | |
* @param {Object} e The event to handle. | |
* @protected | |
* @static | |
**/ | |
Touch._IE_handleEvent = function (stage, e) { | |
if (!stage) { | |
return; | |
} | |
if (stage.__touch.preventDefault) { | |
e.preventDefault && e.preventDefault(); | |
} | |
var type = e.type; | |
var id = e.pointerId; | |
var ids = stage.__touch.activeIDs; | |
if (type == "MSPointerDown" || type == "pointerdown") { | |
if (e.srcElement != stage.canvas) { | |
return; | |
} | |
ids[id] = true; | |
this._handleStart(stage, id, e, e.pageX, e.pageY); | |
} else if (ids[id]) { | |
// it's an id we're watching | |
if (type == "MSPointerMove" || type == "pointermove") { | |
this._handleMove(stage, id, e, e.pageX, e.pageY); | |
} else if ( | |
type == "MSPointerUp" || | |
type == "MSPointerCancel" || | |
type == "pointerup" || | |
type == "pointercancel" | |
) { | |
delete ids[id]; | |
this._handleEnd(stage, id, e); | |
} | |
} | |
}; | |
/** | |
* @method _handleStart | |
* @param {Stage} stage | |
* @param {String|Number} id | |
* @param {Object} e | |
* @param {Number} x | |
* @param {Number} y | |
* @protected | |
**/ | |
Touch._handleStart = function (stage, id, e, x, y) { | |
var props = stage.__touch; | |
if (!props.multitouch && props.count) { | |
return; | |
} | |
var ids = props.pointers; | |
if (ids[id]) { | |
return; | |
} | |
ids[id] = true; | |
props.count++; | |
stage._handlePointerDown(id, e, x, y); | |
}; | |
/** | |
* @method _handleMove | |
* @param {Stage} stage | |
* @param {String|Number} id | |
* @param {Object} e | |
* @param {Number} x | |
* @param {Number} y | |
* @protected | |
**/ | |
Touch._handleMove = function (stage, id, e, x, y) { | |
if (!stage.__touch.pointers[id]) { | |
return; | |
} | |
stage._handlePointerMove(id, e, x, y); | |
}; | |
/** | |
* @method _handleEnd | |
* @param {Stage} stage | |
* @param {String|Number} id | |
* @param {Object} e | |
* @protected | |
**/ | |
Touch._handleEnd = function (stage, id, e) { | |
// TODO: cancel should be handled differently for proper UI (ex. an up would trigger a click, a cancel would more closely resemble an out). | |
var props = stage.__touch; | |
var ids = props.pointers; | |
if (!ids[id]) { | |
return; | |
} | |
props.count--; | |
stage._handlePointerUp(id, e, true); | |
delete ids[id]; | |
}; | |
createjs.Touch = Touch; | |
})(); | |
//############################################################################## | |
// version.js | |
//############################################################################## | |
this.createjs = this.createjs || {}; | |
(function () { | |
"use strict"; | |
/** | |
* Static class holding library specific information such as the version and buildDate of | |
* the library. | |
* @class EaselJS | |
**/ | |
var s = (createjs.EaselJS = createjs.EaselJS || {}); | |
/** | |
* The version string for this release. | |
* @property version | |
* @type String | |
* @static | |
**/ | |
s.version = /*=version*/ "NEXT"; // injected by build process | |
/** | |
* The build date for this release in UTC format. | |
* @property buildDate | |
* @type String | |
* @static | |
**/ | |
s.buildDate = /*=date*/ "Thu, 14 Sep 2017 22:19:48 GMT"; // injected by build process | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment