PageRenderTime 51ms CodeModel.GetById 21ms app.highlight 23ms RepoModel.GetById 0ms app.codeStats 0ms

/src/com/greensock/BlitMask.as

https://bitbucket.org/HopeSky/mars_nd2d
ActionScript | 892 lines | 560 code | 58 blank | 274 comment | 197 complexity | 0af6dbad5db5a563a7b6f1a451da3188 MD5 | raw file
  1/**
  2 * VERSION: 0.6
  3 * DATE: 2012-01-20
  4 * AS3
  5 * UPDATES AND DOCS AT: http://www.greensock.com
  6 **/
  7package com.greensock {	
  8	import flash.display.BitmapData;
  9	import flash.display.DisplayObject;
 10	import flash.display.Graphics;
 11	import flash.display.Sprite;
 12	import flash.events.Event;
 13	import flash.events.MouseEvent;
 14	import flash.geom.ColorTransform;
 15	import flash.geom.Matrix;
 16	import flash.geom.Point;
 17	import flash.geom.Rectangle;
 18	import flash.geom.Transform;
 19/**
 20 * A BlitMask is basically a rectangular Sprite that acts as a high-performance mask for a DisplayObject
 21 * by caching a bitmap version of it and blitting only the pixels that should be visible at any given time,
 22 * although its <code>bitmapMode</code> can be turned off to restore interactivity in the DisplayObject 
 23 * whenever you want. When scrolling very large images or text blocks, a BlitMask can greatly improve 
 24 * performance, especially on mobile devices that have weaker processors. <br /><br />
 25 * 
 26 * Here are some of the conveniences BlitMask offers:<br />
 27 * <ul>
 28 * 		<li>Excellent scrolling performance</li>
 29 * 		<li>You don't need to do anything special with your target DisplayObject - move/scale/rotate it 
 30 * 			however you please and then <code>update()</code> the BlitMask and it syncs the pixels.
 31 * 			The BlitMask basically sits on top of the DisplayObject in the display list and you can 
 32 * 			move it independently too if you want.</li>
 33 * 		<li>Use the BlitMask's <code>scrollX</code> and <code>scrollY</code> properties to move the
 34 * 			target DisplayObject inside the masked area. For example, to scroll from top to bottom over 
 35 * 			the course of 2 seconds, simply do: <br /><code>myBlitMask.scrollY = 0;<br />
 36 * 			TweenLite.to(myBlitMask, 2, {scrollY:1});</code> </li>
 37 * 		<li>Use the "wrap" feature to make the bitmap wrap around to the opposite side when it scrolls 
 38 * 			off one of the edges (only in <code>bitmapMode</code> of course), as though the BlitMask is 
 39 * 			filled with a grid of bitmap copies of the target.</li>
 40 * 		<li>For maximum performance in bitmapMode, set <code>smoothing</code> to <code>false</code> or 
 41 * 			for maximum quality, set it to <code>true</code></li>
 42 * 		<li>You can toggle the <code>bitmapMode</code> to get either maximum performance or interactivity 
 43 * 			in the target DisplayObject anytime. (some other solutions out there are only viable for 
 44 * 			non-interactive bitmap content) </li>
 45 * 		<li>MouseEvents are dispatched by the BlitMask, so you can listen for clicks, rollovers, rollouts, etc.</li>
 46 * </ul>
 47 * 
 48 * @example Example AS3 code:<listing version="3.0">
 49 import com.greensock.~~;
 50 
 51 //create a 200x200 BlitMask positioned at x:20, y:50 to mask our "mc" object and turn smoothing on:
 52 var blitMask:BlitMask = new BlitMask(mc, 20, 50, 200, 200, true);
 53 
 54 //position mc at the top left of the BlitMask using the scrollX and scrollY properties
 55 blitMask.scrollX = 0;
 56 blitMask.scrollY = 0;
 57 
 58 //tween the scrollY to make mc scroll to the bottom over the course of 3 seconds and then turn off bitmapMode so that mc becomes interactive:
 59 TweenLite.to(blitMask, 3, {scrollY:1, onComplete:blitMask.disableBitmapMode});
 60 
 61 //or simply position mc manually and then call update() to sync the display:
 62 mc.x = 350;
 63 blitMask.update();
 64 
 65 </listing>
 66 * 
 67 * Notes:
 68 * <ul>
 69 * 		<li>BlitMasks themselves should not be rotated or scaled (although technically you can alter the scaleX and scaleY 
 70 * 			but doing so will only change the width or height instead). You can, of course, alter their x, y, width, 
 71 * 			or height properties as much as you want. </li>
 72 * 		<li>BlitMasks don't perform nearly as well in bitmapMode when the <code>target</code> is being scaled or rotated 
 73 * 			because it forces a flushing and recapture of the internal bitmap. BlitMasks are <b>MUCH</b> better when you are
 74 * 			simply changing x/y properties (scrolling) because it can reuse the same cached bitmap over and over.</li>
 75 * 		<li>If the target content is changing frequently (like if it has nested MovieClips that are animating on every frame),
 76 * 			you'd need to call update(null, true) each time you want the BlitMask to redraw itself to sync with the changes 
 77 * 			in the target, but that's a relatively expensive operation so it's not a great use case for BlitMask. You may
 78 * 			be better off just turning off bitmapMode during that animation sequence.</li>
 79 * </ul><br /><br />
 80 * 
 81 * <b>Copyright 2011-2012, GreenSock. All rights reserved.</b> This work is subject to the terms in <a href="http://www.greensock.com/terms_of_use.html">http://www.greensock.com/terms_of_use.html</a> or for corporate Club GreenSock members, the software agreement that was issued with the corporate membership.
 82 * 
 83 * @author Jack Doyle, jack@greensock.com
 84 **/
 85	public class BlitMask extends Sprite {
 86		/** @private **/
 87		public static var version:Number = 0.6;
 88		
 89		// In order to conserve memory and improve performance, we create a few instances of Rectangles, Sprites, Points, Matrices, and Arrays and reuse them rather than creating new instances over and over.
 90		/** @private **/
 91		protected static var _tempContainer:Sprite = new Sprite();
 92		/** @private **/
 93		protected static var _sliceRect:Rectangle = new Rectangle();
 94		/** @private **/
 95		protected static var _drawRect:Rectangle = new Rectangle();
 96		/** @private **/
 97		protected static var _destPoint:Point = new Point();
 98		/** @private **/
 99		protected static var _tempMatrix:Matrix = new Matrix();
100		/** @private **/
101		protected static var _emptyArray:Array = [];
102		/** @private **/
103		protected static var _colorTransform:ColorTransform = new ColorTransform();
104		/** @private **/
105		protected static var _mouseEvents:Array = [MouseEvent.CLICK, MouseEvent.DOUBLE_CLICK, MouseEvent.MOUSE_DOWN, MouseEvent.MOUSE_MOVE, MouseEvent.MOUSE_OUT, MouseEvent.MOUSE_OVER, MouseEvent.MOUSE_UP, MouseEvent.MOUSE_WHEEL, MouseEvent.ROLL_OUT, MouseEvent.ROLL_OVER];
106		
107		/** @private **/
108		protected var _target:DisplayObject;
109		/** @private **/
110		protected var _fillColor:uint;
111		/** @private **/
112		protected var _smoothing:Boolean;
113		/** @private **/
114		protected var _width:Number;
115		/** @private **/
116		protected var _height:Number;
117		/** @private **/
118		protected var _bd:BitmapData;
119		/** @private maximum number of pixels (minus one) that each BitmapData cell in the grid can be **/
120		protected var _gridSize:int = 2879;
121		/** @private **/
122		protected var _grid:Array;
123		/** @private **/
124		protected var _bounds:Rectangle;
125		/** @private **/
126		protected var _clipRect:Rectangle;
127		/** @private **/
128		protected var _bitmapMode:Boolean;
129		/** @private **/
130		protected var _rows:int;
131		/** @private **/
132		protected var _columns:int;
133		/** @private **/
134		protected var _scaleX:Number;
135		/** @private **/
136		protected var _scaleY:Number;
137		/** @private **/
138		protected var _prevMatrix:Matrix;
139		/** @private **/
140		protected var _transform:Transform;
141		/** @private **/
142		protected var _prevRotation:Number;
143		/** @private **/
144		protected var _autoUpdate:Boolean;
145		/** @private **/
146		protected var _wrap:Boolean;
147		/** @private **/
148		protected var _wrapOffsetX:Number = 0;
149		/** @private **/
150		protected var _wrapOffsetY:Number = 0;
151		
152		/**
153		 * Constructor
154		 * 
155		 * @param target The DisplayObject that will be masked by the BlitMask
156		 * @param x x coorinate of the upper left corner of the BlitMask. If <code>smoothing</code> is <code>false</code>, the x coordinate will be rounded to the closest integer.
157		 * @param y y coordinate of the upper right corner of the BlitMask
158		 * @param width width of the BlitMask (in pixels)
159		 * @param height height of the BlitMask (in pixels)
160		 * @param smoothing If <code>false</code> (the default), the bitmap (and the BlitMask's x/y coordinates) will be rendered only on whole pixels which is faster in terms of processing. However, for the best quality and smoothest animation, set <code>smoothing</code> to <code>true</code>.
161		 * @param autoUpdate If <code>true</code>, the BlitMask will automatically watch the <code>target</code> to see if its position/scale/rotation has changed on each frame (while <code>bitmapMode</code> is <code>true</code>) and if so, it will <code>update()</code> to make sure the BlitMask always stays synced with the <code>target</code>. This is the easiest way to use BlitMask but it is slightly less efficient than manually calling <code>update()</code> whenever you need to. Keep in mind that if you're tweening with TweenLite or TweenMax, you can simply set its <code>onUpdate</code> to the BlitMask's <code>update()</code> method to keep things synced. Like <code>onUpdate:myBlitMask.update</code>.
162		 * @param fillColor The ARGB hexadecimal color that should fill the empty areas of the BlitMask. By default, it is transparent (0x00000000). If you wanted a red color, for example, it would be <code>0xFFFF0000</code>.
163		 * @param wrap If <code>true</code>, the bitmap will be wrapped around to the opposite side when it scrolls off one of the edges (only in <code>bitmapMode</code> of course), like the BlitMask is filled with a grid of bitmap copies of the target. Use the <code>wrapOffsetX</code> and <code>wrapOffsetY</code> properties to affect how far apart the copies are from each other. 
164		 */
165		public function BlitMask(target:DisplayObject, x:Number=0, y:Number=0, width:Number=100, height:Number=100, smoothing:Boolean=false, autoUpdate:Boolean=false, fillColor:uint=0x00000000, wrap:Boolean=false) {
166			super();
167			if (width < 0 || height < 0) {
168				throw new Error("A FlexBlitMask cannot have a negative width or height.");
169			}
170			_width = width;
171			_height = height;
172			_scaleX = _scaleY = 1;
173			_smoothing = smoothing;
174			_fillColor = fillColor;
175			_autoUpdate = autoUpdate;
176			_wrap = wrap;
177			_grid = [];
178			_bounds = new Rectangle();
179			if (_smoothing) {
180				super.x = x;
181				super.y = y;
182			} else { 
183				super.x = (x < 0) ? (x - 0.5) >> 0 : (x + 0.5) >> 0;
184				super.y = (y < 0) ? (y - 0.5) >> 0 : (y + 0.5) >> 0;
185			}
186			_clipRect = new Rectangle(0, 0, _gridSize + 1, _gridSize + 1);
187			_bd = new BitmapData(width + 1, height + 1, true, _fillColor);
188			_bitmapMode = true;
189			this.target = target;
190		}
191		
192		/** @private **/
193		protected function _captureTargetBitmap():void {
194			if (_bd == null || _target == null) { //must have been disposed, so don't update. 
195				return;
196			}
197			
198			_disposeGrid();
199			
200			//capturing when the target is masked (or has a scrollRect) can cause problems. 
201			var prevMask:DisplayObject = _target.mask;
202			if (prevMask != null) {
203				_target.mask = null; 
204			}
205			var prevScrollRect:Rectangle = _target.scrollRect;
206			if (prevScrollRect != null) {
207				_target.scrollRect = null;
208			}
209			var prevFilters:Array = _target.filters;
210			if (prevFilters.length != 0) {
211				_target.filters = _emptyArray;
212			}
213			
214			_grid = [];
215			if (_target.parent == null) {
216				_tempContainer.addChild(_target);
217			}
218			_bounds = _target.getBounds(_target.parent);
219			var w:Number = 0;
220			var h:Number = 0;
221			_columns = Math.ceil(_bounds.width / _gridSize);
222			_rows = Math.ceil(_bounds.height / _gridSize);
223			var cumulativeHeight:Number = 0;
224			var matrix:Matrix = _transform.matrix;
225			var xOffset:Number = matrix.tx - _bounds.x;
226			var yOffset:Number = matrix.ty - _bounds.y;
227			if (!_smoothing) {
228				xOffset = (xOffset + 0.5) >> 0;
229				yOffset = (yOffset + 0.5) >> 0;
230			}
231			
232			var bd:BitmapData, cumulativeWidth:Number;
233			for (var row:int = 0; row < _rows; row++) {
234				h = (_bounds.height - cumulativeHeight > _gridSize) ? _gridSize : _bounds.height - cumulativeHeight;
235				matrix.ty = -cumulativeHeight + yOffset;
236				cumulativeWidth = 0;
237				_grid[row] = [];
238				for (var column:int = 0; column < _columns; column++) {
239					w = (_bounds.width - cumulativeWidth > _gridSize) ? _gridSize : _bounds.width - cumulativeWidth;
240					_grid[row][column] = bd = new BitmapData(w + 1, h + 1, true, _fillColor);
241					matrix.tx = -cumulativeWidth + xOffset;
242					bd.draw(_target, matrix, null, null, _clipRect, _smoothing);
243					cumulativeWidth += w;
244				}
245				cumulativeHeight += h;
246			}
247			
248			if (_target.parent == _tempContainer) {
249				_tempContainer.removeChild(_target);
250			}
251			
252			if (prevMask != null) {
253				_target.mask = prevMask;
254			}
255			if (prevScrollRect != null) {
256				_target.scrollRect = prevScrollRect;
257			}
258			if (prevFilters.length != 0) {
259				_target.filters = prevFilters;
260			}
261		}
262		
263		/** @private **/
264		protected function _disposeGrid():void {
265			var i:int = _grid.length, j:int, r:Array;
266			while (--i > -1) {
267				r = _grid[i];
268				j = r.length;
269				while (--j > -1) {
270					BitmapData(r[j]).dispose();
271				}
272			}
273		}
274	
275		/**
276		 * Updates the BlitMask's internal bitmap to reflect the <code>target's</code> current position/scale/rotation. 
277		 * This is a very important method that you'll need to call whenever visual or transformational changes are made 
278		 * to the target so that the BlitMask remains synced with it. 
279		 * 
280		 * @param event An optional Event object (which isn't used at all internally) in order to make it easier to use <code>update()</code> as an event handler. For example, you could <code>addEventListener(Event.ENTER_FRAME, myBlitMask.update)</code> to make sure it is updated on every frame (although it would be more efficient to simply set <code>autoUpdate</code> to <code>true</code> in this case). 
281		 * @param forceRecaptureBitmap Normally, the cached bitmap of the <code>target</code> is only recaptured if its scale or rotation changed because doing so is rather processor-intensive, but you can force a full update (and regeneration of the cached bitmap) by setting <code>forceRecaptureBitmap</code> to <code>true</code>.
282		 */
283		public function update(event:Event=null, forceRecaptureBitmap:Boolean=false):void {
284			if (_bd == null) {
285				return;
286			} else if (_target == null) {
287				_render();
288			}  else if (_target.parent) {
289				_bounds = _target.getBounds(_target.parent);
290				if (this.parent != _target.parent) {
291					_target.parent.addChildAt(this, _target.parent.getChildIndex(_target));
292				}
293			}
294			if (_bitmapMode || forceRecaptureBitmap) {
295				var m:Matrix = _transform.matrix;
296				if (forceRecaptureBitmap || _prevMatrix == null || m.a != _prevMatrix.a || m.b != _prevMatrix.b || m.c != _prevMatrix.c || m.d != _prevMatrix.d) {
297					_captureTargetBitmap();
298					_render();
299				} else if (m.tx != _prevMatrix.tx || m.ty != _prevMatrix.ty) {
300					_render();
301				} else if (_bitmapMode && _target != null) {
302					this.filters = _target.filters;
303					this.transform.colorTransform = _transform.colorTransform;
304				}
305				_prevMatrix = m;
306			}
307		}
308		
309		/** @private  **/
310		protected function _render(xOffset:Number=0, yOffset:Number=0, clear:Boolean=true, limitRecursion:Boolean=false):void {
311			//note: the code in this method was optimized for speed rather than readability or succinctness (since the whole point of this class is to help things perform better)
312			if (clear) {
313				_sliceRect.x = _sliceRect.y = 0;
314				_sliceRect.width = _width + 1;
315				_sliceRect.height = _height + 1;
316				_bd.fillRect(_sliceRect, _fillColor);
317				
318				if (_bitmapMode && _target != null) {
319					this.filters = _target.filters;
320					this.transform.colorTransform = _transform.colorTransform;
321				} else {
322					this.filters = _emptyArray;
323					this.transform.colorTransform = _colorTransform;
324				}
325			}
326			
327			if (_bd == null) {
328				return;
329			} else if (_rows == 0) { //sometimes (especially in Flex) objects take a frame or two to render in Flash and properly report their width/height. Before that, their width/height is typically 0. This works around that issue and forces a refresh if we didn't capture any pixels last time we did a capture.
330				_captureTargetBitmap();
331			}
332			
333			var x:Number = super.x + xOffset;
334			var y:Number = super.y + yOffset;
335			
336			
337			var wrapWidth:int = (_bounds.width + _wrapOffsetX + 0.5) >> 0;
338			var wrapHeight:int = (_bounds.height + _wrapOffsetY + 0.5) >> 0;
339			var g:Graphics = this.graphics;
340			
341			if (_bounds.width == 0 || _bounds.height == 0 || (_wrap && (wrapWidth == 0 || wrapHeight == 0)) || (!_wrap && (x + _width < _bounds.x || y + _height < _bounds.y || x > _bounds.right || y > _bounds.bottom))) {
342				g.clear();
343				g.beginBitmapFill(_bd);
344				g.drawRect(0, 0, _width, _height);
345				g.endFill();
346				return;
347			}
348			
349			var column:int = int((x - _bounds.x) / _gridSize);
350			if (column < 0) {
351				column = 0;
352			}
353			var row:int = int((y - _bounds.y) / _gridSize);
354			if (row < 0) {
355				row = 0;
356			}
357			
358			var maxColumn:int = int(((x + _width) - _bounds.x) / _gridSize);
359			if (maxColumn >= _columns) {
360				maxColumn = _columns - 1;
361			}
362			var maxRow:uint = int(((y + _height) - _bounds.y) / _gridSize);
363			if (maxRow >= _rows) {
364				maxRow = _rows - 1;
365			}
366			
367			var xNudge:Number = (_bounds.x - x) % 1;
368			var yNudge:Number = (_bounds.y - y) % 1;
369			
370			if (y <= _bounds.y) {
371				_destPoint.y = (_bounds.y - y) >> 0;
372				_sliceRect.y = -1; //subtract 1 to make sure the whole image gets included - without this, a very slight vibration can occur on the edge during animation.
373				
374			} else {
375				_destPoint.y = 0;
376				_sliceRect.y = Math.ceil(y - _bounds.y) - (row * _gridSize) - 1; //subtract 1 to make sure the whole image gets included - without this, a very slight vibration can occur on the edge during animation.
377				if (clear && yNudge != 0) {
378					yNudge += 1;
379				}
380				
381			}
382			if (x <= _bounds.x) {
383				_destPoint.x = (_bounds.x - x) >> 0;
384				_sliceRect.x = -1; //subtract 1 to make sure the whole image gets included - without this, a very slight vibration can occur on the edge during animation.
385				
386			} else {
387				_destPoint.x = 0;
388				_sliceRect.x = Math.ceil(x - _bounds.x) - (column * _gridSize) - 1; //subtract 1 to make sure the whole image gets included - without this, a very slight vibration can occur on the edge during animation.
389				if (clear && xNudge != 0) {
390					xNudge += 1;
391				}
392			}
393			
394			if (_wrap && clear) {
395				//make sure to offset appropriately so that we start drawing directly on the image. We must use consistent xNudge and yNudge values across all the recursive calls too, otherwise the copies may vibrate visually a bit as they move
396				_render(Math.ceil((_bounds.x - x) / wrapWidth) * wrapWidth, Math.ceil((_bounds.y - y) / wrapHeight) * wrapHeight, false, false);
397			} else if (_rows != 0) {
398				var xDestReset:Number = _destPoint.x;
399				var xSliceReset:Number = _sliceRect.x;
400				var columnReset:int = column;
401				var bd:BitmapData;
402				while (row <= maxRow) {
403					bd = _grid[row][0];
404					_sliceRect.height = bd.height - _sliceRect.y;
405					_destPoint.x = xDestReset;
406					_sliceRect.x = xSliceReset;
407					column = columnReset;
408					while (column <= maxColumn) {
409						bd = _grid[row][column];
410						_sliceRect.width = bd.width - _sliceRect.x;
411						
412						_bd.copyPixels(bd, _sliceRect, _destPoint);
413						
414						_destPoint.x += _sliceRect.width - 1;
415						_sliceRect.x = 0;
416						column++;
417					}
418					_destPoint.y += _sliceRect.height - 1;
419					_sliceRect.y = 0;
420					row++;
421				}
422				
423			}
424			
425			if (clear) {
426				_tempMatrix.tx = xNudge - 1; //subtract 1 to compensate for the pixel we added above.
427				_tempMatrix.ty = yNudge - 1;
428				g.clear();
429				g.beginBitmapFill(_bd, _tempMatrix, false, _smoothing);
430				g.drawRect(0, 0, _width, _height);
431				g.endFill();
432			} else if (_wrap) {
433				//if needed, recursively call _render() and adjust the offset(s) to wrap the bitmap.
434				if (x + _width > _bounds.right) {
435					_render(xOffset - wrapWidth, yOffset, false, true);
436				} 
437				if (!limitRecursion && y + _height > _bounds.bottom) {
438					_render(xOffset, yOffset - wrapHeight, false, false);
439				}
440			}
441		}
442		
443		/** 
444		 * Sets the width and height of the BlitMask. 
445		 * Keep in mind that a BlitMask should not be rotated or scaled. 
446		 * You can also directly set the <code>width</code> or <code>height</code> properties. 
447		 * 
448		 * @param width The width of the BlitMask
449		 * @param height The height of the BlitMask
450		 * @see #width
451		 * @see #height
452		 **/
453		public function setSize(width:Number, height:Number):void {
454			if (_width == width && _height == height) {
455				return;
456			} else if (width < 0 || height < 0) {
457				throw new Error("A BlitMask cannot have a negative width or height.");
458			} else if (_bd != null) {
459				_bd.dispose();
460			}
461			_width = width;
462			_height = height;
463			_bd = new BitmapData(width + 1, height + 1, true, _fillColor);
464			_render();
465		}
466		
467		/** @private **/
468		protected function _mouseEventPassthrough(event:MouseEvent):void {
469			if (this.mouseEnabled && (!_bitmapMode || this.hitTestPoint(event.stageX, event.stageY, false))) {
470				dispatchEvent(event);
471			}
472		}
473		
474		/**
475		 * Identical to setting <code>bitmapMode = true</code> but this method simplifies adding that
476		 * functionality to tweens or using it as an event handler. For example, to enable bitmapMode at
477		 * the beginning of a tween and then disable it when the tween completes, you could do: <br /><br /><code>
478		 * 
479		 * TweenLite.to(mc, 3, {x:400, onStart:myBlitMask.enableBitmapMode, onUpdate:myBlitMask.update, onComplete:myBlitMask.disableBitmapMode});
480		 * </code>
481		 * 
482		 * @param event An optional Event that isn't used internally but makes it possible to use the method as an event handler like <code>addEventListener(MouseEvent.CLICK, myBlitMask.enableBitmapMode)</code>.
483		 * @see #disableBitmapMode()
484		 * @see #bitmapMode
485		 */
486		public function enableBitmapMode(event:Event=null):void {
487			this.bitmapMode = true;
488		}
489		
490		/**
491		 * Identical to setting <code>bitmapMode = false</code> but this method simplifies adding that
492		 * functionality to tweens or using it as an event handler. For example, to enable bitmapMode at
493		 * the beginning of a tween and then disable it when the tween completes, you could do: <br /><br /><code>
494		 * 
495		 * TweenLite.to(mc, 3, {x:400, onStart:myBlitMask.enableBitmapMode, onUpdate:myBlitMask.update, onComplete:myBlitMask.disableBitmapMode});
496		 * </code>
497		 * 
498		 * @param event An optional Event that isn't used internally but makes it possible to use the method as an event handler like <code>addEventListener(MouseEvent.CLICK, myBlitMask.disableBitmapMode)</code>.
499		 * @see #enableBitmapMode()
500		 * @see #bitmapMode
501		 */
502		public function disableBitmapMode(event:Event=null):void {
503			this.bitmapMode = false;
504		}
505		
506		/**
507		 * Repositions the <code>target</code> so that it is visible within the BlitMask, as though <code>wrap</code>
508		 * was enabled (this method is called automatically when <code>bitmapMode</code> is disabled while <code>wrap</code> 
509		 * is <code>true</code>). For example, if you tween the <code>target</code> way off the edge of the BlitMask and
510		 * have <code>wrap</code> enabled, it will appear to come back in from the other side even though the raw coordinates
511		 * of the target would indicate that it is outside the BlitMask. If you want to force the coordinates to normalize 
512		 * so that they reflect that wrapped position, simply call <code>normalizePosition()</code>. It will automatically 
513		 * choose the coordinates that would maximize the visible portion of the target if a seam is currently showing.
514		 **/
515		public function normalizePosition():void {
516			if (_target && _bounds) {
517				var wrapWidth:int = (_bounds.width + _wrapOffsetX + 0.5) >> 0;
518				var wrapHeight:int = (_bounds.height + _wrapOffsetY + 0.5) >> 0;
519				var offsetX:Number = (_bounds.x - this.x) % wrapWidth;
520				var offsetY:Number = (_bounds.y - this.y) % wrapHeight;
521				
522				if (offsetX > (_width + _wrapOffsetX) / 2) {
523					offsetX -= wrapWidth;
524				} else if (offsetX < (_width + _wrapOffsetX) / -2) {
525					offsetX += wrapWidth;
526				}
527				if (offsetY > (_height + _wrapOffsetY) / 2) {
528					offsetY -= wrapHeight;
529				} else if (offsetY < (_height + _wrapOffsetY) / -2) {
530					offsetY += wrapHeight;
531				}
532				
533				_target.x += this.x + offsetX - _bounds.x;
534				_target.y += this.y + offsetY - _bounds.y;
535			}
536		}
537		
538		/** Disposes of the BlitMask and its internal BitmapData instances, releasing them for garbage collection. **/
539		public function dispose():void {
540			if (_bd == null) { //already disposed.
541				return;
542			}
543			_disposeGrid();
544			_bd.dispose();
545			_bd = null;
546			this.bitmapMode = false;
547			this.autoUpdate = false;
548			if (_target != null) {
549				_target.mask = null;
550			}
551			if (this.parent != null) {
552				this.parent.removeChild(this);
553			}
554			this.target = null;
555		}
556	
557//---- GETTERS / SETTERS --------------------------------------------------------------------
558		
559		/** 
560		 * When <code>true</code>, the BlitMask optimizes itself for performance by setting the <code>target's</code> 
561		 * <code>visible</code> property to <code>false</code> (greatly reducing the load on Flash's graphics rendering 
562		 * routines) and uses its internally cached bitmap version of the <code>target</code> to redraw only the necessary
563		 * pixels inside the masked area. Since only a bitmap version of the <code>target</code> is shown while in bitmapMode,
564		 * the <code>target</code> won't be interactive. So if you have buttons and other objects that normally react to 
565		 * MouseEvents, they won't while in bitmapMode. If you need the interactivity, simply set <code>bitmapMode</code>
566		 * to <code>false</code> and then it will turn the <code>target's</code> <code>visible</code> property back to <code>true</code>
567		 * and its <code>mask</code> property to the BlitMask itself. Typically it is best to turn bitmapMode on at least when you're 
568		 * animating the <code>target</code> or the BlitMask itself, and then when the tween/animation is done and you need 
569		 * interactivity, set bitmapMode back to false. For example: <br /><br /><code>
570		 * 
571		 * var bm:BlitMask = new BlitMask(mc, 0, 0, 300, 200, true);<br /><br />
572		 * 
573		 * TweenLite.to(mc, 3, {x:200, onUpdate:bm.update, onComplete:completeHandler});<br /><br />
574		 * 
575		 * function completeHandler():void {<br />
576		 *     bm.bitmapMode = false;<br />
577		 * }<br />
578		 * </code><br /><br />
579		 * 
580		 * @see #enableBitmapMode()
581		 * @see #disableBitmapMode()
582		 **/
583		public function get bitmapMode():Boolean {
584			return _bitmapMode;
585		}
586		public function set bitmapMode(value:Boolean):void {
587			if (_bitmapMode != value) {
588				_bitmapMode = value;
589				if (_target != null) {
590					_target.visible = !_bitmapMode;
591					update(null);
592					if (_bitmapMode) {
593						this.filters = _target.filters;
594						this.transform.colorTransform = _transform.colorTransform;
595						this.blendMode = _target.blendMode;
596						_target.mask = null;
597					} else {
598						this.filters = _emptyArray;
599						this.transform.colorTransform = _colorTransform;
600						this.blendMode = "normal";
601						this.cacheAsBitmap = false; //if cacheAsBitmap is true on both the _target and the FlexBlitMask instance, the transparent areas of the mask will be...well...transparent which isn't what we want when bitmapMode is false (it could hide visible areas unless update(null, true) is called regularly, like if the target has animated children and bitmapMode is false)
602						_target.mask = this;
603						if (_wrap) {
604							normalizePosition();
605						}
606					}
607					if (_bitmapMode && _autoUpdate) {
608						this.addEventListener(Event.ENTER_FRAME, update, false, -10, true);
609					} else {
610						this.removeEventListener(Event.ENTER_FRAME, update);
611					}
612				}
613			}
614		}
615		
616		/** 
617		 * If <code>true</code>, the BlitMask will automatically watch the <code>target</code> to see if 
618		 * its position/scale/rotation has changed on each frame (while <code>bitmapMode</code> is <code>true</code>) 
619		 * and if so, it will <code>update()</code> to make sure the BlitMask always stays synced with the <code>target</code>. 
620		 * This is the easiest way to use BlitMask but it is slightly less efficient than manually calling <code>update()</code> 
621		 * whenever you need to. Keep in mind that if you're tweening with TweenLite or TweenMax, you can simply set 
622		 * its <code>onUpdate</code> to the BlitMask's <code>update()</code> method to keep things synced. 
623		 * Like <code>onUpdate:myBlitMask.update</code>. 
624		 **/
625		public function get autoUpdate():Boolean {
626			return _autoUpdate;
627		}
628		public function set autoUpdate(value:Boolean):void {
629			if (_autoUpdate != value) {
630				_autoUpdate = value;
631				if (_bitmapMode && _autoUpdate) {
632					this.addEventListener(Event.ENTER_FRAME, update, false, -10, true);
633				} else {
634					this.removeEventListener(Event.ENTER_FRAME, update);
635				}
636			}
637		}
638		
639		/** The target DisplayObject that the BlitMask should mask **/
640		public function get target():DisplayObject {
641			return _target;
642		}
643		public function set target(value:DisplayObject):void {
644			if (_target != value) {
645				var i:int = _mouseEvents.length;
646				if (_target != null) {
647					while (--i > -1) {
648						_target.removeEventListener(_mouseEvents[i], _mouseEventPassthrough);
649					}
650				}
651				_target = value;
652				if (_target != null) {
653					i = _mouseEvents.length;
654					while (--i > -1) {
655						_target.addEventListener(_mouseEvents[i], _mouseEventPassthrough, false, 0, true);
656					}
657					_prevMatrix = null;
658					_transform = _target.transform;
659					_bitmapMode = !_bitmapMode; 
660					this.bitmapMode = !_bitmapMode; //forces a refresh (applying the mask, doing an update(), etc.)
661				} else {
662					_bounds = new Rectangle();
663				}
664			}
665		}
666		
667		/** x coordinate of the BlitMask (it will automatically be forced to whole pixel values if <code>smoothing</code> is <code>false</code>). **/
668		override public function get x():Number {
669			return super.x;
670		}
671		override public function set x(value:Number):void {
672			if (_smoothing) {
673				super.x = value;
674			} else if (value >= 0) {
675				super.x = (value + 0.5) >> 0;
676			} else {
677				super.x = (value - 0.5) >> 0;
678			}
679			if (_bitmapMode) {
680				_render();
681			}
682		}
683		
684		/** y coordinate of the BlitMask (it will automatically be forced to whole pixel values if <code>smoothing</code> is <code>false</code>). **/
685		override public function get y():Number {
686			return super.y;
687		}
688		override public function set y(value:Number):void {
689			if (_smoothing) {
690				super.y = value;
691			} else if (value >= 0) {
692				super.y = (value + 0.5) >> 0;
693			} else {
694				super.y = (value - 0.5) >> 0;
695			}
696			if (_bitmapMode) {
697				_render();
698			}
699		}
700		
701		/** Width of the BlitMask **/
702		override public function get width():Number {
703			return _width;
704		}
705		override public function set width(value:Number):void {
706			setSize(value, _height);
707		}
708		
709		/** Height of the BlitMask **/
710		override public function get height():Number {
711			return _height;
712		}
713		override public function set height(value:Number):void {
714			setSize(_width, value);
715		}
716		
717		/** scaleX (warning: altering the scaleX won't actually change its value - instead, it affects the <code>width</code> property accordingly) **/
718		override public function get scaleX():Number {
719			return 1;
720		}
721		override public function set scaleX(value:Number):void {
722			var oldScaleX:Number = _scaleX;
723			_scaleX = value;
724			setSize(_width * (_scaleX / oldScaleX), _height);
725		}
726		
727		/** scaleY (warning: altering the scaleY won't actually change its value - instead, it affects the <code>height</code> property accordingly) **/
728		override public function get scaleY():Number {
729			return 1;
730		}
731		override public function set scaleY(value:Number):void {
732			var oldScaleY:Number = _scaleY;
733			_scaleY = value;
734			setSize(_width, _height * (_scaleY / oldScaleY));
735		}
736		
737		/** Rotation of the BlitMask (always 0 because BlitMasks can't be rotated!) **/
738		override public function set rotation(value:Number):void {
739			if (value != 0) {
740				throw new Error("Cannot set the rotation of a BlitMask to a non-zero number. BlitMasks should remain unrotated.");
741			}
742		}
743		
744		/** 
745		 * Typically a value between 0 and 1 indicating the <code>target's</code> position in relation to the BlitMask 
746		 * on the x-axis where 0 is at the beginning, 0.5 is scrolled to exactly the halfway point, and 1 is scrolled 
747		 * all the way. This makes it very easy to animate the scroll. For example, to scroll from beginning to end 
748		 * over 5 seconds, you could do: <br /><br /><code>
749		 * 
750		 * myBlitMask.scrollX = 0; <br />
751		 * TweenLite.to(myBlitMask, 5, {scrollX:1});
752		 * </code>
753		 * @see #scrollY
754		 **/
755		public function get scrollX():Number {
756			return (super.x - _bounds.x) / (_bounds.width - _width);
757		}
758		public function set scrollX(value:Number):void {
759			if (_target != null && _target.parent) {
760				_bounds = _target.getBounds(_target.parent);
761				var dif:Number;
762				dif = (super.x - (_bounds.width - _width) * value) - _bounds.x;
763				_target.x += dif;
764				_bounds.x += dif;
765				if (_bitmapMode) {
766					_render();
767				}
768			}
769		}
770		
771		/** 
772		 * Typically a value between 0 and 1 indicating the <code>target's</code> position in relation to the BlitMask 
773		 * on the y-axis where 0 is at the beginning, 0.5 is scrolled to exactly the halfway point, and 1 is scrolled 
774		 * all the way. This makes it very easy to animate the scroll. For example, to scroll from beginning to end 
775		 * over 5 seconds, you could do: <br /><br /><code>
776		 * 
777		 * myBlitMask.scrollY = 0; <br />
778		 * TweenLite.to(myBlitMask, 5, {scrollY:1});
779		 * </code>
780		 * @see #scrollX
781		 **/
782		public function get scrollY():Number {
783			return (super.y - _bounds.y) / (_bounds.height - _height);
784		}
785		public function set scrollY(value:Number):void {
786			if (_target != null && _target.parent) {
787				_bounds = _target.getBounds(_target.parent);
788				var dif:Number = (super.y - (_bounds.height - _height) * value) - _bounds.y;
789				_target.y += dif;
790				_bounds.y += dif;
791				if (_bitmapMode) {
792					_render();
793				}
794			}
795		}
796		
797		/** 
798		 * If <code>false</code> (the default), the bitmap (and the BlitMask's x/y coordinates) 
799		 * will be rendered only on whole pixels which is faster in terms of processing. However, 
800		 * for the best quality and smoothest animation, set <code>smoothing</code> to <code>true</code>. 
801		 **/
802		public function get smoothing():Boolean {
803			return _smoothing;
804		}
805		public function set smoothing(value:Boolean):void {
806			if (_smoothing != value) {
807				_smoothing = value;
808				_captureTargetBitmap();
809				if (_bitmapMode) {
810					_render();
811				}
812			}
813		}
814		
815		/** 
816		 * The ARGB hexadecimal color that should fill the empty areas of the BlitMask. By default, 
817		 * it is transparent (0x00000000). If you wanted a red color, for example, it would be 
818		 * <code>0xFFFF0000</code>. 
819		 **/
820		public function get fillColor():uint {
821			return _fillColor;
822		}
823		public function set fillColor(value:uint):void {
824			if (_fillColor != value) {
825				_fillColor = value;
826				if (_bitmapMode) {
827					_render();
828				}
829			}
830		}
831		
832		/** 
833		 * If <code>true</code>, the bitmap will be wrapped around to the opposite side when it scrolls off 
834		 * one of the edges (only in <code>bitmapMode</code> of course), like the BlitMask is filled with a 
835		 * grid of bitmap copies of the target. Use the <code>wrapOffsetX</code> and <code>wrapOffsetY</code> 
836		 * properties to affect how far apart the copies are from each other. You can reposition the 
837		 * <code>target</code> anywhere and BlitMask will align the copies accordingly.
838		 * @see #wrapOffsetX
839		 * @see #wrapOffsetY
840		 **/
841		public function get wrap():Boolean {
842			return _wrap;
843		}
844		public function set wrap(value:Boolean):void {
845			if (_wrap != value) {
846				_wrap = value;
847				if (_bitmapMode) {
848					_render();
849				}
850			}
851		}
852		
853		/** 
854		 * When <code>wrap</code> is <code>true</code>, <code>wrapOffsetX</code> controls how many pixels
855		 * along the x-axis the wrapped copies of the bitmap are spaced. It is essentially the gap between
856		 * the copies (although you can use a negative value or 0 to avoid any gap). 
857		 * @see #wrap
858		 * @see #wrapOffsetY
859		 **/
860		public function get wrapOffsetX():Number {
861			return _wrapOffsetX;
862		}
863		public function set wrapOffsetX(value:Number):void {
864			if (_wrapOffsetX != value) {
865				_wrapOffsetX = value;
866				if (_bitmapMode) {
867					_render();
868				}
869			}
870		}
871		
872		/** 
873		 * When <code>wrap</code> is <code>true</code>, <code>wrapOffsetY</code> controls how many pixels
874		 * along the y-axis the wrapped copies of the bitmap are spaced. It is essentially the gap between
875		 * the copies (although you can use a negative value or 0 to avoid any gap). 
876		 * @see #wrap
877		 * @see #wrapOffsetX
878		 **/
879		public function get wrapOffsetY():Number {
880			return _wrapOffsetY;
881		}
882		public function set wrapOffsetY(value:Number):void {
883			if (_wrapOffsetY != value) {
884				_wrapOffsetY = value;
885				if (_bitmapMode) {
886					_render();
887				}
888			}
889		}
890	
891	}
892}