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.
Derrick Grigg is a Rich Internet Application (RIA) freelance contractor based in Toronto, Canada. He specializes in architecting and developing applications using a variety of technologies, most notably Flash, Flex and Coldfusion.
Mar 06, 2009 at 10:00 AM
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...
Apr 09, 2009 at 7:05 PM
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.
Apr 09, 2009 at 7:14 PM
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
Apr 29, 2009 at 10:09 AM
Has anyone answered Ryan im stuck with the same problem too. any help would be much appreciated
May 04, 2009 at 2:54 AM
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 :)
May 04, 2009 at 8:32 AM
Hey all, here is a sample of how I have used it in an AIR application.
Hope that helps.
Derrick
May 06, 2009 at 2:34 PM
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 ;)
May 14, 2009 at 3:00 AM
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.
May 27, 2009 at 3:10 PM
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?
Sep 28, 2009 at 12:36 PM
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 ?
Feb 02, 2010 at 6:53 AM
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