Demo: Montage Engine

I. The Project

This posting describes a montage engine, a fairly simple solution for deploying a random selection of images in an overlapping stream. Obviously, this is a useful way to present a gallery of images, or catch the attention of ever-fickle Web surfers. A close cousin of the code described here runs the image montage on my home page.

This project will teach you some important aspects of ActionScript 3, namely the highly useful Loader class.

II. Architecture

There is no starter file for this project, because the .fla or main movie file is almost entirely empty. Everything here is done with a document class, which we'll explore presently. There are no pre-definied Movie Clips or other timeline assets. The main movie's default single keyframe is empty. In the Document Class window in the Movie Properties dialog, we've entered montageDocClass; but that's the only modification necessary to get started.

III. Code (document class)

As you'll remember, a Document Class is just another ActionScript class, this time attached to the main movie, not to one of its sub-components. In an object-oriented system, everything tends to be an object, and this is true of the main movie, which is in fact just another Movie Clip.

Here's how montageDocClass.as begins, after the start of the standard package{} container:

  import flash.display.MovieClip;
  import flash.display.Loader
  import flash.net.URLRequest
  import flash.events.Event;

We import flash.display.MovieClip, since we are scripting the main movie. (Actually, since the main movie has no effective timeline in this project, we can import flash.display.Sprite if we want.)

The next two imports, flash.display.Loader, and flash.net.URLRequest, are part of the mechanism with which we'll handle our image files, which are external JPEGs.

We import flash.events.Event)in order to handle the ENTER_FRAME event.

