PageRenderTime 61ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/src/org/openzoom/flash/renderers/images/ImagePyramidRenderManager.as

http://github.com/openzoom/sdk
ActionScript | 569 lines | 319 code | 98 blank | 152 comment | 35 complexity | 379370b6054a4b1563a95d63f5ed3c62 MD5 | raw file
  1. ////////////////////////////////////////////////////////////////////////////////
  2. //
  3. // OpenZoom SDK
  4. //
  5. // Version: MPL 1.1/GPL 3/LGPL 3
  6. //
  7. // The contents of this file are subject to the Mozilla Public License Version
  8. // 1.1 (the "License"); you may not use this file except in compliance with
  9. // the License. You may obtain a copy of the License at
  10. // http://www.mozilla.org/MPL/
  11. //
  12. // Software distributed under the License is distributed on an "AS IS" basis,
  13. // WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  14. // for the specific language governing rights and limitations under the
  15. // License.
  16. //
  17. // The Original Code is the OpenZoom SDK.
  18. //
  19. // The Initial Developer of the Original Code is Daniel Gasienica.
  20. // Portions created by the Initial Developer are Copyright (c) 2007-2010
  21. // the Initial Developer. All Rights Reserved.
  22. //
  23. // Contributor(s):
  24. // Daniel Gasienica <daniel@gasienica.ch>
  25. //
  26. // Alternatively, the contents of this file may be used under the terms of
  27. // either the GNU General Public License Version 3 or later (the "GPL"), or
  28. // the GNU Lesser General Public License Version 3 or later (the "LGPL"),
  29. // in which case the provisions of the GPL or the LGPL are applicable instead
  30. // of those above. If you wish to allow use of your version of this file only
  31. // under the terms of either the GPL or the LGPL, and not to allow others to
  32. // use your version of this file under the terms of the MPL, indicate your
  33. // decision by deleting the provisions above and replace them with the notice
  34. // and other provisions required by the GPL or the LGPL. If you do not delete
  35. // the provisions above, a recipient may use your version of this file under
  36. // the terms of any one of the MPL, the GPL or the LGPL.
  37. //
  38. ////////////////////////////////////////////////////////////////////////////////
  39. package org.openzoom.flash.renderers.images
  40. {
  41. import flash.display.BitmapData;
  42. import flash.display.Graphics;
  43. import flash.display.Shape;
  44. import flash.display.Sprite;
  45. import flash.events.Event;
  46. import flash.geom.Matrix;
  47. import flash.geom.Point;
  48. import flash.geom.Rectangle;
  49. import flash.system.Capabilities;
  50. import flash.system.System;
  51. import flash.utils.getTimer;
  52. import org.openzoom.flash.core.openzoom_internal;
  53. import org.openzoom.flash.descriptors.IImagePyramidDescriptor;
  54. import org.openzoom.flash.descriptors.IImagePyramidLevel;
  55. import org.openzoom.flash.descriptors.deepzoom.DeepZoomImageDescriptor;
  56. import org.openzoom.flash.events.ViewportEvent;
  57. import org.openzoom.flash.net.INetworkQueue;
  58. import org.openzoom.flash.scene.IMultiScaleScene;
  59. import org.openzoom.flash.scene.IReadonlyMultiScaleScene;
  60. import org.openzoom.flash.utils.Cache;
  61. import org.openzoom.flash.utils.IDisposable;
  62. import org.openzoom.flash.utils.MortonOrder;
  63. import org.openzoom.flash.viewport.INormalizedViewport;
  64. use namespace openzoom_internal;
  65. //[ExcludeClass]
  66. /**
  67. * @private
  68. *
  69. * Manages the rendering of all image pyramid renderers on stage.
  70. */
  71. public final class ImagePyramidRenderManager implements IDisposable
  72. {
  73. include "../../core/Version.as"
  74. //--------------------------------------------------------------------------
  75. //
  76. // Class constants
  77. //
  78. //--------------------------------------------------------------------------
  79. private static const TILE_BLEND_DURATION:Number = 500 // milliseconds
  80. private static const MAX_CACHE_SIZE:uint = 180
  81. private static const MAX_DOWNLOADS_STATIC:uint = 4
  82. private static const MAX_DOWNLOADS_DYNAMIC:uint = 2
  83. // Experimental
  84. private static const LEVEL_BLENDING_ENABLED:Boolean = false
  85. private static const PRE_FLASH_10_FRAME_EVENT_NAME:String = "enterFrame"
  86. private static const POST_FLASH_10_FRAME_EVENT_NAME:String = "exitFrame"
  87. private var frameEventName:String = PRE_FLASH_10_FRAME_EVENT_NAME
  88. //--------------------------------------------------------------------------
  89. //
  90. // Constructor
  91. //
  92. //--------------------------------------------------------------------------
  93. /**
  94. * Constructor.
  95. */
  96. public function ImagePyramidRenderManager(owner:Sprite,
  97. scene:IMultiScaleScene,
  98. viewport:INormalizedViewport,
  99. loader:INetworkQueue)
  100. {
  101. // FIXME
  102. // var playerMajorVersion:int = parseInt(Capabilities.version.split(" ")[1].toString().split(",")[0])
  103. // if (playerMajorVersion >= 10)
  104. // frameEventName = POST_FLASH_10_FRAME_EVENT_NAME
  105. this.owner = owner
  106. this.scene = scene
  107. this.viewport = viewport
  108. this.loader = loader
  109. tileCache = new Cache(MAX_CACHE_SIZE)
  110. tileLoader = new TileLoader(this,
  111. loader,
  112. tileCache,
  113. MAX_DOWNLOADS_STATIC)
  114. this.viewport.addEventListener(ViewportEvent.TRANSFORM_UPDATE,
  115. viewport_transformUpdateHandler,
  116. false, 0, true)
  117. this.viewport.addEventListener(ViewportEvent.TRANSFORM_START,
  118. viewport_transformStartHandler,
  119. false, 0, true)
  120. this.viewport.addEventListener(ViewportEvent.TRANSFORM_END,
  121. viewport_transformEndHandler,
  122. false, 0, true)
  123. }
  124. //--------------------------------------------------------------------------
  125. //
  126. // Variables
  127. //
  128. //--------------------------------------------------------------------------
  129. private var renderers:Array /* of ImagePyramidRenderer */ = []
  130. private var owner:Sprite
  131. private var viewport:INormalizedViewport
  132. private var scene:IMultiScaleScene
  133. private var loader:INetworkQueue
  134. private var tileLoader:TileLoader
  135. private var tileCache:Cache
  136. //--------------------------------------------------------------------------
  137. //
  138. // Methods: Validation/Invalidation
  139. //
  140. //--------------------------------------------------------------------------
  141. private var invalidateDisplayListFlag:Boolean = true
  142. /**
  143. * @private
  144. */
  145. public function invalidateDisplayList():void
  146. {
  147. if (!invalidateDisplayListFlag)
  148. invalidateDisplayListFlag = true
  149. }
  150. /**
  151. * @private
  152. */
  153. public function validateDisplayList():void
  154. {
  155. if (invalidateDisplayListFlag)
  156. {
  157. invalidateDisplayListFlag = false
  158. // FIXME Test performance
  159. var length:int = renderers.length
  160. for (var i:int = 0; i < length; i++)
  161. updateDisplayList(renderers[i])
  162. // for each (var renderer:ImagePyramidRenderer in renderers)
  163. // updateDisplayList(renderer)
  164. }
  165. }
  166. //--------------------------------------------------------------------------
  167. //
  168. // Methods
  169. //
  170. //--------------------------------------------------------------------------
  171. /**
  172. * @private
  173. */
  174. private function updateDisplayList(renderer:ImagePyramidRenderer):void
  175. {
  176. // Abort if we're not visible
  177. if (!renderer.visible)
  178. return
  179. var descriptor:IImagePyramidDescriptor = renderer.source
  180. // Abort if we have no descriptor
  181. if (!descriptor)
  182. return
  183. var viewport:INormalizedViewport = renderer.viewport
  184. var scene:IReadonlyMultiScaleScene = renderer.scene
  185. // Is renderer on scene?
  186. if (!viewport)
  187. return
  188. // Cache scene dimensions
  189. var sceneWidth:Number = scene.sceneWidth
  190. var sceneHeight:Number = scene.sceneHeight
  191. // Get scene bounds of renderer
  192. var sceneBounds:Rectangle = renderer.getBounds(scene.targetCoordinateSpace)
  193. // Normalize scene bounds
  194. sceneBounds.x /= sceneWidth
  195. sceneBounds.y /= sceneHeight
  196. sceneBounds.width /= sceneWidth
  197. sceneBounds.height /= sceneHeight
  198. // Visibility test
  199. var visible:Boolean = viewport.intersects(sceneBounds)
  200. if (!visible)
  201. return
  202. // Cache scene bounds dimensions
  203. var sceneBoundsWidth:Number = sceneBounds.width
  204. var sceneBoundsHeight:Number = sceneBounds.height
  205. // Get viewport bounds (normalized)
  206. var viewportBounds:Rectangle = viewport.getBounds()
  207. // Compute normalized visible bounds in renderer coordinate system
  208. var localBounds:Rectangle = sceneBounds.intersection(viewportBounds)
  209. localBounds.offset(-sceneBounds.x, -sceneBounds.y)
  210. localBounds.x /= sceneBoundsWidth
  211. localBounds.y /= sceneBoundsHeight
  212. localBounds.width /= sceneBoundsWidth
  213. localBounds.height /= sceneBoundsHeight
  214. // Determine stage bounds
  215. var stageBounds:Rectangle = renderer.getBounds(renderer.stage)
  216. var stageBoundsWidth:Number = stageBounds.width
  217. var stageBoundsHeight:Number = stageBounds.height
  218. // Determine optimal level
  219. var optimalLevel:IImagePyramidLevel =
  220. descriptor.getLevelForSize(stageBoundsWidth,
  221. stageBoundsHeight)
  222. // Render image pyramid from bottom up (painter's algorithm)
  223. var currentTime:int = getTimer()
  224. var toLevel:int
  225. var fromLevel:int
  226. toLevel = 0
  227. fromLevel = optimalLevel.index
  228. var level:IImagePyramidLevel
  229. var nextTile:ImagePyramidTile
  230. var renderingQueue:Array = []
  231. // Iterate over levels
  232. for (var l:int = fromLevel; l >= toLevel; --l)
  233. {
  234. var done:Boolean = true
  235. level = descriptor.getLevelAt(l)
  236. // Cache level dimensions
  237. var levelWidth:Number = level.width
  238. var levelHeight:Number = level.height
  239. // FIXME Level blending
  240. var levelAlpha:Number = 1
  241. // if (LEVEL_BLENDING_ENABLED)
  242. // levelAlpha = Math.min(1.0, (stageBoundsWidth / levelWidth - 0.5) * 2)
  243. // Load or draw visible tiles
  244. var fromPoint:Point = new Point(localBounds.left * levelWidth,
  245. localBounds.top * levelHeight)
  246. var toPoint:Point = new Point(localBounds.right * levelWidth,
  247. localBounds.bottom * levelHeight)
  248. var fromTile:Point = descriptor.getTileAtPoint(l, fromPoint)
  249. var toTile:Point = descriptor.getTileAtPoint(l, toPoint)
  250. // FIXME: Currently center, calculate true origin
  251. var t:Point = new Point(0.5, 0.5) // viewport.transform.origin
  252. var origin:Point = new Point((1 - t.x) * fromTile.x + t.x * toTile.x,
  253. (1 - t.y) * fromTile.y + t.y * toTile.y)
  254. // Iterate over columns
  255. for (var c:int = fromTile.x; c <= toTile.x; c++)
  256. {
  257. // Iterate over rows
  258. for (var r:int = fromTile.y; r <= toTile.y; r++)
  259. {
  260. var tile:ImagePyramidTile =
  261. renderer.openzoom_internal::getTile(l, c, r)
  262. if (!tile)
  263. continue
  264. if (!tile.source)
  265. {
  266. if (tileCache.contains(tile.url))
  267. {
  268. var sourceTile:SourceTile = tileCache.get(tile.url) as SourceTile
  269. tile.source = sourceTile
  270. tile.loading = false
  271. }
  272. }
  273. var dx:Number = tile.column - origin.x
  274. var dy:Number = tile.row - origin.y
  275. var distance:Number = dx*dx + dy*dy
  276. tile.distance = distance
  277. if (!tile.loaded)
  278. {
  279. if (!tile.loading && (!nextTile || tile.compareTo(nextTile) >= 0))
  280. nextTile = tile
  281. done = false
  282. continue
  283. }
  284. // Prepare alpha bitmap
  285. if (tile.blendStartTime == 0)
  286. tile.blendStartTime = currentTime
  287. tile.source.lastAccessTime = currentTime
  288. var duration:Number = TILE_BLEND_DURATION
  289. var currentAlpha:Number = (currentTime - tile.blendStartTime) / duration
  290. var tileAlpha:Number = currentAlpha
  291. tile.alpha = Math.min(1, currentAlpha) * levelAlpha
  292. if (tile.alpha < 1)
  293. done = false
  294. renderingQueue.push(tile)
  295. }
  296. }
  297. if (done)
  298. break
  299. }
  300. if (nextTile)
  301. {
  302. tileLoader.loadTile(nextTile)
  303. invalidateDisplayList()
  304. }
  305. // Prepare tile layer
  306. var tileLayer:Shape = renderer.openzoom_internal::tileLayer
  307. var g:Graphics = tileLayer.graphics
  308. g.clear()
  309. g.beginFill(0xFF0000, 0)
  310. g.drawRect(0, 0, descriptor.width, descriptor.height)
  311. g.endFill()
  312. tileLayer.width = renderer.width
  313. tileLayer.height = renderer.height
  314. while (renderingQueue.length > 0)
  315. {
  316. tile = renderingQueue.pop()
  317. var textureMap:BitmapData
  318. if (tile.alpha < 1)
  319. {
  320. invalidateDisplayList()
  321. textureMap = new BitmapData(tile.bitmapData.width,
  322. tile.bitmapData.height)
  323. var alphaMultiplier:uint = (tile.alpha * 256) << 24
  324. var alphaMap:BitmapData
  325. alphaMap = new BitmapData(tile.bitmapData.width,
  326. tile.bitmapData.height,
  327. true,
  328. alphaMultiplier | 0x00000000)
  329. textureMap.copyPixels(tile.bitmapData,
  330. tile.bitmapData.rect,
  331. ZERO_POINT,
  332. alphaMap)
  333. }
  334. else
  335. {
  336. textureMap = tile.bitmapData
  337. }
  338. // Draw tiles
  339. level = descriptor.getLevelAt(tile.level)
  340. var matrix:Matrix = new Matrix()
  341. var sx:Number
  342. var sy:Number
  343. var dziDescriptor:DeepZoomImageDescriptor = descriptor as DeepZoomImageDescriptor
  344. if (dziDescriptor &&
  345. dziDescriptor.collection &&
  346. tile.level <= dziDescriptor.collection.maxLevel)
  347. {
  348. // Deep Zoom collection
  349. var levelSize:uint = 1 << tile.level
  350. var position:Point = MortonOrder.getPoint(dziDescriptor.mortonNumber)
  351. var tileSize:uint = dziDescriptor.collection.tileSize
  352. var offsetX:uint = (position.x * levelSize) % tileSize
  353. var offsetY:uint = (position.y * levelSize) % tileSize
  354. sx = descriptor.width / level.width
  355. sy = descriptor.height / level.height
  356. matrix.createBox(sx, sy, 0, -offsetX * sx, -offsetY * sy)
  357. }
  358. else
  359. {
  360. // Normal
  361. sx = descriptor.width / level.width
  362. sy = descriptor.height / level.height
  363. var w:Number = tile.bounds.x * sx
  364. var h:Number = tile.bounds.y * sy
  365. matrix.createBox(sx, sy, 0, w, h)
  366. }
  367. g.beginBitmapFill(textureMap,
  368. matrix,
  369. false, /* repeat */
  370. true /* smoothing */)
  371. g.drawRect(tile.bounds.x * sx,
  372. tile.bounds.y * sy,
  373. tile.bounds.width * sx,
  374. tile.bounds.height * sy)
  375. g.endFill()
  376. }
  377. }
  378. private static const ZERO_POINT:Point = new Point(0, 0)
  379. //--------------------------------------------------------------------------
  380. //
  381. // Event handler
  382. //
  383. //--------------------------------------------------------------------------
  384. /**
  385. * @private
  386. */
  387. private function frameHandler(event:Event):void
  388. {
  389. // Rendering loop
  390. validateDisplayList()
  391. }
  392. /**
  393. * @private
  394. */
  395. private function viewport_transformUpdateHandler(event:ViewportEvent):void
  396. {
  397. invalidateDisplayList()
  398. }
  399. /**
  400. * @private
  401. */
  402. private function viewport_transformStartHandler(event:ViewportEvent):void
  403. {
  404. if (tileLoader)
  405. tileLoader.maxDownloads = MAX_DOWNLOADS_DYNAMIC
  406. }
  407. /**
  408. * @private
  409. */
  410. private function viewport_transformEndHandler(event:ViewportEvent):void
  411. {
  412. if (tileLoader)
  413. tileLoader.maxDownloads = MAX_DOWNLOADS_STATIC
  414. // FIXME
  415. invalidateDisplayList()
  416. }
  417. //--------------------------------------------------------------------------
  418. //
  419. // Methods: Renderer management
  420. //
  421. //--------------------------------------------------------------------------
  422. /**
  423. * @private
  424. */
  425. public function addRenderer(renderer:ImagePyramidRenderer):ImagePyramidRenderer
  426. {
  427. if (renderers.indexOf(renderer) != -1)
  428. throw new ArgumentError("[ImagePyramidRenderManager] " +
  429. "Renderer already added.")
  430. if (renderers.length == 0)
  431. owner.addEventListener(frameEventName,
  432. frameHandler,
  433. false, 0, true)
  434. renderers.push(renderer)
  435. invalidateDisplayList()
  436. return renderer
  437. }
  438. /**
  439. * @private
  440. */
  441. public function removeRenderer(renderer:ImagePyramidRenderer):ImagePyramidRenderer
  442. {
  443. var index:int = renderers.indexOf(renderer)
  444. if (index == -1)
  445. throw new ArgumentError("[ImagePyramidRenderManager] " +
  446. "Renderer does not exist.")
  447. renderers.splice(index, 1)
  448. if (renderers.length == 0)
  449. owner.removeEventListener(frameEventName,
  450. frameHandler)
  451. return renderer
  452. }
  453. //--------------------------------------------------------------------------
  454. //
  455. // Methods: IDisposable
  456. //
  457. //--------------------------------------------------------------------------
  458. public function dispose():void
  459. {
  460. // Remove render loop
  461. owner.removeEventListener(frameEventName,
  462. frameHandler)
  463. owner = null
  464. scene = null
  465. viewport = null
  466. loader = null
  467. tileCache.dispose()
  468. tileCache = null
  469. }
  470. }
  471. }