From the monthly archives:

January 2010

FreeSpin3D: full 3D in Flash Professional

by Tom Krcha on January 21, 2010


Pixel Bender Explorer

by Jozef Chúťka on January 19, 2010

Pixel Bender Explorer is simple application that lets you dynamicly load .pbj files, apply it on image and edit shader values. Application uses two classes. ShaderLoader class loads external .pbj file and casts loaded binary data into Shader object. ShaderExplorer class inspects created Shader object and generates editable form based on Shader parameters. You can load your own external .pbj files (crossdomain.xml required near .pbj file) or feel free to download and use whole .swf application localy.

Application inspired by Enumerating Pixel Bender filter parameters and metadata dynamically in Flash and Introduction to Pixel Bender: Part 2 articles.

Pixel Bender Explorer application

sk.yoz.shader.ShaderLoader

package sk.yoz.shader
{
    import flash.display.Shader;
    import flash.events.Event;
    import flash.events.EventDispatcher;
    import flash.events.IOErrorEvent;
    import flash.filters.ShaderFilter;
    import flash.net.URLLoader;
    import flash.net.URLLoaderDataFormat;
    import flash.net.URLRequest;

    public class ShaderLoader extends EventDispatcher
    {
        protected var loader:URLLoader = new URLLoader();
        protected var _filters:Array = [];
        protected var _shader:Shader;
        protected var _shaderFilter:ShaderFilter;

        public function ShaderLoader()
        {
            super();

            loader.dataFormat = URLLoaderDataFormat.BINARY;
            loader.addEventListener(Event.COMPLETE, loadCompleteHandler);
            loader.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
        }

        [Bindable(event="filtersChanged")]
        public function get filters():Array
        {
            return _filters;
        }

        protected function set filters(value:Array):void
        {
            _filters = value;
            dispatchEvent(new Event("filtersChanged"));
        }

        [Bindable(event="shaderChanged")]
        public function get shader():Shader
        {
            return _shader;
        }

        protected function set shader(value:Shader):void
        {
            _shader = value;
            dispatchEvent(new Event("shaderChanged"));
        }

        [Bindable(event="shaderFilterChanged")]
        public function get shaderFilter():ShaderFilter
        {
            return _shaderFilter;
        }

        protected function set shaderFilter(value:ShaderFilter):void
        {
            _shaderFilter = value;
            dispatchEvent(new Event("shaderFilterChanged"));
        }

        public function load(url:String):void
        {
            loader.load(new URLRequest(url));
        }

        protected function loadCompleteHandler(event:Event):void
        {
            try
            {
                protected::shader = new Shader(event.target.data);
                protected::shaderFilter = new ShaderFilter(public::shader);
                protected::filters = [public::shaderFilter];
            }
            catch(error:Error)
            {
                protected::shader = null;
                protected::shaderFilter = null;
                protected::filters = [];
            }
        }

        protected function ioErrorHandler(event:IOErrorEvent):void
        {
        }
    }
}

sk.yoz.shader.ShaderExplorer

