Visual Reflections in AS2
I’m currently working on a project where I’m using a bit of reflection, and although I found a couple of reflection classes on the web, I was looking for something that worked out of the box without too much hassle.
Basically I was looking for something that allowed me to do new Reflection(myClip) and be done with it 9 times out of 10.
Making a reflection is not that hard (although I need to work on the alpha and falloff, will post a new version later), but getting it to work right with scaled clips with weird registration points was a little harder.
Anyway I think I came up with something nice, and it performs quite well. You can download the source and a bunch of demo’s here Reflection demo’s
Here is a list of features:
A couple samples:
The code:
import flash.display.BitmapData;import flash.geom.Matrix;import flash.geom.Rectangle;/*** ReflectionManager manages the reflection for one clip.** Short feature set:* - simple setup by default. In most cases doing new ReflectionManager (sourceClip) will suffice.* - default setup is a clip that fades from 40 to 0 alpha at 40% of the image.* - you can change the falloff height, overall alpha, and the redraw interval (eg to mirror video)* - retrievable reflection and source clips to apply filters or otherwise control clips* - redraw interval is disconnected from framerate, specify a low redraw interval to save system resources* - works with scaled, non top left regpoints, and masked clip, using an autobounds detection algorithm* - works with negative scaled clips* - tries to warn you in most cases when the result is likely to be different from what you expect* - very high performance* - no dependencies on non flash classes** @author J.C. Wichman, TriMM Interactive Media (www.trimm.nl / j.c.wichman@trimm.nl),* ObjectPainters.com (blog.objectpainters.com / j.c.wichman@objectpainters.com)*/class nl.trimm.movieclip.Reflection {private var _sourceClip:MovieClip = null; //reference to the source you want to mirrorprivate var _sourceClipParentBounds:Rectangle = null; //the source bounds as interpreted by the parentprivate var _sourceClipLocalBounds:Rectangle = null; //the source bounds as interpreted by source itself//same as parentbounds for nonscaled, nontranslated clipsprivate var _reflectionClip:MovieClip = null; //contains _reflectionClipContent and _reflectionClipMaskprivate var _reflectionClipContent:MovieClip = null; //contains resultbitmap into which source reflection is drawnprivate var _reflectionClipMask:MovieClip = null; //contains alpha mask used as fade out effectprivate var _redrawMatrix:Matrix = null; //contains all transformation necessary to draw a scaled,//translated and flipped reflection of the source into the destprivate var _redrawRectangle:Rectangle = null; //specifies clip on redrawing methodprivate var _resultBitmap:BitmapData = null; //contains result of drawing source into dest through matrixprivate var _updateIntervalId:Number = null; //redraw interval, use null for static imagesprivate var _autoClear:Boolean = false; //auto clear the bitmap before redrawing/*** The purpose of this reflection class is to make it as simple as possible to provide a working reflection,* yet give you the freedom to adjust what's needed.** Restrictions:* - source and reflection share the same parent* - source should not be scaled, if you want to scale, either wrap the scaled clip, or scale the parent** @param pTarget the object you want to create a reflection for* @param pAlpha the alpha of the reflection 0..1* @param pHeight the height of the reflection, 1 is the whole image height is used as fall off, 0 is no reflection, and* everything in between (0 .. 1)* @param pFrameRate the framerate for the redraw method* @param pBounds explicit bounds that define the area to be reflected, if these are not passed, bounds are auto detected,* based on what's visible at the moment of creation. The bounds are relative to the source clips coordinate* system. In other words, IMAGINE you edit the source clip, draw a rectangle around the area you want to see* reflected, and write down those bounds, THAT area defines the boundsrectangle.* @param pCustomClip an empty clip on the same parent as pSource that you would like to display the reflection.* The only reason I can think of to do so, is that this will allow you to apply filters to this* clip through the IDE instead of filter classes, which might be convenient for non coders.* @param pAutoClear often it's not necessary to auto clear the canvas again before redrawing, UNLESS you are moving* your content, and your content is transparent. By default autoclear is off, but you might want* to enable it.*/public function Reflection(pSource:MovieClip, pAlpha:Number, pHeight:Number, pFrameRate:Number, pBounds:Rectangle, pCustomClip:MovieClip, pAutoClear:Boolean) {if (pAlpha != null && (pAlpha < 0 || pAlpha > 1)) trace ("pAlpha has to be > 0 < 1");if (pHeight != null && (pHeight < 0 || pHeight > 1)) trace ("pHeight has to be > 0 < 1");if (_global.warn == null) _global.warn = trace;_sourceClip = pSource;_setupReflection(pAlpha == null?40:pAlpha * 100,pHeight == null?0.4:pHeight,pBounds,pCustomClip);if (pFrameRate != null) setAutoUpdateFrameRate (pFrameRate);if (pAutoClear != null) _autoClear = pAutoClear;}/*** Create a gradient required to create the fading out alpha effect*/private function _setupReflection(pAlpha:Number, pHeight:Number, pBounds:Rectangle, pCustomClip:MovieClip) {//store short reference to the parent that contains our source, it will contain the reflection and//reflection alpha mask as wellvar lReflectionParent:MovieClip = _sourceClip._parent;if (pCustomClip == null) {_reflectionClip = lReflectionParent.createEmptyMovieClip ("reflection_[" + _sourceClip._name + "]",lReflectionParent.getNextHighestDepth());} else {if (pCustomClip._parent != lReflectionParent) {_global.warn ("WARNING:the clip you have passed is not on the same timeline as the clip to reflect.");}_reflectionClip = pCustomClip;}if (pBounds == null || (pBounds.width == 0 && pBounds.height == 0)) {//now get the local boundsvar lBounds:Object = _sourceClip.getBounds(_sourceClip);_sourceClipLocalBounds = new Rectangle (lBounds.xMin, lBounds.yMin, lBounds.xMax - lBounds.xMin, lBounds.yMax - lBounds.yMin);//an important thing to note is that flash returns a _width and _height based on unmasked content.//if you have a 100x100 clip, with a topleft mask of 50x50, the bounds will be based on the 100x100 size,//not on the 50x50. This causes an issue in a number of cases, and reflecting the clip is one of them:)//therefore we are going to check the visible area using the bounds we just established to check if we need to//adjust our bounds//we create a bitmap to draw the clip into firstvar lBoundsDetectionBitmap:BitmapData = new BitmapData (_sourceClipLocalBounds.width, _sourceClipLocalBounds.height, true, 0x0);var lBoundMatrix:Matrix = new Matrix();//enforce top leftlBoundMatrix.translate ( -_sourceClipLocalBounds.x, -_sourceClipLocalBounds.y);lBoundsDetectionBitmap.draw (_sourceClip, lBoundMatrix);//now detect visible area in the bitmap, and translate it back to the original originvar lVisibleBounds:Rectangle = lBoundsDetectionBitmap.getColorBoundsRect(0xff000000, 0xff000000, true);lVisibleBounds.offset (_sourceClipLocalBounds.x, _sourceClipLocalBounds.y);//dispose tha bitmaplBoundsDetectionBitmap.dispose();lVisibleBounds.width = Math.ceil (lVisibleBounds.width);_sourceClipLocalBounds = lVisibleBounds;} else {_sourceClipLocalBounds = pBounds;}//we can establish parent bounds using _sourceClip.getBounds(lReflectionParent), but the bounds might have//been prepassed or altered by getColorBoundRect detection, established etc, so we do it through globalToLocal,//localToGlobal so instead of//var lBounds:Object = _sourceClip.getBounds(lReflectionParent);//_sourceClipParentBounds = new Rectangle (lBounds.xMin, lBounds.yMin, lBounds.xMax - lBounds.xMin, lBounds.yMax - lBounds.yMin);//we dovar xMinyMin:Object = { x: _sourceClipLocalBounds.x, y: _sourceClipLocalBounds.y };var xMaxyMax:Object = { x: _sourceClipLocalBounds.x+_sourceClipLocalBounds.width, y: _sourceClipLocalBounds.y+_sourceClipLocalBounds.height};_sourceClip.localToGlobal (xMinyMin);_sourceClip.localToGlobal (xMaxyMax);lReflectionParent._parent.globalToLocal (xMinyMin);lReflectionParent._parent.globalToLocal (xMaxyMax);var lWidth:Number = xMaxyMax.x - xMinyMin.x;var lHeight:Number = xMaxyMax.y - xMinyMin.y;_sourceClipParentBounds = new Rectangle (xMinyMin.x, xMinyMin.y, Math.abs(lWidth), Math.abs(lHeight));//note that for an unscaled clip with left top registration these bounds will be equal//we will use the parent bounds as much as possible, in short this means that if you are reflecting a movieclip//that is 1000x1000 but scaled down to 10%, the reflection will only create a bitmap sized 100x100//beside offsetting, we need to establish a reflection height, since we might want a greater falloff than a complete reflectionvar lReflectionHeight:Number = _sourceClipParentBounds.height * pHeight;//we create a clip to be used as mask, and we draw an alpha gradient on it (yes it's possible:))_reflectionClipMask = _reflectionClip.createEmptyMovieClip ("mask", 0);_reflectionClipMask.lineStyle (0, 0x0, 0);var lBottomGradientMatrix:Matrix = new Matrix();lBottomGradientMatrix.createGradientBox(_sourceClipParentBounds.width, lReflectionHeight, Math.PI/2, 0, 0);_reflectionClipMask.beginGradientFill ("linear", [0xffffff, 0xffffff], [pAlpha, 0], [0, 255], lBottomGradientMatrix);_reflectionClipMask.moveTo (0, 0);_reflectionClipMask.lineTo (_sourceClipParentBounds.width, 0);_reflectionClipMask.lineTo (_sourceClipParentBounds.width, lReflectionHeight);_reflectionClipMask.lineTo (0, lReflectionHeight);_reflectionClipMask.lineTo (0, 0);_reflectionClipMask.endFill();//now create reflection container_reflectionClipContent = _reflectionClip.createEmptyMovieClip ("content", 1);//and we set up our resulting bitmap_resultBitmap = new BitmapData (_sourceClipParentBounds.width, lReflectionHeight, true, 0x0);_reflectionClipContent.attachBitmap (_resultBitmap, 0, "none", true);//align the reflection clip to the source_reflectionClip._x = _sourceClipParentBounds.x;_reflectionClip._y = _sourceClipParentBounds.y + _sourceClipParentBounds.height;if (lWidth < 0) {_global.warn ("Source clip has been scaled negatively horizontally, trying to adjust, this will hurt performance." +"Try to wrap the source clip in another clip.");_reflectionClipContent._xscale = -100;_reflectionClipContent._x += _reflectionClipContent._width;_reflectionClip._x -= _reflectionClipContent._width;}if (lHeight < 0) {_global.warn ("Source clip has been scaled negatively vertically, trying to adjust, this will hurt performance." +"Try to wrap the source clip in another clip.");_reflectionClipContent._yscale = -100;_reflectionClipContent._y += _reflectionClipContent._height;_reflectionClip._y -= _reflectionClipContent._height;}//hook up the mask and set cacheAsBitmap to true so the alpha in the mask is applied to_reflectionClipContent.setMask (_reflectionClipMask);_reflectionClipContent.cacheAsBitmap = true;_reflectionClipMask.cacheAsBitmap = true;//the redraw matrix is just one matrix, no matter how many operations you add. So it doesn't hurt//to keep operations separated for clarity. View the redraw matrix from the perspective of the source clip:_redrawMatrix = new Matrix();//first we 'enforce' a top left registration point_redrawMatrix.translate ( -_sourceClipLocalBounds.x, -_sourceClipLocalBounds.y);//then we scale the relation between parent and child, if the parent bounds for clip a say the clip is 200 wide,//and the local bounds say 400, appearently is has been scaled by 50 percent_redrawMatrix.scale (_sourceClipParentBounds.width/_sourceClipLocalBounds.width, _sourceClipParentBounds.height/_sourceClipLocalBounds.height);//now flip the whole thing upside down_redrawMatrix.scale (1, -1);//and move it down again to we can see it_redrawMatrix.translate (0, _sourceClipParentBounds.height);//everything is tranformed so it fits within this rectangle:_redrawRectangle = new Rectangle (0, 0, _sourceClipParentBounds.width, lReflectionHeight);//if original clip has filters attached, warn.if (_reflectionClip.filters.length > 0) {_global.warn ("Warning source clip has filters. You might want to wrap this clip in a parent clip without filters, " +"or pass correct bounds. You might want to add yourReflection.getReflection().filters = yourSourceClip.filters.");}//redraw once to get startedredraw();}public function redraw() {if (_autoClear) {//seems faster than a fillRect but fillRect less processor intensive//_resultBitmap.dispose();//_resultBitmap = new BitmapData (_redrawRectangle.width, _redrawRectangle.height, true, 0x0);//_reflectionClipContent.attachBitmap (_resultBitmap, 0, "none", true);_resultBitmap.fillRect (_redrawRectangle, 0x0);}_resultBitmap.draw (_sourceClip, _redrawMatrix, null, null, _redrawRectangle, true);}public function setAutoUpdateFrameRate(pFrameRate:Number) {//clear any old intervalsif (_updateIntervalId != null) {clearInterval (_updateIntervalId);_updateIntervalId = null;}//if no interval specified exitif (pFrameRate == null) return;_updateIntervalId = setInterval (this, "redraw", Math.round (1000/pFrameRate));}public function setAutoClear (pAutoClear:Boolean) {_autoClear = pAutoClear;}public function getReflection():MovieClip {return _reflectionClip;}public function getSourceClip():MovieClip {return _sourceClip;}public function finalize() {setAutoUpdateFrameRate (null);_sourceClip = null;_reflectionClipContent.setMask (null);_reflectionClipContent = null;_reflectionClipMask = null;_reflectionClip.removeMovieClip();_reflectionClip.filters = null;_reflectionClip = null;_resultBitmap.dispose();}}- Download this code: Reflection.as
A couple of screenshots of the other demo’s:

November 24th, 2008 at 12:12 pm
One note, if (_reflectionClip.filters.length > 0) should be if (_sourceClip.filters.length > 0), I’ll fix this tonight. It ony applies to the warnings given not the functionality
November 24th, 2008 at 3:52 pm
Looks awesome!
November 26th, 2008 at 10:33 am
[…] Op het blog van Hans (mijn zwager) zag ik dat hij bezig is met een hele makkelijke manier te programeren om van die mooie reflecties te krijgen met flash. Dus misschien voor de webnerds onder ons interessant om even te kijken. Source files en uitleg staan er allemaal bij. objectpainters.com […]