5.5 ImageViewer Implementation (Take 2)

 <  Day Day Up  >  

Now that we have a working, albeit simple, version of the ImageViewer class, we can add more features to it. As we do so, we'll take care not the change the public API of the class. For example, at this stage we shouldn't change the loadImage( ) method's name to loadAndDisplayImage( ) . Nor should we change the order or datatype of the parameters for the constructor or the loadImage( ) method.

However, adding to the ImageViewer class's API is acceptable and normal. Changing its private methods or properties is also acceptable because such changes do not affect external code. However, changing the class's public API is dangerous and considered bad form because it forces corresponding rewrites in all code that uses the class.

During your internal development stages (alpha and beta), API changes are relatively common. But once a class is released to the world, its public API should remain fixed.

Any changes to the public API should preferably be accompanied by documentation and a change in the class's major version number. Additions to the public API (i.e., new methods) need not require a major version change because they won't break existing code.


We've completed the first two features (loading and displaying an image) from our list of possible functional requirements shown earlier under Section 5.2 Let's move on to the third and fourth requirements: cropping the image and giving it a border.

Flash doesn't have any built-in means of altering a bitmap once it's loaded. Hence, we can't literally crop our loaded image. Instead, we must apply a mask to it, which hides unwanted areas of the image from view. To mask our image, we'll first create an empty movie clip, then draw a filled square in it, then apply that square as a mask over the image-holding clip. We'll add parameters to the ImageViewer constructor to specify the size of the mask and the position of the cropped image.

At this stage of development, we've made the design decision to add two parameters to the constructor function to control cropping. Doesn't this undermine our earlier goal of a stable public API? Not necessarily . First of all, we haven't released any code publicly , so no one else is using our class yet. Second, to ensure backward compatibility, we can allow the constructor to assume sensible default values even if the new arguments are omitted when calling the constructor.

To create our border, we'll create a movie clip, then draw a square outline in it. We'll place the border clip visually on top of the image. We'll retrieve the thickness and color of the border from parameters that we'll add to the ImageViewer constructor. Again, we've made the design decision to specify the border thickness and color at the time the object is instantiated . Another option would be to create a new method through which these could be set. The latter option gives the caller the flexibility to change those settings even after the object has been created.

To accommodate our border and mask clips, we'll redesign the structure of our on-screen assets. Previously, we created a single movie clip, container_mc , into which we loaded an image.

For the sake of brevity and clarity, when I say "the movie clip container_mc ," I really mean "the movie clip referenced by the instance property container_mc ." The name of the clip itself is technically container_mc depth (where depth is the depth on which the clip resides). But the instance property is named container_mc .


Here's the original clip-creation code:

 container_mc = target.createEmptyMovieClip("container_mc" + depth, depth); 

This time we'll use container_mc merely as a holder for three movie clips: the mask clip ( mask_mc ), the border clip ( border_mc ), and a new image-holding clip ( image_mc ). We'll put the image_mc clip on the bottom (at depth 0 in container_mc ), the mask_mc clip in the middle (at depth 1), and the border_mc clip on top (at depth 2), as shown in Figure 5-1.

Figure 5-1. ImageViewer movie clip structure
figs/as2e_0501.gif

The question is, within the ImageViewer class, when should we create the border, mask, image, and container clips? In the first version of the ImageViewer class, we created the container_mc clip in the constructor function. We could theoretically take the same approach now (i.e., create the border, mask, and image clips in the constructor). But instead, in this version of the ImageViewer class, we'll define internal (private) methods to handle the creation of the various clips. Splitting out the work into separate methods has the following benefits:

  • It makes the code more intelligible.

  • It simplifies the testing process (testing each method individually is easier than testing a large block of code that performs many operations).

  • It allows assets to be created and re-created independently (e.g., the border on an image can change without reloading the image).

  • It allows asset-creation operations to be modified or overridden independently (e.g., a new border-creation routine can be coded without disturbing other code).

Thus, we've made this design decision to increase our class's flexibility and maintainability.

Table 5-1 lists the new clip-creation methods, all of which are declared private .

Table 5-1. The ImageViewer class's new instance methods that create movie clips

Method name

Method description

buildViewer( )

Invokes individual methods to create the container, image, mask, and border clips

createMainContainer( )

Creates the container_mc clip

createImageClip( )

Creates the image_mc clip

createImageClipMask( )

Creates the mask_mc clip

createBorder( )

Creates the border_mc clip


Now that our clip-creation operations are separated into methods, we need to add properties that provide those methods with the following information:

  • A reference to the target clip to which container_mc should be attached

  • A list of depths indicating the visual stacking order of the container, image, mask, and border clips

  • The style (line weight and color) of the border around the image

