PageRenderTime 38ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/starling/extensions/textureAtlas/DynamicAtlas.as

http://citrus-engine.googlecode.com/
ActionScript | 577 lines | 330 code | 101 blank | 146 comment | 40 complexity | 388ec9d43c1a091ac9aec811aec1798a MD5 | raw file
  1. package starling.extensions.textureAtlas
  2. {
  3. import flash.display.BitmapData;
  4. import flash.display.DisplayObject;
  5. import flash.display.MovieClip;
  6. import flash.display.Sprite;
  7. import flash.geom.ColorTransform;
  8. import flash.geom.Matrix;
  9. import flash.geom.Rectangle;
  10. import flash.text.AntiAliasType;
  11. import flash.text.Font;
  12. import flash.text.TextFieldAutoSize;
  13. import flash.text.TextFormat;
  14. import flash.utils.getQualifiedClassName;
  15. import starling.text.BitmapFont;
  16. import starling.textures.Texture;
  17. import starling.textures.TextureAtlas;
  18. import starling.text.TextField;
  19. /**
  20. * DynamicAtlas.as
  21. * https://github.com/emibap/Dynamic-Texture-Atlas-Generator
  22. * @author Emibap (Emiliano Angelini) - http://www.emibap.com
  23. * Contribution by Thomas Haselwanter - https://github.com/thomashaselwanter
  24. * Most of this comes thanks to the inspiration (and code) of Thibault Imbert (http://www.bytearray.org) and Nicolas Gans (http://www.flashxpress.net/)
  25. *
  26. * Dynamic Texture Atlas and Bitmap Font Generator (Starling framework Extension)
  27. * ========
  28. *
  29. * This tool will convert any MovieClip containing Other MovieClips, Sprites or Graphics into a starling Texture Atlas, all in runtime.
  30. * It can also register bitmap Fonts from system or embedded regular fonts.
  31. * By using it, you won't have to statically create your spritesheets or fonts. For instance, you can just take a regular MovieClip containing all the display objects you wish to put into your Altas, and convert everything from vectors to bitmap textures.
  32. * Or you can select which font (specifying characters) you'd like to register as a Bitmap Font, using a string or passing a Regular TextField as a parameter.
  33. * This extension could save you a lot of time specially if you'll be coding mobile apps with the [starling framework](http://www.starling-framework.org/).
  34. *
  35. * # version 1.0 #
  36. * - Added the checkBounds parameter to scan the clip prior the rasterization in order to get the bounds of the entire MovieClip (prevent scaling in some cases). Thank you Aymeric Lamboley.
  37. * - Added the fontCustomID parameter to the Bitmap font creation. Thank you Regan.
  38. *
  39. * ### Features ###
  40. *
  41. * * Dynamic creation of a Texture Atlas from a MovieClip (flash.display.MovieClip) container that could act as a sprite sheet, or from a Vector of Classes
  42. * * Filters made to the objects are captured
  43. * * Color transforms (tint, alpha) are optionally captured
  44. * * Scales the objects (and also the filters) to a specified value
  45. * * Automatically detects the objects bounds so you don't necessarily have to set the registration points to TOP LEFT
  46. * * Registers Bitmap Fonts based on system or embedded fonts from strings or from good old Flash TextFields
  47. *
  48. * ### TODO List ###
  49. *
  50. * * Further code optimization
  51. * * A better implementation of the Bitmap Font creation process
  52. * * Documentation (?)
  53. *
  54. * ### Whish List ###
  55. * * Optional division of the process into small intervals (for smooth performance of the app)
  56. *
  57. * ### Usage ###
  58. *
  59. * You can use the following static methods (examples at the gitHub Repo):
  60. *
  61. * [Texture Atlas creation]
  62. * - DynamicAtlas.fromMovieClipContainer(swf:flash.display.MovieClip, scaleFactor:Number = 1, margin:uint=0, preserveColor:Boolean = true):starling.textures.TextureAtlas
  63. * - DynamicAtlas.fromClassVector(assets:Vector.<Class>, scaleFactor:Number = 1, margin:uint=0, preserveColor:Boolean = true):starling.textures.TextureAtlas
  64. *
  65. * [Bitmap Font registration]
  66. * - DynamicAtlas.bitmapFontFromString(chars:String, fontFamily:String, fontSize:Number = 12, bold:Boolean = false, italic:Boolean = false, charMarginX:int=0):void
  67. * - DynamicAtlas.bitmapFontFromTextField(tf:flash.text.TextField, charMarginX:int=0):void
  68. *
  69. * Enclose inside a try/catch for error handling:
  70. * try {
  71. * var atlas:TextureAtlas = DynamicAtlas.fromMovieClipContainer(mc);
  72. * } catch (e:Error) {
  73. * trace("There was an error in the creation of the texture Atlas. Please check if the dimensions of your clip exceeded the maximun allowed texture size. -", e.message);
  74. * }
  75. *
  76. * History:
  77. * -------
  78. * # version 0.9.5 #
  79. * - Added the fromClassVector static function. Thank you Thomas Haselwanter
  80. *
  81. * # version 0.9 #
  82. * - Added Bitmap Font creation support
  83. * - Scaling also applies to filters.
  84. * - Added Margin and PreserveColor Properties
  85. *
  86. * # version 0.8 #
  87. * - Added the scaleFactor constructor parameter. Now you can define a custom scale to the final result.
  88. * - Scaling also applies to filters.
  89. * - Added Margin and PreserveColor Properties
  90. *
  91. * # version 0.7 #
  92. * First Public version
  93. **/
  94. public class DynamicAtlas
  95. {
  96. static protected const DEFAULT_CANVAS_WIDTH:Number = 640;
  97. static protected var _items:Array;
  98. static protected var _canvas:Sprite;
  99. static protected var _currentLab:String;
  100. static protected var _x:Number;
  101. static protected var _y:Number;
  102. static protected var _bData:BitmapData;
  103. static protected var _mat:Matrix;
  104. static protected var _margin:Number;
  105. static protected var _preserveColor:Boolean;
  106. // Will not be used - Only using one static method
  107. public function DynamicAtlas()
  108. {
  109. }
  110. // Private methods
  111. static protected function appendIntToString(num:int, numOfPlaces:int):String
  112. {
  113. var numString:String = num.toString();
  114. var outString:String = "";
  115. for (var i:int = 0; i < numOfPlaces - numString.length; i++)
  116. {
  117. outString += "0";
  118. }
  119. return outString + numString;
  120. }
  121. static protected function layoutChildren():void
  122. {
  123. var xPos:Number = 0;
  124. var yPos:Number = 0;
  125. var maxY:Number = 0;
  126. var len:int = _items.length;
  127. var itm:TextureItem;
  128. for (var i:uint = 0; i < len; i++)
  129. {
  130. itm = _items[i];
  131. if ((xPos + itm.width) > DEFAULT_CANVAS_WIDTH)
  132. {
  133. xPos = 0;
  134. yPos += maxY;
  135. maxY = 0;
  136. }
  137. if (itm.height + 1 > maxY)
  138. {
  139. maxY = itm.height + 1;
  140. }
  141. itm.x = xPos;
  142. itm.y = yPos;
  143. xPos += itm.width + 1;
  144. }
  145. }
  146. /**
  147. * isEmbedded
  148. *
  149. * @param fontFamily:Boolean - The name of the Font
  150. * @return Boolean - True if the font is an embedded one
  151. */
  152. static protected function isEmbedded(fontFamily:String):Boolean
  153. {
  154. var embeddedFonts:Vector.<Font> = Vector.<Font>(Font.enumerateFonts());
  155. for (var i:int = embeddedFonts.length - 1; i > -1 && embeddedFonts[i].fontName != fontFamily; i--) { }
  156. return (i > -1);
  157. }
  158. static protected function getRealBounds(clip:DisplayObject):Rectangle {
  159. var bounds:Rectangle = clip.getBounds(clip.parent);
  160. bounds.x = Math.floor(bounds.x);
  161. bounds.y = Math.floor(bounds.y);
  162. bounds.height = Math.ceil(bounds.height);
  163. bounds.width = Math.ceil(bounds.width);
  164. var realBounds:Rectangle = new Rectangle(0, 0, bounds.width + _margin * 2, bounds.height + _margin * 2);
  165. // Checking filters in case we need to expand the outer bounds
  166. if (clip.filters.length > 0)
  167. {
  168. // filters
  169. var j:int = 0;
  170. //var clipFilters:Array = clipChild.filters.concat();
  171. var clipFilters:Array = clip.filters;
  172. var clipFiltersLength:int = clipFilters.length;
  173. var tmpBData:BitmapData;
  174. var filterRect:Rectangle;
  175. tmpBData = new BitmapData(realBounds.width, realBounds.height, false);
  176. filterRect = tmpBData.generateFilterRect(tmpBData.rect, clipFilters[j]);
  177. tmpBData.dispose();
  178. while (++j < clipFiltersLength)
  179. {
  180. tmpBData = new BitmapData(filterRect.width, filterRect.height, true, 0);
  181. filterRect = tmpBData.generateFilterRect(tmpBData.rect, clipFilters[j]);
  182. realBounds = realBounds.union(filterRect);
  183. tmpBData.dispose();
  184. }
  185. }
  186. realBounds.offset(bounds.x, bounds.y);
  187. realBounds.width = Math.max(realBounds.width, 1);
  188. realBounds.height = Math.max(realBounds.height, 1);
  189. tmpBData = null;
  190. return realBounds;
  191. }
  192. /**
  193. * drawItem - This will actually rasterize the display object passed as a parameter
  194. * @param clip
  195. * @param name
  196. * @param baseName
  197. * @param clipColorTransform
  198. * @param frameBounds
  199. * @return TextureItem
  200. */
  201. static protected function drawItem(clip:DisplayObject, name:String = "", baseName:String = "", clipColorTransform:ColorTransform = null, frameBounds:Rectangle=null):TextureItem
  202. {
  203. var realBounds:Rectangle = getRealBounds(clip);
  204. _bData = new BitmapData(realBounds.width, realBounds.height, true, 0);
  205. _mat = clip.transform.matrix;
  206. _mat.translate(-realBounds.x + _margin, -realBounds.y + _margin);
  207. _bData.draw(clip, _mat, _preserveColor ? clipColorTransform : null);
  208. var label:String = "";
  209. if (clip is MovieClip) {
  210. if (clip["currentLabel"] != _currentLab && clip["currentLabel"] != null)
  211. {
  212. _currentLab = clip["currentLabel"];
  213. label = _currentLab;
  214. }
  215. }
  216. if (frameBounds) {
  217. realBounds.x = frameBounds.x - realBounds.x;
  218. realBounds.y = frameBounds.y - realBounds.y;
  219. realBounds.width = frameBounds.width;
  220. realBounds.height = frameBounds.height;
  221. }
  222. var item:TextureItem = new TextureItem(_bData, name, label, realBounds.x, realBounds.y, realBounds.width, realBounds.height);
  223. _items.push(item);
  224. _canvas.addChild(item);
  225. _bData = null;
  226. return item;
  227. }
  228. // Public methods
  229. /**
  230. * This method takes a vector of MovieClip class and converts it into a Texture Atlas.
  231. *
  232. * @param assets:Vector.<Class> - The MovieClip classes you wish to convert into a TextureAtlas. Must contain classes whose instances are of type MovieClip that will be rasterized and become the subtextures of your Atlas.
  233. * @param scaleFactor:Number - The scaling factor to apply to every object. Default value is 1 (no scaling).
  234. * @param margin:uint - The amount of pixels that should be used as the resulting image margin (for each side of the image). Default value is 0 (no margin).
  235. * @param preserveColor:Boolean - A Flag which indicates if the color transforms should be captured or not. Default value is true (capture color transform).
  236. * @param checkBounds:Boolean - A Flag used to scan the clip prior the rasterization in order to get the bounds of the entire MovieClip. By default is false because it adds overhead to the process.
  237. * @return TextureAtlas - The dynamically generated Texture Atlas.
  238. */
  239. static public function fromClassVector(assets:Vector.<Class>, scaleFactor:Number = 1, margin:uint=0, preserveColor:Boolean = true, checkBounds:Boolean=false):TextureAtlas
  240. {
  241. var container:MovieClip = new MovieClip();
  242. for each (var assetClass:Class in assets) {
  243. var assetInstance:MovieClip = new assetClass();
  244. assetInstance.name = getQualifiedClassName(assetClass);
  245. container.addChild(assetInstance);
  246. }
  247. return fromMovieClipContainer(container, scaleFactor, margin, preserveColor, checkBounds);
  248. }
  249. /** Retrieves all textures for a class. Returns <code>null</code> if it is not found.
  250. * This method can be used if TextureAtlass doesn't support classes.
  251. */
  252. static public function getTexturesByClass(textureAtlas:TextureAtlas, assetClass:Class):Vector.<Texture> {
  253. return textureAtlas.getTextures(getQualifiedClassName(assetClass));
  254. }
  255. /**
  256. * This method will take a MovieClip sprite sheet (containing other display objects) and convert it into a Texture Atlas.
  257. *
  258. * @param swf:MovieClip - The MovieClip sprite sheet you wish to convert into a TextureAtlas. I must contain named instances of every display object that will be rasterized and become the subtextures of your Atlas.
  259. * @param scaleFactor:Number - The scaling factor to apply to every object. Default value is 1 (no scaling).
  260. * @param margin:uint - The amount of pixels that should be used as the resulting image margin (for each side of the image). Default value is 0 (no margin).
  261. * @param preserveColor:Boolean - A Flag which indicates if the color transforms should be captured or not. Default value is true (capture color transform).
  262. * @param checkBounds:Boolean - A Flag used to scan the clip prior the rasterization in order to get the bounds of the entire MovieClip. By default is false because it adds overhead to the process.
  263. * @return TextureAtlas - The dynamically generated Texture Atlas.
  264. */
  265. static public function fromMovieClipContainer(swf:MovieClip, scaleFactor:Number = 1, margin:uint=0, preserveColor:Boolean = true, checkBounds:Boolean=false):TextureAtlas
  266. {
  267. var parseFrame:Boolean = false;
  268. var selected:MovieClip;
  269. var selectedTotalFrames:int;
  270. var selectedColorTransform:ColorTransform;
  271. var frameBounds:Rectangle = new Rectangle(0, 0, 0, 0);
  272. var children:uint = swf.numChildren;
  273. var canvasData:BitmapData;
  274. var texture:Texture;
  275. var xml:XML;
  276. var subText:XML;
  277. var atlas:TextureAtlas;
  278. var itemsLen:int;
  279. var itm:TextureItem;
  280. var m:uint;
  281. _margin = margin;
  282. _preserveColor = preserveColor;
  283. _items = [];
  284. if (!_canvas)
  285. _canvas = new Sprite();
  286. swf.gotoAndStop(1);
  287. for (var i:uint = 0; i < children; i++)
  288. {
  289. selected = MovieClip(swf.getChildAt(i));
  290. selectedTotalFrames = selected.totalFrames;
  291. selectedColorTransform = selected.transform.colorTransform;
  292. _x = selected.x;
  293. _y = selected.y;
  294. // Scaling if needed (including filters)
  295. if (scaleFactor != 1)
  296. {
  297. selected.scaleX *= scaleFactor;
  298. selected.scaleY *= scaleFactor;
  299. if (selected.filters.length > 0)
  300. {
  301. var filters:Array = selected.filters;
  302. var filtersLen:int = selected.filters.length;
  303. var filter:Object;
  304. for (var j:uint = 0; j < filtersLen; j++)
  305. {
  306. filter = filters[j];
  307. if (filter.hasOwnProperty("blurX"))
  308. {
  309. filter.blurX *= scaleFactor;
  310. filter.blurY *= scaleFactor;
  311. }
  312. if (filter.hasOwnProperty("distance"))
  313. {
  314. filter.distance *= scaleFactor;
  315. }
  316. }
  317. selected.filters = filters;
  318. }
  319. }
  320. // Gets the frame bounds by performing a frame-by-frame check
  321. if (selectedTotalFrames > 1 && checkBounds) {
  322. selected.gotoAndStop(0);
  323. frameBounds = getRealBounds(selected);
  324. m = 1;
  325. while (++m <= selectedTotalFrames)
  326. {
  327. selected.gotoAndStop(m);
  328. frameBounds = frameBounds.union(getRealBounds(selected));
  329. }
  330. }
  331. m = 0;
  332. // Draw every frame
  333. while (++m <= selectedTotalFrames)
  334. {
  335. selected.gotoAndStop(m);
  336. drawItem(selected, selected.name + "_" + appendIntToString(m - 1, 5), selected.name, selectedColorTransform, frameBounds);
  337. }
  338. }
  339. _currentLab = "";
  340. layoutChildren();
  341. canvasData = new BitmapData(_canvas.width, _canvas.height, true, 0x000000);
  342. canvasData.draw(_canvas);
  343. xml = new XML(<TextureAtlas></TextureAtlas>);
  344. xml.@imagePath = "atlas.png";
  345. itemsLen = _items.length;
  346. for (var k:uint = 0; k < itemsLen; k++)
  347. {
  348. itm = _items[k];
  349. itm.graphic.dispose();
  350. // xml
  351. subText = new XML(<SubTexture />);
  352. subText.@name = itm.textureName;
  353. subText.@x = itm.x;
  354. subText.@y = itm.y;
  355. subText.@width = itm.width;
  356. subText.@height = itm.height;
  357. subText.@frameX = itm.frameX;
  358. subText.@frameY = itm.frameY;
  359. subText.@frameWidth = itm.frameWidth;
  360. subText.@frameHeight = itm.frameHeight;
  361. if (itm.frameName != "")
  362. subText.@frameLabel = itm.frameName;
  363. xml.appendChild(subText);
  364. }
  365. texture = Texture.fromBitmapData(canvasData);
  366. atlas = new TextureAtlas(texture, xml);
  367. _items.length = 0;
  368. _canvas.removeChildren();
  369. _items = null;
  370. xml = null;
  371. _canvas = null;
  372. _currentLab = null;
  373. //_x = _y = _margin = null;
  374. return atlas;
  375. }
  376. /**
  377. * This method will register a Bitmap Font based on each char that belongs to a String.
  378. *
  379. * @param chars:String - The collection of chars which will become the Bitmap Font
  380. * @param fontFamily:String - The name of the Font that will be converted to a Bitmap Font
  381. * @param fontSize:Number - The size in pixels of the font.
  382. * @param bold:Boolean - A flag indicating if the font will be rasterized as bold.
  383. * @param italic:Boolean - A flag indicating if the font will be rasterized as italic.
  384. * @param charMarginX:int - The number of pixels that each character should have as horizontal margin (negative values are allowed). Default value is 0.
  385. * @param fontCustomID:String - A custom font family name indicated by the user. Helpful when using differnt effects for the same font. [Optional]
  386. */
  387. static public function bitmapFontFromString(chars:String, fontFamily:String, fontSize:Number = 12, bold:Boolean = false, italic:Boolean = false, charMarginX:int=0, fontCustomID:String=""):void {
  388. var format:TextFormat = new TextFormat(fontFamily, fontSize, 0xFFFFFF, bold, italic);
  389. var tf:flash.text.TextField = new flash.text.TextField();
  390. tf.autoSize = TextFieldAutoSize.LEFT;
  391. // If the font is an embedded one (I couldn't get to work the Array.indexOf method) :(
  392. if (isEmbedded(fontFamily)) {
  393. tf.antiAliasType = AntiAliasType.ADVANCED;
  394. tf.embedFonts = true;
  395. }
  396. tf.defaultTextFormat = format;
  397. tf.text = chars;
  398. if (fontCustomID == "") fontCustomID = fontFamily;
  399. bitmapFontFromTextField(tf, charMarginX, fontCustomID);
  400. }
  401. /**
  402. * This method will register a Bitmap Font based on each char that belongs to a regular flash TextField, rasterizing filters and color transforms as well.
  403. *
  404. * @param tf:flash.text.TextField - The textfield that will be used to rasterize every char of the text property
  405. * @param charMarginX:int - The number of pixels that each character should have as horizontal margin (negative values are allowed). Default value is 0.
  406. * @param fontCustomID:String - A custom font family name indicated by the user. Helpful when using differnt effects for the same font. [Optional]
  407. */
  408. static public function bitmapFontFromTextField(tf:flash.text.TextField, charMarginX:int=0, fontCustomID:String=""):void {
  409. var charCol:Vector.<String> = Vector.<String>(tf.text.split(""));
  410. var format:TextFormat = tf.defaultTextFormat;
  411. var fontFamily:String = format.font;
  412. var fontSize:Object = format.size;
  413. var oldAutoSize:String = tf.autoSize;
  414. tf.autoSize = TextFieldAutoSize.LEFT;
  415. var canvasData:BitmapData;
  416. var texture:Texture;
  417. var xml:XML;
  418. var myChar:String;
  419. _margin = 0;
  420. _preserveColor = true;
  421. _items = [];
  422. var itm:TextureItem;
  423. var itemsLen:int;
  424. if (!_canvas) _canvas = new Sprite();
  425. // Add the blank space char if not present;
  426. if (charCol.indexOf(" ") == -1) charCol.push(" ");
  427. for (var i:int = charCol.length - 1; i > -1; i--) {
  428. myChar = tf.text = charCol[i];
  429. drawItem(tf, myChar.charCodeAt().toString());
  430. }
  431. _currentLab = "";
  432. layoutChildren();
  433. canvasData = new BitmapData(_canvas.width, _canvas.height, true, 0x000000);
  434. canvasData.draw(_canvas);
  435. itemsLen = _items.length;
  436. xml = new XML(<font></font>);
  437. var infoNode:XML = new XML(<info />);
  438. infoNode.@face = (fontCustomID == "")? fontFamily : fontCustomID;
  439. infoNode.@size = fontSize;
  440. xml.appendChild(infoNode);
  441. //var commonNode:XML = new XML(<common alphaChnl="1" redChnl="0" greenChnl="0" blueChnl="0" />);
  442. var commonNode:XML = new XML(<common />);
  443. commonNode.@lineHeight = fontSize;
  444. xml.appendChild(commonNode);
  445. xml.appendChild(new XML(<pages><page id="0" file="texture.png" /></pages>));
  446. var charsNode:XML = new XML(<chars> </chars>);
  447. charsNode.@count = itemsLen;
  448. var charNode:XML;
  449. for (var k:uint = 0; k < itemsLen; k++)
  450. {
  451. itm = _items[k];
  452. itm.graphic.dispose();
  453. // xml
  454. charNode = new XML(<char page="0" xoffset="0" yoffset="0"/>);
  455. charNode.@id = itm.textureName;
  456. charNode.@x = itm.x;
  457. charNode.@y = itm.y;
  458. charNode.@width = itm.width;
  459. charNode.@height = itm.height;
  460. charNode.@xadvance = itm.width + 2*charMarginX;
  461. charsNode.appendChild(charNode);
  462. }
  463. xml.appendChild(charsNode);
  464. texture = Texture.fromBitmapData(canvasData);
  465. TextField.registerBitmapFont(new BitmapFont(texture, xml));
  466. _items.length = 0;
  467. _canvas.removeChildren();
  468. tf.autoSize = oldAutoSize;
  469. tf.text = charCol.join();
  470. _items = null;
  471. xml = null;
  472. _canvas = null;
  473. _currentLab = null;
  474. }
  475. }
  476. }