Hack 19 Fix Alpha Property Inaccuracies

figs/moderate.gif figs/hack19.gif

The movie clip alpha property can return inaccurate results. Work around the problem by storing a custom internal alpha-like property, resulting in smoother and more accurate fades.

The MovieClip._alpha property is used to set and retrieve a movie clip's transparency. But Flash rounds the value internally, so you may get a different value when retrieving it than the last value set. In this hack, we store a custom alpha property to avoid problems caused by the rounding discrepancy in Flash's built-in _alpha property.

The MovieClip._alpha property is stored internally as an integer between 0 and 255. ActionScript is feeding us a lie when it claims to be working with alpha values on a 0% to 100% scale. You can demonstrate the potential cumulative error by running the following code:

var clip:MovieClip = this.createEmptyMovieClip(

                 "clip", this.getNextHighestDepth( ));

clip._alpha = 0;

for(var i = 1; i <= 100; i++){

  clip._alpha++;

  trace(i+"% =  " + clip._alpha + "% alpha");

}

This code creates an empty movie clip and then changes the movie clip's _alpha property from 1 to 100 in steps of 1, or so we thought. In fact, Flash converts each of the values from 0% to 100% to the nearest approximation on the 0 to 255 scale. The last few values generated by this code look like:

95%  =  74.21875% alpha

96%  =  75% alpha

97%  =  75.78125% alpha

98%  =  76.5625% alpha

99%  =  77.34375% alpha

100% =  78.125% alpha

So, by the time we expect 100% alpha, Flash has messed with our values so much that the actual alpha value being displayed is 78.125%, an error of more than 20%! How is this possible? Well, every time we set the _alpha property, it is rounded to the nearest value in the range 0 to 255. But when we requery the _alpha property, the value is rounded down, resulting in each increment increasing the value by less than 1%.

The way to avoid the rounding error is to either use alpha percent values that aren't rounded or to write code that uses a custom property to store the current alpha value you want set.

The Five Alpha Values that Result in No Error

The _alpha property rounding error is caused by attempting to convert a 0% to 100% range to a 0 to 255 integer range. Although most percentage values have no corresponding exact value in the 0-255 range, five of them do: 0%, 25%, 50%, 75%, and 100%.

For example, suppose you want to change the alpha of a movie clip to "nearly transparent." The best value to pick here is 25% transparency, because 25% will give you a movie clip that is exactly 25% transparent. This is because 25% on the 0% to 100% scale gives you a whole number in the 0-255 range, namely 64. You can prove this is the case with the following code:

var clip:MovieClip = this.createEmptyMovieClip("clip",

                           this.getNextHighestDepth( ));

clip._alpha = 25;

trace(clip._alpha);  // Gives 25

clip._alpha = 20;

trace(clip._alpha);  // Gives 19.921875

Setting the alpha to 25% and then reading it back returns the same, unadulterated 25%. Using other values, such as 20% (for example), returns a value that is nearly, but not exactly, the value you set.

Mirroring the _alpha Property

Using the five alpha values that result in no error is cool when you want to set your movie clip to an alpha value and leave it there, but they are not as useful when you want to create an animated transition from one alpha value to another. In this case, you should write code that doesn't rely on _alpha being accurate.

For example, the following code creates a transition from 0% to 100% alpha and forces Flash to do it properly:

function fader(mc, startAlpha, endAlpha) {

  mc.fade1 = startAlpha;

  mc.fade2 = endAlpha;

  mc.onEnterFrame = fade;

}

function fade( ) {

  this._alpha = this.fade1++;

  if (this.fade1 >= this.fade2) {

    this._alpha = this.fade2;

    delete this.fade1;

    delete this.fade2;

    delete this.onEnterFrame;

  }

}

var clip:MovieClip = this.createEmptyMovieClip("clip",

                          this.getNextHighestDepth( ));

var size:Number = 100;

clip._x = 275;

clip._y = 200;

clip.lineStyle(0, 0x0, 100);

clip.beginFill(0x0000FF, 100);

clip.moveTo(-size/2, -size/2);

clip.lineTo(size/2, -size/2);

clip.lineTo(size/2, size/2);

clip.lineTo(-size/2, size/2);

clip.endFill( );

clip._alpha = 0;

fader(clip, 0, 100);

The preceding code assumes that the value returned by querying the _alpha property may be incorrect (rounded from the value to which it was previously set). So it doesn't rely on the accuracy of the retrieved value. Rather than increase the value of _alpha directly, the code increases the value of a separate property, fade1, and equates _alpha to it. This technique prevents errors from building up over time. Rather than examining the _alpha value to see if it has reached 100%, we examine fade1 instead, because it doesn't suffer from any inaccuracy. When we have reached the required value (100% in this case), we explicitly set the _alpha value to the required value.

Alpha transparencies make Flash render more slowly because it has to draw both the foreground and background pixels. If rounding errors result in an alpha value that is a fraction below 100%, Flash may run sluggishly. When a fade transition completes, set _alpha explicitly to 100 to avoid performance degradation.


Avoiding Alpha Errors via Classes/Prototypes