package sk.yoz.shader
{
    import flash.display.Shader;
    import flash.display.ShaderInput;
    import flash.display.ShaderParameter;
    import flash.events.Event;
    import flash.filters.ShaderFilter;

    import mx.containers.VBox;
    import mx.controls.HRule;
    import mx.controls.HSlider;
    import mx.events.SliderEvent;
    import mx.controls.Spacer;
    import mx.controls.Text;

    public class ShaderExplorer extends VBox
    {
        protected var _shader:Shader;

        public function ShaderExplorer()
        {
            super();
        }

        public function get shader():Shader
        {
            return _shader;
        }

        public function set shader(value:Shader):void
        {
            _shader = value;
            createControls();
            dispatchEvent(new Event("shaderFiltersChanged"));
        }

        [Bindable(event="shaderFiltersChanged")]
        public function get shaderFilters():Array
        {
            if(!shader)
                return [];
            return [new ShaderFilter(shader)];
        }

        protected function createControls():void
        {
            var spacer:Spacer;

            removeAllChildren();
            createDescriptors();
            spacer = new Spacer();
            spacer.height = 20;
            addChild(spacer);
            createInputs();
            spacer = new Spacer();
            spacer.height = 20;
            addChild(spacer);
            createParameters();
        }

        protected function createDescriptors():void
        {
            var param:String;
            for(param in shader.data)
                if(shader.data[param] is String)
                    add_text(param + ": " + shader.data[param]);
        }

        protected function createInputs():void
        {
            var param:String;
            for(param in shader.data)
                if(shader.data[param] is ShaderInput)
                    add_text(param + ": " + shader.data[param]);
        }

        protected function sliderChangeHandler(event:SliderEvent):void
        {
            if(!event.target.name)
                return;
            var name:String = String(event.target.name).replace(/_[0-9]+$/, '');
            var shaderParameter:ShaderParameter = shader.data[name];
            var values:Array = [];
            var i:uint = 0;
            while(getChildByName(name + "_" + i))
                values.push(HSlider(getChildByName(name + "_" + i++)).value);
            shaderParameter.value = values;
            dispatchEvent(new Event("shaderFiltersChanged"));
        }

        protected function createParameters():void
        {
            var param:String;
            var shaderParameter:ShaderParameter;
            for(param in shader.data)
            {
                if(!(shader.data[param] is ShaderParameter))
                    continue;

                shaderParameter = shader.data[param] as ShaderParameter;
                try
                {
                    this["add_" + shaderParameter.type].apply(null, [shaderParameter]);
                }
                catch(error:Error)
                {
                    add_text("unknown type (" + shaderParameter.type + ")");
                }
            }
        }

        protected function add_text(text:String):void
        {
            var txt:Text = new Text();
            txt.text = text;
            txt.percentWidth = 100;
            addChild(txt);
        }

        protected function add_slider(name:String, value:Number, minimum:Number,
            maximum:Number, snapInterval:Number = 0):void
        {
            var slider:HSlider = new HSlider();
            slider.name = name;
            slider.minimum = minimum;
            slider.maximum = maximum;
            slider.value = value;
            slider.snapInterval = snapInterval;
            slider.percentWidth = 100;
            slider.addEventListener(SliderEvent.CHANGE, sliderChangeHandler);
            addChild(slider);
        }

        protected function add_float(p:ShaderParameter):void
        {
            add_text(p.name + " (" + p.type + ")");
            add_slider(p.name + "_0", p.defaultValue[0], p.minValue[0], p.maxValue[0]);
        }

        protected function add_float2(p:ShaderParameter):void
        {
            add_text(p.name + " (" + p.type + ")");
            add_slider(p.name + "_0", p.defaultValue[0], p.minValue[0], p.maxValue[0]);
            add_slider(p.name + "_1", p.defaultValue[1], p.minValue[1], p.maxValue[1]);
        }

        protected function add_float3(p:ShaderParameter):void
        {
            add_text(p.name + " (" + p.type + ")");
            add_slider(p.name + "_0", p.defaultValue[0], p.minValue[0], p.maxValue[0]);
            add_slider(p.name + "_1", p.defaultValue[1], p.minValue[1], p.maxValue[1]);
            add_slider(p.name + "_2", p.defaultValue[2], p.minValue[2], p.maxValue[2]);
        }

        protected function add_float4(p:ShaderParameter):void
        {
            add_text(p.name + " (" + p.type + ")");
            add_slider(p.name + "_0", p.defaultValue[0], p.minValue[0], p.maxValue[0]);
            add_slider(p.name + "_1", p.defaultValue[1], p.minValue[1], p.maxValue[1]);
            add_slider(p.name + "_2", p.defaultValue[2], p.minValue[2], p.maxValue[2]);
            add_slider(p.name + "_3", p.defaultValue[3], p.minValue[3], p.maxValue[3]);
        }

        protected function add_int(p:ShaderParameter):void
        {
            add_text(p.name + " (" + p.type + ")");
            add_slider(p.name + "_0", p.defaultValue[0], p.minValue[0], p.maxValue[0] ,1);
        }

        protected function add_int2(p:ShaderParameter):void
        {
            add_text(p.name + " (" + p.type + ")");
            add_slider(p.name + "_0", p.defaultValue[0], p.minValue[0], p.maxValue[0] ,1);
            add_slider(p.name + "_1", p.defaultValue[1], p.minValue[1], p.maxValue[1] ,1);
        }

        protected function add_int3(p:ShaderParameter):void
        {
            add_text(p.name + " (" + p.type + ")");
            add_slider(p.name + "_0", p.defaultValue[0], p.minValue[0], p.maxValue[0] ,1);
            add_slider(p.name + "_1", p.defaultValue[1], p.minValue[1], p.maxValue[1] ,1);
            add_slider(p.name + "_2", p.defaultValue[2], p.minValue[2], p.maxValue[2] ,1);
        }

        protected function add_int4(p:ShaderParameter):void
        {
            add_text(p.name + " (" + p.type + ")");
            add_slider(p.name + "_0", p.defaultValue[0], p.minValue[0], p.maxValue[0] ,1);
            add_slider(p.name + "_1", p.defaultValue[1], p.minValue[1], p.maxValue[1] ,1);
            add_slider(p.name + "_2", p.defaultValue[2], p.minValue[2], p.maxValue[2] ,1);
            add_slider(p.name + "_3", p.defaultValue[3], p.minValue[3], p.maxValue[3], 1);
        }

        // TODO, matrix, matrix4x4...
    }
}

