Compress Transparent PNGs with Flash

Do you use transparent PNGs in Flash, Flex or AIR? Want to cut your application file size in half?

Problem:
Embedding transparent PNGs dramatically increases the size of your Flash, Flex and AIR applications. Photoshop and Fireworks dot not have any compression available for transparent PNGs (that I am aware of).

Solution:
Import your PNGs into Flash, give them a linkage id, and export the file as a SWF. Make sure to set your compression level in the publish settings (default is 80%). Using this method, I was able to cut the file size in half for a number of applications.

[Embed(source="/assets/images/images.swf#myImage")]
private var MyImage:Class;
private var myImage:Sprite = new MyImage() as Sprite;

rather than…

// Don't use this one 😉
[Embed(source="/assets/images/myImage.png")]
private var MyImage:Class;
private var myImage:BitmapAsset = new MyImage() as BitmapAsset;

It is also much easier to manage your external assets from a SWF. Designers can set up a nice slice sheet using the stage in Flash. Much easier than managing tons of small PNGs in folders everywhere.

Image Manager

In addition to using the SWF it can also be very useful to set up an ImageManager to serve out images to your application. This prevents having to change code in multiple places to update the same image reference.

Image Manager Class:

package com.cb.utils
{
	import flash.display.Sprite;
	
	public class ImageManager
	{
		public static const MY_IMAGE_ID:String = "my_image_id";
		
		[Embed(source="/assets/images/images.swf#myImage")]
		public static var MyImage:Class;

		public static function getMovie(id:String):Sprite
		{
			var result:Sprite;
			switch(id){
				case MY_IMAGE_ID:
				default:
					result = new MyImage();
				break;
			}
			return result;
		}
	}
}

To access an image from the manager class just make this call:

var myImage:BitmapAsset = ImageManager.getImage(ImageManager.MY_IMAGE_ID);

Importing PNGs into Flash

And for those of you that need a refresher on importing PNGs into Flash and setting up linkage IDs:

File->Import->Import to Stage...

File->Import->Import to Stage...

Right click on the image. Select ‘Convert to Symbol’…

Step 2

Finally…

Step 3

Enjoy!

Disclaimer: It may be obvious but you must be using transparent PNGs in your app to see a decrease in file size 😉 Most of the apps I work on have lots of transparent PNGs!

Paint Algorithm for ActionScript

I bet you can’t fill the canvas with paint using the bad painting algorithm below (keep an eye on the frame rate counter). Now check out your paint skills using the good paint algorithm. What’s the difference? Check out the answer after the examples.

Bad Paint:

Alternate Page.

Get Adobe Flash player

(pretend your cursor is a paint brush, click and drag around)

Good Paint:

Alternate Page.

Get Adobe Flash player

(notice the fps counter in the top left)

I would like to start by pointing out that every Flash ‘paint’ example I found online uses lineTo. That’s fine if your just playing around but what if you want to use a more advanced painting algorithm? It looks MUCH better. Drawing a circle using the ‘lineTo’ method has noticeable rigid corners. Using a good painting algorithm allows for much smoother edges and more control: Safe from the Losing Fight – How to implement a basic bitmap brush.

Well my first implementation of a simple painting algorithm turned out really bad (bad paint above). Why? Two reasons. I was creating a new sprite and BitmapAsset for every circle added (thousands of circles eating up memory). In addition to that I was using my own painting algorithm which was just barely a step up from lineTo.

The fix? First I converted a nice painting algorithm from C (CProgramming.com – Line Drawing Algorithm) into ActionScript. Second I took another look at the circle creation method.

My bad code:

override public function paint(point:Point):void
{

	_wrapper = new Sprite();
	_stroke = new Sprite();
	_stroke.addChild(_particle);
	_stroke.width = _stroke.height = 20;
	
	_wrapper.addChild(_stroke);
	
	while(Math.abs(_point.y - point.y) > _travel || Math.abs(_point.x - point.x) > _travel){
		var bitmapData:BitmapData = new BitmapData(_wrapper.width, _wrapper.height, true, 0x00000000);
		bitmapData.draw(_wrapper);
		
		// Creating a new BitmapAsset every time a particle is added: BAD!!!
		var bitmap:BitmapAsset = new BitmapAsset(bitmapData);
		bitmap.x = _point.x - (bitmap.width / 2);
		bitmap.y = _point.y - (bitmap.height / 2);
		this.addChild(bitmap);
		
		// Average painting algorithm
		if(_point.x < point.x){
			_point.x = _point.x + _travel;
		}else if(_point.x > point.x){
			_point.x = _point.x - _travel;
		}else{
			_point.x = point.x;
		}
		
		if(_point.y < point.y){
			_point.y = _point.y + _travel;
		}else if(_point.y > point.y){
			_point.y = _point.y - _travel;
		}else{
			_point.y = point.y;
		}
	}
}	

