diff --git a/changelog.txt b/changelog.txt index a479cd2d..525f28e6 100644 --- a/changelog.txt +++ b/changelog.txt @@ -5,7 +5,7 @@ OPENSEADRAGON CHANGELOG * BREAKING CHANGE: Dropped support for IE11 (#2300, #2361 @AndrewADev) * DEPRECATION: The OpenSeadragon.createCallback function is no longer recommended (#2367 @akansjain) -* The viewer now uses WebGL when available (#2310, #2462, #2466, #2468, #2469, #2472, #2478, #2488, #2492 @pearcetm, @Aiosa, @thec0keman) +* The viewer now uses WebGL when available (#2310, #2462, #2466, #2468, #2469, #2472, #2478, #2488, #2492, #2521, #2537 @pearcetm, @Aiosa, @thec0keman) * Added webp to supported image formats (#2455 @BeebBenjamin) * Introduced maxTilesPerFrame option to allow loading more tiles simultaneously (#2387 @jetic83) * Now when creating a viewer or navigator, we leave its position style alone if possible (#2393 @VIRAT9358) @@ -20,6 +20,8 @@ OPENSEADRAGON CHANGELOG * Fixed: placeholderFillStyle didn't work properly when the image was rotated (#2469 @pearcetm) * Fixed: Sometimes exponential springs wouldn't ever settle (#2469 @pearcetm) * Fixed: The navigator wouldn't update its tracking rectangle when the navigator was resized (#2491 @pearcetm) +* Fixed: The drawer would improperly crop when the viewport was flipped and a tiled image was rotated (#2511 @pearcetm, @eug-L) +* Fixed: Flipped viewport caused image to be flipped again when going fullscreen or resizing (#2518 @pearcetm) 4.1.1: diff --git a/src/canvasdrawer.js b/src/canvasdrawer.js index e755c97b..e494d1f2 100644 --- a/src/canvasdrawer.js +++ b/src/canvasdrawer.js @@ -80,8 +80,6 @@ class CanvasDrawer extends OpenSeadragon.DrawerBase{ // Canvas default is "true", so this will only be changed if user specifies "false" in the options or via setImageSmoothinEnabled. this._imageSmoothingEnabled = true; - this._viewportFlipped = false; - // Since the tile-drawn and tile-drawing events are fired by this drawer, make sure handlers can be added for them this.viewer.allowEventHandler("tile-drawn"); this.viewer.allowEventHandler("tile-drawing"); @@ -118,7 +116,6 @@ class CanvasDrawer extends OpenSeadragon.DrawerBase{ this._prepareNewFrame(); // prepare to draw a new frame if(this.viewer.viewport.getFlip() !== this._viewportFlipped){ this._flip(); - this._viewportFlipped = !this._viewportFlipped; } for(const tiledImage of tiledImages){ if (tiledImage.opacity !== 0) { @@ -147,10 +144,12 @@ class CanvasDrawer extends OpenSeadragon.DrawerBase{ } /** + * @param {TiledImage} tiledImage the tiled image that is calling the function * @returns {Boolean} Whether this drawer requires enforcing minimum tile overlap to avoid showing seams. + * @private */ - minimumOverlapRequired() { - return true; + minimumOverlapRequired(tiledImage) { + return true; } @@ -189,6 +188,14 @@ class CanvasDrawer extends OpenSeadragon.DrawerBase{ context.restore(); } + /** + * Test whether the current context is flipped or not + * @private + */ + get _viewportFlipped(){ + return this.context.getTransform().a < 0; + } + /** * Fires the tile-drawing event. * @private diff --git a/src/drawerbase.js b/src/drawerbase.js index 0a0d04ce..083aba79 100644 --- a/src/drawerbase.js +++ b/src/drawerbase.js @@ -141,12 +141,13 @@ OpenSeadragon.DrawerBase = class DrawerBase{ } /** + * @param {TiledImage} tiledImage the tiled image that is calling the function * @returns {Boolean} Whether this drawer requires enforcing minimum tile overlap to avoid showing seams. * @private */ - minimumOverlapRequired() { + minimumOverlapRequired(tiledImage) { return false; - } + } /** diff --git a/src/htmldrawer.js b/src/htmldrawer.js index 80f851e4..8b2a9305 100644 --- a/src/htmldrawer.js +++ b/src/htmldrawer.js @@ -86,11 +86,13 @@ class HTMLDrawer extends OpenSeadragon.DrawerBase{ } /** + * @param {TiledImage} tiledImage the tiled image that is calling the function * @returns {Boolean} Whether this drawer requires enforcing minimum tile overlap to avoid showing seams. + * @private */ - minimumOverlapRequired() { + minimumOverlapRequired(tiledImage) { return true; - } + } /** * create the HTML element (e.g. canvas, div) that the image will be drawn into diff --git a/src/tiledimage.js b/src/tiledimage.js index 20fa2ebf..c1562497 100644 --- a/src/tiledimage.js +++ b/src/tiledimage.js @@ -72,8 +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}. @@ -1081,7 +1081,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag // _tilesToDraw might have been updated by the update; refresh it tileArray = this._tilesToDraw.flat(); - // mark the tiles as being drawn, so that they won't be discarded from + // mark the tiles as being drawn, so that they won't be discarded from // the tileCache tileArray.forEach(tileInfo => { tileInfo.tile.beingDrawn = true; @@ -1312,10 +1312,9 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag // returns boolean flag of whether the image should be marked as fully loaded _updateLevelsForViewport: function(){ var levelsInterval = this._getLevelsInterval(); - var lowestLevel = levelsInterval.lowestLevel; - var highestLevel = levelsInterval.highestLevel; + var lowestLevel = levelsInterval.lowestLevel; // the lowest level we should draw at our current zoom + var highestLevel = levelsInterval.highestLevel; // the highest level we should draw at our current zoom var bestTiles = []; - var haveDrawn = false; var drawArea = this.getDrawArea(); var currentTime = $.now(); @@ -1339,6 +1338,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag for(let i = 0, level = highestLevel; level >= lowestLevel; level--, i++){ levelList[i] = level; } + // if a single-tile level is loaded, add that to the end of the list // as a fallback to use during zooming out, until a lower-res tile is // loaded @@ -1350,32 +1350,32 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag ); if(tile && tile.isBottomMost && tile.isRightMost && tile.loaded){ levelList.push(level); - levelList.hasHigherResolutionFallback = true; break; } } - // Update any level that will be drawn + // Update any level that will be drawn. + // We are iterating from highest resolution to lowest resolution + // Once a level fully covers the viewport the loop is halted and + // lower-resolution levels are skipped + let useLevel = false; for (let i = 0; i < levelList.length; i++) { let level = levelList[i]; - var drawLevel = false; - //Avoid calculations for draw if we have already drawn this var currentRenderPixelRatio = this.viewport.deltaPixelsFromPointsNoRotate( this.source.getPixelRatio(level), true ).x * this._scaleSpring.current.value; - if (i === levelList.length - 1 || - (!haveDrawn && currentRenderPixelRatio >= this.minPixelRatio) ) { - drawLevel = true; - haveDrawn = true; - } else if (!haveDrawn) { + // make sure we skip levels until currentRenderPixelRatio becomes >= minPixelRatio + // but always use the last level in the list so we draw something + if (i === levelList.length - 1 || currentRenderPixelRatio >= this.minPixelRatio ) { + useLevel = true; + } else if (!useLevel) { continue; } - //Perform calculations for draw if we haven't drawn this var targetRenderPixelRatio = this.viewport.deltaPixelsFromPointsNoRotate( this.source.getPixelRatio(level), false @@ -1398,10 +1398,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag ); // Update the level and keep track of 'best' tiles to load - // the bestTiles var result = this._updateLevel( - haveDrawn, - drawLevel, level, levelOpacity, levelVisibility, @@ -1558,8 +1555,6 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag /** * Updates all tiles at a given resolution level. * @private - * @param {Boolean} haveDrawn - * @param {Boolean} drawLevel * @param {Number} level * @param {Number} levelOpacity * @param {Number} levelVisibility @@ -1568,8 +1563,8 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag * @param {OpenSeadragon.Tile[]} best Array of the current best tiles * @returns {Object} Dictionary {bestTiles: OpenSeadragon.Tile - the current "best" tiles to draw, updatedTiles: OpenSeadragon.Tile) - the updated tiles}. */ - _updateLevel: function(haveDrawn, drawLevel, level, levelOpacity, - levelVisibility, drawArea, currentTime, best) { + _updateLevel: function(level, levelOpacity, + levelVisibility, drawArea, currentTime, best) { var topLeftBound = drawArea.getBoundingBox().getTopLeft(); var bottomRightBound = drawArea.getBoundingBox().getBottomRight(); @@ -1583,7 +1578,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag * @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} havedrawn - deprecated, always true (kept for backwards compatibility) * @property {Object} level * @property {Object} opacity * @property {Object} visibility @@ -1596,7 +1591,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag */ this.viewer.raiseEvent('update-level', { tiledImage: this, - havedrawn: haveDrawn, + havedrawn: true, // deprecated, kept for backwards compatibility level: level, opacity: levelOpacity, visibility: levelVisibility, @@ -1651,8 +1646,6 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag } var result = this._updateTile( - drawLevel, - haveDrawn, flippedX, y, level, levelVisibility, @@ -1706,7 +1699,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag tileCenter = positionT.plus( sizeT.divide( 2 ) ), tileSquaredDistance = viewportCenter.squaredDistanceTo( tileCenter ); - if(this.viewer.drawer.minimumOverlapRequired()){ + if(this.viewer.drawer.minimumOverlapRequired(this)){ if ( !overlap ) { sizeC = sizeC.plus( new $.Point(1, 1)); } @@ -1729,8 +1722,6 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag /** * Update a single tile at a particular resolution level. * @private - * @param {Boolean} haveDrawn - * @param {Boolean} drawLevel * @param {Number} x * @param {Number} y * @param {Number} level @@ -1741,16 +1732,15 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag * @param {OpenSeadragon.Tile} best - The current "best" tile to draw. * @returns {Object} Dictionary {bestTiles: OpenSeadragon.Tile[] - the current best tiles, tile: OpenSeadragon.Tile the current tile} */ - _updateTile: function( haveDrawn, drawLevel, x, y, level, - levelVisibility, viewportCenter, numberOfTiles, currentTime, best){ + _updateTile: function( x, y, level, + levelVisibility, viewportCenter, numberOfTiles, currentTime, best){ var tile = this._getTile( x, y, level, currentTime, numberOfTiles - ), - drawTile = drawLevel; + ); if( this.viewer ){ /** @@ -1784,20 +1774,6 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag if (tile.loaded && tile.opacity === 1){ this._setCoverage( this.coverage, level, x, y, true ); } - if ( haveDrawn && !drawTile ) { - if ( this._isCovered( this.coverage, level, x, y ) ) { - this._setCoverage( this.coverage, level, x, y, true ); - } else { - drawTile = true; - } - } - - if ( !drawTile ) { - return { - bestTiles: best, - tile: tile - }; - } this._positionTile( tile, @@ -2183,9 +2159,11 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag return -1; } if (a.visibility === b.visibility) { + // sort by smallest squared distance return (a.squaredDistance - b.squaredDistance); } else { - return (a.visibility - b.visibility); + // sort by largest visibility value + return (b.visibility - a.visibility); } }); }, diff --git a/src/webgldrawer.js b/src/webgldrawer.js index 25bbda66..a8167b00 100644 --- a/src/webgldrawer.js +++ b/src/webgldrawer.js @@ -210,6 +210,16 @@ return 'webgl'; } + /** + * @param {TiledImage} tiledImage the tiled image that is calling the function + * @returns {Boolean} Whether this drawer requires enforcing minimum tile overlap to avoid showing seams. + * @private + */ + minimumOverlapRequired(tiledImage) { + // return true if the tiled image is tainted, since the backup canvas drawer will be used. + return tiledImage.isTainted(); + } + /** * create the HTML element (canvas in this case) that the image will be drawn into * @private