Monday, July 20, 2009

Recording motion in Flash CS4 Part 1

I should be more specific. This is creating a swf that allows someone to record and play back the animation of a dragged object. This is not recording the motion of dragged objects in the Flash IDE. (I have not played around with extending flash. Maybe I should.)

I also want to give some credit to the person and website where I got the idea. I was at the FriendsOfEd.com site checking out some of the new Flash titles, specifically "Foundation Actionscript 3.0 Image Effects" by Todd Yard, because BitmapData manipulation is not one of my stronger skills at this point. I followed some links to Todd's site at 27bobs.com, where I found his tutorial on capturing motion. The example was written for Flash 5 and used onClipEvents. I did not read much of the tutorial. I only flipped through it until I had an "Ahh Ha" or "No Duh" moment when it was mentioned using arrays to store x and y locations. I do not know what he did after that. I like to figure out the details on my own. Besides, I don't think it would have helped much looking at all that old code. Sometimes it is just easier starting from scratch. I also want to use some Flash Player 10 specific code before the process is done.

First let's look at the results. My first example is at http://www.richhumphreys.com/flash-experiments/recording-animation-1.html. Just follow the instructions to test it.

Now that you see where we are headed, let's see how to get there.
  1. We need some assets to play with. This version has a background image and a translucent bitmap image for the bee, but all we really need is a MovieClip and some buttons (I got mine from the Flash Common Libraries found in the Window menu. The two buttons just get added to the stage and given the instance names of "play_btn" and "record_btn".
  2. Go to the Properties panel for whatever symbol will be your moving object and export it for ActionScript. I gave mine the class name "Bee" and just left the default Base class as MovieClip. (If I were creating hundreds of these, I would probably change it to Sprite) We will be adding this using the ActionScript addChild method.
  3. Now create a new ActionScript file and call it MotionRecorder.as
  4. Add the skeleton of a Class file.

package
{
    public class MotionRecorder extends MovieClip
    {
      public function MotionRecorder():void
      {

      {
    }
}


  1. Before the class statement, import the classes we will need for our Class

import flash.display.MovieClip;
import flash.events.MouseEvent;
import flash.events.Event;
import flash.geom.Point;


  1. Create variables for our Bee object, a boolean called "recording" to keep track of whether we are recording or playing, an Array called "points" that will hold the recorded information, and a variable called "playPosition" which will call up the correct indexed value from the Array.

private var bee:Bee;
private var recording:Boolean = false;
private var points:Array = new Array();
private var playPosition:int = 0;


  1. In the constructor function we add a new instance of the Bee object from our library to the stage and give it an x and y location. We also add MOUSE_DOWN and MOUSE_UP event listeners to the bee instance and CLICK events to the record and play buttons.

bee = new Bee();
addChild(bee);
bee.x = 350;
bee.y = 375;

bee.addEventListener(MouseEvent.MOUSE_DOWN, onMousePress);
bee.addEventListener(MouseEvent.MOUSE_UP, onMouseRelease);
record_btn.addEventListener(MouseEvent.CLICK, skinButtonClick);
play_btn.addEventListener(MouseEvent.CLICK, skinButtonClick);


  1. Our first function is to handle the play and record buttons. It simply tests to see which button was clicked (using a switch). If it's the record button, we empty the points Array and set "recording" to true. If the play button is clicked, we tell flash we are no longer recording and start an ENTER_FRAME loop to create our playback.


private function skinButtonClick(event:MouseEvent):void
  {
    switch(event.target.name){
      case "record_btn":
      points.length = 0;
      recording = true;
      break;

    case "play_btn":
      recording = false;
      addEventListener(Event.ENTER_FRAME, onLoop);
      break;
    default:
      points.length = 0;
      recording = false;
    }
}

  1. Our next two functions handle the MOUSE_DOWN and MOUSE_UP events and add the drag and drop functionality to the bee. If recording is set to true, the onMousePress function starts a loop to allow for recording each frame. The onMouseRelease function stops the loop by removing the ENTER_FRAME event listener.

private function onMousePress(event:MouseEvent):void
{
   if(recording){
    bee.startDrag();
    addEventListener(Event.ENTER_FRAME, onLoop);
   }
}

private function onMouseRelease(event:MouseEvent):void
{
   removeEventListener(Event.ENTER_FRAME, onLoop);
   bee.stopDrag();
}

  1. The last function, onLoop, handles both recording frames of motion and playing them back. If recording is set to true, the function adds Points to the points Array, recording the x and y locations each time the loop runs (at the file's frame rate). If recording is set to false, the loop moves the bee MovieClip to the x,y location specified by the Point at the current index setting according to the playPosition variable. We increment the variable and continue until we run out of items in the Array. When we run out of items in the Array, we remove the event listener (thus stopping the looping) and reset the playPosition so we can run it again.
private function onLoop(event:Event):void
{
   if(recording){
    points.push(new Point(bee.x, bee.y));
   } else {
    if(playPosition < points.length){
     bee.x = points[playPosition].x;
     bee.y = points[playPosition].y;
     playPosition++;
    }else{
     removeEventListener(Event.ENTER_FRAME, onLoop);
     playPosition = 0;
    }
   }
}


That's it. The record button tells the script to add to the Array and the play button tells it to read from the Array.

For the full ActionScript file go here.

In the next post I will look into how we can make this code run with a few new features of Flash Player 10.

One last thing... Since I did not have a photo of my own that fit my needs, I turned to Flicker and used one by adulau. Thank you adulau for using Creative Commons rights and allowing your file to be modified, and having some cool pics to choose from.

Thursday, January 8, 2009

Encoding JPEGs from Flash 10

I love the web. Combine old information and misinformation with a heavy dash of bias, and you have the Internet.

I am working on a project where I want users to be able to export what they have created as either a JPEG or PNG file and save it to their own systems.

When I started I knew flash could not create the Jpeg or Png formats natively, but also knew there was some code out there to get the job done. Most examples of encoding jpg or png files involve two things. First there is the AS3 Core Library, which are a series of classes contributed to the community from Adobe as open source code.

This code can be found at http://code.google.com/p/as3corelib/ , and can then be added to your class path. More on that later.

In a nutshell the way this is supposed to work goes like this...
Using the JPGEncoder class from the as3corelib code, flash is able to produce correct content to create a jpeg compressed bitmap file. This information is then attached to the header of an HttpRequest and sent off to a server-side script. The server script does not have to create the file. It just gathers the incoming data and tells the browser to make the file. The end user just needs to tell the browser what to call it and where to put it. To the user it just looks like they have downloaded the file they were looking at.

My problem is that all the examples out there are in Php. I have nothing against Php. Some of my favorite servers have Php, but the server I am using at the moment is running Coldfusion. I am currently trying to explore the pros and cons of working within Adobe's toolset. I wander off quite a bit, but it would still be nice to know what can be done taking advantage of any synergy available in a collection of applications.

But I digress...

Being what I consider to be a beginner at CF, I have yet to find a concise solution to replicate what is being done in the Php files I have seen. (for an example, go to http://henryjones.us/articles/using-the-as3-jpeg-encoder) This is surprising. Most of the Coldfusion solutions tend to be clear, concise and easy to understand.

What I am leading up to is.... I was mostly a waste of time. The Flash 10 player does not need a server-side helper!

With only a extra Class or two and a few lines of code, Flash Player 10 can let the user save Jpg or Png files based on content in the swf.

Step 1. Download and unzip the code library from http://code.google.com/p/as3corelib/
Step 2. You have two basic options from here. You can use the "src" folder and it's contents as a class library. You can also use the swc file found in the "lib" folder. In either case I like to have one location where I store all third party code. For me, this is a folder on the desktop I call "classes and components". If I'm using the source files I place them in a folder called "as3 classes" and then in the "com" folder. That way when referencing the Actionscript classes I want, I can call them with an import statement like this...

import com.adobe.images.JPGEncoder;

in order for Flash to find the "com" folder go to File>Publish Settings. Go to the Flash tab and the "Settings" button next to the Script combo box.

In "Advanced Actionscript 3.0 Settings" make sure you are on the "Source Path" tab and add a new path to the folder above or parent to your "com" folder. Click Ok to to finish the process and you are ready to add your code.

The other option is to point to the SWC file. A SWC file is a compiled component that can contain classes, graphics and so on. All the class files from AS3 Code Library are available in the SWC file.

In flash CS4, you can attach the SWC file in the same "Advanced Actionscript 3.0 Settings" panel. You use the "Library Path" tab rather than "Source Path" and the SWC file can be located anywhere on your system.

Once you have either source in place, it's time to start adding your code.

I my test file I created a shape on the the desktop and converted it to a MovieClip with the instance name "sketch_mc". I also added a Button from the Components panel and changed the label to read "Save JPG". I gave the button the instance name of "saveJPG_btn". (in hindsight _btn was not the most appropriate choice)

my code begins with the import statement.

import com.adobe.images.JPGEncoder;

This makes the JPGEncoder class available to my code and the compiler to format the bitmap data and compress it.

The next bit of code should be familier.

saveJPG_btn.addEventListener(MouseEvent.CLICK, onSaveJPG, false, 0, true);

...sets up a CLICK mouse event on the saveJPG_btn and calls the function "onSaveJPG".

Now for the function.

function onSaveJPG(e:MouseEvent):void
{
var paintGrab:BitmapData = new BitmapData(sketch_mc.width,sketch_mc.height);
paintGrab.draw(sketch_mc);
var myEncoder:JPGEncoder = new JPGEncoder(85);
var byteArray:ByteArray = myEncoder.encode(paintGrab);
var file:FileReference = new FileReference();
file.save(byteArray,"myimage.jpg");
}

First the paintGrab variable holds a new BitmapData object set to the size of the sketch_mc MovieClip.

We then use the BitmapData draw() method to basically copy the pixels in the rendered MovieClip into the paintGrab variable.

Next, we create an instance of the JPGEncoder object and set the compression rate in the constructor.

In order to save the BitmapData as a JPG file it needs to be converted to a ByteArray, so we use the encode() method of the JPGEncoder instance we created earlier. If we had multiple images to save with the same compression, we could just reuse the myEncoder variable many times again without modification.

We are now storing the new JPEG encoded content as the ByteArray variable called "byteArray".

Up to this point, all of this can be done in Flash CS3. The next part is new to Flash CS4 and FlashPlayer 10.

Create an instance of a FileReference Object. I called mine "file".

Now call the "save" method of the FileReference object.

file.save(byteArray, "myimage.jpg");

The first argument is the data to be stored in the new file. The second argument is the suggested name given to that file in the "Save As..." dialog box that pops up.

That's it. Run your Flash file and test the button. You should be able to save a rasterized copy of your movieclip as a JPG file anywhere you want.

--As a side note; we are starting to run our Adobe CS4 classes. I teach a introductory level Dreamweaver CS4 class at the end of this month and there's also Flash CS4 and Illustrator CS4 classes coming in the next 6 weeks. Check out Ascend's Schedule for more information.