Table 5-2 lists the ImageViewer class's complete set of instance and class properties, all of which are declared private .

The properties indicated as class properties define a single value that all ImageViewer instances reference. Instance properties pertain separately to each instance.

Table 5-2. The ImageViewer class's instance and class properties

Property name

Type

Property description

container_mc

Instance

A reference to the main container clip, which contains all movie clips used by each ImageViewer instance

target_mc

Instance

A reference to the clip that will contain the container_mc clip, as specified by the ImageViewer constructor

containerDepth

Instance

The depth on which container_mc is created in target_mc , as specified by the ImageViewer constructor

imageDepth

Class

The depth on which image_mc is created in container_mc

maskDepth

Class

The depth on which mask_mc is created in container_mc

borderDepth

Class

The depth on which border_mc is created in container_mc

borderThickness

Instance

The thickness, in pixels, of the border around the image_mc clip

borderColor

Instance

The integer RGB color of the border around the image_mc clip


The final change to our ImageViewer class comes in the constructor function, which must define new parameters to support the new methods and properties of the class. We must define parameters to specify:

  • The position of the image ( x and y )

  • The size of the mask over the image ( w and h )

  • The style of the border around the image ( borderThickness and borderColor )

In addition to these new parameters, we'll keep our original target and depth parameters, so the ImageViewer constructor signature is now:

 ImageViewer(target:MovieClip, depth:Number, x:Number, y:Number,             w:Number, h:Number, borderThickness:Number, borderColor:Number) 

5.5.1 ImageViewer (Take 2), Design Summary

With the constructor redesign finished, the changes for version 2 of our ImageViewer class are complete.

Here's the final class design for this version. It repeats information from tables 5-1 and 5-2 in order to show how you might represent a class during the design phase of a project, perhaps before you've actually produced any code:

Constructor
 ImageViewer(target:MovieClip, depth:Number, x:Number, y:Number,             w:Number, h:Number, borderThickness:Number, borderColor:Number) 

Private properties
container_mc
target_mc
containerDepth
imageDepth
maskDepth
borderDepth
borderThickness
borderColor
Public properties
None
Private methods
buildViewer(x:Number, y:Number, w:Number, h:Number)
createMainContainer(x:Number, y:Number)
createImageClip( )
createImageClipMask(w:Number, h:Number)
createBorder(w:Number, h:Number)
Public methods
loadImage(URL:String)

5.5.2 ImageViewer Implementation (Take 2)