Next we begin the class body:

  public class montageDocClass extends MovieClip
  {

And so to the variable-declaration section:

  //Loaders
    var image_1:Loader = new Loader();
    var image_2:Loader = new Loader();
  //Utility variables
    var whichImg: Loader;
    var imageRequest:URLRequest;	
    var fadeDir:String = "down";
  //Randomization variables
    var thisRand:Number = 0;
    var prevRand:Number = 0;

We declare and instantiate two object variables, image_1 and image_2. They are instances of Loader, a built-in ActionScript class that provides support for external images and media files (such as sound and video).

Next we set up three "utility" variables that we'll need for various purposes. One of these is another Loader object called whichImg; note the variable is defined without instantiating a third Loader object. That's because whichImg will act as a container for (successively) each of the Loaders defined above.

The variable imageRequest is another object variable, giving us specifications we'll use to pass an image location to our Loaders.

Third in this list is fadeDir, a String variable which we initialize as "down." This is a flag variable that controls which way we are fading our dynamic image, up (alpha level increasing) or down (alpha decreasing, making the image more transparent).

Finally we have two variables we'll use in our randomization function, rando(), to which we are coming. If you recall what we've said before about non-repeating random values, you'll remember that we need a pair of variables to compare current and previous random picks, to ensure they are different. Both these variables must be set to the same value initially.

With those variables out of the way, we proceed to the Constructor, which looks like this:

    public function montageDocClass():void
    {
      getImage(image_1);
      getImage(image_2);
      addChild(image_1)
      addChild(image_2);
      this.addEventListener(Event.ENTER_FRAME, fadeBiz);
    }

We call a custom method called getImage() twice, passing it the names of our two Loaders. We'll discuss this method in just a bit. As you'll see, it associates a particular JPEG file with each Loader.

Next we call the addChild() method of MovieClip, also once for each of our two Loaders. These statements add the Loaders to the Display List, which makes them visible, among other things.

Finally, we add an Event Listener for the ENTER_FRAME system event, indicating a callback function called fadeBiz(), to which we'll come eventually.

Now we come to the custom methods. Let's begin with getImage(), which looks like this:

    function getImage(whichImg):void
    {
      var imgName:String ="pix/img_"+rando()+".jpg";
      imageRequest = new URLRequest(imgName);
      whichImg.load(imageRequest);
    }

First off, notice that the getImage() method uses our whichImg object variable as an argument. This lets us pass each of the Loaders successively to this method as its context, letting this one bit of code serve for both.

Within this function, we declare a new (local) variable called imgName, which is a String that contains the file location for the JPEG we're seeking. As you can see, we assume two things about our JPEGS: first, they're all located in a folder called pix, which is located within the same directory as the .swf file; and second, all our images are named img_XX.jpg, where "XX" is replaced by a number.

That number is filled in by a method called rando(), which generates a random number between 0 and 9, guaranteed not to repeat twice in succession. We'll discuss detals of this function in just a bit. For the moment, notice that we drop in the function call as if it were a variable. We can do that because rando() includes a special statement called return. More on that in a second.

Next, we instantiate a new URLRequest object, passing it the address String imgName. This is quite a bit like writing an <IMG> tag in HTML, and passing a value to its SRC attribute.

Finally, we call the load() method of whichever Loader we're working with (indicated by whichImg), and pass to that method the URLRequest object imageRequest. Yes, it's all a bit complicated for our purposes, but most of these complications are there to give us improved control over media loading; so hold your complaints.

The getImage() method depends on another custom method called rando(), which serves up random numbers. You may find it a bit familiar:

    function rando():Number
    {
      while(thisRand == prevRand)
      {
        thisRand = Math.ceil(Math.random()*9)
      }
      prevRand = thisRand;
      return thisRand;
    }

Basically, this is the same non-repeating, or constrained random-number generator we've seen before. However, this function is not declared as void, like most of our custom methods, because it returns a value. Since that value is a Number, the function is given that type.

You'll remember the logic of the non-repeating randomizer. It first compares two values, one representing a current pick, the other the previous pick. If the two are the same (as they must be at the start, since we set both variables to 0 in our declarations), then we put a new value into the current pick (thisRand) and re-check. If it's not the previous number, we put the current value into the previous-value variable (prevRand), so that the two variables are once again equal, and we will be guaranteed a fresh number on the next iteration.

Finally, we say return thisRand, which sends whatever Number we've chosen back to wherever we've plugged in this method -- in our case, in our composition of the String called imgName, that represents the name and address of our randomly-selected JPEG. (If you're wondering how we can add a Number to a String, that's an old ActionScript and JavaScript trick that's somehow escaped the notice of those who want to make ActionScript more like real programming languages.)

Now, at last, we come to the callback from the enter-frame listener, a custom method called fadeBiz(). It's a bit like the animate() methods we've been using as enter-frame callbacks, but instead of pushing things around the screen, it mainly handles alpha changes on one of our two graphics.

Here's the code, followed by an explanation, since its function is not perhaps immediately obvious:

    function fadeBiz(event:Event):void
    {
        if(fadeDir == "down") //TOP IMAGE FADING OUT
        {
          image_2.alpha -= 0.04;
        }
        else //TOP IMAGE FADING IN
        {
          image_2.alpha += 0.04;
        }
		
    //TOP IMAGE INVISIBLE, SO RELOAD IT
        if(image_2.alpha <= 0)
        {
          getImage(image_2);
          fadeDir = "up";
        }
		
    //TOP IMAGE FULLY OPAQUE, SO RELOAD BOTTOM IMAGE
        if(image_2.alpha >= 1)
        {
          getImage(image_1);
          fadeDir = "down";
        }
    }

The first thing to understand here is that image_1 and image_2, our two Loader instances, occupy the same X and Y coordinates on the screen. That is, image_2 sits on top of image_1, because it is added to the Display List later. So image_2 is our TOP image, and image_1 is BOTTOM.

Our alpha fade effect only applies to image_2, the TOP. When our flag variable is set to "down," we decrease the alpha setting (opacity) for this object by 0.04 on each frame entry. Alpha settings are calibrated in decimals between 0 (invisible) and 1(fully opaque).

Using an if/else structure, we account for the opposite case as well. If the flag is set the other way ("up"), we make image_2 more opaque with each frame entry. Fade down, fade up.

The charm comes in with the two if tests that follow the if/else. In the first, we ask if the alpha value of image_2 (which remember, is our TOP image) has decreased to zero or something less. In that case, our top image has completely faded out, and we can use getImage() to load a new JPEG without the viewer seeing the swap.

Likewise, if the alpha of image_2 hits 100% (or more), then it's fully opaque, and we can swap out the JPEG assigned to image_1, our BOTTOM image, with the same visual subtlety.

The effect of all this sleight-of-object is an illusion of one image fading into another. Actually, only the top image undergoes any alpha change. The lower image appears to dissolve in, because the upper image is dissolving out.

And so we come to the end of montageDocClass.as.

IV. Refinements

If you set everything up properly, the code described above will work on your personal machine, and in most cases, it will also run just fine on the Web. However, some browsers, and some page or server architectures, can cause this code to function badly, or even break.

Here are two ways to improve the basic montage machine to make it impervious to these problems.

You might want to add a small but important change to the fadeBiz() method, like so:

    function fadeBiz(event:Event):void
    {
      if(image_1.content != null && image_2.content != null)
      {
        if(fadeDir == "down")
        {
          image_2.alpha -= 0.04;
        }
        else
        {
          image_2.alpha += 0.04;
        }
		
        if(image_2.alpha <= 0)
        {
          getImage(image_2);
          fadeDir = "up";
        }
		
        if(image_2.alpha >= 1)
        {
          getImage(image_1);
          fadeDir = "down";
        }
      }
    }

We've added a new double if condition to the fade mechanism, checking to see that neither image_1 nor image_2 has a content value of null. The content property of a Loader indicates the name of the media object (JPEG, video movie, sound file) currently loaded. If nothing is loaded, the value of this property is null.

Adding this condition suspends the fader if one of the two images is in the process of loading its JPEG. With only 10 images in play, you may never notice this problem, since all images will quickly go into your browser cache; likewise, if you're running locally on your PC, there will be virtually no load time for the images. On the Net, however, these JPEGS can sometimes take a second or so to load. The result is a sudden appearance (a visual snap) that breaks up the smooth fade effect.

Add the check for content, and this problem will go away.

We've also noticed a second, more troublesome problem, and while we've only seen it in one browser type (Windows Firefox), and only on pages that also contain server-side includes, we'll discuss the solution in case you ever see anything similar.

The problem concerns the getImage() custom method. In our code examples, for the sake of simplicity, we've written the file location in relative form. This will work just fine if you're developing your project on a personal machine. When you move to the Web, however, you may find that some browsers, in some circumstances, won't handle the relative file location properly. The Flash Player may complain that it can't load the specified URL, or it may simply not load an image, without throwing an error.

To eliminate this problem, use a fully qualified URL instead of the local file reference. So in the .swf you see running at the top of this page, getImage() is coded as:

  var imgName:String ="http://iat.ubalt.edu/courses/idia619.185_Sp08
  /swf/montage/pix/img_"+rando()+".jpg";

It's probably a good idea to use fully-qualified URLs whenever referring to external media objects, both in Flash and HTML.

V. Source files

Source files for this project can be found in the montage folder within MULTIMEDIA in the shared account on student-iat.



University of Baltimore Logo

Last updated: 06/24/08 16:26:21
Copyright © 2008 School of Information Arts and Technologies