SoTexty AS3 Text Effects Framework

February 27th, 2009

The core of my FITC presentation revolved around a new as3 text effects framework that I've been working on, which I'm currently calling "SoTexty".

The basic premise of the framework is that it sets up the various "stages" or "states" of a text effect:

  • Intro : how the effect appears
  • Loop : the "main" effect, this can loop between 1 and infinity times
  • Exit : how the effect goes away
  • MouseOver : from the loop effect, an optional behaviour reacting to user interactions
  • MouseOut : either a "reverse" of the mouseOver, or a different effect
  • MouseClick : either a link to the "exit", or an alternate effect

Some of these "states" or "stages" are optional. The framework expects a textfield, along with a list of parametrized effects which are assigned to the various "stages", and then produces the complete effect.

The Framework also comes with a "catalogue" of effect types, which for the moment are:

  • BlockEffect : an effect which animates the entire text as one
  • SplitEffect : animates the characters in the text individually
  • ParticleEffect : animates "particles" which are assigned to populate the "shape" of individual characters
  • VectorEffect : animates the vector shapes (points) which construct the individual characters

A basic example of a SoTexty effect can be produced with code that looks something like:

Actionscript:
  1. override protected function init(tf:TextField):void{
  2.    
  3.     this._intro_effect=new TextEffect(tf);
  4.     var f:Fade=new Fade(2,0,1);
  5.     this._intro_effect.addEffect(f);
  6.    
  7.     this._loop_effect=new TextEffect(tf);
  8.     var rf:RandomFade=new RandomFade(0,1,1,3);
  9.     this._loop_effect.addEffect(rf);
  10.  
  11.     this._exit_effect=new TextEffect(tf);
  12.     f=new Fade(3,1,0);
  13.     f.easing=Elastic.easeOut;
  14.     this._exit_effect.addEffect(f);
  15.  
  16. }

All SoTexty Effects extend one of the "catalogue effect" types, and populates instances of "effect states" with effects like Fade, Move, Scale etc.

The framework uses Grant Skinners GTween for animations. I intend to put it up on google code once I've sorted out some kinks. Hopefully in the coming few months.

The main idea is to take out the "donkey work" of generating common effects for loaders, between game levels, intros etc. Leaving the programmer to focus on the interesting bits of the application (game play etc.)

Click the images below for a few quick samples:


Block Effect


Split Effect


Particle Effect

**Apparently throws errors in macs**

Vector Effect

**Apparently this one also, throws errors in macs**

3D Text Effect

Stay tuned, plenty more to come :)

FITC Amsterdam 2009

February 27th, 2009

In one word, FITC was hectic for me. I spent the week before frantically preparing my presentation. Having ran into some technical issues, I was behind schedule. So much so that I spent most of sunday and monday in my hotel room cranking out code, slides and examples.

Here's me rocking the Artemis hotel room:

I finished my last example Tuesday morning around 9, followed by a thrilling cab ride to FITC. Steven told the cab that we were in a hurry, and the driver certainly delivered : He ran 4 red lights, drove on bus and tram lanes, honked a lot, and seemed to just LOVE it all the way! He deserved a FAT tip :D Alas, I was on time for my presentation. The "Tekenzaal" filled up slowly with hungover attendees.

I was worried that I wouldn't have enough time to go through my slides, so, wired on coffee I managed to whiz through my slides in just 30 mins... Questions anybody? ;) I have to say that I'm a bit bummed about it, having put that much effort, only to rush through whilst forgetting half of what I was meaning to say. In the end, I got some positive feedback, and it seems there's some genuine interest in my framework.

I won't be posting my slides just yet, as it seems some of my examples fail on macs :( Instead, I'm gonna be posting bits and pieces with code, in easily digestible portions.

After my preso I managed to catch a couple of really inspirational ones, namely Keith Peters "Art From Code" and Mario Klingemanns "The Tinkerer's Box". Got me all Juiced up for actionscript adventures :)

The evening was followed by an Adobe dinner... Om Nom Nom... Exhausted from the lack of sleep, we stopped by the party for a quick beer then retreated to our hotel.

