Anda di halaman 1dari 12

package com.emibap.

textureAtlas
{

import flash.display.BitmapData;
import flash.display.DisplayObject;
import flash.display.MovieClip;
import flash.display.Sprite;
import flash.geom.ColorTransform;
import flash.geom.Matrix;
import flash.geom.Rectangle;
import flash.text.AntiAliasType;
import flash.text.Font;
import flash.text.TextFieldAutoSize;
import flash.text.TextFormat;
import flash.utils.getQualifiedClassName;

import starling.text.BitmapFont;
import starling.textures.Texture;
import starling.textures.TextureAtlas;
import starling.text.TextField;

import com.emibap.textureAtlas.TextureItem;

/**
* DynamicAtlas.as
* https://github.com/emibap/Dynamic-Texture-Atlas-Generator
* @author Emibap (Emiliano Angelini) - http://www.emibap.com
* Contribution by Thomas Haselwanter -
https://github.com/thomashaselwanter
* Most of this comes thanks to the inspiration (and code) of Thibault Imbert
(http://www.bytearray.org) and Nicolas Gans (http://www.flashxpress.net/)
*
* Dynamic Texture Atlas and Bitmap Font Generator (Starling framework
Extension)
* ========
*
* This tool will convert any MovieClip containing Other MovieClips, Sprites
or Graphics into a starling Texture Atlas, all in runtime.
* It can also register bitmap Fonts from system or embedded regular fonts.
* 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.
* 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.
* 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/).
*
* # version 1.0 #
* - 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.
* - Added the fontCustomID parameter to the Bitmap font creation. Thank you
Regan.
*
* ### Features ###
*
* * 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
* * Filters made to the objects are captured
* * Color transforms (tint, alpha) are optionally captured
* * Scales the objects (and also the filters) to a specified value
* * Automatically detects the objects bounds so you don't necessarily have
to set the registration points to TOP LEFT
* * Registers Bitmap Fonts based on system or embedded fonts from strings or
from good old Flash TextFields
*
* ### TODO List ###
*
* * Further code optimization
* * A better implementation of the Bitmap Font creation process
* * Documentation (?)
*
* ### Whish List ###
* * Optional division of the process into small intervals (for smooth
performance of the app)
*
* ### Usage ###
*
* You can use the following static methods (examples at the gitHub Repo):
*
* [Texture Atlas creation]
* - DynamicAtlas.fromMovieClipContainer(swf:flash.display.MovieClip,
scaleFactor:Number = 1, margin:uint=0, preserveColor:Boolean =
true):starling.textures.TextureAtlas
* - DynamicAtlas.fromClassVector(assets:Vector.<Class>,
scaleFactor:Number = 1, margin:uint=0, preserveColor:Boolean =
true):starling.textures.TextureAtlas
*
* [Bitmap Font registration]
* - DynamicAtlas.bitmapFontFromString(chars:String, fontFamily:String,
fontSize:Number = 12, bold:Boolean = false, italic:Boolean = false,
charMarginX:int=0):void
* - DynamicAtlas.bitmapFontFromTextField(tf:flash.text.TextField,
charMarginX:int=0):void
*
* Enclose inside a try/catch for error handling:
* try {
* var atlas:TextureAtlas =
DynamicAtlas.fromMovieClipContainer(mc);
* } catch (e:Error) {
* 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);
* }
*
* History:
* -------
* # version 0.9.5 #
* - Added the fromClassVector static function. Thank you Thomas Haselwanter
*
* # version 0.9 #
* - Added Bitmap Font creation support
* - Scaling also applies to filters.
* - Added Margin and PreserveColor Properties
*
* # version 0.8 #
* - Added the scaleFactor constructor parameter. Now you can define a custom
scale to the final result.
* - Scaling also applies to filters.
* - Added Margin and PreserveColor Properties
*
* # version 0.7 #
* First Public version
**/

public class DynamicAtlas


{
static protected const DEFAULT_CANVAS_WIDTH:Number = 640;

static protected var _items:Array;


static protected var _canvas:Sprite;

static protected var _currentLab:String;

static protected var _x:Number;


static protected var _y:Number;

static protected var _bData:BitmapData;


static protected var _mat:Matrix;
static protected var _margin:Number;
static protected var _preserveColor:Boolean;

// Will not be used - Only using one static method


public function DynamicAtlas()
{

// Private methods

static protected function appendIntToString(num:int,


numOfPlaces:int):String
{
var numString:String = num.toString();
var outString:String = "";
for (var i:int = 0; i < numOfPlaces - numString.length; i++)
{
outString += "0";
}
return outString + numString;
}

static protected function layoutChildren():void


{
var xPos:Number = 0;
var yPos:Number = 0;
var maxY:Number = 0;
var len:int = _items.length;

var itm:TextureItem;

for (var i:uint = 0; i < len; i++)


{
itm = _items[i];
if ((xPos + itm.width) > DEFAULT_CANVAS_WIDTH)
{
xPos = 0;
yPos += maxY;
maxY = 0;
}
if (itm.height + 1 > maxY)
{
maxY = itm.height + 1;
}
itm.x = xPos;
itm.y = yPos;
xPos += itm.width + 1;
}
}

/**
* isEmbedded
*
* @param fontFamily:Boolean - The name of the Font
* @return Boolean - True if the font is an embedded one
*/
static protected function isEmbedded(fontFamily:String):Boolean
{
var embeddedFonts:Vector.<Font> =
Vector.<Font>(Font.enumerateFonts());

for (var i:int = embeddedFonts.length - 1; i > -1 &&


embeddedFonts[i].fontName != fontFamily; i--) { }

return (i > -1);

static protected function getRealBounds(clip:DisplayObject):Rectangle {


var bounds:Rectangle = clip.getBounds(clip.parent);
bounds.x = Math.floor(bounds.x);
bounds.y = Math.floor(bounds.y);
bounds.height = Math.ceil(bounds.height);
bounds.width = Math.ceil(bounds.width);

var realBounds:Rectangle = new Rectangle(0, 0, bounds.width +


_margin * 2, bounds.height + _margin * 2);

// Checking filters in case we need to expand the outer bounds


if (clip.filters.length > 0)
{
// filters
var j:int = 0;
//var clipFilters:Array = clipChild.filters.concat();
var clipFilters:Array = clip.filters;
var clipFiltersLength:int = clipFilters.length;
var tmpBData:BitmapData;
var filterRect:Rectangle;

tmpBData = new BitmapData(realBounds.width,


realBounds.height, false);
filterRect = tmpBData.generateFilterRect(tmpBData.rect,
clipFilters[j]);
realBounds = realBounds.union(filterRect);
tmpBData.dispose();

while (++j < clipFiltersLength)


{
tmpBData = new BitmapData(filterRect.width,
filterRect.height, true, 0);
filterRect =
tmpBData.generateFilterRect(tmpBData.rect, clipFilters[j]);
realBounds = realBounds.union(filterRect);
tmpBData.dispose();
}
}

realBounds.offset(bounds.x, bounds.y);
realBounds.width = Math.max(realBounds.width, 1);
realBounds.height = Math.max(realBounds.height, 1);

tmpBData = null;
return realBounds;
}

/**
* drawItem - This will actually rasterize the display object passed as
a parameter
* @param clip
* @param name
* @param baseName
* @param clipColorTransform
* @param frameBounds
* @return TextureItem
*/
static protected function drawItem(clip:DisplayObject, name:String =
"", baseName:String = "", clipColorTransform:ColorTransform = null,
frameBounds:Rectangle=null):TextureItem
{
var realBounds:Rectangle = getRealBounds(clip);

_bData = new BitmapData(realBounds.width, realBounds.height,


true, 0);
_mat = clip.transform.matrix;
_mat.translate(-realBounds.x + _margin, -realBounds.y + _margin);

_bData.draw(clip, _mat, _preserveColor ? clipColorTransform :


null);

var label:String = "";


if (clip is MovieClip) {
if (clip["currentLabel"] != _currentLab &&
clip["currentLabel"] != null)
{
_currentLab = clip["currentLabel"];
label = _currentLab;
}
}

if (frameBounds) {
realBounds.x = frameBounds.x - realBounds.x;
realBounds.y = frameBounds.y - realBounds.y;
realBounds.width = frameBounds.width;
realBounds.height = frameBounds.height;
}

var item:TextureItem = new TextureItem(_bData, name, label,


realBounds.x, realBounds.y, realBounds.width, realBounds.height);

_items.push(item);
_canvas.addChild(item);

_bData = null;

return item;
}

// Public methods

/**
* This method takes a vector of DisplayObject class and converts it into a
Texture Atlas.
*
* @param assets:Vector.<Class> - The DisplayObject classes you wish to
convert into a TextureAtlas. Must contain classes whose instances are of type
DisplayObject that will be rasterized and become the subtextures of your Atlas.
* @param scaleFactor:Number - The scaling factor to apply to every object.
Default value is 1 (no scaling).
* @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).
* @param preserveColor:Boolean - A Flag which indicates if the color
transforms should be captured or not. Default value is true (capture color
transform).
* @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.
* @return TextureAtlas - The dynamically generated Texture Atlas.
*/
static public function fromClassVector(assets:Vector.<Class>,
scaleFactor:Number = 1, margin:uint=0, preserveColor:Boolean = true,
checkBounds:Boolean=false):TextureAtlas
{
var container:MovieClip = new MovieClip();
for each (var assetClass:Class in assets) {
var assetInstance:DisplayObject = new assetClass();
assetInstance.name = getQualifiedClassName(assetClass);
container.addChild(assetInstance);
}
return fromMovieClipContainer(container, scaleFactor, margin,
preserveColor, checkBounds);
}

/** Retrieves all textures for a class. Returns <code>null</code> if it is


not found.
* This method can be used if TextureAtlass doesn't support classes.
*/
static public function getTexturesByClass(textureAtlas:TextureAtlas,
assetClass:Class):Vector.<Texture> {
return textureAtlas.getTextures(getQualifiedClassName(assetClass));
}

/**
* This method will take a MovieClip sprite sheet (containing other
display objects) and convert it into a Texture Atlas.
*
* @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.
* @param scaleFactor:Number - The scaling factor to apply to every
object. Default value is 1 (no scaling).
* @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).
* @param preserveColor:Boolean - A Flag which indicates if the color
transforms should be captured or not. Default value is true (capture color
transform).
* @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.
* @return TextureAtlas - The dynamically generated Texture Atlas.
*/
static public function fromMovieClipContainer(swf:Sprite,
scaleFactor:Number = 1, margin:uint=0, preserveColor:Boolean = true,
checkBounds:Boolean=false):TextureAtlas
{
var parseFrame:Boolean = false;
var selected:DisplayObject;
var selectedTotalFrames:int;
var selectedColorTransform:ColorTransform;
var frameBounds:Rectangle = new Rectangle(0, 0, 0, 0);

var children:uint = swf.numChildren;

var canvasData:BitmapData;

var texture:Texture;
var xml:XML;
var subText:XML;
var atlas:TextureAtlas;

var itemsLen:int;
var itm:TextureItem;

var m:uint;

_margin = margin;
_preserveColor = preserveColor;

_items = [];

if (!_canvas)
_canvas = new Sprite();

if(swf is MovieClip)
MovieClip(swf).gotoAndStop(1);

for (var i:uint = 0; i < children; i++)


{
selected = swf.getChildAt(i);
selectedColorTransform = selected.transform.colorTransform;
_x = selected.x;
_y = selected.y;

// Scaling if needed (including filters)


if (scaleFactor != 1)
{

selected.scaleX *= scaleFactor;
selected.scaleY *= scaleFactor;

if (selected.filters.length > 0)
{
var filters:Array = selected.filters;
var filtersLen:int = selected.filters.length;
var filter:Object;
for (var j:uint = 0; j < filtersLen; j++)
{
filter = filters[j];

if (filter.hasOwnProperty("blurX"))
{
filter.blurX *= scaleFactor;
filter.blurY *= scaleFactor;
}
if (filter.hasOwnProperty("distance"))
{
filter.distance *= scaleFactor;
}
}
selected.filters = filters;
}
}

// Not all children will be MCs. Some could be sprites


if (selected is MovieClip)
{
selectedTotalFrames =
MovieClip(selected).totalFrames;
// Gets the frame bounds by performing a frame-by-
frame check
if (checkBounds) {
MovieClip(selected).gotoAndStop(0);
frameBounds = getRealBounds(selected);
m = 1;
while (++m <= selectedTotalFrames)
{
MovieClip(selected).gotoAndStop(m);
frameBounds =
frameBounds.union(getRealBounds(selected));
}
}
}
else selectedTotalFrames = 1;
m = 0;
// Draw every frame (if MC - else will just be one)
while (++m <= selectedTotalFrames)
{
if (selected is MovieClip)
MovieClip(selected).gotoAndStop(m);
drawItem(selected, selected.name + "_" +
appendIntToString(m - 1, 5), selected.name, selectedColorTransform, frameBounds);
}
}

_currentLab = "";

layoutChildren();

canvasData = new BitmapData(_canvas.width, _canvas.height, true,


0x000000);
canvasData.draw(_canvas);

xml = new XML(<TextureAtlas></TextureAtlas>);


xml.@imagePath = "atlas.png";

itemsLen = _items.length;

for (var k:uint = 0; k < itemsLen; k++)


{
itm = _items[k];

itm.graphic.dispose();

// xml
subText = new XML(<SubTexture />);
subText.@name = itm.textureName;
subText.@x = itm.x;
subText.@y = itm.y;
subText.@width = itm.width;
subText.@height = itm.height;
subText.@frameX = itm.frameX;
subText.@frameY = itm.frameY;
subText.@frameWidth = itm.frameWidth;
subText.@frameHeight = itm.frameHeight;

if (itm.frameName != "")
subText.@frameLabel = itm.frameName;
xml.appendChild(subText);
}
texture = Texture.fromBitmapData(canvasData);
atlas = new TextureAtlas(texture, xml);

_items.length = 0;
_canvas.removeChildren();

_items = null;
xml = null;
_canvas = null;
_currentLab = null;
//_x = _y = _margin = null;

return atlas;
}

/**
* This method will register a Bitmap Font based on each char that
belongs to a String.
*
* @param chars:String - The collection of chars which will become
the Bitmap Font
* @param fontFamily:String - The name of the Font that will be
converted to a Bitmap Font
* @param fontSize:Number - The size in pixels of the font.
* @param bold:Boolean - A flag indicating if the font will be
rasterized as bold.
* @param italic:Boolean - A flag indicating if the font will be
rasterized as italic.
* @param charMarginX:int - The number of pixels that each character
should have as horizontal margin (negative values are allowed). Default value is 0.
* @param fontCustomID:String - A custom font family name indicated
by the user. Helpful when using differnt effects for the same font. [Optional]
*/
static public function bitmapFontFromString(chars:String,
fontFamily:String, fontSize:Number = 12, bold:Boolean = false, italic:Boolean =
false, charMarginX:int=0, fontCustomID:String=""):void {
var format:TextFormat = new TextFormat(fontFamily, fontSize,
0xFFFFFF, bold, italic);
var tf:flash.text.TextField = new flash.text.TextField();

tf.autoSize = TextFieldAutoSize.LEFT;

// If the font is an embedded one (I couldn't get to work the


Array.indexOf method) :(
if (isEmbedded(fontFamily)) {
tf.antiAliasType = AntiAliasType.ADVANCED;
tf.embedFonts = true;
}

tf.defaultTextFormat = format;
tf.text = chars;

if (fontCustomID == "") fontCustomID = fontFamily;


bitmapFontFromTextField(tf, charMarginX, fontCustomID);
}

/**
* 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.
*
* @param tf:flash.text.TextField - The textfield that will be used
to rasterize every char of the text property
* @param charMarginX:int - The number of pixels that each character
should have as horizontal margin (negative values are allowed). Default value is 0.
* @param fontCustomID:String - A custom font family name indicated
by the user. Helpful when using differnt effects for the same font. [Optional]
*/
static public function bitmapFontFromTextField(tf:flash.text.TextField,
charMarginX:int=0, fontCustomID:String=""):void {
var charCol:Vector.<String> = Vector.<String>(tf.text.split(""));
var format:TextFormat = tf.defaultTextFormat;
var fontFamily:String = format.font;
var fontSize:Object = format.size;
var oldAutoSize:String = tf.autoSize;
tf.autoSize = TextFieldAutoSize.LEFT;

var canvasData:BitmapData;
var texture:Texture;
var xml:XML;

var myChar:String;

_margin = 0;
_preserveColor = true;

_items = [];
var itm:TextureItem;
var itemsLen:int;

if (!_canvas) _canvas = new Sprite();

// Add the blank space char if not present;


if (charCol.indexOf(" ") == -1) charCol.push(" ");

for (var i:int = charCol.length - 1; i > -1; i--) {


myChar = tf.text = charCol[i];
drawItem(tf, myChar.charCodeAt().toString());
}

_currentLab = "";

layoutChildren();

canvasData = new BitmapData(_canvas.width, _canvas.height, true,


0x000000);
canvasData.draw(_canvas);

itemsLen = _items.length;

xml = new XML(<font></font>);


var infoNode:XML = new XML(<info />);
infoNode.@face = (fontCustomID == "")? fontFamily : fontCustomID;
infoNode.@size = fontSize;
xml.appendChild(infoNode);
//var commonNode:XML = new XML(<common alphaChnl="1" redChnl="0"
greenChnl="0" blueChnl="0" />);
var commonNode:XML = new XML(<common />);
commonNode.@lineHeight = fontSize;
xml.appendChild(commonNode);
xml.appendChild(new XML(<pages><page id="0" file="texture.png"
/></pages>));
var charsNode:XML = new XML(<chars> </chars>);
charsNode.@count = itemsLen;
var charNode:XML;

for (var k:uint = 0; k < itemsLen; k++)


{
itm = _items[k];

itm.graphic.dispose();
// xml
charNode = new XML(<char page="0" xoffset="0"
yoffset="0"/>);
charNode.@id = itm.textureName;
charNode.@x = itm.x;
charNode.@y = itm.y;
charNode.@width = itm.width;
charNode.@height = itm.height;
charNode.@xadvance = itm.width + 2*charMarginX;
charsNode.appendChild(charNode);
}

xml.appendChild(charsNode);

texture = Texture.fromBitmapData(canvasData);
TextField.registerBitmapFont(new BitmapFont(texture, xml));

_items.length = 0;
_canvas.removeChildren();

tf.autoSize = oldAutoSize;
tf.text = charCol.join();

_items = null;
xml = null;
_canvas = null;
_currentLab = null;
}

Anda mungkin juga menyukai