ActionScript.org Flash, Flex and ActionScript Resources - http://www.actionscript.org/resources
Creating a Reflection Effect with AS3
http://www.actionscript.org/resources/articles/916/1/Creating-a-Reflection-Effect-with-AS3/Page1.html
Jean André Mas
Graphic designer converted since 2004 to coding. I play around with C++, OpenGL, Java, Javascript, AND Actionscript.
My website: ASWC 
By Jean André Mas
Published on August 17, 2009
 
A full tutorial about creating a reflection effect with actionscript3.

Creating a Reflection Effect
Many people have read the simple reflection tutorial for AS2 that I wrote a while ago. Many also had a lot of questions about how to make this more dynamic, with more objects and so on. Rather than answering case after case, I decided to write a new tutorial exploring the reflection concept deeper. I then eventually created a new easy-to-use class. Of course this tutorial is for AS3 but the basic principles that I will show are applicable to AS2 as well.

So let’s start by showing the basic principles behind a simple reflection in Flash. So let’s say we have an object that we want to add a reflection effect to:



Step number 1 is to create a copy of the object:



Step number 2 is to flip the copy. Here the reflection will be at the bottom so we flip the copy vertically:



Step number 3 is to create a gradient:



Step number 4 is to set one color of the gradient to transparent:



Finally set the gradient on top of the flipped copy and set the gradient as a mask for the flipped copy:



So to resume creating a reflection involves a few steps: Create a copy, flip the copy, create a gradient, set one color of the gradient transparent, and then set the gradient as a mask for the flipped copy. Now to translate that into code all we need to do is write a code that does all these steps for us.

It’s important also to notice that the way the reflection looks is completely dependent on the way the gradient is made. The second color of the gradient is fully transparent but what happens if I make the first one 50% transparent?



What happens if I move the gradient up?



So the gradient is the centerpiece of a nice reflection effect. We can move it, set different transparencies for its colors; we could also put the gradient at an angle to get interesting effect and so on. So now it’s time to apply what we learned and get into some coding.

The First Attempt

So let’s write a little class and try to apply what we have learned so far. The scope of this class will be to create a DisplayObject that will be the reflection of the object passed to it. We will explore wider scopes later on. So all we need to do is to pass an object that we want to apply a reflection to in our constructor:

[as]package ASWC{ import flash.display.*; public class SimpleReflection extends Sprite{ private var TargetDisplayObject:DisplayObject; public function SimpleReflection(Target:DisplayObject){ TargetDisplayObject = Target; } }//end class }//end package[/as]

In a document class or on the Flash timeline we could create an instance of this class that way:

[as]var reflection:SimpleReflection = new SimpleReflection(test_mc); addChild(reflection); reflection.x = test_mc.x; reflection.y = test_mc.y+test_mc.height+10;[/as]

Where test_mc is a MovieClip on the stage with a top left registration.

So the first thing we need in our class is create a copy of the object.

We could try to replicate the graphics property of the object and also to get and replicate all other objects that are currently in the target object display list but that will be slow and overkill. Also if the object is a Flash animation or a movie we would need to somehow synchronize both the object and the reflection.

Instead we will draw directly the object in a BitmapData which is basically like taking a screenshot. So let’s create a new private method that we call from our constructor:

[as]private function CopyAndFlip():void{ var W:Number = TargetDisplayObject.width;//store the width of the target var H:Number = TargetDisplayObject.height;//store the height of the target var bmd:BitmapData = new BitmapData(W, H, true, 0x00000000); //create a bitmapdata with the width and height of the target bitmap = new Bitmap(bmd);//assign our bitmapdata to a bitmap instance member variable bmd.draw(TargetDisplayObject);//draw the target in our bitmapdata addChild(bitmap);//add the bitmap on our instance display list }[/as]

And here we go:


First step done, now we need to flip it. For now just flipping the bitmap will do just fine:

[as]private function CopyAndFlip():void{ var W:Number = TargetDisplayObject.width; var H:Number = TargetDisplayObject.height; var bmd:BitmapData = new BitmapData(W, H, true, 0x00000000); var bitmap:Bitmap = new Bitmap(bmd); bmd.draw(TargetDisplayObject); bitmap.scaleY = -1;//flip it vertically bitmap.y = bitmap.height;//reposition the bitmap addChild(bitmap); }[/as]


Perfect now let’s create a gradient. We’ll need to import the Matrix class and then we’ll create a new private method:

[as]private function createGradient():void{ var W:Number = TargetDisplayObject.width; var H:Number = TargetDisplayObject.height; var myMatrix:Matrix = new Matrix();//create a matrix instance myMatrix.createGradientBox(W, H, 0, 0, 0);//use this handy method createGradientBox //and pass it the width and height of the target, leave all other parameter at zero var colors:Array = [0xFF0000, 0x0000FF];//these are the colors for the gradient var alphas:Array = [1, 1];//these are the alpha values for the colors var ratios:Array = [0, 0xFF];//these control how the two colors merge into one another Mask = new Sprite(); //a new member variable of type Sprite Mask.graphics.beginGradientFill(GradientType.LINEAR, colors, alphas, ratios, myMatrix); //and we set the gradient using our values Mask.graphics.drawRect(0, 0, W, H);//and we draw it addChild(Mask);//then we add the gradient to our instance }[/as]

And here it is:


Well the gradient is not in the right direction. Let’s fix that here:

[as]myMatrix.createGradientBox(W, H, 0, 0, 0);[/as]

The third value lets us define an angle (in radians) so let’s do just that:

[as]myMatrix.createGradientBox(W, H, Math.PI/2, 0, 0);[/as]

And here we go:


Next step is setting the transparency of the colors so we do that here:

[as]var alphas:Array = [1, 0];//change the second value to zero[/as]

And here is the result:


Next step is setting the copy and gradient on top of each other but in our case it’s already done. So last step, set both the copy and gradient cacheAsBitmap property to true and then set the gradient as a mask for the copy. Let’s do that in our constructor:

[as]public function SimpleReflection(Target:DisplayObject){ TargetDisplayObject = Target; CopyAndFlip();//copy and flip our target createGradient();//create our gradient bitmap.cacheAsBitmap = true;//set the copy cacheAsBitmap property to true Mask.cacheAsBitmap = true;//set the gradient cacheAsBitmap property to true bitmap.mask = Mask;//set the gradient as a mask for the copy }[/as]

And here is our reflection:


Let’s adjust our alpha a bit to get a cleaner result:

[as]var alphas:Array = [0.4, 0];//first value to 0.4[/as]

And here our reflection is pretty clean:


Now let’s see how this could work in a real situation. Let’s make it so we can drag the target object and make sure the reflection follows:

[as]test_mc.addEventListener(MouseEvent.MOUSE_DOWN, handleDown); //register a mouse down event with the object test_mc.addEventListener(MouseEvent.MOUSE_UP, handleUp);//register a mouse up event with the object var isDown:Boolean;//create a boolean to check the mouse state function handleDown(e:MouseEvent):void{ if(e.currentTarget == e.target){//if we mouse down on the target isDown = true;//then the mouse is down set the boolean to true e.target.startDrag();//start dragging addEventListener(MouseEvent.MOUSE_MOVE, handleMove);//and register a mouse move listener } } function handleUp(e:MouseEvent):void{ if(isDown){//the mouse is up, was the mouse down on our object? isDown = false;//then set the boolean to false e.target.stopDrag();//stop dragging removeEventListener(MouseEvent.MOUSE_MOVE, handleMove);//remove the listener } } function handleMove(e:MouseEvent):void{ reflection.x = test_mc.x;//we are moving so set the x property to match the one of the object reflection.y = test_mc.y+test_mc.height+10; //set the y property to the object x+height+ a little gap of 10 e.updateAfterEvent();//make sure everything work smoothly }[/as]

And here it is, drag the object around:


Not bad for a simple reflection class but there are still a lot of situations where this class will not work correctly. So let’s study that in the next section.


What’s Wrong?

What would happen if the target object has one or many filters applied?


Well, the reflection doesn’t show the filter. What would happen if we scale the object?


Again we get a not-so-good result. What about rotation?


Still no luck. We also do not reproduce the registration point of the target object. We have no way of forcing a redraw of the reflection and we can’t set any of the properties that are important for the reflection effect. So we need to address all of these issues in a new class. Let’s see that in the next section.


A New Start

So let’s create a new class called DynamicReflection. This new class will be quite different from the first one. First we’ll take advantage of the RENDER event that we can call from the stage.invalidate() method. This will allow us to draw a reflection only when we need one and so saving some CPU usage. Here is our first draft:

[as]import flash.display.*; import flash.events.*;[/as]

Import the Display package and events package.

[as]private var TargetDisplayObject:DisplayObject = null; private var needsRedraw:Boolean; private var W:Number; private var H:Number;[/as]

Four member variables to start, one to store the target object, one Boolean to drive our RENDER event, and two for the width and height. Now the constructor:

[as]public function DynamicReflection(Target:DisplayObject = null){//parameter is optional if(Target != null){//if we got a DisplayObject TargetDisplayObject = Target;//then set our member variable redrawReflection(); //we have an object so we can draw a reflection when possible } addEventListener(Event.RENDER, initReflection);//we listen for a RENDER event addEventListener(Event.ADDED_TO_STAGE, initReflection); //make sure we draw our reflection if we need it when added on the display list }[/as]

Flash does not dispatch a RENDER event by default, we have to make a call to stage.invalidate() to force Flash to dispatch this event.

[as]public function set target(Target:DisplayObject):void{ TargetDisplayObject = Target;//set our member variable redrawReflection(); //ask to redraw our reflection }[/as]

The setter method will allow us to change the target object on the fly thus allowing us to reuse a DynamicReflection instance.

[as]private function redrawReflection():void{ needsRedraw = true;//set to true so we'll need to redraw the reflection //next time a RENDER event is dispatched if(stage){//if the reflection instance is on the display list stage.invalidate();//ask for a RENDER event to be dispatched } }[/as]

Basically we force Flash to dispatch a RENDER event but we still use a Boolean to check if our reflection needs to redraw itself. If another object requested the RENDER event we don’t want our reflection to redraw itself if it doesn’t need to.

[as]private function initReflection(e:Event):void{ if(stage && TargetDisplayObject.visible && needsRedraw){ //if there's a valid stage and if the target object is visible and if we need to redraw the reflection needsRedraw = false;//then we won't need to redraw the reflection anymore createReflection();//and here we finally draw or redraw the reflection } }[/as]

The createReflection method is the one that will draw the reflection but by using the RENDER event we make sure that this function will always run only once and only when necessary. Without this we would have to call the createReflection method each time we make a change in the DynamicReflection instance thus running the same createReflection method many times. Now we can add more setters method (we’ll create the getters too).


Setting and Getting

So here are a few setters to start with. Notice that we always call the redrawReflection() method but thanks to the RENDER event we will only draw our reflection once each time the screen is updated.

[as]public function set gradientMode(T:String):void{ if(T == (GradientType.LINEAR || GradientType.RADIAL)){ gradientType = T; } else{ gradientType = GradientType.LINEAR; } redrawReflection(); } [/as]

With this we will be able to set the gradient type we will use. It is LINEAR by default.

[as]public function set alphas(T:Array):void{ _alphas = T; if(_ratios.length != _alphas.length){ resetRatios(_alphas.length); } if(_colors.length != _alphas.length){ resetColors(_alphas.length); } redrawReflection(); }[/as]

We can use as many colors as we want for our gradient but then the alpha array number of element and ratio array number of element must match perfectly. This setter method makes sure all arrays have the same number of elements. The setter method for the ratios is similar. We won’t need one for the colors since we don’t see them anyway. Now time to get to our drawing method. We will create more setters and getters as we move on but for now let’s get into the drawing matter in the next section.


First Drawing

We saw that one of the challenges is that the object we need to draw might be scaled or rotated or have some filters applied. Flash does not update the screen until all code is executed so we can take advantage of that. We can set the target object back to normal by removing all filters, rotation, scaling, then we can draw our reflection, then we reset the target object back with filters, scale, rotation. Let’s do that here:

[as]private function getTargetProperty():void{ angle = TargetDisplayObject.rotation; TargetDisplayObject.rotation = 0; SCALEX = TargetDisplayObject.scaleX; TargetDisplayObject.scaleX = 1; SCALEY = TargetDisplayObject.scaleY; TargetDisplayObject.scaleY = 1; FILTERS = TargetDisplayObject.filters; TargetDisplayObject.filters = []; }[/as]

We store all these property into new member variables. Then of course once we are done drawing we add back the property:

[as]private function setTargetProperty():void{ TargetDisplayObject.rotation = angle; TargetDisplayObject.scaleX = SCALEX; TargetDisplayObject.scaleY = SCALEY; TargetDisplayObject.filters = FILTERS; }[/as]

And in our drawing method we have this:

[as]private function createReflection():void{ getTargetProperty(); //we draw here setTargetProperty(); }[/as]

So when we are ready to draw, we draw the target object exactly as it is but the next step is to get the registration point.

[as]private function getRegistration():void{ rect = TargetDisplayObject.getBounds(TargetDisplayObject.parent); Xoffset = rect.x; Yoffset = rect.y; }[/as]

So we store the target object bounds in a new member variable rectangle instance, then we store in two number member variables the distance from the rectangle coordinates to the target coordinates. So now we can basically draw:

[as]private function createReflection():void{ getTargetProperty(); getRegistration(); bmd.dispose();//clear the former bitmapdata var temp:BitmapData = new BitmapData(rect.width, rect.height, true, 0xFFFFFFFF); //we create a temporary bitmapdata matrix = new Matrix();//start fresh with a clean matrix matrix.translate(-Xoffset, -Yoffset); //this is here that we handle the registration! temp.draw(TargetDisplayObject, matrix);//and we draw of course matrix.translate(Xoffset, Yoffset); // now time to get back to normal registration matrix.translate(0, -rect.height); //get ready to flip the drawing matrix.scale(1,-1);//flip the matrix bmd = new BitmapData(rect.width, rect.height, true, 0xFFFFFFFF); //our bitmapdata member variable bmd.draw(temp, matrix);//and we draw the flipped bitmapdata bitmap.bitmapData = bmd;//assign our new bitmapdata to our bitmap temp.dispose();//clean up setTargetProperty(); }[/as]

Time to run a little test just to see how all this goes. I changed the registration of our MovieClip and assigned to it an instance of our new class:


The drawing is good but we did not catch up the registration yet. Also we should be able to set where the reflection should be set like right, left, bottom, top. This will change the way we flip the bitmapdata. Let’s see that in the next section.


Draw With Registration

So let’s define some constants first:

[as]public static const LEFT:String = "left"; public static const RIGHT:String = "right"; public static const BOTTOM:String = "bottom"; public static const TOP:String = "top";[/as]

We can use that to set how we want the reflection. We create a setter for the direction:

[as]public function set direction(T:String):void{ if(T == ("left" || "right" || "bottom" || "top")){//check if it's valid currentDirection = T;//assign the value }else{//not valid currentDirection = DynamicReflection.BOTTOM;//assign default value } redrawReflection(); //the reflection settings changed so redraw }[/as]

Now depending on the direction we need to prepare our matrix differently. We also set the angle of the gradient we will use for our reflection. We also need to catch up the registration differently depending on the current direction mode:

[as]var mwidth:Number = 0;//shift the matrix by width var mheight:Number = 0;//shift the matrix by height var mX:Number = 1;//scale the matrix on the x coordinate var mY:Number = 1;//scale the matrix on the y coordinate var Xreg:Number = 0;//catch the registration on the x coordinate var Yreg:Number = 0; //catch the registration on the y coordinate if(currentDirection == "bottom" || currentDirection == "top"){ mwidth = 0; mheight = -rect.height;//so sift the height mX = 1; mY = -1;//flip the scaleY Xreg = Xoffset;//catch up on the x registration Yreg = -Yoffset-rect.height;//catch up on the y registration if(currentDirection == "bottom"){ gradientAngle = Math.PI/2;//gradient angle } else{ gradientAngle = -Math.PI/2;//top refelction so change the angle accordingly } } else{//this is left or right mode mwidth = -rect.width;//sift the width for drawing mheight = 0; mX = -1;//flip the scaleX mY = 1; Xreg = -Xoffset-rect.width;//catch up the x registration Yreg = Yoffset;//catch up the y registration if(currentDirection == "right"){ gradientAngle = Math.PI;//set the gradient angle for right mode } else{ gradientAngle = 0;//set the gradient angle for left mode } } matrix.translate(mwidth,mheight);//shift the matrix matrix.scale(mX,mY);//scale the matrix bmd = new BitmapData(rect.width, rect.height, true, 0x00FF0000); //create a new bitmapdata bmd.draw(temp, matrix, null, null, null, true);//draw using our matrix bitmap.bitmapData = bmd;//assign our bitmapdata to our bitmap bitmap.x = Xreg; //catch up the registration bitmap.y = Yreg;//catch up the registration[/as]

The neat thing here is that not only did we draw respecting the registration of the target object but the reflection instance has also the same registration than the target object but flipped as well. Here I added a little circle to show the registration point and both the target object and the reflection have their registration point in the center of the stage. The target object rotates and automatically the reflection rotates in opposite way. Click on the stage to change the direction mode and see that the registration point of the reflection is always the opposite of the registration point of the target object:


Now our reflection is similar to our target object in all major points and we already reassign scaleX, scaleY and rotation properties from the target object to the reflection object:

[as]scaleX = SCALEX; scaleY = SCALEY; rotation = -angle;//the rotation should be opposite[/as]


Since we draw the object at its normal size, if the object is scaled we might end up with a loss of quality as you might notice here. We can of course avoid this by adding a quality factor. We’ll see that later but for now let’s work on the gradient.


Creating the Gradient

Let’s create a method that creates the gradient. We will then position our masker object the same way than our bitmap object:

[as]createGradient(); masker.x = Xreg;//masker is our mask! masker.y = Yreg;[/as]

Now let’s create our method:

[as]private function createGradient():void{ var W:Number = rect.width; var H:Number = rect.height; var myMatrix:Matrix = new Matrix();
myMatrix.createGradientBox(W, H, gradientAngle, 0, 0);//create our gradient box masker.graphics.clear();//make sure we clear what we drew earlier masker.graphics.beginGradientFill(GradientType.LINEAR, _colors, _alphas, _ratios, myMatrix); masker.graphics.drawRect(0, 0, W, H);//and draw our gradient }[/as]

And here is the result with a left mode reflection:


Now if we rotate a bit:


Not good. The gradient does not follow the angle. We can fix that by taking into account the actual angle of the target object:

[as]gradientAngle = gradientAngle+(angle*Math.PI/180);//angle is the angle of the target object //here we translate degrees in radians[/as]

And the result is perfect, now click on the stage to animate this a little and see the gradient is always in line with the target object:


We are left with the filter problem. We want to be able to draw any filters that are associated with the target object but the difficulty here is that filters can alter the size of the target object without us being directly able to get this new size. Let’s see if we can find a solution to this problem in the next section.


Dealing with Filters

If we apply a shadow to our target object a simple BimapData draw (bottom) gives us this:


The shadow is ignored because the size of the BitmapData matches only the size of the target object without the filter. If we make the BimapData bigger then the shadow would be drawn. Here I manually added 10 pixels in the width and height of our BitmapData:


So our only difficulty is to make sure our BitmapData is big enough to draw the target object with any filter associated. The gradient should then be as big as the BitmapData. So what we need to do is, keep the filters associated with the target object, determine how big our BitampData should be, do any matrix translation if need be, and draw the target object in our bitmap.

So let’s get started on this. First, we change the getTargetProperty() method so we don’t remove the filters from the target object. Second, we work with the getRegistration() method. This is where logically we’ll get the real size of our object. No need to run any code for this if there are no filters associated with our target object so this is of course inside a conditional:

[as]private function getRegistration():void{ if(TargetDisplayObject.filters.length > 0){//no filters? no need to run this code then var temp:BitmapData = new BitmapData(TargetDisplayObject.width, TargetDisplayObject.height, true, 0x00ffffff); //create a simple bitmapdata with the size of our target object temp.floodFill(0,0,0xFFFF0000);//fill the bitmapdata with some color var tempRect:Rectangle = new Rectangle(0, 0, temp.width, temp.height);//make a rectangle out of our bitmapdata filteredRect = tempRect.clone();//make another one just like the preceding for(var i:Number = 0; i<TargetDisplayObject.filters.length; i++){//run a for loop as many times as there are filters var adding:Rectangle = temp.generateFilterRect(tempRect, TargetDisplayObject.filters[i]); //this is the cool method that allows us to find out the real size with filters filteredRect = filteredRect.union(adding);//merge each new rectangle to the preceding } Xfilter = filteredRect.x;//If we need any registration catch up due to filters that will be stored here Yfilter = filteredRect.y;//If we need any registration catch up due to filters that will be stored here rect = TargetDisplayObject.getBounds(TargetDisplayObject.stage); } else{//no filters so run a normal code rect = TargetDisplayObject.getBounds(TargetDisplayObject.stage); filteredRect = rect.clone(); Xfilter = 0; Yfilter = 0; } Xoffset = rect.x+Xfilter;//set the registration catch up with any filter catch up is nessecary Yoffset = rect.y+Yfilter; }[/as]

That’s a bit nasty I know. Basically we create a bimapdata the size of the actual target object, then we “test” all filters with this bitmapdata and its method generateFilterRect() which tests a filter. Then we generate a rectangle instance and add each generated rectangle together to form the maximum size needed. Finally, we assign the result to our new member variable filteredRect. Next we need to modify our createReflection() method a bit:

[as]matrix.translate(-Xoffset+Xfilter, -Yoffset+Yfilter); //now we translate our matrix with also the x and y property of our filteredRect //rectangle variable since now our filteredRect variable might have negative x and y property because of the filters.[/as]

We also need to move the final bitmap and gradient a bit:

[as]bitmap.x = Xreg+(Xfilter/2); //divide by 2 to get a balance bitmap.y = Yreg+(Yfilter/2); masker.x = Xreg+(Xfilter/2); masker.y = Yreg+(Yfilter/2);[/as]

And we are done:


All of our problems have been answered but to make this class usable we need to give the user a lot more options. Let’s see that in the next section.


Setting the Quality

If the target object is scaled we might start to see a loss in the quality of the reflection. This is because we draw the target at its normal size so when it’s scaled, as any bitmap would do, there is a loss of quality. So we should answer this by giving the user of the class the possibility to set a quality. Finally we should also be able to set if we need the reflection to be redrawn or not. So let’s define 3 new constants for the quality:

[as]public static const NORMAL:String = "notmal"; public static const AUTO:String = "auto"; public static const HIGH:String = "high"; [/as]

The normal mode is how the class is working so far. The auto mode will set the quality according to the actual target object scale factor. The high mode will set the quality to the highest possible resolution. The BitmapData we create is simply made of pixels. To raise its resolution we just need to increase the amount of pixels. Once we have done that we can simply resize the bitmap holding our BitmapData to the target object size. We of course create setter and getter for the quality and you already know how we do that so let’s move on directly to the two new methods we create. All we really need to do is multiply some key values by a scale factor. Obviously in normal mode this value will be 1 as in 10*1 = 10, meaning no change. In auto mode we multiply by the actual target object scale factor but there can be a different scale factor for the width and height so we need to get the highest value and we also need to make sure we don’t have a scale factor that will make our BitmapData exceed the maximum size allowed in Flash Player 9 which is 2880 pixels (note that Flash Player 10 allows for bigger values). So here is the method that calculates this:

[as]private function getAutoQualityFactor():Number{ var factor:Number = Math.max(SCALEX, SCALEY); //get the biggest value between the two target object scale factors var isTooBig:Boolean = true;//start assuming the scale is too big while(isTooBig){ //while the scale factor is too big if(TargetDisplayObject.width*factor < 2880 && TargetDisplayObject.height*factor < 2880){ isTooBig = false;//if we are under 2880 then break the while loop
}else{factor--; }//else decrease the scale factor } return factor; } [/as]

Now to get the highest factor possible:

[as]private function getBestQualityFactor():Number{ var factor:Number = Math.max(2880/TargetDisplayObject.width, 2880/TargetDisplayObject.height); //we divide the maximum number of pixel allowed by our actual width and height and get the highest of these two values var isTooBig:Boolean = true;//let's assume it's too big to start with while(isTooBig){ if(TargetDisplayObject.width*factor < 2880 && TargetDisplayObject.height*factor < 2880){ isTooBig = false;//if we are good then break the while loop } else{factor--; }//else decrease the scale factor } return factor; }[/as]

Now let’s add our scale factor value to our code. We start with some if else statements:

[as]if(currentQuality == "normal"){ qualityScaleFactor = 1; } else if(currentQuality == "auto"){
qualityScaleFactor = getAutoQualityFactor(); } else if(currentQuality == "high"){ qualityScaleFactor = getBestQualityFactor(); }[/as]

qualityScaleFactor is the member variable we’ll use. Here depending on the quality option choosen we set this variable to the right value.

[as]var temp:BitmapData = new BitmapData(rect.width*qualityScaleFactor, rect.height*qualityScaleFactor, true, 0x0000FF00);[/as]

Create a big enough BitmapData. Here we multiply the rect width and height by the scale factor.

[as]matrix.translate((-Xoffset+Xfilter)*qualityScaleFactor, (-Yoffset+Yfilter)*qualityScaleFactor); matrix.scale(qualityScaleFactor, qualityScaleFactor);[/as]

Our matrix must catch up with the new size. Here we move the matrix according to the new size. We also of course scale the matrix accordingly.

[as]mheight = -filteredRect.height*qualityScaleFactor;[/as]

Depending on the direction chosen we also catch up the offset by multiplying by the scale factor.

[as]matrix = new Matrix(); matrix.translate(mwidth,mheight); matrix.scale(mX,mY); bmd = new BitmapData(temp.width, temp.height, true, 0x00FF0000); bmd.draw(temp, matrix, null, null, null, true);[/as]

Here we draw our final BitmapData according to the scaled first BitmapData.

[as]bitmap.width = rect.width; bitmap.height = rect.height;[/as]

And finally we reset the size by giving our bitmap the right dimensions. Now if we scale our target object and set our quality to normal we get this:


Do you see the pixilated circle? Our BitmapData is about 100×100 pixels here. Now let’s set the quality to auto:


That looks way better. Here our BitmapData is about 300×300 pixels. Finally let’s try to set to high:


Very nice too. Here our BitmapData is about 2800×2800 pixels which is big! Obviously we’ll want to keep the high quality for a particular case. The auto quality will be mostly the one we want if we are going to scale the target object a lot. In all other cases the normal mode will do just fine.


Saving Resources

Let’s see a simple example of what we have so far:


The reflection follows perfectly. The problem we have here is that we redraw constantly the target object but the target object does not change. Only its scale or rotation property changes so we should be able to save resources by turning off the redrawing. We’ll create a setter and getter for an update property with a value of true or false:

[as]public function get update():Boolean{ return updating; }[/as] [as]public function set update(T:Boolean):void{ updating = T; if(T){ redrawReflection(); } }[/as]

Updating is our new boolean member variable. We simply set it to true or false. If it’s false we won’t constantly redraw the target object. We’ll leave it to true by default. Now in our code:

[as]if(!updating && bitmap.bitmapData != null){[/as]

We simply check if updating is false meaning we don’t want to redraw the target object. We also check to see if we already drew something in which case the bitmap.bitmapData won’t be null. So if updating is false and bitmap.bitmapData is not null then we skip most of our drawing code. On the other hand if updating is true or bitmap.bitmapData is null then we draw. Here is another example:


Another example with update set to true. This time we draw constantly the target object since the target object has moving objects (the wheels):


Our class is ready! You can download all files here.

A few key points:

The stage.invalidate() and RENDER event allow us to run a display related code only once for each screen update. This saves CPU resources so don’t forget to include this design in all your display related class.

Our class does not provide an auto update drawing (Timer driven or else). This is because I always prefer to let the programmer choose how he/she wants to manage the CPU resources.

The key classes we used are the BitampData, Matrix and Rectangle.


Don’t hesitate to leave a comment!