Pixel Bender Explorer application:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
    layout="vertical" xmlns:shader="sk.yoz.shader.*"
    creationComplete="shadersService.send();" width="620" height="650"
    paddingTop="10" paddingRight="10" paddingBottom="10" paddingLeft="10"
    backgroundGradientColors="{[0xffffff, 0xffffff]}">
<mx:Script>
<![CDATA[
    import sk.yoz.shader.ShaderLoader;

    [Embed(source="../assets/img.jpg")]
    public static const IMAGE_CLASS:Class;

    [Bindable]
    private var loader:ShaderLoader = new ShaderLoader();
]]>
</mx:Script>
<mx:HTTPService id="shadersService" url="shaders.xml" />
<mx:HBox width="100%">
    <mx:CheckBox id="useFilter" selected="true"/>
    <mx:ComboBox id="combo" editable="true"
        close="{loader.load(String(combo.value))}"
        enter="{loader.load(String(combo.value))}"
        dataProvider="{shadersService.lastResult.data.shader}" width="510"/>
    <mx:Button label="Load" click="{loader.load(String(combo.value))}"/>
</mx:HBox>
<mx:HBox width="100%" height="100%">
    <mx:Image source="{IMAGE_CLASS}"
        filters="{useFilter.selected ? explorer.shaderFilters : []}"/>
    <mx:Canvas width="100%" height="100%" verticalScrollPolicy="auto">
        <shader:ShaderExplorer id="explorer" shader="{loader.shader}"
            height="100%" />
    </mx:Canvas>
</mx:HBox>
</mx:Application>

What Else You Should Know About Flash/Flex

by josefchutka on January 18, 2010

Even for seniors, there is still something to learn. I have chosen those great articles that would help you improve your knowledge about flash and flex and something littlebit behind the scenes.

Flex SDK coding conventions and best practices

This document lays out the coding standards for writing open-source Flex framework components in ActionScript 3. Adhering to these standards makes the source code look consistent, well-organized, and professional…

The Flex Non-Docs (previous site)

For all those secrets in Flex, those that aren’t known, those that aren’t published, or those that are just hard to find for some unknown reason, like percentWidth…

10 Things Every Senior Flash Developer Should Know

John Lindquist interviewed quite a few Flash/Flex developers as potential employees for Roundarch. The hard part for him is knowing exactly what questions to ask to be able to gauge a Flash dev’s skill level…

Nemo 440 – ActionScript 3 disassembler in AIR

Nemo 440 is free ABC code diassembler implemented as AIR-based application. Nemo 440 can read SWF files compiled with Flex 2/Flex 3/Flex 4 and translates ActionScript 3 byte code to more understandable text dump.

Upcoming Conferences Jan/Feb/Mar 10

by Tom Krcha on January 16, 2010


Embed Bitmap text into Flex

by Jozef Chúťka on January 12, 2010

pixelFont

