Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to dynamically load a progressive jpeg/jpg in ActionScript 3 using Flash and know it's width/height before it is fully loaded

I am trying to dynamically load a progressive jpeg using ActionScript 3. To do so, I have created a class called Progressiveloader that creates a URLStream and uses it to streamload the progressive jpeg bytes into a byteArray. Every time the byteArray grows, I use a Loader to loadBytes the byteArray. This works, to some extent, because if I addChild the Loader, I am able to see the jpeg as it is streamed, but I am unable to access the Loader's content and most importantly, I can not change the width and height of the Loader.

After a lot of testing, I seem to have figured out the cause of the problem is that until the Loader has completely loaded the jpg, meaning until he actually sees the end byte of the jpg, he does not know the width and height and he does not create a content DisplayObject to be associated with the Loader's content.

My question is, would there be a way to actually know the width and height of the jpeg before it is loaded?

P.S.: I would believe this would be possible, because of the nature of a progressive jpeg, it is loaded to it's full size, but with less detail, so size should be known. Even when loading a normal jpeg in this way, the size is seen on screen, except the pixels which are not loaded yet are showing as gray.

Thank You.

like image 976
Didier A. Avatar asked Nov 29 '09 06:11

Didier A.


2 Answers

I chose Cay's answer, but I'll also answer my own question to add some clarification and inform everyone else of what I went through, did wrong and eventually did right, just in case someone else stumbles upon my question.

If you want to progressively load a jpeg, you need two things: a URLStream and a Loader. Then you need to follow these steps:

1) You must use a URLStream to load the jpeg from a URLRequest into a ByteArray.

2) You need to add a PROGRESS event handler to the URLStream. In that handler, you need to use a Loader to loadBytes() the newly loaded bytes of the URLStream.

3) You need a Loader COMPLETE event handler to acces every pass of the loaded jpeg and do whatever you want on it, like display it or resize it, etc.

4) You need a URLStream COMPLETE event handler to make sure all bytes have been loaded and to clean after yourself and close the stream.

var urlStream:URLStream = new URLStream(); //We will use this to progressively stream the bytes of the jpeg
var byteArray:ByteArray = new ByteArray(); //Bytes from the URLStream will go here
var loader:Loader = new Loader(); //We will use this Loader to load the bytes from the ByteArray
var url:String = "http://myAddressToMyJpeg.jpg"; //The url to the jpeg

urlStream.load(new URLRequest(url));

urlStream.addEventListener(ProgressEvent.PROGRESS, onStreamProgress, false, 0, true);
urlStream.addEventListener(Event.COMPLETE, onStreamComplete, false, 0, true);
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoaderComplete, false, 0, true);

function onStreamProgress(evt:ProgressEvent):void
{
    // You could put a condition here to restrain the number of calls to updateBytes().
    // Use the evt.bytesTotal and evt.bytesLoaded to help accomplish this.
    // You will find that by limiting it, it will increase responssivness of your
    // program and give an overall better result.
    // Have it call updateBytes() every 100 bytes or so.
    updateBytes();
}

function onStreamComplete(evt:Event):void
{
    updateBytes(); // Call updateBytes one more time to load in the last bytes.

    urlStream.removeEventListener(ProgressEvent.PROGRESS, onStreamProgress); // Clean after yourself
    urlStream.removeEventListener(Event.COMPLETE, onStreamComplete); // Clean after yourself

    // Somehow, without this, it does not work. You will end up with a ~90% loaded image
    setTimeout(confirmBytesLoaded,500); // Would be nice if someone could tell me why this makes it work!!
}

function confirmBytesLoaded():void
{
    updateBytes(); // As said earlier, you need to check it one last time it seems.
    if (urlStream.connected) urlStream.close(); // Close the stream once you're done with it.
}

function updateBytes():void
{
    // Important step. We copy the bytes from the stream into our byteArray,
    // but we only want to add the new bytes to our byteArray, so we use the lenght
    // attribute as an offset so that the new bytes gets added after the bytes that we added before.
    urlStream.readBytes(byteArray, byteArray.length);

    if(byteArray.length > 0) // Make sure there are new bytes to load.
    {
        loader.loadBytes(byteArray); // Use the Loader to decode the loaded bytes.
    }
}

// onLoaderComplete will be called many times.
// Every time there is enough new bytes to diplay more of the image
// onLoaderComplete will be called. So for every pass of the jpeg one
// this will be called.
function onLoaderComplete(evt:Event):void
{   
    // bm will now contain the bitmapData of the progressively loaded jpeg.
    var bm:Bitmap = Bitmap(loader); // We make a bitmap object from the loader.

    bm.width = 400; // Because my goal was to be able to resize the image as it is loaded and display it :).
    bm.height = 400; // Because my goal was to be able to resize the image as it is loaded and display it :).
    addChild(bm); // See the result for yourself...
}

Some notes on the whole process:

1) The confirmBytesLoaded is your queue to know when the image has been fully loaded.

2) The Loader will not dispatch a complete event if the bytes it was given does not allow to display more of the image. Therefore, the Loader progress event is not required unless you want to know the progress of the loading of every pass of the jpeg.

3) In onLoaderComplete you can do whatever you want. At that point, the event gives you a full image to work with . You can access the loader.content attribute. Remeber that if is not the last Loader complete event, it means it is CustomActions partially loaded image that you will have, so either in lower defenition or with some gray pixels in it.

4) When you use loadBytes, it loads the image in your application context. So make sure you only load trusted content this way. I'm not yet sure if there is a way around this, to make it secure. See: http://onflash.org/ted/2008/01/loaderload-vs-loaderloadbytes.php

P.S: Here is the link to where most of my code comes from:

http://orangeflash.eu/?p=13

Here are some links that actually show you a way to read the width and height yourself by parsing every byte as their are loaded using the jped specification:

http://www.anttikupila.com/flash/getting-jpg-dimensions-with-as3-without-loading-the-entire-file/

http://www.emstris.com/2009/05/extracting-binary-info/

http://blog.onebyonedesign.com/?p=71

like image 70
Didier A. Avatar answered Nov 15 '22 05:11

Didier A.


I think your problem is that loadBytes is asyncronous... that means that you need to wait until the "complete" event before you retrieve (or change) its width and height... I mean:

var loader:Loader=new Loader();
loader.loadBytes(myBytes);
trace(loader.width, loader.contentLoaderInfo.width); //will always output zero
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, imgLoaded);
function imgLoaded(e) {
   trace(loader.width, loader.height); //will output the loader dimensions
   trace(loader.contentLoaderInfo.width, loader.contentLoaderInfo.height); //will always output the original JPEG dimensions
}
like image 28
Cay Avatar answered Nov 15 '22 05:11

Cay