Export timeline animation frames as a PNG stack
December 30th, 2011
Today - as is usually the case with these posts - I found myself writing code that I've written a dozen times before. I had some animation keyframed out on the timeline that I wanted to export as a stack of pngs.
I know there are a few tools built in that should be able to do this for me, but in the past I've had lots of trouble getting them to work - I usually end up getting dropped frames and strange behavior for nested MCs and the like.
So today I took a little extra time and made a timeline exporter that should be pretty robust. The basic idea is that we take manual control of the framerate and snapshot each frame, which is saved out to a folder during runtime. In order to pull this off, we need to the flash.filesystem package so your fla needs to be targetting the AIR runtime. In CS5.5 you can change this on the fly under Publish Settings (look for the Player menu up at the top) - I recall that on older versions of Flash you would have to make a new AIR project and copy your animation into it. FWIW it wouldn't be too hard to modify this script to load an external swf file and use that as the animation source if copying things out of one fla into another is too big of a problem.
Anyhow - with your animation in a fla targetting AIR - drop this code into the first frame of the timeline and jump into the configuration block at the top of the script. You'll want to set up your cropping and file path here, and set targetMC to point at whatever you want to capture; it can be a specific MovieClip or you can just set it to "this" and grab the whole stage.
When you have the config options fine tuned, compile the fla (you don't even need to save it) and you'll be prompted for a directory to save into. As soon as you do that, the animation is captured frame by frame and saved off into the folder you chose.
Two things to be aware of in here - first, this script uses recursion in a couple of places. If you're not familiar (or even if you are) that means that the amount of processing going on is going to scale up with how many MCs and other objects the code has to dig through. Anything with a ton of symbols or very deep nesting could get you into trouble. I highly recommend you save your work (in all programs - not just Flash) before testing this! It is very possible to crash the player as well as Flash or even your whole system. You've been warned - use this at your own risk.
Second - the png exporting used here is from as3corelib. I copied the com.adobe.images.PNGEncoder class right into the bottom of the script (with some tiny tweaks) to make the whole thing self-contained. While everything I write for this blog is available under the MIT license unless otherwise noted, the portion of this script from as3corelib is offered under their original license included at the top of that block of code, and also available on the as3 corelib project page here. If you encounter Darron Schall or Mike Chambers - buy those guys a beer or two!
Stringify Anything
December 5th, 2011
This probably brands me as an old-school AS2 crank, but I have to admit that I'm not the biggest fan of the FlashBuilder debugger. Don't get me wrong, I use it daily and it's miles ahead of the Flash Pro version; but for my money, there is still a lot of mileage left in the old trace console.
To really get good utility out of tracing, I find that I write the same helper methods over and over again in every project. The big one being a function for converting collections (Objects, Arrays, etc) to a readable format. I took a little time over the last week and tried to get this implementation as bulletproof as possible, and I'm finally pretty happy with where it is.
I'm intending to add this to T-Rex Arms either as a top level utility function, or else as a helper in the Console. In the meantime, you can drop it into a project wherever you need an eye on your data.
I set it up to handle all of the collection types: Arrays, Associative Arrays, Vectors, Dictionaries, and Objects, in addition to the primitive data types. If the input doesn't fall into those categories, it will still try to read any public attributes visible to a for-in loop.
This function is also set up for deep recursion (read the header comment) for nested or multi-dimensional collections. I hope someone else out there gets some use from this - and as always, if you find any bugs please let me know. Finally, if you want to explicitly add support to a class, just give it a toString() method. And now for some examples!
Data Sanitization
December 4th, 2011
In the past few years I've worked with engineers from a lot of different backgrounds, which has really helped me define my own coding philosophy. It's also taught me to sanitize my data at every possible point of failure. In development this has saved me countless hours of trying to track down little bits of weirdness - I can't count the number of times a little data checking has removed a weird bug symptom on a production app when I didn't have the time to dig into deep systems and track down the original source of the bug.
This is obviously a subject as broad as there are devs in the world so I'm not going to claim this is foolproof for every situation - I'll just say that it's my go-to method for catching a lot of bugs as they get written, before they can crash the application.
A couple of things: I use the Console class from T-Rex Arms all the time now for debugging - in this example, those calls can be replaced with traces. Second, I use a stringify function to convert complex data types to readable strings - I'm polishing that function and I should post that code in the nearish future; until then you can just breakpoint those lines when they get executed.
The actual nuts and bolts of this are happening in the if-then conditions. The first checks that we have an argument at all. This will catch null, undefined, 0, NaN, false Booleans, and empty strings. This is always my first line of defense because it protects against null pointer errors later in the function. The second half of that condition is making sure we're at least dealing with an Object in some capacity - no primitive types.
The next if-then is specific to checking Objects - this is where things will diverge pretty quickly for different types of data. I deal with a lot of JSON objects coming from a server or through javascript so I always make sure my attributes exist before I start calling them.
Lastly I like to verify that the data in the attributes exist and is what I'm expecting. This is another spot where things will get pretty detailed as the data gets more complex.
If this were coming from javascript (read: Unfucking the ExternalInterface) I will use the rest operator to allow any possible value(s) to come in, before I start sanitizing. This ensures that even if a malicious call is injected, I can catch it and fail gracefully instead of locking up the application.
So that's the basic checking I use. And here are some shorthands I use when I trust the data source a little better but just want to make sure things are kosher before processing them.
FoamPlucker for iPad
September 30th, 2011
I pushed my iPad build of FoamPlucker.com to the app store on Sunday evening and it got approved last night. If you don't use Sabol foam trays to store small, oddly shaped things, then it's probably not a useful app for you - but as far as developing AS3 for iOS deployment - I learned a lot.
Automatically updating AIR applications
September 15th, 2011
I always jump at the chance to write a utility in AIR to help other members of my team. The short turnaround and direct communication between engineer and consumer really reminds me how hard it is to see software from the end user's perspective, and how much a small feature or bug can make or break someone's day.
One little feature I really like to include is the Application Updater built into AIR. I just implemented this a couple days ago so I wanted to document it while the pitfalls are fresh in my mind.
Start out by going into your application XML (usually AppName.xml in the default package) and find the version number. In AIR 2.5 and later this tag is <versionNumber>, before that it was just <version>; you can find the schema you're using at the end of the url in the second line of the file - something like this:
<?xml version="1.0" encoding="utf-8" standalone="no"?> <application xmlns="http://ns.adobe.com/air/application/2.6"> <!-- Adobe AIR Application Descriptor File Template. ... --> <versionNumber>1.2</versionNumber>
For the updater to work, the installed versions of the app will look for an xml manifest (somewhere on your webserver) to compare its own version to. Here is an example of what that file looks like:
<!--?xml version="1.0" encoding="utf-8"?--> <update xmlns="http://ns.adobe.com/air/framework/update/description/2.5"> <versionNumber>1.2</versionNumber> <url>http://www.static.path.to/my/Application.air</url> <description> 1.2 Bug fixes in the UI. Fixed intermittent NPE on startup. 1.1 Optimized save/load performance. Reduced CPU load while rendering. 1.0 First version of the application - core functionality. </description> </update>
Again, if you're on AIR 2.5 or later, use the 2.5 schema and the <versionNumber> tag, if you're on an earlier SDK, use the 1.0 schema and the <version> tag. The url in here is a direct link to the updated .air file, and the description text will show up as "Release notes" during the upgrade. Save this file out to a webserver somewhere and copy the address for the next step.
WARNING: As soon as you upload this file, people will start getting prompted to update - make sure the new app is ready!
Now that the xml is set up - you can handle the rest in ActionScript. As soon as your application starts up, you'll want to call this code:
var updater:ApplicationUpdaterUI = new ApplicationUpdaterUI(); // point to the xml manifest on the server updater.updateURL = "http://www.static.path.to/my/update_descriptor.xml" updater.isCheckForUpdateVisible = false; updater.isDownloadUpdateVisible = true; updater.isDownloadProgressVisible = true; updater.isInstallUpdateVisible = true; updater.addEventListener(UpdateEvent.INITIALIZED, function(e:UpdateEvent):void{ e.target.removeEventListener(e.type, arguments.callee); updater.checkNow(); }); updater.initialize();
Export a release build and upload the finished file to where the update descriptor is expecting it and you're finished. The next time you increment the version number in the manifest and app.xml, your users will be prompted to upgrade the next time they start the app.
