From 973ef29d668cd3ca591bbeeebcc2a2e548caba46 Mon Sep 17 00:00:00 2001 From: Ian Gilman Date: Wed, 6 Aug 2014 13:38:56 -0700 Subject: [PATCH 01/16] Copied drawer.js to tiledImage.js --- src/tiledimage.js | 1237 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1237 insertions(+) create mode 100644 src/tiledimage.js diff --git a/src/tiledimage.js b/src/tiledimage.js new file mode 100644 index 00000000..a4b7f2ec --- /dev/null +++ b/src/tiledimage.js @@ -0,0 +1,1237 @@ +/* + * OpenSeadragon - Drawer + * + * Copyright (C) 2009 CodePlex Foundation + * Copyright (C) 2010-2013 OpenSeadragon contributors + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of CodePlex Foundation nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +(function( $ ){ + +var DEVICE_SCREEN = $.getWindowSize(), + BROWSER = $.Browser.vendor, + BROWSER_VERSION = $.Browser.version, + + SUBPIXEL_RENDERING = ( + ( BROWSER == $.BROWSERS.FIREFOX ) || + ( BROWSER == $.BROWSERS.OPERA ) || + ( BROWSER == $.BROWSERS.SAFARI && BROWSER_VERSION >= 4 ) || + ( BROWSER == $.BROWSERS.CHROME && BROWSER_VERSION >= 2 ) || + ( BROWSER == $.BROWSERS.IE && BROWSER_VERSION >= 9 ) + ); + + +/** + * @class Drawer + * @classdesc Handles rendering of tiles for an {@link OpenSeadragon.Viewer}. + * A new instance is created for each TileSource opened (see {@link OpenSeadragon.Viewer#drawer}). + * + * @memberof OpenSeadragon + * @param {OpenSeadragon.TileSource} source - Reference to Viewer tile source. + * @param {OpenSeadragon.Viewport} viewport - Reference to Viewer viewport. + * @param {Element} element - Parent element. + */ +$.Drawer = function( options ) { + + //backward compatibility for positional args while prefering more + //idiomatic javascript options object as the only argument + var args = arguments, + i; + + if( !$.isPlainObject( options ) ){ + options = { + source: args[ 0 ], // Reference to Viewer tile source. + viewport: args[ 1 ], // Reference to Viewer viewport. + element: args[ 2 ] // Parent element. + }; + } + + this._worldX = options.x || 0; + delete options.x; + this._worldY = options.y || 0; + delete options.y; + + // Ratio of zoomable image height to width. + this.normHeight = options.source.dimensions.y / options.source.dimensions.x; + + if ( options.width ) { + this._scale = options.width; + delete options.width; + + if ( options.height ) { + $.console.error( "specifying both width and height to a drawer is not supported" ); + delete options.height; + } + } else if ( options.height ) { + this._scale = options.height / this.normHeight; + delete options.height; + } else { + this._scale = 1; + } + + this._worldWidth = this._scale; + this._worldHeight = this.normHeight * this._scale; + + $.extend( true, this, { + + //internal state properties + viewer: null, + imageLoader: new $.ImageLoader(), + tilesMatrix: {}, // A '3d' dictionary [level][x][y] --> Tile. + tilesLoaded: [], // An unordered list of Tiles with loaded images. + coverage: {}, // A '3d' dictionary [level][x][y] --> Boolean. + lastDrawn: [], // An unordered list of Tiles drawn last frame. + lastResetTime: 0, // Last time for which the drawer was reset. + midUpdate: false, // Is the drawer currently updating the viewport? + updateAgain: true, // Does the drawer need to update the viewport again? + + + //internal state / configurable settings + collectionOverlays: {}, // For collection mode. Here an overlay is actually a viewer. + + //configurable settings + opacity: $.DEFAULT_SETTINGS.opacity, + maxImageCacheCount: $.DEFAULT_SETTINGS.maxImageCacheCount, + minZoomImageRatio: $.DEFAULT_SETTINGS.minZoomImageRatio, + wrapHorizontal: $.DEFAULT_SETTINGS.wrapHorizontal, + wrapVertical: $.DEFAULT_SETTINGS.wrapVertical, + immediateRender: $.DEFAULT_SETTINGS.immediateRender, + blendTime: $.DEFAULT_SETTINGS.blendTime, + alwaysBlend: $.DEFAULT_SETTINGS.alwaysBlend, + minPixelRatio: $.DEFAULT_SETTINGS.minPixelRatio, + debugMode: $.DEFAULT_SETTINGS.debugMode, + timeout: $.DEFAULT_SETTINGS.timeout, + crossOriginPolicy: $.DEFAULT_SETTINGS.crossOriginPolicy + + }, options ); + + this.useCanvas = $.supportsCanvas && ( this.viewer ? this.viewer.useCanvas : true ); + /** + * The parent element of this Drawer instance, passed in when the Drawer was created. + * The parent of {@link OpenSeadragon.Drawer#canvas}. + * @member {Element} container + * @memberof OpenSeadragon.Drawer# + */ + this.container = $.getElement( this.element ); + /** + * A <canvas> element if the browser supports them, otherwise a <div> element. + * Child element of {@link OpenSeadragon.Drawer#container}. + * @member {Element} canvas + * @memberof OpenSeadragon.Drawer# + */ + this.canvas = $.makeNeutralElement( this.useCanvas ? "canvas" : "div" ); + /** + * 2d drawing context for {@link OpenSeadragon.Drawer#canvas} if it's a <canvas> element, otherwise null. + * @member {Object} context + * @memberof OpenSeadragon.Drawer# + */ + this.context = this.useCanvas ? this.canvas.getContext( "2d" ) : null; + + /** + * @member {Element} element + * @memberof OpenSeadragon.Drawer# + * @deprecated Alias for {@link OpenSeadragon.Drawer#container}. + */ + this.element = this.container; + + // We force our container to ltr because our drawing math doesn't work in rtl. + // This issue only affects our canvas renderer, but we do it always for consistency. + // Note that this means overlays you want to be rtl need to be explicitly set to rtl. + this.container.dir = 'ltr'; + + this.canvas.style.width = "100%"; + this.canvas.style.height = "100%"; + this.canvas.style.position = "absolute"; + $.setElementOpacity( this.canvas, this.opacity, true ); + + // explicit left-align + this.container.style.textAlign = "left"; + this.container.appendChild( this.canvas ); + + //this.profiler = new $.Profiler(); +}; + +$.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{ + + /** + * Adds an html element as an overlay to the current viewport. Useful for + * highlighting words or areas of interest on an image or other zoomable + * interface. + * @method + * @param {Element|String|Object} element - A reference to an element or an id for + * the element which will overlayed. Or an Object specifying the configuration for the overlay + * @param {OpenSeadragon.Point|OpenSeadragon.Rect} location - The point or + * rectangle which will be overlayed. + * @param {OpenSeadragon.OverlayPlacement} placement - The position of the + * viewport which the location coordinates will be treated as relative + * to. + * @param {function} onDraw - If supplied the callback is called when the overlay + * needs to be drawn. It it the responsibility of the callback to do any drawing/positioning. + * It is passed position, size and element. + * @fires OpenSeadragon.Viewer.event:add-overlay + * @deprecated - use {@link OpenSeadragon.Viewer#addOverlay} instead. + */ + addOverlay: function( element, location, placement, onDraw ) { + $.console.error("drawer.addOverlay is deprecated. Use viewer.addOverlay instead."); + this.viewer.addOverlay( element, location, placement, onDraw ); + return this; + }, + + /** + * Updates the overlay represented by the reference to the element or + * element id moving it to the new location, relative to the new placement. + * @method + * @param {OpenSeadragon.Point|OpenSeadragon.Rect} location - The point or + * rectangle which will be overlayed. + * @param {OpenSeadragon.OverlayPlacement} placement - The position of the + * viewport which the location coordinates will be treated as relative + * to. + * @return {OpenSeadragon.Drawer} Chainable. + * @fires OpenSeadragon.Viewer.event:update-overlay + * @deprecated - use {@link OpenSeadragon.Viewer#updateOverlay} instead. + */ + updateOverlay: function( element, location, placement ) { + $.console.error("drawer.updateOverlay is deprecated. Use viewer.updateOverlay instead."); + this.viewer.updateOverlay( element, location, placement ); + return this; + }, + + /** + * Removes and overlay identified by the reference element or element id + * and schedules and update. + * @method + * @param {Element|String} element - A reference to the element or an + * element id which represent the ovelay content to be removed. + * @return {OpenSeadragon.Drawer} Chainable. + * @fires OpenSeadragon.Viewer.event:remove-overlay + * @deprecated - use {@link OpenSeadragon.Viewer#removeOverlay} instead. + */ + removeOverlay: function( element ) { + $.console.error("drawer.removeOverlay is deprecated. Use viewer.removeOverlay instead."); + this.viewer.removeOverlay( element ); + return this; + }, + + /** + * Removes all currently configured Overlays from this Drawer and schedules + * and update. + * @method + * @return {OpenSeadragon.Drawer} Chainable. + * @fires OpenSeadragon.Viewer.event:clear-overlay + * @deprecated - use {@link OpenSeadragon.Viewer#clearOverlays} instead. + */ + clearOverlays: function() { + $.console.error("drawer.clearOverlays is deprecated. Use viewer.clearOverlays instead."); + this.viewer.clearOverlays(); + return this; + }, + + /** + * Set the opacity of the drawer. + * @method + * @param {Number} opacity + * @return {OpenSeadragon.Drawer} Chainable. + */ + setOpacity: function( opacity ) { + this.opacity = opacity; + $.setElementOpacity( this.canvas, this.opacity, true ); + return this; + }, + + /** + * Get the opacity of the drawer. + * @method + * @returns {Number} + */ + getOpacity: function() { + return this.opacity; + }, + /** + * Returns whether the Drawer is scheduled for an update at the + * soonest possible opportunity. + * @method + * @returns {Boolean} - Whether the Drawer is scheduled for an update at the + * soonest possible opportunity. + */ + needsUpdate: function() { + return this.updateAgain; + }, + + /** + * Returns the total number of tiles that have been loaded by this Drawer. + * @method + * @returns {Number} - The total number of tiles that have been loaded by + * this Drawer. + */ + numTilesLoaded: function() { + return this.tilesLoaded.length; + }, + + /** + * Clears all tiles and triggers an update on the next call to + * Drawer.prototype.update(). + * @method + * @return {OpenSeadragon.Drawer} Chainable. + */ + reset: function() { + clearTiles( this ); + this.lastResetTime = $.now(); + this.updateAgain = true; + return this; + }, + + /** + * Forces the Drawer to update. + * @method + * @return {OpenSeadragon.Drawer} Chainable. + */ + update: function() { + //this.profiler.beginUpdate(); + this.midUpdate = true; + updateViewport( this ); + this.midUpdate = false; + //this.profiler.endUpdate(); + return this; + }, + + /** + * Returns whether rotation is supported or not. + * @method + * @return {Boolean} True if rotation is supported. + */ + canRotate: function() { + return this.useCanvas; + }, + + /** + * Destroy the drawer (unload current loaded tiles) + * @method + * @return null + */ + destroy: function() { + //unload current loaded tiles (=empty TILE_CACHE) + for ( var i = 0; i < this.tilesLoaded.length; ++i ) { + this.tilesLoaded[i].unload(); + } + + //force unloading of current canvas (1x1 will be gc later, trick not necessarily needed) + this.canvas.width = 1; + this.canvas.height = 1; + } +}; + +/** + * @private + * @inner + * Pretty much every other line in this needs to be documented so it's clear + * how each piece of this routine contributes to the drawing process. That's + * why there are so many TODO's inside this function. + */ +function updateViewport( drawer ) { + + drawer.updateAgain = false; + + if( drawer.viewer ){ + /** + * - Needs documentation - + * + * @event update-viewport + * @memberof OpenSeadragon.Viewer + * @type {object} + * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. + * @property {?Object} userData - Arbitrary subscriber-defined object. + */ + drawer.viewer.raiseEvent( 'update-viewport', {} ); + } + + var tile, + level, + best = null, + haveDrawn = false, + currentTime = $.now(), + viewportSize = drawer.viewport.getContainerSize(), + viewportBounds = drawer.viewport.getBounds( true ), + viewportTL = viewportBounds.getTopLeft(), + viewportBR = viewportBounds.getBottomRight(), + zeroRatioC = drawer.viewport.deltaPixelsFromPoints( + drawer.source.getPixelRatio( 0 ), + true + ).x * drawer._scale, + lowestLevel = Math.max( + drawer.source.minLevel, + Math.floor( + Math.log( drawer.minZoomImageRatio ) / + Math.log( 2 ) + ) + ), + highestLevel = Math.min( + Math.abs(drawer.source.maxLevel), + Math.abs(Math.floor( + Math.log( zeroRatioC / drawer.minPixelRatio ) / + Math.log( 2 ) + )) + ), + degrees = drawer.viewport.degrees, + renderPixelRatioC, + renderPixelRatioT, + zeroRatioT, + optimalRatio, + levelOpacity, + levelVisibility; + + viewportTL.x -= drawer._worldX; + viewportTL.y -= drawer._worldY; + viewportBR.x -= drawer._worldX; + viewportBR.y -= drawer._worldY; + + // Reset tile's internal drawn state + while ( drawer.lastDrawn.length > 0 ) { + tile = drawer.lastDrawn.pop(); + tile.beingDrawn = false; + } + + // Clear canvas + drawer.canvas.innerHTML = ""; + if ( drawer.useCanvas ) { + if( drawer.canvas.width != viewportSize.x || + drawer.canvas.height != viewportSize.y ){ + drawer.canvas.width = viewportSize.x; + drawer.canvas.height = viewportSize.y; + } + drawer.context.clearRect( 0, 0, viewportSize.x, viewportSize.y ); + } + + //Change bounds for rotation + if (degrees === 90 || degrees === 270) { + var rotatedBounds = viewportBounds.rotate( degrees ); + viewportTL = rotatedBounds.getTopLeft(); + viewportBR = rotatedBounds.getBottomRight(); + } + + //Don't draw if completely outside of the viewport + if ( !drawer.wrapHorizontal && + ( viewportBR.x < 0 || viewportTL.x > drawer._worldWidth ) ) { + return; + } else if + ( !drawer.wrapVertical && + ( viewportBR.y < 0 || viewportTL.y > drawer._worldHeight ) ) { + return; + } + + // Calculate viewport rect / bounds + if ( !drawer.wrapHorizontal ) { + viewportTL.x = Math.max( viewportTL.x, 0 ); + viewportBR.x = Math.min( viewportBR.x, drawer._worldWidth ); + } + if ( !drawer.wrapVertical ) { + viewportTL.y = Math.max( viewportTL.y, 0 ); + viewportBR.y = Math.min( viewportBR.y, drawer._worldHeight ); + } + + // Calculations for the interval of levels to draw + // (above in initial var statement) + // can return invalid intervals; fix that here if necessary + lowestLevel = Math.min( lowestLevel, highestLevel ); + + // Update any level that will be drawn + var drawLevel; // FIXME: drawLevel should have a more explanatory name + for ( level = highestLevel; level >= lowestLevel; level-- ) { + drawLevel = false; + + //Avoid calculations for draw if we have already drawn this + renderPixelRatioC = drawer.viewport.deltaPixelsFromPoints( + drawer.source.getPixelRatio( level ), + true + ).x * drawer._scale; + + if ( ( !haveDrawn && renderPixelRatioC >= drawer.minPixelRatio ) || + ( level == lowestLevel ) ) { + drawLevel = true; + haveDrawn = true; + } else if ( !haveDrawn ) { + continue; + } + + //Perform calculations for draw if we haven't drawn this + renderPixelRatioT = drawer.viewport.deltaPixelsFromPoints( + drawer.source.getPixelRatio( level ), + false + ).x * drawer._scale; + + zeroRatioT = drawer.viewport.deltaPixelsFromPoints( + drawer.source.getPixelRatio( + Math.max( + drawer.source.getClosestLevel( drawer.viewport.containerSize ) - 1, + 0 + ) + ), + false + ).x * drawer._scale; + + optimalRatio = drawer.immediateRender ? + 1 : + zeroRatioT; + + levelOpacity = Math.min( 1, ( renderPixelRatioC - 0.5 ) / 0.5 ); + + levelVisibility = optimalRatio / Math.abs( + optimalRatio - renderPixelRatioT + ); + + // Update the level and keep track of 'best' tile to load + best = updateLevel( + drawer, + haveDrawn, + drawLevel, + level, + levelOpacity, + levelVisibility, + viewportTL, + viewportBR, + currentTime, + best + ); + + // Stop the loop if lower-res tiles would all be covered by + // already drawn tiles + if ( providesCoverage( drawer.coverage, level ) ) { + break; + } + } + + // Perform the actual drawing + drawTiles( drawer, drawer.lastDrawn ); + + // Load the new 'best' tile + if ( best ) { + loadTile( drawer, best, currentTime ); + // because we haven't finished drawing, so + drawer.updateAgain = true; + } + +} + + +function updateLevel( drawer, haveDrawn, drawLevel, level, levelOpacity, levelVisibility, viewportTL, viewportBR, currentTime, best ){ + + var x, y, + tileTL, + tileBR, + numberOfTiles, + viewportCenter = drawer.viewport.pixelFromPoint( drawer.viewport.getCenter() ); + + + if( drawer.viewer ){ + /** + * - Needs documentation - + * + * @event update-level + * @memberof OpenSeadragon.Viewer + * @type {object} + * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. + * @property {Object} havedrawn + * @property {Object} level + * @property {Object} opacity + * @property {Object} visibility + * @property {Object} topleft + * @property {Object} bottomright + * @property {Object} currenttime + * @property {Object} best + * @property {?Object} userData - Arbitrary subscriber-defined object. + */ + drawer.viewer.raiseEvent( 'update-level', { + havedrawn: haveDrawn, + level: level, + opacity: levelOpacity, + visibility: levelVisibility, + topleft: viewportTL, + bottomright: viewportBR, + currenttime: currentTime, + best: best + }); + } + + //OK, a new drawing so do your calculations + tileTL = drawer.source.getTileAtPoint( level, viewportTL.divide( drawer._scale )); + tileBR = drawer.source.getTileAtPoint( level, viewportBR.divide( drawer._scale )); + numberOfTiles = drawer.source.getNumTiles( level ); + + resetCoverage( drawer.coverage, level ); + + if ( !drawer.wrapHorizontal ) { + tileBR.x = Math.min( tileBR.x, numberOfTiles.x - 1 ); + } + if ( !drawer.wrapVertical ) { + tileBR.y = Math.min( tileBR.y, numberOfTiles.y - 1 ); + } + + for ( x = tileTL.x; x <= tileBR.x; x++ ) { + for ( y = tileTL.y; y <= tileBR.y; y++ ) { + + best = updateTile( + drawer, + drawLevel, + haveDrawn, + x, y, + level, + levelOpacity, + levelVisibility, + viewportCenter, + numberOfTiles, + currentTime, + best + ); + + } + } + + return best; +} + +function updateTile( drawer, drawLevel, haveDrawn, x, y, level, levelOpacity, levelVisibility, viewportCenter, numberOfTiles, currentTime, best){ + + var tile = getTile( + x, y, + level, + drawer.source, + drawer.tilesMatrix, + currentTime, + numberOfTiles, + drawer._worldWidth, + drawer._worldHeight + ), + drawTile = drawLevel; + + if( drawer.viewer ){ + /** + * - Needs documentation - + * + * @event update-tile + * @memberof OpenSeadragon.Viewer + * @type {object} + * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. + * @property {OpenSeadragon.Tile} tile + * @property {?Object} userData - Arbitrary subscriber-defined object. + */ + drawer.viewer.raiseEvent( 'update-tile', { + tile: tile + }); + } + + setCoverage( drawer.coverage, level, x, y, false ); + + if ( !tile.exists ) { + return best; + } + + if ( haveDrawn && !drawTile ) { + if ( isCovered( drawer.coverage, level, x, y ) ) { + setCoverage( drawer.coverage, level, x, y, true ); + } else { + drawTile = true; + } + } + + if ( !drawTile ) { + return best; + } + + positionTile( + tile, + drawer.source.tileOverlap, + drawer.viewport, + viewportCenter, + levelVisibility, + drawer + ); + + if ( tile.loaded ) { + var needsUpdate = blendTile( + drawer, + tile, + x, y, + level, + levelOpacity, + currentTime + ); + + if ( needsUpdate ) { + drawer.updateAgain = true; + } + } else if ( tile.loading ) { + // the tile is already in the download queue + // thanks josh1093 for finally translating this typo + } else { + best = compareTiles( best, tile ); + } + + return best; +} + +function getTile( x, y, level, tileSource, tilesMatrix, time, numTiles, worldWidth, worldHeight ) { + var xMod, + yMod, + bounds, + exists, + url, + tile; + + if ( !tilesMatrix[ level ] ) { + tilesMatrix[ level ] = {}; + } + if ( !tilesMatrix[ level ][ x ] ) { + tilesMatrix[ level ][ x ] = {}; + } + + if ( !tilesMatrix[ level ][ x ][ y ] ) { + xMod = ( numTiles.x + ( x % numTiles.x ) ) % numTiles.x; + yMod = ( numTiles.y + ( y % numTiles.y ) ) % numTiles.y; + bounds = tileSource.getTileBounds( level, xMod, yMod ); + exists = tileSource.tileExists( level, xMod, yMod ); + url = tileSource.getTileUrl( level, xMod, yMod ); + + bounds.x += worldWidth * ( x - xMod ) / numTiles.x; + bounds.y += worldHeight * ( y - yMod ) / numTiles.y; + + tilesMatrix[ level ][ x ][ y ] = new $.Tile( + level, + x, + y, + bounds, + exists, + url + ); + } + + tile = tilesMatrix[ level ][ x ][ y ]; + tile.lastTouchTime = time; + + return tile; +} + +function loadTile( drawer, tile, time ) { + if( drawer.viewport.collectionMode ){ + drawer.midUpdate = false; + onTileLoad( drawer, tile, time ); + } else { + tile.loading = true; + drawer.imageLoader.addJob({ + src: tile.url, + crossOriginPolicy: drawer.crossOriginPolicy, + callback: function( image ){ + onTileLoad( drawer, tile, time, image ); + } + }); + } +} + +function onTileLoad( drawer, tile, time, image ) { + var insertionIndex, + cutoff, + worstTile, + worstTime, + worstLevel, + worstTileIndex, + prevTile, + prevTime, + prevLevel, + i; + + tile.loading = false; + + if ( drawer.midUpdate ) { + $.console.warn( "Tile load callback in middle of drawing routine." ); + return; + } else if ( !image && !drawer.viewport.collectionMode ) { + $.console.log( "Tile %s failed to load: %s", tile, tile.url ); + if( !drawer.debugMode ){ + tile.exists = false; + return; + } + } else if ( time < drawer.lastResetTime ) { + $.console.log( "Ignoring tile %s loaded before reset: %s", tile, tile.url ); + return; + } + + tile.loaded = true; + tile.image = image; + + insertionIndex = drawer.tilesLoaded.length; + + if ( drawer.tilesLoaded.length >= drawer.maxImageCacheCount ) { + cutoff = Math.ceil( Math.log( drawer.source.getTileSize(tile.level) ) / Math.log( 2 ) ); + + worstTile = null; + worstTileIndex = -1; + + for ( i = drawer.tilesLoaded.length - 1; i >= 0; i-- ) { + prevTile = drawer.tilesLoaded[ i ]; + + if ( prevTile.level <= drawer.cutoff || prevTile.beingDrawn ) { + continue; + } else if ( !worstTile ) { + worstTile = prevTile; + worstTileIndex = i; + continue; + } + + prevTime = prevTile.lastTouchTime; + worstTime = worstTile.lastTouchTime; + prevLevel = prevTile.level; + worstLevel = worstTile.level; + + if ( prevTime < worstTime || + ( prevTime == worstTime && prevLevel > worstLevel ) ) { + worstTile = prevTile; + worstTileIndex = i; + } + } + + if ( worstTile && worstTileIndex >= 0 ) { + worstTile.unload(); + insertionIndex = worstTileIndex; + } + } + + drawer.tilesLoaded[ insertionIndex ] = tile; + drawer.updateAgain = true; +} + + +function positionTile( tile, overlap, viewport, viewportCenter, levelVisibility, drawer ){ + var boundsTL = tile.bounds.getTopLeft(); + + boundsTL.x *= drawer._scale; + boundsTL.y *= drawer._scale; + boundsTL.x += drawer._worldX; + boundsTL.y += drawer._worldY; + + var boundsSize = tile.bounds.getSize(); + + boundsSize.x *= drawer._scale; + boundsSize.y *= drawer._scale; + + var positionC = viewport.pixelFromPoint( boundsTL, true ), + positionT = viewport.pixelFromPoint( boundsTL, false ), + sizeC = viewport.deltaPixelsFromPoints( boundsSize, true ), + sizeT = viewport.deltaPixelsFromPoints( boundsSize, false ), + tileCenter = positionT.plus( sizeT.divide( 2 ) ), + tileDistance = viewportCenter.distanceTo( tileCenter ); + + if ( !overlap ) { + sizeC = sizeC.plus( new $.Point( 1, 1 ) ); + } + + tile.position = positionC; + tile.size = sizeC; + tile.distance = tileDistance; + tile.visibility = levelVisibility; +} + + +function blendTile( drawer, tile, x, y, level, levelOpacity, currentTime ){ + var blendTimeMillis = 1000 * drawer.blendTime, + deltaTime, + opacity; + + if ( !tile.blendStart ) { + tile.blendStart = currentTime; + } + + deltaTime = currentTime - tile.blendStart; + opacity = blendTimeMillis ? Math.min( 1, deltaTime / ( blendTimeMillis ) ) : 1; + + if ( drawer.alwaysBlend ) { + opacity *= levelOpacity; + } + + tile.opacity = opacity; + + drawer.lastDrawn.push( tile ); + + if ( opacity == 1 ) { + setCoverage( drawer.coverage, level, x, y, true ); + } else if ( deltaTime < blendTimeMillis ) { + return true; + } + + return false; +} + + +function clearTiles( drawer ) { + drawer.tilesMatrix = {}; + drawer.tilesLoaded = []; +} + +/** + * @private + * @inner + * Returns true if the given tile provides coverage to lower-level tiles of + * lower resolution representing the same content. If neither x nor y is + * given, returns true if the entire visible level provides coverage. + * + * Note that out-of-bounds tiles provide coverage in this sense, since + * there's no content that they would need to cover. Tiles at non-existent + * levels that are within the image bounds, however, do not. + */ +function providesCoverage( coverage, level, x, y ) { + var rows, + cols, + i, j; + + if ( !coverage[ level ] ) { + return false; + } + + if ( x === undefined || y === undefined ) { + rows = coverage[ level ]; + for ( i in rows ) { + if ( rows.hasOwnProperty( i ) ) { + cols = rows[ i ]; + for ( j in cols ) { + if ( cols.hasOwnProperty( j ) && !cols[ j ] ) { + return false; + } + } + } + } + + return true; + } + + return ( + coverage[ level ][ x] === undefined || + coverage[ level ][ x ][ y ] === undefined || + coverage[ level ][ x ][ y ] === true + ); +} + +/** + * @private + * @inner + * Returns true if the given tile is completely covered by higher-level + * tiles of higher resolution representing the same content. If neither x + * nor y is given, returns true if the entire visible level is covered. + */ +function isCovered( coverage, level, x, y ) { + if ( x === undefined || y === undefined ) { + return providesCoverage( coverage, level + 1 ); + } else { + return ( + providesCoverage( coverage, level + 1, 2 * x, 2 * y ) && + providesCoverage( coverage, level + 1, 2 * x, 2 * y + 1 ) && + providesCoverage( coverage, level + 1, 2 * x + 1, 2 * y ) && + providesCoverage( coverage, level + 1, 2 * x + 1, 2 * y + 1 ) + ); + } +} + +/** + * @private + * @inner + * Sets whether the given tile provides coverage or not. + */ +function setCoverage( coverage, level, x, y, covers ) { + if ( !coverage[ level ] ) { + $.console.warn( + "Setting coverage for a tile before its level's coverage has been reset: %s", + level + ); + return; + } + + if ( !coverage[ level ][ x ] ) { + coverage[ level ][ x ] = {}; + } + + coverage[ level ][ x ][ y ] = covers; +} + +/** + * @private + * @inner + * Resets coverage information for the given level. This should be called + * after every draw routine. Note that at the beginning of the next draw + * routine, coverage for every visible tile should be explicitly set. + */ +function resetCoverage( coverage, level ) { + coverage[ level ] = {}; +} + +/** + * @private + * @inner + * Determines whether the 'last best' tile for the area is better than the + * tile in question. + */ +function compareTiles( previousBest, tile ) { + if ( !previousBest ) { + return tile; + } + + if ( tile.visibility > previousBest.visibility ) { + return tile; + } else if ( tile.visibility == previousBest.visibility ) { + if ( tile.distance < previousBest.distance ) { + return tile; + } + } + + return previousBest; +} + +function drawTiles( drawer, lastDrawn ){ + var i, + tile, + tileKey, + viewer, + viewport, + position, + tileSource, + collectionTileSource; + + // We need a callback to give image manipulation a chance to happen + var drawingHandler = function(args) { + if (drawer.viewer) { + /** + * This event is fired just before the tile is drawn giving the application a chance to alter the image. + * + * NOTE: This event is only fired when the drawer is using a . + * + * @event tile-drawing + * @memberof OpenSeadragon.Viewer + * @type {object} + * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. + * @property {OpenSeadragon.Tile} tile + * @property {?Object} userData - 'context', 'tile' and 'rendered'. + */ + drawer.viewer.raiseEvent('tile-drawing', args); + } + }; + + for ( i = lastDrawn.length - 1; i >= 0; i-- ) { + tile = lastDrawn[ i ]; + + //We dont actually 'draw' a collection tile, rather its used to house + //an overlay which does the drawing in its own viewport + if( drawer.viewport.collectionMode ){ + + tileKey = tile.x + '/' + tile.y; + viewport = drawer.viewport; + collectionTileSource = viewport.collectionTileSource; + + if( !drawer.collectionOverlays[ tileKey ] ){ + + position = collectionTileSource.layout == 'horizontal' ? + tile.y + ( tile.x * collectionTileSource.rows ) : + tile.x + ( tile.y * collectionTileSource.rows ); + + if (position < collectionTileSource.tileSources.length) { + tileSource = collectionTileSource.tileSources[ position ]; + } else { + tileSource = null; + } + + //$.console.log("Rendering collection tile %s | %s | %s", tile.y, tile.y, position); + if( tileSource ){ + drawer.collectionOverlays[ tileKey ] = viewer = new $.Viewer({ + hash: viewport.viewer.hash + "-" + tileKey, + element: $.makeNeutralElement( "div" ), + mouseNavEnabled: false, + showNavigator: false, + showSequenceControl: false, + showNavigationControl: false, + tileSources: [ + tileSource + ] + }); + + //TODO: IE seems to barf on this, not sure if its just the border + // but we probably need to clear this up with a better + // test of support for various css features + if( SUBPIXEL_RENDERING ){ + viewer.element.style.border = '1px solid rgba(255,255,255,0.38)'; + viewer.element.style['-webkit-box-reflect'] = + 'below 0px -webkit-gradient('+ + 'linear,left '+ + 'top,left '+ + 'bottom,from(transparent),color-stop(62%,transparent),to(rgba(255,255,255,0.62))'+ + ')'; + } + + drawer.viewer.addOverlay( + viewer.element, + tile.bounds + ); + } + + }else{ + viewer = drawer.collectionOverlays[ tileKey ]; + if( viewer.viewport ){ + viewer.viewport.resize( tile.size, true ); + viewer.viewport.goHome( true ); + } + } + + } else { + + if ( drawer.useCanvas ) { + // TODO do this in a more performant way + // specifically, don't save,rotate,restore every time we draw a tile + if( drawer.viewport.degrees !== 0 ) { + offsetForRotation( tile, drawer.canvas, drawer.context, drawer.viewport.degrees ); + tile.drawCanvas( drawer.context, drawingHandler ); + restoreRotationChanges( tile, drawer.canvas, drawer.context ); + } else { + tile.drawCanvas( drawer.context, drawingHandler ); + } + } else { + tile.drawHTML( drawer.canvas ); + } + + + tile.beingDrawn = true; + } + + if( drawer.debugMode ){ + try{ + drawDebugInfo( drawer, tile, lastDrawn.length, i ); + }catch(e){ + $.console.error(e); + } + } + + if( drawer.viewer ){ + /** + * - Needs documentation - + * + * @event tile-drawn + * @memberof OpenSeadragon.Viewer + * @type {object} + * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. + * @property {OpenSeadragon.Tile} tile + * @property {?Object} userData - Arbitrary subscriber-defined object. + */ + drawer.viewer.raiseEvent( 'tile-drawn', { + tile: tile + }); + } + } +} + +function offsetForRotation( tile, canvas, context, degrees ){ + var cx = canvas.width / 2, + cy = canvas.height / 2, + px = tile.position.x - cx, + py = tile.position.y - cy; + + context.save(); + + context.translate(cx, cy); + context.rotate( Math.PI / 180 * degrees); + tile.position.x = px; + tile.position.y = py; +} + +function restoreRotationChanges( tile, canvas, context ){ + var cx = canvas.width / 2, + cy = canvas.height / 2, + px = tile.position.x + cx, + py = tile.position.y + cy; + + tile.position.x = px; + tile.position.y = py; + + context.restore(); +} + + +function drawDebugInfo( drawer, tile, count, i ){ + + if ( drawer.useCanvas ) { + drawer.context.save(); + drawer.context.lineWidth = 2; + drawer.context.font = 'small-caps bold 13px ariel'; + drawer.context.strokeStyle = drawer.debugGridColor; + drawer.context.fillStyle = drawer.debugGridColor; + drawer.context.strokeRect( + tile.position.x, + tile.position.y, + tile.size.x, + tile.size.y + ); + if( tile.x === 0 && tile.y === 0 ){ + drawer.context.fillText( + "Zoom: " + drawer.viewport.getZoom(), + tile.position.x, + tile.position.y - 30 + ); + drawer.context.fillText( + "Pan: " + drawer.viewport.getBounds().toString(), + tile.position.x, + tile.position.y - 20 + ); + } + drawer.context.fillText( + "Level: " + tile.level, + tile.position.x + 10, + tile.position.y + 20 + ); + drawer.context.fillText( + "Column: " + tile.x, + tile.position.x + 10, + tile.position.y + 30 + ); + drawer.context.fillText( + "Row: " + tile.y, + tile.position.x + 10, + tile.position.y + 40 + ); + drawer.context.fillText( + "Order: " + i + " of " + count, + tile.position.x + 10, + tile.position.y + 50 + ); + drawer.context.fillText( + "Size: " + tile.size.toString(), + tile.position.x + 10, + tile.position.y + 60 + ); + drawer.context.fillText( + "Position: " + tile.position.toString(), + tile.position.x + 10, + tile.position.y + 70 + ); + drawer.context.restore(); + } +} + + +}( OpenSeadragon )); From dbb60c0ab27f242dc39007314f3c9056e7ab8ce5 Mon Sep 17 00:00:00 2001 From: Ian Gilman Date: Wed, 6 Aug 2014 13:48:18 -0700 Subject: [PATCH 02/16] Splitting drawer into drawer/tileCache/tiledImage; adding World --- Gruntfile.js | 5 +- src/drawer.js | 1051 ++++++------------------------------------ src/openseadragon.js | 3 +- src/tilecache.js | 113 +++++ src/tiledimage.js | 685 ++++++--------------------- src/viewer.js | 66 +-- src/world.js | 164 +++++++ 7 files changed, 603 insertions(+), 1484 deletions(-) create mode 100644 src/tilecache.js create mode 100644 src/world.js diff --git a/Gruntfile.js b/Gruntfile.js index 18ac1bd2..8dff6cd2 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -48,7 +48,10 @@ module.exports = function(grunt) { "src/tile.js", "src/overlay.js", "src/drawer.js", - "src/viewport.js" + "src/viewport.js", + "src/tiledimage.js", + "src/tilecache.js", + "src/world.js" ]; // ---------- diff --git a/src/drawer.js b/src/drawer.js index a4b7f2ec..1f24fbdb 100644 --- a/src/drawer.js +++ b/src/drawer.js @@ -50,7 +50,6 @@ var DEVICE_SCREEN = $.getWindowSize(), /** * @class Drawer * @classdesc Handles rendering of tiles for an {@link OpenSeadragon.Viewer}. - * A new instance is created for each TileSource opened (see {@link OpenSeadragon.Viewer#drawer}). * * @memberof OpenSeadragon * @param {OpenSeadragon.TileSource} source - Reference to Viewer tile source. @@ -58,6 +57,9 @@ var DEVICE_SCREEN = $.getWindowSize(), * @param {Element} element - Parent element. */ $.Drawer = function( options ) { + var self = this; + + $.console.assert( options.viewer, "[Drawer] options.viewer is required" ); //backward compatibility for positional args while prefering more //idiomatic javascript options object as the only argument @@ -72,32 +74,10 @@ $.Drawer = function( options ) { }; } - this._worldX = options.x || 0; - delete options.x; - this._worldY = options.y || 0; - delete options.y; - - // Ratio of zoomable image height to width. - this.normHeight = options.source.dimensions.y / options.source.dimensions.x; - - if ( options.width ) { - this._scale = options.width; - delete options.width; - - if ( options.height ) { - $.console.error( "specifying both width and height to a drawer is not supported" ); - delete options.height; - } - } else if ( options.height ) { - this._scale = options.height / this.normHeight; - delete options.height; - } else { - this._scale = 1; + if ( options.source ) { + $.console.error( "[Drawer] options.source is no longer accepted; use TiledImage instead" ); } - this._worldWidth = this._scale; - this._worldHeight = this.normHeight * this._scale; - $.extend( true, this, { //internal state properties @@ -174,7 +154,24 @@ $.Drawer = function( options ) { this.container.style.textAlign = "left"; this.container.appendChild( this.canvas ); - //this.profiler = new $.Profiler(); + // We need a callback to give image manipulation a chance to happen + this._drawingHandler = function(args) { + if (self.viewer) { + /** + * This event is fired just before the tile is drawn giving the application a chance to alter the image. + * + * NOTE: This event is only fired when the drawer is using a . + * + * @event tile-drawing + * @memberof OpenSeadragon.Viewer + * @type {object} + * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. + * @property {OpenSeadragon.Tile} tile + * @property {?Object} userData - 'context', 'tile' and 'rendered'. + */ + self.viewer.raiseEvent('tile-drawing', args); + } + }; }; $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{ @@ -343,895 +340,121 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{ //force unloading of current canvas (1x1 will be gc later, trick not necessarily needed) this.canvas.width = 1; this.canvas.height = 1; + }, + + clear: function() { + this.canvas.innerHTML = ""; + if ( this.useCanvas ) { + var viewportSize = this.viewport.getContainerSize(); + if( this.canvas.width != viewportSize.x || + this.canvas.height != viewportSize.y ) { + this.canvas.width = viewportSize.x; + this.canvas.height = viewportSize.y; + } + this.context.clearRect( 0, 0, viewportSize.x, viewportSize.y ); + } + }, + + drawTile: function( tile ) { + if ( this.useCanvas ) { + // TODO do this in a more performant way + // specifically, don't save,rotate,restore every time we draw a tile + if( this.viewport.degrees !== 0 ) { + this._offsetForRotation( tile, this.viewport.degrees ); + tile.drawCanvas( this.context, this._drawingHandler ); + this._restoreRotationChanges( tile ); + } else { + tile.drawCanvas( this.context, this._drawingHandler ); + } + } else { + tile.drawHTML( this.canvas ); + } + }, + + drawDebugInfo: function( tile, count, i ){ + if ( this.useCanvas ) { + this.context.save(); + this.context.lineWidth = 2; + this.context.font = 'small-caps bold 13px ariel'; + this.context.strokeStyle = this.debugGridColor; + this.context.fillStyle = this.debugGridColor; + this.context.strokeRect( + tile.position.x, + tile.position.y, + tile.size.x, + tile.size.y + ); + if( tile.x === 0 && tile.y === 0 ){ + this.context.fillText( + "Zoom: " + this.viewport.getZoom(), + tile.position.x, + tile.position.y - 30 + ); + this.context.fillText( + "Pan: " + this.viewport.getBounds().toString(), + tile.position.x, + tile.position.y - 20 + ); + } + this.context.fillText( + "Level: " + tile.level, + tile.position.x + 10, + tile.position.y + 20 + ); + this.context.fillText( + "Column: " + tile.x, + tile.position.x + 10, + tile.position.y + 30 + ); + this.context.fillText( + "Row: " + tile.y, + tile.position.x + 10, + tile.position.y + 40 + ); + this.context.fillText( + "Order: " + i + " of " + count, + tile.position.x + 10, + tile.position.y + 50 + ); + this.context.fillText( + "Size: " + tile.size.toString(), + tile.position.x + 10, + tile.position.y + 60 + ); + this.context.fillText( + "Position: " + tile.position.toString(), + tile.position.x + 10, + tile.position.y + 70 + ); + this.context.restore(); + } + }, + + _offsetForRotation: function( tile, degrees ){ + var cx = this.canvas.width / 2, + cy = this.canvas.height / 2, + px = tile.position.x - cx, + py = tile.position.y - cy; + + this.context.save(); + + this.context.translate(cx, cy); + this.context.rotate( Math.PI / 180 * degrees); + tile.position.x = px; + tile.position.y = py; + }, + + _restoreRotationChanges: function( tile ){ + var cx = this.canvas.width / 2, + cy = this.canvas.height / 2, + px = tile.position.x + cx, + py = tile.position.y + cy; + + tile.position.x = px; + tile.position.y = py; + + this.context.restore(); } }; -/** - * @private - * @inner - * Pretty much every other line in this needs to be documented so it's clear - * how each piece of this routine contributes to the drawing process. That's - * why there are so many TODO's inside this function. - */ -function updateViewport( drawer ) { - - drawer.updateAgain = false; - - if( drawer.viewer ){ - /** - * - Needs documentation - - * - * @event update-viewport - * @memberof OpenSeadragon.Viewer - * @type {object} - * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. - * @property {?Object} userData - Arbitrary subscriber-defined object. - */ - drawer.viewer.raiseEvent( 'update-viewport', {} ); - } - - var tile, - level, - best = null, - haveDrawn = false, - currentTime = $.now(), - viewportSize = drawer.viewport.getContainerSize(), - viewportBounds = drawer.viewport.getBounds( true ), - viewportTL = viewportBounds.getTopLeft(), - viewportBR = viewportBounds.getBottomRight(), - zeroRatioC = drawer.viewport.deltaPixelsFromPoints( - drawer.source.getPixelRatio( 0 ), - true - ).x * drawer._scale, - lowestLevel = Math.max( - drawer.source.minLevel, - Math.floor( - Math.log( drawer.minZoomImageRatio ) / - Math.log( 2 ) - ) - ), - highestLevel = Math.min( - Math.abs(drawer.source.maxLevel), - Math.abs(Math.floor( - Math.log( zeroRatioC / drawer.minPixelRatio ) / - Math.log( 2 ) - )) - ), - degrees = drawer.viewport.degrees, - renderPixelRatioC, - renderPixelRatioT, - zeroRatioT, - optimalRatio, - levelOpacity, - levelVisibility; - - viewportTL.x -= drawer._worldX; - viewportTL.y -= drawer._worldY; - viewportBR.x -= drawer._worldX; - viewportBR.y -= drawer._worldY; - - // Reset tile's internal drawn state - while ( drawer.lastDrawn.length > 0 ) { - tile = drawer.lastDrawn.pop(); - tile.beingDrawn = false; - } - - // Clear canvas - drawer.canvas.innerHTML = ""; - if ( drawer.useCanvas ) { - if( drawer.canvas.width != viewportSize.x || - drawer.canvas.height != viewportSize.y ){ - drawer.canvas.width = viewportSize.x; - drawer.canvas.height = viewportSize.y; - } - drawer.context.clearRect( 0, 0, viewportSize.x, viewportSize.y ); - } - - //Change bounds for rotation - if (degrees === 90 || degrees === 270) { - var rotatedBounds = viewportBounds.rotate( degrees ); - viewportTL = rotatedBounds.getTopLeft(); - viewportBR = rotatedBounds.getBottomRight(); - } - - //Don't draw if completely outside of the viewport - if ( !drawer.wrapHorizontal && - ( viewportBR.x < 0 || viewportTL.x > drawer._worldWidth ) ) { - return; - } else if - ( !drawer.wrapVertical && - ( viewportBR.y < 0 || viewportTL.y > drawer._worldHeight ) ) { - return; - } - - // Calculate viewport rect / bounds - if ( !drawer.wrapHorizontal ) { - viewportTL.x = Math.max( viewportTL.x, 0 ); - viewportBR.x = Math.min( viewportBR.x, drawer._worldWidth ); - } - if ( !drawer.wrapVertical ) { - viewportTL.y = Math.max( viewportTL.y, 0 ); - viewportBR.y = Math.min( viewportBR.y, drawer._worldHeight ); - } - - // Calculations for the interval of levels to draw - // (above in initial var statement) - // can return invalid intervals; fix that here if necessary - lowestLevel = Math.min( lowestLevel, highestLevel ); - - // Update any level that will be drawn - var drawLevel; // FIXME: drawLevel should have a more explanatory name - for ( level = highestLevel; level >= lowestLevel; level-- ) { - drawLevel = false; - - //Avoid calculations for draw if we have already drawn this - renderPixelRatioC = drawer.viewport.deltaPixelsFromPoints( - drawer.source.getPixelRatio( level ), - true - ).x * drawer._scale; - - if ( ( !haveDrawn && renderPixelRatioC >= drawer.minPixelRatio ) || - ( level == lowestLevel ) ) { - drawLevel = true; - haveDrawn = true; - } else if ( !haveDrawn ) { - continue; - } - - //Perform calculations for draw if we haven't drawn this - renderPixelRatioT = drawer.viewport.deltaPixelsFromPoints( - drawer.source.getPixelRatio( level ), - false - ).x * drawer._scale; - - zeroRatioT = drawer.viewport.deltaPixelsFromPoints( - drawer.source.getPixelRatio( - Math.max( - drawer.source.getClosestLevel( drawer.viewport.containerSize ) - 1, - 0 - ) - ), - false - ).x * drawer._scale; - - optimalRatio = drawer.immediateRender ? - 1 : - zeroRatioT; - - levelOpacity = Math.min( 1, ( renderPixelRatioC - 0.5 ) / 0.5 ); - - levelVisibility = optimalRatio / Math.abs( - optimalRatio - renderPixelRatioT - ); - - // Update the level and keep track of 'best' tile to load - best = updateLevel( - drawer, - haveDrawn, - drawLevel, - level, - levelOpacity, - levelVisibility, - viewportTL, - viewportBR, - currentTime, - best - ); - - // Stop the loop if lower-res tiles would all be covered by - // already drawn tiles - if ( providesCoverage( drawer.coverage, level ) ) { - break; - } - } - - // Perform the actual drawing - drawTiles( drawer, drawer.lastDrawn ); - - // Load the new 'best' tile - if ( best ) { - loadTile( drawer, best, currentTime ); - // because we haven't finished drawing, so - drawer.updateAgain = true; - } - -} - - -function updateLevel( drawer, haveDrawn, drawLevel, level, levelOpacity, levelVisibility, viewportTL, viewportBR, currentTime, best ){ - - var x, y, - tileTL, - tileBR, - numberOfTiles, - viewportCenter = drawer.viewport.pixelFromPoint( drawer.viewport.getCenter() ); - - - if( drawer.viewer ){ - /** - * - Needs documentation - - * - * @event update-level - * @memberof OpenSeadragon.Viewer - * @type {object} - * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. - * @property {Object} havedrawn - * @property {Object} level - * @property {Object} opacity - * @property {Object} visibility - * @property {Object} topleft - * @property {Object} bottomright - * @property {Object} currenttime - * @property {Object} best - * @property {?Object} userData - Arbitrary subscriber-defined object. - */ - drawer.viewer.raiseEvent( 'update-level', { - havedrawn: haveDrawn, - level: level, - opacity: levelOpacity, - visibility: levelVisibility, - topleft: viewportTL, - bottomright: viewportBR, - currenttime: currentTime, - best: best - }); - } - - //OK, a new drawing so do your calculations - tileTL = drawer.source.getTileAtPoint( level, viewportTL.divide( drawer._scale )); - tileBR = drawer.source.getTileAtPoint( level, viewportBR.divide( drawer._scale )); - numberOfTiles = drawer.source.getNumTiles( level ); - - resetCoverage( drawer.coverage, level ); - - if ( !drawer.wrapHorizontal ) { - tileBR.x = Math.min( tileBR.x, numberOfTiles.x - 1 ); - } - if ( !drawer.wrapVertical ) { - tileBR.y = Math.min( tileBR.y, numberOfTiles.y - 1 ); - } - - for ( x = tileTL.x; x <= tileBR.x; x++ ) { - for ( y = tileTL.y; y <= tileBR.y; y++ ) { - - best = updateTile( - drawer, - drawLevel, - haveDrawn, - x, y, - level, - levelOpacity, - levelVisibility, - viewportCenter, - numberOfTiles, - currentTime, - best - ); - - } - } - - return best; -} - -function updateTile( drawer, drawLevel, haveDrawn, x, y, level, levelOpacity, levelVisibility, viewportCenter, numberOfTiles, currentTime, best){ - - var tile = getTile( - x, y, - level, - drawer.source, - drawer.tilesMatrix, - currentTime, - numberOfTiles, - drawer._worldWidth, - drawer._worldHeight - ), - drawTile = drawLevel; - - if( drawer.viewer ){ - /** - * - Needs documentation - - * - * @event update-tile - * @memberof OpenSeadragon.Viewer - * @type {object} - * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. - * @property {OpenSeadragon.Tile} tile - * @property {?Object} userData - Arbitrary subscriber-defined object. - */ - drawer.viewer.raiseEvent( 'update-tile', { - tile: tile - }); - } - - setCoverage( drawer.coverage, level, x, y, false ); - - if ( !tile.exists ) { - return best; - } - - if ( haveDrawn && !drawTile ) { - if ( isCovered( drawer.coverage, level, x, y ) ) { - setCoverage( drawer.coverage, level, x, y, true ); - } else { - drawTile = true; - } - } - - if ( !drawTile ) { - return best; - } - - positionTile( - tile, - drawer.source.tileOverlap, - drawer.viewport, - viewportCenter, - levelVisibility, - drawer - ); - - if ( tile.loaded ) { - var needsUpdate = blendTile( - drawer, - tile, - x, y, - level, - levelOpacity, - currentTime - ); - - if ( needsUpdate ) { - drawer.updateAgain = true; - } - } else if ( tile.loading ) { - // the tile is already in the download queue - // thanks josh1093 for finally translating this typo - } else { - best = compareTiles( best, tile ); - } - - return best; -} - -function getTile( x, y, level, tileSource, tilesMatrix, time, numTiles, worldWidth, worldHeight ) { - var xMod, - yMod, - bounds, - exists, - url, - tile; - - if ( !tilesMatrix[ level ] ) { - tilesMatrix[ level ] = {}; - } - if ( !tilesMatrix[ level ][ x ] ) { - tilesMatrix[ level ][ x ] = {}; - } - - if ( !tilesMatrix[ level ][ x ][ y ] ) { - xMod = ( numTiles.x + ( x % numTiles.x ) ) % numTiles.x; - yMod = ( numTiles.y + ( y % numTiles.y ) ) % numTiles.y; - bounds = tileSource.getTileBounds( level, xMod, yMod ); - exists = tileSource.tileExists( level, xMod, yMod ); - url = tileSource.getTileUrl( level, xMod, yMod ); - - bounds.x += worldWidth * ( x - xMod ) / numTiles.x; - bounds.y += worldHeight * ( y - yMod ) / numTiles.y; - - tilesMatrix[ level ][ x ][ y ] = new $.Tile( - level, - x, - y, - bounds, - exists, - url - ); - } - - tile = tilesMatrix[ level ][ x ][ y ]; - tile.lastTouchTime = time; - - return tile; -} - -function loadTile( drawer, tile, time ) { - if( drawer.viewport.collectionMode ){ - drawer.midUpdate = false; - onTileLoad( drawer, tile, time ); - } else { - tile.loading = true; - drawer.imageLoader.addJob({ - src: tile.url, - crossOriginPolicy: drawer.crossOriginPolicy, - callback: function( image ){ - onTileLoad( drawer, tile, time, image ); - } - }); - } -} - -function onTileLoad( drawer, tile, time, image ) { - var insertionIndex, - cutoff, - worstTile, - worstTime, - worstLevel, - worstTileIndex, - prevTile, - prevTime, - prevLevel, - i; - - tile.loading = false; - - if ( drawer.midUpdate ) { - $.console.warn( "Tile load callback in middle of drawing routine." ); - return; - } else if ( !image && !drawer.viewport.collectionMode ) { - $.console.log( "Tile %s failed to load: %s", tile, tile.url ); - if( !drawer.debugMode ){ - tile.exists = false; - return; - } - } else if ( time < drawer.lastResetTime ) { - $.console.log( "Ignoring tile %s loaded before reset: %s", tile, tile.url ); - return; - } - - tile.loaded = true; - tile.image = image; - - insertionIndex = drawer.tilesLoaded.length; - - if ( drawer.tilesLoaded.length >= drawer.maxImageCacheCount ) { - cutoff = Math.ceil( Math.log( drawer.source.getTileSize(tile.level) ) / Math.log( 2 ) ); - - worstTile = null; - worstTileIndex = -1; - - for ( i = drawer.tilesLoaded.length - 1; i >= 0; i-- ) { - prevTile = drawer.tilesLoaded[ i ]; - - if ( prevTile.level <= drawer.cutoff || prevTile.beingDrawn ) { - continue; - } else if ( !worstTile ) { - worstTile = prevTile; - worstTileIndex = i; - continue; - } - - prevTime = prevTile.lastTouchTime; - worstTime = worstTile.lastTouchTime; - prevLevel = prevTile.level; - worstLevel = worstTile.level; - - if ( prevTime < worstTime || - ( prevTime == worstTime && prevLevel > worstLevel ) ) { - worstTile = prevTile; - worstTileIndex = i; - } - } - - if ( worstTile && worstTileIndex >= 0 ) { - worstTile.unload(); - insertionIndex = worstTileIndex; - } - } - - drawer.tilesLoaded[ insertionIndex ] = tile; - drawer.updateAgain = true; -} - - -function positionTile( tile, overlap, viewport, viewportCenter, levelVisibility, drawer ){ - var boundsTL = tile.bounds.getTopLeft(); - - boundsTL.x *= drawer._scale; - boundsTL.y *= drawer._scale; - boundsTL.x += drawer._worldX; - boundsTL.y += drawer._worldY; - - var boundsSize = tile.bounds.getSize(); - - boundsSize.x *= drawer._scale; - boundsSize.y *= drawer._scale; - - var positionC = viewport.pixelFromPoint( boundsTL, true ), - positionT = viewport.pixelFromPoint( boundsTL, false ), - sizeC = viewport.deltaPixelsFromPoints( boundsSize, true ), - sizeT = viewport.deltaPixelsFromPoints( boundsSize, false ), - tileCenter = positionT.plus( sizeT.divide( 2 ) ), - tileDistance = viewportCenter.distanceTo( tileCenter ); - - if ( !overlap ) { - sizeC = sizeC.plus( new $.Point( 1, 1 ) ); - } - - tile.position = positionC; - tile.size = sizeC; - tile.distance = tileDistance; - tile.visibility = levelVisibility; -} - - -function blendTile( drawer, tile, x, y, level, levelOpacity, currentTime ){ - var blendTimeMillis = 1000 * drawer.blendTime, - deltaTime, - opacity; - - if ( !tile.blendStart ) { - tile.blendStart = currentTime; - } - - deltaTime = currentTime - tile.blendStart; - opacity = blendTimeMillis ? Math.min( 1, deltaTime / ( blendTimeMillis ) ) : 1; - - if ( drawer.alwaysBlend ) { - opacity *= levelOpacity; - } - - tile.opacity = opacity; - - drawer.lastDrawn.push( tile ); - - if ( opacity == 1 ) { - setCoverage( drawer.coverage, level, x, y, true ); - } else if ( deltaTime < blendTimeMillis ) { - return true; - } - - return false; -} - - -function clearTiles( drawer ) { - drawer.tilesMatrix = {}; - drawer.tilesLoaded = []; -} - -/** - * @private - * @inner - * Returns true if the given tile provides coverage to lower-level tiles of - * lower resolution representing the same content. If neither x nor y is - * given, returns true if the entire visible level provides coverage. - * - * Note that out-of-bounds tiles provide coverage in this sense, since - * there's no content that they would need to cover. Tiles at non-existent - * levels that are within the image bounds, however, do not. - */ -function providesCoverage( coverage, level, x, y ) { - var rows, - cols, - i, j; - - if ( !coverage[ level ] ) { - return false; - } - - if ( x === undefined || y === undefined ) { - rows = coverage[ level ]; - for ( i in rows ) { - if ( rows.hasOwnProperty( i ) ) { - cols = rows[ i ]; - for ( j in cols ) { - if ( cols.hasOwnProperty( j ) && !cols[ j ] ) { - return false; - } - } - } - } - - return true; - } - - return ( - coverage[ level ][ x] === undefined || - coverage[ level ][ x ][ y ] === undefined || - coverage[ level ][ x ][ y ] === true - ); -} - -/** - * @private - * @inner - * Returns true if the given tile is completely covered by higher-level - * tiles of higher resolution representing the same content. If neither x - * nor y is given, returns true if the entire visible level is covered. - */ -function isCovered( coverage, level, x, y ) { - if ( x === undefined || y === undefined ) { - return providesCoverage( coverage, level + 1 ); - } else { - return ( - providesCoverage( coverage, level + 1, 2 * x, 2 * y ) && - providesCoverage( coverage, level + 1, 2 * x, 2 * y + 1 ) && - providesCoverage( coverage, level + 1, 2 * x + 1, 2 * y ) && - providesCoverage( coverage, level + 1, 2 * x + 1, 2 * y + 1 ) - ); - } -} - -/** - * @private - * @inner - * Sets whether the given tile provides coverage or not. - */ -function setCoverage( coverage, level, x, y, covers ) { - if ( !coverage[ level ] ) { - $.console.warn( - "Setting coverage for a tile before its level's coverage has been reset: %s", - level - ); - return; - } - - if ( !coverage[ level ][ x ] ) { - coverage[ level ][ x ] = {}; - } - - coverage[ level ][ x ][ y ] = covers; -} - -/** - * @private - * @inner - * Resets coverage information for the given level. This should be called - * after every draw routine. Note that at the beginning of the next draw - * routine, coverage for every visible tile should be explicitly set. - */ -function resetCoverage( coverage, level ) { - coverage[ level ] = {}; -} - -/** - * @private - * @inner - * Determines whether the 'last best' tile for the area is better than the - * tile in question. - */ -function compareTiles( previousBest, tile ) { - if ( !previousBest ) { - return tile; - } - - if ( tile.visibility > previousBest.visibility ) { - return tile; - } else if ( tile.visibility == previousBest.visibility ) { - if ( tile.distance < previousBest.distance ) { - return tile; - } - } - - return previousBest; -} - -function drawTiles( drawer, lastDrawn ){ - var i, - tile, - tileKey, - viewer, - viewport, - position, - tileSource, - collectionTileSource; - - // We need a callback to give image manipulation a chance to happen - var drawingHandler = function(args) { - if (drawer.viewer) { - /** - * This event is fired just before the tile is drawn giving the application a chance to alter the image. - * - * NOTE: This event is only fired when the drawer is using a . - * - * @event tile-drawing - * @memberof OpenSeadragon.Viewer - * @type {object} - * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. - * @property {OpenSeadragon.Tile} tile - * @property {?Object} userData - 'context', 'tile' and 'rendered'. - */ - drawer.viewer.raiseEvent('tile-drawing', args); - } - }; - - for ( i = lastDrawn.length - 1; i >= 0; i-- ) { - tile = lastDrawn[ i ]; - - //We dont actually 'draw' a collection tile, rather its used to house - //an overlay which does the drawing in its own viewport - if( drawer.viewport.collectionMode ){ - - tileKey = tile.x + '/' + tile.y; - viewport = drawer.viewport; - collectionTileSource = viewport.collectionTileSource; - - if( !drawer.collectionOverlays[ tileKey ] ){ - - position = collectionTileSource.layout == 'horizontal' ? - tile.y + ( tile.x * collectionTileSource.rows ) : - tile.x + ( tile.y * collectionTileSource.rows ); - - if (position < collectionTileSource.tileSources.length) { - tileSource = collectionTileSource.tileSources[ position ]; - } else { - tileSource = null; - } - - //$.console.log("Rendering collection tile %s | %s | %s", tile.y, tile.y, position); - if( tileSource ){ - drawer.collectionOverlays[ tileKey ] = viewer = new $.Viewer({ - hash: viewport.viewer.hash + "-" + tileKey, - element: $.makeNeutralElement( "div" ), - mouseNavEnabled: false, - showNavigator: false, - showSequenceControl: false, - showNavigationControl: false, - tileSources: [ - tileSource - ] - }); - - //TODO: IE seems to barf on this, not sure if its just the border - // but we probably need to clear this up with a better - // test of support for various css features - if( SUBPIXEL_RENDERING ){ - viewer.element.style.border = '1px solid rgba(255,255,255,0.38)'; - viewer.element.style['-webkit-box-reflect'] = - 'below 0px -webkit-gradient('+ - 'linear,left '+ - 'top,left '+ - 'bottom,from(transparent),color-stop(62%,transparent),to(rgba(255,255,255,0.62))'+ - ')'; - } - - drawer.viewer.addOverlay( - viewer.element, - tile.bounds - ); - } - - }else{ - viewer = drawer.collectionOverlays[ tileKey ]; - if( viewer.viewport ){ - viewer.viewport.resize( tile.size, true ); - viewer.viewport.goHome( true ); - } - } - - } else { - - if ( drawer.useCanvas ) { - // TODO do this in a more performant way - // specifically, don't save,rotate,restore every time we draw a tile - if( drawer.viewport.degrees !== 0 ) { - offsetForRotation( tile, drawer.canvas, drawer.context, drawer.viewport.degrees ); - tile.drawCanvas( drawer.context, drawingHandler ); - restoreRotationChanges( tile, drawer.canvas, drawer.context ); - } else { - tile.drawCanvas( drawer.context, drawingHandler ); - } - } else { - tile.drawHTML( drawer.canvas ); - } - - - tile.beingDrawn = true; - } - - if( drawer.debugMode ){ - try{ - drawDebugInfo( drawer, tile, lastDrawn.length, i ); - }catch(e){ - $.console.error(e); - } - } - - if( drawer.viewer ){ - /** - * - Needs documentation - - * - * @event tile-drawn - * @memberof OpenSeadragon.Viewer - * @type {object} - * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. - * @property {OpenSeadragon.Tile} tile - * @property {?Object} userData - Arbitrary subscriber-defined object. - */ - drawer.viewer.raiseEvent( 'tile-drawn', { - tile: tile - }); - } - } -} - -function offsetForRotation( tile, canvas, context, degrees ){ - var cx = canvas.width / 2, - cy = canvas.height / 2, - px = tile.position.x - cx, - py = tile.position.y - cy; - - context.save(); - - context.translate(cx, cy); - context.rotate( Math.PI / 180 * degrees); - tile.position.x = px; - tile.position.y = py; -} - -function restoreRotationChanges( tile, canvas, context ){ - var cx = canvas.width / 2, - cy = canvas.height / 2, - px = tile.position.x + cx, - py = tile.position.y + cy; - - tile.position.x = px; - tile.position.y = py; - - context.restore(); -} - - -function drawDebugInfo( drawer, tile, count, i ){ - - if ( drawer.useCanvas ) { - drawer.context.save(); - drawer.context.lineWidth = 2; - drawer.context.font = 'small-caps bold 13px ariel'; - drawer.context.strokeStyle = drawer.debugGridColor; - drawer.context.fillStyle = drawer.debugGridColor; - drawer.context.strokeRect( - tile.position.x, - tile.position.y, - tile.size.x, - tile.size.y - ); - if( tile.x === 0 && tile.y === 0 ){ - drawer.context.fillText( - "Zoom: " + drawer.viewport.getZoom(), - tile.position.x, - tile.position.y - 30 - ); - drawer.context.fillText( - "Pan: " + drawer.viewport.getBounds().toString(), - tile.position.x, - tile.position.y - 20 - ); - } - drawer.context.fillText( - "Level: " + tile.level, - tile.position.x + 10, - tile.position.y + 20 - ); - drawer.context.fillText( - "Column: " + tile.x, - tile.position.x + 10, - tile.position.y + 30 - ); - drawer.context.fillText( - "Row: " + tile.y, - tile.position.x + 10, - tile.position.y + 40 - ); - drawer.context.fillText( - "Order: " + i + " of " + count, - tile.position.x + 10, - tile.position.y + 50 - ); - drawer.context.fillText( - "Size: " + tile.size.toString(), - tile.position.x + 10, - tile.position.y + 60 - ); - drawer.context.fillText( - "Position: " + tile.position.toString(), - tile.position.x + 10, - tile.position.y + 70 - ); - drawer.context.restore(); - } -} - - }( OpenSeadragon )); diff --git a/src/openseadragon.js b/src/openseadragon.js index 74c6c66e..4819f55f 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -2224,7 +2224,8 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){ debug: nullfunction, info: nullfunction, warn: nullfunction, - error: nullfunction + error: nullfunction, + assert: nullfunction }; diff --git a/src/tilecache.js b/src/tilecache.js new file mode 100644 index 00000000..2e8faadb --- /dev/null +++ b/src/tilecache.js @@ -0,0 +1,113 @@ +/* + * OpenSeadragon - TileCache + * + * Copyright (C) 2009 CodePlex Foundation + * Copyright (C) 2010-2013 OpenSeadragon contributors + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of CodePlex Foundation nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +(function( $ ){ + +/** + * @class TileCache + * @classdesc + */ +$.TileCache = function( options ) { + options = options || {}; + + this._tilesLoaded = []; + this._maxImageCacheCount = options.maxImageCacheCount || $.DEFAULT_SETTINGS.maxImageCacheCount; +}; + +$.TileCache.prototype = /** @lends OpenSeadragon.TileCache.prototype */{ + /** + * Returns the total number of tiles that have been loaded by this TileCache. + * @method + * @returns {Number} - The total number of tiles that have been loaded by + * this TileCache. + */ + numTilesLoaded: function() { + return this._tilesLoaded.length; + }, + + cacheTile: function( tile, cutoff ) { + cutoff = cutoff || 0; + var insertionIndex = this._tilesLoaded.length; + + if ( this._tilesLoaded.length >= this._maxImageCacheCount ) { + var worstTile = null; + var worstTileIndex = -1; + var prevTile, worstTime, worstLevel, prevTime, prevLevel; + + for ( var i = this._tilesLoaded.length - 1; i >= 0; i-- ) { + prevTile = this._tilesLoaded[ i ]; + + if ( prevTile.level <= cutoff || prevTile.beingDrawn ) { + continue; + } else if ( !worstTile ) { + worstTile = prevTile; + worstTileIndex = i; + continue; + } + + prevTime = prevTile.lastTouchTime; + worstTime = worstTile.lastTouchTime; + prevLevel = prevTile.level; + worstLevel = worstTile.level; + + if ( prevTime < worstTime || + ( prevTime == worstTime && prevLevel > worstLevel ) ) { + worstTile = prevTile; + worstTileIndex = i; + } + } + + if ( worstTile && worstTileIndex >= 0 ) { + worstTile.unload(); + insertionIndex = worstTileIndex; + } + } + + this._tilesLoaded[ insertionIndex ] = tile; + }, + + /** + * Clears all tiles. + * @method + */ + reset: function() { + for ( var i = 0; i < this._tilesLoaded.length; ++i ) { + this._tilesLoaded[i].unload(); + } + + this._tilesLoaded = []; + } +}; + +}( OpenSeadragon )); diff --git a/src/tiledimage.js b/src/tiledimage.js index a4b7f2ec..750f7d2c 100644 --- a/src/tiledimage.js +++ b/src/tiledimage.js @@ -1,5 +1,5 @@ /* - * OpenSeadragon - Drawer + * OpenSeadragon - TiledImage * * Copyright (C) 2009 CodePlex Foundation * Copyright (C) 2010-2013 OpenSeadragon contributors @@ -34,43 +34,22 @@ (function( $ ){ -var DEVICE_SCREEN = $.getWindowSize(), - BROWSER = $.Browser.vendor, - BROWSER_VERSION = $.Browser.version, - - SUBPIXEL_RENDERING = ( - ( BROWSER == $.BROWSERS.FIREFOX ) || - ( BROWSER == $.BROWSERS.OPERA ) || - ( BROWSER == $.BROWSERS.SAFARI && BROWSER_VERSION >= 4 ) || - ( BROWSER == $.BROWSERS.CHROME && BROWSER_VERSION >= 2 ) || - ( BROWSER == $.BROWSERS.IE && BROWSER_VERSION >= 9 ) - ); - - /** - * @class Drawer + * @class TiledImage * @classdesc Handles rendering of tiles for an {@link OpenSeadragon.Viewer}. - * A new instance is created for each TileSource opened (see {@link OpenSeadragon.Viewer#drawer}). + * A new instance is created for each TileSource opened. * * @memberof OpenSeadragon - * @param {OpenSeadragon.TileSource} source - Reference to Viewer tile source. - * @param {OpenSeadragon.Viewport} viewport - Reference to Viewer viewport. - * @param {Element} element - Parent element. */ -$.Drawer = function( options ) { +$.TiledImage = function( options ) { + $.console.assert( options.tileCache, "[TiledImage] options.tileCache is required" ); + $.console.assert( options.drawer, "[TiledImage] options.drawer is required" ); - //backward compatibility for positional args while prefering more - //idiomatic javascript options object as the only argument - var args = arguments, - i; + this._tileCache = options.tileCache; + delete options.tileCache; - if( !$.isPlainObject( options ) ){ - options = { - source: args[ 0 ], // Reference to Viewer tile source. - viewport: args[ 1 ], // Reference to Viewer viewport. - element: args[ 2 ] // Parent element. - }; - } + this._drawer = options.drawer; + delete options.drawer; this._worldX = options.x || 0; delete options.x; @@ -85,7 +64,7 @@ $.Drawer = function( options ) { delete options.width; if ( options.height ) { - $.console.error( "specifying both width and height to a drawer is not supported" ); + $.console.error( "specifying both width and height to a tiledImage is not supported" ); delete options.height; } } else if ( options.height ) { @@ -104,12 +83,11 @@ $.Drawer = function( options ) { viewer: null, imageLoader: new $.ImageLoader(), tilesMatrix: {}, // A '3d' dictionary [level][x][y] --> Tile. - tilesLoaded: [], // An unordered list of Tiles with loaded images. coverage: {}, // A '3d' dictionary [level][x][y] --> Boolean. lastDrawn: [], // An unordered list of Tiles drawn last frame. - lastResetTime: 0, // Last time for which the drawer was reset. - midUpdate: false, // Is the drawer currently updating the viewport? - updateAgain: true, // Does the drawer need to update the viewport again? + lastResetTime: 0, // Last time for which the tiledImage was reset. + midUpdate: false, // Is the tiledImage currently updating the viewport? + updateAgain: true, // Does the tiledImage need to update the viewport again? //internal state / configurable settings @@ -130,219 +108,73 @@ $.Drawer = function( options ) { crossOriginPolicy: $.DEFAULT_SETTINGS.crossOriginPolicy }, options ); - - this.useCanvas = $.supportsCanvas && ( this.viewer ? this.viewer.useCanvas : true ); - /** - * The parent element of this Drawer instance, passed in when the Drawer was created. - * The parent of {@link OpenSeadragon.Drawer#canvas}. - * @member {Element} container - * @memberof OpenSeadragon.Drawer# - */ - this.container = $.getElement( this.element ); - /** - * A <canvas> element if the browser supports them, otherwise a <div> element. - * Child element of {@link OpenSeadragon.Drawer#container}. - * @member {Element} canvas - * @memberof OpenSeadragon.Drawer# - */ - this.canvas = $.makeNeutralElement( this.useCanvas ? "canvas" : "div" ); - /** - * 2d drawing context for {@link OpenSeadragon.Drawer#canvas} if it's a <canvas> element, otherwise null. - * @member {Object} context - * @memberof OpenSeadragon.Drawer# - */ - this.context = this.useCanvas ? this.canvas.getContext( "2d" ) : null; - - /** - * @member {Element} element - * @memberof OpenSeadragon.Drawer# - * @deprecated Alias for {@link OpenSeadragon.Drawer#container}. - */ - this.element = this.container; - - // We force our container to ltr because our drawing math doesn't work in rtl. - // This issue only affects our canvas renderer, but we do it always for consistency. - // Note that this means overlays you want to be rtl need to be explicitly set to rtl. - this.container.dir = 'ltr'; - - this.canvas.style.width = "100%"; - this.canvas.style.height = "100%"; - this.canvas.style.position = "absolute"; - $.setElementOpacity( this.canvas, this.opacity, true ); - - // explicit left-align - this.container.style.textAlign = "left"; - this.container.appendChild( this.canvas ); - - //this.profiler = new $.Profiler(); }; -$.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{ - +$.TiledImage.prototype = /** @lends OpenSeadragon.TiledImage.prototype */{ /** - * Adds an html element as an overlay to the current viewport. Useful for - * highlighting words or areas of interest on an image or other zoomable - * interface. - * @method - * @param {Element|String|Object} element - A reference to an element or an id for - * the element which will overlayed. Or an Object specifying the configuration for the overlay - * @param {OpenSeadragon.Point|OpenSeadragon.Rect} location - The point or - * rectangle which will be overlayed. - * @param {OpenSeadragon.OverlayPlacement} placement - The position of the - * viewport which the location coordinates will be treated as relative - * to. - * @param {function} onDraw - If supplied the callback is called when the overlay - * needs to be drawn. It it the responsibility of the callback to do any drawing/positioning. - * It is passed position, size and element. - * @fires OpenSeadragon.Viewer.event:add-overlay - * @deprecated - use {@link OpenSeadragon.Viewer#addOverlay} instead. - */ - addOverlay: function( element, location, placement, onDraw ) { - $.console.error("drawer.addOverlay is deprecated. Use viewer.addOverlay instead."); - this.viewer.addOverlay( element, location, placement, onDraw ); - return this; - }, - - /** - * Updates the overlay represented by the reference to the element or - * element id moving it to the new location, relative to the new placement. - * @method - * @param {OpenSeadragon.Point|OpenSeadragon.Rect} location - The point or - * rectangle which will be overlayed. - * @param {OpenSeadragon.OverlayPlacement} placement - The position of the - * viewport which the location coordinates will be treated as relative - * to. - * @return {OpenSeadragon.Drawer} Chainable. - * @fires OpenSeadragon.Viewer.event:update-overlay - * @deprecated - use {@link OpenSeadragon.Viewer#updateOverlay} instead. - */ - updateOverlay: function( element, location, placement ) { - $.console.error("drawer.updateOverlay is deprecated. Use viewer.updateOverlay instead."); - this.viewer.updateOverlay( element, location, placement ); - return this; - }, - - /** - * Removes and overlay identified by the reference element or element id - * and schedules and update. - * @method - * @param {Element|String} element - A reference to the element or an - * element id which represent the ovelay content to be removed. - * @return {OpenSeadragon.Drawer} Chainable. - * @fires OpenSeadragon.Viewer.event:remove-overlay - * @deprecated - use {@link OpenSeadragon.Viewer#removeOverlay} instead. - */ - removeOverlay: function( element ) { - $.console.error("drawer.removeOverlay is deprecated. Use viewer.removeOverlay instead."); - this.viewer.removeOverlay( element ); - return this; - }, - - /** - * Removes all currently configured Overlays from this Drawer and schedules - * and update. - * @method - * @return {OpenSeadragon.Drawer} Chainable. - * @fires OpenSeadragon.Viewer.event:clear-overlay - * @deprecated - use {@link OpenSeadragon.Viewer#clearOverlays} instead. - */ - clearOverlays: function() { - $.console.error("drawer.clearOverlays is deprecated. Use viewer.clearOverlays instead."); - this.viewer.clearOverlays(); - return this; - }, - - /** - * Set the opacity of the drawer. + * Set the opacity of the TiledImage. * @method * @param {Number} opacity - * @return {OpenSeadragon.Drawer} Chainable. + * @return {OpenSeadragon.TiledImage} Chainable. */ setOpacity: function( opacity ) { this.opacity = opacity; - $.setElementOpacity( this.canvas, this.opacity, true ); + // TODO: trigger update return this; }, /** - * Get the opacity of the drawer. + * Get the opacity of the TiledImage. * @method * @returns {Number} */ getOpacity: function() { return this.opacity; }, + /** - * Returns whether the Drawer is scheduled for an update at the + * Returns whether the TiledImage is scheduled for an update at the * soonest possible opportunity. * @method - * @returns {Boolean} - Whether the Drawer is scheduled for an update at the + * @returns {Boolean} - Whether the TiledImage is scheduled for an update at the * soonest possible opportunity. */ needsUpdate: function() { return this.updateAgain; }, - /** - * Returns the total number of tiles that have been loaded by this Drawer. - * @method - * @returns {Number} - The total number of tiles that have been loaded by - * this Drawer. - */ - numTilesLoaded: function() { - return this.tilesLoaded.length; - }, - /** * Clears all tiles and triggers an update on the next call to - * Drawer.prototype.update(). + * TiledImage.prototype.update(). * @method - * @return {OpenSeadragon.Drawer} Chainable. + * @return {OpenSeadragon.TiledImage} Chainable. */ reset: function() { - clearTiles( this ); + this._tileCache.clearTilesFor(this); this.lastResetTime = $.now(); this.updateAgain = true; return this; }, /** - * Forces the Drawer to update. + * Forces the TiledImage to update. * @method - * @return {OpenSeadragon.Drawer} Chainable. + * @return {OpenSeadragon.TiledImage} Chainable. */ update: function() { - //this.profiler.beginUpdate(); this.midUpdate = true; updateViewport( this ); this.midUpdate = false; - //this.profiler.endUpdate(); return this; }, /** - * Returns whether rotation is supported or not. - * @method - * @return {Boolean} True if rotation is supported. - */ - canRotate: function() { - return this.useCanvas; - }, - - /** - * Destroy the drawer (unload current loaded tiles) + * Destroy the TiledImage (unload current loaded tiles) * @method * @return null */ destroy: function() { - //unload current loaded tiles (=empty TILE_CACHE) - for ( var i = 0; i < this.tilesLoaded.length; ++i ) { - this.tilesLoaded[i].unload(); - } - - //force unloading of current canvas (1x1 will be gc later, trick not necessarily needed) - this.canvas.width = 1; - this.canvas.height = 1; + this.reset(); } }; @@ -353,11 +185,11 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{ * how each piece of this routine contributes to the drawing process. That's * why there are so many TODO's inside this function. */ -function updateViewport( drawer ) { +function updateViewport( tiledImage ) { - drawer.updateAgain = false; + tiledImage.updateAgain = false; - if( drawer.viewer ){ + if( tiledImage.viewer ){ /** * - Needs documentation - * @@ -367,7 +199,7 @@ function updateViewport( drawer ) { * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. * @property {?Object} userData - Arbitrary subscriber-defined object. */ - drawer.viewer.raiseEvent( 'update-viewport', {} ); + tiledImage.viewer.raiseEvent( 'update-viewport', {} ); } var tile, @@ -375,29 +207,29 @@ function updateViewport( drawer ) { best = null, haveDrawn = false, currentTime = $.now(), - viewportSize = drawer.viewport.getContainerSize(), - viewportBounds = drawer.viewport.getBounds( true ), + viewportSize = tiledImage.viewport.getContainerSize(), + viewportBounds = tiledImage.viewport.getBounds( true ), viewportTL = viewportBounds.getTopLeft(), viewportBR = viewportBounds.getBottomRight(), - zeroRatioC = drawer.viewport.deltaPixelsFromPoints( - drawer.source.getPixelRatio( 0 ), + zeroRatioC = tiledImage.viewport.deltaPixelsFromPoints( + tiledImage.source.getPixelRatio( 0 ), true - ).x * drawer._scale, + ).x * tiledImage._scale, lowestLevel = Math.max( - drawer.source.minLevel, + tiledImage.source.minLevel, Math.floor( - Math.log( drawer.minZoomImageRatio ) / + Math.log( tiledImage.minZoomImageRatio ) / Math.log( 2 ) ) ), highestLevel = Math.min( - Math.abs(drawer.source.maxLevel), + Math.abs(tiledImage.source.maxLevel), Math.abs(Math.floor( - Math.log( zeroRatioC / drawer.minPixelRatio ) / + Math.log( zeroRatioC / tiledImage.minPixelRatio ) / Math.log( 2 ) )) ), - degrees = drawer.viewport.degrees, + degrees = tiledImage.viewport.degrees, renderPixelRatioC, renderPixelRatioT, zeroRatioT, @@ -405,28 +237,17 @@ function updateViewport( drawer ) { levelOpacity, levelVisibility; - viewportTL.x -= drawer._worldX; - viewportTL.y -= drawer._worldY; - viewportBR.x -= drawer._worldX; - viewportBR.y -= drawer._worldY; + viewportTL.x -= tiledImage._worldX; + viewportTL.y -= tiledImage._worldY; + viewportBR.x -= tiledImage._worldX; + viewportBR.y -= tiledImage._worldY; // Reset tile's internal drawn state - while ( drawer.lastDrawn.length > 0 ) { - tile = drawer.lastDrawn.pop(); + while ( tiledImage.lastDrawn.length > 0 ) { + tile = tiledImage.lastDrawn.pop(); tile.beingDrawn = false; } - // Clear canvas - drawer.canvas.innerHTML = ""; - if ( drawer.useCanvas ) { - if( drawer.canvas.width != viewportSize.x || - drawer.canvas.height != viewportSize.y ){ - drawer.canvas.width = viewportSize.x; - drawer.canvas.height = viewportSize.y; - } - drawer.context.clearRect( 0, 0, viewportSize.x, viewportSize.y ); - } - //Change bounds for rotation if (degrees === 90 || degrees === 270) { var rotatedBounds = viewportBounds.rotate( degrees ); @@ -435,23 +256,23 @@ function updateViewport( drawer ) { } //Don't draw if completely outside of the viewport - if ( !drawer.wrapHorizontal && - ( viewportBR.x < 0 || viewportTL.x > drawer._worldWidth ) ) { + if ( !tiledImage.wrapHorizontal && + ( viewportBR.x < 0 || viewportTL.x > tiledImage._worldWidth ) ) { return; } else if - ( !drawer.wrapVertical && - ( viewportBR.y < 0 || viewportTL.y > drawer._worldHeight ) ) { + ( !tiledImage.wrapVertical && + ( viewportBR.y < 0 || viewportTL.y > tiledImage._worldHeight ) ) { return; } // Calculate viewport rect / bounds - if ( !drawer.wrapHorizontal ) { + if ( !tiledImage.wrapHorizontal ) { viewportTL.x = Math.max( viewportTL.x, 0 ); - viewportBR.x = Math.min( viewportBR.x, drawer._worldWidth ); + viewportBR.x = Math.min( viewportBR.x, tiledImage._worldWidth ); } - if ( !drawer.wrapVertical ) { + if ( !tiledImage.wrapVertical ) { viewportTL.y = Math.max( viewportTL.y, 0 ); - viewportBR.y = Math.min( viewportBR.y, drawer._worldHeight ); + viewportBR.y = Math.min( viewportBR.y, tiledImage._worldHeight ); } // Calculations for the interval of levels to draw @@ -465,12 +286,12 @@ function updateViewport( drawer ) { drawLevel = false; //Avoid calculations for draw if we have already drawn this - renderPixelRatioC = drawer.viewport.deltaPixelsFromPoints( - drawer.source.getPixelRatio( level ), + renderPixelRatioC = tiledImage.viewport.deltaPixelsFromPoints( + tiledImage.source.getPixelRatio( level ), true - ).x * drawer._scale; + ).x * tiledImage._scale; - if ( ( !haveDrawn && renderPixelRatioC >= drawer.minPixelRatio ) || + if ( ( !haveDrawn && renderPixelRatioC >= tiledImage.minPixelRatio ) || ( level == lowestLevel ) ) { drawLevel = true; haveDrawn = true; @@ -479,22 +300,22 @@ function updateViewport( drawer ) { } //Perform calculations for draw if we haven't drawn this - renderPixelRatioT = drawer.viewport.deltaPixelsFromPoints( - drawer.source.getPixelRatio( level ), + renderPixelRatioT = tiledImage.viewport.deltaPixelsFromPoints( + tiledImage.source.getPixelRatio( level ), false - ).x * drawer._scale; + ).x * tiledImage._scale; - zeroRatioT = drawer.viewport.deltaPixelsFromPoints( - drawer.source.getPixelRatio( + zeroRatioT = tiledImage.viewport.deltaPixelsFromPoints( + tiledImage.source.getPixelRatio( Math.max( - drawer.source.getClosestLevel( drawer.viewport.containerSize ) - 1, + tiledImage.source.getClosestLevel( tiledImage.viewport.containerSize ) - 1, 0 ) ), false - ).x * drawer._scale; + ).x * tiledImage._scale; - optimalRatio = drawer.immediateRender ? + optimalRatio = tiledImage.immediateRender ? 1 : zeroRatioT; @@ -506,7 +327,7 @@ function updateViewport( drawer ) { // Update the level and keep track of 'best' tile to load best = updateLevel( - drawer, + tiledImage, haveDrawn, drawLevel, level, @@ -520,34 +341,34 @@ function updateViewport( drawer ) { // Stop the loop if lower-res tiles would all be covered by // already drawn tiles - if ( providesCoverage( drawer.coverage, level ) ) { + if ( providesCoverage( tiledImage.coverage, level ) ) { break; } } // Perform the actual drawing - drawTiles( drawer, drawer.lastDrawn ); + drawTiles( tiledImage, tiledImage.lastDrawn ); // Load the new 'best' tile if ( best ) { - loadTile( drawer, best, currentTime ); + loadTile( tiledImage, best, currentTime ); // because we haven't finished drawing, so - drawer.updateAgain = true; + tiledImage.updateAgain = true; } } -function updateLevel( drawer, haveDrawn, drawLevel, level, levelOpacity, levelVisibility, viewportTL, viewportBR, currentTime, best ){ +function updateLevel( tiledImage, haveDrawn, drawLevel, level, levelOpacity, levelVisibility, viewportTL, viewportBR, currentTime, best ){ var x, y, tileTL, tileBR, numberOfTiles, - viewportCenter = drawer.viewport.pixelFromPoint( drawer.viewport.getCenter() ); + viewportCenter = tiledImage.viewport.pixelFromPoint( tiledImage.viewport.getCenter() ); - if( drawer.viewer ){ + if( tiledImage.viewer ){ /** * - Needs documentation - * @@ -565,7 +386,7 @@ function updateLevel( drawer, haveDrawn, drawLevel, level, levelOpacity, levelVi * @property {Object} best * @property {?Object} userData - Arbitrary subscriber-defined object. */ - drawer.viewer.raiseEvent( 'update-level', { + tiledImage.viewer.raiseEvent( 'update-level', { havedrawn: haveDrawn, level: level, opacity: levelOpacity, @@ -578,16 +399,16 @@ function updateLevel( drawer, haveDrawn, drawLevel, level, levelOpacity, levelVi } //OK, a new drawing so do your calculations - tileTL = drawer.source.getTileAtPoint( level, viewportTL.divide( drawer._scale )); - tileBR = drawer.source.getTileAtPoint( level, viewportBR.divide( drawer._scale )); - numberOfTiles = drawer.source.getNumTiles( level ); + tileTL = tiledImage.source.getTileAtPoint( level, viewportTL.divide( tiledImage._scale )); + tileBR = tiledImage.source.getTileAtPoint( level, viewportBR.divide( tiledImage._scale )); + numberOfTiles = tiledImage.source.getNumTiles( level ); - resetCoverage( drawer.coverage, level ); + resetCoverage( tiledImage.coverage, level ); - if ( !drawer.wrapHorizontal ) { + if ( !tiledImage.wrapHorizontal ) { tileBR.x = Math.min( tileBR.x, numberOfTiles.x - 1 ); } - if ( !drawer.wrapVertical ) { + if ( !tiledImage.wrapVertical ) { tileBR.y = Math.min( tileBR.y, numberOfTiles.y - 1 ); } @@ -595,7 +416,7 @@ function updateLevel( drawer, haveDrawn, drawLevel, level, levelOpacity, levelVi for ( y = tileTL.y; y <= tileBR.y; y++ ) { best = updateTile( - drawer, + tiledImage, drawLevel, haveDrawn, x, y, @@ -614,21 +435,21 @@ function updateLevel( drawer, haveDrawn, drawLevel, level, levelOpacity, levelVi return best; } -function updateTile( drawer, drawLevel, haveDrawn, x, y, level, levelOpacity, levelVisibility, viewportCenter, numberOfTiles, currentTime, best){ +function updateTile( tiledImage, drawLevel, haveDrawn, x, y, level, levelOpacity, levelVisibility, viewportCenter, numberOfTiles, currentTime, best){ var tile = getTile( x, y, level, - drawer.source, - drawer.tilesMatrix, + tiledImage.source, + tiledImage.tilesMatrix, currentTime, numberOfTiles, - drawer._worldWidth, - drawer._worldHeight + tiledImage._worldWidth, + tiledImage._worldHeight ), drawTile = drawLevel; - if( drawer.viewer ){ + if( tiledImage.viewer ){ /** * - Needs documentation - * @@ -639,20 +460,20 @@ function updateTile( drawer, drawLevel, haveDrawn, x, y, level, levelOpacity, le * @property {OpenSeadragon.Tile} tile * @property {?Object} userData - Arbitrary subscriber-defined object. */ - drawer.viewer.raiseEvent( 'update-tile', { + tiledImage.viewer.raiseEvent( 'update-tile', { tile: tile }); } - setCoverage( drawer.coverage, level, x, y, false ); + setCoverage( tiledImage.coverage, level, x, y, false ); if ( !tile.exists ) { return best; } if ( haveDrawn && !drawTile ) { - if ( isCovered( drawer.coverage, level, x, y ) ) { - setCoverage( drawer.coverage, level, x, y, true ); + if ( isCovered( tiledImage.coverage, level, x, y ) ) { + setCoverage( tiledImage.coverage, level, x, y, true ); } else { drawTile = true; } @@ -664,16 +485,16 @@ function updateTile( drawer, drawLevel, haveDrawn, x, y, level, levelOpacity, le positionTile( tile, - drawer.source.tileOverlap, - drawer.viewport, + tiledImage.source.tileOverlap, + tiledImage.viewport, viewportCenter, levelVisibility, - drawer + tiledImage ); if ( tile.loaded ) { var needsUpdate = blendTile( - drawer, + tiledImage, tile, x, y, level, @@ -682,7 +503,7 @@ function updateTile( drawer, drawLevel, haveDrawn, x, y, level, levelOpacity, le ); if ( needsUpdate ) { - drawer.updateAgain = true; + tiledImage.updateAgain = true; } } else if ( tile.loading ) { // the tile is already in the download queue @@ -735,46 +556,30 @@ function getTile( x, y, level, tileSource, tilesMatrix, time, numTiles, worldWid return tile; } -function loadTile( drawer, tile, time ) { - if( drawer.viewport.collectionMode ){ - drawer.midUpdate = false; - onTileLoad( drawer, tile, time ); - } else { - tile.loading = true; - drawer.imageLoader.addJob({ - src: tile.url, - crossOriginPolicy: drawer.crossOriginPolicy, - callback: function( image ){ - onTileLoad( drawer, tile, time, image ); - } - }); - } +function loadTile( tiledImage, tile, time ) { + tile.loading = true; + tiledImage.imageLoader.addJob({ + src: tile.url, + crossOriginPolicy: tiledImage.crossOriginPolicy, + callback: function( image ){ + onTileLoad( tiledImage, tile, time, image ); + } + }); } -function onTileLoad( drawer, tile, time, image ) { - var insertionIndex, - cutoff, - worstTile, - worstTime, - worstLevel, - worstTileIndex, - prevTile, - prevTime, - prevLevel, - i; - +function onTileLoad( tiledImage, tile, time, image ) { tile.loading = false; - if ( drawer.midUpdate ) { + if ( tiledImage.midUpdate ) { $.console.warn( "Tile load callback in middle of drawing routine." ); return; - } else if ( !image && !drawer.viewport.collectionMode ) { + } else if ( !image ) { $.console.log( "Tile %s failed to load: %s", tile, tile.url ); - if( !drawer.debugMode ){ + if( !tiledImage.debugMode ){ tile.exists = false; return; } - } else if ( time < drawer.lastResetTime ) { + } else if ( time < tiledImage.lastResetTime ) { $.console.log( "Ignoring tile %s loaded before reset: %s", tile, tile.url ); return; } @@ -782,60 +587,29 @@ function onTileLoad( drawer, tile, time, image ) { tile.loaded = true; tile.image = image; - insertionIndex = drawer.tilesLoaded.length; + var cutoff = Math.ceil( Math.log( tiledImage.source.getTileSize(tile.level) ) / Math.log( 2 ) ); + tiledImage._tileCache.cacheTile({ + tile: tile, + cutoff: cutoff, + tiledImage: tiledImage + }); - if ( drawer.tilesLoaded.length >= drawer.maxImageCacheCount ) { - cutoff = Math.ceil( Math.log( drawer.source.getTileSize(tile.level) ) / Math.log( 2 ) ); - - worstTile = null; - worstTileIndex = -1; - - for ( i = drawer.tilesLoaded.length - 1; i >= 0; i-- ) { - prevTile = drawer.tilesLoaded[ i ]; - - if ( prevTile.level <= drawer.cutoff || prevTile.beingDrawn ) { - continue; - } else if ( !worstTile ) { - worstTile = prevTile; - worstTileIndex = i; - continue; - } - - prevTime = prevTile.lastTouchTime; - worstTime = worstTile.lastTouchTime; - prevLevel = prevTile.level; - worstLevel = worstTile.level; - - if ( prevTime < worstTime || - ( prevTime == worstTime && prevLevel > worstLevel ) ) { - worstTile = prevTile; - worstTileIndex = i; - } - } - - if ( worstTile && worstTileIndex >= 0 ) { - worstTile.unload(); - insertionIndex = worstTileIndex; - } - } - - drawer.tilesLoaded[ insertionIndex ] = tile; - drawer.updateAgain = true; + tiledImage.updateAgain = true; } -function positionTile( tile, overlap, viewport, viewportCenter, levelVisibility, drawer ){ +function positionTile( tile, overlap, viewport, viewportCenter, levelVisibility, tiledImage ){ var boundsTL = tile.bounds.getTopLeft(); - boundsTL.x *= drawer._scale; - boundsTL.y *= drawer._scale; - boundsTL.x += drawer._worldX; - boundsTL.y += drawer._worldY; + boundsTL.x *= tiledImage._scale; + boundsTL.y *= tiledImage._scale; + boundsTL.x += tiledImage._worldX; + boundsTL.y += tiledImage._worldY; var boundsSize = tile.bounds.getSize(); - boundsSize.x *= drawer._scale; - boundsSize.y *= drawer._scale; + boundsSize.x *= tiledImage._scale; + boundsSize.y *= tiledImage._scale; var positionC = viewport.pixelFromPoint( boundsTL, true ), positionT = viewport.pixelFromPoint( boundsTL, false ), @@ -855,8 +629,8 @@ function positionTile( tile, overlap, viewport, viewportCenter, levelVisibility, } -function blendTile( drawer, tile, x, y, level, levelOpacity, currentTime ){ - var blendTimeMillis = 1000 * drawer.blendTime, +function blendTile( tiledImage, tile, x, y, level, levelOpacity, currentTime ){ + var blendTimeMillis = 1000 * tiledImage.blendTime, deltaTime, opacity; @@ -867,16 +641,16 @@ function blendTile( drawer, tile, x, y, level, levelOpacity, currentTime ){ deltaTime = currentTime - tile.blendStart; opacity = blendTimeMillis ? Math.min( 1, deltaTime / ( blendTimeMillis ) ) : 1; - if ( drawer.alwaysBlend ) { + if ( tiledImage.alwaysBlend ) { opacity *= levelOpacity; } tile.opacity = opacity; - drawer.lastDrawn.push( tile ); + tiledImage.lastDrawn.push( tile ); if ( opacity == 1 ) { - setCoverage( drawer.coverage, level, x, y, true ); + setCoverage( tiledImage.coverage, level, x, y, true ); } else if ( deltaTime < blendTimeMillis ) { return true; } @@ -884,12 +658,6 @@ function blendTile( drawer, tile, x, y, level, levelOpacity, currentTime ){ return false; } - -function clearTiles( drawer ) { - drawer.tilesMatrix = {}; - drawer.tilesLoaded = []; -} - /** * @private * @inner @@ -1007,7 +775,7 @@ function compareTiles( previousBest, tile ) { return previousBest; } -function drawTiles( drawer, lastDrawn ){ +function drawTiles( tiledImage, lastDrawn ){ var i, tile, tileKey, @@ -1019,11 +787,11 @@ function drawTiles( drawer, lastDrawn ){ // We need a callback to give image manipulation a chance to happen var drawingHandler = function(args) { - if (drawer.viewer) { + if (tiledImage.viewer) { /** * This event is fired just before the tile is drawn giving the application a chance to alter the image. * - * NOTE: This event is only fired when the drawer is using a . + * NOTE: This event is only fired when the tiledImage is using a . * * @event tile-drawing * @memberof OpenSeadragon.Viewer @@ -1032,103 +800,24 @@ function drawTiles( drawer, lastDrawn ){ * @property {OpenSeadragon.Tile} tile * @property {?Object} userData - 'context', 'tile' and 'rendered'. */ - drawer.viewer.raiseEvent('tile-drawing', args); + tiledImage.viewer.raiseEvent('tile-drawing', args); } }; for ( i = lastDrawn.length - 1; i >= 0; i-- ) { tile = lastDrawn[ i ]; + tiledImage._drawer.drawTile( tile ); + tile.beingDrawn = true; - //We dont actually 'draw' a collection tile, rather its used to house - //an overlay which does the drawing in its own viewport - if( drawer.viewport.collectionMode ){ - - tileKey = tile.x + '/' + tile.y; - viewport = drawer.viewport; - collectionTileSource = viewport.collectionTileSource; - - if( !drawer.collectionOverlays[ tileKey ] ){ - - position = collectionTileSource.layout == 'horizontal' ? - tile.y + ( tile.x * collectionTileSource.rows ) : - tile.x + ( tile.y * collectionTileSource.rows ); - - if (position < collectionTileSource.tileSources.length) { - tileSource = collectionTileSource.tileSources[ position ]; - } else { - tileSource = null; - } - - //$.console.log("Rendering collection tile %s | %s | %s", tile.y, tile.y, position); - if( tileSource ){ - drawer.collectionOverlays[ tileKey ] = viewer = new $.Viewer({ - hash: viewport.viewer.hash + "-" + tileKey, - element: $.makeNeutralElement( "div" ), - mouseNavEnabled: false, - showNavigator: false, - showSequenceControl: false, - showNavigationControl: false, - tileSources: [ - tileSource - ] - }); - - //TODO: IE seems to barf on this, not sure if its just the border - // but we probably need to clear this up with a better - // test of support for various css features - if( SUBPIXEL_RENDERING ){ - viewer.element.style.border = '1px solid rgba(255,255,255,0.38)'; - viewer.element.style['-webkit-box-reflect'] = - 'below 0px -webkit-gradient('+ - 'linear,left '+ - 'top,left '+ - 'bottom,from(transparent),color-stop(62%,transparent),to(rgba(255,255,255,0.62))'+ - ')'; - } - - drawer.viewer.addOverlay( - viewer.element, - tile.bounds - ); - } - - }else{ - viewer = drawer.collectionOverlays[ tileKey ]; - if( viewer.viewport ){ - viewer.viewport.resize( tile.size, true ); - viewer.viewport.goHome( true ); - } - } - - } else { - - if ( drawer.useCanvas ) { - // TODO do this in a more performant way - // specifically, don't save,rotate,restore every time we draw a tile - if( drawer.viewport.degrees !== 0 ) { - offsetForRotation( tile, drawer.canvas, drawer.context, drawer.viewport.degrees ); - tile.drawCanvas( drawer.context, drawingHandler ); - restoreRotationChanges( tile, drawer.canvas, drawer.context ); - } else { - tile.drawCanvas( drawer.context, drawingHandler ); - } - } else { - tile.drawHTML( drawer.canvas ); - } - - - tile.beingDrawn = true; - } - - if( drawer.debugMode ){ + if( tiledImage.debugMode ){ try{ - drawDebugInfo( drawer, tile, lastDrawn.length, i ); + tiledImage._drawer.drawDebugInfo( tile, lastDrawn.length, i ); }catch(e){ $.console.error(e); } } - if( drawer.viewer ){ + if( tiledImage.viewer ){ /** * - Needs documentation - * @@ -1139,99 +828,11 @@ function drawTiles( drawer, lastDrawn ){ * @property {OpenSeadragon.Tile} tile * @property {?Object} userData - Arbitrary subscriber-defined object. */ - drawer.viewer.raiseEvent( 'tile-drawn', { + tiledImage.viewer.raiseEvent( 'tile-drawn', { tile: tile }); } } } -function offsetForRotation( tile, canvas, context, degrees ){ - var cx = canvas.width / 2, - cy = canvas.height / 2, - px = tile.position.x - cx, - py = tile.position.y - cy; - - context.save(); - - context.translate(cx, cy); - context.rotate( Math.PI / 180 * degrees); - tile.position.x = px; - tile.position.y = py; -} - -function restoreRotationChanges( tile, canvas, context ){ - var cx = canvas.width / 2, - cy = canvas.height / 2, - px = tile.position.x + cx, - py = tile.position.y + cy; - - tile.position.x = px; - tile.position.y = py; - - context.restore(); -} - - -function drawDebugInfo( drawer, tile, count, i ){ - - if ( drawer.useCanvas ) { - drawer.context.save(); - drawer.context.lineWidth = 2; - drawer.context.font = 'small-caps bold 13px ariel'; - drawer.context.strokeStyle = drawer.debugGridColor; - drawer.context.fillStyle = drawer.debugGridColor; - drawer.context.strokeRect( - tile.position.x, - tile.position.y, - tile.size.x, - tile.size.y - ); - if( tile.x === 0 && tile.y === 0 ){ - drawer.context.fillText( - "Zoom: " + drawer.viewport.getZoom(), - tile.position.x, - tile.position.y - 30 - ); - drawer.context.fillText( - "Pan: " + drawer.viewport.getBounds().toString(), - tile.position.x, - tile.position.y - 20 - ); - } - drawer.context.fillText( - "Level: " + tile.level, - tile.position.x + 10, - tile.position.y + 20 - ); - drawer.context.fillText( - "Column: " + tile.x, - tile.position.x + 10, - tile.position.y + 30 - ); - drawer.context.fillText( - "Row: " + tile.y, - tile.position.x + 10, - tile.position.y + 40 - ); - drawer.context.fillText( - "Order: " + i + " of " + count, - tile.position.x + 10, - tile.position.y + 50 - ); - drawer.context.fillText( - "Size: " + tile.size.toString(), - tile.position.x + 10, - tile.position.y + 60 - ); - drawer.context.fillText( - "Position: " + tile.position.toString(), - tile.position.x + 10, - tile.position.y + 70 - ); - drawer.context.restore(); - } -} - - }( OpenSeadragon )); diff --git a/src/viewer.js b/src/viewer.js index 667ef4fb..9e4c4a1e 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -151,7 +151,7 @@ $.Viewer = function( options ) { * @memberof OpenSeadragon.Viewer# */ drawer: null, - drawers: [], + world: null, // Container inside the canvas where drawers (layers) are drawn. drawersContainer: null, /** @@ -554,7 +554,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, this.source = null; this.drawer = null; - this.drawers = []; + this.world = null; this.viewport = this.preserveViewport ? this.viewport : null; @@ -1102,11 +1102,10 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, return; } - var drawer = new $.Drawer({ + var tiledImage = new $.TiledImage({ viewer: _this, source: tileSource, viewport: _this.viewport, - element: _this.drawersContainer, x: options.x, y: options.y, width: options.width, @@ -1126,7 +1125,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, debugMode: _this.debugMode, debugGridColor: _this.debugGridColor }); - _this.drawers.push( drawer ); + _this.world.add( tiledImage ); if ( options.level !== undefined ) { _this.setLayerLevel( drawer, options.level ); } @@ -1971,17 +1970,16 @@ function openTileSource( viewer, source, options ) { _this.source.overlays = _this.source.overlays || []; + _this.tileCache = new $.TileCache({ + maxImageCacheCount: _this.maxImageCacheCount + }); + _this.drawer = new $.Drawer({ viewer: _this, source: _this.source, viewport: _this.viewport, element: _this.drawersContainer, - x: options.x, - y: options.y, - width: options.width, - height: options.height, opacity: _this.opacity, - maxImageCacheCount: _this.maxImageCacheCount, imageLoaderLimit: _this.imageLoaderLimit, minZoomImageRatio: _this.minZoomImageRatio, wrapHorizontal: _this.wrapHorizontal, @@ -1995,6 +1993,32 @@ function openTileSource( viewer, source, options ) { debugGridColor: _this.debugGridColor, crossOriginPolicy: _this.crossOriginPolicy }); + + var tiledImage = new $.TiledImage({ + viewer: _this, + source: _this.source, + viewport: _this.viewport, + drawer: _this.drawer, + tileCache: _this.tileCache, + x: options.x, + y: options.y, + width: options.width, + height: options.height, + opacity: _this.opacity, + imageLoaderLimit: _this.imageLoaderLimit, + minZoomImageRatio: _this.minZoomImageRatio, + wrapHorizontal: _this.wrapHorizontal, + wrapVertical: _this.wrapVertical, + immediateRender: _this.immediateRender, + blendTime: _this.blendTime, + alwaysBlend: _this.alwaysBlend, + minPixelRatio: _this.collectionMode ? 0 : _this.minPixelRatio, + timeout: _this.timeout, + debugMode: _this.debugMode, + debugGridColor: _this.debugGridColor, + crossOriginPolicy: _this.crossOriginPolicy + }); + _this.drawers = [_this.drawer]; // Now that we have a drawer, see if it supports rotate. If not we need to remove the rotate buttons @@ -2711,7 +2735,7 @@ function updateOnce( viewer ) { } if ( animated ) { - updateDrawers( viewer ); + updateWorld( viewer ); drawOverlays( viewer.viewport, viewer.currentOverlays, viewer.overlaysContainer ); if( viewer.navigator ){ viewer.navigator.update( viewer.viewport ); @@ -2726,8 +2750,8 @@ function updateOnce( viewer ) { * @property {?Object} userData - Arbitrary subscriber-defined object. */ viewer.raiseEvent( "animation" ); - } else if ( THIS[ viewer.hash ].forceRedraw || drawersNeedUpdate( viewer ) ) { - updateDrawers( viewer ); + } else if ( THIS[ viewer.hash ].forceRedraw || viewer.world.needsUpdate() ) { + updateWorld( viewer ); drawOverlays( viewer.viewport, viewer.currentOverlays, viewer.overlaysContainer ); if( viewer.navigator ){ viewer.navigator.update( viewer.viewport ); @@ -2782,19 +2806,9 @@ function resizeViewportAndRecenter( viewer, containerSize, oldBounds, oldCenter viewport.fitBounds( newBounds, true ); } -function updateDrawers( viewer ) { - for (var i = 0; i < viewer.drawers.length; i++ ) { - viewer.drawers[i].update(); - } -} - -function drawersNeedUpdate( viewer ) { - for (var i = 0; i < viewer.drawers.length; i++ ) { - if (viewer.drawers[i].needsUpdate()) { - return true; - } - } - return false; +function updateWorld( viewer ) { + viewer.drawer.clear(); + viewer.world.update(); } /////////////////////////////////////////////////////////////////////////////// diff --git a/src/world.js b/src/world.js new file mode 100644 index 00000000..31e8520c --- /dev/null +++ b/src/world.js @@ -0,0 +1,164 @@ +/* + * OpenSeadragon - World + * + * Copyright (C) 2009 CodePlex Foundation + * Copyright (C) 2010-2013 OpenSeadragon contributors + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of CodePlex Foundation nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +(function( $ ){ + +/** + * @class World + * @classdesc + */ +$.World = function( options ) { + $.console.assert( options.viewer, "[World] options.viewer is required" ); + + this.viewer = options.viewer; + this._items = []; +}; + +$.World.prototype = /** @lends OpenSeadragon.World.prototype */{ + addItem: function( item ) { + this._items.push( item ); + }, + + /** + * Get the item at the specified level. + * @param {Number} level The item to retrieve level. + * @returns {OpenSeadragon.TiledImage} The item at the specified level. + */ + getItemAt: function( level ) { + if ( level >= this._items.length ) { + throw new Error( "Level bigger than number of items." ); + } + return this._items[ level ]; + }, + + /** + * Get the level of the given item or -1 if not present. + * @param {OpenSeadragon.TiledImage} item The item. + * @returns {Number} The level of the item or -1 if not present. + */ + getLevelOfItem: function( item ) { + return $.indexOf( this._items, item ); + }, + + /** + * Get the number of items used. + * @returns {Number} The number of items used. + */ + getItemCount: function() { + return this._items.length; + }, + + /** + * Change the level of a layer so that it appears over or under others. + * @param {OpenSeadragon.Drawer} drawer The underlying drawer of the changing + * level layer. + * @param {Number} level The new level + * @fires OpenSeadragon.Viewer.event:layer-level-changed + */ + setItemLevel: function( item, level ) { + var oldLevel = this.getLevelOfItem( item ); + + if ( level >= this._items.length ) { + throw new Error( "Level bigger than number of layers." ); + } + if ( level === oldLevel || oldLevel === -1 ) { + return; + } + this._items.splice( oldLevel, 1 ); + this._items.splice( level, 0, item ); + + /** + * Raised when the order of the layers has been changed. + * @event layer-level-changed + * @memberOf OpenSeadragon.Viewer + * @type {object} + * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. + * @property {OpenSeadragon.Drawer} drawer - The drawer which level has + * been changed + * @property {Number} previousLevel - The previous level of the drawer + * @property {Number} newLevel - The new level of the drawer + * @property {?Object} userData - Arbitrary subscriber-defined object. + */ + // TODO: deprecate + this.viewer.raiseEvent( 'layer-level-changed', { + drawer: item, + previousLevel: oldLevel, + newLevel: level + } ); + }, + + /** + * Remove a layer. If there is only one layer, close the viewer. + * @function + * @param {OpenSeadragon.Drawer} drawer The underlying drawer of the layer + * to remove + * @fires OpenSeadragon.Viewer.event:remove-layer + */ + removeItem: function( item ) { + var index = this._items.indexOf( item ); + if ( index === -1 ) { + return; + } + + this._items.splice( index, 1 ); + /** + * Raised when a layer is removed. + * @event remove-layer + * @memberOf OpenSeadragon.Viewer + * @type {object} + * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. + * @property {OpenSeadragon.Drawer} drawer The layer's underlying drawer. + * @property {?Object} userData - Arbitrary subscriber-defined object. + */ + // TODO: deprecate + this.raiseEvent( 'remove-layer', { drawer: item } ); + }, + + update: function() { + for (var i = 0; i < this._items.length; i++ ) { + this._items[i].update(); + } + }, + + needsUpdate: function() { + for (var i = 0; i < this._items.length; i++ ) { + if (this._items[i].needsUpdate()) { + return true; + } + } + return false; + } +}; + +}( OpenSeadragon )); From f04d538b5baea3aed49a87535377855e45f66ee8 Mon Sep 17 00:00:00 2001 From: Ian Gilman Date: Fri, 8 Aug 2014 16:15:23 -0700 Subject: [PATCH 03/16] Deprecated layer functions in viewer; additional cleanup. --- src/drawer.js | 2 - src/viewer.js | 113 +++++++++----------------------------------------- 2 files changed, 20 insertions(+), 95 deletions(-) diff --git a/src/drawer.js b/src/drawer.js index a5f35720..fd0d9e90 100644 --- a/src/drawer.js +++ b/src/drawer.js @@ -297,7 +297,6 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{ * @return {OpenSeadragon.Drawer} Chainable. */ reset: function() { - clearTiles( this ); this.lastResetTime = $.now(); this.updateAgain = true; return this; @@ -311,7 +310,6 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{ update: function() { //this.profiler.beginUpdate(); this.midUpdate = true; - updateViewport( this ); this.midUpdate = false; //this.profiler.endUpdate(); return this; diff --git a/src/viewer.js b/src/viewer.js index 46bccf78..e851ced3 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -152,8 +152,6 @@ $.Viewer = function( options ) { */ drawer: null, world: null, - // Container inside the canvas where drawers (layers) are drawn. - drawersContainer: null, /** * Handles coordinate-related functionality - zoom, pan, rotation, etc. Created for each TileSource opened. * @member {OpenSeadragon.Viewport} viewport @@ -264,7 +262,6 @@ $.Viewer = function( options ) { this.element = this.element || document.getElementById( this.id ); this.canvas = $.makeNeutralElement( "div" ); this.keyboardCommandArea = $.makeNeutralElement( "textarea" ); - this.drawersContainer = $.makeNeutralElement( "div" ); this.overlaysContainer = $.makeNeutralElement( "div" ); this.canvas.className = "openseadragon-canvas"; @@ -304,7 +301,6 @@ $.Viewer = function( options ) { this.container.insertBefore( this.canvas, this.container.firstChild ); this.container.insertBefore( this.keyboardCommandArea, this.container.firstChild ); this.element.appendChild( this.container ); - this.canvas.appendChild( this.drawersContainer ); this.canvas.appendChild( this.overlaysContainer ); //Used for toggling between fullscreen and default container size @@ -540,7 +536,6 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, } this.clearOverlays(); - this.drawersContainer.innerHTML = ""; this.overlaysContainer.innerHTML = ""; if ( this.drawer ) { @@ -1101,6 +1096,8 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, viewer: _this, source: tileSource, viewport: _this.viewport, + drawer: _this.drawer, + tileCache: _this.tileCache, x: options.x, y: options.y, width: options.width, @@ -1120,9 +1117,9 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, debugMode: _this.debugMode, debugGridColor: _this.debugGridColor }); - _this.world.add( tiledImage ); + _this.world.addItem( tiledImage ); if ( options.level !== undefined ) { - _this.setLayerLevel( drawer, options.level ); + _this.world.setItemLevel( tiledImage, options.level ); } THIS[ _this.hash ].forceRedraw = true; /** @@ -1137,7 +1134,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, */ _this.raiseEvent( 'add-layer', { options: options, - drawer: drawer + drawer: tiledImage }); }, function( event ) { event.options = options; @@ -1153,10 +1150,8 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, * @returns {OpenSeadragon.Drawer} The layer at the specified level. */ getLayerAtLevel: function( level ) { - if ( level >= this.drawers.length ) { - throw new Error( "Level bigger than number of layers." ); - } - return this.drawers[ level ]; + $.console.error( "[Viewer.getLayerAtLevel] this function is deprecated." ); + return null; }, /** @@ -1166,7 +1161,8 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, * @returns {Number} The level of the layer or -1 if not present. */ getLevelOfLayer: function( drawer ) { - return $.indexOf( this.drawers, drawer ); + $.console.error( "[Viewer.getLevelOfLayer] this function is deprecated." ); + return -1; }, /** @@ -1174,7 +1170,8 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, * @returns {Number} The number of layers used. */ getLayersCount: function() { - return this.drawers.length; + $.console.error( "[Viewer.getLayersCount] this function is deprecated." ); + return 0; }, /** @@ -1186,54 +1183,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, * @fires OpenSeadragon.Viewer.event:layer-level-changed */ setLayerLevel: function( drawer, level ) { - var oldLevel = this.getLevelOfLayer( drawer ); - - if ( level >= this.drawers.length ) { - throw new Error( "Level bigger than number of layers." ); - } - if ( level === oldLevel || oldLevel === -1 ) { - return this; - } - if ( level === 0 || oldLevel === 0 ) { - if ( THIS[ this.hash ].sequenced ) { - throw new Error( "Cannot reassign base level when in sequence mode." ); - } - // We need to re-assign the base drawer and the source - this.drawer = level === 0 ? drawer : this.getLayerAtLevel( level ); - this.source = this.drawer.source; - } - this.drawers.splice( oldLevel, 1 ); - this.drawers.splice( level, 0, drawer ); - this.drawersContainer.removeChild( drawer.canvas ); - if ( level === 0 ) { - var nextLevelCanvas = this.drawers[ 1 ].canvas; - nextLevelCanvas.parentNode.insertBefore( drawer.canvas, - nextLevelCanvas ); - } else { - // Insert right after layer at level - 1 - var prevLevelCanvas = this.drawers[level - 1].canvas; - prevLevelCanvas.parentNode.insertBefore( drawer.canvas, - prevLevelCanvas.nextSibling ); - } - - /** - * Raised when the order of the layers has been changed. - * @event layer-level-changed - * @memberOf OpenSeadragon.Viewer - * @type {object} - * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. - * @property {OpenSeadragon.Drawer} drawer - The drawer which level has - * been changed - * @property {Number} previousLevel - The previous level of the drawer - * @property {Number} newLevel - The new level of the drawer - * @property {?Object} userData - Arbitrary subscriber-defined object. - */ - this.raiseEvent( 'layer-level-changed', { - drawer: drawer, - previousLevel: oldLevel, - newLevel: level - } ); - + $.console.error( "[Viewer.setLayerLevel] this function is deprecated." ); return this; }, @@ -1246,38 +1196,12 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, * @fires OpenSeadragon.Viewer.event:remove-layer */ removeLayer: function( drawer ) { - var index = this.drawers.indexOf( drawer ); - if ( index === -1 ) { - return this; - } - if ( index === 0 ) { - if ( THIS[ this.hash ].sequenced ) { - throw new Error( "Cannot remove base layer when in sequence mode." ); - } - if ( this.drawers.length === 1 ) { - this.close(); - return this; - } - this.drawer = this.drawers[ 1 ]; - } - - this.drawers.splice( index, 1 ); - this.drawersContainer.removeChild( drawer.canvas ); - /** - * Raised when a layer is removed. - * @event remove-layer - * @memberOf OpenSeadragon.Viewer - * @type {object} - * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. - * @property {OpenSeadragon.Drawer} drawer The layer's underlying drawer. - * @property {?Object} userData - Arbitrary subscriber-defined object. - */ - this.raiseEvent( 'remove-layer', { drawer: drawer } ); + $.console.error( "[Viewer.removeLayer] this function is deprecated." ); return this; }, /** - * Force the viewer to redraw its drawers. + * Force the viewer to redraw its contents. * @returns {OpenSeadragon.Viewer} Chainable. */ forceRedraw: function() { @@ -1969,11 +1893,14 @@ function openTileSource( viewer, source, options ) { maxImageCacheCount: _this.maxImageCacheCount }); + _this.world = new $.World({ + viewer: _this + }); + _this.drawer = new $.Drawer({ viewer: _this, - source: _this.source, viewport: _this.viewport, - element: _this.drawersContainer, + element: _this.canvas, opacity: _this.opacity, imageLoaderLimit: _this.imageLoaderLimit, minZoomImageRatio: _this.minZoomImageRatio, @@ -2014,7 +1941,7 @@ function openTileSource( viewer, source, options ) { crossOriginPolicy: _this.crossOriginPolicy }); - _this.drawers = [_this.drawer]; + _this.world.addItem( tiledImage ); // Now that we have a drawer, see if it supports rotate. If not we need to remove the rotate buttons if (!_this.drawer.canRotate()) { From 94080c318015988f0478bf67e39257f08196d74d Mon Sep 17 00:00:00 2001 From: Ian Gilman Date: Fri, 8 Aug 2014 16:32:43 -0700 Subject: [PATCH 04/16] Cleaned up tileCache code. --- src/tilecache.js | 40 ++++++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/src/tilecache.js b/src/tilecache.js index 2e8faadb..7228eff6 100644 --- a/src/tilecache.js +++ b/src/tilecache.js @@ -34,6 +34,14 @@ (function( $ ){ +var TileRecord = function( params ) { + $.console.assert( params, "[TileCache.cacheTile] params is required" ); + $.console.assert( params.tile, "[TileCache.cacheTile] params.tile is required" ); + $.console.assert( params.tiledImage, "[TileCache.cacheTile] params.tiledImage is required" ); + this.tile = params.tile; + this.tiledImage = params.tiledImage; +}; + /** * @class TileCache * @classdesc @@ -56,17 +64,22 @@ $.TileCache.prototype = /** @lends OpenSeadragon.TileCache.prototype */{ return this._tilesLoaded.length; }, - cacheTile: function( tile, cutoff ) { - cutoff = cutoff || 0; + cacheTile: function( params ) { + $.console.assert( params, "[TileCache.cacheTile] params is required" ); + $.console.assert( params.tile, "[TileCache.cacheTile] params.tile is required" ); + $.console.assert( params.tiledImage, "[TileCache.cacheTile] params.tiledImage is required" ); + + var cutoff = params.cutoff || 0; var insertionIndex = this._tilesLoaded.length; if ( this._tilesLoaded.length >= this._maxImageCacheCount ) { var worstTile = null; var worstTileIndex = -1; - var prevTile, worstTime, worstLevel, prevTime, prevLevel; + var prevTile, worstTime, worstLevel, prevTime, prevLevel, prevTileRecord; for ( var i = this._tilesLoaded.length - 1; i >= 0; i-- ) { - prevTile = this._tilesLoaded[ i ]; + prevTileRecord = this._tilesLoaded[ i ]; + prevTile = prevTileRecord.tile; if ( prevTile.level <= cutoff || prevTile.beingDrawn ) { continue; @@ -94,19 +107,26 @@ $.TileCache.prototype = /** @lends OpenSeadragon.TileCache.prototype */{ } } - this._tilesLoaded[ insertionIndex ] = tile; + this._tilesLoaded[ insertionIndex ] = new TileRecord({ + tile: params.tile, + tiledImage: params.tiledImage + }); }, /** - * Clears all tiles. + * Clears all tiles associated with the specified tiledImage. * @method */ - reset: function() { + clearTilesFor: function( tiledImage ) { + var tileRecord; for ( var i = 0; i < this._tilesLoaded.length; ++i ) { - this._tilesLoaded[i].unload(); + tileRecord = this._tilesLoaded[ i ]; + if ( tileRecord.tiledImage === tiledImage ) { + tileRecord.tile.unload(); + this._tilesLoaded.splice( i, 1 ); + i--; + } } - - this._tilesLoaded = []; } }; From 45b71187327d6a325890da4f2de2450f8a1c98e3 Mon Sep 17 00:00:00 2001 From: Ian Gilman Date: Mon, 11 Aug 2014 17:04:20 -0700 Subject: [PATCH 05/16] Further cleanup --- src/drawer.js | 75 +++++++++-------------------------------------- src/tiledimage.js | 36 ++++------------------- src/viewer.js | 23 +++------------ src/world.js | 6 ++++ 4 files changed, 30 insertions(+), 110 deletions(-) diff --git a/src/drawer.js b/src/drawer.js index fd0d9e90..6f31d4ca 100644 --- a/src/drawer.js +++ b/src/drawer.js @@ -34,19 +34,6 @@ (function( $ ){ -var DEVICE_SCREEN = $.getWindowSize(), - BROWSER = $.Browser.vendor, - BROWSER_VERSION = $.Browser.version, - - SUBPIXEL_RENDERING = ( - ( BROWSER == $.BROWSERS.FIREFOX ) || - ( BROWSER == $.BROWSERS.OPERA ) || - ( BROWSER == $.BROWSERS.SAFARI && BROWSER_VERSION >= 4 ) || - ( BROWSER == $.BROWSERS.CHROME && BROWSER_VERSION >= 2 ) || - ( BROWSER == $.BROWSERS.IE && BROWSER_VERSION >= 9 ) - ); - - /** * @class Drawer * @classdesc Handles rendering of tiles for an {@link OpenSeadragon.Viewer}. @@ -63,8 +50,7 @@ $.Drawer = function( options ) { //backward compatibility for positional args while prefering more //idiomatic javascript options object as the only argument - var args = arguments, - i; + var args = arguments; if( !$.isPlainObject( options ) ){ options = { @@ -74,42 +60,15 @@ $.Drawer = function( options ) { }; } + $.console.assert( options.viewport, "[Drawer] options.viewport is required" ); + $.console.assert( options.element, "[Drawer] options.element is required" ); + if ( options.source ) { $.console.error( "[Drawer] options.source is no longer accepted; use TiledImage instead" ); } - $.extend( true, this, { - - //internal state properties - viewer: null, - imageLoader: new $.ImageLoader(), - tilesMatrix: {}, // A '3d' dictionary [level][x][y] --> Tile. - tilesLoaded: [], // An unordered list of Tiles with loaded images. - coverage: {}, // A '3d' dictionary [level][x][y] --> Boolean. - lastDrawn: [], // An unordered list of Tiles drawn last frame. - lastResetTime: 0, // Last time for which the drawer was reset. - midUpdate: false, // Is the drawer currently updating the viewport? - updateAgain: true, // Does the drawer need to update the viewport again? - - - //internal state / configurable settings - collectionOverlays: {}, // For collection mode. Here an overlay is actually a viewer. - - //configurable settings - opacity: $.DEFAULT_SETTINGS.opacity, - maxImageCacheCount: $.DEFAULT_SETTINGS.maxImageCacheCount, - minZoomImageRatio: $.DEFAULT_SETTINGS.minZoomImageRatio, - wrapHorizontal: $.DEFAULT_SETTINGS.wrapHorizontal, - wrapVertical: $.DEFAULT_SETTINGS.wrapVertical, - immediateRender: $.DEFAULT_SETTINGS.immediateRender, - blendTime: $.DEFAULT_SETTINGS.blendTime, - alwaysBlend: $.DEFAULT_SETTINGS.alwaysBlend, - minPixelRatio: $.DEFAULT_SETTINGS.minPixelRatio, - debugMode: $.DEFAULT_SETTINGS.debugMode, - timeout: $.DEFAULT_SETTINGS.timeout, - crossOriginPolicy: $.DEFAULT_SETTINGS.crossOriginPolicy - - }, options ); + this.viewer = options.viewer; + this.opacity = options.opacity === undefined ? $.DEFAULT_SETTINGS.opacity : options.opacity; this.useCanvas = $.supportsCanvas && ( this.viewer ? this.viewer.useCanvas : true ); /** @@ -118,7 +77,7 @@ $.Drawer = function( options ) { * @member {Element} container * @memberof OpenSeadragon.Drawer# */ - this.container = $.getElement( this.element ); + this.container = $.getElement( options.element ); /** * A <canvas> element if the browser supports them, otherwise a <div> element. * Child element of {@link OpenSeadragon.Drawer#container}. @@ -269,6 +228,7 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{ getOpacity: function() { return this.opacity; }, + /** * Returns whether the Drawer is scheduled for an update at the * soonest possible opportunity. @@ -277,7 +237,8 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{ * soonest possible opportunity. */ needsUpdate: function() { - return this.updateAgain; + $.console.error( "[Drawer.needsUpdate] this function is deprecated." ); + return false; }, /** @@ -287,7 +248,8 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{ * this Drawer. */ numTilesLoaded: function() { - return this.tilesLoaded.length; + $.console.error( "[Drawer.numTilesLoaded] this function is deprecated." ); + return 0; }, /** @@ -297,8 +259,7 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{ * @return {OpenSeadragon.Drawer} Chainable. */ reset: function() { - this.lastResetTime = $.now(); - this.updateAgain = true; + $.console.error( "[Drawer.reset] this function is deprecated." ); return this; }, @@ -308,10 +269,7 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{ * @return {OpenSeadragon.Drawer} Chainable. */ update: function() { - //this.profiler.beginUpdate(); - this.midUpdate = true; - this.midUpdate = false; - //this.profiler.endUpdate(); + $.console.error( "[Drawer.update] this function is deprecated." ); return this; }, @@ -330,11 +288,6 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{ * @return null */ destroy: function() { - //unload current loaded tiles (=empty TILE_CACHE) - for ( var i = 0; i < this.tilesLoaded.length; ++i ) { - this.tilesLoaded[i].unload(); - } - //force unloading of current canvas (1x1 will be gc later, trick not necessarily needed) this.canvas.width = 1; this.canvas.height = 1; diff --git a/src/tiledimage.js b/src/tiledimage.js index 750f7d2c..55b7e2f3 100644 --- a/src/tiledimage.js +++ b/src/tiledimage.js @@ -44,6 +44,8 @@ $.TiledImage = function( options ) { $.console.assert( options.tileCache, "[TiledImage] options.tileCache is required" ); $.console.assert( options.drawer, "[TiledImage] options.drawer is required" ); + $.console.assert( options.viewer, "[TiledImage] options.viewer is required" ); + $.console.assert( options.imageLoader, "[TiledImage] options.imageLoader is required" ); this._tileCache = options.tileCache; delete options.tileCache; @@ -51,6 +53,9 @@ $.TiledImage = function( options ) { this._drawer = options.drawer; delete options.drawer; + this._imageLoader = options.imageLoader; + delete options.imageLoader; + this._worldX = options.x || 0; delete options.x; this._worldY = options.y || 0; @@ -81,7 +86,6 @@ $.TiledImage = function( options ) { //internal state properties viewer: null, - imageLoader: new $.ImageLoader(), tilesMatrix: {}, // A '3d' dictionary [level][x][y] --> Tile. coverage: {}, // A '3d' dictionary [level][x][y] --> Boolean. lastDrawn: [], // An unordered list of Tiles drawn last frame. @@ -89,13 +93,7 @@ $.TiledImage = function( options ) { midUpdate: false, // Is the tiledImage currently updating the viewport? updateAgain: true, // Does the tiledImage need to update the viewport again? - - //internal state / configurable settings - collectionOverlays: {}, // For collection mode. Here an overlay is actually a viewer. - //configurable settings - opacity: $.DEFAULT_SETTINGS.opacity, - maxImageCacheCount: $.DEFAULT_SETTINGS.maxImageCacheCount, minZoomImageRatio: $.DEFAULT_SETTINGS.minZoomImageRatio, wrapHorizontal: $.DEFAULT_SETTINGS.wrapHorizontal, wrapVertical: $.DEFAULT_SETTINGS.wrapVertical, @@ -104,34 +102,12 @@ $.TiledImage = function( options ) { alwaysBlend: $.DEFAULT_SETTINGS.alwaysBlend, minPixelRatio: $.DEFAULT_SETTINGS.minPixelRatio, debugMode: $.DEFAULT_SETTINGS.debugMode, - timeout: $.DEFAULT_SETTINGS.timeout, crossOriginPolicy: $.DEFAULT_SETTINGS.crossOriginPolicy }, options ); }; $.TiledImage.prototype = /** @lends OpenSeadragon.TiledImage.prototype */{ - /** - * Set the opacity of the TiledImage. - * @method - * @param {Number} opacity - * @return {OpenSeadragon.TiledImage} Chainable. - */ - setOpacity: function( opacity ) { - this.opacity = opacity; - // TODO: trigger update - return this; - }, - - /** - * Get the opacity of the TiledImage. - * @method - * @returns {Number} - */ - getOpacity: function() { - return this.opacity; - }, - /** * Returns whether the TiledImage is scheduled for an update at the * soonest possible opportunity. @@ -558,7 +534,7 @@ function getTile( x, y, level, tileSource, tilesMatrix, time, numTiles, worldWid function loadTile( tiledImage, tile, time ) { tile.loading = true; - tiledImage.imageLoader.addJob({ + tiledImage._imageLoader.addJob({ src: tile.url, crossOriginPolicy: tiledImage.crossOriginPolicy, callback: function( image ){ diff --git a/src/viewer.js b/src/viewer.js index e851ced3..e6af8260 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -1102,9 +1102,6 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, y: options.y, width: options.width, height: options.height, - opacity: options.opacity !== undefined ? - options.opacity : _this.opacity, - maxImageCacheCount: _this.maxImageCacheCount, imageLoaderLimit: _this.imageLoaderLimit, minZoomImageRatio: _this.minZoomImageRatio, wrapHorizontal: _this.wrapHorizontal, @@ -1113,7 +1110,6 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, blendTime: _this.blendTime, alwaysBlend: _this.alwaysBlend, minPixelRatio: _this.minPixelRatio, - timeout: _this.timeout, debugMode: _this.debugMode, debugGridColor: _this.debugGridColor }); @@ -1889,6 +1885,8 @@ function openTileSource( viewer, source, options ) { _this.source.overlays = _this.source.overlays || []; + _this.imageLoader = new $.ImageLoader(); + _this.tileCache = new $.TileCache({ maxImageCacheCount: _this.maxImageCacheCount }); @@ -1901,19 +1899,7 @@ function openTileSource( viewer, source, options ) { viewer: _this, viewport: _this.viewport, element: _this.canvas, - opacity: _this.opacity, - imageLoaderLimit: _this.imageLoaderLimit, - minZoomImageRatio: _this.minZoomImageRatio, - wrapHorizontal: _this.wrapHorizontal, - wrapVertical: _this.wrapVertical, - immediateRender: _this.immediateRender, - blendTime: _this.blendTime, - alwaysBlend: _this.alwaysBlend, - minPixelRatio: _this.collectionMode ? 0 : _this.minPixelRatio, - timeout: _this.timeout, - debugMode: _this.debugMode, - debugGridColor: _this.debugGridColor, - crossOriginPolicy: _this.crossOriginPolicy + opacity: _this.opacity }); var tiledImage = new $.TiledImage({ @@ -1922,11 +1908,11 @@ function openTileSource( viewer, source, options ) { viewport: _this.viewport, drawer: _this.drawer, tileCache: _this.tileCache, + imageLoader: _this.imageLoader, x: options.x, y: options.y, width: options.width, height: options.height, - opacity: _this.opacity, imageLoaderLimit: _this.imageLoaderLimit, minZoomImageRatio: _this.minZoomImageRatio, wrapHorizontal: _this.wrapHorizontal, @@ -1935,7 +1921,6 @@ function openTileSource( viewer, source, options ) { blendTime: _this.blendTime, alwaysBlend: _this.alwaysBlend, minPixelRatio: _this.collectionMode ? 0 : _this.minPixelRatio, - timeout: _this.timeout, debugMode: _this.debugMode, debugGridColor: _this.debugGridColor, crossOriginPolicy: _this.crossOriginPolicy diff --git a/src/world.js b/src/world.js index 31e8520c..a6f98e94 100644 --- a/src/world.js +++ b/src/world.js @@ -145,6 +145,12 @@ $.World.prototype = /** @lends OpenSeadragon.World.prototype */{ this.raiseEvent( 'remove-layer', { drawer: item } ); }, + resetTiles: function() { + for (var i = 0; i < this._items.length; i++ ) { + this._items[i].reset(); + } + }, + update: function() { for (var i = 0; i < this._items.length; i++ ) { this._items[i].update(); From 2ee59635fa4c0bc14952ede3fcf4678f977e2b80 Mon Sep 17 00:00:00 2001 From: Ian Gilman Date: Tue, 12 Aug 2014 16:04:55 -0700 Subject: [PATCH 06/16] World.getHomeBounds() --- src/tiledimage.js | 4 ++++ src/world.js | 30 ++++++++++++++++++++++++++---- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/tiledimage.js b/src/tiledimage.js index 55b7e2f3..46f1e3e3 100644 --- a/src/tiledimage.js +++ b/src/tiledimage.js @@ -151,6 +151,10 @@ $.TiledImage.prototype = /** @lends OpenSeadragon.TiledImage.prototype */{ */ destroy: function() { this.reset(); + }, + + getWorldBounds: function() { + return new $.Rect( this._worldX, this._worldY, this._worldWidth, this._worldHeight ); } }; diff --git a/src/world.js b/src/world.js index a6f98e94..8eeb3141 100644 --- a/src/world.js +++ b/src/world.js @@ -146,24 +146,46 @@ $.World.prototype = /** @lends OpenSeadragon.World.prototype */{ }, resetTiles: function() { - for (var i = 0; i < this._items.length; i++ ) { + for ( var i = 0; i < this._items.length; i++ ) { this._items[i].reset(); } }, update: function() { - for (var i = 0; i < this._items.length; i++ ) { + for ( var i = 0; i < this._items.length; i++ ) { this._items[i].update(); } }, needsUpdate: function() { - for (var i = 0; i < this._items.length; i++ ) { - if (this._items[i].needsUpdate()) { + for ( var i = 0; i < this._items.length; i++ ) { + if ( this._items[i].needsUpdate() ) { return true; } } return false; + }, + + getHomeBounds: function() { + if ( !this._items.length ) { + return new $.Rect(0, 0, 1, 1); + } + + var bounds = this._items[0].getWorldBounds(); + var left = bounds.x; + var top = bounds.y; + var right = bounds.x + bounds.width; + var bottom = bounds.y + bounds.height; + var box; + for ( var i = 1; i < this._items.length; i++ ) { + box = this._items[i].getWorldBounds(); + left = Math.min( left, box.x ); + top = Math.min( top, box.y ); + right = Math.max( right, box.x + box.width ); + bottom = Math.max( bottom, box.y + box.height ); + } + + return new $.Rect( left, top, right - left, bottom - top ); } }; From 66b8d7e191960e2f58c8a39197ed8955e8d8b2f1 Mon Sep 17 00:00:00 2001 From: Ian Gilman Date: Tue, 12 Aug 2014 16:15:17 -0700 Subject: [PATCH 07/16] Previous cleanup was a little too aggressive. --- src/drawer.js | 2 ++ src/viewer.js | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/drawer.js b/src/drawer.js index 6f31d4ca..fbb8db0f 100644 --- a/src/drawer.js +++ b/src/drawer.js @@ -68,6 +68,8 @@ $.Drawer = function( options ) { } this.viewer = options.viewer; + this.viewport = options.viewport; + this.debugGridColor = options.debugGridColor || $.DEFAULT_SETTINGS.debugGridColor; this.opacity = options.opacity === undefined ? $.DEFAULT_SETTINGS.opacity : options.opacity; this.useCanvas = $.supportsCanvas && ( this.viewer ? this.viewer.useCanvas : true ); diff --git a/src/viewer.js b/src/viewer.js index e6af8260..69f8ae73 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -1098,6 +1098,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, viewport: _this.viewport, drawer: _this.drawer, tileCache: _this.tileCache, + imageLoader: _this.imageLoader, x: options.x, y: options.y, width: options.width, @@ -1899,7 +1900,8 @@ function openTileSource( viewer, source, options ) { viewer: _this, viewport: _this.viewport, element: _this.canvas, - opacity: _this.opacity + opacity: _this.opacity, + debugGridColor: _this.debugGridColor }); var tiledImage = new $.TiledImage({ From 5c7c1d5b6c46a3dbec0179964f58f483f14090be Mon Sep 17 00:00:00 2001 From: Ian Gilman Date: Tue, 12 Aug 2014 16:44:53 -0700 Subject: [PATCH 08/16] Moved Viewport.goHome to Viewer.goHome. --- src/viewer.js | 29 ++++++++++++++++++--- src/viewport.js | 22 ++++------------ test/basic.js | 2 +- test/demo/collections/main.js | 1 + test/demo/fitboundswithconstraints.html | 34 ++++++++++++------------- test/overlays.js | 4 +-- 6 files changed, 51 insertions(+), 41 deletions(-) diff --git a/src/viewer.js b/src/viewer.js index 69f8ae73..4b91185c 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -333,7 +333,7 @@ $.Viewer = function( options ) { _this.viewport.applyConstraints(); return false; case 48://0|) - _this.viewport.goHome(); + _this.goHome(); _this.viewport.applyConstraints(); return false; case 119://w @@ -622,6 +622,28 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, this.element = null; }, + /** + * @function + * @param {Boolean} immediately + * @fires OpenSeadragon.Viewer.event:home + */ + goHome: function(immediately) { + /** + * Raised when the "home" operation occurs (see {@link OpenSeadragon.Viewport#goHome}). + * + * @event home + * @memberof OpenSeadragon.Viewer + * @type {object} + * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. + * @property {Boolean} immediately + * @property {?Object} userData - Arbitrary subscriber-defined object. + */ + this.raiseEvent( 'home', { + immediately: immediately + }); + + this.viewport.fitBounds( this.world.getHomeBounds(), immediately ); + }, /** * @function @@ -1929,6 +1951,7 @@ function openTileSource( viewer, source, options ) { }); _this.world.addItem( tiledImage ); + _this.goHome( true ); // Now that we have a drawer, see if it supports rotate. If not we need to remove the rotate buttons if (!_this.drawer.canRotate()) { @@ -2793,9 +2816,7 @@ function lightUp() { function onHome() { - if ( this.viewport ) { - this.viewport.goHome(); - } + this.goHome(); } diff --git a/src/viewport.js b/src/viewport.js index 7b910c49..5e232157 100644 --- a/src/viewport.js +++ b/src/viewport.js @@ -105,7 +105,6 @@ $.Viewport = function( options ) { }); this.resetContentSize( this.contentSize ); - this.goHome( true ); this.update(); }; @@ -178,26 +177,15 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{ /** * @function - * @param {Boolean} immediately - * @fires OpenSeadragon.Viewer.event:home + * @private */ goHome: function( immediately ) { + $.console.error("[Viewport.goHome] this function is deprecated; use Viewer.goHome instead"); if( this.viewer ){ - /** - * Raised when the "home" operation occurs (see {@link OpenSeadragon.Viewport#goHome}). - * - * @event home - * @memberof OpenSeadragon.Viewer - * @type {object} - * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. - * @property {Boolean} immediately - * @property {?Object} userData - Arbitrary subscriber-defined object. - */ - this.viewer.raiseEvent( 'home', { - immediately: immediately - }); + this.viewer.goHome(immediately); } - return this.fitBounds( this.getHomeBounds(), immediately ); + + return this; }, /** diff --git a/test/basic.js b/test/basic.js index 811efd3b..46abc823 100644 --- a/test/basic.js +++ b/test/basic.js @@ -133,7 +133,7 @@ }; viewer.addHandler('animation-finish', homeHandler); - viewport.goHome(true); + viewer.goHome(true); } viewer.addHandler("open", opener); diff --git a/test/demo/collections/main.js b/test/demo/collections/main.js index 3713ba26..0f78ab4f 100644 --- a/test/demo/collections/main.js +++ b/test/demo/collections/main.js @@ -38,6 +38,7 @@ var addLayerHandler = function( event ) { if ( event.options === options ) { self.viewer.removeHandler( "add-layer", addLayerHandler ); + self.viewer.goHome(); } }; this.viewer.addHandler( "add-layer", addLayerHandler ); diff --git a/test/demo/fitboundswithconstraints.html b/test/demo/fitboundswithconstraints.html index c0317c98..d0c1f460 100644 --- a/test/demo/fitboundswithconstraints.html +++ b/test/demo/fitboundswithconstraints.html @@ -13,7 +13,7 @@ #highlights li { cursor: pointer; } - + @@ -23,33 +23,33 @@
- + - + diff --git a/test/overlays.js b/test/overlays.js index 223f99de..c90c669f 100644 --- a/test/overlays.js +++ b/test/overlays.js @@ -270,7 +270,7 @@ waitForViewer( function() { checkOverlayPosition( "after zoom and pan using image coordinates" ); - viewer.viewport.goHome(); + viewer.goHome(); waitForViewer( function() { checkOverlayPosition( "after goHome using image coordinates" ); start(); @@ -333,7 +333,7 @@ waitForViewer( function() { checkOverlayPosition( "after zoom and pan using viewport coordinates" ); - viewer.viewport.goHome(); + viewer.goHome(); waitForViewer( function() { checkOverlayPosition( "after goHome using viewport coordinates" ); start(); From 8efad6f3a0e07ff15c2dface0587d369e4a8bd1a Mon Sep 17 00:00:00 2001 From: Ian Gilman Date: Mon, 18 Aug 2014 16:04:49 -0700 Subject: [PATCH 09/16] Fixed home/constraints for multi-image. --- changelog.txt | 2 + src/point.js | 7 ++ src/rectangle.js | 7 ++ src/tiledimage.js | 4 ++ src/viewer.js | 40 +++--------- src/viewport.js | 119 +++++++++++++++++++++++----------- src/world.js | 26 +++++++- test/demo/collections/main.js | 6 +- 8 files changed, 137 insertions(+), 74 deletions(-) diff --git a/changelog.txt b/changelog.txt index e9c895f1..babc5979 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,6 +1,8 @@ OPENSEADRAGON CHANGELOG ======================= +2.0.0: (in progress) + 1.2.0: (in progress) * New combined IIIF TileSource for 1.0 through 2.0 (#441) diff --git a/src/point.js b/src/point.js index 38aad1f1..0004426b 100644 --- a/src/point.js +++ b/src/point.js @@ -60,6 +60,13 @@ $.Point = function( x, y ) { }; $.Point.prototype = /** @lends OpenSeadragon.Point.prototype */{ + /** + * @function + * @returns {OpenSeadragon.Point} a duplicate of this Point + */ + clone: function() { + return new $.Point(this.x, this.y); + }, /** * Add another Point to this point and return a new Point. diff --git a/src/rectangle.js b/src/rectangle.js index 99172e7d..3a3ba547 100644 --- a/src/rectangle.js +++ b/src/rectangle.js @@ -75,6 +75,13 @@ $.Rect = function( x, y, width, height ) { }; $.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{ + /** + * @function + * @returns {OpenSeadragon.Rect} a duplicate of this Rect + */ + clone: function() { + return new $.Rect(this.x, this.y, this.width, this.height); + }, /** * The aspect ratio is simply the ratio of width to height. diff --git a/src/tiledimage.js b/src/tiledimage.js index 46f1e3e3..31b30a0f 100644 --- a/src/tiledimage.js +++ b/src/tiledimage.js @@ -155,6 +155,10 @@ $.TiledImage.prototype = /** @lends OpenSeadragon.TiledImage.prototype */{ getWorldBounds: function() { return new $.Rect( this._worldX, this._worldY, this._worldWidth, this._worldHeight ); + }, + + getContentSize: function() { + return new $.Point(this.source.dimensions.x, this.source.dimensions.y); } }; diff --git a/src/viewer.js b/src/viewer.js index e872ee03..d8c99b52 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -333,7 +333,7 @@ $.Viewer = function( options ) { _this.viewport.applyConstraints(); return false; case 48://0|) - _this.goHome(); + _this.viewport.goHome(); _this.viewport.applyConstraints(); return false; case 119://w @@ -622,29 +622,6 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, this.element = null; }, - /** - * @function - * @param {Boolean} immediately - * @fires OpenSeadragon.Viewer.event:home - */ - goHome: function(immediately) { - /** - * Raised when the "home" operation occurs (see {@link OpenSeadragon.Viewport#goHome}). - * - * @event home - * @memberof OpenSeadragon.Viewer - * @type {object} - * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. - * @property {Boolean} immediately - * @property {?Object} userData - Arbitrary subscriber-defined object. - */ - this.raiseEvent( 'home', { - immediately: immediately - }); - - this.viewport.fitBounds( this.world.getHomeBounds(), immediately ); - }, - /** * @function * @return {Boolean} @@ -1137,6 +1114,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, debugGridColor: _this.debugGridColor }); _this.world.addItem( tiledImage ); + _this.viewport.setHomeBounds(_this.world.getHomeBounds(), _this.world.getContentFactor()); if ( options.level !== undefined ) { _this.world.setItemLevel( tiledImage, options.level ); } @@ -1868,7 +1846,6 @@ function openTileSource( viewer, source, options ) { collectionMode: true, collectionTileSource: _this.source, containerSize: THIS[ _this.hash ].prevContainerSize, - contentSize: _this.source.dimensions, springStiffness: _this.springStiffness, animationTime: _this.animationTime, showNavigator: false, @@ -1886,7 +1863,6 @@ function openTileSource( viewer, source, options ) { } _this.viewport = _this.viewport ? _this.viewport : new $.Viewport({ containerSize: THIS[ _this.hash ].prevContainerSize, - contentSize: _this.source.dimensions, springStiffness: _this.springStiffness, animationTime: _this.animationTime, minZoomImageRatio: _this.minZoomImageRatio, @@ -1903,9 +1879,10 @@ function openTileSource( viewer, source, options ) { }); } - if( _this.preserveViewport ){ - _this.viewport.resetContentSize( _this.source.dimensions ); - } + // TODO: what to do about this? + // if( _this.preserveViewport ){ + // _this.viewport.resetContentSize( _this.source.dimensions ); + // } _this.source.overlays = _this.source.overlays || []; @@ -1952,7 +1929,8 @@ function openTileSource( viewer, source, options ) { }); _this.world.addItem( tiledImage ); - _this.goHome( true ); + _this.viewport.setHomeBounds(_this.world.getHomeBounds(), _this.world.getContentFactor()); + _this.viewport.goHome( true ); // Now that we have a drawer, see if it supports rotate. If not we need to remove the rotate buttons if (!_this.drawer.canRotate()) { @@ -2818,7 +2796,7 @@ function lightUp() { function onHome() { - this.goHome(); + this.viewport.goHome(); } diff --git a/src/viewport.js b/src/viewport.js index 48fa11a2..6a4c4156 100644 --- a/src/viewport.js +++ b/src/viewport.js @@ -104,43 +104,82 @@ $.Viewport = function( options ) { animationTime: this.animationTime }); - this.resetContentSize( this.contentSize ); + if (this.contentSize) { + this.resetContentSize( this.contentSize ); + } else { + this.setHomeBounds(new $.Rect(0, 0, 1, 1), 1); + } + + this.goHome( true ); this.update(); }; $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{ - /** + * Updates the viewport's home bounds and constraints for the given content size. * @function + * @param {OpenSeadragon.Point} contentSize - size of the content in content units * @return {OpenSeadragon.Viewport} Chainable. * @fires OpenSeadragon.Viewer.event:reset-size */ resetContentSize: function( contentSize ){ - this.contentSize = contentSize; + $.console.assert(contentSize, "[Viewport.resetContentSize] contentSize is required"); + $.console.assert(contentSize instanceof $.Point, "[Viewport.resetContentSize] contentSize must be an OpenSeadragon.Point"); + $.console.assert(contentSize.x > 0, "[Viewport.resetContentSize] contentSize.x must be greater than 0"); + $.console.assert(contentSize.y > 0, "[Viewport.resetContentSize] contentSize.y must be greater than 0"); + + this.setHomeBounds(new $.Rect(0, 0, 1, contentSize.y / contentSize.x), contentSize.x); + return this; + }, + + /** + * Updates the viewport's home bounds and constraints. + * @function + * @param {OpenSeadragon.Rect} bounds - the new bounds in world coordinates + * @param {Number} contentFactor - how many content units per world unit + * @fires OpenSeadragon.Viewer.event:reset-size + */ + setHomeBounds: function(bounds, contentFactor) { + $.console.assert(bounds, "[Viewport.setHomeBounds] bounds is required"); + $.console.assert(bounds instanceof $.Rect, "[Viewport.setHomeBounds] bounds must be an OpenSeadragon.Rect"); + $.console.assert(bounds.width > 0, "[Viewport.setHomeBounds] bounds.width must be greater than 0"); + $.console.assert(bounds.height > 0, "[Viewport.setHomeBounds] bounds.height must be greater than 0"); + + this.homeBounds = bounds.clone(); + this.contentSize = this.homeBounds.getSize().times(contentFactor); this.contentAspectX = this.contentSize.x / this.contentSize.y; this.contentAspectY = this.contentSize.y / this.contentSize.x; - this.fitWidthBounds = new $.Rect( 0, 0, 1, this.contentAspectY ); - this.fitHeightBounds = new $.Rect( 0, 0, this.contentAspectY, this.contentAspectY); - this.homeBounds = new $.Rect( 0, 0, 1, this.contentAspectY ); + // TODO: seems like fitWidthBounds and fitHeightBounds should be thin slices + // across the appropriate axis, centered in the image, rather than what we have + // here. + this.fitWidthBounds = new $.Rect(this.homeBounds.x, this.homeBounds.y, + this.homeBounds.width, this.homeBounds.width); + + this.fitHeightBounds = new $.Rect(this.homeBounds.x, this.homeBounds.y, + this.homeBounds.height, this.homeBounds.height); if( this.viewer ){ /** - * Raised when the viewer's content size is reset (see {@link OpenSeadragon.Viewport#resetContentSize}). + * Raised when the viewer's content size or home bounds are reset + * (see {@link OpenSeadragon.Viewport#resetContentSize}, + * {@link OpenSeadragon.Viewport#setHomeBounds}). * * @event reset-size * @memberof OpenSeadragon.Viewer * @type {object} * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. * @property {OpenSeadragon.Point} contentSize + * @property {OpenSeadragon.Rect} homeBounds + * @property {Number} contentFactor * @property {?Object} userData - Arbitrary subscriber-defined object. */ this.viewer.raiseEvent( 'reset-size', { - contentSize: contentSize + contentSize: this.contentSize.clone(), + contentFactor: contentFactor, + homeBounds: this.homeBounds.clone() }); } - - return this; }, /** @@ -153,9 +192,11 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{ if( this.defaultZoomLevel ){ return this.defaultZoomLevel; } else { - return ( aspectFactor >= 1 ) ? + var output = ( aspectFactor >= 1 ) ? 1 : aspectFactor; + + return output / this.homeBounds.width; } }, @@ -163,29 +204,31 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{ * @function */ getHomeBounds: function() { - var center = this.homeBounds.getCenter( ), - width = 1.0 / this.getHomeZoom( ), - height = width / this.getAspectRatio(); - - return new $.Rect( - center.x - ( width / 2.0 ), - center.y - ( height / 2.0 ), - width, - height - ); + return this.homeBounds.clone(); }, /** * @function - * @private + * @param {Boolean} immediately + * @fires OpenSeadragon.Viewer.event:home */ goHome: function( immediately ) { - $.console.error("[Viewport.goHome] this function is deprecated; use Viewer.goHome instead"); if( this.viewer ){ - this.viewer.goHome(immediately); + /** + * Raised when the "home" operation occurs (see {@link OpenSeadragon.Viewport#goHome}). + * + * @event home + * @memberof OpenSeadragon.Viewer + * @type {object} + * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. + * @property {Boolean} immediately + * @property {?Object} userData - Arbitrary subscriber-defined object. + */ + this.viewer.raiseEvent( 'home', { + immediately: immediately + }); } - - return this; + return this.fitBounds( this.getHomeBounds(), immediately ); }, /** @@ -204,9 +247,11 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{ * @function */ getMaxZoom: function() { - var zoom = this.maxZoomLevel ? - this.maxZoomLevel : - ( this.contentSize.x * this.maxZoomPixelRatio / this.containerSize.x ); + var zoom = this.maxZoomLevel; + if (!zoom) { + zoom = this.contentSize.x * this.maxZoomPixelRatio / this.containerSize.x; + zoom /= this.homeBounds.width; + } return Math.max( zoom, this.getHomeZoom() ); }, @@ -335,9 +380,9 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{ verticalThreshold = this.visibilityRatio * newBounds.height; left = newBounds.x + newBounds.width; - right = 1 - newBounds.x; + right = this.homeBounds.width - newBounds.x; top = newBounds.y + newBounds.height; - bottom = this.contentAspectY - newBounds.y; + bottom = this.homeBounds.height - newBounds.y; if ( this.wrapHorizontal ) { //do nothing @@ -368,11 +413,11 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{ if ( dx || dy || immediately ) { newBounds.x += dx; newBounds.y += dy; - if( newBounds.width > 1 ){ - newBounds.x = 0.5 - newBounds.width/2; + if( newBounds.width > this.homeBounds.width ){ + newBounds.x = this.homeBounds.width / 2 - newBounds.width/2; } - if( newBounds.height > this.contentAspectY ){ - newBounds.y = this.contentAspectY/2 - newBounds.height/2; + if( newBounds.height > this.homeBounds.height){ + newBounds.y = this.homeBounds.height / 2 - newBounds.height/2; } } @@ -401,10 +446,6 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{ * @fires OpenSeadragon.Viewer.event:constrain */ applyConstraints: function( immediately ) { - if (true) { - return; // TEMP - } - var actualZoom = this.getZoom(), constrainedZoom = Math.max( Math.min( actualZoom, this.getMaxZoom() ), diff --git a/src/world.js b/src/world.js index 8eeb3141..1b2245b3 100644 --- a/src/world.js +++ b/src/world.js @@ -43,11 +43,13 @@ $.World = function( options ) { this.viewer = options.viewer; this._items = []; + this._figureSizes(); }; $.World.prototype = /** @lends OpenSeadragon.World.prototype */{ addItem: function( item ) { this._items.push( item ); + this._figureSizes(); }, /** @@ -132,6 +134,8 @@ $.World.prototype = /** @lends OpenSeadragon.World.prototype */{ } this._items.splice( index, 1 ); + this._figureSizes(); + /** * Raised when a layer is removed. * @event remove-layer @@ -167,11 +171,26 @@ $.World.prototype = /** @lends OpenSeadragon.World.prototype */{ }, getHomeBounds: function() { + return this._homeBounds.clone(); + }, + + getContentSize: function() { + return this._contentSize.clone(); + }, + + getContentFactor: function() { + return this._contentFactor; + }, + + _figureSizes: function() { if ( !this._items.length ) { - return new $.Rect(0, 0, 1, 1); + this._homeBounds = new $.Rect(0, 0, 1, 1); + this._contentSize = new $.Point(1, 1); + return; } var bounds = this._items[0].getWorldBounds(); + this._contentFactor = this._items[0].getContentSize().x / bounds.width; var left = bounds.x; var top = bounds.y; var right = bounds.x + bounds.width; @@ -179,13 +198,16 @@ $.World.prototype = /** @lends OpenSeadragon.World.prototype */{ var box; for ( var i = 1; i < this._items.length; i++ ) { box = this._items[i].getWorldBounds(); + this._contentFactor = Math.max(this._contentFactor, this._items[i].getContentSize().x / bounds.width); left = Math.min( left, box.x ); top = Math.min( top, box.y ); right = Math.max( right, box.x + box.width ); bottom = Math.max( bottom, box.y + box.height ); } - return new $.Rect( left, top, right - left, bottom - top ); + this._homeBounds = new $.Rect( left, top, right - left, bottom - top ); + this._contentSize = new $.Point(this._homeBounds.width * this._contentFactor, + this._homeBounds.height * this._contentFactor); } }; diff --git a/test/demo/collections/main.js b/test/demo/collections/main.js index 0f78ab4f..03547079 100644 --- a/test/demo/collections/main.js +++ b/test/demo/collections/main.js @@ -1,6 +1,8 @@ +/* globals $, App */ + (function() { - var App = { + window.App = { init: function() { var self = this; @@ -38,7 +40,7 @@ var addLayerHandler = function( event ) { if ( event.options === options ) { self.viewer.removeHandler( "add-layer", addLayerHandler ); - self.viewer.goHome(); + self.viewer.viewport.goHome(); } }; this.viewer.addHandler( "add-layer", addLayerHandler ); From 796588ace2ba2f9cb66e1aa67066943dfc35cffa Mon Sep 17 00:00:00 2001 From: Ian Gilman Date: Mon, 18 Aug 2014 16:32:21 -0700 Subject: [PATCH 10/16] Unit test fixes. --- test/basic.js | 2 +- test/test.js | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/test/basic.js b/test/basic.js index 46abc823..4a7892d9 100644 --- a/test/basic.js +++ b/test/basic.js @@ -133,7 +133,7 @@ }; viewer.addHandler('animation-finish', homeHandler); - viewer.goHome(true); + viewer.viewport.goHome(true); } viewer.addHandler("open", opener); diff --git a/test/test.js b/test/test.js index c051d14b..68010ef4 100644 --- a/test/test.js +++ b/test/test.js @@ -134,6 +134,12 @@ } } + testConsole.assert = function(condition, message) { + if (condition) { + testConsole.error(message); + } + }; + OpenSeadragon.console = testConsole; } )(); From 33f0fa1e4b9083bdb1d27ca05ab3fad4d71deb8c Mon Sep 17 00:00:00 2001 From: Ian Gilman Date: Tue, 19 Aug 2014 15:02:04 -0700 Subject: [PATCH 11/16] World cleanup; viewer layer function deprecation --- src/viewer.js | 155 ++++++++++++++++++++++--------------------- src/world.js | 180 +++++++++++++++++++++++++++++++++----------------- 2 files changed, 200 insertions(+), 135 deletions(-) diff --git a/src/viewer.js b/src/viewer.js index d8c99b52..367aeb53 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -1032,59 +1032,54 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, }, /** - * Add a layer. + * Add a tiled image to the viewer. * options.tileSource can be anything that {@link OpenSeadragon.Viewer#open} - * supports except arrays of images as layers cannot be sequences. + * supports except arrays of images. * Note that you can specify options.width or options.height, but not both. - * The other dimension will be calculated according to the layer's aspect ratio. + * The other dimension will be calculated according to the item's aspect ratio. * @function * @param {Object} options - * @param {String|Object|Function} options.tileSource The TileSource of the layer. - * @param {Number} [options.opacity=1] The opacity of the layer. - * @param {Number} [options.level] The level of the layer. Added on top of - * all other layers if not specified. + * @param {String|Object|Function} options.tileSource - The TileSource of the item. + * @param {Number} [options.index] The index of the item. Added on top of + * all other items if not specified. * @param {Number} [options.x=0] The X position for the image in world coordinates. * @param {Number} [options.y=0] The Y position for the image in world coordinates. * @param {Number} [options.width=1] The width for the image in world coordinates. * @param {Number} [options.height] The height for the image in world coordinates. - * @returns {OpenSeadragon.Viewer} Chainable. - * @fires OpenSeadragon.Viewer.event:add-layer - * @fires OpenSeadragon.Viewer.event:add-layer-failed + * @fires OpenSeadragon.World.event:add-item + * @fires OpenSeadragon.Viewer.event:add-item-failed */ - addLayer: function( options ) { + addTiledImage: function( options ) { + $.console.assert(options, "[Viewer.addTiledImage] options is required"); + $.console.assert(options.tileSource, "[Viewer.addTiledImage] options.tileSource is required"); + var _this = this, tileSource = options.tileSource; if ( !this.isOpen() ) { - throw new Error( "An image must be loaded before adding layers." ); - } - if ( !tileSource ) { - throw new Error( "No tile source provided as new layer." ); - } - if ( this.collectionMode ) { - throw new Error( "Layers not supported in collection mode." ); + throw new Error( "An image must be loaded before adding additional images." ); } - function raiseAddLayerFailed( event ) { + function raiseAddItemFailed( event ) { /** - * Raised when an error occurs while adding a layer. - * @event add-layer-failed + * Raised when an error occurs while adding a item. + * @event add-item-failed * @memberOf OpenSeadragon.Viewer * @type {object} * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. * @property {String} message * @property {String} source - * @property {Object} options The options passed to the addLayer method. + * @property {Object} options The options passed to the addTiledImage method. * @property {?Object} userData - Arbitrary subscriber-defined object. */ - _this.raiseEvent( 'add-layer-failed', event ); + _this.raiseEvent( 'add-item-failed', event ); } getTileSourceImplementation( this, tileSource, function( tileSource ) { if ( tileSource instanceof Array ) { - raiseAddLayerFailed({ - message: "Sequences can not be added as layers.", + raiseAddItemFailed({ + message: "[Viewer.addTiledImage] Sequences can not be added.", source: tileSource, options: options }); @@ -1113,88 +1108,87 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, debugMode: _this.debugMode, debugGridColor: _this.debugGridColor }); - _this.world.addItem( tiledImage ); - _this.viewport.setHomeBounds(_this.world.getHomeBounds(), _this.world.getContentFactor()); - if ( options.level !== undefined ) { - _this.world.setItemLevel( tiledImage, options.level ); - } - THIS[ _this.hash ].forceRedraw = true; - /** - * Raised when a layer is successfully added. - * @event add-layer - * @memberOf OpenSeadragon.Viewer - * @type {object} - * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. - * @property {Object} options The options passed to the addLayer method. - * @property {OpenSeadragon.Drawer} drawer The layer's underlying drawer. - * @property {?Object} userData - Arbitrary subscriber-defined object. - */ - _this.raiseEvent( 'add-layer', { - options: options, - drawer: tiledImage + + _this.world.addItem( tiledImage, { + index: options.index }); }, function( event ) { event.options = options; - raiseAddLayerFailed(event); + raiseAddItemFailed(event); } ); + }, + /** + * @function + * @private + */ + addLayer: function( options ) { + var self = this; + + $.console.error( "[Viewer.addLayer] this function is deprecated; use Viewer.addTiledImage() instead." ); + + var addItemHandler = function(event) { + self.world.removeHandler("add-item", addItemHandler); + self.raiseEvent("add-layer", { + options: options, + drawer: event.item + }); + }; + + var failureHandler = function(event) { + self.removeHandler("add-item-failed", failureHandler); + self.raiseEvent("add-layer-failed", event); + }; + + this.world.addHandler("add-item", addItemHandler); + this.addHandler("add-item-failed", failureHandler); + this.addTiledImage(options); return this; }, /** - * Get the layer at the specified level. - * @param {Number} level The layer to retrieve level. - * @returns {OpenSeadragon.Drawer} The layer at the specified level. + * @function + * @private */ getLayerAtLevel: function( level ) { - $.console.error( "[Viewer.getLayerAtLevel] this function is deprecated." ); - return null; + $.console.error( "[Viewer.getLayerAtLevel] this function is deprecated; use World.getItemAt() instead." ); + return this.world.getItemAt(level); }, /** - * Get the level of the layer associated with the given drawer or -1 if not - * present. - * @param {OpenSeadragon.Drawer} drawer The underlying drawer of the layer. - * @returns {Number} The level of the layer or -1 if not present. + * @function + * @private */ getLevelOfLayer: function( drawer ) { - $.console.error( "[Viewer.getLevelOfLayer] this function is deprecated." ); - return -1; + $.console.error( "[Viewer.getLevelOfLayer] this function is deprecated; use World.getIndexOfItem() instead." ); + return this.world.getIndexOfItem(drawer); }, /** - * Get the number of layers used. - * @returns {Number} The number of layers used. + * @function + * @private */ getLayersCount: function() { - $.console.error( "[Viewer.getLayersCount] this function is deprecated." ); - return 0; + $.console.error( "[Viewer.getLayersCount] this function is deprecated; use World.getItemCount() instead." ); + return this.world.getItemCount(); }, /** - * Change the level of a layer so that it appears over or under others. - * @param {OpenSeadragon.Drawer} drawer The underlying drawer of the changing - * level layer. - * @param {Number} level The new level - * @returns {OpenSeadragon.Viewer} Chainable. - * @fires OpenSeadragon.Viewer.event:layer-level-changed + * @function + * @private */ setLayerLevel: function( drawer, level ) { - $.console.error( "[Viewer.setLayerLevel] this function is deprecated." ); - return this; + $.console.error( "[Viewer.setLayerLevel] this function is deprecated; use World.setItemIndex() instead." ); + return this.world.setItemIndex(drawer, level); }, /** - * Remove a layer. If there is only one layer, close the viewer. * @function - * @param {OpenSeadragon.Drawer} drawer The underlying drawer of the layer - * to remove - * @returns {OpenSeadragon.Viewer} Chainable. - * @fires OpenSeadragon.Viewer.event:remove-layer + * @private */ removeLayer: function( drawer ) { - $.console.error( "[Viewer.removeLayer] this function is deprecated." ); - return this; + $.console.error( "[Viewer.removeLayer] this function is deprecated; use World.removeItem() instead." ); + return this.world.removeItem(drawer); }, /** @@ -1896,6 +1890,16 @@ function openTileSource( viewer, source, options ) { viewer: _this }); + _this.world.addHandler('add-item', function(event) { + _this.viewport.setHomeBounds(_this.world.getHomeBounds(), _this.world.getContentFactor()); + THIS[ _this.hash ].forceRedraw = true; + }); + + _this.world.addHandler('remove-item', function(event) { + _this.viewport.setHomeBounds(_this.world.getHomeBounds(), _this.world.getContentFactor()); + THIS[ _this.hash ].forceRedraw = true; + }); + _this.drawer = new $.Drawer({ viewer: _this, viewport: _this.viewport, @@ -1929,7 +1933,6 @@ function openTileSource( viewer, source, options ) { }); _this.world.addItem( tiledImage ); - _this.viewport.setHomeBounds(_this.world.getHomeBounds(), _this.world.getContentFactor()); _this.viewport.goHome( true ); // Now that we have a drawer, see if it supports rotate. If not we need to remove the rotate buttons diff --git a/src/world.js b/src/world.js index 1b2245b3..a9299edb 100644 --- a/src/world.js +++ b/src/world.js @@ -35,41 +35,77 @@ (function( $ ){ /** + * Keeps track of all of the tiled images in the scene. + * * @class World * @classdesc - */ + * + * @memberof OpenSeadragon + * @extends OpenSeadragon.EventSource + * @param {OpenSeadragon.Options} options - World options. + **/ $.World = function( options ) { $.console.assert( options.viewer, "[World] options.viewer is required" ); + $.EventSource.call( this ); + this.viewer = options.viewer; this._items = []; this._figureSizes(); }; -$.World.prototype = /** @lends OpenSeadragon.World.prototype */{ - addItem: function( item ) { - this._items.push( item ); - this._figureSizes(); - }, - +$.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.World.prototype */{ /** - * Get the item at the specified level. - * @param {Number} level The item to retrieve level. - * @returns {OpenSeadragon.TiledImage} The item at the specified level. + * Add the specified item. + * @param {OpenSeadragon.TiledImage} item - The item to add. + * @param {Number} options.index - index for the item (optional). + * @fires OpenSeadragon.World.event:add-item */ - getItemAt: function( level ) { - if ( level >= this._items.length ) { - throw new Error( "Level bigger than number of items." ); + addItem: function( item, options ) { + $.console.assert(item, "[World.addItem] item is required"); + $.console.assert(item instanceof $.TiledImage, "[World.addItem] only TiledImages supported at this time"); + + options = options || {}; + if (options.index !== undefined) { + var index = Math.max(0, Math.min(this._items.length, options.index)); + this._items.splice(index, 0, item); + } else { + this._items.push( item ); } - return this._items[ level ]; + + this._figureSizes(); + + /** + * Raised when an item is added to the World. + * @event add-item + * @memberOf OpenSeadragon.World + * @type {object} + * @property {OpenSeadragon.Viewer} eventSource - A reference to the World which raised the event. + * @property {OpenSeadragon.Drawer} item - The item that has been added + * @property {?Object} userData - Arbitrary subscriber-defined object. + */ + this.raiseEvent( 'add-item', { + item: item + } ); }, /** - * Get the level of the given item or -1 if not present. - * @param {OpenSeadragon.TiledImage} item The item. - * @returns {Number} The level of the item or -1 if not present. + * Get the item at the specified index. + * @param {Number} index - The item's index. + * @returns {OpenSeadragon.TiledImage} The item at the specified index. */ - getLevelOfItem: function( item ) { + getItemAt: function( index ) { + $.console.assert(index !== 'undefined', "[World.getItemAt] index is required"); + return this._items[ index ]; + }, + + /** + * Get the index of the given item or -1 if not present. + * @param {OpenSeadragon.TiledImage} item - The item. + * @returns {Number} The index of the item or -1 if not present. + */ + getIndexOfItem: function( item ) { + $.console.assert(item, "[World.getIndexOfItem] item is required"); return $.indexOf( this._items, item ); }, @@ -82,52 +118,56 @@ $.World.prototype = /** @lends OpenSeadragon.World.prototype */{ }, /** - * Change the level of a layer so that it appears over or under others. - * @param {OpenSeadragon.Drawer} drawer The underlying drawer of the changing - * level layer. - * @param {Number} level The new level - * @fires OpenSeadragon.Viewer.event:layer-level-changed + * Change the index of a item so that it appears over or under others. + * @param {OpenSeadragon.TiledImage} item - The item to move. + * @param {Number} index - The new index. + * @fires OpenSeadragon.World.event:item-index-changed */ - setItemLevel: function( item, level ) { - var oldLevel = this.getLevelOfItem( item ); + setItemIndex: function( item, index ) { + $.console.assert(item, "[World.setItemIndex] item is required"); + $.console.assert(index !== 'undefined', "[World.setItemIndex] index is required"); - if ( level >= this._items.length ) { - throw new Error( "Level bigger than number of layers." ); + var oldIndex = this.getIndexOfItem( item ); + + if ( index >= this._items.length ) { + throw new Error( "Index bigger than number of layers." ); } - if ( level === oldLevel || oldLevel === -1 ) { + + if ( index === oldIndex || oldIndex === -1 ) { return; } - this._items.splice( oldLevel, 1 ); - this._items.splice( level, 0, item ); + + this._items.splice( oldIndex, 1 ); + this._items.splice( index, 0, item ); /** - * Raised when the order of the layers has been changed. - * @event layer-level-changed - * @memberOf OpenSeadragon.Viewer + * Raised when the order of the indexes has been changed. + * @event item-index-changed + * @memberOf OpenSeadragon.World * @type {object} - * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. - * @property {OpenSeadragon.Drawer} drawer - The drawer which level has + * @property {OpenSeadragon.World} eventSource - A reference to the World which raised the event. + * @property {OpenSeadragon.TiledImage} item - The item whose index has * been changed - * @property {Number} previousLevel - The previous level of the drawer - * @property {Number} newLevel - The new level of the drawer + * @property {Number} previousIndex - The previous index of the item + * @property {Number} newIndex - The new index of the item * @property {?Object} userData - Arbitrary subscriber-defined object. */ - // TODO: deprecate - this.viewer.raiseEvent( 'layer-level-changed', { - drawer: item, - previousLevel: oldLevel, - newLevel: level + this.raiseEvent( 'item-index-changed', { + item: item, + previousIndex: oldIndex, + newIndex: index } ); }, /** - * Remove a layer. If there is only one layer, close the viewer. + * Remove an item. * @function - * @param {OpenSeadragon.Drawer} drawer The underlying drawer of the layer - * to remove - * @fires OpenSeadragon.Viewer.event:remove-layer + * @param {OpenSeadragon.TiledImage} item - The item to remove. + * @fires OpenSeadragon.World.event:remove-item */ removeItem: function( item ) { + $.console.assert(item, "[World.removeItem] item is required"); + var index = this._items.indexOf( item ); if ( index === -1 ) { return; @@ -137,30 +177,41 @@ $.World.prototype = /** @lends OpenSeadragon.World.prototype */{ this._figureSizes(); /** - * Raised when a layer is removed. - * @event remove-layer - * @memberOf OpenSeadragon.Viewer + * Raised when a item is removed. + * @event remove-item + * @memberOf OpenSeadragon.World * @type {object} - * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. - * @property {OpenSeadragon.Drawer} drawer The layer's underlying drawer. + * @property {OpenSeadragon.World} eventSource - A reference to the World which raised the event. + * @property {OpenSeadragon.TiledImage} item - The item's underlying item. * @property {?Object} userData - Arbitrary subscriber-defined object. */ - // TODO: deprecate - this.raiseEvent( 'remove-layer', { drawer: item } ); + this.raiseEvent( 'remove-item', { item: item } ); }, - resetTiles: function() { + /** + * Clears all tiles and triggers updates for all items. + * @function + */ + resetItems: function() { for ( var i = 0; i < this._items.length; i++ ) { this._items[i].reset(); } }, + /** + * Updates (i.e. draws) all items. + * @function + */ update: function() { for ( var i = 0; i < this._items.length; i++ ) { this._items[i].update(); } }, + /** + * @function + * @returns {Boolean} true if any items need updating. + */ needsUpdate: function() { for ( var i = 0; i < this._items.length; i++ ) { if ( this._items[i].needsUpdate() ) { @@ -170,18 +221,29 @@ $.World.prototype = /** @lends OpenSeadragon.World.prototype */{ return false; }, + /** + * @function + * @returns {OpenSeadragon.Rect} the smallest rectangle that encloses all items, in world coordinates. + */ getHomeBounds: function() { return this._homeBounds.clone(); }, - getContentSize: function() { - return this._contentSize.clone(); - }, - + /** + * To facilitate zoom constraints, we keep track of the pixel density of the + * densest item in the World (i.e. the item whose content size to world size + * ratio is the highest) and save it as this "content factor". + * @function + * @returns {Number} the number of content units per world unit. + */ getContentFactor: function() { return this._contentFactor; }, + /** + * @function + * @private + */ _figureSizes: function() { if ( !this._items.length ) { this._homeBounds = new $.Rect(0, 0, 1, 1); @@ -209,6 +271,6 @@ $.World.prototype = /** @lends OpenSeadragon.World.prototype */{ this._contentSize = new $.Point(this._homeBounds.width * this._contentFactor, this._homeBounds.height * this._contentFactor); } -}; +}); }( OpenSeadragon )); From 98111212cddc98ac75ca1b431d5211e91b768b2f Mon Sep 17 00:00:00 2001 From: Ian Gilman Date: Tue, 19 Aug 2014 15:02:35 -0700 Subject: [PATCH 12/16] Converted layer tests to multi-image tests --- test/layers.js | 271 ++++++++++++++----------------------------------- 1 file changed, 75 insertions(+), 196 deletions(-) diff --git a/test/layers.js b/test/layers.js index a20c92d0..5559f110 100644 --- a/test/layers.js +++ b/test/layers.js @@ -1,16 +1,16 @@ -/* global module, asyncTest, $, ok, equal, notEqual, start, test, Util, testLog */ +/* global module, asyncTest, $, ok, equal, notEqual, start, test, Util, testLog, expect */ ( function() { var viewer; - module( 'Layers', { + module( 'Multi-Image', { setup: function() { - $( '
' ).appendTo( "#qunit-fixture" ); + $( '
' ).appendTo( "#qunit-fixture" ); testLog.reset(); viewer = OpenSeadragon( { - id: 'layersexample', + id: 'itemsexample', prefixUrl: '/build/openseadragon/images/', springStiffness: 100 // Faster animation = faster tests }); @@ -21,16 +21,16 @@ } viewer = null; - $( "#layersexample" ).remove(); + $( "#itemsexample" ).remove(); } } ); // ---------- - asyncTest( 'Layers operations', function() { - expect( 23 ); + asyncTest( 'Multi-image operations', function() { + expect( 21 ); viewer.addHandler( "open", function( ) { - equal( 1, viewer.getLayersCount( ), - "One layer should be present after opening." ); + equal( 1, viewer.world.getItemCount( ), + "One item should be present after opening." ); var options = { tileSource: { type: 'legacy-image-pyramid', @@ -41,87 +41,76 @@ } ] } }; - viewer.addLayer( options ); - viewer.addHandler( "add-layer", function addFirstLayerHandler( event ) { - viewer.removeHandler( "add-layer", addFirstLayerHandler ); - var layer1 = event.drawer; - equal( viewer.getLayersCount( ), 2, - "2 layers should be present after adding a layer." ); - equal( options, event.options, - "The options should be transmitted via the event." ); - equal( viewer.getLevelOfLayer( layer1 ), 1, - "The first added layer should have a level of 1" ); - equal( viewer.getLayerAtLevel( 1 ), layer1, - "The layer at level 1 should be the first added layer." ); + viewer.addTiledImage( options ); + viewer.world.addHandler( "add-item", function addFirstItemHandler( event ) { + viewer.world.removeHandler( "add-item", addFirstItemHandler ); + var item1 = event.item; + equal( viewer.world.getItemCount( ), 2, + "2 items should be present after adding a item." ); + equal( viewer.world.getIndexOfItem( item1 ), 1, + "The first added item should have a index of 1" ); + equal( viewer.world.getItemAt( 1 ), item1, + "The item at index 1 should be the first added item." ); - viewer.addLayer( options ); - viewer.addHandler( "add-layer", function addSecondLayerHandler( event ) { - viewer.removeHandler( "add-layer", addSecondLayerHandler ); - var layer2 = event.drawer; - equal( viewer.getLayersCount( ), 3, - "3 layers should be present after adding a second layer." ); - equal( viewer.getLevelOfLayer( layer2 ), 2, - "If not specified, a layer should be added with the highest level." ); - equal( viewer.getLayerAtLevel( 2 ), layer2, - "The layer at level 2 should be the second added layer." ); + viewer.addTiledImage( options ); + viewer.world.addHandler( "add-item", function addSecondItemHandler( event ) { + viewer.world.removeHandler( "add-item", addSecondItemHandler ); + var item2 = event.item; + equal( viewer.world.getItemCount( ), 3, + "3 items should be present after adding a second item." ); + equal( viewer.world.getIndexOfItem( item2 ), 2, + "If not specified, a item should be added with the highest index." ); + equal( viewer.world.getItemAt( 2 ), item2, + "The item at index 2 should be the second added item." ); - viewer.addHandler( "layer-level-changed", - function layerLevelChangedHandler( event ) { - viewer.removeHandler( "layer-level-changed", - layerLevelChangedHandler ); - equal( event.drawer, layer2, - "The layer which changed level should be layer2" ); - equal( event.previousLevel, 2, "Previous level should be 2." ); - equal( event.newLevel, 1, "New level should be 1." ); + viewer.world.addHandler( "item-index-changed", + function itemIndexChangedHandler( event ) { + viewer.world.removeHandler( "item-index-changed", + itemIndexChangedHandler ); + equal( event.item, item2, + "The item which changed index should be item2" ); + equal( event.previousIndex, 2, "Previous index should be 2." ); + equal( event.newIndex, 1, "New index should be 1." ); }); - viewer.setLayerLevel( layer2, 1 ); - equal( viewer.getLevelOfLayer( layer2 ), 1, - "Layer2 level should be 1 after setLayerLevel." ); - equal( viewer.getLevelOfLayer( layer1 ), 2, - "Layer1 level should be 2 after setLayerLevel." ); - equal( viewer.getLayerAtLevel( 1 ), layer2, - "The layer at level 1 should be layer2." ); - equal( viewer.getLayerAtLevel( 2 ), layer1, - "The layer at level 2 should be layer1." ); + viewer.world.setItemIndex( item2, 1 ); + equal( viewer.world.getIndexOfItem( item2 ), 1, + "Item2 index should be 1 after setItemIndex." ); + equal( viewer.world.getIndexOfItem( item1 ), 2, + "Item1 index should be 2 after setItemIndex." ); + equal( viewer.world.getItemAt( 1 ), item2, + "The item at index 1 should be item2." ); + equal( viewer.world.getItemAt( 2 ), item1, + "The item at index 2 should be item1." ); - options.level = 2; + options.index = 2; options.tileSource.levels[0].url = "data/CCyan.png"; - options.opacity = 0.5; - viewer.addLayer( options ); - viewer.addHandler( "add-layer", function addThirdLayerHandler( event ) { - viewer.removeHandler( "add-layer", addThirdLayerHandler ); - var layer3 = event.drawer; - equal( viewer.getLayersCount( ), 4, - "4 layers should be present after adding a third layer." ); - equal( viewer.getLevelOfLayer( layer3 ), 2, - "Layer 3 should be added with level 2." ); - equal( viewer.getLevelOfLayer( layer2 ), 1, - "Layer 2 should stay at level 1." ); + viewer.addTiledImage( options ); + viewer.world.addHandler( "add-item", function addThirdItemHandler( event ) { + viewer.world.removeHandler( "add-item", addThirdItemHandler ); + var item3 = event.item; + equal( viewer.world.getItemCount( ), 4, + "4 items should be present after adding a third item." ); + equal( viewer.world.getIndexOfItem( item3 ), 2, + "Item 3 should be added with index 2." ); + equal( viewer.world.getIndexOfItem( item2 ), 1, + "Item 2 should stay at index 1." ); - viewer.addHandler( "remove-layer", function removeLayerHandler( event ) { - viewer.removeHandler( "remove-layer", removeLayerHandler ); + viewer.world.addHandler( "remove-item", function removeItemHandler( event ) { + viewer.world.removeHandler( "remove-item", removeItemHandler ); - equal( layer2, event.drawer, "Removed layer should be layer2." ); + equal( item2, event.item, "Removed item should be item2." ); - equal( viewer.getLevelOfLayer( layer1 ), 2, - "Layer 1 should be at level 2." ); - equal( viewer.getLevelOfLayer( layer2 ), -1, - "Layer 2 should be at level -1." ); - equal( viewer.getLevelOfLayer( layer3 ), 1, - "Layer 3 should be at level 1." ); - - }); - viewer.removeLayer( layer2 ); - - options.tileSource.levels[0].width = 500; - viewer.addHandler( "add-layer-failed", function addLayerFailed( event ) { - viewer.removeHandler( "add-layer-failed", addLayerFailed ); - - equal( viewer.getLayersCount(), 3 ); + equal( viewer.world.getIndexOfItem( item1 ), 2, + "Item 1 should be at index 2." ); + equal( viewer.world.getIndexOfItem( item2 ), -1, + "Item 2 should be at index -1." ); + equal( viewer.world.getIndexOfItem( item3 ), 1, + "Item 3 should be at index 1." ); start(); }); - viewer.addLayer( options ); + + viewer.world.removeItem( item2 ); }); }); }); @@ -129,7 +118,7 @@ viewer.open( '/test/data/testpattern.dzi' ); }); - asyncTest( 'Sequences as layers', function() { + asyncTest( 'Sequences as items', function() { var options = { tileSource: [{ @@ -152,127 +141,17 @@ viewer.addHandler( "open", function openHandler() { viewer.removeHandler( "open", openHandler ); - viewer.addHandler( "add-layer-failed", - function addLayerFailedHandler( event ) { - viewer.removeHandler( "add-layer-failed", addLayerFailedHandler ); - equal( event.message, "Sequences can not be added as layers." ); - equal( event.options, options, "Layer failed event should give the options." ); + viewer.addHandler( "add-item-failed", + function addItemFailedHandler( event ) { + viewer.removeHandler( "add-item-failed", addItemFailedHandler ); + equal( event.message, "[Viewer.addTiledImage] Sequences can not be added." ); + equal( event.options, options, "Item failed event should give the options." ); start(); } ); - viewer.addLayer( options ); + viewer.addTiledImage( options ); }); viewer.open( '/test/data/testpattern.dzi' ); }); - - asyncTest( 'Reassign base layer', function() { - - var options = { - tileSource: { - type: 'legacy-image-pyramid', - levels: [{ - url: "data/A.png", - width: 1000, - height: 1000 - }] - }, - level: 0 - }; - viewer.addHandler( "open", function openHandler( ) { - viewer.removeHandler( "open", openHandler ); - var testPatternDrawer = viewer.drawer; - equal( viewer.drawer, testPatternDrawer, "Viewer.drawer should be set to testPatternDrawer." ); - viewer.addHandler( "add-layer", function addLayerHandler( event ) { - viewer.removeHandler( "add-layer", addLayerHandler ); - var aDrawer = event.drawer; - equal( viewer.drawer, aDrawer, "Viewer.drawer should be set to aDrawer." ); - viewer.setLayerLevel( aDrawer, 1 ); - equal( viewer.drawer, testPatternDrawer, "Viewer.drawer should be set back to testPatternDrawer." ); - - viewer.removeLayer( viewer.drawer ); - equal( viewer.drawer, aDrawer, "Viewer.drawer must be reassigned when removing base layer." ); - - viewer.removeLayer( viewer.drawer ); - ok( !viewer.isOpen(), "Viewer should be closed when removing last layer." ); - - start(); - }); - viewer.addLayer( options ); - }); - viewer.open( '/test/data/testpattern.dzi' ); - }); - - asyncTest( 'Layers and sequences', function() { - expect( 1 ); - -// TODO: Remove workaround when issue #321 is fixed. -// https://github.com/openseadragon/openseadragon/issues/321 -// viewer.open( [{ -// type: 'legacy-image-pyramid', -// levels: [ { -// url: "data/A.png", -// width: 1000, -// height: 1000 -// }] -// }, -// { -// type: 'legacy-image-pyramid', -// levels: [ { -// url: "data/BBlue.png", -// width: 1000, -// height: 1000 -// }]}] ); - - viewer.close(); - viewer = OpenSeadragon({ - id: 'layersexample', - prefixUrl: '/build/openseadragon/images/', - springStiffness: 100, // Faster animation = faster tests - tileSources: [{ - type: 'legacy-image-pyramid', - levels: [{ - url: "data/A.png", - width: 1000, - height: 1000 - }] - }, - { - type: 'legacy-image-pyramid', - levels: [{ - url: "data/BBlue.png", - width: 1000, - height: 1000 - }] - }] - }); -// End workaround - - var options = { - tileSource: { - type: 'legacy-image-pyramid', - levels: [{ - url: "data/CCyan.png", - width: 1000, - height: 1000 - }] - } - }; - - viewer.addHandler( "open", function openHandler() { - viewer.addHandler( "add-layer", function addLayerHandler( event ) { - viewer.removeHandler( "add-layer", addLayerHandler ); - - var layer = event.drawer; - try { - viewer.setLayerLevel( layer, 0 ); - } catch (e) { - ok( true ); - } - start(); - } ); - viewer.addLayer( options ); - }); - - }); })(); From b2e2b2b125eb46a53453db1a2ebe0769f4ed14ac Mon Sep 17 00:00:00 2001 From: Ian Gilman Date: Tue, 19 Aug 2014 15:03:45 -0700 Subject: [PATCH 13/16] Renamed layers.js to multi-image.js --- test/{layers.js => multi-image.js} | 0 test/test.html | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename test/{layers.js => multi-image.js} (100%) diff --git a/test/layers.js b/test/multi-image.js similarity index 100% rename from test/layers.js rename to test/multi-image.js diff --git a/test/test.html b/test/test.html index 227dba36..8743f133 100644 --- a/test/test.html +++ b/test/test.html @@ -28,7 +28,7 @@ - + From 08a38a8602ef04018a3d72b0c6a405a8ceabbfe7 Mon Sep 17 00:00:00 2001 From: Ian Gilman Date: Tue, 19 Aug 2014 15:27:04 -0700 Subject: [PATCH 14/16] More test fixes. --- test/controls.js | 7 ++++++- test/demo/fitboundswithconstraints.html | 2 +- test/overlays.js | 4 ++-- test/test.html | 4 +++- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/test/controls.js b/test/controls.js index a2d7d998..44c917cd 100644 --- a/test/controls.js +++ b/test/controls.js @@ -5,7 +5,12 @@ module('Controls', { setup: function () { - var example = $('
').appendTo("#qunit-fixture"); + var example = $('
') + .css({ + width: 1000, + height: 1000 + }) + .appendTo("#qunit-fixture"); testLog.reset(); diff --git a/test/demo/fitboundswithconstraints.html b/test/demo/fitboundswithconstraints.html index d0c1f460..51889d88 100644 --- a/test/demo/fitboundswithconstraints.html +++ b/test/demo/fitboundswithconstraints.html @@ -105,7 +105,7 @@ } function goHome() { - _viewer.goHome(); + _viewer.viewport.goHome(); } diff --git a/test/overlays.js b/test/overlays.js index c90c669f..223f99de 100644 --- a/test/overlays.js +++ b/test/overlays.js @@ -270,7 +270,7 @@ waitForViewer( function() { checkOverlayPosition( "after zoom and pan using image coordinates" ); - viewer.goHome(); + viewer.viewport.goHome(); waitForViewer( function() { checkOverlayPosition( "after goHome using image coordinates" ); start(); @@ -333,7 +333,7 @@ waitForViewer( function() { checkOverlayPosition( "after zoom and pan using viewport coordinates" ); - viewer.goHome(); + viewer.viewport.goHome(); waitForViewer( function() { checkOverlayPosition( "after goHome using viewport coordinates" ); start(); diff --git a/test/test.html b/test/test.html index 8743f133..76abbd60 100644 --- a/test/test.html +++ b/test/test.html @@ -22,7 +22,6 @@ reassignments which could be done by other test. --> - @@ -31,5 +30,8 @@ + + From 1f13d1f90995e21a5b647dcc5563ba38571e6690 Mon Sep 17 00:00:00 2001 From: Ian Gilman Date: Tue, 19 Aug 2014 15:43:36 -0700 Subject: [PATCH 15/16] Moved update-viewport event --- src/tiledimage.js | 13 ------------- src/viewer.js | 11 +++++++++++ src/viewport.js | 2 +- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/tiledimage.js b/src/tiledimage.js index 31b30a0f..4a19278c 100644 --- a/src/tiledimage.js +++ b/src/tiledimage.js @@ -173,19 +173,6 @@ function updateViewport( tiledImage ) { tiledImage.updateAgain = false; - if( tiledImage.viewer ){ - /** - * - Needs documentation - - * - * @event update-viewport - * @memberof OpenSeadragon.Viewer - * @type {object} - * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. - * @property {?Object} userData - Arbitrary subscriber-defined object. - */ - tiledImage.viewer.raiseEvent( 'update-viewport', {} ); - } - var tile, level, best = null, diff --git a/src/viewer.js b/src/viewer.js index 367aeb53..45714ac2 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -2715,6 +2715,17 @@ function resizeViewportAndRecenter( viewer, containerSize, oldBounds, oldCenter function updateWorld( viewer ) { viewer.drawer.clear(); viewer.world.update(); + + /** + * - Needs documentation - + * + * @event update-viewport + * @memberof OpenSeadragon.Viewer + * @type {object} + * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. + * @property {?Object} userData - Arbitrary subscriber-defined object. + */ + viewer.raiseEvent( 'update-viewport', {} ); } /////////////////////////////////////////////////////////////////////////////// diff --git a/src/viewport.js b/src/viewport.js index 6a4c4156..9ad8dab3 100644 --- a/src/viewport.js +++ b/src/viewport.js @@ -768,7 +768,7 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{ /** * Raised when rotation has been changed. * - * @event update-viewport + * @event rotate * @memberof OpenSeadragon.Viewer * @type {object} * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. From 7b80263718187dbd7400b21d637aaa32d5a8804c Mon Sep 17 00:00:00 2001 From: Ian Gilman Date: Thu, 21 Aug 2014 10:11:21 -0700 Subject: [PATCH 16/16] Reinstating a small check. --- src/viewer.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/viewer.js b/src/viewer.js index 45714ac2..93c8394b 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -2810,7 +2810,9 @@ function lightUp() { function onHome() { - this.viewport.goHome(); + if ( this.viewport ) { + this.viewport.goHome(); + } }