diff --git a/src/openseadragon.js b/src/openseadragon.js index 278f61c8..2cb2ac0d 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -290,6 +290,12 @@ * @property {Number} [rotationIncrement=90] * The number of degrees to rotate right or left when the rotate buttons or keyboard shortcuts are activated. * + * @property {Number} [maxTilesPerFrame=1] + * The number of tiles loaded per frame. As the frame rate of the client's machine is usually high (e.g., 50 fps), + * one tile per frame should be a good choice. However, for large screens or lower frame rates, the number of + * loaded tiles per frame can be adjusted here. Reasonable values might be 2 or 3 tiles per frame. + * (Note that the actual frame rate is given by the client's browser and machine). + * * @property {Number} [pixelsPerWheelLine=40] * For pixel-resolution scrolling devices, the number of pixels equal to one scroll line. * @@ -1288,6 +1294,7 @@ function OpenSeadragon( options ){ preserveImageSizeOnResize: false, // requires autoResize=true minScrollDeltaTime: 50, rotationIncrement: 90, + maxTilesPerFrame: 1, //DEFAULT CONTROL SETTINGS showSequenceControl: true, //SEQUENCE diff --git a/src/tiledimage.js b/src/tiledimage.js index 6ad0cf66..0bedb701 100644 --- a/src/tiledimage.js +++ b/src/tiledimage.js @@ -182,7 +182,8 @@ $.TiledImage = function( options ) { opacity: $.DEFAULT_SETTINGS.opacity, preload: $.DEFAULT_SETTINGS.preload, compositeOperation: $.DEFAULT_SETTINGS.compositeOperation, - subPixelRoundingForTransparency: $.DEFAULT_SETTINGS.subPixelRoundingForTransparency + subPixelRoundingForTransparency: $.DEFAULT_SETTINGS.subPixelRoundingForTransparency, + maxTilesPerFrame: $.DEFAULT_SETTINGS.maxTilesPerFrame }, options ); this._preload = this.preload; @@ -1208,7 +1209,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag var levelsInterval = this._getLevelsInterval(); var lowestLevel = levelsInterval.lowestLevel; var highestLevel = levelsInterval.highestLevel; - var bestTile = null; + var bestTiles = []; var haveDrawn = false; var currentTime = $.now(); @@ -1253,7 +1254,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag ); // Update the level and keep track of 'best' tile to load - bestTile = this._updateLevel( + bestTiles = this._updateLevel( haveDrawn, drawLevel, level, @@ -1261,7 +1262,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag levelVisibility, drawArea, currentTime, - bestTile + bestTiles ); // Stop the loop if lower-res tiles would all be covered by @@ -1274,9 +1275,13 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag // Perform the actual drawing this._drawTiles(this.lastDrawn); - // Load the new 'best' tile - if (bestTile && !bestTile.context2D) { - this._loadTile(bestTile, currentTime); + // Load the new 'best' n tiles + if (bestTiles && bestTiles.length > 0) { + bestTiles.forEach(function (tile) { + if (tile && !tile.context2D) { + this._loadTile(tile, currentTime); + } + }, this); this._needsDraw = true; this._setFullyLoaded(false); } else { @@ -1335,7 +1340,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag * @param {Number} levelVisibility * @param {OpenSeadragon.Rect} drawArea * @param {Number} currentTime - * @param {OpenSeadragon.Tile} best - The current "best" tile to draw. + * @param {OpenSeadragon.Tile[]} best - The current "best" n tiles to draw. */ _updateLevel: function(haveDrawn, drawLevel, level, levelOpacity, levelVisibility, drawArea, currentTime, best) { @@ -1360,7 +1365,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag * @property {Object} topleft deprecated, use drawArea instead * @property {Object} bottomright deprecated, use drawArea instead * @property {Object} currenttime - * @property {Object} best + * @property {Object[]} best * @property {?Object} userData - Arbitrary subscriber-defined object. */ this.viewer.raiseEvent('update-level', { @@ -1449,7 +1454,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag * @param {OpenSeadragon.Point} viewportCenter * @param {Number} numberOfTiles * @param {Number} currentTime - * @param {OpenSeadragon.Tile} best - The current "best" tile to draw. + * @param {OpenSeadragon.Tile[]} best - The current "best" tiles to draw. */ _updateTile: function( haveDrawn, drawLevel, x, y, level, levelOpacity, levelVisibility, viewportCenter, numberOfTiles, currentTime, best){ @@ -1538,7 +1543,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag // the tile is already in the download queue this._tilesLoading++; } else if (!loadingCoverage) { - best = this._compareTiles( best, tile ); + best = this._compareTiles( best, tile, this.maxTilesPerFrame ); } return best; @@ -1912,28 +1917,49 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag /** * @private * @inner - * Determines whether the 'last best' tile for the area is better than the + * Determines the 'best tiles' from the given 'last best' tiles and the * tile in question. * - * @param {OpenSeadragon.Tile} previousBest - * @param {OpenSeadragon.Tile} tile - * @returns {OpenSeadragon.Tile} The new best tile. + * @param {OpenSeadragon.Tile[]} previousBest The best tiles so far. + * @param {OpenSeadragon.Tile} tile The new tile to consider. + * @param {Number} maxNTiles The max number of best tiles. + * @returns {OpenSeadragon.Tile[]} The new best tiles. */ - _compareTiles: function( previousBest, tile ) { + _compareTiles: function( previousBest, tile, maxNTiles ) { if ( !previousBest ) { - return tile; + return [tile]; } - - if ( tile.visibility > previousBest.visibility ) { - return tile; - } else if ( tile.visibility === previousBest.visibility ) { - if ( tile.squaredDistance < previousBest.squaredDistance ) { - return tile; - } + previousBest.push(tile); + this._sortTiles(previousBest); + if (previousBest.length > maxNTiles) { + previousBest.pop(); } return previousBest; }, + /** + * @private + * @inner + * Sorts tiles in an array according to distance and visibility. + * + * @param {OpenSeadragon.Tile[]} tiles The tiles. + */ + _sortTiles: function( tiles ) { + tiles.sort(function (a, b) { + if (a === null) { + return 1; + } + if (b === null) { + return -1; + } + if (a.visibility === b.visibility) { + return (a.squaredDistance - b.squaredDistance); + } else { + return (a.visibility - b.visibility); + } + }); + }, + /** * @private * @inner diff --git a/src/viewer.js b/src/viewer.js index 0a25238c..d82a2d77 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -1604,6 +1604,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, minZoomImageRatio: _this.minZoomImageRatio, wrapHorizontal: _this.wrapHorizontal, wrapVertical: _this.wrapVertical, + maxTilesPerFrame: _this.maxTilesPerFrame, immediateRender: _this.immediateRender, blendTime: _this.blendTime, alwaysBlend: _this.alwaysBlend, diff --git a/test/demo/MaxNTilesPerFrame.html b/test/demo/MaxNTilesPerFrame.html new file mode 100644 index 00000000..f24a8b7f --- /dev/null +++ b/test/demo/MaxNTilesPerFrame.html @@ -0,0 +1,35 @@ + + + + OpenSeadragon Zoomify Demo + + + + + +
+ Simple demo page to show a default OpenSeadragon viewer with a Zoomify tile source. +
+
+ + +