This article helps you embed pixel sharp fonts (without antialias) into your flex apps. As far as I know you wouldn’t be able to do this just by direct embedding local font file into your flex application, but you gonna need Flash CS4 authoring tool. There are two ways you can make Bitmap text work in flex. You can use pure css method or actionscript method. The downside of both methods is, you have to define and export from flash exact font size you are going to use.

1. Embedding pixel fonts using CSS Style

  1. create new .fla file
  2. create Dynamic Text field:
    1. set font (Arial, Regular)
    2. set Anti-alias value to Bitmap text [no anti-alias]
    3. important: when embedding Bitmap text, only selected font size is created, select 12pt
    4. set characters to embed
  3. export .fla into .swf
  4. create flex project and insert the following code:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Style>
    @font-face
    {
        src:url("font.swf");
        fontFamily: "Arial_12pt_st";
        fontAntialiasType: "normal";
    }

    Text
    {
        fontFamily: "Arial_12pt_st";
        fontSize: 12;
    }
</mx:Style>
<mx:Text text="Abc Def"/>
</mx:Application>

Notice font family name Arial_12pt_st, this is how flash exports Bitmap text at specific font size and font style. This pure css method seems to be cleaner and faster to me, it would even be easier to use it with large scale projects just by separating application and style files.

2. Embedding pixel fonts using ActionsScript

Method is described by Grant Skinner in Bitmap Fonts in Flex (via Flash) article:

  1. create new .fla file
  2. create Dynamic Text field:
    1. set font (Arial, Regular)
    2. set Anti-alias value to Bitmap text [no anti-alias]
    3. important: when embedding Bitmap text, only selected font size is created, select 12pt
    4. set characters to embed
  3. add text field into exportable MovieClip (select text field -> F8 -> MovieClip -> Linkage Class “font“)
  4. export .fla into .swf
  5. create flex project and insert the following code:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
<![CDATA[
    [Embed(source="font.swf", symbol="font")]
    private var FontSourceClass:Class;
]]>
</mx:Script>
<mx:Text text="Abc Def"
    fontFamily="Arial_12pt_st" fontSize="12" fontAntiAliasType="normal"/>
</mx:Application>

Notice the step when you add text field into MovieClip and linkage, than in Application you embed directly the symbol!

Lets assume you are working on a bigger project and all-code-in-one-palce is not fine solution. I would suggest to separate logic and style this way:

Application file (notice important import Fonts + var fonts:Fonts definition):

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:local="*">
<mx:Script>
<![CDATA[
    import Fonts;
    private var fonts:Fonts;
]]>
</mx:Script>
<mx:Style source="style.css" />
<mx:Text text="Abc Def"/>
</mx:Application>

Fonts.as working as embed font database:

package
{
    import flash.text.Font;

    public class Fonts extends Object
    {
        [Embed(source="fonts.swf", symbol="Arial_12pt_st")]
        public static const Arial_12pt_st:Class;

        [Embed(source="fonts.swf", symbol="Verdana_11pt_st")]
        public static const Verdana_11pt_st:Class;

        [Embed(source="fonts.swf", symbol="Verdana_12pt_st")]
        public static const Verdana_12pt_st:Class;

        [Embed(source="fonts.swf", symbol="Verdana")]
        public static const Verdana:Class;

        public function Fonts()
        {
            super();
        }

        public static function traceAll():void
        {
            var fontList:Array = Font.enumerateFonts(false);
            for(var i:uint = 0; i < fontList.length; i++)
                trace("font: " + fontList[i].fontName);
        }
    }
}

And style.css file should look like this:

Text
{
   fontFamily: "Verdana_11pt_st";
   fontSize: 11;
   fontAntialiasType: "normal";
}

List of Flash Gaming Engines

by Tom Krcha on January 11, 2010


GeometryMath 3 points in line test

by Jozef Chúťka on January 11, 2010

inLine

GeometryMath.isLine() static function tests 3 Points and returns true if these points are in line. Optional 4th parameter defines if you require point2 to be in the middle between 1 and 3. Function is not based on vector algorithm, but on triangle equation. This simple function was developed in order to optimize number of points necessary to draw some paths. To draw a line, you do not need all the points, you can omit all the middle points that are on the same line and the result looks the same – optimizes performance or storage.