Example 5-5 shows the actual code for the ImageViewer class, version 2. Study the comments carefully for code explanations . If some of the specific ActionScript techniques are new to you (e.g., drawing lines or masking movie clips), consult an ActionScript language reference such as ActionScript for Flash MX: The Definitive Guide (O'Reilly).

Example 5-5. The ImageViewer class, take 2
 // ImageViewer class, Version 2 class ImageViewer {   // Movie clip references   private var container_mc:MovieClip;   private var target_mc:MovieClip;   // Movie clip depths   private var containerDepth:Number;     private static var imageDepth:Number  = 0;   private static var maskDepth:Number   = 1;   private static var borderDepth:Number = 2;   // Border style   private var borderThickness:Number;   private var borderColor:Number;   // Constructor   public function ImageViewer (target:MovieClip,                                 depth:Number,                                 x:Number,                                 y:Number,                                 w:Number,                                 h:Number,                                borderThickness:Number,                                borderColor:Number) {     // Assign property values.     target_mc = target;     containerDepth = depth;     this.borderThickness = borderThickness;     this.borderColor = borderColor;     // Set up the visual assets for this ImageViewer.     buildViewer(x, y, w, h);   }   // Creates the clips to hold the image, mask, and border.   // This method subcontracts all its work out to individual   // clip-creation methods.   private function buildViewer (x:Number,                                  y:Number,                                  w:Number,                                  h:Number):Void {       createMainContainer(x, y);       createImageClip( );       createImageClipMask(w, h);       createBorder(w, h);   }   // Creates the container that holds all the assets   private function createMainContainer (x:Number, y:Number):Void {     container_mc =        target_mc.createEmptyMovieClip("container_mc" + containerDepth,                                       containerDepth);     // Position the container clip.     container_mc._x = x;     container_mc._y = y;   }   // Creates the clip into which the image is actually loaded   private function createImageClip ( ):Void {     container_mc.createEmptyMovieClip("image_mc", imageDepth);   }   // Creates the mask over the image   private function createImageClipMask (w:Number,                                         h:Number):Void {     // Create the mask only if a valid width and height are specified.     if (!(w > 0 && h > 0)) {       return;     }     // In the container, create a clip to act as the mask over the image.     container_mc.createEmptyMovieClip("mask_mc", maskDepth);     // Draw a rectangle in the mask.     container_mc.mask_mc.moveTo(0, 0);     container_mc.mask_mc.beginFill(0x0000FF);  // Use blue for debugging     container_mc.mask_mc.lineTo(w, 0);     container_mc.mask_mc.lineTo(w, h);     container_mc.mask_mc.lineTo(0, h);     container_mc.mask_mc.lineTo(0, 0);     container_mc.mask_mc.endFill( );        // Hide the mask (it will still function as a mask when invisible).     // To see the mask during debugging, comment out the next line.     container_mc.mask_mc._visible = false;      // Notice that we don't apply the mask yet. We must do that     // after the image starts loading, otherwise the loading of      // the image will remove the mask.   }   // Creates the border around the image   private function createBorder (w:Number,                                  h:Number):Void {     // Create the border only if a valid width and height are specified.     if (!(w > 0 && h > 0)) {       return;     }     // In the container, create a clip to hold the border around the image.     container_mc.createEmptyMovieClip("border_mc", borderDepth);        // Draw a rectangular outline in the border clip, with the     // specified dimensions and color.     container_mc.border_mc.lineStyle(borderThickness, borderColor);     container_mc.border_mc.moveTo(0, 0);     container_mc.border_mc.lineTo(w, 0);     container_mc.border_mc.lineTo(w, h);     container_mc.border_mc.lineTo(0, h);     container_mc.border_mc.lineTo(0, 0);   }   // Loads the image   public function loadImage (URL:String):Void {     // Load the JPEG file into the image_mc clip.     container_mc.image_mc.loadMovie(URL);     // Here comes an ugly hack. We'll clean this up in Example 5-6     // when we add proper preloading support in Version 3.     // After one frame passes, the image load will have started,     // at which point we safely apply the mask to the image_mc clip.     container_mc.onEnterFrame = function ( ):Void {       this.image_mc.setMask(this.mask_mc);       delete this.onEnterFrame;     }   } } 

5.5.3 Using ImageViewer (Take 2)

Now that our ImageViewer class can crop an image and add a border to it, let's put ourselves in the position of a developer who's using the class (not creating and maintaining it). When we used the first version of the ImageViewer class, we placed the following code on frame 15 of imageViewer.fla :

 var viewer:ImageViewer = new ImageViewer(this, 1); viewer.loadImage("picture.jpg"); 

ImageViewer version 2 added two new features comprising quite a lot of code. However, none of the public API defined by ImageViewer version 1 changed in version 2; version 2 made additions only to that API. The loadImage( ) method changed internally, but its external usage did not change at all. Likewise, the constructor function changed internally, but externally it only added parameters. It retains the target and depth parameters from version 1 and adds six new parameters: x , y , w , h , borderThickness , and borderColor . Hence, users of ImageViewer version 1 can easily update imageViewer.fla to use ImageViewer version 2's new features, as follows :

  1. Replace the old ImageViewer.as file with the new one.

  2. Change the code on frame 15 of imageViewer.fla to include the six new parameters expected by the ImageViewer constructor. For example:

     var viewer:ImageViewer = new ImageViewer(this, 1, 100, 100,                                          250, 250, 10, 0xCE9A3C); viewer.loadImage("picture.jpg"); 

Now let's consider what would happen if we upgraded from ImageViewer version 1 to version 2 without changing any code in imageViewer.fla . (For the sake of this scenario, let's presume that version 2 contains performance enhancements that have prompted us to upgrade.) We'd replace our ImageViewer.as file, as in the previous Step 1, but we'd skip Step 2, leaving the following code on frame 15:

 var viewer:ImageViewer = new ImageViewer(this, 1); viewer.loadImage("picture.jpg"); 

How would ImageViewer version 2 respond to being constructed with only two parameters? Fortunately, very well. Version 2 specifically safeguards against missing parameters. The createImageClipMask( ) and createBorder( ) methods create the mask and border clips only if a useful width and height are supplied, as shown in this excerpt from Example 5-5:

 if (!(w > 0 && h > 0)) {   return; } 

Thus, when no width and height are supplied to the constructor, ImageViewer version 2's behavior matches ImageViewer version 1's behavior exactly.

After a class is formally released, upgrading to a new version should not break code that uses the old version. Changes to the class itself should not force changes to code that merely uses the class. One way to achieve this is for the new class to assume reasonable default behavior if some arguments are not supplied.


 <  Day Day Up  >  


Essential ActionScript 2.0
Essential ActionScript 2.0
ISBN: 0596006527
EAN: 2147483647
Year: 2004
Pages: 177
Authors: Colin Moock

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net