Asynchronous Image Encoders

A while back i posted some ThreadedImageEncoder classes to convert BitmapData objects to .PNG or .JPG format over multiple Flash frames. With the arrival of ActionScript Workers i figured they would become obsolete, but we’re still waiting for Workers in mobile AIR so i’m still using them. Anyway, i needed a ‘real’ project to practice generating ASDocs with, so i chose the image encoder classes. I wasn’t planning on doing more than adding some comments, but i got carried away and ended up refactoring a lot of the code too.

Now they’re a bit more polished, i’ve renamed them to AsyncImageEncoders and given them their own github repository. In the repo you’ll find all the source code, a precompiled SWC, ASDocs for the classes and instructions on using them.

They’re easy to use:

//generate a BitmapData object to encode
var myBitmapData:BitmapData = new BitmapData(1000, 1000, true, 0x80FF9900);

//create an encoder
var encoder:IAsyncImageEncoder = new AsyncPNGEncoder();

//add listeners
encoder.addEventListener(AsyncImageEncoderEvent.PROGRESS, encodeProgressHandler);
encoder.addEventListener(AsyncImageEncoderEvent.COMPLETE, encodeCompleteHandler);

//start encoding for 20 milliseconds per frame
encoder.start(myBitmapData, 20);

function encodeProgressHandler(event:AsyncImageEncoderEvent):void
{
    //trace progress
    trace("encoding progress:", Math.floor(event.percentComplete)+"% complete");
}

function encodeCompleteHandler(event:AsyncImageEncoderEvent):void
{
    encoder.removeEventListener(AsyncImageEncoderEvent.PROGRESS, encodeProgressHandler);
    encoder.removeEventListener(AsyncImageEncoderEvent.COMPLETE, encodeCompleteHandler);
    //trace size of result
    trace("encoding completed:", encoder.encodedBytes.length+" bytes");
    //do something with the bytes...
    //..save to filesystem?
    //..upload to server?
    //..set as source for flex Image component?
}

There is also a stop() method if you get impatient with a long encode (large JPEGs on mobile can take ages) and an isRunning property which should need no explanation.

Lastly, the AsyncImageEncoderBase class can be extended to create other asynchronous encoders; hopefully, i will demonstrate that by adding a .BMP encoder in the next few days it took me 5 minutes to create a .BMP encoder.

13 thoughts on “Asynchronous Image Encoders

    1. Lee Post author

      Hi.
      At 25fps, each frame gets 40ms to do its stuff (without the frame rate dropping) so allocating 20ms per frame to encoding should be fine.

      Reply
  1. Kamil

    This is fantastic code! thanks a lot. Though I need some advice, I need to decode this byte array (Specifically PNG) back to bitmap data. Currently I get an EOF error every time I setPixels, Rectagle is exactly the same size as it was in the original BitmapData and the byte array length comes back in the same size that was written. Any advice would be really appreciated.

    Reply
    1. Lee Post author

      Hi Kamil
      Not sure what your problem could be without seeing your code. But heres how i would convert a PNG byteArray back to BitmapData (making use of Flash’s internal decoding):
      1. Create a standard Loader and use loadBytes(byteArray) method to load the PNG data into the Loader (rather than the standard load method).
      2. Wait for Event.COMPLETE
      3. Create a BitmapData object (you can get size from loader.content.width and loader.content.height)
      4. Fill BitmapData with Loader content, eg: myBitmapData.draw(loader.content);

      Reply
      1. Kamil

        I tried the Loader method and it returns “Loaded file is an unknown type.”. I write the encoded bytes into an SQLite database as a BLOB, the code below shows how I read it back.


        var bytes:ByteArray = result.data[counter].ByteData;
        trace("READING: " + bytes.bytesAvailable + " BYTES");
        var ldr:Loader = new Loader();
        ldr.addEventListener(Event.COMPLETE, doneLoader);
        ldr.loadBytes(bytes);

        private function doneLoader(e:Event):void
        {
        var ldr:Loader = e.target as Loader;
        var tmp:BitmapData = new BitmapData(ldr.content.width, ldr.content.height);
        tmp.draw(ldr.content);
        }

        Thanks for your help thus far.

        Reply
        1. Lee Post author

          The EOF error could be related to the ByteArray. Try resetting the position:

          var bytes:ByteArray = result.data[counter].ByteData;
          bytes.position = 0;
          trace("READING: " + bytes.bytesAvailable + " BYTES");
          //etc...

          Reply
  2. John

    I noticed that after I call stop, the CPU drops but memory usage remains the same…I wonder if it is specific to my application or is there a need for garbage collection?

    Also, have you tried doing this using workers? In their docs they say something scary like: “workers require additional system memory and CPU use, which can be costly to overall application performance. Because each worker uses its own instance of the runtime virtual machine, even the overhead of a trivial worker can be large.”

    Reply
    1. Lee Post author

      Hi John,

      Calling stop() doesn’t delete any data that has already been generated – as theoretically, the user may still want to access the (partial) data. So memory usage not dropping is the expected behaviour. Of course, memory usage will drop if the encoder instance itself is deleted.

      However, i think it would be a good idea to give users the option of clearing data (and it should be a doddle to implement) so i will add that option in the next day or so have added a dispose() method.

      Regarding workers, they would certainly be a good fit for this kind of processing, but they’re not yet available on mobile devices. When they are, i will certainly be looking at using them.

      Lee

      Reply
      1. Lee Post author

        While adding the dispose() method i spotted some unnecessary retention of objects once encoding had finished. These were mostly responsible for the memory usage you were seeing. Technically, they weren’t memory-leaks, as the memory would get reused the next time encoding started, but still an unnecessary overhead.
        Anyway, well spotted John. I’ve optimised the code (v1.0.4), so you should now see a drop in memory use once encoding finishes. And you can also call dispose() to clear out any encoded bytes if you don’t need them.

        Reply
  3. Thomas

    You, my friend, are really awesome.
    Free, easy to get access and perfect explanation.
    I will know for sure, if your code is working as intended after I have tested my whole application in detail, but you made my day for now!

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>