All in all was a great experience! Thanks again Shawn!

Get first non transparent pixel in a bitmap Part III

February 18th, 2009

I know, I know... such a riveting topic, just can't get enough! :D

So I felt quite embarrassed when Mario commented on my previous entry, stating that in fact I had misunderstood his method. Either just me being an idiot, or me being an idiot after a few drinks at the Max party.

He then proceeded to blog about the real deal. Indeed this requires less code, looks cleaner and more 1337 in every way! I then included the new method into my test run.

Here is Marios code:

Actionscript:
  1. public static function getFirstNonTransparentPixel( bmd:BitmapData ) :P oint{
  2.     var r1:Rectangle = bmd.getColorBoundsRect( 0xff000000, 0, false );
  3.     if ( r1.width> 0 ){
  4.         var temp:BitmapData = new BitmapData( r1.width, 1, true, 0 );
  5.         temp.copyPixels( bmd, r1, new Point());
  6.         var r2:Rectangle = temp.getColorBoundsRect( 0xff000000, 0, false );
  7.         return r1.topLeft.add( r2.topLeft );
  8.     }
  9.     return null;
  10. }

Here (once again) be the results:

Test with image w:100,h:100
testFirstBitmapLooping() first:(x=30, y=30)
testFirstBitmapLooping() test took : 3 milliseconds
testFirstBitmapFloodFill() first:(x=30, y=30)
testFirstBitmapFloodFill() test took : 4 milliseconds
testFirstBitmapHitTest() first:(x=30, y=30)
testFirstBitmapHitTest() test took : 1 milliseconds
testFirstBitmapMario() first:(x=30, y=30)
testFirstBitmapMario() test took : 1 milliseconds

Test with image w:500,h:500
testFirstBitmapLooping() first:(x=150, y=150)
testFirstBitmapLooping() test took : 30 milliseconds
testFirstBitmapFloodFill() first:(x=150, y=150)
testFirstBitmapFloodFill() test took : 17 milliseconds
testFirstBitmapHitTest() first:(x=150, y=150)
testFirstBitmapHitTest() test took : 1 milliseconds
testFirstBitmapMario() first:(x=150, y=150)
testFirstBitmapMario() test took : 3 milliseconds

Test with image w:1000,h:1000
testFirstBitmapLooping() first:(x=300, y=300)
testFirstBitmapLooping() test took : 62 milliseconds
testFirstBitmapFloodFill() first:(x=300, y=300)
testFirstBitmapFloodFill() test took : 41 milliseconds
testFirstBitmapHitTest() first:(x=300, y=300)
testFirstBitmapHitTest() test took : 7 milliseconds
testFirstBitmapMario() first:(x=300, y=300)
testFirstBitmapMario() test took : 10 milliseconds

Test with image w:2000,h:2000
testFirstBitmapLooping() first:(x=600, y=600)
testFirstBitmapLooping() test took : 306 milliseconds
testFirstBitmapFloodFill() first:(x=600, y=600)
testFirstBitmapFloodFill() test took : 96 milliseconds
testFirstBitmapHitTest() first:(x=600, y=600)
testFirstBitmapHitTest() test took : 32 milliseconds
testFirstBitmapMario() first:(x=600, y=600)
testFirstBitmapMario() test took : 55 milliseconds

...so it turns out this wasn't an entirely futile exercise after all! I felt like a schmuck having misunderstood Mario, but I'm quite chuffed that the HitTest method is faster (even if by a practically irrelevant amount).

To be sure, I ran the two competitors 20 times on a bitmap of 2000 by 2000 and took the averages:

Mario Average: :36.7
HitTest Average: :21.2

In any case, I got to better terms with some of the BitmapData methods, which is something we all should aspire to anyway :) Thanks again Mario!

********** FINAL UPDATE (hopefully)************

Mario had a go at the HitTest version, and modified it slightly for even better performance:

