Image Distortion and Tapering in ActionScript

Wednesday, June 24th, 2009 | ActionScript | 1 Comment

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

**Update: 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:

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

Set up your DistortImage and DistortVO:

Actionscript:
  1. _image = new Sprite();
  2. _image.addChild(_background);
  3. this.addChild(_image);
  4.  
  5. _d = new DistortImage( _image, 2, 2 );
  6. _image.removeChild(_background);
  7.  
  8. _dVO = new DistortVO();
  9. _dVO.data = _image;

Use your favorite tweening engine to do the rest:

Actionscript:
  1. private function clickHandler(event:MouseEvent = null):void
  2. {
  3.     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});   
  4. }
  5.  
  6. private function applyMatrix($dVO:DistortVO):void
  7. {
  8.    _d.setTransform($dVO.x0, $dVO.y0, $dVO.x1, $dVO.y1, $dVO.x2, $dVO.y2 ,$dVO.x3, $dVO.y3)
  9. }

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!

Actionscript:
  1. /*
  2. ****************************
  3. * From a first idea and first implementation of Andre Michelle www.andre-michelle.com
  4. * @version 2.0
  5. * @author Thomas Pfeiffer - kiroukou - http://www.thomas-pfeiffer.info
  6. * @author Richard Lester - RichL
  7. * @author Didier Brun - foxy - http://www.foxaweb.com
  8. * @website: http://sandy.media-box.net
  9. * @description: Tesselate a movieclip into several triangles to allow free transform distorsion.
  10. * *************************
  11. * Licensed under the CREATIVE COMMONS Attribution-NonCommercial-ShareAlike 2.0
  12. * you may not use this file except in compliance with the License.
  13. * You may obtain a copy of the License at
  14. * http://creativecommons.org/licenses/by-nc-sa/2.0/fr/deed.en_GB
  15. ***************************
  16. * DistortImage class
  17. * Availability : Flash Player 8.
  18. **********************************************
  19. */
  20. import flash.geom.Matrix;
  21. import flash.display.BitmapData;
  22. class DistortImage
  23. {
  24.     // -- texture to distord
  25.     public var texture:BitmapData;
  26.     /*
  27.     * Constructor
  28.     * @param mc MovieClip : the movieClip containing the distorded picture
  29.     * @param symbolId String : th link name of the picture in the library
  30.     * @param vseg Number : the vertical precision
  31.     * @param hseg Number : the horizontal precision
  32.     * @throws: An error in the second constructor parameter isn't a BitmapData or a MovieClip
  33.     */
  34.     public function DistortImage( mc: MovieClip, ptexture, vseg: Number, hseg: Number )
  35.     {
  36.         _mc = mc;
  37.         if( ptexture instanceof BitmapData )
  38.         {
  39.             texture = ptexture;
  40.         }
  41.         else if( ptexture instanceof MovieClip )
  42.         {
  43.             texture = new BitmapData( ptexture._width, ptexture._height );
  44.             texture.draw( ptexture );
  45.         }
  46.         else
  47.         {
  48.             throw new Error('Second argument in DistortImage class must be a BitmapData object or a Movieclip');
  49.         }
  50.         _vseg = vseg || 0;
  51.         _hseg = hseg || 0;
  52.         // --
  53.         _w = texture.width ;
  54.         _h = texture.height;
  55.         // --
  56.         _aMcs   = new Array();
  57.         _p   = new Array();
  58.         _tri    = new Array();
  59.         // --
  60.         __init();
  61.     }
  62.     /**
  63.     * setTransform
  64.     *
  65.     * @param x0 Number the horizontal coordinate of the first point
  66.     * @param y0 Number the vertical coordinate of the first point
  67.     * @param x1 Number the horizontal coordinate of the second point
  68.     * @param y1 Number the vertical coordinate of the second point
  69.     * @param x2 Number the horizontal coordinate of the third point
  70.     * @param y2 Number the vertical coordinate of the third point
  71.     * @param x3 Number the horizontal coordinate of the fourth point
  72.     * @param y3 Number the vertical coordinate of the fourth point
  73.     *
  74.     * @description : Distord the bitmap to ajust it to those points.
  75.     */
  76.     function setTransform( x0:Number , y0:Number , x1:Number , y1:Number , x2:Number , y2:Number , x3:Number , y3:Number ): Void
  77.     {
  78.         var w:Number = _w;
  79.         var h:Number = _h;
  80.         var dx30:Number = x3 - x0;
  81.         var dy30:Number = y3 - y0;
  82.         var dx21:Number = x2 - x1;
  83.         var dy21:Number = y2 - y1;
  84.         var l:Number = _p.length;
  85.         while( --l> -1 )
  86.         {
  87.             var point:Object = _p[ l ];
  88.             var gx = ( point.x - _xMin ) / w;
  89.             var gy = ( point.y - _yMin ) / h;
  90.             var bx = x0 + gy * ( dx30 );
  91.             var by = y0 + gy * ( dy30 );
  92.             point.sx = bx + gx * ( ( x1 + gy * ( dx21 ) ) - bx );
  93.             point.sy = by + gx * ( ( y1 + gy * ( dy21 ) ) - by );
  94.         }
  95.         __render();
  96.     }
  97.     /////////////////////////
  98.     ///  PRIVATE METHODS  ///
  99.     /////////////////////////
  100.     private function __init( Void ): Void
  101.     {
  102.         _p = new Array();
  103.         _tri = new Array();
  104.         var ix:Number, iy:Number;
  105.         var w2: Number = _w / 2;
  106.         var h2: Number = _h / 2;
  107.         _xMin = _yMin = 0;
  108.         _xMax = _w; _yMax = _h;
  109.         _hsLen = _w / ( _hseg + 1 );
  110.         _vsLen = _h / ( _vseg + 1 );
  111.         var x:Number, y:Number;
  112.         var p0:Object, p1:Object, p2:Object;
  113.         // -- we create the points
  114.         for ( ix = 0 ; ix <_hseg + 2 ; ix++ )
  115.         {
  116.             for ( iy = 0 ; iy <_vseg + 2 ; iy++ )
  117.             {
  118.                 x = ix * _hsLen;
  119.                 y = iy * _vsLen;
  120.                 _p.push( { x: x, y: y, sx: x, sy: y } );
  121.             }
  122.         }
  123.         // -- we create the triangles
  124.         for ( ix = 0 ; ix <_vseg + 1 ; ix++ )
  125.         {
  126.             for ( iy = 0 ; iy <_hseg + 1 ; iy++ )
  127.             {
  128.                 p0 = _p[ iy + ix * ( _hseg + 2 ) ];
  129.                 p1 = _p[ iy + ix * ( _hseg + 2 ) + 1 ];
  130.                 p2 = _p[ iy + ( ix + 1 ) * ( _hseg + 2 ) ];
  131.                 __addTriangle( p0, p1, p2 );
  132.                 // --
  133.                 p0 = _p[ iy + ( ix + 1 ) * ( _vseg + 2 ) + 1 ];
  134.                 p1 = _p[ iy + ( ix + 1 ) * ( _vseg + 2 ) ];
  135.                 p2 = _p[ iy + ix * ( _vseg + 2 ) + 1 ];
  136.                 __addTriangle( p0, p1, p2 );
  137.             }
  138.         }
  139.         __render();
  140.     }
  141.     private function __addTriangle( p0:Object, p1:Object, p2:Object ):Void
  142.     {
  143.         var u0:Number, v0:Number, u1:Number, v1:Number, u2:Number, v2:Number;
  144.         var tMat:Object = {};
  145.         // --
  146.         u0 = p0.x; v0 = p0.y;
  147.         u1 = p1.x; v1 = p1.y;
  148.         u2 = p2.x; v2 = p2.y;
  149.         tMat.tx = -v0*(_w / (v1 - v0));
  150.         tMat.ty = -u0*(_h / (u2 - u0));
  151.         tMat.a = tMat.d = 0;
  152.         tMat.b = _h / (u2 - u0);
  153.         tMat.c = _w / (v1 - v0);
  154.         // --
  155.         _tri.push( [p0, p1, p2, tMat] );
  156.     }
  157.     private function __render( Void ): Void
  158.     {
  159.         var vertices: Array;
  160.         var p0, p1, p2:Object;
  161.         var x0:Number, y0:Number;
  162.         var ih:Number = 1/_h, iw:Number = 1/_w;
  163.         var c:MovieClip = _mc; c.clear();
  164.         var a:Array;
  165.         var sM = {};
  166.         var tM = {};
  167.         //--
  168.         var l:Number = _tri.length;
  169.         while( --l> -1 )
  170.         {
  171.             a   = _tri[ l ];
  172.             p0  = a[0];
  173.             p1  = a[1];
  174.             p2  = a[2];
  175.             tM = a[3];
  176.             // --
  177.             sM.a = ( p1.sx - ( x0 = p0.sx ) ) * iw;
  178.             sM.b = ( p1.sy - ( y0 = p0.sy ) ) * iw;
  179.             sM.c = ( p2.sx - x0 ) * ih;
  180.             sM.d = ( p2.sy - y0 ) * ih;
  181.             sM.tx = x0;
  182.             sM.ty = y0;
  183.             // --
  184.             sM = __concat( sM, tM );
  185.             c.beginBitmapFill( texture, sM, false, false );
  186.             c.moveTo( x0, y0 );
  187.             c.lineTo( p1.sx, p1.sy );
  188.             c.lineTo( p2.sx, p2.sy );
  189.             c.endFill();
  190.         }
  191.     }
  192.     private function __concat( m1, m2 ):Object
  193.     {   //Relies on the original triangles being right angled with p0 being the right angle.
  194.         //Therefore a = d = zero (before and after invert)
  195.         var mat = {};
  196.         mat.a  = m1.c * m2.b;
  197.         mat.b  = m1.d * m2.b;
  198.         mat.c  = m1.a * m2.c;
  199.         mat.d  = m1.b * m2.c;
  200.         mat.tx = m1.a * m2.tx + m1.c * m2.ty + m1.tx;
  201.         mat.ty = m1.b * m2.tx + m1.d * m2.ty + m1.ty;
  202.         return mat;
  203.     }
  204.     /////////////////////////
  205.     /// PRIVATE PROPERTIES //
  206.     /////////////////////////
  207.     private var _mc:MovieClip;
  208.     private var _w:Number;
  209.     private var _h:Number;
  210.     private var _xMin:Number, _xMax:Number, _yMin:Number, _yMax:Number;
  211.     // -- picture segmentation properties
  212.     private var _hseg:Number;
  213.     private var _vseg:Number;
  214.     private var _hsLen:Number;
  215.     private var _vsLen:Number;
  216.     // -- arrays of differents datas types
  217.     private var _p:Array;
  218.     private var _tri:Array;
  219.     private var _aMcs:Array;
  220. }

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

