Flash Player 10 comes with a nifty new method for the flash.net.FileReference class called “save“. What’s dope is that you can send it a ByteArray, and it’ll write it verbatim. This means, saving images out of your Flash & Flex Applications is wicked fast now AND gets around that lamesauce security restriction that plagued a lot of older Flash Player 9 code. In the past, you had to either upload the image, ask the server to convert/save a ByteArray/Base64 encoded string for you somewhere, which you would then download via FileReference.download. That code would fail in Flash Player 10 because you cannot initiate a download unless it’s via a mouse click.
The problem I’ve found is getting the image directly, meaning, without converting it or re-compressing it before saving. Here’s the 4 ways you can get an image into a ByteArray for saving:
- Use a Loader, and upon Event.COMPLETE, cast the the Loader.content as a Bitmap, and throw to a JPEGEncoder/PNGEncoder.
- Use a URLStream, and upon Event.COMPLETE, copy the bytes out into a new ByteArray.
- In Flex, use an mx:Image and repeat #1.
- In Flex, use Ely’s SuperImage, and repeat #1… or modify his class to expose the BitmapData it caches.
The problem is, you usually want to “show” the actual image before you save it. If you use #1 & #3, and the image you are downloading is a JPEG, you’ll either be compressing it again through the JPEGEncoder, or taking a lossy compressed JPEG and converting it to a lossless PNG before you save it which will sometimes unnecessarily increase file size while not retaining PNG’s lossless compression. Additionally, while the PNGEncoder is pretty quick, the JPEG one (whether as3corelib or built in one in Flex SDK 3.1/3.2) is slow as nuts. It will most likely lock up your browser for a second or 2 while converting decent size images. Wrapping it with a try catch in case your paranoid to avoid the script timeout error is nice and all, but it doesn’t make for a nice user experience, and browsers get flaky when you start doing that kind of stuff.
The problem with #2 & #4 is that you’re writing extra code just to feed a ByteArray to an mx:Image control via his source property. The whole point of components is encapsulate common tasks, and now the mx:Image /controls:SuperImage is just a display tag vs. a loader + display tag, putting the onus on you to code it for each time you need this functionality. Additionally, Ely’s SuperImage doesn’t support ByteArray’s like mx:Image does since it does all the BitmapData caching internally. So, you’re code gets worse as you THEN have to use a Loader to snag the Bitmap from the ByteArray.
On the flip side, at least you can use the ByteArray to both display it in a Loader via loadBytes or in the mx:Image component. To get it to work with controls:SuperImage, you just add one extra step of loading the ByteArray into a Loader.loadBytes, and then 1 frame later you can snag out the Bitmap from the loader.content, clone the Bitmap’s BitmapData into a new Bitmap, and feed that new, unique Bitmap to mx:SuperImage… which’ll then cache it internally. Don’t forget to do a Bitmap(loader.content).bitmapData.dispose() at the end since you just duplicated the Bitmap in RAM, you’ll want to remove the one you aren’t using immediately.
Now, you can immediately pass the ByteArray you got form the URLStream to the FileReference.save method which’ll be wicked fast. You can also display it immediately in an mx:Image Flex component, or 1 frame later in a Loader/controls:SuperImage (1/30th of a second in a 30 fps movie). Most importantly, what you are passing to FileReference.save is the exact, un-modified bytes of the image, so you don’t have to worry about quality loss due to encoding, nor do you need to download it again using a separate class.
The point here is to utilize URLStream as the initial mechanism to download the image and then feed the bytes to your components to display rather than having your components download it for you. In Flex, mx:Image and controls:SuperImage do not use URLStream, but rather Loader, so you have no choice but to re-convert it to a JPEG or PNG, which can (recap) potentially lose quality for the JPEG, or un-necessarily increase file size for the JPEG’s converted to PNG’s.
Took me awhile to figure that out today since I didn’t want to download the image twice, nor convert it. I ended up with a beast of a wrapper class around controls:SuperImage. It uses the URLStream to load it which then feeds the downloaded ByteArray into a Loader which then feeds the Bitmap into the controls:SuperImage. Doesn’t seem to bad, but man, a lot of crap just to “save the image I already downloaded”. A try/catch around each load and then listening to all 3 events (complete, io error, security error). Bleh! All this SWF security better pay off somehow.
One other thing interesting of note is that FileReference.save will throw a ReferenceError if you run a Flash Player 10 SWF in Flash Player 9. This doesn’t seem to be documented vs. the 4 that are documented (IllegalOperationError, ArgumentError, Error, and MemoryError).
Here’s an example application that shows the above concepts in action.