My good code:

public function PaintPngStroke(color:uint, start:Point)
{
	super(color, start);
	
	_stroke = new Sprite();
	_stroke.addChild(_particle);
	_stroke.width = _stroke.height = 20;

	var colorTransform:ColorTransform = _stroke.transform.colorTransform;
	colorTransform.color = _color;
	_stroke.transform.colorTransform = colorTransform;
	
	// Only create ONE particle
	_wrapper = new Sprite();
	_wrapper.addChild(_stroke);

	_bitmapData = new BitmapData(SimplePaint.CANVAS_WIDTH, SimplePaint.CANVAS_HEIGHT, true, 0x00FF3300);
	
	// Only create ONE BitmapAsset
	_bitmap = new BitmapAsset(_bitmapData);
	this.addChild(_bitmap);
}

override protected function wp(startx:int, starty:int):void
{
	// Move particle
	_wrapper.x = startx - (_wrapper.width / 2);
	_wrapper.y = starty - (_wrapper.height / 2);
	
	var matrix:Matrix = new Matrix();
	matrix.tx = _wrapper.x;
	matrix.ty = _wrapper.y;
	
	// GOOD paint: reuse the BitmapData
	_bitmapData.draw(_wrapper, matrix);
}

The good code runs much faster, is more efficient, looks better, and so on. Check out the source files to get a copy of the GOOD paint algorithm.

Enjoy!

Resources:
CProgramming.com – Line Drawing Algorithm
Safe from the Losing Fight – How to implement a basic bitmap brush

Image Distortion and Tapering in ActionScript

Distort and taper images in BOTH ActionScript 2 and 3 without a big 3D engine. Is it possible? YES!

**Update
(12/15/09): Alex Uhlmann made this class even better! Distortion Effects (AS3 only)

(06/24/09): After doing some more research I found that Ruben Swieringa re-wrote this same class into AS3 exactly two years ago. I wish I would have found his post earlier today :)
****

Problem:
ActionScript does not have native support image distorting and tapering (Flash Player 9 and below). After a lot of digging I stumbled upon an AS2 class that distorts images and it’s less than 200 lines of code.

Solution:
You can use the AS2 class ‘as is’ from FlashSandy. Or, if you’d prefer to code in a AS3, I’ve ported it over and provided an example:

Alternate Page.

Get Adobe Flash player

Move the red squares by clicking, dragging, and releasing them on the stage. Source View

What’s the total filesize?
Well, lets see. The demo above is 53KB for the swf- 41.5 for the embedded image – 3KB for TweenLite = 8.5KB :)

How does it work?
Embed an image:

[Embed(source="/assets/glacier.jpg")]
private var Background: Class;
private var _background:BitmapAsset = new Background();		

Set up your DistortImage and DistortVO:

_image = new Sprite();
_image.addChild(_background);
this.addChild(_image);

_d = new DistortImage( _image, 2, 2 );
_image.removeChild(_background);

_dVO = new DistortVO();
_dVO.data = _image;

Use your favorite tweening engine to do the rest:

private function clickHandler(event:MouseEvent = null):void
{
	TweenLite.to(_dVO, 0.5, {x0:_tl.x, y0:_tl.y, x1:_tr.x, y1:_tr.y, x2:_bl.x, y2:_bl.y, x3:_br.x, y3:_br.y, onUpdate:applyMatrix, onUpdateParams:[_dVO], ease:Cubic.easeOut});			
}

private function applyMatrix($dVO:DistortVO):void 
{
   _d.setTransform($dVO.x0, $dVO.y0, $dVO.x1, $dVO.y1, $dVO.x2, $dVO.y2 ,$dVO.x3, $dVO.y3);	
}

The following is ActionScript 2 related
I am re-posting the AS2 code for redundancy purposes. The original code (DistordImage) is sitting on a broken link and I feel lucky to have found the sample code on FlashSandy. The above example can be done in AS2 which is really cool. Thank you to all of the authors of the DistortImage AS2 class!