isLine() function is a part of sk.yoz.math. GeometryMath class

public static function isLine(point1:Point, point2:Point,
	point3:Point, orderSensitive:Boolean = true):Boolean
{
	var x1:Number = point1.x - point2.x;
	var x2:Number = point2.x - point3.x;
	var y1:Number = point1.y - point2.y;
	var y2:Number = point2.y - point3.y;

	if(orderSensitive && ((x1 > 0 && x2 < 0) || (x1 < 0 && x2 > 0)
		|| (y1 < 0 && y2 > 0) || (y1 < 0 && y2 > 0)))
		return false;
	else if(!y2)
		return !y1;
	else if(!x2)
		return !x1;
	else
		return x1 / x2 == y1 / y2;
}

Flash Love Letter

by Tom Krcha on January 9, 2010


How to resize an image with ActionScript (update)

by josefchutka on January 8, 2010

imageResizeLanczos

If you need to resize an image on client side, I mean real bitmapData resize (not just showing scaled), feel free to use ImageResizer class. This class takes bitmapData of source image, new width and height and resize method (defined by ResizeMath class), and returns new bitmapData of resized image.

Update: It seems that bitmapData.draw() method uses bilinear algorithm when scaling via Matrix. Bilinear algorithm gives you fine results when downscaling no more than 2 times (400px -> 200px). So, I added bilinearIterative() method into ImageResizer class that creates resized bitmapData by multiple steps achieving much smoother results!

Source of sk.yoz.image.ImageResizer class:

package sk.yoz.image
{
    import flash.display.BitmapData;
    import flash.geom.Matrix;
    import flash.geom.Point;

    import sk.yoz.math.ResizeMath;

    public class ImageResizer extends Object
    {
        public function ImageResizer()
        {
        }

        public static function bilinear(source:BitmapData, width:uint,
            height:uint, method:String, allowEnlarging:Boolean=true):BitmapData
        {
            var scale:Point = ResizeMath.scale(
                new Point(source.width, source.height),
                new Point(width, height), method, allowEnlarging);
            var result:BitmapData = new BitmapData(width, height, true, 0x0);
            var matrix:Matrix = new Matrix();
            matrix.scale(scale.x, scale.y);
            matrix.tx = (width - source.width * scale.x) / 2;
            matrix.ty = (height - source.height * scale.y) / 2;
            result.draw(source, matrix, null, null, null, true);
            return result;
        }

        public static function bilinearIterative(source:BitmapData, width:uint,
            height:uint, method:String, allowEnlarging:Boolean = true,
            iterationMultiplier:Number = 2):BitmapData
        {
            var w:uint = source.width;
            var h:uint = source.height;
            var result:BitmapData;
            while(!result || w != width || h != height)
            {
                w = source.width > width
                    ? Math.max(w / iterationMultiplier, width)
                    : Math.min(w * iterationMultiplier, width);
                h = source.height > height
                    ? Math.max(h / iterationMultiplier, height)
                    : Math.min(h * iterationMultiplier, height);
                result = bilinear(result || source, w, h, method,
                    allowEnlarging);
            }
            return result;
        }
    }
}

Usage is pretty simple. You also gonna need sk.yoz.math.ResizeMath class.

var result:BitmapData = ImageResizer.bilinearIterative(sourceBitmapData, 100, 100,
    ResizeMath.METHOD_PAN_AND_SCAN);

Alternatives & Benchmarking

There are also another possible methods to resize/resample images. Read more about bilinear resampling and bicubic resampling methods using pixel bender. I benchamrked all methods and here are the results:

source    | result  | ImageResizer | ImageResizer        | Bilinear PB | Bicubic PB
          |         | bilinear()   | bilinearIterative() |             |
4x4       | 200x200 |         0 ms |                3 ms |       16 ms |      31 ms
946x946   | 200x200 |         0 ms |                7 ms |       16 ms |      47 ms
1200x1200 | 200x200 |         0 ms |                9 ms |       32 ms |      47 ms

Comparing ImageResizer.bilinear() method with bilinear and bicubic pixel bender resizing. Even though, all methods are pretty fast, the results are far from beeing as good as Lanczos algorithm results (e.g. used in IrfanView):

