sevenson.com.au logo

Reflection in ActionScript 3

I was creating an online gallery for a project at work when came across the need for dynamically created reflections. We had an existing class that we usually used for reflections, but it was a bad port from AS2 to AS3 and I was never happy with it. It tended to be hard to work with and pretty inefficient.

In an attempt to make things better, and make my life easier in the process, I sat down and created my own Reflection class.

Here is what I came up with.

Below is a sample of the result. The moving text shows how elements fade away from the reflection point. Also, if you click and drag in the top half of the image you will see how the reflection can update dynamically.

Click to open demo

The idea behind the class is pretty simple. Basically, you create a new Reflection object and then pass it any display object that you want to reflect. Then, whenever you tell it to ‘refresh’, it will look at the target object, copy the bottom area, flip and fade it.

It uses the bounding rectangle of the target object to figure out what to reflect, but you can also override this by specifying a rectangle area to reflect.

You can also specify a number of different things, such as how tall you want the reflection be, how often it should refresh, and how transparent it should start and finish.

Code:

Here is the code for the reflection class. Feel free to use it however you see fit.

/**
 * com.sevenson.BasicReflection
 * 
 * 
 * @author Andrew Sevenson
 * @version 1.0
 * 
 * This is a basic reflection class. It takes a target display object and reflects
 * the bottom section of it.  It allows you to set up options such as how tall the
 * reflection should be, how much of an alpha it should have, and what ratios it 
 * should fade off like.
 * 
 * There is also the option of a timer that can be set up to auto refresh the
 * reflection.
 * 
*/