Tags: , ,

Logic != Math: Transparency Example

Wednesday, June 17th, 2009 | Uncategorized | 3 Comments

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.

Six Months on AIR

Friday, June 5th, 2009 | AIR | 3 Comments

Attention AIR developers: Are you interested in learning about caching images in binary format, SQLite database migration, re-using existing view components, memory fragmentation and general tips / tricks about developing your application using Adobe AIR? If you answered YES than you should attend Minh Vu and Chris Black's session at Flashbelt on June 8th at 1:30PM. We're going to have a killer presentation for everyone interested in developing Adobe AIR desktop applications.

Can't attend Flashbelt?
I would highly recommend attending Flashbelt. Networking opportunities, great presentations and lots of fun! OK, if you absolutely can go than check out our presentation below.

Who should attend?
Developers that are currently developing AIR applications or are interested in doing AIR development in the future. Project managers would also benefit from knowing the decisions that go into creating large scale applications. How about designers? This presentation is geared towards the development side of AIR but designers are welcome.

BulkLoader with Binary data format:
link

About the presenters:
Chris Black is a Senior Developer at Sierra Bravo who focuses on ActionScript development with Adobe Flex and AIR, and is interested in integrating social networking APIs into Rich Internet Applications. He covers these topics as well as sharing solutions to the problems he encounters when working with Flex and AIR on his blog, blackcj.com. Chris has a degree in Computer Science from the University of Wisconsin - Eau Claire, and when he's away from the computer he enjoys rock climbing, backpacking, and tennis.