/*
****************************
* From a first idea and first implementation of Andre Michelle www.andre-michelle.com
* @version 2.0
* @author Thomas Pfeiffer - kiroukou - http://www.thomas-pfeiffer.info
* @author Richard Lester - RichL
* @author Didier Brun - foxy - http://www.foxaweb.com
* @website: http://sandy.media-box.net
* @description: Tesselate a movieclip into several triangles to allow free transform distorsion.
* *************************
* Licensed under the CREATIVE COMMONS Attribution-NonCommercial-ShareAlike 2.0
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://creativecommons.org/licenses/by-nc-sa/2.0/fr/deed.en_GB
***************************
* DistortImage class
* Availability : Flash Player 8.
**********************************************
*/
import flash.geom.Matrix;
import flash.display.BitmapData;
class DistortImage
{
	// -- texture to distord
	public var texture:BitmapData;
	/*
	* Constructor
	* @param mc MovieClip : the movieClip containing the distorded picture
	* @param symbolId String : th link name of the picture in the library
	* @param vseg Number : the vertical precision
	* @param hseg Number : the horizontal precision
	* @throws: An error in the second constructor parameter isn't a BitmapData or a MovieClip
	*/
	public function DistortImage( mc: MovieClip, ptexture, vseg: Number, hseg: Number )
	{
		_mc = mc;
		if( ptexture instanceof BitmapData )
		{
			texture = ptexture;
		}
		else if( ptexture instanceof MovieClip )
		{
			texture = new BitmapData( ptexture._width, ptexture._height );
			texture.draw( ptexture );
		}
		else
		{
			throw new Error('Second argument in DistortImage class must be a BitmapData object or a Movieclip');
		}
		_vseg = vseg || 0;
		_hseg = hseg || 0;
		// --
		_w = texture.width ;
		_h = texture.height;
		// --
		_aMcs   = new Array();
		_p   = new Array();
		_tri	= new Array();
		// --
		__init();
	}
	/**
	* setTransform
	*
	* @param x0 Number the horizontal coordinate of the first point
	* @param y0 Number the vertical coordinate of the first point
	* @param x1 Number the horizontal coordinate of the second point
	* @param y1 Number the vertical coordinate of the second point
	* @param x2 Number the horizontal coordinate of the third point
	* @param y2 Number the vertical coordinate of the third point
	* @param x3 Number the horizontal coordinate of the fourth point
	* @param y3 Number the vertical coordinate of the fourth point
	*
	* @description : Distord the bitmap to ajust it to those points.
	*/
	function setTransform( x0:Number , y0:Number , x1:Number , y1:Number , x2:Number , y2:Number , x3:Number , y3:Number ): Void
	{
		var w:Number = _w;
		var h:Number = _h;
		var dx30:Number = x3 - x0;
		var dy30:Number = y3 - y0;
		var dx21:Number = x2 - x1;
		var dy21:Number = y2 - y1;
		var l:Number = _p.length;
		while( --l> -1 )
		{
			var point:Object = _p[ l ];
			var gx = ( point.x - _xMin ) / w;
			var gy = ( point.y - _yMin ) / h;
			var bx = x0 + gy * ( dx30 );
			var by = y0 + gy * ( dy30 );
			point.sx = bx + gx * ( ( x1 + gy * ( dx21 ) ) - bx );
			point.sy = by + gx * ( ( y1 + gy * ( dy21 ) ) - by );
		}
		__render();
	}
	/////////////////////////
	///  PRIVATE METHODS  ///
	/////////////////////////
	private function __init( Void ): Void
	{
		_p = new Array();
		_tri = new Array();
		var ix:Number, iy:Number;
		var w2: Number = _w / 2;
		var h2: Number = _h / 2;
		_xMin = _yMin = 0;
		_xMax = _w; _yMax = _h;
		_hsLen = _w / ( _hseg + 1 );
		_vsLen = _h / ( _vseg + 1 );
		var x:Number, y:Number;
		var p0:Object, p1:Object, p2:Object;
		// -- we create the points
		for ( ix = 0 ; ix <_hseg + 2 ; ix++ )
		{
			for ( iy = 0 ; iy <_vseg + 2 ; iy++ )
			{
				x = ix * _hsLen;
				y = iy * _vsLen;
				_p.push( { x: x, y: y, sx: x, sy: y } );
			}
		}
		// -- we create the triangles
		for ( ix = 0 ; ix <_vseg + 1 ; ix++ )
		{
			for ( iy = 0 ; iy <_hseg + 1 ; iy++ )
			{
				p0 = _p[ iy + ix * ( _hseg + 2 ) ];
				p1 = _p[ iy + ix * ( _hseg + 2 ) + 1 ];
				p2 = _p[ iy + ( ix + 1 ) * ( _hseg + 2 ) ];
				__addTriangle( p0, p1, p2 );
				// --
				p0 = _p[ iy + ( ix + 1 ) * ( _vseg + 2 ) + 1 ];
				p1 = _p[ iy + ( ix + 1 ) * ( _vseg + 2 ) ];
				p2 = _p[ iy + ix * ( _vseg + 2 ) + 1 ];
				__addTriangle( p0, p1, p2 );
			}
		}
		__render();
	}
	private function __addTriangle( p0:Object, p1:Object, p2:Object ):Void
	{
		var u0:Number, v0:Number, u1:Number, v1:Number, u2:Number, v2:Number;
		var tMat:Object = {};
		// --
		u0 = p0.x; v0 = p0.y;
		u1 = p1.x; v1 = p1.y;
		u2 = p2.x; v2 = p2.y;
		tMat.tx = -v0*(_w / (v1 - v0));
		tMat.ty = -u0*(_h / (u2 - u0));
		tMat.a = tMat.d = 0;
		tMat.b = _h / (u2 - u0);
		tMat.c = _w / (v1 - v0);
		// --
		_tri.push( [p0, p1, p2, tMat] );
	}
	private function __render( Void ): Void
	{
		var vertices: Array;
		var p0, p1, p2:Object;
		var x0:Number, y0:Number;
		var ih:Number = 1/_h, iw:Number = 1/_w;
		var c:MovieClip = _mc; c.clear();
		var a:Array;
		var sM = {};
		var tM = {};
		//--
		var l:Number = _tri.length;
		while( --l> -1 )
		{
			a   = _tri[ l ];
			p0  = a[0];
			p1  = a[1];
			p2  = a[2];
			tM = a[3];
			// --
			sM.a = ( p1.sx - ( x0 = p0.sx ) ) * iw;
			sM.b = ( p1.sy - ( y0 = p0.sy ) ) * iw;
			sM.c = ( p2.sx - x0 ) * ih;
			sM.d = ( p2.sy - y0 ) * ih;
			sM.tx = x0;
			sM.ty = y0;
			// --
			sM = __concat( sM, tM );
			c.beginBitmapFill( texture, sM, false, false );
			c.moveTo( x0, y0 );
			c.lineTo( p1.sx, p1.sy );
			c.lineTo( p2.sx, p2.sy );
			c.endFill();
		}
	}
	private function __concat( m1, m2 ):Object
	{   //Relies on the original triangles being right angled with p0 being the right angle.
		//Therefore a = d = zero (before and after invert)
		var mat = {};
		mat.a  = m1.c * m2.b;
		mat.b  = m1.d * m2.b;
		mat.c  = m1.a * m2.c;
		mat.d  = m1.b * m2.c;
		mat.tx = m1.a * m2.tx + m1.c * m2.ty + m1.tx;
		mat.ty = m1.b * m2.tx + m1.d * m2.ty + m1.ty;
		return mat;
	}
	/////////////////////////
	/// PRIVATE PROPERTIES //
	/////////////////////////
	private var _mc:MovieClip;
	private var _w:Number;
	private var _h:Number;
	private var _xMin:Number, _xMax:Number, _yMin:Number, _yMax:Number;
	// -- picture segmentation properties
	private var _hseg:Number;
	private var _vseg:Number;
	private var _hsLen:Number;
	private var _vsLen:Number;
	// -- arrays of differents datas types
	private var _p:Array;
	private var _tri:Array;
	private var _aMcs:Array;
}

Resources:
Understanding the Transformation Matrix in Flash 8
FlashSandy
Ruben Swieringa – DistortImage

Logic != Math: Transparency Example

If you have a 100% alpha red circle how many 10% alpha blue circles does it take to completely cover the red circle? The answer is 10, right? No, wait maybe 20? How about 100? Maybe 1,000? Believe it or not, an infinite number of blue circles are required it’s actually impossible. Give it a try in Flash or Photoshop and you will get the following no matter how many times you click:

Math

Why?
To busy right now to look for the mathematical explanation to this (but I know there is one!). Adding alpha transparent objects will eventually hit a limit somewhere around 97% rather than reaching one hundred percent. Maybe I’ll take a look later tonight for some additional resources. Anybody know off the top of their head?

Is there any way to get around this?
Add a new layer and stamp a bunch more blue circles. One layer will hit a limit around 97% opacity but two layers each at 97% will appear pretty darn close to 100%, which is expected.