Merge branch 'master' into rotation-seams

This commit is contained in:
pearcetm 2023-11-29 17:28:42 -05:00 committed by GitHub
commit 36d8b3d9ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 715 additions and 220 deletions

View File

@ -165,8 +165,11 @@ module.exports = function(grunt) {
normal: { normal: {
options: { options: {
urls: [ "http://localhost:8000/test/test.html" ], urls: [ "http://localhost:8000/test/test.html" ],
timeout: 10000 timeout: 10000,
} puppeteer: {
headless: 'new'
}
},
}, },
coverage: { coverage: {
options: { options: {

View File

@ -4,6 +4,14 @@ OPENSEADRAGON CHANGELOG
5.0.0: (in progress...) 5.0.0: (in progress...)
* BREAKING CHANGE: Dropped support for IE11 (#2300, #2361 @AndrewADev) * BREAKING CHANGE: Dropped support for IE11 (#2300, #2361 @AndrewADev)
* DEPRECATION: The OpenSeadragon.createCallback function is no longer recommended (#2367 @akansjain)
* 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)
* Test improvements (#2382 @AndrewADev)
* MouseTracker options documentation fixes (#2389 @msalsbery)
* Fixed: Sometimes if the viewport was flipped and the user zoomed in far enough, it would flip back (#2364 @SebDelile)
* Fixed: Strange behavior if IIIF sizes were not in ascending order (#2416 @lutzhelm)
* Fixed: Two-finger tap on a Mac trackpad would zoom you out (#2431 @cavenel)
4.1.0: 4.1.0:

717
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -35,7 +35,7 @@
"grunt-contrib-compress": "^2.0.0", "grunt-contrib-compress": "^2.0.0",
"grunt-contrib-concat": "^2.1.0", "grunt-contrib-concat": "^2.1.0",
"grunt-contrib-connect": "^3.0.0", "grunt-contrib-connect": "^3.0.0",
"grunt-contrib-qunit": "^6.2.0", "grunt-contrib-qunit": "^7.0.1",
"grunt-contrib-uglify": "^5.0.1", "grunt-contrib-uglify": "^5.0.1",
"grunt-contrib-watch": "^1.1.0", "grunt-contrib-watch": "^1.1.0",
"grunt-eslint": "^24.0.1", "grunt-eslint": "^24.0.1",

View File

@ -59,7 +59,9 @@
if( this.element ){ if( this.element ){
this.element = $.getElement( this.element ); this.element = $.getElement( this.element );
this.element.appendChild( this.container ); this.element.appendChild( this.container );
this.element.style.position = 'relative'; if( $.getElementStyle(this.element).position === 'static' ){
this.element.style.position = 'relative';
}
this.container.style.width = '100%'; this.container.style.width = '100%';
this.container.style.height = '100%'; this.container.style.height = '100%';
} }

View File

@ -145,7 +145,7 @@ $.IIIFTileSource = function( options ){
if( this.sizes ) { if( this.sizes ) {
var sizeLength = this.sizes.length; var sizeLength = this.sizes.length;
if ( (sizeLength === options.maxLevel) || (sizeLength === options.maxLevel + 1) ) { if ( (sizeLength === options.maxLevel) || (sizeLength === options.maxLevel + 1) ) {
this.levelSizes = this.sizes; this.levelSizes = this.sizes.slice().sort(( size1, size2 ) => size1.width - size2.width);
// Need to take into account that the list may or may not include the full resolution size // Need to take into account that the list may or may not include the full resolution size
if( sizeLength === options.maxLevel ) { if( sizeLength === options.maxLevel ) {
this.levelSizes.push( {width: this.width, height: this.height} ); this.levelSizes.push( {width: this.width, height: this.height} );

View File

@ -57,16 +57,16 @@
* @param {Boolean} [options.startDisabled=false] * @param {Boolean} [options.startDisabled=false]
* If true, event tracking on the element will not start until * If true, event tracking on the element will not start until
* {@link OpenSeadragon.MouseTracker.setTracking|setTracking} is called. * {@link OpenSeadragon.MouseTracker.setTracking|setTracking} is called.
* @param {Number} options.clickTimeThreshold * @param {Number} [options.clickTimeThreshold=300]
* The number of milliseconds within which a pointer down-up event combination * The number of milliseconds within which a pointer down-up event combination
* will be treated as a click gesture. * will be treated as a click gesture.
* @param {Number} options.clickDistThreshold * @param {Number} [options.clickDistThreshold=5]
* The maximum distance allowed between a pointer down event and a pointer up event * The maximum distance allowed between a pointer down event and a pointer up event
* to be treated as a click gesture. * to be treated as a click gesture.
* @param {Number} options.dblClickTimeThreshold * @param {Number} [options.dblClickTimeThreshold=300]
* The number of milliseconds within which two pointer down-up event combinations * The number of milliseconds within which two pointer down-up event combinations
* will be treated as a double-click gesture. * will be treated as a double-click gesture.
* @param {Number} options.dblClickDistThreshold * @param {Number} [options.dblClickDistThreshold=20]
* The maximum distance allowed between two pointer click events * The maximum distance allowed between two pointer click events
* to be treated as a click gesture. * to be treated as a click gesture.
* @param {Number} [options.stopDelay=50] * @param {Number} [options.stopDelay=50]
@ -2070,7 +2070,7 @@
// y-index scrolling. // y-index scrolling.
// event.deltaMode: 0=pixel, 1=line, 2=page // event.deltaMode: 0=pixel, 1=line, 2=page
// TODO: Deltas in pixel mode should be accumulated then a scroll value computed after $.DEFAULT_SETTINGS.pixelsPerWheelLine threshold reached // TODO: Deltas in pixel mode should be accumulated then a scroll value computed after $.DEFAULT_SETTINGS.pixelsPerWheelLine threshold reached
nDelta = event.deltaY < 0 ? 1 : -1; nDelta = event.deltaY ? (event.deltaY < 0 ? 1 : -1) : 0;
eventInfo = { eventInfo = {
originalEvent: event, originalEvent: event,

View File

@ -300,6 +300,12 @@
* @property {Number} [rotationIncrement=90] * @property {Number} [rotationIncrement=90]
* The number of degrees to rotate right or left when the rotate buttons or keyboard shortcuts are activated. * The number of degrees to rotate right or left when the rotate buttons or keyboard shortcuts are activated.
* *
* @property {Number} [maxTilesPerFrame=1]
* The number of tiles loaded per frame. As the frame rate of the client's machine is usually high (e.g., 50 fps),
* one tile per frame should be a good choice. However, for large screens or lower frame rates, the number of
* loaded tiles per frame can be adjusted here. Reasonable values might be 2 or 3 tiles per frame.
* (Note that the actual frame rate is given by the client's browser and machine).
*
* @property {Number} [pixelsPerWheelLine=40] * @property {Number} [pixelsPerWheelLine=40]
* For pixel-resolution scrolling devices, the number of pixels equal to one scroll line. * For pixel-resolution scrolling devices, the number of pixels equal to one scroll line.
* *
@ -1298,6 +1304,7 @@ function OpenSeadragon( options ){
preserveImageSizeOnResize: false, // requires autoResize=true preserveImageSizeOnResize: false, // requires autoResize=true
minScrollDeltaTime: 50, minScrollDeltaTime: 50,
rotationIncrement: 90, rotationIncrement: 90,
maxTilesPerFrame: 1,
//DEFAULT CONTROL SETTINGS //DEFAULT CONTROL SETTINGS
showSequenceControl: true, //SEQUENCE showSequenceControl: true, //SEQUENCE
@ -2277,25 +2284,12 @@ function OpenSeadragon( options ){
event.stopPropagation(); event.stopPropagation();
}, },
// Deprecated
/**
* Similar to OpenSeadragon.delegate, but it does not immediately call
* the method on the object, returning a function which can be called
* repeatedly to delegate the method. It also allows additional arguments
* to be passed during construction which will be added during each
* invocation, and each invocation can add additional arguments as well.
*
* @function
* @param {Object} object
* @param {Function} method
* @param [args] any additional arguments are passed as arguments to the
* created callback
* @returns {Function}
*/
createCallback: function( object, method ) { createCallback: function( object, method ) {
//TODO: This pattern is painful to use and debug. It's much cleaner //TODO: This pattern is painful to use and debug. It's much cleaner
// to use pinning plus anonymous functions. Get rid of this // to use pinning plus anonymous functions. Get rid of this
// pattern! // pattern!
console.error('The createCallback function is deprecated and will be removed in future versions. Please use alternativeFunction instead.');
var initialArgs = [], var initialArgs = [],
i; i;
for ( i = 2; i < arguments.length; i++ ) { for ( i = 2; i < arguments.length; i++ ) {

View File

@ -185,7 +185,8 @@ $.TiledImage = function( options ) {
opacity: $.DEFAULT_SETTINGS.opacity, opacity: $.DEFAULT_SETTINGS.opacity,
preload: $.DEFAULT_SETTINGS.preload, preload: $.DEFAULT_SETTINGS.preload,
compositeOperation: $.DEFAULT_SETTINGS.compositeOperation, compositeOperation: $.DEFAULT_SETTINGS.compositeOperation,
subPixelRoundingForTransparency: $.DEFAULT_SETTINGS.subPixelRoundingForTransparency subPixelRoundingForTransparency: $.DEFAULT_SETTINGS.subPixelRoundingForTransparency,
maxTilesPerFrame: $.DEFAULT_SETTINGS.maxTilesPerFrame
}, options ); }, options );
this._preload = this.preload; this._preload = this.preload;
@ -1277,7 +1278,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
var levelsInterval = this._getLevelsInterval(); var levelsInterval = this._getLevelsInterval();
var lowestLevel = levelsInterval.lowestLevel; var lowestLevel = levelsInterval.lowestLevel;
var highestLevel = levelsInterval.highestLevel; var highestLevel = levelsInterval.highestLevel;
var bestTile = null; var bestTiles = [];
var haveDrawn = false; var haveDrawn = false;
var drawArea = this.getDrawArea(); var drawArea = this.getDrawArea();
var currentTime = $.now(); var currentTime = $.now();
@ -1360,7 +1361,8 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
optimalRatio - targetRenderPixelRatio optimalRatio - targetRenderPixelRatio
); );
// Update the level and keep track of 'best' tile to load // Update the level and keep track of 'best' tiles to load
var result = this._updateLevel( var result = this._updateLevel(
haveDrawn, haveDrawn,
drawLevel, drawLevel,
@ -1369,10 +1371,10 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
levelVisibility, levelVisibility,
drawArea, drawArea,
currentTime, currentTime,
bestTile bestTiles
); );
bestTile = result.best; bestTiles = result.best;
var tiles = result.tiles.filter(tile => tile.loaded); var tiles = result.tiles.filter(tile => tile.loaded);
var makeTileInfoObject = (function(level, levelOpacity, currentTime){ var makeTileInfoObject = (function(level, levelOpacity, currentTime){
return function(tile){ return function(tile){
@ -1394,9 +1396,15 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
} }
} }
// Load the new 'best' tile
if (bestTile && !bestTile.context2D) { // Load the new 'best' n tiles
this._loadTile(bestTile, currentTime); if (bestTiles && bestTiles.length > 0) {
bestTiles.forEach(function (tile) {
if (tile && !tile.context2D) {
this._loadTile(tile, currentTime);
}
}, this);
this._needsDraw = true; this._needsDraw = true;
return false; return false;
} else { } else {
@ -1523,7 +1531,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
* @param {Number} levelVisibility * @param {Number} levelVisibility
* @param {OpenSeadragon.Rect} drawArea * @param {OpenSeadragon.Rect} drawArea
* @param {Number} currentTime * @param {Number} currentTime
* @param {Object} result Dictionary {best: OpenSeadragon.Tile - the current "best" tile to draw, tiles: Array(OpenSeadragon.Tile) - the updated tiles}. * @param {Object} result Dictionary {best: OpenSeadragon.Tile - the current "best" tiles to draw, tiles: Array(OpenSeadragon.Tile) - the updated tiles}.
*/ */
_updateLevel: function(haveDrawn, drawLevel, level, levelOpacity, _updateLevel: function(haveDrawn, drawLevel, level, levelOpacity,
levelVisibility, drawArea, currentTime, best) { levelVisibility, drawArea, currentTime, best) {
@ -1548,7 +1556,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
* @property {Object} topleft deprecated, use drawArea instead * @property {Object} topleft deprecated, use drawArea instead
* @property {Object} bottomright deprecated, use drawArea instead * @property {Object} bottomright deprecated, use drawArea instead
* @property {Object} currenttime * @property {Object} currenttime
* @property {Object} best * @property {Object[]} best
* @property {?Object} userData - Arbitrary subscriber-defined object. * @property {?Object} userData - Arbitrary subscriber-defined object.
*/ */
this.viewer.raiseEvent('update-level', { this.viewer.raiseEvent('update-level', {
@ -1782,7 +1790,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
// the tile is already in the download queue // the tile is already in the download queue
this._tilesLoading++; this._tilesLoading++;
} else if (!loadingCoverage) { } else if (!loadingCoverage) {
best = this._compareTiles( best, tile ); best = this._compareTiles( best, tile, this.maxTilesPerFrame );
} }
return { return {
@ -2117,24 +2125,22 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
/** /**
* @private * @private
* @inner * @inner
* Determines whether the 'last best' tile for the area is better than the * Determines the 'best tiles' from the given 'last best' tiles and the
* tile in question. * tile in question.
* *
* @param {OpenSeadragon.Tile} previousBest * @param {OpenSeadragon.Tile[]} previousBest The best tiles so far.
* @param {OpenSeadragon.Tile} tile * @param {OpenSeadragon.Tile} tile The new tile to consider.
* @returns {OpenSeadragon.Tile} The new best tile. * @param {Number} maxNTiles The max number of best tiles.
* @returns {OpenSeadragon.Tile[]} The new best tiles.
*/ */
_compareTiles: function( previousBest, tile ) { _compareTiles: function( previousBest, tile, maxNTiles ) {
if ( !previousBest ) { if ( !previousBest ) {
return tile; return [tile];
} }
previousBest.push(tile);
if ( tile.visibility > previousBest.visibility ) { this._sortTiles(previousBest);
return tile; if (previousBest.length > maxNTiles) {
} else if ( tile.visibility === previousBest.visibility ) { previousBest.pop();
if ( tile.squaredDistance < previousBest.squaredDistance ) {
return tile;
}
} }
return previousBest; return previousBest;
}, },

View File

@ -1662,6 +1662,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
minZoomImageRatio: _this.minZoomImageRatio, minZoomImageRatio: _this.minZoomImageRatio,
wrapHorizontal: _this.wrapHorizontal, wrapHorizontal: _this.wrapHorizontal,
wrapVertical: _this.wrapVertical, wrapVertical: _this.wrapVertical,
maxTilesPerFrame: _this.maxTilesPerFrame,
immediateRender: _this.immediateRender, immediateRender: _this.immediateRender,
blendTime: _this.blendTime, blendTime: _this.blendTime,
alwaysBlend: _this.alwaysBlend, alwaysBlend: _this.alwaysBlend,

View File

@ -0,0 +1,35 @@
<!DOCTYPE html>
<html>
<head>
<title>OpenSeadragon maxTilesPerFrame Demo</title>
<script type="text/javascript" src='../../build/openseadragon/openseadragon.js'></script>
<script type="text/javascript" src='../lib/jquery-1.9.1.min.js'></script>
<style type="text/css">
.openseadragon1 {
width: 100vw;
height: 100vh;
}
</style>
</head>
<body>
<div>
Simple demo page to show an OpenSeadragon viewer with a higher maxTilesPerFrame.
</div>
<div id="contentDiv" class="openseadragon1"></div>
<script type="text/javascript">
var viewer = OpenSeadragon({
debugMode: true,
id: "contentDiv",
prefixUrl: "../../build/openseadragon/images/",
tileSources: "https://openseadragon.github.io/example-images/duomo/duomo.dzi",
showNavigator:true,
debugMode:true,
maxTilesPerFrame:3,
});
</script>
</body>
</html>

View File

@ -234,25 +234,36 @@
return; return;
} }
viewer.addHandler("open", function () { viewer.addHandler('open', function () {
assert.ok(!OpenSeadragon.isFullScreen(), 'Started out not fullscreen'); assert.ok(!OpenSeadragon.isFullScreen(), 'Started out not fullscreen');
const checkEnteringPreFullScreen = function(event) { const checkEnteringPreFullScreen = (event) => {
viewer.removeHandler('pre-full-screen', checkEnteringPreFullScreen); viewer.removeHandler('pre-full-screen', checkEnteringPreFullScreen);
assert.ok(event.fullScreen, 'Switching to fullscreen'); assert.ok(event.fullScreen, 'Switching to fullscreen');
assert.ok(!OpenSeadragon.isFullScreen(), 'Not yet fullscreen'); assert.ok(!OpenSeadragon.isFullScreen(), 'Not yet fullscreen');
}; };
// The fullscreen mode is always denied during tests so we are const checkExitingFullScreen = (event) => {
// exiting directly.
const checkExitingFullScreen = function(event) {
viewer.removeHandler('full-screen', checkExitingFullScreen); viewer.removeHandler('full-screen', checkExitingFullScreen);
assert.ok(!event.fullScreen, 'Exiting fullscreen'); assert.ok(!event.fullScreen, 'Disabling fullscreen');
assert.ok(!OpenSeadragon.isFullScreen(), 'Disabled fullscreen'); assert.ok(!OpenSeadragon.isFullScreen(), 'Fullscreen disabled');
done(); done();
}
// The 'new' headless mode allows us to enter fullscreen, so verify
// that we see the correct values returned. We will then close out
// of fullscreen to check the same values when exiting.
const checkAcquiredFullScreen = (event) => {
viewer.removeHandler('full-screen', checkAcquiredFullScreen);
viewer.addHandler('full-screen', checkExitingFullScreen);
assert.ok(event.fullScreen, 'Acquired fullscreen');
assert.ok(OpenSeadragon.isFullScreen(), 'Fullscreen enabled');
viewer.setFullScreen(false);
}; };
viewer.addHandler("pre-full-screen", checkEnteringPreFullScreen);
viewer.addHandler("full-screen", checkExitingFullScreen);
viewer.addHandler('pre-full-screen', checkEnteringPreFullScreen);
viewer.addHandler('full-screen', checkAcquiredFullScreen);
viewer.setFullScreen(true); viewer.setFullScreen(true);
}); });

View File

@ -107,6 +107,21 @@
], ],
"profile": "level0" "profile": "level0"
}, },
infoJson3level0WithTiles = {
"@context": "http://iiif.io/api/image/3/context.json",
"id": id,
"width": 2000,
"height": 1000,
"tiles": [
{ "width": 256, "scaleFactors": [ 2, 4, 1 ] }
],
"sizes": [
{ width: 2000, height: 1000 },
{ width: 1000, height: 500 },
{ width: 500, height: 250 }
],
"profile": "level0"
},
infoJson3level0ContextExtension = { infoJson3level0ContextExtension = {
"@context": [ "@context": [
"http://iiif.io/api/image/3/context.json", "http://iiif.io/api/image/3/context.json",
@ -141,6 +156,21 @@
"width": 2000, "width": 2000,
"height": 1000, "height": 1000,
"profile": "level1" "profile": "level1"
},
infoJson3DescendingSizeOrder = {
"@context": "http://iiif.io/api/image/3/context.json",
"id": id,
"width": 2000,
"height": 1000,
"tiles": [
{ "width": 512, "scaleFactors": [ 1, 2, 4 ] }
],
"sizes": [
{ width: 2000, height: 1000 },
{ width: 1000, height: 500 },
{ width: 500, height: 250 }
],
"profile": "level1",
}; };
QUnit.module('IIIF'); QUnit.module('IIIF');
@ -238,6 +268,11 @@
assert.equal(source2Level0.getTileUrl(0, 0, 0), "http://example.com/identifier/full/1000,/0/default.jpg"); assert.equal(source2Level0.getTileUrl(0, 0, 0), "http://example.com/identifier/full/1000,/0/default.jpg");
assert.equal(source2Level0.getTileUrl(1, 0, 0), "http://example.com/identifier/full/2000,/0/default.jpg"); assert.equal(source2Level0.getTileUrl(1, 0, 0), "http://example.com/identifier/full/2000,/0/default.jpg");
var source3Level0WithTiles = getSource(infoJson3level0WithTiles);
assert.equal(source3Level0WithTiles.getTileUrl(0, 0, 0), "http://example.com/identifier/0,0,1024,1000/256,250/0/default.jpg");
assert.equal(source3Level0WithTiles.getTileUrl(1, 1, 0), "http://example.com/identifier/512,0,512,512/256,256/0/default.jpg");
assert.equal(source3Level0WithTiles.getTileUrl(2, 0, 0), "http://example.com/identifier/0,0,256,256/256,256/0/default.jpg");
var source3Level1 = getSource(infoJson3level1); var source3Level1 = getSource(infoJson3level1);
assert.equal(source3Level1.getTileUrl(0, 0, 0), "http://example.com/identifier/full/8,4/0/default.jpg"); assert.equal(source3Level1.getTileUrl(0, 0, 0), "http://example.com/identifier/full/8,4/0/default.jpg");
assert.equal(source3Level1.getTileUrl(7, 0, 0), "http://example.com/identifier/0,0,1024,1000/512,500/0/default.jpg"); assert.equal(source3Level1.getTileUrl(7, 0, 0), "http://example.com/identifier/0,0,1024,1000/512,500/0/default.jpg");
@ -246,6 +281,11 @@
assert.equal(source3Level1.getTileUrl(8, 3, 0), "http://example.com/identifier/1536,0,464,512/464,512/0/default.jpg"); assert.equal(source3Level1.getTileUrl(8, 3, 0), "http://example.com/identifier/1536,0,464,512/464,512/0/default.jpg");
assert.equal(source3Level1.getTileUrl(8, 0, 1), "http://example.com/identifier/0,512,512,488/512,488/0/default.jpg"); assert.equal(source3Level1.getTileUrl(8, 0, 1), "http://example.com/identifier/0,512,512,488/512,488/0/default.jpg");
assert.equal(source3Level1.getTileUrl(8, 3, 1), "http://example.com/identifier/1536,512,464,488/464,488/0/default.jpg"); assert.equal(source3Level1.getTileUrl(8, 3, 1), "http://example.com/identifier/1536,512,464,488/464,488/0/default.jpg");
var source3DescendingSizeOrder = getSource(infoJson3DescendingSizeOrder);
assert.equal(source3DescendingSizeOrder.getTileUrl(0, 0, 0), "http://example.com/identifier/full/500,250/0/default.jpg");
assert.equal(source3DescendingSizeOrder.getTileUrl(1, 1, 0), "http://example.com/identifier/1024,0,976,1000/488,500/0/default.jpg");
assert.equal(source3DescendingSizeOrder.getTileUrl(2, 0, 0), "http://example.com/identifier/0,0,512,512/512,512/0/default.jpg");
}); });
})(); })();