text: /*
  * SpriteSheet
  * 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.
  */
  
  /**
   * @module EaselJS
   */
  
  // namespace:
  this.createjs = this.createjs||{};
  
  (function() {
  	"use strict";
  /**
   * 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 three critical pieces of information:<ol>
   *    <li> The image or images to use.</li>
   *    <li> The positions of individual image frames. This data can be represented in one of two ways:
   *    As a regular grid of sequential, equal-sized frames, or as individually defined, variable sized frames arranged in
   *    an irregular (non-sequential) fashion.</li>
   *    <li> Likewise, animations can be represented in two ways: As a series of sequential frames, defined by a start and
   *    end frame [0,3], or as a list of frames [0,1,2,3].</li>
   * </OL>
   *
   * <h4>SpriteSheet Format</h4>
   *
   *      data = {
   *          // DEFINING FRAMERATE:
   *          // this specifies the framerate that will be set on the SpriteSheet. See Spritesheet.framerate
   *          // for more information.
   *          framerate: 20,
   *
   *          // DEFINING IMAGES:
   *          // list of images or image URIs to use. SpriteSheet can handle preloading.
   *          // the order dictates their index value for frame definition.
   *          images: [image1, "path/to/image2.png"],
   *
   *          // DEFINING FRAMES:
   * 	        // the simple way to define frames, only requires frame size because frames are consecutive:
   * 	        // define frame width/height, and optionally the frame count and registration point x/y.
   * 	        // if count is omitted, it will be calculated automatically based on image dimensions.
   * 	        frames: {width:64, height:64, count:20, regX: 32, regY:64},
   *
   * 	        // OR, the complex way that defines individual rects for frames.
   * 	        // The 5th value is the image index per the list defined in "images" (defaults to 0).
   * 	        frames: [
   * 	        	// x, y, width, height, imageIndex, regX, regY
   * 	        	[0,0,64,64,0,32,64],
   * 	        	[64,0,96,64,0]
   * 	        ],
   *
   *          // DEFINING ANIMATIONS:
   *
   * 	        // simple animation definitions. Define a consecutive range of frames (begin to end inclusive).
   * 	        // optionally define a "next" animation to sequence to (or false to stop) and a playback "speed"
   * 	        animations: {
   * 	        	// start, end, next, speed
   * 	        	run: [0,8],
   * 	        	jump: [9,12,"run",2]
   * 	        }
   *
   *          // the complex approach which specifies every frame in the animation by index.
   *          animations: {
   *          	run: {
   *          		frames: [1,2,3,3,2,1]
   *          	},
   *          	jump: {
   *          		frames: [1,4,5,6,1],
   *          		next: "run",
   *          		speed: 2
   *          	},
   *          	stand: { frames: [7] }
   *          }
   *
   * 	        // the above two approaches can be combined, you can also use a single frame definition:
   * 	        animations: {
   * 	        	run: [0,8,true,2],
   * 	        	jump: {
   * 	        		frames: [8,9,10,9,8],
   * 	        		next: "run",
   * 	        		speed: 2
   * 	        	},
   * 	        	stand: 7
   * 	        }
   *      }
   *
   * <h4>Example</h4>
   * To define a simple sprite sheet, with a single image "sprites.jpg" arranged in a regular 50x50 grid with two
   * animations, "run" looping from frame 0-4 inclusive, and "jump" playing from frame 5-8 and sequencing back to run:
   *
   *      var data = {
   *          images: ["sprites.jpg"],
   *          frames: {width:50, height:50},
   *          animations: {run:[0,4], jump:[5,8,"run"]}
   *      };
   *      var spriteSheet = new createjs.SpriteSheet(data);
   *      var animation = new createjs.Sprite(spriteSheet, "run");
   *
   * @class SpriteSheet
   * @constructor
   * @param {Object} data An object describing the SpriteSheet data.
   * @extends EventDispatcher
   **/
  var SpriteSheet = function(data) {
    this.initialize(data);
  };
  var p = SpriteSheet.prototype = new createjs.EventDispatcher();
  
  // 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.
  	 * <pre><code>var sheet = new SpriteSheet(data);
  	 * if (!sheet.complete) {
  	 *  &nbsp; // not preloaded, listen for the complete event:
  	 *  &nbsp; sheet.addEventListener("complete", handler);
  	 * }</code></pre>
  	 * @event complete
  	 * @param {Object} target The object that dispatched the event.
  	 * @param {String} type The event type.
  	 * @since 0.6.0
  	 */
  
  // public properties:
  	/**
  	 * Indicates whether all images are finished loading.
  	 * @property complete
  	 * @type Boolean
  	 * @readonly
  	 **/
  	p.complete = true;
  
  
  	/**
  	 * Specifies the framerate to use by default for Sprite instances using the SpriteSheet. See
  	 * Sprite.framerate for more information.
  	 * @property framerate
  	 * @type Number
  	 **/
  	p.framerate = 0;
  
  	// TODO: deprecated.
  	/**
  	 * REMOVED. Use {{#crossLink "EventDispatcher/addEventListener"}}{{/crossLink}} and the {{#crossLink "SpriteSheet/complete:event"}}{{/crossLink}}
  	 * event.
  	 * @property onComplete
  	 * @type Function
  	 * @deprecated Use addEventListener and the "complete" event.
  	 **/
  
  // private properties:
  	/**
  	 * @property _animations
  	 * @protected
  	 **/
  	p._animations = null;
  
  	/**
  	 * @property _frames
  	 * @protected
  	 **/
  	p._frames = null;
  
  	/**
  	 * @property _images
  	 * @protected
  	 **/
  	p._images = null;
  
  	/**
  	 * @property _data
  	 * @protected
  	 **/
  	p._data = null;
  
  	/**
  	 * @property _loadCount
  	 * @protected
  	 **/
  	p._loadCount = 0;
  
  	// only used for simple frame defs:
  	/**
  	 * @property _frameHeight
  	 * @protected
  	 **/
  	p._frameHeight = 0;
  
  	/**
  	 * @property _frameWidth
  	 * @protected
  	 **/
  	p._frameWidth = 0;
  
  	/**
  	 * @property _numFrames
  	 * @protected
  	 **/
  	p._numFrames = 0;
  
  	/**
  	 * @property _regX
  	 * @protected
  	 **/
  	p._regX = 0;
  
  	/**
  	 * @property _regY
  	 * @protected
  	 **/
  	p._regY = 0;
  
  // constructor:
  	/**
  	 * @method initialize
  	 * @param {Object} data An object describing the SpriteSheet data.
  	 * @protected
  	 **/
  	p.initialize = 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.complete) {
  					this._loadCount++;
  					this.complete = false;
  					(function(o) { img.onload = function() { o._handleImageLoad(); } })(this);
  				}
  			}
  		}
  
  		// parse frames:
  		if (data.frames == null) { // nothing
  		} else if (data.frames instanceof Array) {
  			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._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 (obj instanceof Array) { // 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;
  			}
  		}
  
  	};
  
  // public methods:
  	/**
  	 * Returns the total number of frames in the specified animation, or in the whole sprite
  	 * sheet if the animation param is omitted.
       * @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;
  		} else {
  			var data = this._data[animation];
  			if (data == null) { return 0; }
  			else { return data.frames.length; }
  		}
  	};
  
  	/**
  	 * Returns an array of all available animation names as strings.
  	 * @method getAnimations
  	 * @return {Array} an array of animation names available on this sprite sheet.
  	 **/
  	p.getAnimations = function() {
  		return this._animations.slice(0);
  	};
  
  	/**
  	 * 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>
  	 * </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()).initialize(-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]";
  	};
  
  	/**
  	 * Returns a clone of the SpriteSheet instance.
  	 * @method clone
  	 * @return {SpriteSheet} a clone of the SpriteSheet instance.
  	 **/
  	p.clone = function() {
  		// TODO: there isn't really any reason to clone SpriteSheet instances, because they can be reused.
  		var o = new SpriteSheet();
  		o.complete = this.complete;
  		o._animations = this._animations;
  		o._frames = this._frames;
  		o._images = this._images;
  		o._data = this._data;
  		o._frameHeight = this._frameHeight;
  		o._frameWidth = this._frameWidth;
  		o._numFrames = this._numFrames;
  		o._loadCount = this._loadCount;
  		return o;
  	};
  
  // private methods:
  	/**
  	 * @method _handleImageLoad
  	 * @protected
  	 **/
  	p._handleImageLoad = function() {
  		if (--this._loadCount == 0) {
  			this._calculateFrames();
  			this.complete = true;
  			this.dispatchEvent("complete");
  		}
  	};
  
  	/**
  	 * @method _calculateFrames
  	 * @protected
  	 **/
  	p._calculateFrames = function() {
  		if (this._frames || this._frameWidth == 0) { return; }
  		this._frames = [];
  		var ttlFrames = 0;
  		var fw = this._frameWidth;
  		var fh = this._frameHeight;
  		for (var i=0,imgs = this._images; i<imgs.length; i++) {
  			var img = imgs[i];
  			var cols = (img.width+1)/fw|0;
  			var rows = (img.height+1)/fh|0;
  			var ttl = this._numFrames>0 ? Math.min(this._numFrames-ttlFrames,cols*rows) : cols*rows;
  			for (var j=0;j<ttl;j++) {
  				this._frames.push({image:img, rect:new createjs.Rectangle(j%cols*fw,(j/cols|0)*fh,fw,fh), regX:this._regX, regY:this._regY });
  			}
  			ttlFrames += ttl;
  		}
  		this._numFrames = ttlFrames;
  	};
  
  createjs.SpriteSheet = SpriteSheet;
  }());
  