EaselJS v1.0.0 API Documentation : easeljs/display/SpriteSheet.js

API Documentation for: 1.0.0
Show:

File:SpriteSheet.js

  1. /*
  2. * SpriteSheet
  3. * Visit http://createjs.com/ for documentation, updates and examples.
  4. *
  5. * Copyright (c) 2010 gskinner.com, inc.
  6. *
  7. * Permission is hereby granted, free of charge, to any person
  8. * obtaining a copy of this software and associated documentation
  9. * files (the "Software"), to deal in the Software without
  10. * restriction, including without limitation the rights to use,
  11. * copy, modify, merge, publish, distribute, sublicense, and/or sell
  12. * copies of the Software, and to permit persons to whom the
  13. * Software is furnished to do so, subject to the following
  14. * conditions:
  15. *
  16. * The above copyright notice and this permission notice shall be
  17. * included in all copies or substantial portions of the Software.
  18. *
  19. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  20. * EXPRESS OR IMPliED, INCLUDING BUT NOT liMITED TO THE WARRANTIES
  21. * OF MERCHANTABIliTY, FITNESS FOR A PARTICULAR PURPOSE AND
  22. * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  23. * HolDERS BE liABLE FOR ANY CLAIM, DAMAGES OR OTHER liABIliTY,
  24. * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  25. * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  26. * OTHER DEAliNGS IN THE SOFTWARE.
  27. */
  28.  
  29. /**
  30. * @module EaselJS
  31. */
  32.  
  33. // namespace:
  34. this.createjs = this.createjs||{};
  35.  
  36. (function() {
  37. "use strict";
  38.  
  39.  
  40. // constructor:
  41. /**
  42. * Encapsulates the properties and methods associated with a sprite sheet. A sprite sheet is a series of images (usually
  43. * animation frames) combined into a larger image (or images). For example, an animation consisting of eight 100x100
  44. * images could be combined into a single 400x200 sprite sheet (4 frames across by 2 high).
  45. *
  46. * The data passed to the SpriteSheet constructor defines:
  47. * <ol>
  48. * <li> The source image or images to use.</li>
  49. * <li> The positions of individual image frames.</li>
  50. * <li> Sequences of frames that form named animations. Optional.</li>
  51. * <li> The target playback framerate. Optional.</li>
  52. * </ol>
  53. * <h3>SpriteSheet Format</h3>
  54. * SpriteSheets are an object with two required properties (`images` and `frames`), and two optional properties
  55. * (`framerate` and `animations`). This makes them easy to define in javascript code, or in JSON.
  56. *
  57. * <h4>images</h4>
  58. * An array of source images. Images can be either an HTMlimage
  59. * instance, or a uri to an image. The former is recommended to control preloading.
  60. *
  61. * images: [image1, "path/to/image2.png"],
  62. *
  63. * <h4>frames</h4>
  64. * Defines the individual frames. There are two supported formats for frame data:
  65. * When all of the frames are the same size (in a grid), use an object with `width`, `height`, `regX`, `regY`,
  66. * and `count` properties.
  67. *
  68. * <ul>
  69. * <li>`width` & `height` are required and specify the dimensions of the frames</li>
  70. * <li>`regX` & `regY` indicate the registration point or "origin" of the frames</li>
  71. * <li>`spacing` indicate the spacing between frames</li>
  72. * <li>`margin` specify the margin around the image(s)</li>
  73. * <li>`count` allows you to specify the total number of frames in the spritesheet; if omitted, this will
  74. * be calculated based on the dimensions of the source images and the frames. Frames will be assigned
  75. * indexes based on their position in the source images (left to right, top to bottom).</li>
  76. * </ul>
  77. *
  78. * frames: {width:64, height:64, count:20, regX: 32, regY:64, spacing:0, margin:0}
  79. *
  80. * If the frames are of different sizes, use an array of frame definitions. Each definition is itself an array
  81. * with 4 required and 3 optional entries, in the order:
  82. *
  83. * <ul>
  84. * <li>The first four, `x`, `y`, `width`, and `height` are required and define the frame rectangle.</li>
  85. * <li>The fifth, `imageIndex`, specifies the index of the source image (defaults to 0)</li>
  86. * <li>The last two, `regX` and `regY` specify the registration point of the frame</li>
  87. * </ul>
  88. *
  89. * frames: [
  90. * // x, y, width, height, imageIndex*, regX*, regY*
  91. * [64, 0, 96, 64],
  92. * [0, 0, 64, 64, 1, 32, 32]
  93. * // etc.
  94. * ]
  95. *
  96. * <h4>animations</h4>
  97. * Optional. An object defining sequences of frames to play as named animations. Each property corresponds to an
  98. * animation of the same name. Each animation must specify the frames to play, and may
  99. * also include a relative playback `speed` (ex. 2 would playback at double speed, 0.5 at half), and
  100. * the name of the `next` animation to sequence to after it completes.
  101. *
  102. * There are three formats supported for defining the frames in an animation, which can be mixed and matched as appropriate:
  103. * <ol>
  104. * <li>for a single frame animation, you can simply specify the frame index
  105. *
  106. * animations: {
  107. * sit: 7
  108. * }
  109. *
  110. * </li>
  111. * <li>
  112. * for an animation of consecutive frames, you can use an array with two required, and two optional entries
  113. * in the order: `start`, `end`, `next`, and `speed`. This will play the frames from start to end inclusive.
  114. *
  115. * animations: {
  116. * // start, end, next*, speed*
  117. * run: [0, 8],
  118. * jump: [9, 12, "run", 2]
  119. * }
  120. *
  121. * </li>
  122. * <li>
  123. * for non-consecutive frames, you can use an object with a `frames` property defining an array of frame
  124. * indexes to play in order. The object can also specify `next` and `speed` properties.
  125. *
  126. * animations: {
  127. * walk: {
  128. * frames: [1,2,3,3,2,1]
  129. * },
  130. * shoot: {
  131. * frames: [1,4,5,6],
  132. * next: "walk",
  133. * speed: 0.5
  134. * }
  135. * }
  136. *
  137. * </li>
  138. * </ol>
  139. * <strong>Note:</strong> the `speed` property was added in EaselJS 0.7.0. Earlier versions had a `frequency`
  140. * property instead, which was the inverse of `speed`. For example, a value of "4" would be 1/4 normal speed in
  141. * earlier versions, but is 4x normal speed in EaselJS 0.7.0+.
  142. *
  143. * <h4>framerate</h4>
  144. * Optional. Indicates the default framerate to play this spritesheet at in frames per second. See
  145. * {{#crossLink "SpriteSheet/framerate:property"}}{{/crossLink}} for more information.
  146. *
  147. * framerate: 20
  148. *
  149. * Note that the Sprite framerate will only work if the stage update method is provided with the {{#crossLink "Ticker/tick:event"}}{{/crossLink}}
  150. * event generated by the {{#crossLink "Ticker"}}{{/crossLink}}.
  151. *
  152. * createjs.Ticker.on("tick", handleTick);
  153. * function handleTick(event) {
  154. * stage.update(event);
  155. * }
  156. *
  157. * <h3>Example</h3>
  158. * To define a simple sprite sheet, with a single image "sprites.jpg" arranged in a regular 50x50 grid with three
  159. * animations: "stand" showing the first frame, "run" looping frame 1-5 inclusive, and "jump" playing frame 6-8 and
  160. * sequencing back to run.
  161. *
  162. * var data = {
  163. * images: ["sprites.jpg"],
  164. * frames: {width:50, height:50},
  165. * animations: {
  166. * stand:0,
  167. * run:[1,5],
  168. * jump:[6,8,"run"]
  169. * }
  170. * };
  171. * var spriteSheet = new createjs.SpriteSheet(data);
  172. * var animation = new createjs.Sprite(spriteSheet, "run");
  173. *
  174. * <h3>Generating SpriteSheet Images</h3>
  175. * Spritesheets can be created manually by combining images in PhotoShop, and specifying the frame size or
  176. * coordinates manually, however there are a number of tools that facilitate this.
  177. * <ul>
  178. * <li>Exporting SpriteSheets or HTML5 content from Adobe Flash/Animate supports the EaselJS SpriteSheet format.</li>
  179. * <li>The popular <a href="https://www.codeandweb.com/texturepacker/easeljs" target="_blank">Texture Packer</a> has
  180. * EaselJS support.
  181. * <li>SWF animations in Adobe Flash/Animate can be exported to SpriteSheets using <a href="http://createjs.com/zoe" target="_blank">Zo&euml;</a></li>
  182. * </ul>
  183. *
  184. * <h3>Cross Origin Issues</h3>
  185. * <strong>Warning:</strong> Images loaded cross-origin will throw cross-origin security errors when interacted with
  186. * using:
  187. * <ul>
  188. * <li>a mouse</li>
  189. * <li>methods such as {{#crossLink "Container/getObjectUnderPoint"}}{{/crossLink}}</li>
  190. * <li>Filters (see {{#crossLink "Filter"}}{{/crossLink}})</li>
  191. * <li>caching (see {{#crossLink "DisplayObject/cache"}}{{/crossLink}})</li>
  192. * </ul>
  193. * You can get around this by setting `crossOrigin` property on your images before passing them to EaselJS, or
  194. * setting the `crossOrigin` property on PreloadJS' LoadQueue or LoadItems.
  195. *
  196. * var img = new Image();
  197. * img.crossOrigin="Anonymous";
  198. * img.src = "http://server-with-CORS-support.com/path/to/image.jpg";
  199. *
  200. * If you pass string paths to SpriteSheets, they will not work cross-origin. The server that stores the image must
  201. * support cross-origin requests, or this will not work. For more information, check out
  202. * <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS" target="_blank">CORS overview on MDN</a>.
  203. *
  204. * @class SpriteSheet
  205. * @constructor
  206. * @param {Object} data An object describing the SpriteSheet data.
  207. * @extends EventDispatcher
  208. **/
  209. function SpriteSheet(data) {
  210. this.EventDispatcher_constructor();
  211.  
  212.  
  213. // public properties:
  214. /**
  215. * Indicates whether all images are finished loading.
  216. * @property complete
  217. * @type Boolean
  218. * @readonly
  219. **/
  220. this.complete = true;
  221.  
  222. /**
  223. * Specifies the framerate to use by default for Sprite instances using the SpriteSheet. See the Sprite class
  224. * {{#crossLink "Sprite/framerate:property"}}{{/crossLink}} for more information.
  225. * @property framerate
  226. * @type Number
  227. **/
  228. this.framerate = 0;
  229.  
  230.  
  231. // private properties:
  232. /**
  233. * @property _animations
  234. * @protected
  235. * @type Array
  236. **/
  237. this._animations = null;
  238.  
  239. /**
  240. * @property _frames
  241. * @protected
  242. * @type Array
  243. **/
  244. this._frames = null;
  245.  
  246. /**
  247. * @property _images
  248. * @protected
  249. * @type Array
  250. **/
  251. this._images = null;
  252.  
  253. /**
  254. * @property _data
  255. * @protected
  256. * @type Object
  257. **/
  258. this._data = null;
  259.  
  260. /**
  261. * @property _loadCount
  262. * @protected
  263. * @type Number
  264. **/
  265. this._loadCount = 0;
  266.  
  267. // only used for simple frame defs:
  268. /**
  269. * @property _frameHeight
  270. * @protected
  271. * @type Number
  272. **/
  273. this._frameHeight = 0;
  274.  
  275. /**
  276. * @property _frameWidth
  277. * @protected
  278. * @type Number
  279. **/
  280. this._frameWidth = 0;
  281.  
  282. /**
  283. * @property _numFrames
  284. * @protected
  285. * @type Number
  286. **/
  287. this._numFrames = 0;
  288.  
  289. /**
  290. * @property _regX
  291. * @protected
  292. * @type Number
  293. **/
  294. this._regX = 0;
  295.  
  296. /**
  297. * @property _regY
  298. * @protected
  299. * @type Number
  300. **/
  301. this._regY = 0;
  302.  
  303. /**
  304. * @property _spacing
  305. * @protected
  306. * @type Number
  307. **/
  308. this._spacing = 0;
  309.  
  310. /**
  311. * @property _margin
  312. * @protected
  313. * @type Number
  314. **/
  315. this._margin = 0;
  316.  
  317. // setup:
  318. this._parseData(data);
  319. }
  320. var p = createjs.extend(SpriteSheet, createjs.EventDispatcher);
  321.  
  322. // TODO: deprecated
  323. // p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details.
  324.  
  325.  
  326. // events:
  327. /**
  328. * Dispatched when all images are loaded. Note that this only fires if the images
  329. * were not fully loaded when the sprite sheet was initialized. You should check the complete property
  330. * to prior to adding a listener. Ex.
  331. *
  332. * var sheet = new createjs.SpriteSheet(data);
  333. * if (!sheet.complete) {
  334. * // not preloaded, listen for the complete event:
  335. * sheet.addEventListener("complete", handler);
  336. * }
  337. *
  338. * @event complete
  339. * @param {Object} target The object that dispatched the event.
  340. * @param {String} type The event type.
  341. * @since 0.6.0
  342. */
  343.  
  344. /**
  345. * Dispatched when getFrame is called with a valid frame index. This is primarily intended for use by {{#crossLink "SpriteSheetBuilder"}}{{/crossLink}}
  346. * when doing on-demand rendering.
  347. * @event getframe
  348. * @param {Number} index The frame index.
  349. * @param {Object} frame The frame object that getFrame will return.
  350. */
  351.  
  352. /**
  353. * Dispatched when an image encounters an error. A SpriteSheet will dispatch an error event for each image that
  354. * encounters an error, and will still dispatch a {{#crossLink "SpriteSheet/complete:event"}}{{/crossLink}}
  355. * event once all images are finished processing, even if an error is encountered.
  356. * @event error
  357. * @param {String} src The source of the image that failed to load.
  358. * @since 0.8.2
  359. */
  360.  
  361.  
  362. // getter / setters:
  363. /**
  364. * Use the {{#crossLink "SpriteSheet/animations:property"}}{{/crossLink}} property instead.
  365. * @method _getAnimations
  366. * @protected
  367. * @return {Array}
  368. **/
  369. p._getAnimations = function() {
  370. return this._animations.slice();
  371. };
  372.  
  373. /**
  374. * Use the {{#crossLink "SpriteSheet/animations:property"}}{{/crossLink}} property instead.
  375. * @method getAnimations
  376. * @deprecated
  377. */
  378. // SpriteSheet.getAnimations is @deprecated. Remove for 1.1+
  379. p.getAnimations = createjs.deprecate(p._getAnimations, "SpriteSheet.getAnimations");
  380.  
  381. /**
  382. * Returns an array of all available animation names available on this sprite sheet as strings.
  383. * @property animations
  384. * @type {Array}
  385. * @readonly
  386. **/
  387. try {
  388. Object.defineProperties(p, {
  389. animations: { get: p._getAnimations }
  390. });
  391. } catch (e) {}
  392.  
  393.  
  394. // public methods:
  395. /**
  396. * Returns the total number of frames in the specified animation, or in the whole sprite
  397. * sheet if the animation param is omitted. Returns 0 if the spritesheet relies on calculated frame counts, and
  398. * the images have not been fully loaded.
  399. * @method getNumFrames
  400. * @param {String} animation The name of the animation to get a frame count for.
  401. * @return {Number} The number of frames in the animation, or in the entire sprite sheet if the animation param is omitted.
  402. */
  403. p.getNumFrames = function(animation) {
  404. if (animation == null) {
  405. return this._frames ? this._frames.length : this._numFrames || 0;
  406. } else {
  407. var data = this._data[animation];
  408. if (data == null) { return 0; }
  409. else { return data.frames.length; }
  410. }
  411. };
  412.  
  413. /**
  414. * Returns an object defining the specified animation. The returned object contains:<UL>
  415. * <li>frames: an array of the frame ids in the animation</li>
  416. * <li>speed: the playback speed for this animation</li>
  417. * <li>name: the name of the animation</li>
  418. * <li>next: the default animation to play next. If the animation loops, the name and next property will be the
  419. * same.</li>
  420. * </UL>
  421. * @method getAnimation
  422. * @param {String} name The name of the animation to get.
  423. * @return {Object} a generic object with frames, speed, name, and next properties.
  424. **/
  425. p.getAnimation = function(name) {
  426. return this._data[name];
  427. };
  428.  
  429. /**
  430. * Returns an object specifying the image and source rect of the specified frame. The returned object has:<UL>
  431. * <li>an image property holding a reference to the image object in which the frame is found</li>
  432. * <li>a rect property containing a Rectangle instance which defines the boundaries for the frame within that
  433. * image.</li>
  434. * <li> A regX and regY property corresponding to the regX/Y values for the frame.
  435. * </UL>
  436. * @method getFrame
  437. * @param {Number} frameIndex The index of the frame.
  438. * @return {Object} a generic object with image and rect properties. Returns null if the frame does not exist.
  439. **/
  440. p.getFrame = function(frameIndex) {
  441. var frame;
  442. if (this._frames && (frame=this._frames[frameIndex])) { return frame; }
  443. return null;
  444. };
  445.  
  446. /**
  447. * Returns a {{#crossLink "Rectangle"}}{{/crossLink}} instance defining the bounds of the specified frame relative
  448. * to the origin. For example, a 90 x 70 frame with a regX of 50 and a regY of 40 would return:
  449. *
  450. * [x=-50, y=-40, width=90, height=70]
  451. *
  452. * @method getFrameBounds
  453. * @param {Number} frameIndex The index of the frame.
  454. * @param {Rectangle} [rectangle] A Rectangle instance to copy the values into. By default a new instance is created.
  455. * @return {Rectangle} A Rectangle instance. Returns null if the frame does not exist, or the image is not fully loaded.
  456. **/
  457. p.getFrameBounds = function(frameIndex, rectangle) {
  458. var frame = this.getFrame(frameIndex);
  459. return frame ? (rectangle||new createjs.Rectangle()).setValues(-frame.regX, -frame.regY, frame.rect.width, frame.rect.height) : null;
  460. };
  461.  
  462. /**
  463. * Returns a string representation of this object.
  464. * @method toString
  465. * @return {String} a string representation of the instance.
  466. **/
  467. p.toString = function() {
  468. return "[SpriteSheet]";
  469. };
  470.  
  471. /**
  472. * SpriteSheet cannot be cloned. A SpriteSheet can be shared by multiple Sprite instances without cloning it.
  473. * @method clone
  474. **/
  475. p.clone = function() {
  476. throw("SpriteSheet cannot be cloned.")
  477. };
  478.  
  479. // private methods:
  480. /**
  481. * @method _parseData
  482. * @param {Object} data An object describing the SpriteSheet data.
  483. * @protected
  484. **/
  485. p._parseData = function(data) {
  486. var i,l,o,a;
  487. if (data == null) { return; }
  488.  
  489. this.framerate = data.framerate||0;
  490.  
  491. // parse images:
  492. if (data.images && (l=data.images.length) > 0) {
  493. a = this._images = [];
  494. for (i=0; i<l; i++) {
  495. var img = data.images[i];
  496. if (typeof img == "string") {
  497. var src = img;
  498. img = document.createElement("img");
  499. img.src = src;
  500. }
  501. a.push(img);
  502. if (!img.getContext && !img.naturalWidth) {
  503. this._loadCount++;
  504. this.complete = false;
  505. (function(o, src) { img.onload = function() { o._handleImageLoad(src); } })(this, src);
  506. (function(o, src) { img.onerror = function() { o._handleImageError(src); } })(this, src);
  507. }
  508. }
  509. }
  510.  
  511. // parse frames:
  512. if (data.frames == null) { // nothing
  513. } else if (Array.isArray(data.frames)) {
  514. this._frames = [];
  515. a = data.frames;
  516. for (i=0,l=a.length;i<l;i++) {
  517. var arr = a[i];
  518. 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 });
  519. }
  520. } else {
  521. o = data.frames;
  522. this._frameWidth = o.width;
  523. this._frameHeight = o.height;
  524. this._regX = o.regX||0;
  525. this._regY = o.regY||0;
  526. this._spacing = o.spacing||0;
  527. this._margin = o.margin||0;
  528. this._numFrames = o.count;
  529. if (this._loadCount == 0) { this._calculateFrames(); }
  530. }
  531.  
  532. // parse animations:
  533. this._animations = [];
  534. if ((o=data.animations) != null) {
  535. this._data = {};
  536. var name;
  537. for (name in o) {
  538. var anim = {name:name};
  539. var obj = o[name];
  540. if (typeof obj == "number") { // single frame
  541. a = anim.frames = [obj];
  542. } else if (Array.isArray(obj)) { // simple
  543. if (obj.length == 1) { anim.frames = [obj[0]]; }
  544. else {
  545. anim.speed = obj[3];
  546. anim.next = obj[2];
  547. a = anim.frames = [];
  548. for (i=obj[0];i<=obj[1];i++) {
  549. a.push(i);
  550. }
  551. }
  552. } else { // complex
  553. anim.speed = obj.speed;
  554. anim.next = obj.next;
  555. var frames = obj.frames;
  556. a = anim.frames = (typeof frames == "number") ? [frames] : frames.slice(0);
  557. }
  558. if (anim.next === true || anim.next === undefined) { anim.next = name; } // loop
  559. if (anim.next === false || (a.length < 2 && anim.next == name)) { anim.next = null; } // stop
  560. if (!anim.speed) { anim.speed = 1; }
  561. this._animations.push(name);
  562. this._data[name] = anim;
  563. }
  564. }
  565. };
  566.  
  567. /**
  568. * @method _handleImageLoad
  569. * @protected
  570. **/
  571. p._handleImageLoad = function(src) {
  572. if (--this._loadCount == 0) {
  573. this._calculateFrames();
  574. this.complete = true;
  575. this.dispatchEvent("complete");
  576. }
  577. };
  578.  
  579. /**
  580. * @method _handleImageError
  581. * @protected
  582. */
  583. p._handleImageError = function (src) {
  584. var errorEvent = new createjs.Event("error");
  585. errorEvent.src = src;
  586. this.dispatchEvent(errorEvent);
  587.  
  588. // Complete is still dispatched.
  589. if (--this._loadCount == 0) {
  590. this.dispatchEvent("complete");
  591. }
  592. };
  593.  
  594. /**
  595. * @method _calculateFrames
  596. * @protected
  597. **/
  598. p._calculateFrames = function() {
  599. if (this._frames || this._frameWidth == 0) { return; }
  600.  
  601. this._frames = [];
  602.  
  603. var maxFrames = this._numFrames || 100000; // if we go over this, something is wrong.
  604. var frameCount = 0, frameWidth = this._frameWidth, frameHeight = this._frameHeight;
  605. var spacing = this._spacing, margin = this._margin;
  606. imgLoop:
  607. for (var i=0, imgs=this._images; i<imgs.length; i++) {
  608. var img = imgs[i], imgW = (img.width||img.naturalWidth), imgH = (img.height||img.naturalHeight);
  609.  
  610. var y = margin;
  611. while (y <= imgH-margin-frameHeight) {
  612. var x = margin;
  613. while (x <= imgW-margin-frameWidth) {
  614. if (frameCount >= maxFrames) { break imgLoop; }
  615. frameCount++;
  616. this._frames.push({
  617. image: img,
  618. rect: new createjs.Rectangle(x, y, frameWidth, frameHeight),
  619. regX: this._regX,
  620. regY: this._regY
  621. });
  622. x += frameWidth+spacing;
  623. }
  624. y += frameHeight+spacing;
  625. }
  626. }
  627. this._numFrames = frameCount;
  628. };
  629.  
  630.  
  631. createjs.SpriteSheet = createjs.promote(SpriteSheet, "EventDispatcher");
  632. }());
  633.