AS3 JPEGEncoder and big images

I'm working on a project where I needed to encode and save large images from Flash to the user's local computer. Thankfully Flash 10 allows local file writing which made the task much easier but the JPEGEncoder was causing all sorts of issues. Timing out, crashing, the spinning beach ball, lots of fun.  Turns out it was a looping issue in the encoding process. Every single pixel needs to be encoded, which means looping 'image width' x 'image height' iterations. I'm trying to encode bitmaps that are 4000x4000, yipes!

Fortunately with a little reworking I was able to convert the encoding process from a synchronous to an asychronous process.

The code below from the com.adobe.images.JPEGEncoder class needed a little tweak to resolve the issue (note: the mx.graphics.codec.JPEGEncoder class has the same issue).

 for (var ypos:int = 0; ypos < height; ypos += 8)
{
for (var xpos:int = 0; xpos < width; xpos += 8)
{
RGB2YUV(sourceBitmapData, sourceByteArray, xpos, ypos, width, height);

DCY = processDU(YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT);
DCU = processDU(UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT);
DCV = processDU(VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT);
}
}

The fix was to have the JPEGEncoder class implement the IEventDispatcher interface so it could dispatch progress and complete events. Now instead of looping over the entire height of the image in one call, an interval is used to "loop" over the image. I also added a cancel method so you can stop the encoding process if needed.

private function doEncode(image:BitmapData):void 
{
for (var xpos:int=0; xpos
{
RGB2YUV(image, xpos, _ypos);
DCY = processDU(YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT);
DCU = processDU(UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT);
DCV = processDU(VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT);
}
_ypos += 8;

var e:ProgressEvent = new ProgressEvent(ProgressEvent.PROGRESS);
if (_ypos
{
e.bytesLoaded = _ypos;
e.bytesTotal = image.height;
dispatchEvent(e);
}
else
{
flash.utils.clearInterval(_encodeTimer);
e.bytesLoaded = image.height;
e.bytesTotal = image.height;
dispatchEvent(e);
finishEncode();
}

}

On the performance side, encoding a 4000x4000 bitmap at 80% quality using the async method takes 119 seconds, using the normal method takes 109 seconds plus it locks up the browser, if it doesn't crash it. That's a fair trade off.

You can download the AsycJPEGEncoder here. Enjoy.


11 Comments:

  1. ryan

    thanks for sharing your patched/optimized version...

    i ran into a similar problem where i was ecoding a few jpegs in one go...

    as a quiker solution i used a timer to encode each photo at 1 second intervals... it worked but

    the jpg encoder can easily chew up a huge amount of resources...

  2. Ryan

    Great work, I can't seem to get it to work though. Can you post an example please? I was using the adobe JPGEncoder but would really like progress event etc due to the hanging.

  3. Ryan

    Eg: I'm getting errors, 1178: Attempted access of inaccessible property byteout through a reference with static type com.dgrigg.utils:AsyncJPEGEncoder.

    1180: Call to a possibly undefined method writeWord.

    etc

  4. andi

    Has anyone answered Ryan im stuck with the same problem too. any help would be much appreciated

  5. Orc

    Yeah same here and i am facing a problem in getting snapshots from certain movie clips :( i am not sure what is the exact cause but it doesnt seem to take snapshots = anyone had some experience with this? and also before it didn't seem to be able to work with big images... any setting for this? thanks :)

  6. Derrick

    Hey all, here is a sample of how I have used it in an AIR application.


    import com.dgrigg.utils.AsyncJPEGEncoder;

    private function encodeFile():void
    {
    var j:AsyncJPEGEncoder = new AsyncJPEGEncoder(80);
    j.addEventListener(ProgressEvent.PROGRESS, jpegProgressHandler);
    j.addEventListener(Event.COMPLETE, jpegComplete);
    var byte:ByteArray = j.encode(mosaic.bitmap);
    }

    private function jpegProgressHandler(event:ProgressEvent):void
    {
    progressBar_pb.setProgress(event.bytesLoaded, event.bytesTotal);
    }

    private function jpegComplete(event:Event):void
    {
    var j:AsyncJPEGEncoder = event.target as AsyncJPEGEncoder;
    var f:File = new File(File.desktopDirectory.nativePath + "/new-jpeg.jpg");

    var fs:FileStream = new FileStream();
    fs.open(f, FileMode.WRITE);
    fs.writeBytes(j.getBytes());
    fs.close();
    }

    Hope that helps.

    Derrick

  7. Raphael Wichmann

    Hey Andi, Ryan and Orc.

    Try changing all properties and methods in the JPGEncoder class from private to protected.
    Actually you can only change the ones you are missing in the AsyncJPEGEncoder class.

    You get an error because in AS3 private properties and methods are not inherited.

    For people who don't have the JPGEncoder class (like me), you get them here: http://code.google.com/p/as3corelib/

    Cheers,
    Raphael ;)

  8. Ranjan

    It works fine but you will need to convert all private methods to protected in existing JPEGEncoder class.

    It simply works like magic. Nice work Derrick.

    Thanks buddy. You extended class can solve many problems of other programmers.

  9. Chris

    This works great but my bench tests weren't as good as yours. I tested encoding 10 images in series (one after the other). Calling mx.graphics.codec.JPEGEncoder with a quality setting of 90 took 31.20 seconds. Using AsyncJPEGEncoder again with a quality of 90 took 50.64 seconds. Perhaps it works better on one large image vs several smaller images? Any way to speed it up?

  10. Ady Levy

    Hi!
    i'd like to merge your work into mine in my multiple file uploader (Multiple files uploader)

    but i don't see any license with your work,
    can i use your code with my "Attribution share alike" licensed code ?

  11. jb

    Hi Derrick,

    I am going thorugh various solution for this things, and I wonder if you got inspired from this code:

    http://www.switchonthecode.com/tutorials/flex-tutorial-an-asynchronous-jpeg-encoder

    Regards

    JB


Leave a comment






wrap code blocks in <code> </code> tags