resizingMethodBenchmark

Comparing ImageResizer.bilinear(), ImageResizer.bilinearIterative() and Lanczos algorithm:

imageResizeLanczos

If you are looking for transformation between BitmapData, Bitmap, ByteArray, read this article.

Flex Size Report

by Jozef Chúťka on January 5, 2010

linkreport

This post is for everyone who is looking for clear and readeable size report from flex.

1. Define “-link-report” flag for compiler
2. Export / publish your project
3. Create static .xsl file
4. Link .xsl file into generated .xml size report

1. Define “-link-report” flag for compiler

In order to get raw .xml size report from flex project you have to define “-link-report” flag. In flex project go to “File / Properties / Flex Compiler / Additional compiler arguments” text field and append neccesary “-link-report” flag. Your argument query now should look someting like this

-locale en_US -link-report=report.xml -optimize

2. Export / publish your project

Now you can release export or debug export your project to get your report. report.xml should appear in bin-debug directory.

3. Create static .xsl file

In same directory, create empty file named “flexSizeReport.xsl”. Put following content into this file and save:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
	xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
	xmlns:msxsl="urn:schemas-microsoft-com:xslt"
	exclude-result-prefixes="msxsl">
	<xsl:output method="html" indent="yes"/>
	<xsl:template match="@* | node()">
		<xsl:copy>
			<xsl:apply-templates select="@* | node()"/>
		</xsl:copy>
	</xsl:template>

	<xsl:template match="/">
		<html>
			<body>
				<h1>ActionScript Link Report</h1>

				<h2>Custom Classes</h2>
				<table border="1">
					<tr>
						<th align="left">Name</th>
						<th align="left">Size (B)</th>
						<th align="left">Optimized Size (B)</th>
					</tr>
					<xsl:for-each select="report/scripts/script">
						<xsl:sort select="@optimizedsize" data-type="number" order="descending"/>
						<xsl:if test="not(contains(@name, 'frameworks'))">
							<tr>
								<td><xsl:value-of select="@name"/></td>
								<td align="right"><xsl:value-of select="@size"/></td>
								<td align="right"><xsl:value-of select="@optimizedsize"/></td>
							</tr>
						</xsl:if>
					</xsl:for-each>
				</table>

				<h2>Framework Classes</h2>
				<table border="1">
					<tr>
						<th align="left">Name</th>
						<th align="left">Size (B)</th>
						<th align="left">Optimized Size (B)</th>
					</tr>
					<xsl:for-each select="report/scripts/script">
						<xsl:sort select="@optimizedsize" data-type="number" order="descending"/>
						<xsl:if test="contains(@name, 'frameworks')">
							<tr>
								<td><xsl:value-of select="@name"/></td>
								<td align="right"><xsl:value-of select="@size"/></td>
								<td align="right"><xsl:value-of select="@optimizedsize"/></td>
							</tr>
						</xsl:if>
					</xsl:for-each>
				</table>
			</body>
		</html>
	</xsl:template>
</xsl:stylesheet>

Now open report.xml file. It should contain some content like:

<report>
  <scripts>
     <script name="Drive:\Path\MyProject\src\accessibility\ComboBoxItemAccessibility.as" mod="1217893038671" size="1593" optimizedsize="875">
       <def id="accessibility:ComboBoxItemAccessibility" />
       <pre id="accessibility:ButtonAccessibility" />
       <dep id="flash.events:MouseEvent" />
       <dep id="accessibility:Constants" />
       <dep id="MenuItem" />
       <dep id="AS3" />
    </script>
  </scripts>
</report>

4. Link .xsl file into generated .xml size report

Prepend this content with two lines:

<?xml version="1.0" encoding="ISO-8859-1"?>
<?xml-stylesheet type="text/xsl" href="flexSizeReport.xsl"?>
<report>
  <scripts>...

Save report.xml and open it in some modern browser, that can handle xslt (Chrome or Firefox work just fine). Now you see your .xml report parsed by .xsl file into two separated tables (custom classes, framework classes) ordered by optimized size descending.

Read more about “-link-report” flag here: