Best way to preload images in a swf

Hey experts!

I'm doing a personal project involving the flexbook component (created by Ely Greenfield), and the images I'm gonna put in the book are gonna fill the entire pages.

The average dimensions of the pictures are 500x768 and approximately 300k each.

Right now, when I open the swf generated in my page, the images load just once I turn a page. This create a little lag between the moment page is turned and when the image finish loading.

The way I coded it, I load the images from a different folder so they aren't embed directly in the swf. This way the user doesn't have to load a big file to see the images, and if he doesn't want to check every page, I simply don't load them.

I thought this was a good solution (except for the lag) until I came accross the website the company Zinio, which seems to specialize in online magazines. I preview the digital issue and it took only like 10seconds to load the entire magazine!

Anyone has an idea how it's made, if their application was created using Flex or Flash, and if there's something better then my current solution?

Thanks a lot for your help. Have a nice day.
Frank

zinio
The_Kingpin08Asked:
Who is Participating?
 
julianopolitoConnect With a Mentor Commented:
Here it is. I would give you a sample of urlloader, but a flexbook sample would be better.
Also take a look at this flexbook sample where there is a custom imagepage component.

http://demo.quietlyscheming.com/book/FullPage.html

My sample is simpler, but it is ok for you to learn, as it has my comments.

For this sample to work you need to :
-Create a flex project
-create a mxml application
-copy and paste code below in the app mxml
-create a "assets" folder in the project
-put into the assets folder 12 images. 6 high res, named p1.jp to p6.jpg and 6 low res, named p1_low.jpg to p6_low.jpg.

Take a loo at the comments in the code. I think I'm clear enough. This is a working example, but not too complex. You can implement some solution your own. Here I just load very low res images as proxies for the pages. When user turns page, I load the high res version.

You could create a component to be the page, and then control if the high res image is already loaded , what to do, or to show loading text, or even animate when high res load complete.

sky is the limit. ;)

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" 
	layout="vertical" 
	xmlns:qs="qs.controls.*" xmlns:local="*">
	
	<mx:Script>
		<![CDATA[
			import mx.controls.Image;
			import qs.controls.flexBookClasses.FlexBookEvent;
			import qs.controls.flexBookClasses.FlexBookPage;
			private function loadContent(event:FlexBookEvent):void{
				//this event is dispatched by each page that appears after turn	
				//in this case the event.renderer is the mx:Image	
				var page:Image = Image(event.renderer);
				//Check to see it is not null
				if(Boolean(page)){
					//change its source to the high res version
					page.source = String(page.source).replace("_low","");
					//you could also use a custom component and then have more control
					//over what appears on the page, like showing a loading text or animating 
					//high res version over low res					
				}
			}
		]]>
	</mx:Script>
	<!-- Here we have the images to be loaded dynamically -->
	<mx:XMLList id="pages" xmlns="">
		<images>
			<image high="assets/p1.jpg" low="assets/p1_low.jpg" />
			<image high="assets/p2.jpg" low="assets/p2_low.jpg" />
			<image high="assets/p3.jpg" low="assets/p3_low.jpg" />
			<image high="assets/p4.jpg" low="assets/p4_low.jpg" />
			<image high="assets/p5.jpg" low="assets/p5_low.jpg" />
			<image high="assets/p6.jpg" low="assets/p6_low.jpg" />
		</images>
	</mx:XMLList>
	<!-- Here is the FlexBook component
	take a look at the turnEnd event. it happens after each page is turned
	also there is the content property that receives the xmllist 
	Here my item renderer is custom, so it renders the low res version first-->
	<qs:FlexBook id="book" y="47" width="600" top="40" height="400" horizontalCenter="0"
		animateCurrentPageIndex="true"
		showCornerTease="true"
		edgeAndCornerSize="150"
		itemSize="halfPage"
		turnEnd="loadContent(event)"
		content="{pages.image}">
		<qs:itemRenderer>
			<mx:Component>
				<mx:Image source="{data.@low}" />
			</mx:Component>
		</qs:itemRenderer>
	</qs:FlexBook>
</mx:Application>

Open in new window

0
 
julianopolitoCommented:
You could try start the preloading process right after the first page is loaded. You could sequence the preload process, using a urlloader. Each time the urlloader completes, start downloading next image. Also you could code something to start downloading the image from the page user types in the page box as he types.

This way, while the user sees 1st page, you already downloaded page 2, and so on. URLLoader gets images to the browser cache, so when user asks for a page, the image will load instantly. As you said you are Guru on subject, I'm only telling you the options I think are best, but if you need example just tell.
0
 
Siva Prasanna KumarPrincipal Solutions ArchitectCommented:
I some how feel embedding every image is a better option than loading them dynamically because when you embed every thing I understand  that over all SWF size grows and take a while to load initially but once loaded this run very very smoothly. instead of making the user wait for loading every image.

0
Keep up with what's happening at Experts Exchange!

Sign up to receive Decoded, a new monthly digest with product updates, feature release info, continuing education opportunities, and more.

 
julianopolitoCommented:
Embedding every image would require that every magazine would be compiled individualy , which I think is not an option. I took a look at the zinio magazine you've mencioned. I have never used the flexbook component , but zinio's component first loads a proxy very low resolution image, and then loads the full res image. If flexbook does not have that option of using proxy images, you could try implementing it. The first way I'd do that would be try to tell flexbook to load proxies, and after complete , change the image to full. If you cannot, try extending the flexbook class. But in zinio's there is no magic, just what I'm saying.
0
 
The_Kingpin08Author Commented:
julianopolito, I really like your solution with the URLLoader  function and I would really appreciate if you had an example!

By the way I made a mistake with the degree of knowledge, I am pretty much beginner with Flex =S

Thanks a lot.
Frank
0
 
julianopolitoCommented:
Ok, I'll make an example for you. I'll do the preloading based on an array with image paths, but you should use your own data source, like xml or so, the same you use to give image paths to the flexbook component.
0
 
julianopolitoCommented:
I was taking a look at  the flexbook component. It has a change event. You could use that to reload the pages without proxies. I'll try to make a working example with flexbook.
0
 
julianopolitoCommented:
getting there. I think you'll like it.
0
 
julianopolitoCommented:
Note: Also, in the flexbook sample
http://demo.quietlyscheming.com/book/FullPage.html

Take a look at the use of ContentCache class. It does what I would do with URLLoader, but easier. So you can even preload the thumb images images. So for the code above you could call the method below in the onCreationComplete event of the app:

private function initApp():void{
      var thumbs:XMLList = pages.@low;//gets low images list
      for(var i:int =0;i<thumbs.length();i++){
            ContentCache.getCache().preloadContent(thumbs[i]);//preload each
      }
}
0
 
The_Kingpin08Author Commented:
Wow, thanks a lot for this great example!

I got some question regarding it if you don't mind.

I'm trying to understand this part: source="{data.@low}"
Does it mean that the application will load the images that have a filename with "low" in it?

Also, in my project,  I'm using the flexbook with the landscape zoom (http://demo.quietlyscheming.com/book/DataDriven.html) and I thought I had put everything correctly, but I have an error here:

<mx:XMLList id="pages" xmlns="">
Error: " <mx:XMLList> not currently supported as Array element"

After I manage to use this example in my project, I'm definately gonna try to use the initApp function you talked about.

Thank you so much for the help so far!
Frank
<?xml version="1.0" encoding="utf-8"?>
<mx:Application 
	xmlns:mx="http://www.adobe.com/2006/mxml" 
    layout="absolute" 
    xmlns:local="*" 
    xmlns:qs="qs.controls.*" 
    xmlns:containers="qs.containers.*">
<!--
Copyright (c) 2006 Adobe Systems Incorporated
 
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
 
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
 
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
-->	
 
<mx:Style source="core.css" />
	
	<mx:Script>
		<![CDATA[
			import mx.controls.Image;
			import mx.controls.Alert;
           	import qs.controls.flexBookClasses.FlexBookEvent;
            import qs.controls.flexBookClasses.FlexBookPage;
			
			/* Turn to the next page */
			private function next():void
			{
				if(book.currentPageIndex + 1 < book.pageCount)
					book.turnToPage(book.currentPageIndex + 1);
					/* Display the index of the current page to the user */
					pageOf.text = "Page " + ((book.currentPageIndex*2)+1) + "-" + ((book.currentPageIndex*2)+2) + " / " + (book.pageCount*2);
			}
			
			/* Turn to the previous page */
			private function previous():void
			{
				if(book.currentPageIndex > 0)
					book.turnToPage(book.currentPageIndex - 1);
				else
					book.turnToPage(-1);
					/* Display the index of the current page to the user */
					pageOf.text = "Page " + ((book.currentPageIndex*2)+1) + "-" + ((book.currentPageIndex*2)+2) + " / " + (book.pageCount*2);
			}
		
			/* Jump to a specific page entered by the user */
			private function goToPage():void
			{				
				if(int(pageNum.text) <= ((book.pageCount*2)))
				{
					if(int(pageNum.text) <= 1)
					{	
						pageOf.text = "Page 1 / " + (book.pageCount*2);
						book.turnToPage(-1);
					}
					else
					{
						book.turnToPage((int(pageNum.text)/2)-1);
						pageOf.text = "Page " + ((book.currentPageIndex*2)+2) + "-" + ((book.currentPageIndex*2)+3) + " / " + ((book.pageCount*2)+1); 
					}
				}	
				else
					Alert.show("Sorry, the last page is " + ((book.pageCount * 2) + 1));
			}
			
			/* Whenever we click on a page (image), it gets the focus so we can zoom in */
			public  function focusOn(target:*):void
			{		
				if(landscape.selection.length == 1 && landscape.selection[0] == target)
				{landscape.selection = [];landscape.height=100;}
				else
				{landscape.selection = [target];landscape.height=10;}					
			}
			
			private function loadContent(event:FlexBookEvent):void
			{
           	//this event is dispatched by each page that appears after turn 
           	//in this case the event.renderer is the mx:Image       
           	var page:Image = Image(event.renderer);
            
            //Check to see it is not null
            if(Boolean(page)){
            	
            	//change its source to the high res version
                page.source = String(page.source).replace("_low","");
               	//you could also use a custom component and then have more control
                //over what appears on the page, like showing a loading text or animating 
               	//high res version over low res                                 
            }
            }
		]]>
	</mx:Script>
        
	<mx:HBox styleName="topBar" width="100%" backgroundImage="@Embed('assets/topBarBG.gif')" backgroundSize="100%" horizontalAlign="center" top="0" >
		<mx:Button fillColors="[#d0d0d0, #ffffff]" fillAlphas="[1.0, 1.0]" label="Précédent" click="previous()" bottom="10" top="10" />
		<mx:Button fillColors="[#d0d0d0, #ffffff]" fillAlphas="[1.0, 1.0]" label="Suivant" click="next()" bottom="10" top="10" />
		<mx:Button fillColors="[#d0d0d0, #ffffff]" fillAlphas="[1.0, 1.0]" label="Agrandir" click="focusOn(event.currentTarget)" bottom="10" top="10" />
		<mx:Button fillColors="[#d0d0d0, #ffffff]" fillAlphas="[1.0, 1.0]" label="Aller à la page" click="goToPage()" bottom="10" top="10" />
		<mx:TextInput id="pageNum" color="#000000" width="40" />	
	</mx:HBox>
	
	<mx:HBox width="100%" styleName="currentPageIndex" top="40" horizontalAlign="center">
		<mx:Text id="pageOf" width="200" horizontalCenter="0" />
	</mx:HBox>
	
	<containers:Landscape width="100%" top="300" bottom="50" paddingLeft="30" paddingTop="30" paddingBottom="30" paddingRight="30" id="landscape" 
		zoomLimit="none"
		clipContent="false"
		cachePolicy="off"
        horizontalCenter="0"
	    verticalCenter="0" right="0"
	    >	
		
		<!-- Here we have the images to be loaded dynamically -->
        <mx:XMLList id="pages" xmlns="">
       	<images>
  			<image high="assets/p1.jpg" low="assets/p1_low.jpg" />
     		<image high="assets/p2.jpg" low="assets/p2_low.jpg" />
      		<image high="assets/p3.jpg" low="assets/p3_low.jpg" />
        	<image high="assets/p4.jpg" low="assets/p4_low.jpg" />
         	<image high="assets/p5.jpg" low="assets/p5_low.jpg" />
         	<image high="assets/p6.jpg" low="assets/p6_low.jpg" />
      	</images>
        </mx:XMLList>
        
		<mx:HBox width="100%" horizontalAlign="center" verticalAlign="bottom">	
		<!-- Here is the FlexBook component
        take a look at the turnEnd event. it happens after each page is turned
        also there is the content property that receives the xmllist 
        Here my item renderer is custom, so it renders the low res version first-->
        <qs:FlexBook id="book" y="47" left="40" right="40" top="40" height="500" width="718" horizontalCenter="0"
                animateCurrentPageIndex="true"
                showCornerTease="true"
                edgeAndCornerSize="30"
                itemSize="halfPage"
                hardbackCovers="false"
				hardbackPages="false"
                turnEnd="loadContent(event)"
                content="{pages.image}">
                <qs:itemRenderer>
                        <mx:Component>
                        	<mx:VBox>								
								<qs:Zoomer>
                                	<mx:Image source="{data.@low}" click="outerDocument.focusOn(event.currentTarget)" />
                                </qs:Zoomer>
							</mx:VBox>
                        </mx:Component>
                </qs:itemRenderer>
        </qs:FlexBook>
		</mx:HBox>
	</containers:Landscape>	
	
	<!-- Bottom bar for the preview images -->
	<mx:HBox width="100%" backgroundImage="@Embed('assets/bottomBarBG.gif')" height="20" backgroundSize="100%" styleName="currentPageIndex" horizontalAlign="center" verticalAlign="bottom" bottom="0">
	</mx:HBox>
</mx:Application>

Open in new window

0
 
julianopolitoCommented:
well, regarding the data.@low, it is very simple, once you understand the way flex works with inline components. If you look inside the flexbook component , you will see the component tag. It is the same as creating an external component. Then when this component is used as an item renderer, it automatically receives a "data" property that contains the dataprovider item that is associated to the displaying item. It is the same for datagrid cells, or list items. In the case of the flexbook, each item is a page. Now look at the way I made it work, creating an XMLList, and associating it to the "content" property of flexbook. So each page, will be rendered using the inline component, and when it is created it receives an item from the xmllist. Each xmllist item is an xml object like <image high="assets/p1.jpg" low="assets/p1_low.jpg" />. So if "data" is an XML object, to access its properties (in this case "low" and "high") we use E4X syntax: data.@low and data.@high. In my example, whenever a page is turned, I just change the file being loaded , removing the "_low" part of the filename (because I've made two versions and named them with and without the "_low" suffix)

I hope you can understand now.

Now, about your problem :

Error: " <mx:XMLList> not currently supported as Array element"


I'll make some tests here, but it is not expected, as you saw in my working example. I'll test it and let you know.




0
 
julianopolitoCommented:
I was looking at your code, and the problem seems to be that you declared the <XMLList> inside the landscape component. Move it to the application scope, ok? test and let me know.
0
 
The_Kingpin08Author Commented:
Thanks a lot for the explanation.

I moved it outside and the error is gone, but now I have a warning:

"Data binding will not be able to detect changes to XMLList "image", need an XML instance"
on this line:
> content="{pages.image}"

Also, if I ever feel lazy and don't want to create 2 versions (1 low and 1 high resolution), I could always use a loading image to replace the low version, right? I think I saw this in your comments, and it would be a great alternative if I decide to add a lot of pages but don't have time to create 2 versions of each one...

Thanks again for the help, I really appreciate.
Frank
0
 
julianopolitoCommented:
yes, right. Instead of creating two versions, you can just create a loading image for all pages, and then load the high version
0
 
The_Kingpin08Author Commented:
Alright, can't wait to try this out!

Do you have an idea what might cause the warning I'm having right now? It prevent me from loading the images correctly, so I can't test.

Thanks again.
Frank
0
 
julianopolitoCommented:
Warnings will never prevent you from compiling. It is just a warn, not an error. There must be an error for you not compile. Also, about this:

"Data binding will not be able to detect changes to XMLList "image", need an XML instance"

It is just telling you that if you modify the pages XMLList directly , it won't update components that uses this XMLList. It is not a problem for you, but if you want to get rid of this, you can change the xmllist tag for a xml tag.
0
 
The_Kingpin08Author Commented:
Sorry, I'm really new to Flex and I don't know how to read error message yet. This is what happens when I start the project:

TypeError: Error #1034: Type Coercion failed: cannot convert Flexbook_inlineComponent1@770e851 to mx.controls.Image.
      at Flexbook/::loadContent()
      at Flexbook/__book_turnEnd()
      at flash.events::EventDispatcher/flash.events:EventDispatcher::dispatchEventFunction()
      at flash.events::EventDispatcher/dispatchEvent()
      at mx.core::UIComponent/dispatchEvent()
      at qs.controls::FlexBook/::dispatchEventForPage()
      at qs.controls::FlexBook/qs.controls:FlexBook::commitProperties()
      at mx.core::UIComponent/validateProperties()
      at mx.managers::LayoutManager/::validateProperties()
      at mx.managers::LayoutManager/::doPhasedInstantiation()
      at Function/http://adobe.com/AS3/2006/builtin::apply()
      at mx.core::UIComponent/::callLaterDispatcher2()
      at mx.core::UIComponent/::callLaterDispatcher()


I found out a resolution for this type of error code, but I'm not sure how to do this:
"1. Add the child component that you are trying to add to a UIComponent (or other component that implements IUIComponent) and then add the UIComponent to the Container."

Do you have an idea what might cause the problem?
Thanks again for the help julianopolito.

Frank
0
 
julianopolitoCommented:
your problem is here:

var page:Image = Image(event.renderer);

In your code, the itemRenderer is NOT an IMAGE, it is:

<qs:itemRenderer>
                        <mx:Component>
                              <mx:VBox>                                                
                  <qs:Zoomer>
                        <mx:Image source="{data.@low}" click="outerDocument.focusOn(event.currentTarget)" />
                  </qs:Zoomer>
            </mx:VBox>
      </mx:Component>
</qs:itemRenderer>

Your renderer is an inline component. So we need to specify some properties in the inline component to access the image inside the renderer. First modify the inline comp as follows:

<qs:itemRenderer>
        <mx:Component className="MyImagePage">
              <mx:VBox>                                                
                        <qs:Zoomer>
                      <mx:Image id="theImage" source="{data.@low}" click="outerDocument.focusOn(event.currentTarget)" />
                </qs:Zoomer>
                  </mx:VBox>
        </mx:Component>
</qs:itemRenderer>

Now the inline component has a className property, which tells flex compiler which class to create for this inline comp, and the image has an id "theImage", so we can access it.
Now change the function:

private function loadContent(event:FlexBookEvent):void{
         //this event is dispatched by each page that appears after turn
         //in this case the event.renderer is the mx:Image      
         var page:MyImagePage = MyImagePage(event.renderer);

    //Check to see it is not null
    if(Boolean(page)){
          
          //change its source to the high res version
        page.theImage.source = String(page.theImage.source).replace("_low","");
             //you could also use a custom component and then have more control
        //over what appears on the page, like showing a loading text or animating
             //high res version over low res                                
    }
}

Now we are casting the renderer to the correct type MyImagePage (look at the className property ;) ), and then we set the source property for the image component inside the MyImagePage "theImage" (look at the id in the inline comp.) Now everything works fine.




