Merge pull request #1764 from lutzhelm/iiif-v3beta

Add support for IIIF Image API 3.0 beta
This commit is contained in:
Ian Gilman 2020-01-16 10:22:50 -08:00 committed by GitHub
commit 898b8456d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 322 additions and 19 deletions

View File

@ -59,6 +59,8 @@ $.IIIFTileSource = function( options ){
this.tileFormat = this.tileFormat || 'jpg';
this.version = options.version;
// N.B. 2.0 renamed scale_factors to scaleFactors
if ( this.tile_width && this.tile_height ) {
options.tileWidth = this.tile_width;
@ -88,7 +90,7 @@ $.IIIFTileSource = function( options ){
}
}
}
} else if ( canBeTiled(options.profile) ) {
} else if ( canBeTiled(options) ) {
// use the largest of tileOptions that is smaller than the short dimension
var shortDim = Math.min( this.height, this.width ),
tileOptions = [256, 512, 1024],
@ -201,11 +203,42 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea
var options = configureFromXml10( data );
options['@context'] = "http://iiif.io/api/image/1.0/context.json";
options['@id'] = url.replace('/info.xml', '');
options.version = 1;
return options;
} else {
if ( !data['@context'] ) {
data['@context'] = 'http://iiif.io/api/image/1.0/context.json';
data['@id'] = url.replace('/info.json', '');
data.version = 1;
} else {
var context = data['@context'];
if (Array.isArray(context)) {
for (var i = 0; i < context.length; i++) {
if (typeof context[i] === 'string' &&
( /^http:\/\/iiif\.io\/api\/image\/[1-3]\/context\.json$/.test(context[i]) ||
context[i] === 'http://library.stanford.edu/iiif/image-api/1.1/context.json' ) ) {
context = context[i];
break;
}
}
}
switch (context) {
case 'http://iiif.io/api/image/1/context.json':
case 'http://library.stanford.edu/iiif/image-api/1.1/context.json':
data.version = 1;
break;
case 'http://iiif.io/api/image/2/context.json':
data.version = 2;
break;
case 'http://iiif.io/api/image/3/context.json':
data.version = 3;
break;
default:
$.console.error('Data has a @context property which contains no known IIIF context URI.');
}
}
if ( !data['@id'] && data['id'] ) {
data['@id'] = data['id'];
}
if(data.preferredFormats) {
for (var f = 0; f < data.preferredFormats.length; f++ ) {
@ -350,27 +383,28 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea
iiifTileH,
iiifSize,
iiifSizeW,
iiifSizeH,
iiifQuality,
uri,
isv1;
uri;
tileWidth = this.getTileWidth(level);
tileHeight = this.getTileHeight(level);
iiifTileSizeWidth = Math.ceil( tileWidth / scale );
iiifTileSizeHeight = Math.ceil( tileHeight / scale );
isv1 = ( this['@context'].indexOf('/1.0/context.json') > -1 ||
this['@context'].indexOf('/1.1/context.json') > -1 ||
this['@context'].indexOf('/1/context.json') > -1 );
if (isv1) {
if (this.version === 1) {
iiifQuality = "native." + this.tileFormat;
} else {
iiifQuality = "default." + this.tileFormat;
}
if ( levelWidth < tileWidth && levelHeight < tileHeight ){
if ( isv1 || levelWidth !== this.width ) {
iiifSize = levelWidth + ",";
} else {
if ( this.version === 2 && levelWidth === this.width ) {
iiifSize = "max";
} else if ( this.version === 3 && levelWidth === this.width && levelHeight === this.height ) {
iiifSize = "max";
} else if ( this.version === 3 ) {
iiifSize = levelWidth + "," + levelHeight;
} else {
iiifSize = levelWidth + ",";
}
iiifRegion = 'full';
} else {
@ -384,8 +418,13 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea
iiifRegion = [ iiifTileX, iiifTileY, iiifTileW, iiifTileH ].join( ',' );
}
iiifSizeW = Math.ceil( iiifTileW * scale );
if ( (!isv1) && iiifSizeW === this.width ) {
iiifSizeH = Math.ceil( iiifTileH * scale );
if ( this.version === 2 && iiifSizeW === this.width ) {
iiifSize = "max";
} else if ( this.version === 3 && iiifSizeW === this.width && iiifSizeH === this.height ) {
iiifSize = "max";
} else if (this.version === 3) {
iiifSize = iiifSizeW + "," + iiifSizeH;
} else {
iiifSize = iiifSizeW + ",";
}
@ -393,6 +432,11 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea
uri = [ this['@id'], iiifRegion, iiifSize, IIIF_ROTATION, iiifQuality ].join( '/' );
return uri;
},
__testonly__: {
canBeTiled: canBeTiled,
constructLevels: constructLevels
}
});
@ -403,18 +447,24 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea
* @param {array} profile - IIIF profile array
* @throws {Error}
*/
function canBeTiled ( profile ) {
function canBeTiled ( options ) {
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"
"http://iiif.io/api/image/2/level0.json",
"level0",
"https://iiif.io/api/image/3/level0.json"
];
var isLevel0 = (level0Profiles.indexOf(profile[0]) !== -1);
var hasSizeByW = false;
if ( profile.length > 1 && profile[1].supports ) {
hasSizeByW = profile[1].supports.indexOf( "sizeByW" ) !== -1;
var profileLevel = Array.isArray(options.profile) ? options.profile[0] : options.profile;
var isLevel0 = (level0Profiles.indexOf(profileLevel) !== -1);
var hasCanoncicalSizeFeature = false;
if ( options.version === 2 && options.profile.length > 1 && options.profile[1].supports ) {
hasCanoncicalSizeFeature = options.profile[1].supports.indexOf( "sizeByW" ) !== -1;
}
return !isLevel0 || hasSizeByW;
if ( options.version === 3 && options.extraFeatures ) {
hasCanoncicalSizeFeature = options.extraFeatures.indexOf( "sizeByWh" ) !== -1;
}
return !isLevel0 || hasCanoncicalSizeFeature;
}
/**
@ -427,7 +477,9 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea
var levels = [];
for(var i = 0; i < options.sizes.length; i++) {
levels.push({
url: options['@id'] + '/full/' + options.sizes[i].width + ',/0/default.' + options.tileFormat,
url: options['@id'] + '/full/' + options.sizes[i].width + ',' +
(options.version === 3 ? options.sizes[i].height : '') +
'/0/default.' + options.tileFormat,
width: options.sizes[i].width,
height: options.sizes[i].height
});

View File

@ -87,6 +87,7 @@
<script src="/test/modules/rectangle.js"></script>
<script src="/test/modules/ajax-tiles.js"></script>
<script src="/test/modules/imageloader.js"></script>
<script src="/test/modules/iiif.js"></script>
<!-- The navigator tests are the slowest (for now; hopefully they can be sped up)
so we put them last. -->
<script src="/test/modules/navigator.js"></script>

249
test/modules/iiif.js Normal file
View File

@ -0,0 +1,249 @@
(function() {
var id = "http://example.com/identifier";
var configure = function(data) {
return OpenSeadragon.IIIFTileSource.prototype.configure.apply(
new OpenSeadragon.TileSource(), [ data, 'http://example.com/identifier' ]
);
};
var getSource = function( data ) {
var options = configure( data );
return new OpenSeadragon.IIIFTileSource( options );
};
var infoXml10level0 = new DOMParser().parseFromString('<?xml version="1.0" encoding="UTF-8"?>' +
'<info xmlns="http://library.stanford.edu/iiif/image-api/ns/">' +
'<identifier>http://example.com/identifier</identifier>' +
'<width>6000</width>' +
'<height>4000</height>' +
'<scale_factors>' +
'<scale_factor>1</scale_factor>' +
'<scale_factor>2</scale_factor>' +
'<scale_factor>4</scale_factor>' +
'</scale_factors>' +
'<profile>http://library.stanford.edu/iiif/image-api/compliance.html#level0</profile>' +
'</info>',
'text/xml'
),
infoXml10level1 = new DOMParser().parseFromString('<?xml version="1.0" encoding="UTF-8"?>' +
'<info xmlns="http://library.stanford.edu/iiif/image-api/ns/">' +
'<identifier>http://example.com/identifier</identifier>' +
'<width>6000</width>' +
'<height>4000</height>' +
'<profile>http://library.stanford.edu/iiif/image-api/compliance.html#level1</profile>' +
'</info>',
'text/xml'
),
infoJson10level0 = {
"identifier": id,
"width": 2000,
"height": 1000,
"profile" : "http://library.stanford.edu/iiif/image-api/compliance.html#level0"
},
infoJson10level1 = {
"identifier": id,
"width": 2000,
"height": 1000,
"profile" : "http://library.stanford.edu/iiif/image-api/compliance.html#level1"
},
infoJson11level0 = {
"@context": "http://library.stanford.edu/iiif/image-api/1.1/context.json",
"@id": id,
"width": 2000,
"height": 1000,
"profile": "http://library.stanford.edu/iiif/image-api/1.1/compliance.html#level0"
},
infoJson11level1 = {
"@context": "http://library.stanford.edu/iiif/image-api/1.1/context.json",
"@id": id,
"width": 2000,
"height": 1000,
"profile": "http://library.stanford.edu/iiif/image-api/1.1/compliance.html#level1"
},
infoJson11level1WithTiles = {
"@context": "http://library.stanford.edu/iiif/image-api/1.1/context.json",
"@id": id,
"width": 2000,
"height": 1000,
"tile_width": 512,
"tile_height": 256,
"profile": "http://library.stanford.edu/iiif/image-api/1.1/compliance.html#level1"
},
infoJson2level0 = {
"@context": "http://iiif.io/api/image/2/context.json",
"@id": id,
"width": 2000,
"height": 1000,
"sizes": [
{ width: 2000, height: 1000 },
{ width: 1000, height: 500 }
],
"profile": ["http://iiif.io/api/image/2/level0.json"]
},
infoJson2level0sizeByW = {
"@context": "http://iiif.io/api/image/2/context.json",
"@id": id,
"width": 2000,
"height": 1000,
"profile": ["http://iiif.io/api/image/2/level0.json", {"supports": "sizeByW"} ]
},
infoJson2level1 = {
"@context": "http://iiif.io/api/image/2/context.json",
"@id": id,
"width": 2000,
"height": 1000,
"profile": ["http://iiif.io/api/image/2/level1.json"]
},
infoJson3level0 = {
"@context": "http://iiif.io/api/image/3/context.json",
"id": id,
"width": 2000,
"height": 1000,
"sizes": [
{ width: 2000, height: 1000 },
{ width: 1000, height: 500 }
],
"profile": "level0"
},
infoJson3level0ContextExtension = {
"@context": [
"http://iiif.io/api/image/3/context.json",
{
"example": "http://example.com/vocab"
}
],
"id": id,
"width": 2000,
"height": 1000,
"profile": "level0"
},
infoJson3level0sizeByW = {
"@context": "http://iiif.io/api/image/3/context.json",
"id": id,
"width": 2000,
"height": 1000,
"profile": "level0",
"extraFeatures": "sizeByW"
},
infoJson3level0sizeByWh = {
"@context": "http://iiif.io/api/image/3/context.json",
"id": id,
"width": 2000,
"height": 1000,
"profile": "level0",
"extraFeatures": "sizeByWh"
},
infoJson3level1 = {
"@context": "http://iiif.io/api/image/3/context.json",
"id": id,
"width": 2000,
"height": 1000,
"profile": "level1"
};
QUnit.test('IIIFTileSource.configure determins correct version', function(assert) {
var options1_0xml = configure(infoXml10level0);
assert.ok(options1_0xml.version);
assert.equal(options1_0xml.version, 1, 'Version is 1 for version 1.0 info.xml');
var options1_0 = configure(infoJson10level0);
assert.ok(options1_0.version);
assert.equal(options1_0.version, 1, 'Version is 1 for version 1.0 info.json');
var options1_1 = configure(infoJson11level0);
assert.ok(options1_1.version);
assert.equal(options1_1.version, 1, 'Version is 1 for version 1.1 info.json');
var options2 = configure(infoJson2level0);
assert.ok(options2.version);
assert.equal(options2.version, 2, 'Version is 2 for version 2 info.json');
var options3 = configure(infoJson3level0);
assert.ok(options3.version);
assert.equal(options3.version, 3, 'Version is 3 for version 3 info.json');
var options3withContextExtension = configure(infoJson3level0ContextExtension);
assert.ok(options3withContextExtension.version);
assert.equal(options3withContextExtension.version, 3, 'Version is 3 for version 3 info.json');
});
QUnit.test('IIIFTileSource private function canBeTiled works as expected', function(assert) {
var canBeTiled = function( data ) {
var source = getSource( data );
return source.__testonly__.canBeTiled( source );
};
assert.notOk(canBeTiled(infoXml10level0));
assert.ok(canBeTiled(infoXml10level1));
assert.notOk(canBeTiled(infoJson10level0));
assert.ok(canBeTiled(infoJson10level1));
assert.notOk(canBeTiled(infoJson11level0));
assert.ok(canBeTiled(infoJson11level1));
assert.notOk(canBeTiled(infoJson2level0));
assert.ok(canBeTiled(infoJson2level0sizeByW));
assert.ok(canBeTiled(infoJson2level1));
assert.notOk(canBeTiled(infoJson3level0));
assert.notOk(canBeTiled(infoJson3level0sizeByW));
assert.ok(canBeTiled(infoJson3level0sizeByWh));
assert.ok(canBeTiled(infoJson3level1));
});
QUnit.test('IIIFTileSource private function constructLevels creates correct URLs for legacy pyramid', function( assert ) {
var constructLevels = function( data ) {
var source = getSource( data );
return source.__testonly__.constructLevels( source );
};
var levelsVersion2 = constructLevels(infoJson2level0);
assert.ok(Array.isArray(levelsVersion2));
assert.equal(levelsVersion2.length, 2, 'Constructed levels contain 2 entries');
assert.equal(levelsVersion2[0].url, 'http://example.com/identifier/full/1000,/0/default.jpg');
assert.equal(levelsVersion2[1].url, 'http://example.com/identifier/full/2000,/0/default.jpg');
// FIXME see below
// assert.equal(levelsVersion2[1].url, 'http://example.com/identifier/full/full/0/default.jpg');
var levelsVersion3 = constructLevels(infoJson3level0);
assert.ok(Array.isArray(levelsVersion3));
assert.equal(levelsVersion3.length, 2, 'Constructed levels contain 2 entries');
assert.equal(levelsVersion3[0].url, 'http://example.com/identifier/full/1000,500/0/default.jpg');
assert.equal(levelsVersion3[1].url, 'http://example.com/identifier/full/2000,1000/0/default.jpg');
/*
* FIXME: following https://iiif.io/api/image/3.0/#47-canonical-uri-syntax and
* https://iiif.io/api/image/2.1/#canonical-uri-syntax, I'd expect 'max' to be required to
* be served by a level 0 compliant service instead of 'w,h', 'full' instead of 'w,' respectivley.
*/
//assert.equal(levelsVersion3[1].url, 'http://example.com/identifier/full/max/0/default.jpg');
});
QUnit.test('IIIFTileSource.getTileUrl returns the correct URLs', function( assert ) {
var source11Level1 = getSource(infoJson11level1);
assert.equal(source11Level1.getTileUrl(0, 0, 0), "http://example.com/identifier/full/8,/0/native.jpg");
assert.equal(source11Level1.getTileUrl(7, 0, 0), "http://example.com/identifier/0,0,1024,1000/512,/0/native.jpg");
assert.equal(source11Level1.getTileUrl(7, 1, 0), "http://example.com/identifier/1024,0,976,1000/488,/0/native.jpg");
assert.equal(source11Level1.getTileUrl(8, 0, 0), "http://example.com/identifier/0,0,512,512/512,/0/native.jpg");
var source2Level1 = getSource(infoJson2level1);
assert.equal(source2Level1.getTileUrl(0, 0, 0), "http://example.com/identifier/full/8,/0/default.jpg");
assert.equal(source2Level1.getTileUrl(7, 0, 0), "http://example.com/identifier/0,0,1024,1000/512,/0/default.jpg");
assert.equal(source2Level1.getTileUrl(7, 1, 0), "http://example.com/identifier/1024,0,976,1000/488,/0/default.jpg");
assert.equal(source2Level1.getTileUrl(8, 0, 0), "http://example.com/identifier/0,0,512,512/512,/0/default.jpg");
assert.equal(source2Level1.getTileUrl(8, 3, 0), "http://example.com/identifier/1536,0,464,512/464,/0/default.jpg");
assert.equal(source2Level1.getTileUrl(8, 0, 1), "http://example.com/identifier/0,512,512,488/512,/0/default.jpg");
assert.equal(source2Level1.getTileUrl(8, 3, 1), "http://example.com/identifier/1536,512,464,488/464,/0/default.jpg");
var source2Level0 = getSource(infoJson2level0);
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");
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(7, 0, 0), "http://example.com/identifier/0,0,1024,1000/512,500/0/default.jpg");
assert.equal(source3Level1.getTileUrl(7, 1, 0), "http://example.com/identifier/1024,0,976,1000/488,500/0/default.jpg");
assert.equal(source3Level1.getTileUrl(8, 0, 0), "http://example.com/identifier/0,0,512,512/512,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, 3, 1), "http://example.com/identifier/1536,512,464,488/464,488/0/default.jpg");
});
})();

View File

@ -44,6 +44,7 @@
<script src="/test/modules/rectangle.js"></script>
<script src="/test/modules/ajax-tiles.js"></script>
<script src="/test/modules/imageloader.js"></script>
<script src="/test/modules/iiif.js"></script>
<!--The navigator tests are the slowest (for now; hopefully they can be sped up)
so we put them last. -->
<script src="/test/modules/navigator.js"></script>