package com.sevenson
{
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.DisplayObject;
	import flash.display.Graphics;
	import flash.display.Shape;
	import flash.display.Sprite;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.utils.Timer;
	import flash.events.TimerEvent;
	import flash.events.Event;
	import flash.display.SpreadMethod;
	import flash.geom.Matrix;
	import flash.display.GradientType;
	
	/**
	 * The BasicReflection class
	 */
	public class BasicReflection extends Sprite
	{
		
		// vars
		protected var _bmp:Bitmap;				// bmp holding the reflection
		protected var _bmpData:BitmapData;			// the data of the reflection
		protected var _target:DisplayObject;			// the object you are reflecting
		protected var _refreshRate:int = -1;			// how often this should auto refresh
		protected var _timer:Timer;				// controls when this reflection is updated
		protected var _gradient:Shape;
		protected var _gradientContainer:Sprite;
		protected var _init:Boolean = false;
		protected var _lastGradientBMP:BitmapData;		// holds the last graident used - so you can avoid re-creating all the tiem
		
		protected var _reflectionRect:Rectangle;
		protected var _startAlpha:Number = 0.5;			// 0 - 1
		protected var _startRatio:Number = 0x00;		// 0x00 - 0xff
		protected var _reflectionHeight:uint = 50;		// how big the reflection should be
		
		
		
		/**
		 * Creates a new instance of the BasicReflection class
		 */
		public function BasicReflection ($reflectionHeight:Number=50, $startAlpha:Number=0.5, $startRatio:Number=0x00)
		{
			reflectionHeight = $reflectionHeight;
			startAlpha = $startAlpha;
			startRatio = $startRatio;
			//
			setupTimer();
			setupBMP();
			generateGradient();
			//
		}
		
		
		// STATIC PUBLIC FUNCTIONS
		// ------------------------------------------------------------------------------------------
		
		
		// STATIC PRIVATE FUNCTIONS
		// ------------------------------------------------------------------------------------------
		
		
		// PUBLIC FUNCTIONS
		// ------------------------------------------------------------------------------------------
		/**
		 * A clean up routine that destroys all external references to prepare the class for garbage collection
		 */
		public function destroy () : void
		{
			_bmp.bitmapData = null;
			if (_lastGradientBMP) _lastGradientBMP.dispose();
			if (_bmpData) _bmpData.dispose();
			if (parent) parent.removeChild(this);
		}
		
		
		/**
		 * Trigger a refresh of the reflection
		 */
		public function refresh():void {
			updateReflection();
		}

		
		// PRIVATE FUNCTIONS
		// ------------------------------------------------------------------------------------------
		
		/**
		 * Sets up the timer used to auto refresh the bitmap
		 */
		protected function setupTimer():void {
			_timer = new Timer(1000);
			_timer.addEventListener(TimerEvent.TIMER, updateReflection, false, 0, true);
		}
		
		
		/**
		 * Sets up a mask to use with the reflection
		 */
		private function generateGradient():void{

			//get a reference to the movie clip - cast the DisplayObject that is returned as a MovieClip
			if (_gradientContainer == null) {
				_gradientContainer = new Sprite();
			}
			if (_gradient == null) {
				_gradient = new Shape();
				_gradientContainer.addChild(_gradient);
			}
			
			var fillType:String = GradientType.LINEAR;
			var colors:Array = [0x000000, 0xffffff];
			var alphas:Array = [_startAlpha, 0];
			var ratios:Array = [_startRatio, 0xFF];
			var matr:Matrix = new Matrix();
			matr.createGradientBox(100, 100, (-90/180)*Math.PI, 0, 0);
			var spreadMethod:String = SpreadMethod.PAD;
			
			var $g:Graphics = _gradient.graphics;
			$g.clear();
			$g.beginGradientFill(fillType, colors, alphas, ratios, matr, spreadMethod);  
			$g.drawRect(0,0,100,100);
			
			// flag as initialised
			_init = true;
			
			// kill of the gradient
			if (_lastGradientBMP) _lastGradientBMP.dispose();
			_lastGradientBMP = null;
			
		}	
		
		
		/**
		 * Sets up the bitmap that is to be displayed
		 */
		protected function setupBMP():void {
			//
			_bmp = new Bitmap(_bmpData, "auto", true);
			//_bmp.cacheAsBitmap = true;
			_bmp.scaleY = -1;
			addChild(_bmp);
		}
		
		
		// EVENT HANDLERS
		// ------------------------------------------------------------------------------------------
		
		
		/**
		 * Updates the reflection graphic
		 * @param	e
		 */
		private function updateReflection(e:Event = null):void {
			// clear out the current reflection
			_bmp.bitmapData = null;
			if(_bmpData) _bmpData.dispose();
				
			// create the new reflection
			if (_target == null) return;
			
			// set up a working rect
			var $workingRect:Rectangle;
			
			// see if you are using a target rect, or if you have to generate your own area
			if(_reflectionRect==null) {
				// get the rect of the content
				var $contentRect:Rectangle = _target.getRect(_target);
				// figure out which height to use
				var $height:Number = (_target.height < _reflectionHeight) ? _target.height : _reflectionHeight;
				$height = Math.floor($height);
				// set teh working height to be similar to the content, but using only the 'reflection' height
				$workingRect = $contentRect.clone();
				$workingRect.top = $workingRect.bottom - $height;
				
			} else {
				// use the target rect as the working rect
				$workingRect = _reflectionRect;
			}
				
			// figure out the matrix to use for the draw method
			var $matr:Matrix = new Matrix(1, 0, 0, 1, -($workingRect.left), -$workingRect.top );
			
			// create a base bmp data
			var $baseBmpData:BitmapData = new BitmapData(Math.ceil($workingRect.width), $workingRect.height, true, 0x00ff0000);
			$baseBmpData.draw(_target, $matr, null, null, null, true);
			
			// figure out if you need to generate a new reflection bmp
			if ((_lastGradientBMP == null) || (_lastGradientBMP.width != $baseBmpData.width || _lastGradientBMP.height != $baseBmpData.height)) {
				if (_lastGradientBMP) _lastGradientBMP.dispose();
				// generate the gradient data
				_gradient.width = $baseBmpData.width;
				_gradient.height = $baseBmpData.height;
				_lastGradientBMP = new BitmapData($baseBmpData.width, $baseBmpData.height, true, 0x00000000);
				_lastGradientBMP.draw(_gradientContainer, null, null, null, null, true);
			}
			
			// create the final data
			_bmpData = new BitmapData($baseBmpData.width, $baseBmpData.height, true, 0xffffffff);
			_bmpData.copyPixels($baseBmpData, new Rectangle(0, 0, $baseBmpData.width, $baseBmpData.height), new Point(0, 0), _lastGradientBMP, new Point(0, 0), false);
			_bmp.bitmapData = _bmpData;
			
			// position the bmp
			_bmp.y = $workingRect.height;
			_bmp.x = int($workingRect.x);
			
			$baseBmpData.dispose();
			
		}
		
		
		
		// GETTERS & SETTERS
		// ------------------------------------------------------------------------------------------
		
		
		/**
		 * Sets the target display object that is reflected
		 */
		public function get target():DisplayObject { return _target; }
		public function set target(value:DisplayObject):void {
			_target = value;
			updateReflection();
		}		
		
		/**
		 * Sets how often the reflection auto updates
		 */
		public function get refreshRate():int { return _refreshRate; }
		public function set refreshRate(value:int):void {
			_refreshRate = value;
			_timer.stop();
			if (_refreshRate > 0) {
				_timer.delay = _refreshRate;
				_timer.start();
			}
		}
		
		
		/**
		 * How tall te reflection should be - ignored if using a target rect
		 */
		public function get reflectionHeight():uint { return _reflectionHeight; }
		public function set reflectionHeight(value:uint):void 
		{
			_reflectionHeight = value;
			if (_init) generateGradient();
		}
		
		/**
		 * What rectangle area to 'reflect' in the target
		 */
		public function get reflectionRect():flash.geom.Rectangle { return _reflectionRect; }
		public function set reflectionRect(value:flash.geom.Rectangle):void 
		{
			_reflectionRect = value;
		}		
		
		/**
		 * The start ratio of the reflection
		 */
		public function get startRatio():Number { return _startRatio; }
		public function set startRatio(value:Number):void 
		{
			_startRatio = value;
			if (_init) generateGradient();
		}
		
		/**
		 * The inital alpha value for the reflection
		 */
		public function get startAlpha():Number { return _startAlpha; }
		public function set startAlpha(value:Number):void 
		{
			_startAlpha = value;
			if (_init) generateGradient();
		}
		

		
		
		
	}
}

 


1 thought on “Reflection in ActionScript 3

  1. Hi Andrew

    Wow, thanks for a brilliant class. Usually I use a reflection class from pixelfumes.com, but I don’t think it’s working well with dynamic textfields. However, your class does and it’s pretty easy to use.

    A little usage information would be nice though, but I figured it out. For anyone else, this class creates a sprite that you have to add to the display list and place with X and Y coordinates. Have a look in the classfile for setting alpha and other options…

    Joel Grøndrup


Leave a Reply

Your email address will not be published. Required fields are marked *

Name *

This site uses Akismet to reduce spam. Learn how your comment data is processed.


sevenson.com.au