diff --git a/src/iiiftilesource.js b/src/iiiftilesource.js index bf3da020..4584f1cd 100644 --- a/src/iiiftilesource.js +++ b/src/iiiftilesource.js @@ -36,8 +36,8 @@ /** * @class IIIFTileSource - * @classdesc A client implementation of the International Image Interoperability - * Format: Image API 1.0 - 2.0 + * @classdesc A client implementation of the International Image Interoperability Framework + * Format: Image API 1.0 - 2.1 * * @memberof OpenSeadragon * @extends OpenSeadragon.TileSource @@ -83,7 +83,7 @@ $.IIIFTileSource = function( options ){ } } } - } else { + } else if ( canBeTiled(options.profile) ) { // use the largest of tileOptions that is smaller than the short dimension var shortDim = Math.min( this.height, this.width ), tileOptions = [256,512,1024], @@ -101,13 +101,32 @@ $.IIIFTileSource = function( options ){ // If we're smaller than 256, just use the short side. options.tileSize = shortDim; } + } else if (this.sizes && this.sizes.length > 0) { + // This info.json can't be tiled, but we can still construct a legacy pyramid from the sizes array. + // In this mode, IIIFTileSource will call functions from the abstract baseTileSource or the + // LegacyTileSource instead of performing IIIF tiling. + this.emulateLegacyImagePyramid = true; + + options.levels = constructLevels( this ); + // use the largest available size to define tiles + $.extend( true, options, { + width: options.levels[ options.levels.length - 1 ].width, + height: options.levels[ options.levels.length - 1 ].height, + tileSize: Math.max( options.height, options.width ), + tileOverlap: 0, + minLevel: 0, + maxLevel: options.levels.length - 1 + }); + this.levels = options.levels; + } else { + $.console.error("Nothing in the info.json to construct image pyramids from"); } - if ( !options.maxLevel ) { - if ( !this.scale_factors ) { - options.maxLevel = Number( Math.ceil( Math.log( Math.max( this.width, this.height ), 2 ) ) ); + if (!options.maxLevel && !this.emulateLegacyImagePyramid) { + if (!this.scale_factors) { + options.maxLevel = Number(Math.ceil(Math.log(Math.max(this.width, this.height), 2))); } else { - options.maxLevel = Math.floor( Math.pow( Math.max.apply(null, this.scale_factors), 0.5) ); + options.maxLevel = Math.floor(Math.pow(Math.max.apply(null, this.scale_factors), 0.5)); } } @@ -192,6 +211,11 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea * @param {Number} level */ getTileWidth: function( level ) { + + if(this.emulateLegacyImagePyramid) { + return $.TileSource.prototype.getTileWidth.call(this, level); + } + var scaleFactor = Math.pow(2, this.maxLevel - level); if (this.tileSizePerScaleFactor && this.tileSizePerScaleFactor[scaleFactor]) { @@ -206,6 +230,11 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea * @param {Number} level */ getTileHeight: function( level ) { + + if(this.emulateLegacyImagePyramid) { + return $.TileSource.prototype.getTileHeight.call(this, level); + } + var scaleFactor = Math.pow(2, this.maxLevel - level); if (this.tileSizePerScaleFactor && this.tileSizePerScaleFactor[scaleFactor]) { @@ -214,9 +243,61 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea return this._tileHeight; }, + /** + * @function + * @param {Number} level + */ + getLevelScale: function ( level ) { + + if(this.emulateLegacyImagePyramid) { + var levelScale = NaN; + if (this.levels.length > 0 && level >= this.minLevel && level <= this.maxLevel) { + levelScale = + this.levels[level].width / + this.levels[this.maxLevel].width; + } + return levelScale; + } + + return $.TileSource.prototype.getLevelScale.call(this, level); + }, /** - * Responsible for retreiving the url which will return an image for the + * @function + * @param {Number} level + */ + getNumTiles: function( level ) { + + if(this.emulateLegacyImagePyramid) { + var scale = this.getLevelScale(level); + if (scale) { + return new $.Point(1, 1); + } else { + return new $.Point(0, 0); + } + } + + return $.TileSource.prototype.getNumTiles.call(this, level); + }, + + + /** + * @function + * @param {Number} level + * @param {OpenSeadragon.Point} point + */ + getTileAtPoint: function( level, point ) { + + if(this.emulateLegacyImagePyramid) { + return new $.Point(0, 0); + } + + return $.TileSource.prototype.getTileAtPoint.call(this, level, point); + }, + + + /** + * Responsible for retrieving the url which will return an image for the * region specified by the given x, y, and level components. * @function * @param {Number} level - z index @@ -226,6 +307,14 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea */ getTileUrl: function( level, x, y ){ + if(this.emulateLegacyImagePyramid) { + var url = null; + if ( this.levels.length > 0 && level >= this.minLevel && level <= this.maxLevel ) { + url = this.levels[ level ].url; + } + return url; + } + //# constants var IIIF_ROTATION = '0', //## get the scale (level as a decimal) @@ -280,6 +369,40 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea }); + /** + * Determine whether arbitrary tile requests can be made against a service with the given profile + * @function + * @param {object} profile - IIIF profile object + * @throws {Error} + */ + function canBeTiled (profile ) { + var level0Profiles = [ + "http://library.stanford.edu/iiif/image-api/compliance.html#level0", + "http://library.stanford.edu/iiif/image-api/1.1/compliance.html#level0", + "http://iiif.io/api/image/2/level0.json" + ]; + var isLevel0 = (level0Profiles.indexOf(profile[0]) != -1); + return !isLevel0 || (profile.indexOf("sizeByW") != -1); + } + + /** + * Build the legacy pyramid URLs (one tile per level) + * @function + * @param {object} options - infoJson + * @throws {Error} + */ + function constructLevels(options) { + var levels = []; + for(var i=0; i + + + OpenSeadragon Demo - IIIF emulation of legacy image pyramid + + + + + +
+

Default OpenSeadragon viewer from IIIF Source

+

This allows IIIF even if you only have a handful of static image sizes.

+
+ + +
+ + + diff --git a/test/demo/iiif.html b/test/demo/iiif.html new file mode 100644 index 00000000..5d978df3 --- /dev/null +++ b/test/demo/iiif.html @@ -0,0 +1,34 @@ + + + + OpenSeadragon Demo - IIIF Tiled + + + + + +
+

Default OpenSeadragon viewer from IIIF Tile Source.

+

This depends on a remote server providing a IIIF Image API endpoint.

+
+
+ + + diff --git a/test/demo/legacy.html b/test/demo/legacy.html new file mode 100644 index 00000000..0c13a2b4 --- /dev/null +++ b/test/demo/legacy.html @@ -0,0 +1,56 @@ + + + + OpenSeadragon Demo - Legacy image pyramid + + + + + +
+ Use an array of full images at different sizes. +
+
+ + + diff --git a/test/modules/formats.js b/test/modules/formats.js index 5166f796..f0edea75 100644 --- a/test/modules/formats.js +++ b/test/modules/formats.js @@ -114,6 +114,11 @@ testOpenUrl('iiif_2_0_tiled/info.json'); }); + // ---------- + asyncTest('IIIF 2.0 JSON, sizes array only', function() { + testOpenUrl('iiif_2_0_sizes/info.json'); + }); + // ---------- asyncTest('IIIF 2.0 JSON String', function() { testOpen( @@ -149,5 +154,38 @@ url: "/test/data/A.png" }); }); + + + // ---------- + asyncTest('Legacy Image Pyramid', function() { + // Although it is using image paths that happen to be in IIIF format, this is not a IIIFTileSource. + // The url values are opaque, just image locations. + // When emulating a legacy pyramid, IIIFTileSource calls functions from LegacyTileSource, so this + // adds a test for the legacy functionality too. + testOpen({ + type: 'legacy-image-pyramid', + levels:[{ + url: '/test/data/iiif_2_0_sizes/full/400,/0/default.jpg', + height: 291, + width: 400 + },{ + url: '/test/data/iiif_2_0_sizes/full/800,/0/default.jpg', + height: 582, + width: 800 + },{ + url: '/test/data/iiif_2_0_sizes/full/1600,/0/default.jpg', + height: 1164, + width: 1600 + },{ + url: '/test/data/iiif_2_0_sizes/full/3200,/0/default.jpg', + height: 2328, + width: 3200 + },{ + url: '/test/data/iiif_2_0_sizes/full/6976,/0/default.jpg', + height: 5074, + width: 6976 + }] + }); + }); })();