Watch for Flying Bitmaps

Aug 26, 2008

Zee goal for today is to use ActionScript 3’s BitmapData to slice and dice a bitmap like a fine piece of sashimi. A few applications include;

  • client-side image croppers.
  • runtime cropping of linked and external photos for panel backgrounds.

What I will illustrate here is just a simple sliding door effect. As seen in the SWF below. The items of greatest interest today include;

  • flash.display.Bitmap
  • flash.display.BitmapData
  • flash.geom.Point
  • flash.geom.Rectangle Otay so lets layout what we need and what we generally want to do.

Halves Too #Include

  • JPG or PNG that we want to slice up.
  • Flash compiler (Flex SDK/Flash 9/etc).

Blueprints, get your blueprints here!!

We are planning a sliding door effect that does not use a mask, where do we start? Well like all things even vaguely cool we require a little math. First lets create an additional 99 frames on our root timeline. Next drag your photo into the Library and create a linkage with a class name of TestPhoto. Done? Good now that we’ve made some room and set the table lets eat!

What size slice of pizza would you like with your BitmapData?

For this test I decided the number of frames works well to decide my slice size. Each frame is equal to a 1% increment reveal of the bitmap because of those extra 99 frames we added earlier.

1
var percentage:int = currentFrame / totalFrames;

I’m full what next?

Remember those extra 99 frames I asked you to add? Well they come in handy when used in conjunction with a frame listener. Below you will find only what is required for the dynamic slice using the current frame to calculate the percentage of image to reveal.

1
2
3
4
5
6
7
8
9
10
11
12
var slice_width:int = percentage * bitmap_width; 
var slice_data:BitmapData = new BitmapData( slice_width, source_bitmap.height );

// left edge is stationary
var crop_region:Rectangle = new Rectangle( 0, 0, slide_width, source_bitmap.height );
var destination_point:Point = new Point( 0, 0 );

// here's where the magic happens
slice_data.copyPixels( source_bitmap, crop_region, destination_point );
var slice_bitmap:Bitmap = new Bitmap( slice_data );

addChild( slice_bitmap );

Would you like a mint sir?

Okay at some point you’re going to be full to the brim, or at least providing a full reveal of your image. What if you want to downsize? As Mr. Saul might argue, downsizing often has a detrimental effect. While I agree, I’m not here to argue corporate politics. The best way to deal with this conundrum is to clean up after yourself.

1
if( numChildren > 1 ) removeChildAt( 0 );

Cheque please!

Lets wrap this all up in a class for take-out.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/**
 * Written by: Nathan Fisher
 * License: Do with this what you want just don't blame me if it blows up. 
 * 
 * For the simple test use this as your root document class.
 */
package {
 // photo linkage class name
 import SourceBitmap;

 import flash.display.Bitmap;
 import flash.display.BitmapData;
 import flash.display.MovieClip;
 import flash.display.Sprite;

 import flash.events.Event;

 import flash.geom.Point;
 import flash.geom.Rectangle;

 public class SlidingDoor extends MovieClip {
  public static const PHOTO_WIDTH:int = 720;
  public static const PHOTO_HEIGHT:int = 478;
  public static const DESTINATION_POINT:Point = new Point( 0, 0 );

  private var source_data:BitmapData;
  private var slice_container:Sprite;
  private var bitmap_width:int;


  /** SlidingDoor: Constructs a sliding door effect using the specified BitmapData.
   *
   */
  public function SlidingDoor( theSourceData:BitmapData = null )
  {
   if( theSourceData == null ) theSourceData = new TestPhoto( PHOTO_WIDTH, PHOTO_HEIGHT );
   source_data = theSourceData;
   bitmap_width = theSourceData.width;

   slice_container = new Sprite( );
   addChild( slice_container );
   addEventListener( Event.ENTER_FRAME, frameListener );
  }


  /** frameListener: Each passing frame represents a percentage of the photo to reveal.
   *
   */
  public function frameListener( e:Event ):void
  {
   var slice_width:int = percentage * bitmap_width; 
   var slice_data:BitmapData = new BitmapData( slice_width, source_bitmap.height );

   // left edge is stationary
   var crop_region:Rectangle = new Rectangle( 0, 0, slide_width, source_bitmap.height );
   var destination_point:Point = new Point( 0, 0 );

   // here's where the magic happens
   slice_data.copyPixels( source_bitmap, crop_region, destination_point );
   var slice_bitmap:Bitmap = new Bitmap( slice_data );

   // what good is art if no one see's it? Add to the slice_container so it is visible.
   slice_container.addChild( slice_bitmap );
   // remove previous slices if applicable.
   if( slice_container.numChildren > 1 ) slice_container.removeChildAt( 0 );
  }
 }
}

In the example above I assumed the bitmap slice is the only element in it’s parent container. As illustrated in the full class definition I suggest creating an empty MovieClip or Sprite that is reserved for such a purpose.

Flash to 2 minutes later in the lavatory… OUTPUT!

Notes & Other Weirdness

The class can be refined much further including a setter for the bitmap data. Like all things in life there is always room for improvement.

The included photo was provided by Tessier Pools and is a Skip Phillips design.

tags: [ actionscript ]