0
 
The_Kingpin08Author Commented:
Ok, now I understand how you make it work.

Still, I have 2 questions:

1- Since the images are created dynamically, is it possible to set an image as the cover? Before I would put them in a tab <controls:cover> but now I don't seem to have access to this.

2- When I run the application, I don't have any error and everything runs smooth, but I don't see the pages. I try debugging and here's what I see:

The pages doesn't seem to be created correctly.
Also, page:MyImagePage is always null (probably because the pages doesn't appear)

I've attach my code so if you have a seconde to take a look at it, I would be really thanksfull (just rename the extension to .mxml)

Thanks a lot.
Frank
flexbook.txt
0
 
julianopolitoCommented:
I will asap.
0
 
The_Kingpin08Author Commented:
Hey there!

Just wanted to know if you has time to check it out.

The problem I have right now with your solution is that it doesn't include the covers, otherwise it would be really nice and I could manage something.

Please let me know if it's gonna be possble to handle the covers.

Thanks a lot for all your help so far julianopolito, really appreciate.

Have a nice day.
Frank
0
 
julianopolitoCommented:
you could set the cover via actionscript manually, using the cover object.
add cover and back to the xmllist:

<mx:XMLList id="pages" xmlns="">
      <cover><image high="assets/cover.jpg" low="assets/cover_low.jpg" /></cover>
      <back><image high="assets/back.jpg" low="assets/back_low.jpg" /></back>
         <images>
              <image high="assets/p1.jpg" low="assets/p1_low.jpg" />
             <image high="assets/p2.jpg" low="assets/p2_low.jpg" />
              <image high="assets/p3.jpg" low="assets/p3_low.jpg" />
                <image high="assets/p4.jpg" low="assets/p4_low.jpg" />
                 <image high="assets/p5.jpg" low="assets/p5_low.jpg" />
                 <image high="assets/p6.jpg" low="assets/p6_low.jpg" />
        </images>
</mx:XMLList>

then add a creationComplete="initApp" to application tag and create the function to set the cover and back via as

private function initApp(){
      var coverImg:Image = new Image();
      coverImg.source = pages.cover.toString();
      book.cover = coverImg;
      var backImg:Image = new Image();
      backImg.source = pages.back.toString();
      book.backCover = backImg;
}


Í can't test it now, but I think it will work fine. let me know
0
 
The_Kingpin08Author Commented:
Ok thanks a lot for the help julianopolito, this definately answer my original question.

I'm gonna post a new one to have a better idea on how to use the getCache function because I'd like to try the the other way too =)

Thanks,
Frank
0
All Courses

From novice to tech pro — start learning today.