Saving Images Out of Flash/Flex Without Losing Quality

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:

  1. Use a Loader, and upon Event.COMPLETE, cast the the Loader.content as a Bitmap, and throw to a JPEGEncoder/PNGEncoder.
  2. Use a URLStream, and upon Event.COMPLETE, copy the bytes out into a new ByteArray.
  3. In Flex, use an mx:Image and repeat #1.
  4. 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.

Image Save ExampleSource | ZIP

14 Replies to “Saving Images Out of Flash/Flex Without Losing Quality”

  1. Jesse, great post! I haven’t had the chance to play with the Flash Player 10 FileReference class yet… but you did all the investigative work for me ;)

    Cheers

  2. If I understand what you’re trying to do, another option is to pull the data out of the Loader’s contentLoaderInfo.bytes property. I’ve done this to extract Exif, etc metadata from loaded images:

    protected function ExtractMetadata(ldr:Loader): void {
            // Flash wraps a SWF around the loaded image. We scan the SWF looking for the
            // embedded image.
            // CONSIDER: we could parse the SWF to deal with this more robustly
            
            try {
                    // Flash versions 9.0.47 and earlier don't have contentLoaderInfo.bytes!
                    var baSWF:ByteArray = ldr.contentLoaderInfo.bytes;
                    
                    // Scan the first 200 bytes looking for a JPEG
                    try {
                            for (var i:int = 0; i < 200; i++)
                            if (baSWF[i] == 0xff && baSWF[i + 1] == 0xd8 &&
                            !(baSWF[i + 2] == 0xff && baSWF[i + 3] == 0xd8)) // JPEG SOI marker
                            break;
                    } catch (err:Error) {
                            return;
                    }
                    if (i == 200)
                    return;
                    
                    baSWF.position = i; // Set position to the start of the JPEG data
                    var meta:ImageMetadata = ImageMetadata.Extract(baSWF);
                    _itemInfo.metadata = meta;
            } catch (err:Error) {
                    return;
            }
    }
    
  3. Why not use a URLLoader to load the binary data? You can then pass to Loader.loadBytes when you want to display.

    Ex. (pseudo)

    loader = new URLLoader();
    loader.dataFormat = URLLoaderDataFormat.BINARY;
    // listeners
    loader.load(new URLRequest(image))

    bytes = loader.data as ByteArray;

    displayLoader = new Loader();
    displayLoader.loadBytes(bytes);

  4. I’m trying to use this method to save filtered images (for example, after applying a ColorMatrixFilter), but it saves the original image. I’m pretty sure that I have to encode the JPEG first in order to get the modified image, or is there any other way to avoid the long and tedious encoding process?
    Thanks in advance.

  5. Hey… I’m just getting started with Flex/AS/Air for a personal project. One part is getting a reference to a directory of large images (jpgs) reading them.. scaling them down and saving them. I don’t need to display them. I have written and tested a scaling routine that I am happy with, it involves getting the image with a loader, and then drawing it to a BitMapData object… scaling that, sending it to the JpegEncoder and saving it.

    Is there an eaiser way? I also have a problem with a couple of images that are over the 16,777,215 pixel limit… does this method get around that?

    thanks for any insights.

    Bob

  6. Hi Jesse,
    Great Post and its exacly what I am looking for. However I am getting an error when trying to call the FileReference.save() method the method is not recognised? I’d really appreciate if anyone could point me in the right direction here?
    Thanks,
    Orla

  7. @Orla Keep in mind if a user runs your content in 9, and it’s compatiable with 9, that’d probably be the only function that doesn’t work in 9. So, wrap it with a try/catch just in case a user runs it in Flash Player 9 vs. 10. If your app NEEDS 10, then nevermind.

  8. I have the same problem that John, i have to modify an image and then save it, but it just save the original image, what are we missing??

Comments are closed.