Minh Vu is a Developer at Sierra Bravo who works on interactive development with ActionScript, JavaScript, and iPhone. He has a strong interest in applying Model Driven Development to Flash or AIR applications. Minh studied at the University of Minnesota Duluth where he got his degree is in Information System & Technology. In his spare time he enjoys disc golfing, snowboarding and ultimate frisbee.

Tags: , ,

Re-Learning ActionScript 2

Tuesday, May 26th, 2009 | ActionScript, Flash | 2 Comments

It's amazing how easy it is to forget the little niches in programming languages after going almost a year without using them. I recently had the pleasure of working on an ActionScript 2 project after almost a year of doing primarily ActionScript 3 development. This post will highlight a few things that can make ActionScript 2 development a bit more like 3. I'll also provide links to resources that helped get me back in the mindset of AS2.

Event dispatching.
One of the toughest parts of going back to AS2 is the event handling. AS3 does this much better and one of my co-workers (@rexeisen) sent me this great link: AS3 Style Events in AS2

Singleton design pattern.
I found a great PDF outlining how to implement the singleton design pattern using AS2: Singleton Design Patter with AS2

My singleton implementation:

Actionscript:
  1. import com.cb.Utils.messaging.*;
  2. import com.cb.events.Event;
  3. import mx.utils.Delegate;
  4.  
  5. class com.sierrabravo.Utils.messaging.MessageManager
  6. {
  7.     private static var instance:MessageManager;
  8.     private var _container:MovieClip;
  9.     private var _currentContent:MovieClip;
  10.     private var _tint:MovieClip;
  11.  
  12.     private function MessageManager()
  13.     {
  14.         // Do nothing
  15.     }
  16.  
  17.     public static function getInstance():MessageManager
  18.     {
  19.         if(MessageManager.instance == null)
  20.         {
  21.             MessageManager.instance = new MessageManager();
  22.         }
  23.         return MessageManager.instance;
  24.     }
  25.  
  26.     public function setParent(parentClip:MovieClip):Void
  27.     {
  28.         _container = parentClip;
  29.     }
  30.    
  31.     public function updatePosition():Void
  32.     {
  33.         if(_currentContent){
  34.             _currentContent._x = (Stage.width - _currentContent._width) / 2;
  35.             _currentContent._y = (Stage.height - _currentContent._height) / 2;
  36.         }
  37.         if(_tint){
  38.             _tint._width = Stage.width;
  39.             _tint._height = Stage.height;
  40.         }
  41.     }
  42.    
  43.     public function addMessageWindowContent(contentWindow:MessageContentBase):Void
  44.     {
  45.         tint();
  46.         _currentContent = _container.createEmptyMovieClip( "content", _container.getNextHighestDepth() );
  47.         contentWindow.init( _currentContent );
  48.  
  49.         _currentContent._x = (Stage.width - _currentContent._width) / 2;
  50.         _currentContent._y = (Stage.height - _currentContent._height) / 2;
  51.         contentWindow.addEventListener(Event.CLOSE, Delegate.create( this, closeWindow ));
  52.     }
  53.    
  54.     public function closeWindow():Void
  55.     {
  56.         trace('close window');
  57.         _currentContent.removeMovieClip();
  58.         clearTint();
  59.     }
  60.    
  61.     public function clearTint():Void
  62.     {
  63.         _tint.removeMovieClip();
  64.     }
  65.  
  66.     public function tint():Void
  67.     {
  68.         _tint = _container.createEmptyMovieClip("rectangles", _container.getNextHighestDepth());
  69.        
  70.         _tint.beginFill(0x000000, 50);
  71.         _tint.lineStyle(1, 0x000033, 100);
  72.         _tint.moveTo(0, 0);
  73.         _tint.lineTo(Stage.width, 0);
  74.         _tint.lineTo(Stage.width, Stage.height);
  75.         _tint.lineTo(0, Stage.height);
  76.         _tint.endFill();
  77.         _tint.onRelease = function(){
  78.             //DO NOTHING
  79.         }
  80.         _tint.useHandCursor = false;
  81.     }
  82. }