Actionscript:
  1. public static function getFirstNonTransparentPixel( bmd:BitmapData ) :P oint{
  2.     var hit_rect:Rectangle=new Rectangle(0,0,bmd.width,1);
  3.     var p:Point = new Point();
  4.     for( hit_rect.y = 0; hit_rect.y <bmd.height; hit_rect.y++ ){
  5.         if( bmd.hitTest( p, 0x01, hit_rect) ){
  6.         var hit_bmd:BitmapData=new BitmapData( bmd.width, 1, true, 0 );
  7.         hit_bmd.copyPixels( bmd, hit_rect, p );
  8.         return hit_rect.topLeft.add( hit_bmd.getColorBoundsRect(0xFF000000, 0, false).topLeft );
  9.         }
  10.     }
  11.     return null;
  12. }

Mind you, I am testing this in debug player... I should use a release version with the release player, but for the sake of consistency with my previous posts I'll stick to my old "test suite".

Results:

HitTest Averages: :16
Mario Averages: :35.285714285714285
Hybrid Averages: :14.142857142857142

Now, finally, back to the code where I actually use this algo ;)

Get first non transparent pixel in a bitmap update

February 10th, 2009

On the way home I realized I had made a mistake in my previous blog post. In my hitTest() version, for some reason I had used a Rectangle which was the full size of the target bitmap. Like the FloodFill version, this "hit test" rectangle only needs to be one pixel in height. This means a lot less checking.

Behold the updated version:

Actionscript:
  1. public static function getFirstNonTransparentPixelHitTest(bmd:flash.display.BitmapData,test_fill_color:uint=0xFFFFFF00) :P oint{
  2.     var hit_rect:Rectangle=new Rectangle(0,0,bmd.width,1);
  3.     for(var i:uint=0;i<bmd.height;i++){
  4.         if(bmd.hitTest(new Point(),0x01,hit_rect)){
  5.             var hit_bmd:BitmapData=new BitmapData(bmd.width,1,true,0);
  6.             var m:Matrix=new Matrix(1,0,0,1,0,-i);
  7.             hit_bmd.draw(bmd,m);
  8.             hit_bmd.floodFill(0,0,test_fill_color);//use yellow, assume that the image tested is monochrome... (black and transparent)
  9.             var bounds:Rectangle=hit_bmd.getColorBoundsRect(0xFFFFFFFF,test_fill_color);
  10.             return new Point(bounds.width,i);
  11.         }
  12.         hit_rect.y++;
  13.     }
  14.     return null;
  15. }

Now behold the new results:

Test with image w:100,h:100
testFirstBitmapHitTest() first:(x=30, y=30)
testFirstBitmapHitTest() test took : 2 milliseconds
testFirstBitmapLooping() first:(x=30, y=30)
testFirstBitmapLooping() test took : 1 milliseconds
testFirstBitmapFloodFill() first:(x=30, y=30)
testFirstBitmapFloodFill() test took : 3 milliseconds

Test with image w:500,h:500
testFirstBitmapHitTest() first:(x=150, y=150)
testFirstBitmapHitTest() test took : 8 milliseconds
testFirstBitmapLooping() first:(x=150, y=150)
testFirstBitmapLooping() test took : 32 milliseconds
testFirstBitmapFloodFill() first:(x=150, y=150)
testFirstBitmapFloodFill() test took : 20 milliseconds

Test with image w:1000,h:1000
testFirstBitmapHitTest() first:(x=300, y=300)
testFirstBitmapHitTest() test took : 13 milliseconds
testFirstBitmapLooping() first:(x=300, y=300)
testFirstBitmapLooping() test took : 117 milliseconds
testFirstBitmapFloodFill() first:(x=300, y=300)
testFirstBitmapFloodFill() test took : 42 milliseconds

Test with image w:2000,h:2000
testFirstBitmapHitTest() first:(x=600, y=600)
testFirstBitmapHitTest() test took : 23 milliseconds
testFirstBitmapLooping() first:(x=600, y=600)
testFirstBitmapLooping() test took : 282 milliseconds
testFirstBitmapFloodFill() first:(x=600, y=600)
testFirstBitmapFloodFill() test took : 102 milliseconds

So the hitTest approach wins after all :)

***EDIT :

AND CLICK HERE TO READ UPDATE!

***

Get first non transparent pixel in a bitmap, 3 approaches and benchmarks

