PageRenderTime 45ms CodeModel.GetById 11ms RepoModel.GetById 1ms app.codeStats 0ms

/src/com/greensock/BlitMask.as

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