diff --git a/exporter/src/main/as/flump/SwfTexture.as b/exporter/src/main/as/flump/SwfTexture.as
index ebc8b58..7c0993a 100644
--- a/exporter/src/main/as/flump/SwfTexture.as
+++ b/exporter/src/main/as/flump/SwfTexture.as
@@ -6,11 +6,15 @@ package flump {
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.DisplayObject;
+import flash.display.DisplayObjectContainer;
import flash.display.MovieClip;
import flash.display.Sprite;
import flash.display.StageQuality;
+import flash.filters.ColorMatrixFilter;
+import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;
+import flump.xfl.XflMovie;
import flump.executor.load.LoadedSwf;
import flump.mold.MovieMold;
@@ -19,11 +23,11 @@ import flump.xfl.XflTexture;
public class SwfTexture
{
- public var symbol :String;
- public var origin :Point;
- public var w :int, h :int, a :int;
- public var scale :Number;
- public var quality :String;
+ public function get symbol():String { return _symbol; }
+ public function get origin():Point { return new Point(_origin.x*_scale, _origin.y*_scale); }
+ public function get w():int { return Math.ceil(_w*_scale); }
+ public function get h():int { return Math.ceil(_h*_scale); }
+ public function get a():int { return this.w*this.h; }
public static function fromFlipbook (lib :XflLibrary, movie :MovieMold, frame :int,
quality :String = StageQuality.BEST, scale :Number = 1) :SwfTexture {
@@ -42,50 +46,131 @@ public class SwfTexture
const instance :Object = new klass();
const disp :DisplayObject = (instance is BitmapData) ?
new Bitmap(BitmapData(instance)) : DisplayObject(instance);
+ disp.filters = disp.filters.concat(tex.filters);
return new SwfTexture(tex.symbol, disp, scale, quality);
}
public function SwfTexture (symbol :String, disp :DisplayObject, scale :Number, quality :String) {
- this.symbol = symbol;
- this.scale = scale;
- this.quality = quality;
- _disp = disp;
-
- origin = getOrigin(_disp, scale);
-
- const size :Point = getSize(_disp, scale);
- w = size.x;
- h = size.y;
- a = w * h;
+ _symbol = symbol;
+ _quality = quality;
+
+ // wrap object twice for convenience
+ var wrapper:Sprite = new Sprite();
+ wrapper.addChild(disp);
+ _disp = new Sprite();
+ _disp.addChild(wrapper);
+
+ // set the scale and size info
+ _treatAsFiltered = hasPotentiallySizeAlteringFilters(disp);
+ setScale(scale);
+ }
+
+ // scale can be changed after creation, if desired
+ public function setScale(value:Number):void
+ {
+ // To compensate for the fact that filters don't "scale", do the following
+ // if filtered:
+ // render at scale 1
+ // then scale bitmapData to target size
+ // if not filtered:
+ // set the scale in the wrapper
+ // render directly at target size with vector renderer
+ if (_treatAsFiltered) {
+ // cache the scale to use later
+ _scale = value;
+ // only need to calculate size once, since _disp is not changing
+ if (_visualBounds == null) {
+ recalculateSizeInfo();
+ }
+ } else {
+ // embed the scale in _disp
+ var wrapper:DisplayObject = _disp.getChildAt(0);
+ wrapper.scaleX = wrapper.scaleY = value;
+ _scale = 1;
+ // recalculate size since _disp has changed
+ recalculateSizeInfo();
+ }
}
public function toBitmapData (borderPadding :int = 0) :BitmapData {
- const bmd :BitmapData = Util.renderToBitmapData(_disp, w, h, quality, scale);
+ // render with vector renderer
+ var bmd :BitmapData = new BitmapData(Math.ceil(_w), Math.ceil(_h), true, 0x00);
+ var m :Matrix = new Matrix();
+ m.translate(_origin.x, _origin.y);
+ bmd.drawWithQuality(_disp, m, null, null, null, true, _quality);
+
+ // scale bitmap to target size if necessary (only used if _disp contains filters)
+ if (_scale != 1.0) {
+ bmd = Util.renderToBitmapData(bmd, this.w, this.h, _quality, _scale);
+ }
+
+ // add padding if necessary
return (borderPadding > 0 ? Util.padBitmapBorder(bmd, borderPadding) : bmd);
}
- public function toString () :String { return "a " + a + " w " + w + " h " + h; }
-
- protected static function getSize (disp :DisplayObject, scale :Number) :Point {
- const bounds :Rectangle = getBounds(disp, scale);
- return new Point(Math.ceil(bounds.width), Math.ceil(bounds.height));
- }
-
- protected static function getOrigin (disp :DisplayObject, scale :Number) :Point {
- const bounds :Rectangle = getBounds(disp, scale);
- return new Point(-bounds.x, -bounds.y);
+ public function toString () :String { return "a " + this.a + " w " + this.w + " h " + this.h; }
+
+ private function recalculateSizeInfo() :void {
+ // get normal bounds
+ _strictBounds = _disp.getChildAt(0).getBounds(_disp);
+ _visualBounds = _strictBounds;
+
+ // possibly increase the visual bounds (due to filter action)
+ if (_treatAsFiltered) {
+ // render to bmd
+ var topLeft:Point = new Point(_s_filteredBmd.width / 2 - _strictBounds.width / 2 - _strictBounds.x, _s_filteredBmd.height / 2 - _strictBounds.height / 2 - _strictBounds.y);
+ var m :Matrix = new Matrix(1,0,0,1, topLeft.x, topLeft.y);
+ _s_filteredBmd.drawWithQuality(_disp, m, null, null, null, true, _quality);
+
+ // calculate visual bounds
+ _visualBounds = _s_filteredBmd.getColorBoundsRect(0xff000000, 0x00000000, false);
+ _s_filteredBmd.fillRect(_visualBounds, 0x0);
+
+ // adjust registration point
+ _visualBounds.x = -(topLeft.x - _visualBounds.x);
+ _visualBounds.y = -(topLeft.y - _visualBounds.y);
+ }
+
+ // calculate derivative info
+ _origin = new Point(-_visualBounds.x, -_visualBounds.y);
+ _w = _visualBounds.width;
+ _h = _visualBounds.height;
}
-
- protected static function getBounds (disp :DisplayObject, scale :Number) :Rectangle {
- const oldScale :Number = disp.scaleX;
- disp.scaleX = disp.scaleY = scale;
- const holder :Sprite = new Sprite();
- holder.addChild(disp);
- const bounds :Rectangle = disp.getBounds(holder);
- disp.scaleX = disp.scaleY = oldScale;
- return bounds;
+
+ private function hasPotentiallySizeAlteringFilters(dObj:DisplayObject) :Boolean {
+ // check dObj's filter list
+ var filters:Array = dObj.filters;
+ for (var ff:int = 0, nf:int = filters.length; ff < nf; ++ff) {
+ // all standard filters except ColorMatrixFilter can change the visual bounds
+ if (!(filters[ff] is ColorMatrixFilter)) {
+ return true;
+ }
+ }
+ // recursively check children
+ var dObjContainer:flash.display.DisplayObjectContainer = dObj as flash.display.DisplayObjectContainer;
+ if (dObjContainer) {
+ for (var cc:int = 0, nc:int = dObjContainer.numChildren; cc < nc; ++cc) {
+ var child:DisplayObject = dObjContainer.getChildAt(cc);
+ if (hasPotentiallySizeAlteringFilters(child)) {
+ return true;
+ }
+ }
+ }
+ // all clear
+ return false;
}
-
- protected var _disp :DisplayObject;
+
+ private var _symbol :String;
+ private var _disp :DisplayObjectContainer;
+ private var _w :int, _h :int;
+ private var _origin :Point;
+ private var _strictBounds :Rectangle;
+ private var _visualBounds :Rectangle;
+ private var _scale :Number;
+ private var _quality :String;
+ private var _treatAsFiltered:Boolean;
+
+ static private var _s_filteredBmd:BitmapData = new BitmapData(2048, 2048, true, 0x0);
+ { _s_filteredBmd.lock(); }
}
}
diff --git a/exporter/src/main/as/flump/xfl/XflKeyframe.as b/exporter/src/main/as/flump/xfl/XflKeyframe.as
index 12ef881..82e8260 100644
--- a/exporter/src/main/as/flump/xfl/XflKeyframe.as
+++ b/exporter/src/main/as/flump/xfl/XflKeyframe.as
@@ -3,7 +3,15 @@
package flump.xfl {
+import fl.motion.AdjustColor;
+import flash.filters.BevelFilter;
+import flash.filters.BitmapFilter;
+import flash.filters.BlurFilter;
+import flash.filters.ColorMatrixFilter;
+import flash.filters.DropShadowFilter;
+import flash.filters.GlowFilter;
import flash.geom.Matrix;
+import flash.utils.Dictionary;
import flump.mold.KeyframeMold;
@@ -106,7 +114,110 @@ public class XflKeyframe
kf.alpha = XmlUtil.getNumberAttr(colorXml, "alphaMultiplier", 1);
}
}
+
+ parseFilters(lib, location, kf, symbolXml);
+
return kf;
}
+
+ // Parse filters for this symbol+keyframe, store them in a static lookup table
+ protected static function parseFilters (lib :XflLibrary, location :String, kf :KeyframeMold, symbolXml :XML) :void {
+ // if filter list is empty, early out
+ if (symbolXml.filters == null) {
+ return;
+ }
+
+ // initialize lookup table entry
+ _s_filtersByKeyframe[kf] = [];
+
+ // for each filter nodes, parse the xml and add a BitmapFilter to the lookup table
+ var filter:BitmapFilter;
+ for each (var filterXml:XML in symbolXml.filters.elements()) {
+ // parse different filter types into their native flash.filter types
+ if (filterXml.name().localName == "AdjustColorFilter") {
+ //
+ var colorFilter:AdjustColor = new AdjustColor();
+ colorFilter.hue = XmlUtil.getNumberAttr(filterXml, "hue", 0);
+ colorFilter.saturation = XmlUtil.getNumberAttr(filterXml, "saturation", 0);
+ colorFilter.brightness = XmlUtil.getNumberAttr(filterXml, "brightness", 0);
+ colorFilter.contrast = XmlUtil.getNumberAttr(filterXml, "contrast", 0);
+ var mMatrix:Array = colorFilter.CalculateFinalFlatArray();
+ filter = new ColorMatrixFilter(mMatrix);
+ _s_filtersByKeyframe[kf].push(filter);
+ } else if (filterXml.name().localName == "BlurFilter") {
+ //
+ filter = new BlurFilter(
+ XmlUtil.getNumberAttr(filterXml, "blurX", 5),
+ XmlUtil.getNumberAttr(filterXml, "blurY", 5),
+ XmlUtil.getNumberAttr(filterXml, "quality", 1)
+ );
+ _s_filtersByKeyframe[kf].push(filter);
+ } else if (filterXml.name().localName == "BevelFilter") {
+ //
+ filter = new BevelFilter(
+ XmlUtil.getNumberAttr(filterXml, "distance", 5.0),
+ XmlUtil.getNumberAttr(filterXml, "angle", 45),
+ parseInt(XmlUtil.getStringAttr(filterXml, "highlightColor", "#ffffff").substr(1), 16),
+ XmlUtil.getNumberAttr(filterXml, "highlightAlpha", 1.0),
+ parseInt(XmlUtil.getStringAttr(filterXml, "shadowColor", "#000000").substr(1), 16),
+ XmlUtil.getNumberAttr(filterXml, "shadowAlpha", 1.0),
+ XmlUtil.getNumberAttr(filterXml, "blurX", 5),
+ XmlUtil.getNumberAttr(filterXml, "blurY", 5),
+ XmlUtil.getNumberAttr(filterXml, "strength", 1),
+ XmlUtil.getNumberAttr(filterXml, "quality", 1),
+ XmlUtil.getStringAttr(filterXml, "type", "inner"),
+ XmlUtil.getBooleanAttr(filterXml, "knockout", false)
+ );
+ _s_filtersByKeyframe[kf].push(filter);
+ } else if (filterXml.name().localName == "DropShadowFilter") {
+ //
+ filter = new DropShadowFilter(
+ XmlUtil.getNumberAttr(filterXml, "distance", 5.0),
+ XmlUtil.getNumberAttr(filterXml, "angle", 45),
+ parseInt(XmlUtil.getStringAttr(filterXml, "color", "#000000").substr(1), 16),
+ XmlUtil.getNumberAttr(filterXml, "alpha", 1.0),
+ XmlUtil.getNumberAttr(filterXml, "blurX", 5),
+ XmlUtil.getNumberAttr(filterXml, "blurY", 5),
+ XmlUtil.getNumberAttr(filterXml, "strength", 1),
+ XmlUtil.getNumberAttr(filterXml, "quality", 1),
+ XmlUtil.getBooleanAttr(filterXml, "inner", false),
+ XmlUtil.getBooleanAttr(filterXml, "knockout", false),
+ XmlUtil.getBooleanAttr(filterXml, "hideObject", false)
+ );
+ _s_filtersByKeyframe[kf].push(filter);
+ } else if (filterXml.name().localName == "GlowFilter") {
+ //
+ filter = new GlowFilter(
+ parseInt(XmlUtil.getStringAttr(filterXml, "color", "#ff0000").substr(1), 16),
+ XmlUtil.getNumberAttr(filterXml, "alpha", 1),
+ XmlUtil.getNumberAttr(filterXml, "blurX", 5),
+ XmlUtil.getNumberAttr(filterXml, "blurY", 5),
+ XmlUtil.getNumberAttr(filterXml, "strength", 1),
+ XmlUtil.getNumberAttr(filterXml, "quality", 1),
+ XmlUtil.getBooleanAttr(filterXml, "inner", false),
+ XmlUtil.getBooleanAttr(filterXml, "knockout", false)
+ );
+ _s_filtersByKeyframe[kf].push(filter);
+ } else {
+ // parsing for this filter type is unimplemented
+ lib.addError(location, ParseError.WARN, "Unimplemented parsing for filter type: '" + filterXml.name().localName + "'");
+ }
+
+ // if we only parsed unsupported filter types, remove the lookup table entry
+ if (_s_filtersByKeyframe[kf].length == 0) {
+ delete _s_filtersByKeyframe[kf];
+ }
+ }
+ }
+
+ // lookup table for filters associated with a given KeyframeMold
+ static private var _s_filtersByKeyframe:Dictionary = new Dictionary(true);
+ static public function getFiltersForKeyframe(kf:KeyframeMold):Array
+ {
+ return (kf in _s_filtersByKeyframe) ? _s_filtersByKeyframe[kf] : null;
+ }
+
}
}
diff --git a/exporter/src/main/as/flump/xfl/XflLibrary.as b/exporter/src/main/as/flump/xfl/XflLibrary.as
index 8954e48..74765e8 100644
--- a/exporter/src/main/as/flump/xfl/XflLibrary.as
+++ b/exporter/src/main/as/flump/xfl/XflLibrary.as
@@ -87,7 +87,87 @@ public class XflLibrary
}
}
- for each (movie in movies) if (isExported(movie)) prepareForPublishing(movie);
+ resolveKfRefs();
+ var sortedMovies:Vector. = getSortedMovies();
+ for each (movie in sortedMovies) {
+ if (isExported(movie)) {
+ propagateFilters(movie, []);
+ prepareForPublishing(movie);
+ }
+ }
+ }
+
+ protected function resolveKfRefs():void {
+ for (var ii:int = 0; ii < movies.length; ++ii) {
+ var movie:MovieMold = movies[ii];
+ if (movie.flipbook)
+ continue;
+ for each (var layer:LayerMold in movie.layers) {
+ for each (var kf:KeyframeMold in layer.keyframes) {
+ if (kf.ref == null)
+ continue;
+ kf.ref = _libraryNameToId.get(kf.ref);
+ }
+ }
+ }
+ }
+
+ // Get movies sorted such that contained movies are after the movies that contain them
+ protected function getSortedMovies():Vector. {
+ var sortedMovies:Vector. = movies.concat();
+ for (var ii:int = 0; ii < sortedMovies.length; ++ii) {
+ var movie:MovieMold = sortedMovies[ii];
+ for each (var layer:LayerMold in movie.layers) {
+ for each (var kf:KeyframeMold in layer.keyframes) {
+ if (kf.ref == null)
+ continue;
+ var item:Object = _idToItem[kf.ref];
+ if (item is MovieMold) {
+ var movieToMove:MovieMold = item as MovieMold;
+ var arrayIndex:int = sortedMovies.indexOf(movieToMove);
+ if (arrayIndex < ii) {
+ sortedMovies.splice(arrayIndex, 1);
+ ii--;
+ }
+ sortedMovies.splice(ii + 1, 0, movieToMove);
+ }
+ }
+ }
+ }
+
+ return sortedMovies;
+ }
+
+ // Propagate filters down the display graph from a movie and attach them to leaf nodes
+ protected function propagateFilters(movie:MovieMold, inFilters:Array):void {
+ for each (var layer:LayerMold in movie.layers) {
+ for each (var kf:KeyframeMold in layer.keyframes) {
+ var kfFilters:Array = XflKeyframe.getFiltersForKeyframe(kf);
+ var filters:Array = kfFilters ? inFilters.concat(kfFilters) : inFilters;
+ var swfTexture:SwfTexture = null;
+ if (movie.flipbook) {
+ // Nothing to be done
+ } else {
+ if (kf.ref == null)
+ continue;
+ var item:Object = _idToItem[kf.ref];
+ if (item == null){
+ // Nothing to be done
+ } else if (item is MovieMold) {
+ // Propagate filters to component movie
+ propagateFilters(MovieMold(item), filters);
+ } else if (item is XflTexture) {
+ // Assign filters to this XflTexture
+ const tex:XflTexture = XflTexture(item);
+ // If filters have previously been assigned,
+ // replace them only if new list is longer
+ if (filters.length > tex.filters.length) {
+ tex.filters = filters;
+ }
+ }
+ }
+ }
+ }
}
protected function prepareForPublishing (movie :MovieMold) :void {
@@ -105,7 +185,6 @@ public class XflLibrary
} else {
if (kf.ref == null) continue;
- kf.ref = _libraryNameToId.get(kf.ref);
var item :Object = _idToItem[kf.ref];
if (item == null) {
addTopLevelError(ParseError.CRIT,
diff --git a/exporter/src/main/as/flump/xfl/XflTexture.as b/exporter/src/main/as/flump/xfl/XflTexture.as
index 820580a..8db83f9 100644
--- a/exporter/src/main/as/flump/xfl/XflTexture.as
+++ b/exporter/src/main/as/flump/xfl/XflTexture.as
@@ -11,6 +11,7 @@ import com.threerings.util.XmlUtil;
public class XflTexture
{
public var symbol :String;
+ public var filters :Array = [];
public function XflTexture (lib :XflLibrary, location :String, xml :XML) {
symbol = XmlUtil.getStringAttr(xml, "linkageClassName");
@@ -21,5 +22,6 @@ public class XflTexture
var swfTex :SwfTexture = SwfTexture.fromTexture(swf, this);
return (swfTex.w > 0 && swfTex.h > 0);
}
+
}
}