If you are creating something that makes heavy use of alpha effects, the extra code to address inherent inaccuracies in the _alpha property might make your code more difficult to read or maintain. In that case, consider creating a custom class that fixes the problem. The following example creates a new class named AlphaClip, which must be stored in an external file named AlphaClip.as. This class defines getter and setter functions that read (get) or write (set) its own property (alphaInternal) that does not suffer from the MovieClip._alpha rounding error. Notice that AlphaClip is not a subclass of MovieClip, but it does store a reference to a movie clip as one of its properties.

// This ActionScript 2.0 code must go in an external AlphaClip.as file

class AlphaClip {

  private var alphaInternal:Number;

  private var target:MovieClip;

  

  public function AlphaClip(mc:MovieClip) {

    target = mc;

    alphaInternal = mc._alpha;

  }

  public function get _alpha( ):Number {

    return alphaInternal;

  }

  public function set _alpha(alphaIn:Number):Void {

    target._alpha = alphaIn;

    alphaInternal = alphaIn;

  }

}

Assuming there is a movie clip named myClip on the Stage, reading and writing the MovieClip._alpha property directly can create discrepancies between the value we set and the value we get back on reading. If we use the _alpha property of our custom AlphaClip class instead, we see that the value returned is the same as the value we set, because it uses the more accurate AlphaClip.alphaInternal property behind the scenes. Using this intermediate value prevents errors getting larger over time. We use getter and setter methods so that the developer can refer to the familiar _alpha property instead of referring directly to the alphaInternal property.

var myAlpha:AlphaClip = new AlphaClip(myClip);



// Change movie clip _alpha directly (old way)

myClip._alpha = 20;

trace(myClip._alpha);  // Displays: 19.921875

// Change movie clip myClip._alpha indirectly via myAlpha._alpha

myAlpha._alpha = 20;

trace(myAlpha._alpha); // Displays: 20

The preceding approach solves the problem, but it is somewhat cumbersome to use because developers must remember to create an AlphaClip instance in addition to the target movie clip whenever they want to avoid the potential inaccuracies of MovieClip._alpha. A more formal ActionScript 2.0 OOP approach would be to make AlphaClip a subclass that extends (inherits from) the built-in MovieClip class. The inheritance approach has the marginal benefit that the developer doesn't have to create separate AlphaClip and MovieClip instances to deal with a single clip, but he still has to remember to create an AlphaClip instead of a MovieClip instance when the alpha property inaccuracy is an issue.

In the preceding example, we opted for the simpler object composition approach rather than formal inheritance. That is, rather than extending the MovieClip class via the extends keyword, our AlphaClip class refers to a particular MovieClip instance using the target property.

(MovieClip subclasses and object composition are both covered extensively in Chapter 13 of Essential ActionScript 2.0 by Colin Moock.)

However, for the convenience of the developer, we'd prefer a direct replacement for MovieClip._alpha without the need to instantiate a separate class or subclass. So here we opt to use the ActionScript 1.0 style of modifying the MovieClip class by attaching properties or methods to its prototype. This code also works in ActionScript 2.0:

// Define getter and setter functions

getAlpha = function ( ) {

  return this.alphaInternal;

};

setAlpha = function (alphaIn) {

  this._alpha = alphaIn;

  this.alphaInternal = alphaIn;

};

initAlpha = function ( ) {

  return 100;

};

// Add the new property MovieClip.alpha (no underscore)

MovieClip.prototype.addProperty("alpha", getAlpha, setAlpha);

MovieClip.prototype.alphaInternal = initAlpha( );

This time, I have used addProperty( ) to create a new MovieClip property named alpha (without the underscore) that uses getter and setter methods. This property does exactly the same thing as MovieClip._alpha, except that it doesn't suffer from the same rounding error problems (it uses the more accurate intermediate variable alphaInternal).

Here we perform a fade in the onEnterFrame( ) handler, which stops when the custom alpha property is zero:

myClip.onEnterFrame = function( ) {

  this.alpha--;

  if (this.alpha == 0) {

    delete this.onEnterFrame;

    trace("done")

  }

};

If you use the built-in _alpha property (with an underscore) instead of the custom alpha property (no underscore), the onEnterFrame( ) handler will never be "done" because _alpha is never set exactly equal to zero:

myClip.onEnterFrame = function( ) {

  this._alpha--;

  if (this._alpha == 0) {

    delete this.onEnterFrame;

    trace("done")

  }

};

Final Thoughts

Animated alpha effects are some of the most processor-intensive graphic effects you can create in Flash, so the potential inaccuracy in the _alpha property can really sap performance. A clip whose alpha is set to 99.6078% looks just like one whose alpha is set to 100% (opaque, not transparent) but it renders much more slowly! Writing efficient animation code for alpha effects depends on knowing about and hacking around the _alpha property inaccuracy.

Although some OOP purists will wrinkle their noses at the prototype-based ActionScript 1.0-style solution, this syntax is still supported in ActionScript 2.0. Bear in mind that ActionScript 2.0 subclasses compile down to the same bytecode as the prototype-based approach. You can use whichever approach you are most comfortable with (prototype-based inheritance, object composition, or formal class-based inheritance).