Preventing click through.
In the above example the screen is tinted to put focus on the pop up window. Unfortunately, even though there is a movie clip on top of everything else, it does not prevent interaction with the elements layered below it. In order to get around this I added an onRelease to the _tint movie clip along with a useHandCursor of false. This prevents interaction with elements below the tint. Here is the link to where I found this solution: Disable "Click-Through" on MovieClips in Flash

Debugging in ActionScript 2.
For the project we were working on it was not possible to use the built in debugger for Flash. Another great opportunity to use the Singleton design patter to include a trace window that can have text dumped out from any part of the application.

Actionscript:
  1. class com.cb.Utils.debug.Debugger
  2. {
  3.     private static var instance:Debugger;
  4.     private var _textField:TextField;
  5.     private var _parentClip:MovieClip;
  6.  
  7.     private function Debugger()
  8.     {
  9.         // Do nothing
  10.     }
  11.  
  12.     public static function getInstance() :D ebugger
  13.     {
  14.         if(Debugger.instance == null)
  15.         {
  16.             Debugger.instance = new Debugger();
  17.         }
  18.         return Debugger.instance;
  19.     }
  20.  
  21.     public function init(myText:TextField, parentClip:MovieClip):Void
  22.     {
  23.         _textField = myText;
  24.         _textField.wordWrap = true;
  25.         _textField.background = true;
  26.         _textField.border = true;
  27.         _textField.multiline = true;
  28.         _parentClip = parentClip;
  29.     }
  30.    
  31.     public function output(myString:String):Void
  32.     {
  33.         if(_textField) {
  34.              _textField.swapDepths(_parentClip.getNextHighestDepth());
  35.              _textField.text += myString + "\n";
  36.         }
  37.     }
  38. }

