Hack 19 Fix Alpha Property Inaccuracies 
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).
|