February 10th, 2009

I was (once again) very inspired by Mario Klingemanns presentation at the Max in Milan. He showed an example of finding the "perimeter of non transparent pixels" in a bitmap. Not long ago I did the same thing while trying to generate 3d text . He briefly mentioned "you just find the first non transparent pixel, then..." which left me wondering.

I had taken the "brute force approach" of just looping through the pixels, doing a if(bmd.getPixel32(ix,iy)>0x00000000) approach, which (unsurprisingly) isn't very fast. So, at the afterparty, I decided to harass Herr Klingemann. He gladly explained that you 'just create a one pixel high BitmapData, the width of your source BitmapData, iterate from top to bottom draw()ing this "scan line", using floodFill() and getColorBoundsRect() each time to compare if the bounds rect was the width of the source bitmap'.

This sounded incredibly slow. In preparation for my FITC presentation, i'm now returning to the 3d text code, and I just had to test this out. I had this "Brilliant" idea to use BitmapData.hitTest() instead... surely this was faster?! I would create a Rectangle, position it above the source BitmapData, then iterate all the way down, merrily hitTesting() all the way.

Here are my findFirstTransparentPixel() mutations:

Actionscript:
  1. public static function getFirstNonTransparentPixelLooping(bmd:flash.display.BitmapData) :P oint{
  2.     var ix:uint,iy:uint,bmd_height:uint=bmd.height,bmd_width:uint=bmd.width;
  3.     for (iy=0;iy<bmd_height;iy++){
  4.         for(ix=0;ix<bmd_width;ix++){
  5.             if(bmd.getPixel32(ix,iy)>0x00000000){
  6.                 return new Point(ix,iy);
  7.             }
  8.         }
  9.     }
  10.     return null;
  11. }
  12.  
  13. public static function getFirstNonTransparentPixelFloodFill(bmd:flash.display.BitmapData,test_fill_color:uint=0xFFFFFF00) :P oint{
  14.     //var hit_bmd:BitmapData=new BitmapData(bmd.width,1,true,0);
  15.     var hit_bmd:BitmapData;
  16.     var m:Matrix=new Matrix();
  17.     for(var i:int=0;i<bmd.height;i++){
  18.         m.ty=-i;
  19.         hit_bmd=new BitmapData(bmd.width,1,true,0);
  20.         hit_bmd.draw(bmd,m);
  21.         hit_bmd.floodFill(0,0,test_fill_color);//use yellow, assume that the image tested is monochrome... (black and transparent)
  22.         var bounds:Rectangle=hit_bmd.getColorBoundsRect(0xFFFFFFFF,test_fill_color);
  23.         if(bounds.width!=bmd.width){
  24.             return new Point(i,bounds.width);
  25.         }
  26.         //hit_bmd.floodFill(0,0,0);
  27.         hit_bmd.dispose();
  28.     }
  29.     return null;
  30. }
  31.  
  32. public static function getFirstNonTransparentPixelHitTest(bmd:flash.display.BitmapData,test_fill_color:uint=0xFFFFFF00) :P oint{
  33.     var hit_rect:Rectangle=new Rectangle(0,-bmd.height+1,bmd.width,bmd.height);   
  34.     for(var i:uint=1;i<bmd.height;i++){
  35.         if(bmd.hitTest(new Point(),0x01,hit_rect)){
  36.             var hit_bmd:BitmapData=new BitmapData(bmd.width,1,true,0);
  37.             var m:Matrix=new Matrix(1,0,0,1,0,-(i-1));
  38.             hit_bmd.draw(bmd,m);
  39.             hit_bmd.floodFill(0,0,test_fill_color);
  40.             var bounds:Rectangle=hit_bmd.getColorBoundsRect(0xFFFFFFFF,test_fill_color);
  41.             return new Point(bounds.width,i-1);
  42.         }
  43.         hit_rect.y++;
  44.     }
  45.     return null;
  46. }

Here's my test code:

