From 68126a82dcc8db74ea233c4184e30498330170a4 Mon Sep 17 00:00:00 2001 From: Jirka Date: Wed, 23 Mar 2022 10:22:13 +0100 Subject: [PATCH 1/6] Update docs on POST data to reflect more accurately the behaviour. Fix bug & implement propositions ~ issue #2094. --- src/imageloader.js | 8 +++++--- src/mousetracker.js | 9 ++++----- src/openseadragon.js | 22 ++++++++++++++-------- src/tile.js | 12 +++++++----- src/tilesource.js | 24 +++++++++++++++++++----- 5 files changed, 49 insertions(+), 26 deletions(-) diff --git a/src/imageloader.js b/src/imageloader.js index a120433a..198882b6 100644 --- a/src/imageloader.js +++ b/src/imageloader.js @@ -43,7 +43,8 @@ * @param {String} [options.loadWithAjax] - Whether to load this image with AJAX. * @param {String} [options.ajaxHeaders] - Headers to add to the image request if using AJAX. * @param {String} [options.crossOriginPolicy] - CORS policy to use for downloads - * @param {String} [options.postData] - HTTP POST data in k=v&k2=v2... form or null + * @param {String} [options.postData] - HTTP POST data (usually but not necessarily in k=v&k2=v2... form, + * see TileSrouce::getPostData) or null * @param {Function} [options.callback] - Called once image has been downloaded. * @param {Function} [options.abort] - Called when this image job is aborted. * @param {Number} [options.timeout] - The max number of milliseconds that this image job may take to complete. @@ -198,9 +199,10 @@ $.ImageLoader.prototype = { * @param {String} [options.loadWithAjax] - Whether to load this image with AJAX. * @param {String} [options.ajaxHeaders] - Headers to add to the image request if using AJAX. * @param {String|Boolean} [options.crossOriginPolicy] - CORS policy to use for downloads - * @param {String} [options.postData] - POST parameters in k=v&k2=v2... form or null + * @param {String} [options.postData] - POST parameters (usually but not necessarily in k=v&k2=v2... form, + * see TileSrouce::getPostData) or null * @param {Boolean} [options.ajaxWithCredentials] - Whether to set withCredentials on AJAX - * requests. + * requests. * @param {Function} [options.callback] - Called once image has been downloaded. * @param {Function} [options.abort] - Called when this image job is aborted. */ diff --git a/src/mousetracker.js b/src/mousetracker.js index 8c811326..cf796b6a 100644 --- a/src/mousetracker.js +++ b/src/mousetracker.js @@ -3286,13 +3286,12 @@ gPoint = updateGPoint; } else { // Initialize for tracking and add to the tracking list (no pointerenter event occurred before this) - $.console.warn('pointerdown event on untracked pointer'); + // NOTE: pointerdown event on untracked pointer gPoint.captured = false; // Handled by updatePointerCaptured() gPoint.insideElementPressed = true; gPoint.insideElement = true; gPoint.originalTarget = eventInfo.originalEvent.target; startTrackingPointer( pointsList, gPoint ); - return; } pointsList.addContact(); @@ -3436,8 +3435,8 @@ releasePoint = updateGPoint.currentPos; releaseTime = updateGPoint.currentTime; } else { - // should never get here...we'll start to track pointer anyway - $.console.warn('updatePointerUp(): pointerup on untracked gPoint'); + // NOTE: updatePointerUp(): pointerup on untracked gPoint + // ...we'll start to track pointer again gPoint.captured = false; // Handled by updatePointerCaptured() gPoint.insideElementPressed = false; gPoint.insideElement = true; @@ -3460,7 +3459,7 @@ if ( pointsList.contacts === 0 ) { // Release (pressed in our element) - if ( tracker.releaseHandler ) { + if ( tracker.releaseHandler && releasePoint ) { tracker.releaseHandler( { eventSource: tracker, diff --git a/src/openseadragon.js b/src/openseadragon.js index ef002f37..731a06b4 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -665,13 +665,18 @@ * * @property {Boolean} [splitHashDataForPost=false] * Allows to treat _first_ hash ('#') symbol as a separator for POST data: - * URL to be opened by a {@link OpenSeadragon.TileSource} can thus look like: http://some.url#postdata=here . - * The URL is split to 'http://some.url' and 'postdata=here'; post data is given to the - * {@link OpenSeadragon.TileSource} of the choice and can be further used within tile requests - * (see TileSource methods). {@link OpenSeadragon.TileSource.prototype.configure} return value - * should contain the post data so that it is given to its subclass in the constructor. - * NOTE: post data is expected to be ampersand-separated (just like GET parameters), and is not used - * to fetch tile image data if loadTilesWithAjax=false (but it is still used for the initial request). + * URL to be opened by a {@link OpenSeadragon.TileSource} can thus look like: http://some.url#postdata=here. + * The whole URL is used to fetch image info metadata and it is then split to 'http://some.url' and + * 'postdata=here'; post data is given to the {@link OpenSeadragon.TileSource} of the choice and can be further + * used within tile requests (see TileSource methods). + * NOTE: {@link OpenSeadragon.TileSource.prototype.configure} return value should contain the post data + * if you want to use it later - so that it is given to your constructor later. + * NOTE: usually, post data is expected to be ampersand-separated (just like GET parameters), and is NOT USED + * to fetch tile image data unless explicitly programmed, or if loadTilesWithAjax=false 4 + * (but it is still used for the initial image info request). + * NOTE: passing POST data from URL by this feature only supports string values, however, + * TileSource can send any data using POST as long as the header is correct + * (@see OpenSeadragon.TileSource.prototype.getTilePostData) */ /** @@ -2315,7 +2320,8 @@ function OpenSeadragon( options ){ * @param {Function} options.error - a function to call on when an error occurs * @param {Object} options.headers - headers to add to the AJAX request * @param {String} options.responseType - the response type of the the AJAX request - * @param {String} options.postData - HTTP POST data in k=v&k2=v2... form, GET method used if null + * @param {String} options.postData - HTTP POST data (usually but not necessarily in k=v&k2=v2... form, + * see TileSrouce::getPostData), GET method used if null * @param {Boolean} [options.withCredentials=false] - whether to set the XHR's withCredentials * @throws {Error} * @returns {XMLHttpRequest} diff --git a/src/tile.js b/src/tile.js index 8f2dfc1a..a6d40fd8 100644 --- a/src/tile.js +++ b/src/tile.js @@ -46,13 +46,14 @@ * this tile failed to load? ) * @param {String} url The URL of this tile's image. * @param {CanvasRenderingContext2D} context2D The context2D of this tile if it - * is provided directly by the tile source. + * is provided directly by the tile source. * @param {Boolean} loadWithAjax Whether this tile image should be loaded with an AJAX request . * @param {Object} ajaxHeaders The headers to send with this tile's AJAX request (if applicable). * @param {OpenSeadragon.Rect} sourceBounds The portion of the tile to use as the source of the - * @param {String} postData HTTP POST data in k=v&k2=v2... form or null - * drawing operation, in pixels. Note that this only works when drawing with canvas; when drawing - * with HTML the entire tile is always used. + * drawing operation, in pixels. Note that this only works when drawing with canvas; when drawing + * with HTML the entire tile is always used. + * @param {String} postData HTTP POST data (usually but not necessarily in k=v&k2=v2... form, + * see TileSrouce::getPostData) or null */ $.Tile = function(level, x, y, bounds, exists, url, context2D, loadWithAjax, ajaxHeaders, sourceBounds, postData) { /** @@ -101,7 +102,8 @@ $.Tile = function(level, x, y, bounds, exists, url, context2D, loadWithAjax, aja /** * Post parameters for this tile. Either it is an URL-encoded string * in k1=v1&k2=v2... format or null - * @member {String} postData HTTP POST data in k=v&k2=v2... form or null + * @member {String} postData HTTP POST data (usually but not necessarily in k=v&k2=v2... form, + * see TileSrouce::getPostData) or null * @memberof OpenSeadragon.Tile# */ this.postData = postData; diff --git a/src/tilesource.js b/src/tilesource.js index c1956ac5..48cfe851 100644 --- a/src/tilesource.js +++ b/src/tilesource.js @@ -550,7 +550,8 @@ $.TileSource.prototype = { * @property {OpenSeadragon.TileSource} eventSource - A reference to the TileSource which raised the event. * @property {String} message * @property {String} source - * @property {String} postData - HTTP POST data in k=v&k2=v2... form or null + * @property {String} postData - HTTP POST data (usually but not necessarily in k=v&k2=v2... form, + * see TileSrouce::getPostData) or null * @property {?Object} userData - Arbitrary subscriber-defined object. */ _this.raiseEvent( 'open-failed', { @@ -623,13 +624,26 @@ $.TileSource.prototype = { /** * Must use AJAX in order to work, i.e. loadTilesWithAjax = true is set. - * It should return url-encoded string with the following structure: - * key=value&key2=value2... - * or null in case GET is used instead. + * If a value is returned, ajax issues POST request to the tile url. + * If null is returned, ajax issues GET request. + * The return value must comply to the header 'content type'. + * + * Examples (USED HEADER --> getTilePostData CODE): + * 'Content-type': 'application/x-www-form-urlencoded' --> + * return "key1=value=1&key2=value2"; + * + * 'Content-type': 'application/x-www-form-urlencoded' --> + * return JSON.stringify({key: "value", number: 5}); + * + * 'Content-type': 'multipart/form-data' --> + * let result = new FormData(); + * result.append("data", myData); + * return result; + * @param level * @param x * @param y - * @return {string || null} post data to send with tile configuration request + * @return {* || null} post data to send with tile configuration request */ getTilePostData: function( level, x, y ) { return null; From aa0119be45fcecc9ad7a9d742525f62626b6729c Mon Sep 17 00:00:00 2001 From: Jirka Date: Wed, 23 Mar 2022 14:05:58 +0100 Subject: [PATCH 2/6] Rewrite TiledImage closure functions to be private class members. Fix tests that expect error message to appear in 'log' instead of 'error'. --- src/tiledimage.js | 1845 +++++++++++++++++++-------------------- test/modules/basic.js | 2 +- test/modules/strings.js | 4 +- 3 files changed, 916 insertions(+), 935 deletions(-) diff --git a/src/tiledimage.js b/src/tiledimage.js index 80204328..368a8c41 100644 --- a/src/tiledimage.js +++ b/src/tiledimage.js @@ -1,36 +1,36 @@ /* - * OpenSeadragon - TiledImage - * - * 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. - */ +* OpenSeadragon - TiledImage +* +* 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( $ ){ @@ -72,7 +72,8 @@ * @param {Boolean} [options.iOSDevice] - See {@link OpenSeadragon.Options}. * @param {Number} [options.opacity=1] - Set to draw at proportional opacity. If zero, images will not draw. * @param {Boolean} [options.preload=false] - Set true to load even when the image is hidden by zero opacity. - * @param {String} [options.compositeOperation] - How the image is composited onto other images; see compositeOperation in {@link OpenSeadragon.Options} for possible values. + * @param {String} [options.compositeOperation] - How the image is composited onto other images; see compositeOperation in {@link OpenSeadragon.Options} for possible + values. * @param {Boolean} [options.debugMode] - See {@link OpenSeadragon.Options}. * @param {String|CanvasGradient|CanvasPattern|Function} [options.placeholderFillStyle] - See {@link OpenSeadragon.Options}. * @param {String|Boolean} [options.crossOriginPolicy] - See {@link OpenSeadragon.Options}. @@ -218,21 +219,21 @@ $.TiledImage = function( options ) { // We need a callback to give image manipulation a chance to happen this._drawingHandler = function(args) { - /** - * 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 <canvas>. - * - * @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 - The Tile being drawn. - * @property {OpenSeadragon.TiledImage} tiledImage - Which TiledImage is being drawn. - * @property {OpenSeadragon.Tile} context - The HTML canvas context being drawn into. - * @property {OpenSeadragon.Tile} rendered - The HTML canvas context containing the tile imagery. - * @property {?Object} userData - Arbitrary subscriber-defined object. - */ + /** + * 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 <canvas>. + * + * @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 - The Tile being drawn. + * @property {OpenSeadragon.TiledImage} tiledImage - Which TiledImage is being drawn. + * @property {OpenSeadragon.Tile} context - The HTML canvas context being drawn into. + * @property {OpenSeadragon.Tile} rendered - The HTML canvas context containing the tile imagery. + * @property {?Object} userData - Arbitrary subscriber-defined object. + */ _this.viewer.raiseEvent('tile-drawing', $.extend({ tiledImage: _this }, args)); @@ -423,7 +424,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag /** * @returns {OpenSeadragon.Point} The TiledImage's content size, in window coordinates. */ - getSizeInWindowCoordinates: function() { + getSizeInWindowCoordinates: function() { var topLeft = this.imageToWindowCoordinates(new $.Point(0, 0)); var bottomRight = this.imageToWindowCoordinates(this.getContentSize()); return new $.Point(bottomRight.x - topLeft.x, bottomRight.y - topLeft.y); @@ -592,7 +593,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag */ windowToImageCoordinates: function( pixel ) { var viewerCoordinates = pixel.minus( - OpenSeadragon.getElementPosition( this.viewer.element )); + OpenSeadragon.getElementPosition( this.viewer.element )); return this.viewerElementToImageCoordinates( viewerCoordinates ); }, @@ -604,7 +605,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag imageToWindowCoordinates: function( pixel ) { var viewerCoordinates = this.imageToViewerElementCoordinates( pixel ); return viewerCoordinates.plus( - OpenSeadragon.getElementPosition( this.viewer.element )); + OpenSeadragon.getElementPosition( this.viewer.element )); }, // private @@ -633,7 +634,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag */ viewportToImageZoom: function( viewportZoom ) { var ratio = this._scaleSpring.current.value * - this.viewport._containerInnerSize.x / this.source.dimensions.x; + this.viewport._containerInnerSize.x / this.source.dimensions.x; return ratio * viewportZoom; }, @@ -650,7 +651,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag */ imageToViewportZoom: function( imageZoom ) { var ratio = this._scaleSpring.current.value * - this.viewport._containerInnerSize.x / this.source.dimensions.x; + this.viewport._containerInnerSize.x / this.source.dimensions.x; return imageZoom / ratio; }, @@ -666,7 +667,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag if (immediately) { if (sameTarget && this._xSpring.current.value === position.x && - this._ySpring.current.value === position.y) { + this._ySpring.current.value === position.y) { return; } @@ -1162,8 +1163,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag ); // Update the level and keep track of 'best' tile to load - bestTile = updateLevel( - this, + bestTile = this._updateLevel( haveDrawn, drawLevel, level, @@ -1176,17 +1176,17 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag // Stop the loop if lower-res tiles would all be covered by // already drawn tiles - if (providesCoverage(this.coverage, level)) { + if ($.TileSource._providesCoverage(this.coverage, level)) { break; } } // Perform the actual drawing - drawTiles(this, this.lastDrawn); + this._drawTiles(this.lastDrawn); // Load the new 'best' tile if (bestTile && !bestTile.context2D) { - loadTile(this, bestTile, currentTime); + this._loadTile(bestTile, currentTime); this._needsDraw = true; this._setFullyLoaded(false); } else { @@ -1233,586 +1233,855 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag topLeft: topLeftTile, bottomRight: bottomRightTile, }; - } -}); - -/** - * @private - * @inner - * Updates all tiles at a given resolution level. - * @param {OpenSeadragon.TiledImage} tiledImage - Which TiledImage is being drawn. - * @param {Boolean} haveDrawn - * @param {Boolean} drawLevel - * @param {Number} level - * @param {Number} levelOpacity - * @param {Number} levelVisibility - * @param {OpenSeadragon.Point} viewportTL - The index of the most top-left visible tile. - * @param {OpenSeadragon.Point} viewportBR - The index of the most bottom-right visible tile. - * @param {Number} currentTime - * @param {OpenSeadragon.Tile} best - The current "best" tile to draw. - */ -function updateLevel(tiledImage, haveDrawn, drawLevel, level, levelOpacity, - levelVisibility, drawArea, currentTime, best) { - - var topLeftBound = drawArea.getBoundingBox().getTopLeft(); - var bottomRightBound = drawArea.getBoundingBox().getBottomRight(); - - if (tiledImage.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 {OpenSeadragon.TiledImage} tiledImage - Which TiledImage is being drawn. - * @property {Object} havedrawn - * @property {Object} level - * @property {Object} opacity - * @property {Object} visibility - * @property {OpenSeadragon.Rect} drawArea - * @property {Object} topleft deprecated, use drawArea instead - * @property {Object} bottomright deprecated, use drawArea instead - * @property {Object} currenttime - * @property {Object} best - * @property {?Object} userData - Arbitrary subscriber-defined object. - */ - tiledImage.viewer.raiseEvent('update-level', { - tiledImage: tiledImage, - havedrawn: haveDrawn, - level: level, - opacity: levelOpacity, - visibility: levelVisibility, - drawArea: drawArea, - topleft: topLeftBound, - bottomright: bottomRightBound, - currenttime: currentTime, - best: best - }); - } - - resetCoverage(tiledImage.coverage, level); - resetCoverage(tiledImage.loadingCoverage, level); - - //OK, a new drawing so do your calculations - var cornerTiles = tiledImage._getCornerTiles(level, topLeftBound, bottomRightBound); - var topLeftTile = cornerTiles.topLeft; - var bottomRightTile = cornerTiles.bottomRight; - var numberOfTiles = tiledImage.source.getNumTiles(level); - - var viewportCenter = tiledImage.viewport.pixelFromPoint( - tiledImage.viewport.getCenter()); - - if (tiledImage.getFlip()) { - // The right-most tile can be narrower than the others. When flipped, - // this tile is now on the left. Because it is narrower than the normal - // left-most tile, the subsequent tiles may not be wide enough to completely - // fill the viewport. Fix this by rendering an extra column of tiles. If we - // are not wrapping, make sure we never render more than the number of tiles - // in the image. - bottomRightTile.x += 1; - if (!tiledImage.wrapHorizontal) { - bottomRightTile.x = Math.min(bottomRightTile.x, numberOfTiles.x - 1); - } - } - - for (var x = topLeftTile.x; x <= bottomRightTile.x; x++) { - for (var y = topLeftTile.y; y <= bottomRightTile.y; y++) { - - var flippedX; - if (tiledImage.getFlip()) { - var xMod = ( numberOfTiles.x + ( x % numberOfTiles.x ) ) % numberOfTiles.x; - flippedX = x + numberOfTiles.x - xMod - xMod - 1; - } else { - flippedX = x; - } - - if (drawArea.intersection(tiledImage.getTileBounds(level, flippedX, y)) === null) { - // This tile is outside of the viewport, no need to draw it - continue; - } - - best = updateTile( - tiledImage, - drawLevel, - haveDrawn, - flippedX, y, - level, - levelOpacity, - levelVisibility, - viewportCenter, - numberOfTiles, - currentTime, - best - ); - - } - } - - return best; -} - -/** - * @private - * @inner - * Update a single tile at a particular resolution level. - * @param {OpenSeadragon.TiledImage} tiledImage - Which TiledImage is being drawn. - * @param {Boolean} haveDrawn - * @param {Boolean} drawLevel - * @param {Number} x - * @param {Number} y - * @param {Number} level - * @param {Number} levelOpacity - * @param {Number} levelVisibility - * @param {OpenSeadragon.Point} viewportCenter - * @param {Number} numberOfTiles - * @param {Number} currentTime - * @param {OpenSeadragon.Tile} best - The current "best" tile to draw. - */ -function updateTile( tiledImage, haveDrawn, drawLevel, x, y, level, levelOpacity, levelVisibility, viewportCenter, numberOfTiles, currentTime, best){ - - var tile = getTile( - x, y, - level, - tiledImage, - tiledImage.source, - tiledImage.tilesMatrix, - currentTime, - numberOfTiles, - tiledImage._worldWidthCurrent, - tiledImage._worldHeightCurrent - ), - drawTile = drawLevel; - - if( tiledImage.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.TiledImage} tiledImage - Which TiledImage is being drawn. - * @property {OpenSeadragon.Tile} tile - * @property {?Object} userData - Arbitrary subscriber-defined object. - */ - tiledImage.viewer.raiseEvent( 'update-tile', { - tiledImage: tiledImage, - tile: tile - }); - } - - setCoverage( tiledImage.coverage, level, x, y, false ); - - var loadingCoverage = tile.loaded || tile.loading || isCovered(tiledImage.loadingCoverage, level, x, y); - setCoverage(tiledImage.loadingCoverage, level, x, y, loadingCoverage); - - if ( !tile.exists ) { - return best; - } - - if ( haveDrawn && !drawTile ) { - if ( isCovered( tiledImage.coverage, level, x, y ) ) { - setCoverage( tiledImage.coverage, level, x, y, true ); - } else { - drawTile = true; - } - } - - if ( !drawTile ) { - return best; - } - - positionTile( - tile, - tiledImage.source.tileOverlap, - tiledImage.viewport, - viewportCenter, - levelVisibility, - tiledImage - ); - - if (!tile.loaded) { - if (tile.context2D) { - setTileLoaded(tiledImage, tile); - } else { - var imageRecord = tiledImage._tileCache.getImageRecord(tile.cacheKey); - if (imageRecord) { - var image = imageRecord.getImage(); - setTileLoaded(tiledImage, tile, image); - } - } - } - - if ( tile.loaded ) { - var needsDraw = blendTile( - tiledImage, - tile, - x, y, - level, - levelOpacity, - currentTime - ); - - if ( needsDraw ) { - tiledImage._needsDraw = true; - } - } else if ( tile.loading ) { - // the tile is already in the download queue - tiledImage._tilesLoading++; - } else if (!loadingCoverage) { - best = compareTiles( best, tile ); - } - - return best; -} - -/** - * @private - * @inner - * Obtains a tile at the given location. - * @param {Number} x - * @param {Number} y - * @param {Number} level - * @param {OpenSeadragon.TiledImage} tiledImage - * @param {OpenSeadragon.TileSource} tileSource - * @param {Object} tilesMatrix - A '3d' dictionary [level][x][y] --> Tile. - * @param {Number} time - * @param {Number} numTiles - * @param {Number} worldWidth - * @param {Number} worldHeight - * @returns {OpenSeadragon.Tile} - */ -function getTile( - x, y, - level, - tiledImage, - tileSource, - tilesMatrix, - time, - numTiles, - worldWidth, - worldHeight -) { - var xMod, - yMod, - bounds, - sourceBounds, - exists, - url, - post, - ajaxHeaders, - context2D, - tile; - - if ( !tilesMatrix[ level ] ) { - tilesMatrix[ level ] = {}; - } - if ( !tilesMatrix[ level ][ x ] ) { - tilesMatrix[ level ][ x ] = {}; - } - - if ( !tilesMatrix[ level ][ x ][ y ] || !tilesMatrix[ level ][ x ][ y ].flipped !== !tiledImage.flipped ) { - xMod = ( numTiles.x + ( x % numTiles.x ) ) % numTiles.x; - yMod = ( numTiles.y + ( y % numTiles.y ) ) % numTiles.y; - bounds = tiledImage.getTileBounds( level, x, y ); - sourceBounds = tileSource.getTileBounds( level, xMod, yMod, true ); - exists = tileSource.tileExists( level, xMod, yMod ); - url = tileSource.getTileUrl( level, xMod, yMod ); - post = tileSource.getTilePostData( level, xMod, yMod ); - - // Headers are only applicable if loadTilesWithAjax is set - if (tiledImage.loadTilesWithAjax) { - ajaxHeaders = tileSource.getTileAjaxHeaders( level, xMod, yMod ); - // Combine tile AJAX headers with tiled image AJAX headers (if applicable) - if ($.isPlainObject(tiledImage.ajaxHeaders)) { - ajaxHeaders = $.extend({}, tiledImage.ajaxHeaders, ajaxHeaders); - } - } else { - ajaxHeaders = null; - } - - context2D = tileSource.getContext2D ? - tileSource.getContext2D(level, xMod, yMod) : undefined; - - tile = new $.Tile( - level, - x, - y, - bounds, - exists, - url, - context2D, - tiledImage.loadTilesWithAjax, - ajaxHeaders, - sourceBounds, - post - ); - - if (tiledImage.getFlip()) { - if (xMod === 0) { - tile.isRightMost = true; - } - } else { - if (xMod === numTiles.x - 1) { - tile.isRightMost = true; - } - } - - if (yMod === numTiles.y - 1) { - tile.isBottomMost = true; - } - - tile.flipped = tiledImage.flipped; - - tilesMatrix[ level ][ x ][ y ] = tile; - } - - tile = tilesMatrix[ level ][ x ][ y ]; - tile.lastTouchTime = time; - - return tile; -} - -/** - * @private - * @inner - * Dispatch a job to the ImageLoader to load the Image for a Tile. - * @param {OpenSeadragon.TiledImage} tiledImage - * @param {OpenSeadragon.Tile} tile - * @param {Number} time - */ -function loadTile( tiledImage, tile, time ) { - tile.loading = true; - tiledImage._imageLoader.addJob({ - src: tile.url, - postData: tile.postData, - loadWithAjax: tile.loadWithAjax, - ajaxHeaders: tile.ajaxHeaders, - crossOriginPolicy: tiledImage.crossOriginPolicy, - ajaxWithCredentials: tiledImage.ajaxWithCredentials, - callback: function( image, errorMsg, tileRequest ){ - onTileLoad( tiledImage, tile, time, image, errorMsg, tileRequest ); - }, - abort: function() { - tile.loading = false; - } - }); -} - -/** - * @private - * @inner - * Callback fired when a Tile's Image finished downloading. - * @param {OpenSeadragon.TiledImage} tiledImage - * @param {OpenSeadragon.Tile} tile - * @param {Number} time - * @param {Image} image - * @param {String} errorMsg - * @param {XMLHttpRequest} tileRequest - */ -function onTileLoad( tiledImage, tile, time, image, errorMsg, tileRequest ) { - if ( !image ) { - $.console.error( "Tile %s failed to load: %s - error: %s", tile, tile.url, errorMsg ); - /** - * Triggered when a tile fails to load. - * - * @event tile-load-failed - * @memberof OpenSeadragon.Viewer - * @type {object} - * @property {OpenSeadragon.Tile} tile - The tile that failed to load. - * @property {OpenSeadragon.TiledImage} tiledImage - The tiled image the tile belongs to. - * @property {number} time - The time in milliseconds when the tile load began. - * @property {string} message - The error message. - * @property {XMLHttpRequest} tileRequest - The XMLHttpRequest used to load the tile if available. - */ - tiledImage.viewer.raiseEvent("tile-load-failed", { - tile: tile, - tiledImage: tiledImage, - time: time, - message: errorMsg, - tileRequest: tileRequest - }); - tile.loading = false; - tile.exists = false; - return; - } - - if ( time < tiledImage.lastResetTime ) { - $.console.warn( "Ignoring tile %s loaded before reset: %s", tile, tile.url ); - tile.loading = false; - return; - } - - var finish = function() { - var cutoff = tiledImage.source.getClosestLevel(); - setTileLoaded(tiledImage, tile, image, cutoff, tileRequest); - }; - - // Check if we're mid-update; this can happen on IE8 because image load events for - // cached images happen immediately there - if ( !tiledImage._midDraw ) { - finish(); - } else { - // Wait until after the update, in case caching unloads any tiles - window.setTimeout( finish, 1); - } -} - -/** - * @private - * @inner - * @param {OpenSeadragon.TiledImage} tiledImage - * @param {OpenSeadragon.Tile} tile - * @param {Image} image - * @param {Number} cutoff - */ -function setTileLoaded(tiledImage, tile, image, cutoff, tileRequest) { - var increment = 0; - - function getCompletionCallback() { - increment++; - return completionCallback; - } - - function completionCallback() { - increment--; - if (increment === 0) { - tile.loading = false; - tile.loaded = true; - if (!tile.context2D) { - tiledImage._tileCache.cacheTile({ - image: image, - tile: tile, - cutoff: cutoff, - tiledImage: tiledImage - }); - } - tiledImage._needsDraw = true; - } - } + }, /** - * Triggered when a tile has just been loaded in memory. That means that the - * image has been downloaded and can be modified before being drawn to the canvas. - * - * @event tile-loaded - * @memberof OpenSeadragon.Viewer - * @type {object} - * @property {Image} image - The image of the tile. - * @property {OpenSeadragon.TiledImage} tiledImage - The tiled image of the loaded tile. - * @property {OpenSeadragon.Tile} tile - The tile which has been loaded. - * @property {XMLHttpRequest} tileRequest - The AJAX request that loaded this tile (if applicable). - * @property {function} getCompletionCallback - A function giving a callback to call - * when the asynchronous processing of the image is done. The image will be - * marked as entirely loaded when the callback has been called once for each - * call to getCompletionCallback. + * @private + * Updates all tiles at a given resolution level. + * @param {Boolean} haveDrawn + * @param {Boolean} drawLevel + * @param {Number} level + * @param {Number} levelOpacity + * @param {Number} levelVisibility + * @param {OpenSeadragon.Rect} drawArea + * @param {Number} currentTime + * @param {OpenSeadragon.Tile} best - The current "best" tile to draw. */ - tiledImage.viewer.raiseEvent("tile-loaded", { - tile: tile, - tiledImage: tiledImage, - tileRequest: tileRequest, - image: image, - getCompletionCallback: getCompletionCallback - }); - // In case the completion callback is never called, we at least force it once. - getCompletionCallback()(); -} + _updateLevel: function(haveDrawn, drawLevel, level, levelOpacity, + levelVisibility, drawArea, currentTime, best) { -/** - * @private - * @inner - * @param {OpenSeadragon.Tile} tile - * @param {Boolean} overlap - * @param {OpenSeadragon.Viewport} viewport - * @param {OpenSeadragon.Point} viewportCenter - * @param {Number} levelVisibility - * @param {OpenSeadragon.TiledImage} tiledImage - */ -function positionTile( tile, overlap, viewport, viewportCenter, levelVisibility, tiledImage ){ - var boundsTL = tile.bounds.getTopLeft(); + var topLeftBound = drawArea.getBoundingBox().getTopLeft(); + var bottomRightBound = drawArea.getBoundingBox().getBottomRight(); - boundsTL.x *= tiledImage._scaleSpring.current.value; - boundsTL.y *= tiledImage._scaleSpring.current.value; - boundsTL.x += tiledImage._xSpring.current.value; - boundsTL.y += tiledImage._ySpring.current.value; + if (this.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 {OpenSeadragon.TiledImage} tiledImage - Which TiledImage is being drawn. + * @property {Object} havedrawn + * @property {Object} level + * @property {Object} opacity + * @property {Object} visibility + * @property {OpenSeadragon.Rect} drawArea + * @property {Object} topleft deprecated, use drawArea instead + * @property {Object} bottomright deprecated, use drawArea instead + * @property {Object} currenttime + * @property {Object} best + * @property {?Object} userData - Arbitrary subscriber-defined object. + */ + this.viewer.raiseEvent('update-level', { + tiledImage: this, + havedrawn: haveDrawn, + level: level, + opacity: levelOpacity, + visibility: levelVisibility, + drawArea: drawArea, + topleft: topLeftBound, + bottomright: bottomRightBound, + currenttime: currentTime, + best: best + }); + } - var boundsSize = tile.bounds.getSize(); + $.TileSource._resetCoverage(this.coverage, level); + $.TileSource._resetCoverage(this.loadingCoverage, level); - boundsSize.x *= tiledImage._scaleSpring.current.value; - boundsSize.y *= tiledImage._scaleSpring.current.value; + //OK, a new drawing so do your calculations + var cornerTiles = this._getCornerTiles(level, topLeftBound, bottomRightBound); + var topLeftTile = cornerTiles.topLeft; + var bottomRightTile = cornerTiles.bottomRight; + var numberOfTiles = this.source.getNumTiles(level); - var positionC = viewport.pixelFromPointNoRotate(boundsTL, true), - positionT = viewport.pixelFromPointNoRotate(boundsTL, false), - sizeC = viewport.deltaPixelsFromPointsNoRotate(boundsSize, true), - sizeT = viewport.deltaPixelsFromPointsNoRotate(boundsSize, false), - tileCenter = positionT.plus( sizeT.divide( 2 ) ), - tileSquaredDistance = viewportCenter.squaredDistanceTo( tileCenter ); + var viewportCenter = this.viewport.pixelFromPoint(this.viewport.getCenter()); - if ( !overlap ) { - sizeC = sizeC.plus( new $.Point( 1, 1 ) ); + if (this.getFlip()) { + // The right-most tile can be narrower than the others. When flipped, + // this tile is now on the left. Because it is narrower than the normal + // left-most tile, the subsequent tiles may not be wide enough to completely + // fill the viewport. Fix this by rendering an extra column of tiles. If we + // are not wrapping, make sure we never render more than the number of tiles + // in the image. + bottomRightTile.x += 1; + if (!this.wrapHorizontal) { + bottomRightTile.x = Math.min(bottomRightTile.x, numberOfTiles.x - 1); + } + } + + for (var x = topLeftTile.x; x <= bottomRightTile.x; x++) { + for (var y = topLeftTile.y; y <= bottomRightTile.y; y++) { + + var flippedX; + if (this.getFlip()) { + var xMod = ( numberOfTiles.x + ( x % numberOfTiles.x ) ) % numberOfTiles.x; + flippedX = x + numberOfTiles.x - xMod - xMod - 1; + } else { + flippedX = x; + } + + if (drawArea.intersection(this.getTileBounds(level, flippedX, y)) === null) { + // This tile is outside of the viewport, no need to draw it + continue; + } + + best = this._updateTile( + drawLevel, + haveDrawn, + flippedX, y, + level, + levelOpacity, + levelVisibility, + viewportCenter, + numberOfTiles, + currentTime, + best + ); + } + } + + return best; + }, + + /** + * @private + * @inner + * Update a single tile at a particular resolution level. + * @param {Boolean} haveDrawn + * @param {Boolean} drawLevel + * @param {Number} x + * @param {Number} y + * @param {Number} level + * @param {Number} levelOpacity + * @param {Number} levelVisibility + * @param {OpenSeadragon.Point} viewportCenter + * @param {Number} numberOfTiles + * @param {Number} currentTime + * @param {OpenSeadragon.Tile} best - The current "best" tile to draw. + */ + _updateTile: function( haveDrawn, drawLevel, x, y, level, levelOpacity, + levelVisibility, viewportCenter, numberOfTiles, currentTime, best){ + + var tile = this._getTile( + x, y, + level, + currentTime, + numberOfTiles, + this._worldWidthCurrent, + this._worldHeightCurrent + ), + drawTile = drawLevel; + + if( this.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.TiledImage} tiledImage - Which TiledImage is being drawn. + * @property {OpenSeadragon.Tile} tile + * @property {?Object} userData - Arbitrary subscriber-defined object. + */ + this.viewer.raiseEvent( 'update-tile', { + tiledImage: this, + tile: tile + }); + } + + $.TileSource._setCoverage( this.coverage, level, x, y, false ); + + var loadingCoverage = tile.loaded || tile.loading || $.TileSource._isCovered(this.loadingCoverage, level, x, y); + $.TileSource._setCoverage(this.loadingCoverage, level, x, y, loadingCoverage); + + if ( !tile.exists ) { + return best; + } + + if ( haveDrawn && !drawTile ) { + if ( $.TileSource._isCovered( this.coverage, level, x, y ) ) { + $.TileSource._setCoverage( this.coverage, level, x, y, true ); + } else { + drawTile = true; + } + } + + if ( !drawTile ) { + return best; + } + + this._positionTile( + tile, + this.source.tileOverlap, + this.viewport, + viewportCenter, + levelVisibility + ); + + if (!tile.loaded) { + if (tile.context2D) { + this._setTileLoaded(tile); + } else { + var imageRecord = this._tileCache.getImageRecord(tile.cacheKey); + if (imageRecord) { + var image = imageRecord.getImage(); + this._setTileLoaded(tile, image); + } + } + } + + if ( tile.loaded ) { + var needsDraw = this._blendTile( + tile, + x, y, + level, + levelOpacity, + currentTime + ); + + if ( needsDraw ) { + this._needsDraw = true; + } + } else if ( tile.loading ) { + // the tile is already in the download queue + this._tilesLoading++; + } else if (!loadingCoverage) { + best = this._compareTiles( best, tile ); + } + + return best; + }, + + /** + * @private + * @inner + * Obtains a tile at the given location. + * @param {Number} x + * @param {Number} y + * @param {Number} level + * @param {Number} time + * @param {Number} numTiles + * @param {Number} worldWidth + * @param {Number} worldHeight + * @returns {OpenSeadragon.Tile} + */ + _getTile: function( + x, y, + level, + time, + numTiles, + worldWidth, + worldHeight + ) { + var xMod, + yMod, + bounds, + sourceBounds, + exists, + url, + post, + ajaxHeaders, + context2D, + tile, + tilesMatrix = this.tilesMatrix, + tileSource = this.source; + + if ( !tilesMatrix[ level ] ) { + tilesMatrix[ level ] = {}; + } + if ( !tilesMatrix[ level ][ x ] ) { + tilesMatrix[ level ][ x ] = {}; + } + + if ( !tilesMatrix[ level ][ x ][ y ] || !tilesMatrix[ level ][ x ][ y ].flipped !== !this.flipped ) { + xMod = ( numTiles.x + ( x % numTiles.x ) ) % numTiles.x; + yMod = ( numTiles.y + ( y % numTiles.y ) ) % numTiles.y; + bounds = this.getTileBounds( level, x, y ); + sourceBounds = tileSource.getTileBounds( level, xMod, yMod, true ); + exists = tileSource.tileExists( level, xMod, yMod ); + url = tileSource.getTileUrl( level, xMod, yMod ); + post = tileSource.getTilePostData( level, xMod, yMod ); + + // Headers are only applicable if loadTilesWithAjax is set + if (this.loadTilesWithAjax) { + ajaxHeaders = tileSource.getTileAjaxHeaders( level, xMod, yMod ); + // Combine tile AJAX headers with tiled image AJAX headers (if applicable) + if ($.isPlainObject(this.ajaxHeaders)) { + ajaxHeaders = $.extend({}, this.ajaxHeaders, ajaxHeaders); + } + } else { + ajaxHeaders = null; + } + + context2D = tileSource.getContext2D ? + tileSource.getContext2D(level, xMod, yMod) : undefined; + + tile = new $.Tile( + level, + x, + y, + bounds, + exists, + url, + context2D, + this.loadTilesWithAjax, + ajaxHeaders, + sourceBounds, + post + ); + + if (this.getFlip()) { + if (xMod === 0) { + tile.isRightMost = true; + } + } else { + if (xMod === numTiles.x - 1) { + tile.isRightMost = true; + } + } + + if (yMod === numTiles.y - 1) { + tile.isBottomMost = true; + } + + tile.flipped = this.flipped; + + tilesMatrix[ level ][ x ][ y ] = tile; + } + + tile = tilesMatrix[ level ][ x ][ y ]; + tile.lastTouchTime = time; + + return tile; + }, + + /** + * @private + * @inner + * Dispatch a job to the ImageLoader to load the Image for a Tile. + * @param {OpenSeadragon.Tile} tile + * @param {Number} time + */ + _loadTile: function(tile, time ) { + var _this = this; + tile.loading = true; + this._imageLoader.addJob({ + src: tile.url, + postData: tile.postData, + loadWithAjax: tile.loadWithAjax, + ajaxHeaders: tile.ajaxHeaders, + crossOriginPolicy: this.crossOriginPolicy, + ajaxWithCredentials: this.ajaxWithCredentials, + callback: function( image, errorMsg, tileRequest ){ + _this._onTileLoad( tile, time, image, errorMsg, tileRequest ); + }, + abort: function() { + tile.loading = false; + } + }); + }, + + /** + * @private + * @inner + * Callback fired when a Tile's Image finished downloading. + * @param {OpenSeadragon.Tile} tile + * @param {Number} time + * @param {Image} image + * @param {String} errorMsg + * @param {XMLHttpRequest} tileRequest + */ + _onTileLoad: function( tile, time, image, errorMsg, tileRequest ) { + if ( !image ) { + $.console.error( "Tile %s failed to load: %s - error: %s", tile, tile.url, errorMsg ); + /** + * Triggered when a tile fails to load. + * + * @event tile-load-failed + * @memberof OpenSeadragon.Viewer + * @type {object} + * @property {OpenSeadragon.Tile} tile - The tile that failed to load. + * @property {OpenSeadragon.TiledImage} tiledImage - The tiled image the tile belongs to. + * @property {number} time - The time in milliseconds when the tile load began. + * @property {string} message - The error message. + * @property {XMLHttpRequest} tileRequest - The XMLHttpRequest used to load the tile if available. + */ + this.viewer.raiseEvent("tile-load-failed", { + tile: tile, + tiledImage: this, + time: time, + message: errorMsg, + tileRequest: tileRequest + }); + tile.loading = false; + tile.exists = false; + return; + } + + if ( time < this.lastResetTime ) { + $.console.warn( "Ignoring tile %s loaded before reset: %s", tile, tile.url ); + tile.loading = false; + return; + } + + var _this = this, + finish = function() { + var ccc = _this.source; + var cutoff = ccc.getClosestLevel(); + _this._setTileLoaded(tile, image, cutoff, tileRequest); + }; + + // Check if we're mid-update; this can happen on IE8 because image load events for + // cached images happen immediately there + if ( !this._midDraw ) { + finish(); + } else { + // Wait until after the update, in case caching unloads any tiles + window.setTimeout( finish, 1); + } + }, + + /** + * @private + * @inner + * @param {OpenSeadragon.Tile} tile + * @param {Image || undefined} image + * @param {Number || undefined} cutoff + * @param {XMLHttpRequest || undefined} tileRequest + */ + _setTileLoaded: function(tile, image, cutoff, tileRequest) { + var increment = 0, + _this = this; + + function getCompletionCallback() { + increment++; + return completionCallback; + } + + function completionCallback() { + increment--; + if (increment === 0) { + tile.loading = false; + tile.loaded = true; + if (!tile.context2D) { + _this._tileCache.cacheTile({ + image: image, + tile: tile, + cutoff: cutoff, + tiledImage: _this + }); + } + _this._needsDraw = true; + } + } + + /** + * Triggered when a tile has just been loaded in memory. That means that the + * image has been downloaded and can be modified before being drawn to the canvas. + * + * @event tile-loaded + * @memberof OpenSeadragon.Viewer + * @type {object} + * @property {Image} image - The image of the tile. + * @property {OpenSeadragon.TiledImage} tiledImage - The tiled image of the loaded tile. + * @property {OpenSeadragon.Tile} tile - The tile which has been loaded. + * @property {XMLHttpRequest} tileRequest - The AJAX request that loaded this tile (if applicable). + * @property {function} getCompletionCallback - A function giving a callback to call + * when the asynchronous processing of the image is done. The image will be + * marked as entirely loaded when the callback has been called once for each + * call to getCompletionCallback. + */ + this.viewer.raiseEvent("tile-loaded", { + tile: tile, + tiledImage: this, + tileRequest: tileRequest, + image: image, + getCompletionCallback: getCompletionCallback + }); + // In case the completion callback is never called, we at least force it once. + getCompletionCallback()(); + }, + + /** + * @private + * @inner + * @param {OpenSeadragon.Tile} tile + * @param {Boolean} overlap + * @param {OpenSeadragon.Viewport} viewport + * @param {OpenSeadragon.Point} viewportCenter + * @param {Number} levelVisibility + */ + _positionTile: function( tile, overlap, viewport, viewportCenter, levelVisibility ){ + var boundsTL = tile.bounds.getTopLeft(); + + boundsTL.x *= this._scaleSpring.current.value; + boundsTL.y *= this._scaleSpring.current.value; + boundsTL.x += this._xSpring.current.value; + boundsTL.y += this._ySpring.current.value; + + var boundsSize = tile.bounds.getSize(); + + boundsSize.x *= this._scaleSpring.current.value; + boundsSize.y *= this._scaleSpring.current.value; + + var positionC = viewport.pixelFromPointNoRotate(boundsTL, true), + positionT = viewport.pixelFromPointNoRotate(boundsTL, false), + sizeC = viewport.deltaPixelsFromPointsNoRotate(boundsSize, true), + sizeT = viewport.deltaPixelsFromPointsNoRotate(boundsSize, false), + tileCenter = positionT.plus( sizeT.divide( 2 ) ), + tileSquaredDistance = viewportCenter.squaredDistanceTo( tileCenter ); + + if ( !overlap ) { + sizeC = sizeC.plus( new $.Point( 1, 1 ) ); + } + + if (tile.isRightMost && this.wrapHorizontal) { + sizeC.x += 0.75; // Otherwise Firefox and Safari show seams + } + + if (tile.isBottomMost && this.wrapVertical) { + sizeC.y += 0.75; // Otherwise Firefox and Safari show seams + } + + tile.position = positionC; + tile.size = sizeC; + tile.squaredDistance = tileSquaredDistance; + tile.visibility = levelVisibility; + }, + + /** + * @private + * @inner + * Updates the opacity of a tile according to the time it has been on screen + * to perform a fade-in. + * Updates coverage once a tile is fully opaque. + * Returns whether the fade-in has completed. + * + * @param {OpenSeadragon.Tile} tile + * @param {Number} x + * @param {Number} y + * @param {Number} level + * @param {Number} levelOpacity + * @param {Number} currentTime + * @returns {Boolean} + */ + _blendTile: function( tile, x, y, level, levelOpacity, currentTime ){ + var blendTimeMillis = 1000 * this.blendTime, + deltaTime, + opacity; + + if ( !tile.blendStart ) { + tile.blendStart = currentTime; + } + + deltaTime = currentTime - tile.blendStart; + opacity = blendTimeMillis ? Math.min( 1, deltaTime / ( blendTimeMillis ) ) : 1; + + if ( this.alwaysBlend ) { + opacity *= levelOpacity; + } + + tile.opacity = opacity; + + this.lastDrawn.push( tile ); + + if ( opacity === 1 ) { + $.TileSource._setCoverage( this.coverage, level, x, y, true ); + this._hasOpaqueTile = true; + } else if ( deltaTime < blendTimeMillis ) { + return true; + } + + return false; + }, + + + /** + * @private + * @inner + * Determines whether the 'last best' tile for the area is better than the + * tile in question. + * + * @param {OpenSeadragon.Tile} previousBest + * @param {OpenSeadragon.Tile} tile + * @returns {OpenSeadragon.Tile} The new best tile. + */ + _compareTiles: function( previousBest, tile ) { + if ( !previousBest ) { + return tile; + } + + if ( tile.visibility > previousBest.visibility ) { + return tile; + } else if ( tile.visibility === previousBest.visibility ) { + if ( tile.squaredDistance < previousBest.squaredDistance ) { + return tile; + } + } + return previousBest; + }, + + /** + * @private + * @inner + * Draws a TiledImage. + * @param {OpenSeadragon.Tile[]} lastDrawn - An unordered list of Tiles drawn last frame. + */ + _drawTiles: function( lastDrawn ) { + if (this.opacity === 0 || (lastDrawn.length === 0 && !this.placeholderFillStyle)) { + return; + } + + var tile = lastDrawn[0]; + var useSketch; + + if (tile) { + useSketch = this.opacity < 1 || + (this.compositeOperation && this.compositeOperation !== 'source-over') || + (!this._isBottomItem() && tile._hasTransparencyChannel()); + } + + var sketchScale; + var sketchTranslate; + + var zoom = this.viewport.getZoom(true); + var imageZoom = this.viewportToImageZoom(zoom); + + if (lastDrawn.length > 1 && + imageZoom > this.smoothTileEdgesMinZoom && + !this.iOSDevice && + this.getRotation(true) % 360 === 0 && // TODO: support tile edge smoothing with tiled image rotation. + $.supportsCanvas && this.viewer.useCanvas) { + // When zoomed in a lot (>100%) the tile edges are visible. + // So we have to composite them at ~100% and scale them up together. + // Note: Disabled on iOS devices per default as it causes a native crash + useSketch = true; + sketchScale = tile.getScaleForEdgeSmoothing(); + sketchTranslate = tile.getTranslationForEdgeSmoothing(sketchScale, + this._drawer.getCanvasSize(false), + this._drawer.getCanvasSize(true)); + } + + var bounds; + if (useSketch) { + if (!sketchScale) { + // Except when edge smoothing, we only clean the part of the + // sketch canvas we are going to use for performance reasons. + bounds = this.viewport.viewportToViewerElementRectangle( + this.getClippedBounds(true)) + .getIntegerBoundingBox(); + + if(this._drawer.viewer.viewport.getFlip()) { + if (this.viewport.degrees !== 0 || this.getRotation(true) % 360 !== 0) { + bounds.x = this._drawer.viewer.container.clientWidth - (bounds.x + bounds.width); + } + } + + bounds = bounds.times($.pixelDensityRatio); + } + this._drawer._clear(true, bounds); + } + + // When scaling, we must rotate only when blending the sketch canvas to + // avoid interpolation + if (!sketchScale) { + if (this.viewport.degrees !== 0) { + this._drawer._offsetForRotation({ + degrees: this.viewport.degrees, + useSketch: useSketch + }); + } + if (this.getRotation(true) % 360 !== 0) { + this._drawer._offsetForRotation({ + degrees: this.getRotation(true), + point: this.viewport.pixelFromPointNoRotate( + this._getRotationPoint(true), true), + useSketch: useSketch + }); + } + + if (this.viewport.degrees === 0 && this.getRotation(true) % 360 === 0){ + if(this._drawer.viewer.viewport.getFlip()) { + this._drawer._flip(); + } + } + } + + var usedClip = false; + if ( this._clip ) { + this._drawer.saveContext(useSketch); + + var box = this.imageToViewportRectangle(this._clip, true); + box = box.rotate(-this.getRotation(true), this._getRotationPoint(true)); + var clipRect = this._drawer.viewportToDrawerRectangle(box); + if (sketchScale) { + clipRect = clipRect.times(sketchScale); + } + if (sketchTranslate) { + clipRect = clipRect.translate(sketchTranslate); + } + this._drawer.setClip(clipRect, useSketch); + + usedClip = true; + } + + if (this._croppingPolygons) { + this._drawer.saveContext(useSketch); + try { + var polygons = this._croppingPolygons.map(function (polygon) { + return polygon.map(function (coord) { + var point = this + .imageToViewportCoordinates(coord.x, coord.y, true) + .rotate(-this.getRotation(true), this._getRotationPoint(true)); + var clipPoint = this._drawer.viewportCoordToDrawerCoord(point); + if (sketchScale) { + clipPoint = clipPoint.times(sketchScale); + } + return clipPoint; + }); + }); + this._drawer.clipWithPolygons(polygons, useSketch); + } catch (e) { + $.console.error(e); + } + usedClip = true; + } + + if ( this.placeholderFillStyle && this._hasOpaqueTile === false ) { + var placeholderRect = this._drawer.viewportToDrawerRectangle(this.getBounds(true)); + if (sketchScale) { + placeholderRect = placeholderRect.times(sketchScale); + } + if (sketchTranslate) { + placeholderRect = placeholderRect.translate(sketchTranslate); + } + + var fillStyle = null; + if ( typeof this.placeholderFillStyle === "function" ) { + fillStyle = this.placeholderFillStyle(this, this._drawer.context); + } + else { + fillStyle = this.placeholderFillStyle; + } + + this._drawer.drawRectangle(placeholderRect, fillStyle, useSketch); + } + + var subPixelRoundingRule = determineSubPixelRoundingRule(this.subPixelRoundingForTransparency); + + var shouldRoundPositionAndSize = false; + + if (subPixelRoundingRule === $.SUBPIXEL_ROUNDING_OCCURRENCES.ALWAYS) { + shouldRoundPositionAndSize = true; + } else if (subPixelRoundingRule === $.SUBPIXEL_ROUNDING_OCCURRENCES.ONLY_AT_REST) { + var isAnimating = this.viewer && this.viewer.isAnimating(); + shouldRoundPositionAndSize = !isAnimating; + } + + for (var i = lastDrawn.length - 1; i >= 0; i--) { + tile = lastDrawn[ i ]; + this._drawer.drawTile( tile, this._drawingHandler, useSketch, sketchScale, sketchTranslate, shouldRoundPositionAndSize ); + tile.beingDrawn = true; + + if( this.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.TiledImage} tiledImage - Which TiledImage is being drawn. + * @property {OpenSeadragon.Tile} tile + * @property {?Object} userData - Arbitrary subscriber-defined object. + */ + this.viewer.raiseEvent( 'tile-drawn', { + tiledImage: this, + tile: tile + }); + } + } + + if ( usedClip ) { + this._drawer.restoreContext( useSketch ); + } + + if (!sketchScale) { + if (this.getRotation(true) % 360 !== 0) { + this._drawer._restoreRotationChanges(useSketch); + } + if (this.viewport.degrees !== 0) { + this._drawer._restoreRotationChanges(useSketch); + } + } + + if (useSketch) { + if (sketchScale) { + if (this.viewport.degrees !== 0) { + this._drawer._offsetForRotation({ + degrees: this.viewport.degrees, + useSketch: false + }); + } + if (this.getRotation(true) % 360 !== 0) { + this._drawer._offsetForRotation({ + degrees: this.getRotation(true), + point: this.viewport.pixelFromPointNoRotate( + this._getRotationPoint(true), true), + useSketch: false + }); + } + } + this._drawer.blendSketch({ + opacity: this.opacity, + scale: sketchScale, + translate: sketchTranslate, + compositeOperation: this.compositeOperation, + bounds: bounds + }); + if (sketchScale) { + if (this.getRotation(true) % 360 !== 0) { + this._drawer._restoreRotationChanges(false); + } + if (this.viewport.degrees !== 0) { + this._drawer._restoreRotationChanges(false); + } + } + } + + if (!sketchScale) { + if (this.viewport.degrees === 0 && this.getRotation(true) % 360 === 0){ + if(this._drawer.viewer.viewport.getFlip()) { + this._drawer._flip(); + } + } + } + + this._drawDebugInfo( lastDrawn ); + }, + + /** + * @private + * @inner + * Draws special debug information for a TiledImage if in debug mode. + * @param {OpenSeadragon.Tile[]} lastDrawn - An unordered list of Tiles drawn last frame. + */ + _drawDebugInfo: function( lastDrawn ) { + if( this.debugMode ) { + for ( var i = lastDrawn.length - 1; i >= 0; i-- ) { + var tile = lastDrawn[ i ]; + try { + this._drawer.drawDebugInfo(tile, lastDrawn.length, i, this); + } catch(e) { + $.console.error(e); + } + } + } } - - if (tile.isRightMost && tiledImage.wrapHorizontal) { - sizeC.x += 0.75; // Otherwise Firefox and Safari show seams - } - - if (tile.isBottomMost && tiledImage.wrapVertical) { - sizeC.y += 0.75; // Otherwise Firefox and Safari show seams - } - - tile.position = positionC; - tile.size = sizeC; - tile.squaredDistance = tileSquaredDistance; - tile.visibility = levelVisibility; -} - -/** - * @private - * @inner - * Updates the opacity of a tile according to the time it has been on screen - * to perform a fade-in. - * Updates coverage once a tile is fully opaque. - * Returns whether the fade-in has completed. - * - * @param {OpenSeadragon.TiledImage} tiledImage - * @param {OpenSeadragon.Tile} tile - * @param {Number} x - * @param {Number} y - * @param {Number} level - * @param {Number} levelOpacity - * @param {Number} currentTime - * @returns {Boolean} - */ -function blendTile( tiledImage, tile, x, y, level, levelOpacity, currentTime ){ - var blendTimeMillis = 1000 * tiledImage.blendTime, - deltaTime, - opacity; - - if ( !tile.blendStart ) { - tile.blendStart = currentTime; - } - - deltaTime = currentTime - tile.blendStart; - opacity = blendTimeMillis ? Math.min( 1, deltaTime / ( blendTimeMillis ) ) : 1; - - if ( tiledImage.alwaysBlend ) { - opacity *= levelOpacity; - } - - tile.opacity = opacity; - - tiledImage.lastDrawn.push( tile ); - - if ( opacity === 1 ) { - setCoverage( tiledImage.coverage, level, x, y, true ); - tiledImage._hasOpaqueTile = true; - } else if ( deltaTime < blendTimeMillis ) { - return true; - } - - return false; -} +}); /** * @private @@ -1831,7 +2100,7 @@ function blendTile( tiledImage, tile, x, y, level, levelOpacity, currentTime ){ * @param {Number} y - The Y position of the tile. * @returns {Boolean} */ -function providesCoverage( coverage, level, x, y ) { +$.TileSource._providesCoverage = function( coverage, level, x, y ) { var rows, cols, i, j; @@ -1861,7 +2130,7 @@ function providesCoverage( coverage, level, x, y ) { coverage[ level ][ x ][ y ] === undefined || coverage[ level ][ x ][ y ] === true ); -} +}; /** * @private @@ -1876,18 +2145,18 @@ function providesCoverage( coverage, level, x, y ) { * @param {Number} y - The Y position of the tile. * @returns {Boolean} */ -function isCovered( coverage, level, x, y ) { +$.TileSource._isCovered = function( coverage, level, x, y ) { if ( x === undefined || y === undefined ) { - return providesCoverage( coverage, level + 1 ); + return this._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 ) + this._providesCoverage( coverage, level + 1, 2 * x, 2 * y ) && + this._providesCoverage( coverage, level + 1, 2 * x, 2 * y + 1 ) && + this._providesCoverage( coverage, level + 1, 2 * x + 1, 2 * y ) && + this._providesCoverage( coverage, level + 1, 2 * x + 1, 2 * y + 1 ) ); } -} +}; /** * @private @@ -1900,7 +2169,7 @@ function isCovered( coverage, level, x, y ) { * @param {Number} y - The Y position of the tile. * @param {Boolean} covers - Whether the tile provides coverage. */ -function setCoverage( coverage, level, x, y, covers ) { +$.TileSource._setCoverage = function( coverage, level, x, y, covers ) { if ( !coverage[ level ] ) { $.console.warn( "Setting coverage for a tile before its level's coverage has been reset: %s", @@ -1914,7 +2183,7 @@ function setCoverage( coverage, level, x, y, covers ) { } coverage[ level ][ x ][ y ] = covers; -} +}; /** * @private @@ -1926,35 +2195,9 @@ function setCoverage( coverage, level, x, y, covers ) { * @param {Object} coverage - A '3d' dictionary [level][x][y] --> Boolean. * @param {Number} level - The resolution level of tiles to completely reset. */ -function resetCoverage( coverage, level ) { +$.TileSource._resetCoverage = function( coverage, level ) { coverage[ level ] = {}; -} - -/** - * @private - * @inner - * Determines whether the 'last best' tile for the area is better than the - * tile in question. - * - * @param {OpenSeadragon.Tile} previousBest - * @param {OpenSeadragon.Tile} tile - * @returns {OpenSeadragon.Tile} The new best tile. - */ -function compareTiles( previousBest, tile ) { - if ( !previousBest ) { - return tile; - } - - if ( tile.visibility > previousBest.visibility ) { - return tile; - } else if ( tile.visibility === previousBest.visibility ) { - if ( tile.squaredDistance < previousBest.squaredDistance ) { - return tile; - } - } - - return previousBest; -} +}; /** * @private @@ -1973,7 +2216,7 @@ var DEFAULT_SUBPIXEL_ROUNDING_RULE = $.SUBPIXEL_ROUNDING_OCCURRENCES.NEVER; * @returns {Boolean} Returns true if the input value is none of the expected * {@link SUBPIXEL_ROUNDING_OCCURRENCES.ALWAYS}, {@link SUBPIXEL_ROUNDING_OCCURRENCES.ONLY_AT_REST} or {@link SUBPIXEL_ROUNDING_OCCURRENCES.NEVER} value. */ - function isSubPixelRoundingRuleUnknown(value) { +function isSubPixelRoundingRuleUnknown(value) { return value !== $.SUBPIXEL_ROUNDING_OCCURRENCES.ALWAYS && value !== $.SUBPIXEL_ROUNDING_OCCURRENCES.ONLY_AT_REST && value !== $.SUBPIXEL_ROUNDING_OCCURRENCES.NEVER; @@ -1988,7 +2231,7 @@ var DEFAULT_SUBPIXEL_ROUNDING_RULE = $.SUBPIXEL_ROUNDING_OCCURRENCES.NEVER; * @param {SUBPIXEL_ROUNDING_OCCURRENCES} value - The subpixel rounding enum value to normalize. * @returns {SUBPIXEL_ROUNDING_OCCURRENCES} Returns a valid subpixel rounding enum value. */ - function normalizeSubPixelRoundingRule(value) { +function normalizeSubPixelRoundingRule(value) { if (isSubPixelRoundingRuleUnknown(value)) { return DEFAULT_SUBPIXEL_ROUNDING_RULE; } @@ -2023,266 +2266,4 @@ function determineSubPixelRoundingRule(subPixelRoundingRules) { return normalizeSubPixelRoundingRule(subPixelRoundingRule); } -/** - * @private - * @inner - * Draws a TiledImage. - * @param {OpenSeadragon.TiledImage} tiledImage - * @param {OpenSeadragon.Tile[]} lastDrawn - An unordered list of Tiles drawn last frame. - */ -function drawTiles( tiledImage, lastDrawn ) { - if (tiledImage.opacity === 0 || (lastDrawn.length === 0 && !tiledImage.placeholderFillStyle)) { - return; - } - - var tile = lastDrawn[0]; - var useSketch; - - if (tile) { - useSketch = tiledImage.opacity < 1 || - (tiledImage.compositeOperation && - tiledImage.compositeOperation !== 'source-over') || - (!tiledImage._isBottomItem() && tile._hasTransparencyChannel()); - } - - var sketchScale; - var sketchTranslate; - - var zoom = tiledImage.viewport.getZoom(true); - var imageZoom = tiledImage.viewportToImageZoom(zoom); - - if (lastDrawn.length > 1 && - imageZoom > tiledImage.smoothTileEdgesMinZoom && - !tiledImage.iOSDevice && - tiledImage.getRotation(true) % 360 === 0 && // TODO: support tile edge smoothing with tiled image rotation. - $.supportsCanvas && tiledImage.viewer.useCanvas) { - // When zoomed in a lot (>100%) the tile edges are visible. - // So we have to composite them at ~100% and scale them up together. - // Note: Disabled on iOS devices per default as it causes a native crash - useSketch = true; - sketchScale = tile.getScaleForEdgeSmoothing(); - sketchTranslate = tile.getTranslationForEdgeSmoothing(sketchScale, - tiledImage._drawer.getCanvasSize(false), - tiledImage._drawer.getCanvasSize(true)); - } - - var bounds; - if (useSketch) { - if (!sketchScale) { - // Except when edge smoothing, we only clean the part of the - // sketch canvas we are going to use for performance reasons. - bounds = tiledImage.viewport.viewportToViewerElementRectangle( - tiledImage.getClippedBounds(true)) - .getIntegerBoundingBox(); - - if(tiledImage._drawer.viewer.viewport.getFlip()) { - if (tiledImage.viewport.degrees !== 0 || tiledImage.getRotation(true) % 360 !== 0){ - bounds.x = tiledImage._drawer.viewer.container.clientWidth - (bounds.x + bounds.width); - } - } - - bounds = bounds.times($.pixelDensityRatio); - } - tiledImage._drawer._clear(true, bounds); - } - - // When scaling, we must rotate only when blending the sketch canvas to - // avoid interpolation - if (!sketchScale) { - if (tiledImage.viewport.degrees !== 0) { - tiledImage._drawer._offsetForRotation({ - degrees: tiledImage.viewport.degrees, - useSketch: useSketch - }); - } - if (tiledImage.getRotation(true) % 360 !== 0) { - tiledImage._drawer._offsetForRotation({ - degrees: tiledImage.getRotation(true), - point: tiledImage.viewport.pixelFromPointNoRotate( - tiledImage._getRotationPoint(true), true), - useSketch: useSketch - }); - } - - if (tiledImage.viewport.degrees === 0 && tiledImage.getRotation(true) % 360 === 0){ - if(tiledImage._drawer.viewer.viewport.getFlip()) { - tiledImage._drawer._flip(); - } - } - } - - var usedClip = false; - if ( tiledImage._clip ) { - tiledImage._drawer.saveContext(useSketch); - - var box = tiledImage.imageToViewportRectangle(tiledImage._clip, true); - box = box.rotate(-tiledImage.getRotation(true), tiledImage._getRotationPoint(true)); - var clipRect = tiledImage._drawer.viewportToDrawerRectangle(box); - if (sketchScale) { - clipRect = clipRect.times(sketchScale); - } - if (sketchTranslate) { - clipRect = clipRect.translate(sketchTranslate); - } - tiledImage._drawer.setClip(clipRect, useSketch); - - usedClip = true; - } - - if (tiledImage._croppingPolygons) { - tiledImage._drawer.saveContext(useSketch); - try { - var polygons = tiledImage._croppingPolygons.map(function (polygon) { - return polygon.map(function (coord) { - var point = tiledImage - .imageToViewportCoordinates(coord.x, coord.y, true) - .rotate(-tiledImage.getRotation(true), tiledImage._getRotationPoint(true)); - var clipPoint = tiledImage._drawer.viewportCoordToDrawerCoord(point); - if (sketchScale) { - clipPoint = clipPoint.times(sketchScale); - } - return clipPoint; - }); - }); - tiledImage._drawer.clipWithPolygons(polygons, useSketch); - } catch (e) { - $.console.error(e); - } - usedClip = true; - } - - if ( tiledImage.placeholderFillStyle && tiledImage._hasOpaqueTile === false ) { - var placeholderRect = tiledImage._drawer.viewportToDrawerRectangle(tiledImage.getBounds(true)); - if (sketchScale) { - placeholderRect = placeholderRect.times(sketchScale); - } - if (sketchTranslate) { - placeholderRect = placeholderRect.translate(sketchTranslate); - } - - var fillStyle = null; - if ( typeof tiledImage.placeholderFillStyle === "function" ) { - fillStyle = tiledImage.placeholderFillStyle(tiledImage, tiledImage._drawer.context); - } - else { - fillStyle = tiledImage.placeholderFillStyle; - } - - tiledImage._drawer.drawRectangle(placeholderRect, fillStyle, useSketch); - } - - var subPixelRoundingRule = determineSubPixelRoundingRule(tiledImage.subPixelRoundingForTransparency); - - var shouldRoundPositionAndSize = false; - - if (subPixelRoundingRule === $.SUBPIXEL_ROUNDING_OCCURRENCES.ALWAYS) { - shouldRoundPositionAndSize = true; - } else if (subPixelRoundingRule === $.SUBPIXEL_ROUNDING_OCCURRENCES.ONLY_AT_REST) { - var isAnimating = tiledImage.viewer && tiledImage.viewer.isAnimating(); - shouldRoundPositionAndSize = !isAnimating; - } - - for (var i = lastDrawn.length - 1; i >= 0; i--) { - tile = lastDrawn[ i ]; - tiledImage._drawer.drawTile( tile, tiledImage._drawingHandler, useSketch, sketchScale, sketchTranslate, shouldRoundPositionAndSize ); - tile.beingDrawn = true; - - if( tiledImage.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.TiledImage} tiledImage - Which TiledImage is being drawn. - * @property {OpenSeadragon.Tile} tile - * @property {?Object} userData - Arbitrary subscriber-defined object. - */ - tiledImage.viewer.raiseEvent( 'tile-drawn', { - tiledImage: tiledImage, - tile: tile - }); - } - } - - if ( usedClip ) { - tiledImage._drawer.restoreContext( useSketch ); - } - - if (!sketchScale) { - if (tiledImage.getRotation(true) % 360 !== 0) { - tiledImage._drawer._restoreRotationChanges(useSketch); - } - if (tiledImage.viewport.degrees !== 0) { - tiledImage._drawer._restoreRotationChanges(useSketch); - } - } - - if (useSketch) { - if (sketchScale) { - if (tiledImage.viewport.degrees !== 0) { - tiledImage._drawer._offsetForRotation({ - degrees: tiledImage.viewport.degrees, - useSketch: false - }); - } - if (tiledImage.getRotation(true) % 360 !== 0) { - tiledImage._drawer._offsetForRotation({ - degrees: tiledImage.getRotation(true), - point: tiledImage.viewport.pixelFromPointNoRotate( - tiledImage._getRotationPoint(true), true), - useSketch: false - }); - } - } - tiledImage._drawer.blendSketch({ - opacity: tiledImage.opacity, - scale: sketchScale, - translate: sketchTranslate, - compositeOperation: tiledImage.compositeOperation, - bounds: bounds - }); - if (sketchScale) { - if (tiledImage.getRotation(true) % 360 !== 0) { - tiledImage._drawer._restoreRotationChanges(false); - } - if (tiledImage.viewport.degrees !== 0) { - tiledImage._drawer._restoreRotationChanges(false); - } - } - } - - if (!sketchScale) { - if (tiledImage.viewport.degrees === 0 && tiledImage.getRotation(true) % 360 === 0){ - if(tiledImage._drawer.viewer.viewport.getFlip()) { - tiledImage._drawer._flip(); - } - } - } - - drawDebugInfo( tiledImage, lastDrawn ); -} - -/** - * @private - * @inner - * Draws special debug information for a TiledImage if in debug mode. - * @param {OpenSeadragon.TiledImage} tiledImage - * @param {OpenSeadragon.Tile[]} lastDrawn - An unordered list of Tiles drawn last frame. - */ -function drawDebugInfo( tiledImage, lastDrawn ) { - if( tiledImage.debugMode ) { - for ( var i = lastDrawn.length - 1; i >= 0; i-- ) { - var tile = lastDrawn[ i ]; - try { - tiledImage._drawer.drawDebugInfo( - tile, lastDrawn.length, i, tiledImage); - } catch(e) { - $.console.error(e); - } - } - } -} - }( OpenSeadragon )); diff --git a/test/modules/basic.js b/test/modules/basic.js index 2eefd509..6cc2ad2b 100644 --- a/test/modules/basic.js +++ b/test/modules/basic.js @@ -58,7 +58,7 @@ assert.equal($(".openseadragon-message").length, 1, "Open failures should display a message"); - assert.ok(testLog.log.contains('["AJAX request returned %d: %s",404,"/test/data/not-a-real-file"]'), + assert.ok(testLog.error.contains('["AJAX request returned %d: %s",404,"/test/data/not-a-real-file"]'), "AJAX failures should be logged to the console"); done(); diff --git a/test/modules/strings.js b/test/modules/strings.js index ec1bf87a..78a324c5 100644 --- a/test/modules/strings.js +++ b/test/modules/strings.js @@ -22,11 +22,11 @@ QUnit.test("getInvalidString", function(assert) { assert.equal(OpenSeadragon.getString("Greeting"), "", "Handled unset string key"); - assert.ok(testLog.log.contains('["Untranslated source string:","Greeting"]'), + assert.ok(testLog.error.contains('["Untranslated source string:","Greeting"]'), 'Invalid string keys are logged'); assert.equal(OpenSeadragon.getString("Errors"), "", "Handled requesting parent key"); - assert.ok(testLog.log.contains('["Untranslated source string:","Errors"]'), + assert.ok(testLog.error.contains('["Untranslated source string:","Errors"]'), 'Invalid string parent keys are logged'); }); From 20594e5a5109bd2c169fe46aa512a5fe65af9386 Mon Sep 17 00:00:00 2001 From: Jirka Date: Wed, 23 Mar 2022 14:25:36 +0100 Subject: [PATCH 3/6] Stupid typo, added 'static' methods to a wrong class... --- src/tiledimage.js | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/tiledimage.js b/src/tiledimage.js index 368a8c41..3d361681 100644 --- a/src/tiledimage.js +++ b/src/tiledimage.js @@ -1176,7 +1176,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag // Stop the loop if lower-res tiles would all be covered by // already drawn tiles - if ($.TileSource._providesCoverage(this.coverage, level)) { + if ($.TiledImage._providesCoverage(this.coverage, level)) { break; } } @@ -1287,8 +1287,8 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag }); } - $.TileSource._resetCoverage(this.coverage, level); - $.TileSource._resetCoverage(this.loadingCoverage, level); + $.TiledImage._resetCoverage(this.coverage, level); + $.TiledImage._resetCoverage(this.loadingCoverage, level); //OK, a new drawing so do your calculations var cornerTiles = this._getCornerTiles(level, topLeftBound, bottomRightBound); @@ -1392,18 +1392,18 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag }); } - $.TileSource._setCoverage( this.coverage, level, x, y, false ); + $.TiledImage._setCoverage( this.coverage, level, x, y, false ); - var loadingCoverage = tile.loaded || tile.loading || $.TileSource._isCovered(this.loadingCoverage, level, x, y); - $.TileSource._setCoverage(this.loadingCoverage, level, x, y, loadingCoverage); + var loadingCoverage = tile.loaded || tile.loading || $.TiledImage._isCovered(this.loadingCoverage, level, x, y); + $.TiledImage._setCoverage(this.loadingCoverage, level, x, y, loadingCoverage); if ( !tile.exists ) { return best; } if ( haveDrawn && !drawTile ) { - if ( $.TileSource._isCovered( this.coverage, level, x, y ) ) { - $.TileSource._setCoverage( this.coverage, level, x, y, true ); + if ( $.TiledImage._isCovered( this.coverage, level, x, y ) ) { + $.TiledImage._setCoverage( this.coverage, level, x, y, true ); } else { drawTile = true; } @@ -1789,7 +1789,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag this.lastDrawn.push( tile ); if ( opacity === 1 ) { - $.TileSource._setCoverage( this.coverage, level, x, y, true ); + $.TiledImage._setCoverage( this.coverage, level, x, y, true ); this._hasOpaqueTile = true; } else if ( deltaTime < blendTimeMillis ) { return true; @@ -2100,7 +2100,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag * @param {Number} y - The Y position of the tile. * @returns {Boolean} */ -$.TileSource._providesCoverage = function( coverage, level, x, y ) { +$.TiledImage._providesCoverage = function( coverage, level, x, y ) { var rows, cols, i, j; @@ -2145,7 +2145,7 @@ $.TileSource._providesCoverage = function( coverage, level, x, y ) { * @param {Number} y - The Y position of the tile. * @returns {Boolean} */ -$.TileSource._isCovered = function( coverage, level, x, y ) { +$.TiledImage._isCovered = function( coverage, level, x, y ) { if ( x === undefined || y === undefined ) { return this._providesCoverage( coverage, level + 1 ); } else { @@ -2169,7 +2169,7 @@ $.TileSource._isCovered = function( coverage, level, x, y ) { * @param {Number} y - The Y position of the tile. * @param {Boolean} covers - Whether the tile provides coverage. */ -$.TileSource._setCoverage = function( coverage, level, x, y, covers ) { +$.TiledImage._setCoverage = function( coverage, level, x, y, covers ) { if ( !coverage[ level ] ) { $.console.warn( "Setting coverage for a tile before its level's coverage has been reset: %s", @@ -2195,7 +2195,7 @@ $.TileSource._setCoverage = function( coverage, level, x, y, covers ) { * @param {Object} coverage - A '3d' dictionary [level][x][y] --> Boolean. * @param {Number} level - The resolution level of tiles to completely reset. */ -$.TileSource._resetCoverage = function( coverage, level ) { +$.TiledImage._resetCoverage = function( coverage, level ) { coverage[ level ] = {}; }; From 7f784835a7492e28d881bd35702f7b915894fd1d Mon Sep 17 00:00:00 2001 From: Jirka Date: Fri, 25 Mar 2022 12:30:18 +0100 Subject: [PATCH 4/6] Revert 'spacing change' for licence disclaimer (TiledImage), fix tests on drag-end event no longer firing when mouse does not move (PR #2064) --- src/tiledimage.js | 64 +++++++++++++++++++++--------------------- test/modules/events.js | 4 +-- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/src/tiledimage.js b/src/tiledimage.js index 3d361681..afb5d48c 100644 --- a/src/tiledimage.js +++ b/src/tiledimage.js @@ -1,36 +1,36 @@ /* -* OpenSeadragon - TiledImage -* -* 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. -*/ + * OpenSeadragon - TiledImage + * + * 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( $ ){ diff --git a/test/modules/events.js b/test/modules/events.js index 7a2b00ca..6900b063 100644 --- a/test/modules/events.js +++ b/test/modules/events.js @@ -425,7 +425,7 @@ clickCount: 2, dblClickCount: 1, dragCount: 0, - dragEndCount: 2, // v2.5.0+ drag-end event now fired even if pointer didn't move (#1459) + dragEndCount: 0, // drag-end event no longer fired if pointer didn't move (#2064) insideElementPressed: true, insideElementReleased: true, contacts: 0, @@ -453,7 +453,7 @@ clickCount: 1, dblClickCount: 0, dragCount: 0, - dragEndCount: 1, // v2.5.0+ drag-end event now fired even if pointer didn't move (#1459) + dragEndCount: 0, // drag-end event no longer fired if pointer didn't move (#2064) insideElementPressed: true, insideElementReleased: true, contacts: 0, From 38e91f1fa58e2a1ddb5f0c91044927088e6a614c Mon Sep 17 00:00:00 2001 From: Jirka Date: Wed, 30 Mar 2022 13:27:23 +0200 Subject: [PATCH 5/6] Change coverage methods on TiledImage to be member methods. --- src/tiledimage.js | 231 +++++++++++++++++++++++----------------------- 1 file changed, 116 insertions(+), 115 deletions(-) diff --git a/src/tiledimage.js b/src/tiledimage.js index afb5d48c..b955a30b 100644 --- a/src/tiledimage.js +++ b/src/tiledimage.js @@ -1176,7 +1176,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag // Stop the loop if lower-res tiles would all be covered by // already drawn tiles - if ($.TiledImage._providesCoverage(this.coverage, level)) { + if (this._providesCoverage(this.coverage, level)) { break; } } @@ -1287,8 +1287,8 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag }); } - $.TiledImage._resetCoverage(this.coverage, level); - $.TiledImage._resetCoverage(this.loadingCoverage, level); + this._resetCoverage(this.coverage, level); + this._resetCoverage(this.loadingCoverage, level); //OK, a new drawing so do your calculations var cornerTiles = this._getCornerTiles(level, topLeftBound, bottomRightBound); @@ -1392,18 +1392,18 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag }); } - $.TiledImage._setCoverage( this.coverage, level, x, y, false ); + this._setCoverage( this.coverage, level, x, y, false ); - var loadingCoverage = tile.loaded || tile.loading || $.TiledImage._isCovered(this.loadingCoverage, level, x, y); - $.TiledImage._setCoverage(this.loadingCoverage, level, x, y, loadingCoverage); + var loadingCoverage = tile.loaded || tile.loading || this._isCovered(this.loadingCoverage, level, x, y); + this._setCoverage(this.loadingCoverage, level, x, y, loadingCoverage); if ( !tile.exists ) { return best; } if ( haveDrawn && !drawTile ) { - if ( $.TiledImage._isCovered( this.coverage, level, x, y ) ) { - $.TiledImage._setCoverage( this.coverage, level, x, y, true ); + if ( this._isCovered( this.coverage, level, x, y ) ) { + this._setCoverage( this.coverage, level, x, y, true ); } else { drawTile = true; } @@ -1789,7 +1789,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag this.lastDrawn.push( tile ); if ( opacity === 1 ) { - $.TiledImage._setCoverage( this.coverage, level, x, y, true ); + this._setCoverage( this.coverage, level, x, y, true ); this._hasOpaqueTile = true; } else if ( deltaTime < blendTimeMillis ) { return true; @@ -2080,124 +2080,125 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag } } } - } -}); + }, -/** - * @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. - * - * @param {Object} coverage - A '3d' dictionary [level][x][y] --> Boolean. - * @param {Number} level - The resolution level of the tile. - * @param {Number} x - The X position of the tile. - * @param {Number} y - The Y position of the tile. - * @returns {Boolean} - */ -$.TiledImage._providesCoverage = function( coverage, level, x, y ) { - var rows, - cols, - i, j; + /** + * @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. + * + * @param {Object} coverage - A '3d' dictionary [level][x][y] --> Boolean. + * @param {Number} level - The resolution level of the tile. + * @param {Number} x - The X position of the tile. + * @param {Number} y - The Y position of the tile. + * @returns {Boolean} + */ + _providesCoverage: function( coverage, level, x, y ) { + var rows, + cols, + i, j; - if ( !coverage[ level ] ) { - return false; - } + if ( !coverage[ level ] ) { + return false; + } - if ( x === undefined || y === undefined ) { - rows = coverage[ level ]; - for ( i in rows ) { - if ( Object.prototype.hasOwnProperty.call( rows, i ) ) { - cols = rows[ i ]; - for ( j in cols ) { - if ( Object.prototype.hasOwnProperty.call( cols, j ) && !cols[ j ] ) { - return false; + if ( x === undefined || y === undefined ) { + rows = coverage[ level ]; + for ( i in rows ) { + if ( Object.prototype.hasOwnProperty.call( rows, i ) ) { + cols = rows[ i ]; + for ( j in cols ) { + if ( Object.prototype.hasOwnProperty.call( cols, j ) && !cols[ j ] ) { + return false; + } } } } + + return true; } - 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. - * - * @param {Object} coverage - A '3d' dictionary [level][x][y] --> Boolean. - * @param {Number} level - The resolution level of the tile. - * @param {Number} x - The X position of the tile. - * @param {Number} y - The Y position of the tile. - * @returns {Boolean} - */ -$.TiledImage._isCovered = function( coverage, level, x, y ) { - if ( x === undefined || y === undefined ) { - return this._providesCoverage( coverage, level + 1 ); - } else { return ( - this._providesCoverage( coverage, level + 1, 2 * x, 2 * y ) && - this._providesCoverage( coverage, level + 1, 2 * x, 2 * y + 1 ) && - this._providesCoverage( coverage, level + 1, 2 * x + 1, 2 * y ) && - this._providesCoverage( coverage, level + 1, 2 * x + 1, 2 * y + 1 ) + 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. + * + * @param {Object} coverage - A '3d' dictionary [level][x][y] --> Boolean. + * @param {Number} level - The resolution level of the tile. + * @param {Number} x - The X position of the tile. + * @param {Number} y - The Y position of the tile. + * @returns {Boolean} + */ + _isCovered: function( coverage, level, x, y ) { + if ( x === undefined || y === undefined ) { + return this._providesCoverage( coverage, level + 1 ); + } else { + return ( + this._providesCoverage( coverage, level + 1, 2 * x, 2 * y ) && + this._providesCoverage( coverage, level + 1, 2 * x, 2 * y + 1 ) && + this._providesCoverage( coverage, level + 1, 2 * x + 1, 2 * y ) && + this._providesCoverage( coverage, level + 1, 2 * x + 1, 2 * y + 1 ) + ); + } + }, + + /** + * @private + * @inner + * Sets whether the given tile provides coverage or not. + * + * @param {Object} coverage - A '3d' dictionary [level][x][y] --> Boolean. + * @param {Number} level - The resolution level of the tile. + * @param {Number} x - The X position of the tile. + * @param {Number} y - The Y position of the tile. + * @param {Boolean} covers - Whether the tile provides coverage. + */ + _setCoverage: function( 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. + * + * @param {Object} coverage - A '3d' dictionary [level][x][y] --> Boolean. + * @param {Number} level - The resolution level of tiles to completely reset. + */ + _resetCoverage: function( coverage, level ) { + coverage[ level ] = {}; } -}; +}); -/** - * @private - * @inner - * Sets whether the given tile provides coverage or not. - * - * @param {Object} coverage - A '3d' dictionary [level][x][y] --> Boolean. - * @param {Number} level - The resolution level of the tile. - * @param {Number} x - The X position of the tile. - * @param {Number} y - The Y position of the tile. - * @param {Boolean} covers - Whether the tile provides coverage. - */ -$.TiledImage._setCoverage = function( 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. - * - * @param {Object} coverage - A '3d' dictionary [level][x][y] --> Boolean. - * @param {Number} level - The resolution level of tiles to completely reset. - */ -$.TiledImage._resetCoverage = function( coverage, level ) { - coverage[ level ] = {}; -}; /** * @private From 2c9d244023264ff170741ad03a5ad555ae7bc2b1 Mon Sep 17 00:00:00 2001 From: Ian Gilman Date: Thu, 31 Mar 2022 13:55:53 -0700 Subject: [PATCH 6/6] Changelog for #2134 --- changelog.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/changelog.txt b/changelog.txt index 303e1478..252a1d98 100644 --- a/changelog.txt +++ b/changelog.txt @@ -13,6 +13,8 @@ OPENSEADRAGON CHANGELOG * Fixed: Setting useCanvas to false would break the viewer (#2116 @rvv-bouvet) * Allow silencing multi-image warnings on viewport coordinate conversion functions (#2120 @claycoleman) * Fixed: Swiping fast multiple times made contact points in MouseTracker out of sync for touch events (#2121 @ronnymikalsen) +* Made MouseTracker more robust in certain scenarios (#2134 @Aiosa) +* Exposed TiledImage's private functions for better maintainability (#2134 @Aiosa) 3.0.0: