This weekend I needed to encode a rather large image as a jpeg using flex. And you know what I found? It was slow. And cpu hungry. While this was not entirely unexpected, it was disappointing. I could have dealt with the program being slow and unresponsive during the time it took to encode the image, except for one problem - after about 15 seconds, the flex app would run up against the script timeout error. You know, the error that goes along the lines of "A script has executed for longer than the default timeout period of 15 seconds." And because of this, the jpeg encode never actually finished.
Well, that was no good. I poked a bit at extending the default timeout period, but really that was only a partial solution. Who knows how long it might take to encode the image on a slow computer? So I started poking at what it would take to make the jpeg encoding asynchronous. Sadly, actionscript and flex do not have the concept of just saying "hey go do this and get back to me when your done." This is probably because actionscript is single threaded - everything just runs on the browser's main thread.
Above we have a small sample app showing off the asynchronous jpeg encoder that we are going to build in this tutorial. You see the spinning lag meter? Right now, it is probably spinning nice and smooth. This is because flex has plenty of cpu cycles to update the spinning circle. When there isn't enough cpu time available, that animation will start to look choppy, as Flex will only be able to update it once every few hundred milliseconds, possibly longer. If there are no spare cycles, the circle will freeze and stop spinning altogether. If you hit "Normal Encode", that is probably exactly what will happen, and your entire browser will freeze along with it. That is because by clicking "normal encode", you told flex to encode the image displayed in the sample app (which is a 2800x2100 pixels) as a jpeg. If your computer is slow like mine, you will eventually get the "script timeout" error, and the encoding will never finish.
On the right hand side, however, is the button to trigger the asynchronous jpeg encoding. When you click that, the app won't freeze and the circle will keep spinning (although not quite as smoothly). You will also get a progress bar that shows the progress of the encoding. By moving the "pixels per iteration" slider bar to the right, you will increase the speed of the encoding, but decrease the responsiveness of the interface, and if you move the slider to the left, you get the opposite effect. A little farther down I will explain how that is accomplished.
So how do you even go about making something like this asynchronous? As I said above, we only have one thread to work with, so it is all hopeless, right? Not quite - there are ways to act like a multi-threaded system, such as using something like setTimeout. You can use setTimeout to emulate threading - essentially do a little work, but then you queue more work for the future (not immediately). The app then has a chance to breathe and deal with things like UI input during the pauses between work items.
It was here that I ran up against another issue. How was I going to break the act of encoding a large jpeg into manageable little pieces? Well, there is no way to do that from outside the jpeg encoding object, so I went into the Flex source code and found the jpeg encoder. And this is where the real meat of this article starts.
Below we have the problem loop in the original jpeg encoder:
{
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);
}
}
This loop can take a long time on large images - or at least on my computer it does. So the essential idea here is that we don't want to do this whole loop at once. We want to process the image a chunk at a time, giving the rest of the app time to work between chunks. So how do we do this? Well, we write a function that can process the loop a chunk at a time:
{
for(var i:int=0; i < PixelsPerIter; i++)
{
RGB2YUV(Source, xpos, ypos, SrcWidth, SrcHeight);
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);
xpos += 8;
if(xpos >= SrcWidth)
{
xpos = 0;
ypos += 8;
}
if(ypos >= SrcHeight)
{
setTimeout(FinishEncode, 10);
return;
}
}
setTimeout(AsyncLoop, 10, xpos, ypos);
}
This AsyncLoop function takes a xpos and ypos into the image, and starts processing from that point. But it only loops PixelsPerIter times. After it has processed that many chunks, it leaves the loop, and calls setTimeout on AsyncLoop. In this setTimeout call, it hands the current x and y position in the image - so when the function is called again, it starts up in the right place.
By setting a 10 millisecond timeout, the application will have time to do other things before it gets back to processing this image data. The PixelsPerIter value is very important - it determines how responsive the application is during the image processing. If PixelsPerIter is 1, the application will seem perfectly responsive. But it will also take a long time for a large image to encode, because the encoding is pausing for 10 milliseconds between event chunk. If you set the value to, say, 10000, the app will become unresponsive, because it is taking multiple seconds to process data before it responds to any UI input. Playing around, I've found that 128 is a pretty good value - the app slows down a little bit, and it takes under 2 minutes to encode a 2880x2880 jpeg.
So what do we do here when we are done processing the pixels? Well we do a setTimeout to FinishEncode, and return out of AsyncLoop. FinishEncode holds all the code that was later than the main loop in the original encode function:
{
//EOI
if (bytepos >= 0)
{
var fillbits:BitString = new BitString();
fillbits.len = bytepos + 1;
fillbits.val = (1 << (bytepos + 1)) - 1;
writeBits(fillbits);
}
writeWord(0xFFD9);
}
Ok, well, that makes sense, but how to we signal the rest of the application that the encoding has completed? Its not like we can just wait for a function call to return. Because the encoding is asynchronous, the original encode call will return immediately - long before the actual encoding is finished. So instead, we use an event. In this case, I created my own event, because I wanted the event object to hold the encoded image:
{
public static const JPEGASYNC_COMPLETE:String = "JPEGAsyncComplete";
public var ImageData:ByteArray;
public function JPEGAsyncCompleteEvent(data:ByteArray)
{
ImageData = data;
super(JPEGASYNC_COMPLETE);
}
}
To use this event on the new jpeg encoding class, we add an attribute at the top of the class, and make the class extend EventDispatcher:
public class JPEGAsyncEncoder extends EventDispatcher
{
...
And now to fire the event, we add a dispatchEvent call to the FinishEncode function:
{
//EOI
if (bytepos >= 0)
{
var fillbits:BitString = new BitString();
fillbits.len = bytepos + 1;
fillbits.val = (1 << (bytepos + 1)) - 1;
writeBits(fillbits);
}
writeWord(0xFFD9);
this.dispatchEvent(new JPEGAsyncCompleteEvent(byteout));
}
I also wanted to add a progress event, so that I could show a progress bar to the user as the jpeg was encoding. To do this, I added another event to the class:
[Event(name=ProgressEvent.PROGRESS, type="flash.events.ProgressEvent")]
public class JPEGAsyncEncoder extends EventDispatcher
{
...
And with this progress event, I added a bunch of logic to the AsyncLoop code to fire the progress event at appropriate points:
{
for(var i:int=0; i < ChunksPerIter; i++)
{
RGB2YUV(Source, xpos, ypos, SrcWidth, SrcHeight);
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);
xpos += 8;
if(xpos >= SrcWidth)
{
xpos = 0;
ypos += 8;
}
if(ypos >= SrcHeight)
{
setTimeout(FinishEncode, 10);
return;
}
CurrentTotalPos += 64;
if(CurrentTotalPos >= NextProgressAt)
{
this.dispatchEvent(new ProgressEvent(ProgressEvent.PROGRESS,
false, false, CurrentTotalPos, TotalSize));
NextProgressAt += PercentageInc;
}
}
setTimeout(AsyncLoop, 10, xpos, ypos);
}
That piece of code actually uses a bunch of fields that I added to the class to keep track of total progress, so that the progress event gets fired only about once a percent or so.
Those are pretty much all the changes that I needed to make (barring the couple added fields). Below you can see the entirety of what I did to the code. I put "...." where code from the original jpeg encoder class would be - there is a lot of it, so I didn't want to paste it all here:
[Event(name=ProgressEvent.PROGRESS, type="flash.events.ProgressEvent")]
public class JPEGAsyncEncoder extends EventDispatcher
{
......
private var DCY:Number = 0;
private var DCU:Number = 0;
private var DCV:Number = 0;
private var SrcWidth:int = 0;
private var SrcHeight:int = 0;
private var Source:Object = null;
private var TotalSize:int = 0;
private var PixelsPerIter:int = 128;
private var PercentageInc:int = 0;
private var NextProgressAt:int = 0;
private var CurrentTotalPos:int = 0;
private var Working:Boolean = false;
.....
public function set PixelsPerIteration(val:int):void
{ PixelsPerIter = val; }
public function get ImageData():ByteArray
{ return byteout; }
public function encodeByteArray(raw:ByteArray, width:int, height:int):Boolean
{ return internalEncode(raw, width, height); }
public function encode(image:BitmapData):Boolean
{ return internalEncode(image, image.width, image.height); }
private function internalEncode(newSource:Object, width:int, height:int):Boolean
{
if(Working)
return false;
Working = true;
Source = newSource;
SrcWidth = width;
SrcHeight = height;
TotalSize = width*height;
PercentageInc = TotalSize/100;
NextProgressAt = PercentageInc;
CurrentTotalPos = 0;
setTimeout(StartEncode, 10);
return true;
}
private function StartEncode():void
{
// Initialize bit writer
byteout = new ByteArray();
bytenew = 0;
bytepos = 7;
// Add JPEG headers
writeWord(0xFFD8); // SOI
writeAPP0();
writeDQT();
writeSOF0(SrcWidth, SrcHeight);
writeDHT();
writeSOS();
DCY = 0;
DCV = 0;
DCU = 0;
bytenew = 0;
bytepos = 7;
this.dispatchEvent(new ProgressEvent(ProgressEvent.PROGRESS,
false, false, 0, TotalSize));
setTimeout(AsyncLoop, 10, 0, 0);
}
private function AsyncLoop(xpos:int, ypos:int):void
{
for(var i:int=0; i < PixelsPerIter; i++)
{
RGB2YUV(Source, xpos, ypos, SrcWidth, SrcHeight);
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);
xpos += 8;
if(xpos >= SrcWidth)
{
xpos = 0;
ypos += 8;
}
if(ypos >= SrcHeight)
{
setTimeout(FinishEncode, 10);
return;
}
CurrentTotalPos += 64;
if(CurrentTotalPos >= NextProgressAt)
{
this.dispatchEvent(new ProgressEvent(ProgressEvent.PROGRESS,
false, false, CurrentTotalPos, TotalSize));
NextProgressAt += PercentageInc;
}
}
setTimeout(AsyncLoop, 10, xpos, ypos);
}
private function FinishEncode():void
{
//EOI
if (bytepos >= 0)
{
var fillbits:BitString = new BitString();
fillbits.len = bytepos + 1;
fillbits.val = (1 << (bytepos + 1)) - 1;
writeBits(fillbits);
}
writeWord(0xFFD9);
this.dispatchEvent(new ProgressEvent(ProgressEvent.PROGRESS,
false, false, TotalSize, TotalSize));
this.dispatchEvent(new JPEGAsyncCompleteEvent(byteout));
Working = false;
}
.......
}
The methods internalEncode, encode, and encodeByteArray replace methods in the original jpeg encoder class. Everything else shown above is an addition. I added all of those fields show at the top because unlike the original encoder (where almost everything was done in a single function call), I needed to remember things across function calls.
One thing to note, this encoder does not implement IImageEncoder like the original jpeg encoder does. This is because that interface expects the encode and encodeByteArray to return a byte array containing the encoded image. Obviously, since this class does all the encoding asynchronously, it can't return the byte array straight out of the original call, because the encoding hasn't actually been done yet. Instead, the functions return a boolean. If the call to encode returns false, it is because this instance of the class is already encoding an image - so it can't start encoding another one yet.
So we have this awesome asynchronous jpeg encoder class. How do we use it? Well, lets take a look at some sample code:
{
var encoder:JPEGAsyncEncoder = new JPEGAsyncEncoder(80);
encoder.PixelsPerIteration = 128;
encoder.addEventListener(JPEGAsyncCompleteEvent.JPEGASYNC_COMPLETE, encodeDone);
encoder.addEventListener(ProgressEvent.PROGRESS, encodeProg);
encoder.encode(imageBitmapData);
}
private function encodeProg(event:ProgressEvent):void
{
var percentage:String = ((event.bytesLoaded / event.bytesTotal)*100) + "%";
//Display the percentage somewhere
}
private function encodeDone(event:JPEGAsyncCompleteEvent):void
{
var data:ByteArray = event.ImageData;
//Do something with the encoded image
}
And there you go. Thats all you need to use this asynchronous jpeg encoder class. You can grab the source code for the whole example above here, and feel free to use it for whatever you want. If you have any questions or comments, feel free to leave them below.
01/16/2008 - 00:15
Indeed a gr8 way to work on images, but what about charts.
asyncEncoder.encode(Bitmap(img.content).bitmapData);for charts we cant get .content,
There we get a bitmap as
bd.draw(bar);
But this is not giving advantages of async encoding, Screen still hangs and timeout is reached. let me know if u have any solution/workaround
01/29/2008 - 13:44
Just want to say that this is an awesome tutorial!
03/10/2008 - 15:13
This article is great! This helped me solve a huge problem. I had to iterate through several thousand items in a datagrid and the operation was timing out. Not anymore! Thank you very much for posting this!
Thanks,
Blake Eaton
04/17/2008 - 12:13
Hi there:
I'm working on a project in Flash MX 2004 for a client and I'm having a problem that is similar to the one that you described in your tutorial. Unfortunately setTimeout is not supported in MX04 and setInterval requires quite a bit of tricky garbage collection. Do you have any recommendations for what I might be able to do in MX04 to faux-multithread? The application I'm working on is targeted for FlashPlayer 7.
05/17/2008 - 08:06
Hi,
Thank you for this! I was about to write something very similar when I ran across yours. I'm using it in the latest version of the CleVR Stitcher and would like to mention you in the credits. How would you like to be credited? Incidentally, the Stitcher uses the same method for fake threading, and it's an absolute nightmare to keep track of. We have literally dozens of different loops that are broken up like this! Oh how I wish there were an easier way.
Regards,
Matt
06/02/2008 - 07:06
Hello, thanks for this incredible class, i've been using in a personal project and i have a problem to free the memory that i use.
I use it to encode 6 images and in the process i put the byteArray in 6 global variables, so then i pass them to MySQL to store them. But i've notice with the profiling that the memory doesn't free after the process.
If you have any clue, please let me know, thanks very much in advance for your time.
Sory about my english, is not my native language.
Agustin
09/03/2008 - 04:24
I too have memory issues with the code when being executed on multiple images
How can I free the memory ?
09/03/2008 - 09:25
So you are running it multiple times in a row?
09/03/2008 - 09:34
This code resides in a function that is being called in a for loop multiple times (once for each image:)
bd.draw(tmploader[i]);
var encoder = new JPEGAsyncEncoder();
encoder.PixelsPerIteration = 128;
encoder.addEventListener(
JPEGAsyncCompleteEvent.JPEGASYNC_COMPLETE,
function(e){
encodeDone(e, lastid, propid);
encoder.removeEventListener(
JPEGAsyncCompleteEvent.JPEGASYNC_COMPLETE,
arguments.callee
);
bd.dispose();
tmploader[i].unload();
}
);
encoder.encode(bd);
tmploader is simply a loader (air.loader()). I am using Javascript/HTML by the way for my lack of knowledge in FLEX, and its lack of support for my language.
Thanks in advance for your help.
09/03/2008 - 13:15
My first attempt at this would be to try a call to System.gc() after the dispose call. Now this is definitely going to slow things down but it is a start in the right direction I think. So something like:
System.gc();
Let me know how that works out.
09/03/2008 - 14:40
Thank you very much for the fast response.
Adding gc() helped reduce the leakage a lot. Memory used to increase by 100MB when 20 images are processed (each 256KB). Now, it processes at least 50 before it increases to the same amount.
On a side note, I get a vague message sometimes (SyntaxError: Syntax error) in a window titled ActionScript. Since my whole application is in HTML/Javascript, I presume this is caused by the JPEGAsycEncoder ??
09/03/2008 - 18:58
Is that all the information it gives? I don't know of any errors it would throw but it would be something wonky with the combination of AIR and JPEGAsyncEncoder.
09/02/2008 - 00:16
good article, good idea to overcome single threaded structure of flash.
for these kind of problems, there is a function named
callLater,
which calls the function after the screen is drawn.
So instead of using a timer, you could use callLater which would be more consistent&efficient.
09/14/2008 - 07:18
I have noticed that the quality of the images produced by the JPEGAsynencoder does not match that of other encoders available (i.e. php's built in image compression functions from the gd library)
Any explanation ? or hints/workarounds for improving the quality of compressed images by JPEGAsyncEncoder ??
Please have a look at the following samples:
original image:
http://img212.imageshack.us/img212/5930/67624460qh5.jpg
Size: 110KB
Image encoded with JPEGAsync:
http://img136.imageshack.us/img136/3554/77570198qp0.jpg
Size:46.7KB
Image encoded with Php's built-in functions (gd library):
http://img382.imageshack.us/img382/7751/1492if8.jpg
Size:33.4KB
Much smoother than the JPEGAsync version.. and smaller too
09/14/2008 - 07:20
In the images I posted in my previous comment,, Please pay attention to the white board in the picture. In one image ,, the word "hotmail" can be easily read,, while this is not the case in the other.
I tried increasing the quality measure to 80 and 100 (as an argument passed to JPEGAsyncEncoder) and it only resulted in increasing the file size.. The picture did not get any smoother..
09/14/2008 - 09:31
Hmm, yeah, that is a big quality difference.
In any case, I don't actually know how the code behind the JPEG encoder works - when I wrote this asyc encoder, I just took the bulk of the code from Flex. You might want to try encoding that image with the regular Flex JPEG encoder, and see if it has the same issues - I bet it does.
09/14/2008 - 09:47
May I ask how you are creating the encoded image and saving it using the JPEGAsyncEncoder? I am quite curious because I haven't noticed any quality issues especially like the ones you have.
09/14/2008 - 11:37
Thanks again for the fast response..
Below is the whole code I have :
Basically, fileList is an array storing several files (all images). They are collected by a drag and drop function.
fileList[fileList.length]=
event.dataTransfer.getData(
"application/x-vnd.adobe.air.file-list");
Then,, I have:
//new file stream
var fileStream= new air.FileStream();
fileStream.open(imageFile, air.FileMode.READ);
//reading image into bytearray and closing stream
var imgBytes = new air.ByteArray();
fileStream.readBytes(imgBytes);
fileStream.close();
//creating loader and injecting image bytes into it
tmploader[i]= new air.Loader();
// events:
tmploader[i].contentLoaderInfo.addEventListener(
air.Event.COMPLETE, function(e){
saveTheFile(e,i,propid);});
// loading the bites
tmploader[i].loadBytes( imgBytes, air.loaderContext );
Inside saveTheFile,, I have:
var bd =
new air.BitmapData(600,tmploader[i].content.height);
bd.draw(tmploader[i]);
var encoder = new JPEGAsyncEncoder(80);
encoder.PixelsPerIteration = 128;
encoder.addEventListener(
JPEGAsyncCompleteEvent.JPEGASYNC_COMPLETE,
function(e){
encodeDone(e, lastid, propid);
bd.dispose();
air.System.gc();
tmploader[i].unload();
});
encoder.encode(bd);
}
This is the last bit that writes the images after being encoded
var myFileStream = new air.FileStream();
myFileStream.openAsync(myFile, air.FileMode.WRITE);
myFileStream.writeBytes(e.ImageData);
myFileStream.close();
}
09/14/2008 - 12:32
ok, I missed these two lines,,
(600 / tmploader[i].content.width) *
tmploader[i].content.height ;
tmploader[i].content.width = 600;
they are right above the line:
var bd = new air.BitmapData(600,tmploader[i].content.height);10/19/2008 - 08:46
It looks to me that you are resizing the image before you encode it (the lines where you set the height and width). I'm not sure what algorithm is being used underneath the covers to do the resize, but I bet it is that algorithm that is causing the artifacts you see in the encoded image.
12/30/2008 - 05:15
‘Imagesmoothing’ is disabled by default… You can enable it by using the parameters when calling the ‘draw’ function.
Example:
bmpData.draw(IMAGE, null, null, null, null, true);
Thanks for the AsyncJPEGEncoder! Works like a charm. Using it to encode multiple images in a loop as well.
12/09/2008 - 08:40
Nice, thank you.
But what on earth is “a bunch of logic” ?!?
12/31/2008 - 11:55
What would it take to get you to write a tutorial showing how to integrate this with AlivePDF? I’ve tried unsuccessfully all day long and need some expert help. Pleeeeeeeease? :D
01/05/2009 - 02:59
Hey,
Any idea if jpeb compression works with better speed in FP10?
01/05/2009 - 14:29
I am trying to use this to process a bunch of images, and I’m noticing that a bunch of BitString objects are hanging around and don’t seem to be garbage collected.
If I do a single image (3888×2592 which has been resized to 150×100), there are 65882 instances left in memory that don’t seem to get garbage collected.
If I do multiple images, then there are bigger issues, as each image causes about the same number of instances left in memory.
01/08/2009 - 23:07
Has anyone found a fix for the memory leak in this Class? I love this Encoder and am using it in a project but after encoding multiple images the memory keeps building up… I currently running it in debug and trying to figure it out where the leak is. If I find anything I will post it here.
01/08/2009 - 23:19
I used the term Memory Leak in my last post. What I really mean is the loiterring objects in memory. The fact that after you are done encoding a JPEG the memory usage doesn’t go back down.
01/09/2009 - 07:14
Do you know if the memory issue is just for this async version of the jpeg encoder, or is it also a problem with the regular Flex one?
02/27/2009 - 15:50
Maybe the memory issues are related to the setTimeout() call. Usually a corresponding clearTimeout() should be called. From the docs:
"If you intend to use the clearTimeout() method to cancel the setTimeout() call, be sure to assign the setTimeout() call to a variable (which the clearTimeout() function will later reference). If you do not call the clearTimeout() function to cancel the setTimeout() call, the object containing the set timeout closure function will not be garbage collected."
02/27/2009 - 22:19
Hmm, that's interesting. From what I understood, you only needed to call clearTimeout if you wanted to cancel the pending call. I'll have to run some tests at some point and find out.
01/13/2009 - 08:45
Thanks for the nice and detailed tutorial. Hats off..! :-)
02/14/2009 - 21:53
Just wanted to say thanks for a really brilliant solution to a very annoying problem. The code works like a charm (no modification required on my part). I wanted an easy way for my wife to upload images to our family site via an Adobe AIR application that auto resizes the images on drop, but was having trouble with the image loading using the JPEGEncoder (as it was freezing up the app until it was completed). Your class fixed the problem and even allowed me to add progress bars to each image load. Love it! Thanks again.
03/25/2009 - 09:03
Downloading the code...
Great work! Thank you veeeeeeeery much.
04/01/2009 - 08:49
Brilliant work man...
04/01/2009 - 19:44
Good stuff :)
http://www.actionscriptdeveloper.co.uk
04/28/2009 - 22:03
I'm also having the memory problems Dennis noted when running a batch of images. The bitstrings seem to pile up in the memory profiler, and loiter around.
The virtual memory only my system keeps increasing while this is running until eventually the app crashes.
As for Seme1's comment about the image quality problems...I also had the same problem when I downsized the bitmaps before encoding. The image smoothing noted by "2am" didn't work to fix this. I also tried using a special biLinear and BiCubic sampling class, which also didn't work.
The fix for this was to apply a blur filter BEFORE the bitmap is resized. The amount of blur must be adjusted according to the initial image size.
Any help with the loitering bitstrings would be a big help.
Thanks all and thanks again for this extremely useful class.
04/29/2009 - 04:45
Using this very usefull class since Flash 9 I run into one major problem now, using Flash 10: Security policy of Flash 10 requires real user action (click or key press) to upload the async encoded jpg.
Calling upload function inside of "function encodeDone()" doesn't really upload the encoded *.jpg anymore now, because calling that from complete handler isn't the required first call of upload function...
... and because it isn't really a result of an user action.
May be I misunderstand some here... Are there any notes, or expiriences about that available? Some helpfull hints to this problem?
Anyway, many thanks for JPEGAsyncEncoder...
--
Regards from Germany
Endur Unixman
04/29/2009 - 10:37
Have fixed that by my self using application/octet-stream instead of multipart/form-data. Had to change our server sided perl too to match this.
--
Thanks
Endur
05/09/2009 - 08:39
Hi,
and thanks for this class.
It works great but, when batching/resizing a lot of images (with a resize external class), I experience memory leaks that in the end cause the application to crash. I tried all the (very few) garbage collection techniques I know, but as of know memory can easily bypass my 2Gb of RAM simply resizing 10 digital camera photos to 320x240,640x480 and encoding them.
Any ideas?
Thanks in advance,
Mark
05/09/2009 - 08:50
SOLVED. I was not doing the bd.dispose() method. Thanks,
Mark
05/19/2009 - 03:40
i just want to set the record straight on my earlier comment.
after reading all over about the memory leaks in flashplayer, and my relative inexperience, I freaked out and was convinced something was wrong in the the jpeg encoding itself, that little bits of memory were being used each time encoding.
In fact, the problem wasn't with the async encoder...it was actually a problem with some of the code I had lifted for grabbing a list of files from the users filesystem into flash.
It seems the fileReferenceList object was storing not only the information to reference the file (like the file name and location) but it was also storing the *entire contents of the file* as it was uploaded into flash. This property is read only so there was way to delete it.
The odd thing is that I wasn't able to see this happening in profiler...it wasn't registering as a big user of memory, and I think this is because the data is somehow cached by the browser.
Regardless, I just wanted to write that the jpeg async encoder was NOT the source of my woes. My app can now upload and resize 1000's of 21 megapixel images without a hitch.
Thanks again for this tip!
(Now if anyone knows how to perserve the exif data...)
09/16/2010 - 15:41
Hi, how did you fix your issue with the filereference ?
thanks
07/23/2009 - 16:54
Could you explain why you are doing this , thanks.
07/23/2009 - 16:57
Yeah we originally wrote this code because we found encoding a large image (10000 x 7500) was taking well forever and would actually lock up the browser. This allows the browser and the rest of the flex application to update itself and allow for user interaction to happen while encoding. Not to mention we can show a progress bar during the encoding.
08/29/2009 - 02:28
hi all, i want to capture a application screenshot and i tried to encode with the asynchronous encoder . i had taken the screenshot with Timer control (400 ms ) . still it has some hangover(maybe memory leakage ). kindly help me out
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" creationComplete="init()" implements="com.igrandee.threads.IRunnable" xmlns:iframe="com.igrandee.iframe.*" horizontalAlign="center" verticalAlign="middle" xmlns:local="*">
<mx:Script>
<![CDATA[
import mx.controls.Alert;
import mx.rpc.events.FaultEvent;
import mx.core.UIComponent;
import mx.rpc.events.ResultEvent;
import mx.graphics.codec.JPEGEncoder;
import flash.errors.ScriptTimeoutError;
import com.pfp.events.JPEGAsyncCompleteEvent;
import com.pfp.utils.JPEGAsyncEncoder;
private var asyncEncoder:JPEGAsyncEncoder;
private var file:FileReference = new FileReference();
private var bd:BitmapData;
private function init():void{
var myTimer:Timer=new Timer(500);
myTimer.addEventListener("timer", timerHandler);
myTimer.start();
}
public function timerHandler(event:Event):void {
takeScreenshot();
}
private function takeScreenshot():void
{
trace(' take snapshot ');
var snapshotHolder:UIComponent = new UIComponent();
bd = new BitmapData(stage.width,stage.height,true);
bd.draw(stage);
trace('drawing image new encoder ');
asyncEncoder = new JPEGAsyncEncoder(90);
asyncEncoder.PixelsPerIteration = 128;
// asyncEncoder.addEventListener(ProgressEvent.PROGRESS, updateProgress);
asyncEncoder.addEventListener(JPEGAsyncCompleteEvent.JPEGASYNC_COMPLETE, asyncComplete);
asyncEncoder.encode(bd);
}
private function asyncComplete(event:JPEGAsyncCompleteEvent):void
{
var data:ByteArray = event.ImageData;
trace('asyncComplete '+data.length);
bd.dispose();
System.gc();
}
private function playVideo():void
{
vdodis.play();
}
]]>
</mx:Script>
<mx:Canvas width="100%" height="100%" id="canvas22" y="0">
<!-- <mx:VideoDisplay id="vdodis" width="299" height="362" x="10" y="60" source="phone.flv" autoPlay="true" autoRewind="true" />-->
<mx:VideoDisplay id="vdodis" width="299" height="362" x="10" y="60" source="phone.flv" autoPlay="true" />
<mx:SWFLoader width="252" height="362" id="swflod"
x="317" y="60" source="top10ReasonstoBuyFP.swf" />
<mx:Button x="76" y="467" label="play" click="playVideo()"/>
<!-- <mx:Image id="imagesize" width="371" height="337" source="kanna.jpg"/> -->
</mx:Canvas>
</mx:Application>
02/24/2010 - 01:39
hi boss why you posted with your company information.. they are going to kick off you...
11/16/2009 - 11:38
awesome!! thanks! :)
05/03/2010 - 20:58
Asynchronicity is nice, but the encoding time still seems very slow versus something like 'Shrink-O-Matic' or 'ImageSizer'. Anyone know what those apps are doing differently?
05/04/2010 - 15:59
I am not really sure what is different. They certainly could be using their own encoding. Also, have you tried running this code in AIR? I don't know if that makes a performance difference or not.
08/20/2010 - 08:17
Had problems posting a long "ThankYou" post (hashcash issue O_o), so here's the short one:
You Guys are Awsome!
08/20/2010 - 11:36
Thanks for this solution, however if you want to save the encoded jpg with FileReference.save it won't work, as FP10 throws a security exception if you call fr.save from anywhere except a onClick function. So I can't save my jpg after encoding it using your asynchronous encoder, since I cannot call the fr.save until the encoder is complete and there is no way to know it's complete except by the complete event it throws. Any idea how to get around this?
08/20/2010 - 11:50
I would just listen to the complete event and then enable a download button. You can show a progress bar so the user knows that something is happening. You could even be a little bit more intrusive by popping up a window saying it's encoding is complete and they can download it now.
08/20/2010 - 12:41
Yes, it looks like having two buttons for the user to click is the way to go. A "prepare download" button to start the encoding, and then a save button can appear when it's ready to be saved.
09/16/2010 - 12:54
Hi,
is the "missing" cleartimeout was an issue after all ?
11/09/2010 - 13:01
Wow...What a tutorial..great. Thanks for sharing the knowledge.
07/11/2011 - 08:00
hi,
i am suing this code to generate multiple screen shots so i have to manage all completed encodings is there any way to manage at once....
thanks
09/29/2011 - 11:20
This is simply amazing, many thanks for such a great tool.
10/02/2011 - 11:25
Great tutorial, thanks!
How to abort the enocding progress after it started? Can you help me with that?
Sarah
10/04/2011 - 17:44
Superb tutorial, very clever! Thank you & well done :D
01/10/2012 - 07:58
Hey Great tutorial!!
But I am not getting the same quality as I get from Synchronous JPEG encoder.
My Application is taking snapshot of movie clip and pasting into pdf using alive pdf
When I use
ImageSnapshot.captureImage(source,250,new JPEGEncoder(),true)
it gives me better quality campare to
imageSnap:BitmapData = ImageSnapshot.captureBitmapData(source)
and pass this bitmap data in asnycronous JPEG encoder
I guess due to conversion of movie clip into bitmap, quality of image degrades.
AnyBody knows how can i get the same quality as i got frm Synchronous JPEG encoder
Add Comment
[language] [/language]
Examples:
[javascript] [/javascript]
[actionscript] [/actionscript]
[csharp] [/csharp]
See here for supported languages.
Javascript must be enabled to submit anonymous comments - or you can login.