Actionscript:
  1. protected function createTestBitmap(w:uint,h:uint):BitmapData{
  2.     var b:BitmapData=new BitmapData(w,h,true,0x00000000);
  3.     b.fillRect(new Rectangle(Math.round(w*.30),Math.round(h*.30),Math.round(w*.40),Math.round(w*.40)),0xFFFF0000);
  4.     return b;
  5. }
  6.  
  7. protected function testFirstBitmapFloodFill(w:uint,h:uint):void{
  8.     var start:uint=flash.utils.getTimer();
  9.     var b:BitmapData=createTestBitmap(w,h);
  10.     trace("testFirstBitmapFloodFill() first:"+BitmapDataUtil.getFirstNonTransparentPixelFloodFill(b));
  11.     trace("testFirstBitmapFloodFill() test took : "+(flash.utils.getTimer()-start)+" milliseconds");   
  12.     b.dispose();
  13.     b=null;
  14. }
  15.  
  16. protected function testFirstBitmapHitTest(w:uint,h:uint):void{
  17.     var start:uint=flash.utils.getTimer();
  18.     var b:BitmapData=createTestBitmap(w,h);
  19.     trace("testFirstBitmapHitTest() first:"+BitmapDataUtil.getFirstNonTransparentPixelHitTest(b));
  20.     trace("testFirstBitmapHitTest() test took : "+(flash.utils.getTimer()-start)+" milliseconds");
  21.     b.dispose();
  22.     b=null;
  23. }
  24.  
  25. protected function testFirstBitmapLooping(w:uint,h:uint):void{
  26.     var start:uint=flash.utils.getTimer();   
  27.     var b:BitmapData=createTestBitmap(w,h);
  28.     trace("testFirstBitmapLooping() first:"+BitmapDataUtil.getFirstNonTransparentPixelLooping(b));
  29.     trace("testFirstBitmapLooping() test took : "+(flash.utils.getTimer()-start)+" milliseconds")
  30.     b.dispose();
  31.     b=null;
  32. }

I tested all three functions with images ranging from 100x100 to 2000x2000 pixels:

Here be the results:

Test with image w:100,h:100
testFirstBitmapHitTest() first:(x=30, y=30)
testFirstBitmapHitTest() test took : 2 milliseconds
testFirstBitmapLooping() first:(x=30, y=30)
testFirstBitmapLooping() test took : 7 milliseconds
testFirstBitmapFloodFill() first:(x=30, y=30)
testFirstBitmapFloodFill() test took : 8 milliseconds

Test with image w:500,h:500
testFirstBitmapHitTest() first:(x=150, y=150)
testFirstBitmapHitTest() test took : 29 milliseconds
testFirstBitmapLooping() first:(x=150, y=150)
testFirstBitmapLooping() test took : 34 milliseconds
testFirstBitmapFloodFill() first:(x=150, y=150)
testFirstBitmapFloodFill() test took : 20 milliseconds

Test with image w:1000,h:1000
testFirstBitmapHitTest() first:(x=300, y=300)
testFirstBitmapHitTest() test took : 147 milliseconds
testFirstBitmapLooping() first:(x=300, y=300)
testFirstBitmapLooping() test took : 111 milliseconds
testFirstBitmapFloodFill() first:(x=300, y=300)
testFirstBitmapFloodFill() test took : 41 milliseconds

Test with image w:2000,h:2000
testFirstBitmapHitTest() first:(x=600, y=600)
testFirstBitmapHitTest() test took : 644 milliseconds
testFirstBitmapLooping() first:(x=600, y=600)
testFirstBitmapLooping() test took : 210 milliseconds
testFirstBitmapFloodFill() first:(x=600, y=600)
testFirstBitmapFloodFill() test took : 75 milliseconds

Started out good for me! With a small image my hitTest approach was over 3 times faster! Then, the bigger the sample image got, the more my method started sucking, and Mario started Rocking. Eventually the "looping" method became faster than the hitTest() :D

So, Conclusion : For small images, the amount of difference is so little it really doesn't matter, with bigger images go FloodFill... Hats off Quasimondo :)

If you have ideas for faster approaches, or improvements to the above three, don't hesitate to comment!

***EDIT :
CLICK HERE TO READ UPDATE

AND CLICK HERE TO READ UPDATE 2

***