XML parsing.
Parsing XML is a key part of AS2 and AS3. I found a great tutorial on Loading XML data in Flash using ActionScript 2. This is my code for parsing XML.

Actionscript:
  1. public function init(clip:MovieClip):Void
  2. {
  3.     example_xml = new XML();
  4.     example_xml.ignoreWhite = true;
  5.     example_xml.load("xml/BookDefinition.xml");
  6.     example_xml.onLoad = Delegate.create(this, onLoadEvent);
  7. }
  8. function onLoadEvent(success:Boolean):Void {
  9.     if (success) {
  10.         var myImage = example_xml.firstChild.firstChild.childNodes;
  11.         for (var i = 0; i<myImage.length; i++) {
  12.             var pageNumber:Number = parseInt(myImage[i].childNodes[0].firstChild);
  13.             var imageURL:String = myImage[i].childNodes[1].firstChild;
  14.             var keywords:String = myImage[i].childNodes[2].firstChild.toString().toLowerCase();
  15.         }
  16.     }
  17. }

XML:
  1. <book>
  2.     <pages>
  3.         <page>
  4.             <pageNumber>1</pageNumber>
  5.             <pageFlash>thumbnails/page01.jpg</pageFlash>
  6.             <pageText>This is page 1.</pageText>k
  7.         </page>
  8.         <page>
  9.             <pageNumber>2</pageNumber>
  10.             <pageImage>thumbnails/page02.jpg</pageImage>
  11.             <pageText>This is page 2.</pageText>
  12.         </page>
  13.     </pages>
  14. </book>

Delegate functions.
Notice above that a delegate function was used to keep the scope of the function with the current class. More information on delegate functions can be found here: The Delegate Class

That's it for now. I know that I'll be using this post as a reference the next time I have to jump on another AS2 project. Being able to use design patterns, event dispatching, and debugging actually made coding in AS2 fun! Enjoy.

Additional resources.
http://www.flashmobileblog.com/tag/flash-lite-31/
http://www.adobe.com/products/flashlite/features/

Tags: , , , ,

Technologies

My Next Conferences



Where I do Nerdy Stuff

Categories