Merge pull request #446 from openseadragon/collections

Collections
This commit is contained in:
Ian Gilman 2015-02-09 10:42:10 -08:00
commit 3842779746
56 changed files with 5856 additions and 2449 deletions

2
.gitignore vendored
View File

@ -2,3 +2,5 @@
node_modules node_modules
build/ build/
sftp-config.json sftp-config.json
coverage/
temp/

View File

@ -1,3 +1,5 @@
/* global module */
module.exports = function(grunt) { module.exports = function(grunt) {
// ---------- // ----------
@ -5,7 +7,7 @@ module.exports = function(grunt) {
grunt.loadNpmTasks("grunt-contrib-concat"); grunt.loadNpmTasks("grunt-contrib-concat");
grunt.loadNpmTasks("grunt-contrib-jshint"); grunt.loadNpmTasks("grunt-contrib-jshint");
grunt.loadNpmTasks("grunt-contrib-uglify"); grunt.loadNpmTasks("grunt-contrib-uglify");
grunt.loadNpmTasks("grunt-contrib-qunit"); grunt.loadNpmTasks("grunt-qunit-istanbul");
grunt.loadNpmTasks("grunt-contrib-connect"); grunt.loadNpmTasks("grunt-contrib-connect");
grunt.loadNpmTasks("grunt-contrib-watch"); grunt.loadNpmTasks("grunt-contrib-watch");
grunt.loadNpmTasks("grunt-contrib-clean"); grunt.loadNpmTasks("grunt-contrib-clean");
@ -44,11 +46,14 @@ module.exports = function(grunt) {
"src/referencestrip.js", "src/referencestrip.js",
"src/displayrectangle.js", "src/displayrectangle.js",
"src/spring.js", "src/spring.js",
"src/imageLoader.js", "src/imageloader.js",
"src/tile.js", "src/tile.js",
"src/overlay.js", "src/overlay.js",
"src/drawer.js", "src/drawer.js",
"src/viewport.js" "src/viewport.js",
"src/tiledimage.js",
"src/tilecache.js",
"src/world.js"
]; ];
// ---------- // ----------
@ -69,6 +74,7 @@ module.exports = function(grunt) {
clean: { clean: {
build: ["build"], build: ["build"],
package: [packageDir], package: [packageDir],
coverage: ["coverage"],
release: { release: {
src: [releaseRoot], src: [releaseRoot],
options: { options: {
@ -134,10 +140,26 @@ module.exports = function(grunt) {
} }
}, },
qunit: { qunit: {
normal: {
options: {
urls: [ "http://localhost:8000/test/test.html" ]
}
},
coverage: {
options: {
urls: [ "http://localhost:8000/test/coverage.html" ],
coverage: {
src: ['src/*.js'],
htmlReport: 'coverage/html/',
instrumentedFiles: 'temp/',
baseUrl: '.',
disposeCollector: true
}
}
},
all: { all: {
options: { options: {
timeout: 10000, timeout: 10000
urls: [ "http://localhost:8000/test/test.html" ]
} }
} }
}, },
@ -151,7 +173,7 @@ module.exports = function(grunt) {
}, },
watch: { watch: {
files: [ "Gruntfile.js", "src/*.js", "images/*" ], files: [ "Gruntfile.js", "src/*.js", "images/*" ],
tasks: "build" tasks: "watchTask"
}, },
jshint: { jshint: {
options: { options: {
@ -206,6 +228,8 @@ module.exports = function(grunt) {
}); });
// ---------- // ----------
// Bower task.
// Generates the Bower file for site-build.
grunt.registerTask("bower", function() { grunt.registerTask("bower", function() {
var path = "../site-build/bower.json"; var path = "../site-build/bower.json";
var data = grunt.file.readJSON(path); var data = grunt.file.readJSON(path);
@ -213,6 +237,18 @@ module.exports = function(grunt) {
grunt.file.write(path, JSON.stringify(data, null, 2) + "\n"); grunt.file.write(path, JSON.stringify(data, null, 2) + "\n");
}); });
// ----------
// Watch task.
// Called from the watch feature; does a full build or a minbuild, depending on
// whether you used --min on the command line.
grunt.registerTask("watchTask", function() {
if (grunt.option('min')) {
grunt.task.run("minbuild");
} else {
grunt.task.run("build");
}
});
// ---------- // ----------
// Build task. // Build task.
// Cleans out the build folder and builds the code and images into it, checking lint. // Cleans out the build folder and builds the code and images into it, checking lint.
@ -221,10 +257,22 @@ module.exports = function(grunt) {
"uglify", "replace:cleanPaths", "copy:build" "uglify", "replace:cleanPaths", "copy:build"
]); ]);
// ----------
// Minimal build task.
// For use during development as desired. Creates only the unminified version.
grunt.registerTask("minbuild", [
"git-describe", "concat", "copy:build"
]);
// ---------- // ----------
// Test task. // Test task.
// Builds and runs unit tests. // Builds and runs unit tests.
grunt.registerTask("test", ["build", "connect", "qunit"]); grunt.registerTask("test", ["build", "connect", "qunit:normal"]);
// ----------
// Coverage task.
// Outputs unit test code coverage report.
grunt.registerTask("coverage", ["clean:coverage", "connect", "qunit:coverage"]);
// ---------- // ----------
// Package task. // Package task.

View File

@ -63,6 +63,12 @@ and open `http://localhost:8000/test/test.html` in your browser.
Another good page, if you want to interactively test out your changes, is `http://localhost:8000/test/demo/basic.html`. Another good page, if you want to interactively test out your changes, is `http://localhost:8000/test/demo/basic.html`.
You can also get a report of the tests' code coverage:
grunt coverage
The report shows up at `coverage/html/index.html` viewable in a browser.
### Contributing ### Contributing
OpenSeadragon is truly a community project; we welcome your involvement! OpenSeadragon is truly a community project; we welcome your involvement!

View File

@ -1,6 +1,54 @@
OPENSEADRAGON CHANGELOG OPENSEADRAGON CHANGELOG
======================= =======================
1.3.0: (in progress)
* True multi-image mode (#450)
* BREAKING CHANGE: Passing an array for the tileSources option is no longer enough to trigger sequence mode; you have to set the sequenceMode option to true as well
* BREAKING CHANGE: Navigator no longer sends an open event when its viewer opens
* BREAKING CHANGE: Viewer.drawers and Viewer.drawersContainer no longer exist
* BREAKING CHANGE: A Viewer's Drawer and Viewport are now made once per Viewer and reused for every image that Viewer opens (rather than being recreated for every open); this means if you change Viewer options between opens, the behavior is different now.
* DEPRECATION: use Viewer.addTiledImage instead of Viewer.addLayer
* addTiledImage supports positioning config properties
* DEPRECATION: use World.getItemAt instead of Viewer.getLayerAtLevel
* DEPRECATION: use World.getIndexOfItem instead of Viewer.getLevelOfLayer
* DEPRECATION: use World.getItemCount instead of Viewer.getLayersCount
* DEPRECATION: use World.setItemIndex instead of Viewer.setLayerLevel
* DEPRECATION: use World.removeItem instead of Viewer.removeLayer
* DEPRECATION: use World.needsDraw instead of Drawer.needsUpdate
* DEPRECATION: use TileCache.numTilesLoaded instead of Drawer.numTilesLoaded
* DEPRECATION: use World.resetItems instead of Drawer.reset
* DEPRECATION: use Drawer.clear and World.draw instead of Drawer.update
* DEPRECATION: the layersAspectRatioEpsilon option is no longer necessary
* DEPRECATION: Viewer's add-layer event is now World's add-item event
* DEPRECATION: Viewer's layer-level-changed event is now World's item-index-change event
* DEPRECATION: Viewer's remove-layer event is now World's remove-item event
* DEPRECATION: Viewer's add-layer-failed event is now add-item-failed
* DEPRECATION: TileSourceCollection has been retired in favor of World
* DEPRECATION: collectionMode no longer draws outlines or reflections for items
* Drawer has been split into three classes:
* TiledImage, tile management and positioning for a single tiled image
* TileCache, tile caching for all images
* Drawer, tile drawing for all images
* New class: World, keeps track of multiple images in the scene
* Viewer now has world and tileCache properties
* Rect and Point now have clone functions
* New Viewport method for managing homeBounds as well as constraints: setHomeBounds
* Viewport.open supports positioning config properties
* Margins option to push the home region in from the edges of the Viewer (#505)
* Rect and Point toString() functions are now consistent: rounding values to nearest hundredth
* Overlays appear in the DOM immediately on open or addOverlay (#507)
* imageLoaderLimit now works (#544)
* Turning off scrollToZoom in gestureSettings now allows scroll events to propagate
* You can now set a minZoomLevel that's greater than the home zoom level
* Added union() to OpenSeadragon.Rect
* Fixed an error in fitBounds if the new and old bounds were extremely close in size
* Added ajaxWithCredentials option (#543)
* Added viewport-change event for after the viewport changes but before it's drawn
* A spring's current value is now updated immediately on reset (#524)
* Fixed an error in fitBounds that occurred sometimes with immediately = true
* Added support for HDPI (retina) displays (#583)
1.2.2: (in progress) 1.2.2: (in progress)
* Corrected IIIF tile source to use canonical syntax (#586) * Corrected IIIF tile source to use canonical syntax (#586)

View File

@ -8,7 +8,8 @@
"*.sublime-workspace" "*.sublime-workspace"
], ],
"folder_exclude_patterns": [ "folder_exclude_patterns": [
"node_modules" "node_modules",
"coverage"
] ]
} }
], ],

View File

@ -10,11 +10,11 @@
"grunt-git-describe": "^2.3.2", "grunt-git-describe": "^2.3.2",
"grunt-contrib-uglify": "^0.4.0", "grunt-contrib-uglify": "^0.4.0",
"grunt-contrib-watch": "^0.6.1", "grunt-contrib-watch": "^0.6.1",
"grunt-contrib-qunit": "^0.5.1",
"grunt-contrib-jshint": "^0.10.0", "grunt-contrib-jshint": "^0.10.0",
"grunt-contrib-compress": "^0.9.1", "grunt-contrib-compress": "^0.9.1",
"grunt-contrib-connect": "^0.7.1", "grunt-contrib-connect": "^0.7.1",
"qunitjs": "^1.14.0" "qunitjs": "^1.14.0",
"grunt-qunit-istanbul": "^0.4.5"
}, },
"scripts": { "scripts": {
"test": "grunt test" "test": "grunt test"

File diff suppressed because it is too large Load Diff

View File

@ -34,16 +34,7 @@
(function( $ ){ (function( $ ){
/** // private class
* @private
* @class ImageJob
* @classdesc Handles loading a single image for use in a single {@link OpenSeadragon.Tile}.
*
* @memberof OpenSeadragon
* @param {String} source - URL of image to download.
* @param {String} crossOriginPolicy - CORS policy to use for downloads
* @param {Function} callback - Called once image has finished downloading.
*/
function ImageJob ( options ) { function ImageJob ( options ) {
$.extend( true, this, { $.extend( true, this, {
@ -60,11 +51,6 @@ function ImageJob ( options ) {
} }
ImageJob.prototype = { ImageJob.prototype = {
/**
* Initiates downloading of associated image.
* @method
*/
start: function(){ start: function(){
var _this = this; var _this = this;
@ -104,20 +90,24 @@ ImageJob.prototype = {
}; };
/** /**
* @class * @class ImageLoader
* @memberof OpenSeadragon
* @classdesc Handles downloading of a set of images using asynchronous queue pattern. * @classdesc Handles downloading of a set of images using asynchronous queue pattern.
* You generally won't have to interact with the ImageLoader directly.
* @param {Object} options - Options for this ImageLoader.
* @param {Number} [options.jobLimit] - The number of concurrent image requests. See imageLoaderLimit in {@link OpenSeadragon.Options} for details.
*/ */
$.ImageLoader = function() { $.ImageLoader = function( options ) {
$.extend( true, this, { $.extend( true, this, {
jobLimit: $.DEFAULT_SETTINGS.imageLoaderLimit, jobLimit: $.DEFAULT_SETTINGS.imageLoaderLimit,
jobQueue: [], jobQueue: [],
jobsInProgress: 0 jobsInProgress: 0
}); }, options );
}; };
$.ImageLoader.prototype = { $.ImageLoader.prototype = /** @lends OpenSeadragon.ImageLoader.prototype */{
/** /**
* Add an unloaded image to the loader queue. * Add an unloaded image to the loader queue.
@ -172,10 +162,10 @@ function completeJob( loader, job, callback ) {
if ( (!loader.jobLimit || loader.jobsInProgress < loader.jobLimit) && loader.jobQueue.length > 0) { if ( (!loader.jobLimit || loader.jobsInProgress < loader.jobLimit) && loader.jobQueue.length > 0) {
nextJob = loader.jobQueue.shift(); nextJob = loader.jobQueue.shift();
nextJob.start(); nextJob.start();
loader.jobsInProgress++;
} }
callback( job.image ); callback( job.image );
} }
}( OpenSeadragon )); }( OpenSeadragon ));

View File

@ -173,7 +173,10 @@ $.Navigator = function( options ){
options.controlOptions options.controlOptions
); );
if ( options.controlOptions.anchor != $.ControlAnchor.ABSOLUTE && options.controlOptions.anchor != $.ControlAnchor.NONE ) { this._resizeWithViewer = options.controlOptions.anchor != $.ControlAnchor.ABSOLUTE &&
options.controlOptions.anchor != $.ControlAnchor.NONE;
if ( this._resizeWithViewer ) {
if ( options.width && options.height ) { if ( options.width && options.height ) {
this.element.style.height = typeof ( options.height ) == "number" ? ( options.height + 'px' ) : options.height; this.element.style.height = typeof ( options.height ) == "number" ? ( options.height + 'px' ) : options.height;
this.element.style.width = typeof ( options.width ) == "number" ? ( options.width + 'px' ) : options.width; this.element.style.width = typeof ( options.width ) == "number" ? ( options.width + 'px' ) : options.width;
@ -194,8 +197,7 @@ $.Navigator = function( options ){
this.displayRegionContainer.appendChild(this.displayRegion); this.displayRegionContainer.appendChild(this.displayRegion);
this.element.getElementsByTagName('div')[0].appendChild(this.displayRegionContainer); this.element.getElementsByTagName('div')[0].appendChild(this.displayRegionContainer);
if (options.navigatorRotate) if (options.navigatorRotate) {
{
options.viewer.addHandler("rotate", function (args) { options.viewer.addHandler("rotate", function (args) {
_setTransformRotate(_this.displayRegionContainer, args.degrees); _setTransformRotate(_this.displayRegionContainer, args.degrees);
_setTransformRotate(_this.displayRegion, -args.degrees); _setTransformRotate(_this.displayRegion, -args.degrees);
@ -213,6 +215,32 @@ $.Navigator = function( options ){
scrollHandler: $.delegate( this, onCanvasScroll ) scrollHandler: $.delegate( this, onCanvasScroll )
}); });
this.addHandler("reset-size", function() {
if (_this.viewport) {
_this.viewport.goHome(true);
}
});
this.addHandler("reset-size", function() {
if (_this.viewport) {
_this.viewport.goHome(true);
}
});
viewer.world.addHandler("item-index-change", function(event) {
var item = _this.world.getItemAt(event.previousIndex);
_this.world.setItemIndex(item, event.newIndex);
});
viewer.world.addHandler("remove-item", function(event) {
var theirItem = event.item;
var myItem = _this._getMatchingItem(theirItem);
if (myItem) {
_this.world.removeItem(myItem);
}
});
this.update(viewer.viewport);
}; };
$.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /** @lends OpenSeadragon.Navigator.prototype */{ $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /** @lends OpenSeadragon.Navigator.prototype */{
@ -228,23 +256,13 @@ $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /*
(this.container.clientWidth === 0 ? 1 : this.container.clientWidth), (this.container.clientWidth === 0 ? 1 : this.container.clientWidth),
(this.container.clientHeight === 0 ? 1 : this.container.clientHeight) (this.container.clientHeight === 0 ? 1 : this.container.clientHeight)
); );
if ( !containerSize.equals( this.oldContainerSize ) ) { if ( !containerSize.equals( this.oldContainerSize ) ) {
var oldBounds = this.viewport.getBounds();
var oldCenter = this.viewport.getCenter();
this.viewport.resize( containerSize, true ); this.viewport.resize( containerSize, true );
var imageHeight = 1 / this.source.aspectRatio; this.viewport.goHome(true);
var newWidth = oldBounds.width <= 1 ? oldBounds.width : 1;
var newHeight = oldBounds.height <= imageHeight ?
oldBounds.height : imageHeight;
var newBounds = new $.Rect(
oldCenter.x - ( newWidth / 2.0 ),
oldCenter.y - ( newHeight / 2.0 ),
newWidth,
newHeight
);
this.viewport.fitBounds( newBounds, true );
this.oldContainerSize = containerSize; this.oldContainerSize = containerSize;
this.drawer.update(); this.drawer.clear();
this.world.draw();
} }
} }
}, },
@ -264,28 +282,36 @@ $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /*
bottomright; bottomright;
viewerSize = $.getElementSize( this.viewer.element ); viewerSize = $.getElementSize( this.viewer.element );
if ( !viewerSize.equals( this.oldViewerSize ) ) { if ( this._resizeWithViewer && !viewerSize.equals( this.oldViewerSize ) ) {
this.oldViewerSize = viewerSize; this.oldViewerSize = viewerSize;
if ( this.maintainSizeRatio ) {
if ( this.maintainSizeRatio || !this.elementArea) {
newWidth = viewerSize.x * this.sizeRatio; newWidth = viewerSize.x * this.sizeRatio;
newHeight = viewerSize.y * this.sizeRatio; newHeight = viewerSize.y * this.sizeRatio;
} } else {
else {
newWidth = Math.sqrt(this.elementArea * (viewerSize.x / viewerSize.y)); newWidth = Math.sqrt(this.elementArea * (viewerSize.x / viewerSize.y));
newHeight = this.elementArea / newWidth; newHeight = this.elementArea / newWidth;
} }
this.element.style.width = Math.round( newWidth ) + 'px'; this.element.style.width = Math.round( newWidth ) + 'px';
this.element.style.height = Math.round( newHeight ) + 'px'; this.element.style.height = Math.round( newHeight ) + 'px';
if (!this.elementArea) {
this.elementArea = newWidth * newHeight;
}
this.updateSize(); this.updateSize();
} }
if( viewport && this.viewport ) { if( viewport && this.viewport ) {
bounds = viewport.getBounds( true ); bounds = viewport.getBounds( true );
topleft = this.viewport.pixelFromPoint( bounds.getTopLeft(), false ); topleft = this.viewport.pixelFromPoint( bounds.getTopLeft(), false );
bottomright = this.viewport.pixelFromPoint( bounds.getBottomRight(), false ).minus( this.totalBorderWidths ); bottomright = this.viewport.pixelFromPoint( bounds.getBottomRight(), false )
.minus( this.totalBorderWidths );
//update style for navigator-box //update style for navigator-box
(function(style) { var style = this.displayRegion.style;
style.display = this.world.getItemCount() ? 'block' : 'none';
style.top = Math.round( topleft.y ) + 'px'; style.top = Math.round( topleft.y ) + 'px';
style.left = Math.round( topleft.x ) + 'px'; style.left = Math.round( topleft.x ) + 'px';
@ -295,24 +321,52 @@ $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /*
// make sure width and height are non-negative so IE doesn't throw // make sure width and height are non-negative so IE doesn't throw
style.width = Math.round( Math.max( width, 0 ) ) + 'px'; style.width = Math.round( Math.max( width, 0 ) ) + 'px';
style.height = Math.round( Math.max( height, 0 ) ) + 'px'; style.height = Math.round( Math.max( height, 0 ) ) + 'px';
}( this.displayRegion.style ));
} }
}, },
open: function( source ) { // overrides Viewer.addTiledImage
this.updateSize(); addTiledImage: function(options) {
var containerSize = this.viewer.viewport.containerSize.times( this.sizeRatio ); var _this = this;
var ts = source.getTileSize(source.maxLevel);
if ( ts > containerSize.x || ts > containerSize.y ) { var original = options.originalTiledImage;
this.minPixelRatio = Math.min( containerSize.x, containerSize.y ) / ts; delete options.original;
} else {
this.minPixelRatio = this.viewer.minPixelRatio; var optionsClone = $.extend({}, options, {
success: function(event) {
var myItem = event.item;
myItem._originalForNavigator = original;
_this._matchBounds(myItem, original, true);
original.addHandler('bounds-change', function() {
_this._matchBounds(myItem, original);
});
}
});
return $.Viewer.prototype.addTiledImage.apply(this, [optionsClone]);
},
// private
_getMatchingItem: function(theirItem) {
var count = this.world.getItemCount();
var item;
for (var i = 0; i < count; i++) {
item = this.world.getItemAt(i);
if (item._originalForNavigator === theirItem) {
return item;
} }
return $.Viewer.prototype.open.apply( this, [ source ] );
} }
return null;
},
// private
_matchBounds: function(myItem, theirItem, immediately) {
var bounds = theirItem.getBounds();
myItem.setPosition(bounds.getTopLeft(), immediately);
myItem.setWidth(bounds.width, immediately);
}
}); });
/** /**

View File

@ -126,20 +126,14 @@
* The element to append the viewer's container element to. If not provided, the 'id' property must be provided. * The element to append the viewer's container element to. If not provided, the 'id' property must be provided.
* If both the element and id properties are specified, the viewer is appended to the element provided in the element property. * If both the element and id properties are specified, the viewer is appended to the element provided in the element property.
* *
* @property {Array|String|Function|Object} [tileSources=null]
* Tile source(s) to open initially. This is a complex parameter; see
* {@link OpenSeadragon.Viewer#open} for details.
*
* @property {Number} [tabIndex=0] * @property {Number} [tabIndex=0]
* Tabbing order index to assign to the viewer element. Positive values are selected in increasing order. When tabIndex is 0 * Tabbing order index to assign to the viewer element. Positive values are selected in increasing order. When tabIndex is 0
* source order is used. A negative value omits the viewer from the tabbing order. * source order is used. A negative value omits the viewer from the tabbing order.
* *
* @property {Array|String|Function|Object[]|Array[]|String[]|Function[]} [tileSources=null]
* As an Array, the tileSource can hold either Objects or mixed
* types of Arrays of Objects, Strings, or Functions. When a value is a String,
* the tileSource is used to create a {@link OpenSeadragon.DziTileSource}.
* When a value is a Function, the function is used to create a new
* {@link OpenSeadragon.TileSource} whose abstract method
* getUrl( level, x, y ) is implemented by the function. Finally, when it
* is an Array of objects, it is used to create a
* {@link OpenSeadragon.LegacyTileSource}.
*
* @property {Array} overlays Array of objects defining permanent overlays of * @property {Array} overlays Array of objects defining permanent overlays of
* the viewer. The overlays added via this option and later removed with * the viewer. The overlays added via this option and later removed with
* {@link OpenSeadragon.Viewer#removeOverlay} will be added back when a new * {@link OpenSeadragon.Viewer#removeOverlay} will be added back when a new
@ -215,9 +209,6 @@
* @property {Number} [opacity=1] * @property {Number} [opacity=1]
* Opacity of the drawer (1=opaque, 0=transparent) * Opacity of the drawer (1=opaque, 0=transparent)
* *
* @property {Number} [layersAspectRatioEpsilon=0.0001]
* Maximum aspectRatio mismatch between 2 layers.
*
* @property {Number} [degrees=0] * @property {Number} [degrees=0]
* Initial rotation. * Initial rotation.
* *
@ -265,10 +256,14 @@
* @property {Number} [visibilityRatio=0.5] * @property {Number} [visibilityRatio=0.5]
* The percentage ( as a number from 0 to 1 ) of the source image which * The percentage ( as a number from 0 to 1 ) of the source image which
* must be kept within the viewport. If the image is dragged beyond that * must be kept within the viewport. If the image is dragged beyond that
* limit, it will 'bounce' back until the minimum visibility ration is * limit, it will 'bounce' back until the minimum visibility ratio is
* achieved. Setting this to 0 and wrapHorizontal ( or wrapVertical ) to * achieved. Setting this to 0 and wrapHorizontal ( or wrapVertical ) to
* true will provide the effect of an infinitely scrolling viewport. * true will provide the effect of an infinitely scrolling viewport.
* *
* @property {Object} [viewportMargins={}]
* Pushes the "home" region in from the sides by the specified amounts.
* Possible subproperties (Numbers, in screen coordinates): left, top, right, bottom.
*
* @property {Number} [imageLoaderLimit=0] * @property {Number} [imageLoaderLimit=0]
* The maximum number of image requests to make concurrently. By default * The maximum number of image requests to make concurrently. By default
* it is set to 0 allowing the browser to make the maximum number of * it is set to 0 allowing the browser to make the maximum number of
@ -455,8 +450,8 @@
* this setting when set to false. * this setting when set to false.
* *
* @property {Boolean} [showSequenceControl=true] * @property {Boolean} [showSequenceControl=true]
* If the viewer has been configured with a sequence of tile sources, then * If sequenceMode is true, then provide buttons for navigating forward and
* provide buttons for navigating forward and backward through the images. * backward through the images.
* *
* @property {OpenSeadragon.ControlAnchor} [sequenceControlAnchor=TOP_LEFT] * @property {OpenSeadragon.ControlAnchor} [sequenceControlAnchor=TOP_LEFT]
* Placement of the default sequence controls. * Placement of the default sequence controls.
@ -514,26 +509,29 @@
* To only change the button images, consider using * To only change the button images, consider using
* {@link OpenSeadragon.Options.navImages} * {@link OpenSeadragon.Options.navImages}
* *
* @property {Boolean} [sequenceMode=false]
* Set to true to have the viewer treat your tilesources as a sequence of images to
* be opened one at a time rather than all at once.
*
* @property {Number} [initialPage=0] * @property {Number} [initialPage=0]
* If the viewer has been configured with a sequence of tile sources, display this page initially. * If sequenceMode is true, display this page initially.
* *
* @property {Boolean} [preserveViewport=false] * @property {Boolean} [preserveViewport=false]
* If the viewer has been configured with a sequence of tile sources, then * If sequenceMode is true, then normally navigating to through each image resets the
* normally navigating through each image resets the viewport to 'home' * viewport to 'home' position. If preserveViewport is set to true, then the viewport
* position. If preserveViewport is set to true, then the viewport position * position is preserved when navigating between images in the sequence.
* is preserved when navigating between images in the sequence.
* *
* @property {Boolean} [preserveOverlays=false] * @property {Boolean} [preserveOverlays=false]
* If the viewer has been configured with a sequence of tile sources, then * If sequenceMode is true, then normally navigating to through each image
* normally navigating through each image resets the overlays. * resets the overlays.
* If preserveOverlays is set to true, then the overlays * If preserveOverlays is set to true, then the overlays
* are preserved when navigating between images in the sequence. * are preserved when navigating between images in the sequence.
* Note: setting preserveOverlays overrides any overlays specified in the * Note: setting preserveOverlays overrides any overlays specified in the
* "overlays" property. * "overlays" property.
* *
* @property {Boolean} [showReferenceStrip=false] * @property {Boolean} [showReferenceStrip=false]
* If the viewer has been configured with a sequence of tile sources, then * If sequenceMode is true, then display a scrolling strip of image thumbnails for
* display a scrolling strip of image thumbnails for navigating through the images. * navigating through the images.
* *
* @property {String} [referenceStripScroll='horizontal'] * @property {String} [referenceStripScroll='horizontal']
* *
@ -548,17 +546,30 @@
* @property {Number} [referenceStripSizeRatio=0.2] * @property {Number} [referenceStripSizeRatio=0.2]
* *
* @property {Boolean} [collectionMode=false] * @property {Boolean} [collectionMode=false]
* Set to true to have the viewer arrange your TiledImages in a grid or line.
* *
* @property {Number} [collectionRows=3] * @property {Number} [collectionRows=3]
* If collectionMode is true, specifies how many rows the grid should have. Use 1 to make a line.
* If collectionLayout is 'vertical', specifies how many columns instead.
* *
* @property {String} [collectionLayout='horizontal'] * @property {String} [collectionLayout='horizontal']
* If collectionMode is true, specifies whether to arrange vertically or horizontally.
* *
* @property {Number} [collectionTileSize=800] * @property {Number} [collectionTileSize=800]
* If collectionMode is true, specifies the size, in viewport coordinates, for each TiledImage to fit into.
* The TiledImage will be centered within a square of the specified size.
*
* @property {Number} [collectionTileMargin=80]
* If collectionMode is true, specifies the margin, in viewport coordinates, between each TiledImage.
* *
* @property {String|Boolean} [crossOriginPolicy=false] * @property {String|Boolean} [crossOriginPolicy=false]
* Valid values are 'Anonymous', 'use-credentials', and false. If false, canvas requests will * Valid values are 'Anonymous', 'use-credentials', and false. If false, canvas requests will
* not use CORS, and the canvas will be tainted. * not use CORS, and the canvas will be tainted.
* *
* @property {Boolean} [ajaxWithCredentials=false]
* Whether to set the withCredentials XHR flag for AJAX requests (when loading tile sources).
* Note that this can be overridden at the {@link OpenSeadragon.TileSource} level.
*
*/ */
/** /**
@ -809,6 +820,25 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
canvasElement.getContext( '2d' ) ); canvasElement.getContext( '2d' ) );
}()); }());
/**
* A ratio comparing the device screen's pixel density to the canvas's backing store pixel density. Defaults to 1 if canvas isn't supported by the browser.
* @member {Number} pixelDensityRatio
* @memberof OpenSeadragon
*/
$.pixelDensityRatio = (function () {
if ( $.supportsCanvas ) {
var context = document.createElement('canvas').getContext('2d');
var devicePixelRatio = window.devicePixelRatio || 1;
var backingStoreRatio = context.webkitBackingStorePixelRatio ||
context.mozBackingStorePixelRatio ||
context.msBackingStorePixelRatio ||
context.oBackingStorePixelRatio ||
context.backingStorePixelRatio || 1;
return devicePixelRatio / backingStoreRatio;
} else {
return 1;
}
}());
}( OpenSeadragon )); }( OpenSeadragon ));
@ -916,6 +946,7 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
tileHost: null, tileHost: null,
initialPage: 0, initialPage: 0,
crossOriginPolicy: false, crossOriginPolicy: false,
ajaxWithCredentials: false,
//PAN AND ZOOM SETTINGS AND CONSTRAINTS //PAN AND ZOOM SETTINGS AND CONSTRAINTS
panHorizontal: true, panHorizontal: true,
@ -988,9 +1019,6 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
// APPEARANCE // APPEARANCE
opacity: 1, opacity: 1,
// LAYERS SETTINGS
layersAspectRatioEpsilon: 0.0001,
//REFERENCE STRIP SETTINGS //REFERENCE STRIP SETTINGS
showReferenceStrip: false, showReferenceStrip: false,
referenceStripScroll: 'horizontal', referenceStripScroll: 'horizontal',
@ -1005,6 +1033,7 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
collectionLayout: 'horizontal', //vertical collectionLayout: 'horizontal', //vertical
collectionMode: false, collectionMode: false,
collectionTileSize: 800, collectionTileSize: 800,
collectionTileMargin: 80,
//PERFORMANCE SETTINGS //PERFORMANCE SETTINGS
imageLoaderLimit: 0, imageLoaderLimit: 0,
@ -1924,13 +1953,25 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
/** /**
* Makes an AJAX request. * Makes an AJAX request.
* @function * @param {Object} options
* @param {String} url - the url to request * @param {String} options.url - the url to request
* @param {Function} onSuccess - a function to call on a successful response * @param {Function} options.success - a function to call on a successful response
* @param {Function} onError - a function to call on when an error occurs * @param {Function} options.error - a function to call on when an error occurs
* @param {Boolean} [options.withCredentials=false] - whether to set the XHR's withCredentials
* @throws {Error} * @throws {Error}
*/ */
makeAjaxRequest: function( url, onSuccess, onError ) { makeAjaxRequest: function( url, onSuccess, onError ) {
var withCredentials;
// Note that our preferred API is that you pass in a single object; the named
// arguments are for legacy support.
if( $.isPlainObject( url ) ){
onSuccess = url.success;
onError = url.error;
withCredentials = url.withCredentials;
url = url.url;
}
var protocol = $.getUrlProtocol( url ); var protocol = $.getUrlProtocol( url );
var request = $.createAjaxRequest( protocol === "file:" ); var request = $.createAjaxRequest( protocol === "file:" );
@ -1957,6 +1998,10 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
} }
}; };
if (withCredentials) {
request.withCredentials = true;
}
try { try {
request.open( "GET", url, true ); request.open( "GET", url, true );
request.send( null ); request.send( null );
@ -2271,7 +2316,8 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
debug: nullfunction, debug: nullfunction,
info: nullfunction, info: nullfunction,
warn: nullfunction, warn: nullfunction,
error: nullfunction error: nullfunction,
assert: nullfunction
}; };

View File

@ -60,6 +60,13 @@ $.Point = function( x, y ) {
}; };
$.Point.prototype = /** @lends OpenSeadragon.Point.prototype */{ $.Point.prototype = /** @lends OpenSeadragon.Point.prototype */{
/**
* @function
* @returns {OpenSeadragon.Point} a duplicate of this Point
*/
clone: function() {
return new $.Point(this.x, this.y);
},
/** /**
* Add another Point to this point and return a new Point. * Add another Point to this point and return a new Point.
@ -189,7 +196,7 @@ $.Point.prototype = /** @lends OpenSeadragon.Point.prototype */{
* @returns {String} A string representation of this point. * @returns {String} A string representation of this point.
*/ */
toString: function() { toString: function() {
return "(" + Math.round(this.x) + "," + Math.round(this.y) + ")"; return "(" + (Math.round(this.x * 100) / 100) + "," + (Math.round(this.y * 100) / 100) + ")";
} }
}; };

View File

@ -75,6 +75,13 @@ $.Rect = function( x, y, width, height ) {
}; };
$.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{ $.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{
/**
* @function
* @returns {OpenSeadragon.Rect} a duplicate of this Rect
*/
clone: function() {
return new $.Rect(this.x, this.y, this.width, this.height);
},
/** /**
* The aspect ratio is simply the ratio of width to height. * The aspect ratio is simply the ratio of width to height.
@ -194,6 +201,21 @@ $.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{
); );
}, },
/**
* Returns the smallest rectangle that will contain this and the given rectangle.
* @param {OpenSeadragon.Rect} rect
* @return {OpenSeadragon.Rect} The new rectangle.
*/
// ----------
union: function(rect) {
var left = Math.min(this.x, rect.x);
var top = Math.min(this.y, rect.y);
var right = Math.max(this.x + this.width, rect.x + rect.width);
var bottom = Math.max(this.y + this.height, rect.y + rect.height);
return new OpenSeadragon.Rect(left, top, right - left, bottom - top);
},
/** /**
* Rotates a rectangle around a point. Currently only 90, 180, and 270 * Rotates a rectangle around a point. Currently only 90, 180, and 270
* degrees are supported. * degrees are supported.
@ -257,10 +279,10 @@ $.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{
*/ */
toString: function() { toString: function() {
return "[" + return "[" +
Math.round(this.x*100) + "," + (Math.round(this.x*100) / 100) + "," +
Math.round(this.y*100) + "," + (Math.round(this.y*100) / 100) + "," +
Math.round(this.width*100) + "x" + (Math.round(this.width*100) / 100) + "x" +
Math.round(this.height*100) + (Math.round(this.height*100) / 100) +
"]"; "]";
} }
}; };

View File

@ -290,6 +290,13 @@ $.extend( $.ReferenceStrip.prototype, $.EventSource.prototype, $.Viewer.prototyp
return true; return true;
} }
return false; return false;
},
// Overrides Viewer.destroy
destroy: function() {
if (this.element) {
this.element.parentNode.removeChild(this.element);
}
} }
} ); } );

View File

@ -117,10 +117,8 @@ $.Spring.prototype = /** @lends OpenSeadragon.Spring.prototype */{
* @param {Number} target * @param {Number} target
*/ */
resetTo: function( target ) { resetTo: function( target ) {
this.target.value = target; this.start.value = this.target.value = this.current.value = target;
this.target.time = this.current.time; this.start.time = this.target.time = this.current.time = $.now();
this.start.value = this.target.value;
this.start.time = this.target.time;
}, },
/** /**

View File

@ -33,7 +33,7 @@
*/ */
(function( $ ){ (function( $ ){
var TILE_CACHE = {};
/** /**
* @class Tile * @class Tile
* @memberof OpenSeadragon * @memberof OpenSeadragon
@ -231,7 +231,8 @@ $.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{
* Renders the tile in a canvas-based context. * Renders the tile in a canvas-based context.
* @function * @function
* @param {Canvas} context * @param {Canvas} context
* @param {Function} method for firing the drawing event. drawingHandler({context, tile, rendered}) * @param {Function} drawingHandler - Method for firing the drawing event.
* drawingHandler({context, tile, rendered})
* where <code>rendered</code> is the context with the pre-drawn image. * where <code>rendered</code> is the context with the pre-drawn image.
*/ */
drawCanvas: function( context, drawingHandler ) { drawCanvas: function( context, drawingHandler ) {
@ -241,16 +242,23 @@ $.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{
rendered, rendered,
canvas; canvas;
if ( !this.loaded || !( this.image || TILE_CACHE[ this.url ] ) ){ if (!this.cacheImageRecord) {
$.console.warn('[Tile.drawCanvas] attempting to draw tile %s when it\'s not cached', this.toString());
return;
}
rendered = this.cacheImageRecord.getRenderedContext();
if ( !this.loaded || !( this.image || rendered) ){
$.console.warn( $.console.warn(
"Attempting to draw tile %s when it's not yet loaded.", "Attempting to draw tile %s when it's not yet loaded.",
this.toString() this.toString()
); );
return; return;
} }
context.globalAlpha = this.opacity;
//context.save(); context.globalAlpha = this.opacity;
//if we are supposed to be rendering fully opaque rectangle, //if we are supposed to be rendering fully opaque rectangle,
//ie its done fading or fading is turned off, and if we are drawing //ie its done fading or fading is turned off, and if we are drawing
@ -260,46 +268,40 @@ $.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{
//clearing only the inside of the rectangle occupied //clearing only the inside of the rectangle occupied
//by the png prevents edge flikering //by the png prevents edge flikering
context.clearRect( context.clearRect(
position.x+1, (position.x * $.pixelDensityRatio)+1,
position.y+1, (position.y * $.pixelDensityRatio)+1,
size.x-2, (size.x * $.pixelDensityRatio)-2,
size.y-2 (size.y * $.pixelDensityRatio)-2
); );
} }
if( !TILE_CACHE[ this.url ] ){ if(!rendered){
canvas = document.createElement( 'canvas' ); canvas = document.createElement( 'canvas' );
canvas.width = this.image.width; canvas.width = this.image.width;
canvas.height = this.image.height; canvas.height = this.image.height;
rendered = canvas.getContext('2d'); rendered = canvas.getContext('2d');
rendered.drawImage( this.image, 0, 0 ); rendered.drawImage( this.image, 0, 0 );
TILE_CACHE[ this.url ] = rendered; this.cacheImageRecord.setRenderedContext(rendered);
//since we are caching the prerendered image on a canvas //since we are caching the prerendered image on a canvas
//allow the image to not be held in memory //allow the image to not be held in memory
this.image = null; this.image = null;
} }
rendered = TILE_CACHE[ this.url ];
// This gives the application a chance to make image manipulation changes as we are rendering the image // This gives the application a chance to make image manipulation changes as we are rendering the image
drawingHandler({context: context, tile: this, rendered: rendered}); drawingHandler({context: context, tile: this, rendered: rendered});
//rendered.save();
context.drawImage( context.drawImage(
rendered.canvas, rendered.canvas,
0, 0,
0, 0,
rendered.canvas.width, rendered.canvas.width,
rendered.canvas.height, rendered.canvas.height,
position.x, position.x * $.pixelDensityRatio,
position.y, position.y * $.pixelDensityRatio,
size.x, size.x * $.pixelDensityRatio,
size.y size.y * $.pixelDensityRatio
); );
//rendered.restore();
//context.restore();
}, },
/** /**
@ -313,9 +315,6 @@ $.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{
if ( this.element && this.element.parentNode ) { if ( this.element && this.element.parentNode ) {
this.element.parentNode.removeChild( this.element ); this.element.parentNode.removeChild( this.element );
} }
if ( TILE_CACHE[ this.url ]){
delete TILE_CACHE[ this.url ];
}
this.element = null; this.element = null;
this.imgElement = null; this.imgElement = null;

238
src/tilecache.js Normal file
View File

@ -0,0 +1,238 @@
/*
* OpenSeadragon - TileCache
*
* 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( $ ){
// private class
var TileRecord = function( options ) {
$.console.assert( options, "[TileCache.cacheTile] options is required" );
$.console.assert( options.tile, "[TileCache.cacheTile] options.tile is required" );
$.console.assert( options.tiledImage, "[TileCache.cacheTile] options.tiledImage is required" );
this.tile = options.tile;
this.tiledImage = options.tiledImage;
};
// private class
var ImageRecord = function(options) {
$.console.assert( options, "[ImageRecord] options is required" );
$.console.assert( options.image, "[ImageRecord] options.image is required" );
this._image = options.image;
this._tiles = [];
};
ImageRecord.prototype = {
destroy: function() {
this._image = null;
this._renderedContext = null;
this._tiles = null;
},
getImage: function() {
return this._image;
},
getRenderedContext: function() {
return this._renderedContext;
},
setRenderedContext: function(renderedContext) {
this._renderedContext = renderedContext;
},
addTile: function(tile) {
$.console.assert(tile, '[ImageRecord.addTile] tile is required');
this._tiles.push(tile);
},
removeTile: function(tile) {
for (var i = 0; i < this._tiles.length; i++) {
if (this._tiles[i] === tile) {
this._tiles.splice(i, 1);
return;
}
}
$.console.warn('[ImageRecord.removeTile] trying to remove unknown tile', tile);
},
getTileCount: function() {
return this._tiles.length;
}
};
/**
* @class TileCache
* @memberof OpenSeadragon
* @classdesc Stores all the tiles displayed in a {@link OpenSeadragon.Viewer}.
* You generally won't have to interact with the TileCache directly.
* @param {Object} options - Configuration for this TileCache.
* @param {Number} [options.maxImageCacheCount] - See maxImageCacheCount in
* {@link OpenSeadragon.Options} for details.
*/
$.TileCache = function( options ) {
options = options || {};
this._maxImageCacheCount = options.maxImageCacheCount || $.DEFAULT_SETTINGS.maxImageCacheCount;
this._tilesLoaded = [];
this._imagesLoaded = [];
this._imagesLoadedCount = 0;
};
$.TileCache.prototype = /** @lends OpenSeadragon.TileCache.prototype */{
/**
* @returns {Number} The total number of tiles that have been loaded by
* this TileCache.
*/
numTilesLoaded: function() {
return this._tilesLoaded.length;
},
/**
* Caches the specified tile, removing an old tile if necessary to stay under the
* maxImageCacheCount specified on construction. Note that if multiple tiles reference
* the same image, there may be more tiles than maxImageCacheCount; the goal is to keep
* the number of images below that number. Note, as well, that even the number of images
* may temporarily surpass that number, but should eventually come back down to the max specified.
* @param {Object} options - Tile info.
* @param {OpenSeadragon.Tile} options.tile - The tile to cache.
* @param {OpenSeadragon.TiledImage} options.tiledImage - The TiledImage that owns that tile.
* @param {Number} [options.cutoff=0] - If adding this tile goes over the cache max count, this
* function will release an old tile. The cutoff option specifies a tile level at or below which
* tiles will not be released.
*/
cacheTile: function( options ) {
$.console.assert( options, "[TileCache.cacheTile] options is required" );
$.console.assert( options.tile, "[TileCache.cacheTile] options.tile is required" );
$.console.assert( options.tile.url, "[TileCache.cacheTile] options.tile.url is required" );
$.console.assert( options.tile.image, "[TileCache.cacheTile] options.tile.image is required" );
$.console.assert( options.tiledImage, "[TileCache.cacheTile] options.tiledImage is required" );
var cutoff = options.cutoff || 0;
var insertionIndex = this._tilesLoaded.length;
var imageRecord = this._imagesLoaded[options.tile.url];
if (!imageRecord) {
imageRecord = this._imagesLoaded[options.tile.url] = new ImageRecord({
image: options.tile.image
});
this._imagesLoadedCount++;
}
imageRecord.addTile(options.tile);
options.tile.cacheImageRecord = imageRecord;
// Note that just because we're unloading a tile doesn't necessarily mean
// we're unloading an image. With repeated calls it should sort itself out, though.
if ( this._imagesLoadedCount > this._maxImageCacheCount ) {
var worstTile = null;
var worstTileIndex = -1;
var prevTile, worstTime, worstLevel, prevTime, prevLevel, prevTileRecord;
for ( var i = this._tilesLoaded.length - 1; i >= 0; i-- ) {
prevTileRecord = this._tilesLoaded[ i ];
prevTile = prevTileRecord.tile;
if ( prevTile.level <= cutoff || prevTile.beingDrawn ) {
continue;
} else if ( !worstTile ) {
worstTile = prevTile;
worstTileIndex = i;
continue;
}
prevTime = prevTile.lastTouchTime;
worstTime = worstTile.lastTouchTime;
prevLevel = prevTile.level;
worstLevel = worstTile.level;
if ( prevTime < worstTime ||
( prevTime == worstTime && prevLevel > worstLevel ) ) {
worstTile = prevTile;
worstTileIndex = i;
}
}
if ( worstTile && worstTileIndex >= 0 ) {
this._unloadTile(worstTile);
insertionIndex = worstTileIndex;
}
}
this._tilesLoaded[ insertionIndex ] = new TileRecord({
tile: options.tile,
tiledImage: options.tiledImage
});
},
/**
* Clears all tiles associated with the specified tiledImage.
* @param {OpenSeadragon.TiledImage} tiledImage
*/
clearTilesFor: function( tiledImage ) {
$.console.assert(tiledImage, '[TileCache.clearTilesFor] tiledImage is required');
var tileRecord;
for ( var i = 0; i < this._tilesLoaded.length; ++i ) {
tileRecord = this._tilesLoaded[ i ];
if ( tileRecord.tiledImage === tiledImage ) {
this._unloadTile(tileRecord.tile);
this._tilesLoaded.splice( i, 1 );
i--;
}
}
},
// private
getImageRecord: function(url) {
$.console.assert(url, '[TileCache.getImageRecord] url is required');
return this._imagesLoaded[url];
},
// private
_unloadTile: function(tile) {
$.console.assert(tile, '[TileCache._unloadTile] tile is required');
tile.unload();
tile.cacheImageRecord = null;
var imageRecord = this._imagesLoaded[tile.url];
imageRecord.removeTile(tile);
if (!imageRecord.getTileCount()) {
imageRecord.destroy();
delete this._imagesLoaded[tile.url];
this._imagesLoadedCount--;
}
}
};
}( OpenSeadragon ));

1149
src/tiledimage.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -38,44 +38,52 @@
/** /**
* @class TileSource * @class TileSource
* @classdesc The TileSource contains the most basic implementation required to create a * @classdesc The TileSource contains the most basic implementation required to create a
* smooth transition between layer in an image pyramid. It has only a single key * smooth transition between layers in an image pyramid. It has only a single key
* interface that must be implemented to complete it key functionality: * interface that must be implemented to complete its key functionality:
* 'getTileUrl'. It also has several optional interfaces that can be * 'getTileUrl'. It also has several optional interfaces that can be
* implemented if a new TileSource wishes to support configuration via a simple * implemented if a new TileSource wishes to support configuration via a simple
* object or array ('configure') and if the tile source supports or requires * object or array ('configure') and if the tile source supports or requires
* configuration via retreival of a document on the network ala AJAX or JSONP, * configuration via retrieval of a document on the network ala AJAX or JSONP,
* ('getImageInfo'). * ('getImageInfo').
* <br/> * <br/>
* By default the image pyramid is split into N layers where the images longest * By default the image pyramid is split into N layers where the image's longest
* side in M (in pixels), where N is the smallest integer which satisfies * side in M (in pixels), where N is the smallest integer which satisfies
* <strong>2^(N+1) >= M</strong>. * <strong>2^(N+1) >= M</strong>.
* *
* @memberof OpenSeadragon * @memberof OpenSeadragon
* @extends OpenSeadragon.EventSource * @extends OpenSeadragon.EventSource
* @param {Number|Object|Array|String} width * @param {Object} options
* If more than a single argument is supplied, the traditional use of * You can either specify a URL, or literally define the TileSource (by specifying
* positional parameters is supplied and width is expected to be the width * width, height, tileSize, tileOverlap, minLevel, and maxLevel). For the former,
* source image at its max resolution in pixels. If a single argument is supplied and * the extending class is expected to implement 'getImageInfo' and 'configure'.
* it is an Object or Array, the construction is assumed to occur through * For the latter, the construction is assumed to occur through
* the extending classes implementation of 'configure'. Finally if only a * the extending classes implementation of 'configure'.
* single argument is supplied and it is a String, the extending class is * @param {String} [options.url]
* expected to implement 'getImageInfo' and 'configure'. * The URL for the data necessary for this TileSource.
* @param {Number} height * @param {Function} [options.success]
* A function to be called upon successful creation.
* @param {Boolean} [options.ajaxWithCredentials]
* If this TileSource needs to make an AJAX call, this specifies whether to set
* the XHR's withCredentials (for accessing secure data).
* @param {Number} [options.width]
* Width of the source image at max resolution in pixels. * Width of the source image at max resolution in pixels.
* @param {Number} tileSize * @param {Number} [options.height]
* Height of the source image at max resolution in pixels.
* @param {Number} [options.tileSize]
* The size of the tiles to assumed to make up each pyramid layer in pixels. * The size of the tiles to assumed to make up each pyramid layer in pixels.
* Tile size determines the point at which the image pyramid must be * Tile size determines the point at which the image pyramid must be
* divided into a matrix of smaller images. * divided into a matrix of smaller images.
* @param {Number} tileOverlap * @param {Number} [options.tileOverlap]
* The number of pixels each tile is expected to overlap touching tiles. * The number of pixels each tile is expected to overlap touching tiles.
* @param {Number} minLevel * @param {Number} [options.minLevel]
* The minimum level to attempt to load. * The minimum level to attempt to load.
* @param {Number} maxLevel * @param {Number} [options.maxLevel]
* The maximum level to attempt to load. * The maximum level to attempt to load.
*/ */
$.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLevel ) { $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLevel ) {
var callback = null, var _this = this;
args = arguments,
var args = arguments,
options, options,
i; i;
@ -102,18 +110,22 @@ $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLeve
//source //source
$.extend( true, this, options ); $.extend( true, this, options );
if (!this.success) {
//Any functions that are passed as arguments are bound to the ready callback //Any functions that are passed as arguments are bound to the ready callback
/*jshint loopfunc:true*/
for ( i = 0; i < arguments.length; i++ ) { for ( i = 0; i < arguments.length; i++ ) {
if ( $.isFunction( arguments[ i ] ) ) { if ( $.isFunction( arguments[ i ] ) ) {
callback = arguments[ i ]; this.success = arguments[ i ];
this.addHandler( 'ready', function ( event ) {
callback( event );
} );
//only one callback per constructor //only one callback per constructor
break; break;
} }
} }
}
if (this.success) {
this.addHandler( 'ready', function ( event ) {
_this.success( event );
} );
}
/** /**
* Ratio of width to height * Ratio of width to height
@ -154,6 +166,10 @@ $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLeve
*/ */
if( 'string' == $.type( arguments[ 0 ] ) ){ if( 'string' == $.type( arguments[ 0 ] ) ){
this.url = arguments[0];
}
if (this.url) {
//in case the getImageInfo method is overriden and/or implies an //in case the getImageInfo method is overriden and/or implies an
//async mechanism set some safe defaults first //async mechanism set some safe defaults first
this.aspectRatio = 1; this.aspectRatio = 1;
@ -165,7 +181,7 @@ $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLeve
this.ready = false; this.ready = false;
//configuration via url implies the extending class //configuration via url implies the extending class
//implements and 'configure' //implements and 'configure'
this.getImageInfo( arguments[ 0 ] ); this.getImageInfo( this.url );
} else { } else {
@ -185,8 +201,8 @@ $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLeve
Math.log( 2 ) Math.log( 2 )
) : 0 ) : 0
); );
if( callback && $.isFunction( callback ) ){ if( this.success && $.isFunction( this.success ) ){
callback( this ); this.success( this );
} }
} }
@ -355,6 +371,10 @@ $.TileSource.prototype = /** @lends OpenSeadragon.TileSource.prototype */{
} }
options = $TileSource.prototype.configure.apply( _this, [ data, url ]); options = $TileSource.prototype.configure.apply( _this, [ data, url ]);
if (options.ajaxWithCredentials === undefined) {
options.ajaxWithCredentials = _this.ajaxWithCredentials;
}
readySource = new $TileSource( options ); readySource = new $TileSource( options );
_this.ready = true; _this.ready = true;
/** /**
@ -383,10 +403,14 @@ $.TileSource.prototype = /** @lends OpenSeadragon.TileSource.prototype */{
}); });
} else { } else {
// request info via xhr asynchronously. // request info via xhr asynchronously.
$.makeAjaxRequest( url, function( xhr ) { $.makeAjaxRequest( {
url: url,
withCredentials: this.ajaxWithCredentials,
success: function( xhr ) {
var data = processResponse( xhr ); var data = processResponse( xhr );
callback( data ); callback( data );
}, function ( xhr, exc ) { },
error: function ( xhr, exc ) {
var msg; var msg;
/* /*
@ -422,6 +446,7 @@ $.TileSource.prototype = /** @lends OpenSeadragon.TileSource.prototype */{
message: msg, message: msg,
source: url source: url
}); });
}
}); });
} }

View File

@ -34,110 +34,9 @@
(function( $ ){ (function( $ ){
/** // deprecated
* @class TileSourceCollection
* @memberof OpenSeadragon
* @extends OpenSeadragon.TileSource
*/
$.TileSourceCollection = function( tileSize, tileSources, rows, layout ) { $.TileSourceCollection = function( tileSize, tileSources, rows, layout ) {
var options; $.console.error('TileSourceCollection is deprecated; use World instead');
if( $.isPlainObject( tileSize ) ){
options = tileSize;
}else{
options = {
tileSize: arguments[ 0 ],
tileSources: arguments[ 1 ],
rows: arguments[ 2 ],
layout: arguments[ 3 ]
};
}
if( !options.layout ){
options.layout = 'horizontal';
}
var minLevel = 0,
levelSize = 1.0,
tilesPerRow = Math.ceil( options.tileSources.length / options.rows ),
longSide = tilesPerRow >= options.rows ?
tilesPerRow :
options.rows;
if( 'horizontal' == options.layout ){
options.width = ( options.tileSize ) * tilesPerRow;
options.height = ( options.tileSize ) * options.rows;
} else {
options.height = ( options.tileSize ) * tilesPerRow;
options.width = ( options.tileSize ) * options.rows;
}
options.tileOverlap = -options.tileMargin;
options.tilesPerRow = tilesPerRow;
//Set min level to avoid loading sublevels since collection is a
//different kind of abstraction
while( levelSize < ( options.tileSize ) * longSide ){
//$.console.log( '%s levelSize %s minLevel %s', options.tileSize * longSide, levelSize, minLevel );
levelSize = levelSize * 2.0;
minLevel++;
}
options.minLevel = minLevel;
//for( var name in options ){
// $.console.log( 'Collection %s %s', name, options[ name ] );
//}
$.TileSource.apply( this, [ options ] );
}; };
$.extend( $.TileSourceCollection.prototype, $.TileSource.prototype, /** @lends OpenSeadragon.TileSourceCollection.prototype */{
/**
* @function
* @param {Number} level
* @param {Number} x
* @param {Number} y
*/
getTileBounds: function( level, x, y ) {
var dimensionsScaled = this.dimensions.times( this.getLevelScale( level ) ),
px = this.tileSize * x - this.tileOverlap,
py = this.tileSize * y - this.tileOverlap,
sx = this.tileSize + 1 * this.tileOverlap,
sy = this.tileSize + 1 * this.tileOverlap,
scale = 1.0 / dimensionsScaled.x;
sx = Math.min( sx, dimensionsScaled.x - px );
sy = Math.min( sy, dimensionsScaled.y - py );
return new $.Rect( px * scale, py * scale, sx * scale, sy * scale );
},
/**
*
* @function
*/
configure: function( data, url ){
return;
},
/**
* @function
* @param {Number} level
* @param {Number} x
* @param {Number} y
*/
getTileUrl: function( level, x, y ) {
//$.console.log([ level, '/', x, '_', y ].join( '' ));
return null;
}
});
}( OpenSeadragon )); }( OpenSeadragon ));

File diff suppressed because it is too large Load Diff

View File

@ -37,10 +37,23 @@
/** /**
* @class Viewport * @class Viewport
* @classdesc Handles coordinate-related functionality (zoom, pan, rotation, etc.) for an {@link OpenSeadragon.Viewer}.
* A new instance is created for each TileSource opened (see {@link OpenSeadragon.Viewer#viewport}).
*
* @memberof OpenSeadragon * @memberof OpenSeadragon
* @classdesc Handles coordinate-related functionality (zoom, pan, rotation, etc.)
* for an {@link OpenSeadragon.Viewer}.
* @param {Object} options - Options for this Viewport.
* @param {Object} [options.margins] - See viewportMargins in {@link OpenSeadragon.Options}.
* @param {Number} [options.springStiffness] - See springStiffness in {@link OpenSeadragon.Options}.
* @param {Number} [options.animationTime] - See animationTime in {@link OpenSeadragon.Options}.
* @param {Number} [options.minZoomImageRatio] - See minZoomImageRatio in {@link OpenSeadragon.Options}.
* @param {Number} [options.maxZoomPixelRatio] - See maxZoomPixelRatio in {@link OpenSeadragon.Options}.
* @param {Number} [options.visibilityRatio] - See visibilityRatio in {@link OpenSeadragon.Options}.
* @param {Boolean} [options.wrapHorizontal] - See wrapHorizontal in {@link OpenSeadragon.Options}.
* @param {Boolean} [options.wrapVertical] - See wrapVertical in {@link OpenSeadragon.Options}.
* @param {Number} [options.defaultZoomLevel] - See defaultZoomLevel in {@link OpenSeadragon.Options}.
* @param {Number} [options.minZoomLevel] - See minZoomLevel in {@link OpenSeadragon.Options}.
* @param {Number} [options.maxZoomLevel] - See maxZoomLevel in {@link OpenSeadragon.Options}.
* @param {Number} [options.degrees] - See degrees in {@link OpenSeadragon.Options}.
* @param {Boolean} [options.homeFillsViewer] - See homeFillsViewer in {@link OpenSeadragon.Options}.
*/ */
$.Viewport = function( options ) { $.Viewport = function( options ) {
@ -63,6 +76,15 @@ $.Viewport = function( options ) {
delete options.config; delete options.config;
} }
this._margins = $.extend({
left: 0,
top: 0,
right: 0,
bottom: 0
}, options.margins || {});
delete options.margins;
$.extend( true, this, { $.extend( true, this, {
//required settings //required settings
@ -89,6 +111,11 @@ $.Viewport = function( options ) {
}, options ); }, options );
this._containerInnerSize = new $.Point(
Math.max(1, this.containerSize.x - (this._margins.left + this._margins.right)),
Math.max(1, this.containerSize.y - (this._margins.top + this._margins.bottom))
);
this.centerSpringX = new $.Spring({ this.centerSpringX = new $.Spring({
initial: 0, initial: 0,
springStiffness: this.springStiffness, springStiffness: this.springStiffness,
@ -105,44 +132,77 @@ $.Viewport = function( options ) {
animationTime: this.animationTime animationTime: this.animationTime
}); });
this._oldCenterX = this.centerSpringX.current.value;
this._oldCenterY = this.centerSpringY.current.value;
this._oldZoom = this.zoomSpring.current.value;
if (this.contentSize) {
this.resetContentSize( this.contentSize ); this.resetContentSize( this.contentSize );
} else {
this.setHomeBounds(new $.Rect(0, 0, 1, 1), 1);
}
this.goHome( true ); this.goHome( true );
this.update(); this.update();
}; };
$.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
/** /**
* Updates the viewport's home bounds and constraints for the given content size.
* @function * @function
* @param {OpenSeadragon.Point} contentSize - size of the content in content units
* @return {OpenSeadragon.Viewport} Chainable. * @return {OpenSeadragon.Viewport} Chainable.
* @fires OpenSeadragon.Viewer.event:reset-size * @fires OpenSeadragon.Viewer.event:reset-size
*/ */
resetContentSize: function( contentSize ){ resetContentSize: function( contentSize ){
this.contentSize = contentSize; $.console.assert(contentSize, "[Viewport.resetContentSize] contentSize is required");
$.console.assert(contentSize instanceof $.Point, "[Viewport.resetContentSize] contentSize must be an OpenSeadragon.Point");
$.console.assert(contentSize.x > 0, "[Viewport.resetContentSize] contentSize.x must be greater than 0");
$.console.assert(contentSize.y > 0, "[Viewport.resetContentSize] contentSize.y must be greater than 0");
this.setHomeBounds(new $.Rect(0, 0, 1, contentSize.y / contentSize.x), contentSize.x);
return this;
},
/**
* Updates the viewport's home bounds and constraints.
* @function
* @param {OpenSeadragon.Rect} bounds - the new bounds in viewport coordinates
* @param {Number} contentFactor - how many content units per viewport unit
* @fires OpenSeadragon.Viewer.event:reset-size
*/
setHomeBounds: function(bounds, contentFactor) {
$.console.assert(bounds, "[Viewport.setHomeBounds] bounds is required");
$.console.assert(bounds instanceof $.Rect, "[Viewport.setHomeBounds] bounds must be an OpenSeadragon.Rect");
$.console.assert(bounds.width > 0, "[Viewport.setHomeBounds] bounds.width must be greater than 0");
$.console.assert(bounds.height > 0, "[Viewport.setHomeBounds] bounds.height must be greater than 0");
this.homeBounds = bounds.clone();
this.contentSize = this.homeBounds.getSize().times(contentFactor);
this.contentAspectX = this.contentSize.x / this.contentSize.y; this.contentAspectX = this.contentSize.x / this.contentSize.y;
this.contentAspectY = this.contentSize.y / this.contentSize.x; this.contentAspectY = this.contentSize.y / this.contentSize.x;
this.fitWidthBounds = new $.Rect( 0, 0, 1, this.contentAspectY );
this.fitHeightBounds = new $.Rect( 0, 0, this.contentAspectY, this.contentAspectY);
this.homeBounds = new $.Rect( 0, 0, 1, this.contentAspectY );
if( this.viewer ){ if( this.viewer ){
/** /**
* Raised when the viewer's content size is reset (see {@link OpenSeadragon.Viewport#resetContentSize}). * Raised when the viewer's content size or home bounds are reset
* (see {@link OpenSeadragon.Viewport#resetContentSize},
* {@link OpenSeadragon.Viewport#setHomeBounds}).
* *
* @event reset-size * @event reset-size
* @memberof OpenSeadragon.Viewer * @memberof OpenSeadragon.Viewer
* @type {object} * @type {object}
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
* @property {OpenSeadragon.Point} contentSize * @property {OpenSeadragon.Point} contentSize
* @property {OpenSeadragon.Rect} homeBounds
* @property {Number} contentFactor
* @property {?Object} userData - Arbitrary subscriber-defined object. * @property {?Object} userData - Arbitrary subscriber-defined object.
*/ */
this.viewer.raiseEvent( 'reset-size', { this.viewer.raiseEvent( 'reset-size', {
contentSize: contentSize contentSize: this.contentSize.clone(),
contentFactor: contentFactor,
homeBounds: this.homeBounds.clone()
}); });
} }
return this;
}, },
/** /**
@ -155,15 +215,18 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
var aspectFactor = var aspectFactor =
this.contentAspectX / this.getAspectRatio(); this.contentAspectX / this.getAspectRatio();
var output;
if( this.homeFillsViewer ){ // fill the viewer and clip the image if( this.homeFillsViewer ){ // fill the viewer and clip the image
return ( aspectFactor >= 1) ? output = ( aspectFactor >= 1) ?
aspectFactor : aspectFactor :
1; 1;
} else { } else {
return ( aspectFactor >= 1 ) ? output = ( aspectFactor >= 1 ) ?
1 : 1 :
aspectFactor; aspectFactor;
} }
return output / this.homeBounds.width;
} }
}, },
@ -216,16 +279,18 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
this.minZoomLevel : this.minZoomLevel :
this.minZoomImageRatio * homeZoom; this.minZoomImageRatio * homeZoom;
return Math.min( zoom, homeZoom ); return zoom;
}, },
/** /**
* @function * @function
*/ */
getMaxZoom: function() { getMaxZoom: function() {
var zoom = this.maxZoomLevel ? var zoom = this.maxZoomLevel;
this.maxZoomLevel : if (!zoom) {
( this.contentSize.x * this.maxZoomPixelRatio / this.containerSize.x ); zoom = this.contentSize.x * this.maxZoomPixelRatio / this._containerInnerSize.x;
zoom /= this.homeBounds.width;
}
return Math.max( zoom, this.getHomeZoom() ); return Math.max( zoom, this.getHomeZoom() );
}, },
@ -234,11 +299,12 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
* @function * @function
*/ */
getAspectRatio: function() { getAspectRatio: function() {
return this.containerSize.x / this.containerSize.y; return this._containerInnerSize.x / this._containerInnerSize.y;
}, },
/** /**
* @function * @function
* @returns {OpenSeadragon.Point} The size of the container, in screen coordinates.
*/ */
getContainerSize: function() { getContainerSize: function() {
return new $.Point( return new $.Point(
@ -250,6 +316,7 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
/** /**
* @function * @function
* @param {Boolean} current - Pass true for the current location; defaults to false (target location). * @param {Boolean} current - Pass true for the current location; defaults to false (target location).
* @returns {OpenSeadragon.Rect} The location you are zoomed/panned to, in viewport coordinates.
*/ */
getBounds: function( current ) { getBounds: function( current ) {
var center = this.getCenter( current ), var center = this.getCenter( current ),
@ -264,6 +331,22 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
); );
}, },
/**
* @function
* @param {Boolean} current - Pass true for the current location; defaults to false (target location).
* @returns {OpenSeadragon.Rect} The location you are zoomed/panned to,
* including the space taken by margins, in viewport coordinates.
*/
getBoundsWithMargins: function( current ) {
var bounds = this.getBounds(current);
var factor = this._containerInnerSize.x * this.getZoom(current);
bounds.x -= this._margins.left / factor;
bounds.y -= this._margins.top / factor;
bounds.width += (this._margins.left + this._margins.right) / factor;
bounds.height += (this._margins.top + this._margins.bottom) / factor;
return bounds;
},
/** /**
* @function * @function
* @param {Boolean} current - Pass true for the current location; defaults to false (target location). * @param {Boolean} current - Pass true for the current location; defaults to false (target location).
@ -304,13 +387,9 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
height height
); );
newZoomPixel = this.zoomPoint.minus( newZoomPixel = this._pixelFromPoint(this.zoomPoint, bounds);
bounds.getTopLeft()
).times(
this.containerSize.x / bounds.width
);
deltaZoomPixels = newZoomPixel.minus( oldZoomPixel ); deltaZoomPixels = newZoomPixel.minus( oldZoomPixel );
deltaZoomPoints = deltaZoomPixels.divide( this.containerSize.x * zoom ); deltaZoomPoints = deltaZoomPixels.divide( this._containerInnerSize.x * zoom );
return centerTarget.plus( deltaZoomPoints ); return centerTarget.plus( deltaZoomPoints );
}, },
@ -335,13 +414,7 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
* @return {OpenSeadragon.Rect} constrained bounds. * @return {OpenSeadragon.Rect} constrained bounds.
*/ */
_applyBoundaryConstraints: function( bounds, immediately ) { _applyBoundaryConstraints: function( bounds, immediately ) {
var horizontalThreshold, var dx = 0,
verticalThreshold,
left,
right,
top,
bottom,
dx = 0,
dy = 0, dy = 0,
newBounds = new $.Rect( newBounds = new $.Rect(
bounds.x, bounds.x,
@ -350,49 +423,52 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
bounds.height bounds.height
); );
horizontalThreshold = this.visibilityRatio * newBounds.width; var horizontalThreshold = this.visibilityRatio * newBounds.width;
verticalThreshold = this.visibilityRatio * newBounds.height; var verticalThreshold = this.visibilityRatio * newBounds.height;
left = newBounds.x + newBounds.width;
right = 1 - newBounds.x;
top = newBounds.y + newBounds.height;
bottom = this.contentAspectY - newBounds.y;
if ( this.wrapHorizontal ) { if ( this.wrapHorizontal ) {
//do nothing //do nothing
} else { } else {
if ( left < horizontalThreshold ) { var thresholdLeft = newBounds.x + (newBounds.width - horizontalThreshold);
dx = horizontalThreshold - left; if (this.homeBounds.x > thresholdLeft) {
dx = this.homeBounds.x - thresholdLeft;
}
var homeRight = this.homeBounds.x + this.homeBounds.width;
var thresholdRight = newBounds.x + horizontalThreshold;
if (homeRight < thresholdRight) {
var newDx = homeRight - thresholdRight;
if (dx) {
dx = (dx + newDx) / 2;
} else {
dx = newDx;
} }
if ( right < horizontalThreshold ) {
dx = dx ?
( dx + right - horizontalThreshold ) / 2 :
( right - horizontalThreshold );
} }
} }
if ( this.wrapVertical ) { if ( this.wrapVertical ) {
//do nothing //do nothing
} else { } else {
if ( top < verticalThreshold ) { var thresholdTop = newBounds.y + (newBounds.height - verticalThreshold);
dy = ( verticalThreshold - top ); if (this.homeBounds.y > thresholdTop) {
dy = this.homeBounds.y - thresholdTop;
}
var homeBottom = this.homeBounds.y + this.homeBounds.height;
var thresholdBottom = newBounds.y + verticalThreshold;
if (homeBottom < thresholdBottom) {
var newDy = homeBottom - thresholdBottom;
if (dy) {
dy = (dy + newDy) / 2;
} else {
dy = newDy;
} }
if ( bottom < verticalThreshold ) {
dy = dy ?
( dy + bottom - verticalThreshold ) / 2 :
( bottom - verticalThreshold );
} }
} }
if ( dx || dy || immediately ) { if ( dx || dy ) {
newBounds.x += dx; newBounds.x += dx;
newBounds.y += dy; newBounds.y += dy;
if( newBounds.width > 1 ){
newBounds.x = 0.5 - newBounds.width/2;
}
if( newBounds.height > this.contentAspectY ){
newBounds.y = this.contentAspectY/2 - newBounds.height/2;
}
} }
if( this.viewer ){ if( this.viewer ){
@ -512,21 +588,28 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
} }
newBounds = this._applyBoundaryConstraints( newBounds, immediately ); newBounds = this._applyBoundaryConstraints( newBounds, immediately );
center = newBounds.getCenter();
} }
if ( newZoom == oldZoom || newBounds.width == oldBounds.width ) { if (immediately) {
return this.panTo( constraints ? newBounds.getCenter() : center, immediately ); this.panTo( center, true );
return this.zoomTo(newZoom, null, true);
}
if (Math.abs(newZoom - oldZoom) < 0.00000000001 ||
Math.abs(newBounds.width - oldBounds.width) < 0.00000000001) {
return this.panTo( center, immediately );
} }
referencePoint = oldBounds.getTopLeft().times( referencePoint = oldBounds.getTopLeft().times(
this.containerSize.x / oldBounds.width this._containerInnerSize.x / oldBounds.width
).minus( ).minus(
newBounds.getTopLeft().times( newBounds.getTopLeft().times(
this.containerSize.x / newBounds.width this._containerInnerSize.x / newBounds.width
) )
).divide( ).divide(
this.containerSize.x / oldBounds.width - this._containerInnerSize.x / oldBounds.width -
this.containerSize.x / newBounds.width this._containerInnerSize.x / newBounds.width
); );
return this.zoomTo( newZoom, referencePoint, immediately ); return this.zoomTo( newZoom, referencePoint, immediately );
@ -559,53 +642,27 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
}, },
/** /**
* @function * Zooms so the image just fills the viewer vertically.
* @param {Boolean} immediately * @param {Boolean} immediately
* @return {OpenSeadragon.Viewport} Chainable. * @return {OpenSeadragon.Viewport} Chainable.
*/ */
fitVertically: function( immediately ) { fitVertically: function( immediately ) {
var center = this.getCenter(); var box = new $.Rect(this.homeBounds.x + (this.homeBounds.width / 2), this.homeBounds.y,
0, this.homeBounds.height);
if ( this.wrapHorizontal ) { return this.fitBounds( box, immediately );
center.x = ( 1 + ( center.x % 1 ) ) % 1;
this.centerSpringX.resetTo( center.x );
this.centerSpringX.update();
}
if ( this.wrapVertical ) {
center.y = (
this.contentAspectY + ( center.y % this.contentAspectY )
) % this.contentAspectY;
this.centerSpringY.resetTo( center.y );
this.centerSpringY.update();
}
return this.fitBounds( this.fitHeightBounds, immediately );
}, },
/** /**
* @function * Zooms so the image just fills the viewer horizontally.
* @param {Boolean} immediately * @param {Boolean} immediately
* @return {OpenSeadragon.Viewport} Chainable. * @return {OpenSeadragon.Viewport} Chainable.
*/ */
fitHorizontally: function( immediately ) { fitHorizontally: function( immediately ) {
var center = this.getCenter(); var box = new $.Rect(this.homeBounds.x, this.homeBounds.y + (this.homeBounds.height / 2),
this.homeBounds.width, 0);
if ( this.wrapHorizontal ) { return this.fitBounds( box, immediately );
center.x = (
this.contentAspectX + ( center.x % this.contentAspectX )
) % this.contentAspectX;
this.centerSpringX.resetTo( center.x );
this.centerSpringX.update();
}
if ( this.wrapVertical ) {
center.y = ( 1 + ( center.y % 1 ) ) % 1;
this.centerSpringY.resetTo( center.y );
this.centerSpringY.update();
}
return this.fitBounds( this.fitWidthBounds, immediately );
}, },
@ -736,7 +793,7 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
/** /**
* Raised when rotation has been changed. * Raised when rotation has been changed.
* *
* @event update-viewport * @event rotate
* @memberof OpenSeadragon.Viewer * @memberof OpenSeadragon.Viewer
* @type {object} * @type {object}
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
@ -769,12 +826,16 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
newBounds = oldBounds, newBounds = oldBounds,
widthDeltaFactor; widthDeltaFactor;
this.containerSize = new $.Point( this.containerSize.x = newContainerSize.x;
newContainerSize.x, this.containerSize.y = newContainerSize.y;
newContainerSize.y
this._containerInnerSize = new $.Point(
Math.max(1, newContainerSize.x - (this._margins.left + this._margins.right)),
Math.max(1, newContainerSize.y - (this._margins.top + this._margins.bottom))
); );
if ( maintain ) { if ( maintain ) {
// TODO: widthDeltaFactor will always be 1; probably not what's intended
widthDeltaFactor = newContainerSize.x / this.containerSize.x; widthDeltaFactor = newContainerSize.x / this.containerSize.x;
newBounds.width = oldBounds.width * widthDeltaFactor; newBounds.width = oldBounds.width * widthDeltaFactor;
newBounds.height = newBounds.width / this.getAspectRatio(); newBounds.height = newBounds.width / this.getAspectRatio();
@ -805,10 +866,7 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
* @function * @function
*/ */
update: function() { update: function() {
var oldCenterX = this.centerSpringX.current.value, var oldZoomPixel,
oldCenterY = this.centerSpringY.current.value,
oldZoom = this.zoomSpring.current.value,
oldZoomPixel,
newZoomPixel, newZoomPixel,
deltaZoomPixels, deltaZoomPixels,
deltaZoomPoints; deltaZoomPoints;
@ -819,7 +877,7 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
this.zoomSpring.update(); this.zoomSpring.update();
if (this.zoomPoint && this.zoomSpring.current.value != oldZoom) { if (this.zoomPoint && this.zoomSpring.current.value != this._oldZoom) {
newZoomPixel = this.pixelFromPoint( this.zoomPoint, true ); newZoomPixel = this.pixelFromPoint( this.zoomPoint, true );
deltaZoomPixels = newZoomPixel.minus( oldZoomPixel ); deltaZoomPixels = newZoomPixel.minus( oldZoomPixel );
deltaZoomPoints = this.deltaPointsFromPixels( deltaZoomPixels, true ); deltaZoomPoints = this.deltaPointsFromPixels( deltaZoomPixels, true );
@ -833,9 +891,15 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
this.centerSpringX.update(); this.centerSpringX.update();
this.centerSpringY.update(); this.centerSpringY.update();
return this.centerSpringX.current.value != oldCenterX || var changed = this.centerSpringX.current.value != this._oldCenterX ||
this.centerSpringY.current.value != oldCenterY || this.centerSpringY.current.value != this._oldCenterY ||
this.zoomSpring.current.value != oldZoom; this.zoomSpring.current.value != this._oldZoom;
this._oldCenterX = this.centerSpringX.current.value;
this._oldCenterY = this.centerSpringY.current.value;
this._oldZoom = this.zoomSpring.current.value;
return changed;
}, },
@ -846,7 +910,7 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
*/ */
deltaPixelsFromPoints: function( deltaPoints, current ) { deltaPixelsFromPoints: function( deltaPoints, current ) {
return deltaPoints.times( return deltaPoints.times(
this.containerSize.x * this.getZoom( current ) this._containerInnerSize.x * this.getZoom( current )
); );
}, },
@ -857,7 +921,7 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
*/ */
deltaPointsFromPixels: function( deltaPixels, current ) { deltaPointsFromPixels: function( deltaPixels, current ) {
return deltaPixels.divide( return deltaPixels.divide(
this.containerSize.x * this.getZoom( current ) this._containerInnerSize.x * this.getZoom( current )
); );
}, },
@ -867,11 +931,17 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
* @param {Boolean} current - Pass true for the current location; defaults to false (target location). * @param {Boolean} current - Pass true for the current location; defaults to false (target location).
*/ */
pixelFromPoint: function( point, current ) { pixelFromPoint: function( point, current ) {
var bounds = this.getBounds( current ); return this._pixelFromPoint(point, this.getBounds( current ));
},
// private
_pixelFromPoint: function( point, bounds ) {
return point.minus( return point.minus(
bounds.getTopLeft() bounds.getTopLeft()
).times( ).times(
this.containerSize.x / bounds.width this._containerInnerSize.x / bounds.width
).plus(
new $.Point(this._margins.left, this._margins.top)
); );
}, },
@ -882,17 +952,27 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
*/ */
pointFromPixel: function( pixel, current ) { pointFromPixel: function( pixel, current ) {
var bounds = this.getBounds( current ); var bounds = this.getBounds( current );
return pixel.divide( return pixel.minus(
this.containerSize.x / bounds.width new $.Point(this._margins.left, this._margins.top)
).divide(
this._containerInnerSize.x / bounds.width
).plus( ).plus(
bounds.getTopLeft() bounds.getTopLeft()
); );
}, },
// private
_viewportToImageDelta: function( viewerX, viewerY ) {
var scale = this.homeBounds.width;
return new $.Point(viewerX * (this.contentSize.x / scale),
viewerY * ((this.contentSize.y * this.contentAspectX) / scale));
},
/** /**
* Translates from OpenSeadragon viewer coordinate system to image coordinate system. * Translates from OpenSeadragon viewer coordinate system to image coordinate system.
* This method can be called either by passing X,Y coordinates or an * This method can be called either by passing X,Y coordinates or an
* OpenSeadragon.Point * OpenSeadragon.Point
* Note: not accurate with multi-image; use TiledImage.viewportToImageCoordinates instead.
* @function * @function
* @param {OpenSeadragon.Point} viewerX the point in viewport coordinate system. * @param {OpenSeadragon.Point} viewerX the point in viewport coordinate system.
* @param {Number} viewerX X coordinate in viewport coordinate system. * @param {Number} viewerX X coordinate in viewport coordinate system.
@ -904,13 +984,26 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
//they passed a point instead of individual components //they passed a point instead of individual components
return this.viewportToImageCoordinates( viewerX.x, viewerX.y ); return this.viewportToImageCoordinates( viewerX.x, viewerX.y );
} }
return new $.Point( viewerX * this.contentSize.x, viewerY * this.contentSize.y * this.contentAspectX );
if (this.viewer && this.viewer.world.getItemCount() > 1) {
$.console.error('[Viewport.viewportToImageCoordinates] is not accurate with multi-image; use TiledImage.viewportToImageCoordinates instead.');
}
return this._viewportToImageDelta(viewerX - this.homeBounds.x, viewerY - this.homeBounds.y);
},
// private
_imageToViewportDelta: function( imageX, imageY ) {
var scale = this.homeBounds.width;
return new $.Point((imageX / this.contentSize.x) * scale,
(imageY / this.contentSize.y / this.contentAspectX) * scale);
}, },
/** /**
* Translates from image coordinate system to OpenSeadragon viewer coordinate system * Translates from image coordinate system to OpenSeadragon viewer coordinate system
* This method can be called either by passing X,Y coordinates or an * This method can be called either by passing X,Y coordinates or an
* OpenSeadragon.Point * OpenSeadragon.Point
* Note: not accurate with multi-image; use TiledImage.imageToViewportCoordinates instead.
* @function * @function
* @param {OpenSeadragon.Point} imageX the point in image coordinate system. * @param {OpenSeadragon.Point} imageX the point in image coordinate system.
* @param {Number} imageX X coordinate in image coordinate system. * @param {Number} imageX X coordinate in image coordinate system.
@ -922,7 +1015,15 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
//they passed a point instead of individual components //they passed a point instead of individual components
return this.imageToViewportCoordinates( imageX.x, imageX.y ); return this.imageToViewportCoordinates( imageX.x, imageX.y );
} }
return new $.Point( imageX / this.contentSize.x, imageY / this.contentSize.y / this.contentAspectX );
if (this.viewer && this.viewer.world.getItemCount() > 1) {
$.console.error('[Viewport.imageToViewportCoordinates] is not accurate with multi-image; use TiledImage.imageToViewportCoordinates instead.');
}
var point = this._imageToViewportDelta(imageX, imageY);
point.x += this.homeBounds.x;
point.y += this.homeBounds.y;
return point;
}, },
/** /**
@ -930,6 +1031,7 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
* pixel coordinates to OpenSeadragon viewport rectangle coordinates. * pixel coordinates to OpenSeadragon viewport rectangle coordinates.
* This method can be called either by passing X,Y,width,height or an * This method can be called either by passing X,Y,width,height or an
* OpenSeadragon.Rect * OpenSeadragon.Rect
* Note: not accurate with multi-image; use TiledImage.imageToViewportRectangle instead.
* @function * @function
* @param {OpenSeadragon.Rect} imageX the rectangle in image coordinate system. * @param {OpenSeadragon.Rect} imageX the rectangle in image coordinate system.
* @param {Number} imageX the X coordinate of the top left corner of the rectangle * @param {Number} imageX the X coordinate of the top left corner of the rectangle
@ -950,10 +1052,11 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
rect.x, rect.y, rect.width, rect.height rect.x, rect.y, rect.width, rect.height
); );
} }
coordA = this.imageToViewportCoordinates( coordA = this.imageToViewportCoordinates(
imageX, imageY imageX, imageY
); );
coordB = this.imageToViewportCoordinates( coordB = this._imageToViewportDelta(
pixelWidth, pixelHeight pixelWidth, pixelHeight
); );
return new $.Rect( return new $.Rect(
@ -969,6 +1072,7 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
* the viewport in point coordinates to image rectangle coordinates. * the viewport in point coordinates to image rectangle coordinates.
* This method can be called either by passing X,Y,width,height or an * This method can be called either by passing X,Y,width,height or an
* OpenSeadragon.Rect * OpenSeadragon.Rect
* Note: not accurate with multi-image; use TiledImage.viewportToImageRectangle instead.
* @function * @function
* @param {OpenSeadragon.Rect} viewerX the rectangle in viewport coordinate system. * @param {OpenSeadragon.Rect} viewerX the rectangle in viewport coordinate system.
* @param {Number} viewerX the X coordinate of the top left corner of the rectangle * @param {Number} viewerX the X coordinate of the top left corner of the rectangle
@ -989,8 +1093,9 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
rect.x, rect.y, rect.width, rect.height rect.x, rect.y, rect.width, rect.height
); );
} }
coordA = this.viewportToImageCoordinates( viewerX, viewerY ); coordA = this.viewportToImageCoordinates( viewerX, viewerY );
coordB = this.viewportToImageCoordinates( pointWidth, pointHeight ); coordB = this._viewportToImageDelta(pointWidth, pointHeight);
return new $.Rect( return new $.Rect(
coordA.x, coordA.x,
coordA.y, coordA.y,
@ -1002,6 +1107,7 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
/** /**
* Convert pixel coordinates relative to the viewer element to image * Convert pixel coordinates relative to the viewer element to image
* coordinates. * coordinates.
* Note: not accurate with multi-image.
* @param {OpenSeadragon.Point} pixel * @param {OpenSeadragon.Point} pixel
* @returns {OpenSeadragon.Point} * @returns {OpenSeadragon.Point}
*/ */
@ -1013,6 +1119,7 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
/** /**
* Convert pixel coordinates relative to the image to * Convert pixel coordinates relative to the image to
* viewer element coordinates. * viewer element coordinates.
* Note: not accurate with multi-image.
* @param {OpenSeadragon.Point} pixel * @param {OpenSeadragon.Point} pixel
* @returns {OpenSeadragon.Point} * @returns {OpenSeadragon.Point}
*/ */
@ -1023,6 +1130,7 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
/** /**
* Convert pixel coordinates relative to the window to image coordinates. * Convert pixel coordinates relative to the window to image coordinates.
* Note: not accurate with multi-image.
* @param {OpenSeadragon.Point} pixel * @param {OpenSeadragon.Point} pixel
* @returns {OpenSeadragon.Point} * @returns {OpenSeadragon.Point}
*/ */
@ -1034,6 +1142,7 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
/** /**
* Convert image coordinates to pixel coordinates relative to the window. * Convert image coordinates to pixel coordinates relative to the window.
* Note: not accurate with multi-image.
* @param {OpenSeadragon.Point} pixel * @param {OpenSeadragon.Point} pixel
* @returns {OpenSeadragon.Point} * @returns {OpenSeadragon.Point}
*/ */
@ -1091,15 +1200,21 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
* 1 means original image size, 0.5 half size... * 1 means original image size, 0.5 half size...
* Viewport zoom: ratio of the displayed image's width to viewport's width. * Viewport zoom: ratio of the displayed image's width to viewport's width.
* 1 means identical width, 2 means image's width is twice the viewport's width... * 1 means identical width, 2 means image's width is twice the viewport's width...
* Note: not accurate with multi-image.
* @function * @function
* @param {Number} viewportZoom The viewport zoom * @param {Number} viewportZoom The viewport zoom
* target zoom. * target zoom.
* @returns {Number} imageZoom The image zoom * @returns {Number} imageZoom The image zoom
*/ */
viewportToImageZoom: function( viewportZoom ) { viewportToImageZoom: function( viewportZoom ) {
var imageWidth = this.viewer.source.dimensions.x; if (this.viewer && this.viewer.world.getItemCount() > 1) {
var containerWidth = this.getContainerSize().x; $.console.error('[Viewport.viewportToImageZoom] is not accurate with multi-image.');
var viewportToImageZoomRatio = containerWidth / imageWidth; }
var imageWidth = this.contentSize.x;
var containerWidth = this._containerInnerSize.x;
var scale = this.homeBounds.width;
var viewportToImageZoomRatio = (containerWidth / imageWidth) * scale;
return viewportZoom * viewportToImageZoomRatio; return viewportZoom * viewportToImageZoomRatio;
}, },
@ -1109,15 +1224,21 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
* 1 means original image size, 0.5 half size... * 1 means original image size, 0.5 half size...
* Viewport zoom: ratio of the displayed image's width to viewport's width. * Viewport zoom: ratio of the displayed image's width to viewport's width.
* 1 means identical width, 2 means image's width is twice the viewport's width... * 1 means identical width, 2 means image's width is twice the viewport's width...
* Note: not accurate with multi-image.
* @function * @function
* @param {Number} imageZoom The image zoom * @param {Number} imageZoom The image zoom
* target zoom. * target zoom.
* @returns {Number} viewportZoom The viewport zoom * @returns {Number} viewportZoom The viewport zoom
*/ */
imageToViewportZoom: function( imageZoom ) { imageToViewportZoom: function( imageZoom ) {
var imageWidth = this.viewer.source.dimensions.x; if (this.viewer && this.viewer.world.getItemCount() > 1) {
var containerWidth = this.getContainerSize().x; $.console.error('[Viewport.imageToViewportZoom] is not accurate with multi-image.');
var viewportToImageZoomRatio = imageWidth / containerWidth; }
var imageWidth = this.contentSize.x;
var containerWidth = this._containerInnerSize.x;
var scale = this.homeBounds.width;
var viewportToImageZoomRatio = (imageWidth / containerWidth) / scale;
return imageZoom * viewportToImageZoomRatio; return imageZoom * viewportToImageZoomRatio;
} }
}; };

393
src/world.js Normal file
View File

@ -0,0 +1,393 @@
/*
* OpenSeadragon - World
*
* 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( $ ){
/**
* @class World
* @memberof OpenSeadragon
* @extends OpenSeadragon.EventSource
* @classdesc Keeps track of all of the tiled images in the scene.
* @param {Object} options - World options.
* @param {OpenSeadragon.Viewer} options.viewer - The Viewer that owns this World.
**/
$.World = function( options ) {
var _this = this;
$.console.assert( options.viewer, "[World] options.viewer is required" );
$.EventSource.call( this );
this.viewer = options.viewer;
this._items = [];
this._needsDraw = false;
this._delegatedFigureSizes = function(event) {
_this._figureSizes();
};
this._figureSizes();
};
$.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.World.prototype */{
/**
* Add the specified item.
* @param {OpenSeadragon.TiledImage} item - The item to add.
* @param {Number} [options.index] - Index for the item. If not specified, goes at the top.
* @fires OpenSeadragon.World.event:add-item
* @fires OpenSeadragon.World.event:metrics-change
*/
addItem: function( item, options ) {
$.console.assert(item, "[World.addItem] item is required");
$.console.assert(item instanceof $.TiledImage, "[World.addItem] only TiledImages supported at this time");
options = options || {};
if (options.index !== undefined) {
var index = Math.max(0, Math.min(this._items.length, options.index));
this._items.splice(index, 0, item);
} else {
this._items.push( item );
}
this._figureSizes();
this._needsDraw = true;
item.addHandler('bounds-change', this._delegatedFigureSizes);
/**
* Raised when an item is added to the World.
* @event add-item
* @memberOf OpenSeadragon.World
* @type {object}
* @property {OpenSeadragon.Viewer} eventSource - A reference to the World which raised the event.
* @property {OpenSeadragon.TiledImage} item - The item that has been added.
* @property {?Object} userData - Arbitrary subscriber-defined object.
*/
this.raiseEvent( 'add-item', {
item: item
} );
},
/**
* Get the item at the specified index.
* @param {Number} index - The item's index.
* @returns {OpenSeadragon.TiledImage} The item at the specified index.
*/
getItemAt: function( index ) {
$.console.assert(index !== undefined, "[World.getItemAt] index is required");
return this._items[ index ];
},
/**
* Get the index of the given item or -1 if not present.
* @param {OpenSeadragon.TiledImage} item - The item.
* @returns {Number} The index of the item or -1 if not present.
*/
getIndexOfItem: function( item ) {
$.console.assert(item, "[World.getIndexOfItem] item is required");
return $.indexOf( this._items, item );
},
/**
* @returns {Number} The number of items used.
*/
getItemCount: function() {
return this._items.length;
},
/**
* Change the index of a item so that it appears over or under others.
* @param {OpenSeadragon.TiledImage} item - The item to move.
* @param {Number} index - The new index.
* @fires OpenSeadragon.World.event:item-index-change
*/
setItemIndex: function( item, index ) {
$.console.assert(item, "[World.setItemIndex] item is required");
$.console.assert(index !== undefined, "[World.setItemIndex] index is required");
var oldIndex = this.getIndexOfItem( item );
if ( index >= this._items.length ) {
throw new Error( "Index bigger than number of layers." );
}
if ( index === oldIndex || oldIndex === -1 ) {
return;
}
this._items.splice( oldIndex, 1 );
this._items.splice( index, 0, item );
this._needsDraw = true;
/**
* Raised when the order of the indexes has been changed.
* @event item-index-change
* @memberOf OpenSeadragon.World
* @type {object}
* @property {OpenSeadragon.World} eventSource - A reference to the World which raised the event.
* @property {OpenSeadragon.TiledImage} item - The item whose index has
* been changed
* @property {Number} previousIndex - The previous index of the item
* @property {Number} newIndex - The new index of the item
* @property {?Object} userData - Arbitrary subscriber-defined object.
*/
this.raiseEvent( 'item-index-change', {
item: item,
previousIndex: oldIndex,
newIndex: index
} );
},
/**
* Remove an item.
* @param {OpenSeadragon.TiledImage} item - The item to remove.
* @fires OpenSeadragon.World.event:remove-item
* @fires OpenSeadragon.World.event:metrics-change
*/
removeItem: function( item ) {
$.console.assert(item, "[World.removeItem] item is required");
var index = $.indexOf(this._items, item );
if ( index === -1 ) {
return;
}
item.removeHandler('bounds-change', this._delegatedFigureSizes);
this._items.splice( index, 1 );
this._figureSizes();
this._needsDraw = true;
this._raiseRemoveItem(item);
},
/**
* Remove all items.
* @fires OpenSeadragon.World.event:remove-item
* @fires OpenSeadragon.World.event:metrics-change
*/
removeAll: function() {
var item;
for (var i = 0; i < this._items.length; i++) {
item = this._items[i];
item.removeHandler('bounds-change', this._delegatedFigureSizes);
}
var removedItems = this._items;
this._items = [];
this._figureSizes();
this._needsDraw = true;
for (i = 0; i < removedItems.length; i++) {
item = removedItems[i];
this._raiseRemoveItem(item);
}
},
/**
* Clears all tiles and triggers updates for all items.
*/
resetItems: function() {
for ( var i = 0; i < this._items.length; i++ ) {
this._items[i].reset();
}
},
/**
* Updates (i.e. animates bounds of) all items.
*/
update: function() {
var animated = false;
for ( var i = 0; i < this._items.length; i++ ) {
animated = this._items[i].update() || animated;
}
return animated;
},
/**
* Draws all items.
*/
draw: function() {
for ( var i = 0; i < this._items.length; i++ ) {
this._items[i].draw();
}
this._needsDraw = false;
},
/**
* @returns {Boolean} true if any items need updating.
*/
needsDraw: function() {
for ( var i = 0; i < this._items.length; i++ ) {
if ( this._items[i].needsDraw() ) {
return true;
}
}
return this._needsDraw;
},
/**
* @returns {OpenSeadragon.Rect} The smallest rectangle that encloses all items, in viewport coordinates.
*/
getHomeBounds: function() {
return this._homeBounds.clone();
},
/**
* To facilitate zoom constraints, we keep track of the pixel density of the
* densest item in the World (i.e. the item whose content size to viewport size
* ratio is the highest) and save it as this "content factor".
* @returns {Number} the number of content units per viewport unit.
*/
getContentFactor: function() {
return this._contentFactor;
},
/**
* Arranges all of the TiledImages with the specified settings.
* @param {Object} options - Specifies how to arrange.
* @param {Boolean} [options.immediately=false] - Whether to animate to the new arrangement.
* @param {String} [options.layout] - See collectionLayout in {@link OpenSeadragon.Options}.
* @param {Number} [options.rows] - See collectionRows in {@link OpenSeadragon.Options}.
* @param {Number} [options.tileSize] - See collectionTileSize in {@link OpenSeadragon.Options}.
* @param {Number} [options.tileMargin] - See collectionTileMargin in {@link OpenSeadragon.Options}.
* @fires OpenSeadragon.World.event:metrics-change
*/
arrange: function(options) {
options = options || {};
var immediately = options.immediately || false;
var layout = options.layout || $.DEFAULT_SETTINGS.collectionLayout;
var rows = options.rows || $.DEFAULT_SETTINGS.collectionRows;
var tileSize = options.tileSize || $.DEFAULT_SETTINGS.collectionTileSize;
var tileMargin = options.tileMargin || $.DEFAULT_SETTINGS.collectionTileMargin;
var increment = tileSize + tileMargin;
var wrap = Math.ceil(this._items.length / rows);
var x = 0;
var y = 0;
var item, box, width, height, position;
for (var i = 0; i < this._items.length; i++) {
if (i && (i % wrap) === 0) {
if (layout === 'horizontal') {
y += increment;
x = 0;
} else {
x += increment;
y = 0;
}
}
item = this._items[i];
box = item.getBounds();
if (box.width > box.height) {
width = tileSize;
} else {
width = tileSize * (box.width / box.height);
}
height = width * (box.height / box.width);
position = new $.Point(x + ((tileSize - width) / 2),
y + ((tileSize - height) / 2));
item.setPosition(position, immediately);
item.setWidth(width, immediately);
if (layout === 'horizontal') {
x += increment;
} else {
y += increment;
}
}
},
// private
_figureSizes: function() {
var oldHomeBounds = this._homeBounds ? this._homeBounds.clone() : null;
var oldContentSize = this._contentSize ? this._contentSize.clone() : null;
var oldContentFactor = this._contentFactor || 0;
if ( !this._items.length ) {
this._homeBounds = new $.Rect(0, 0, 1, 1);
this._contentSize = new $.Point(1, 1);
this._contentFactor = 1;
} else {
var bounds = this._items[0].getBounds();
this._contentFactor = this._items[0].getContentSize().x / bounds.width;
var left = bounds.x;
var top = bounds.y;
var right = bounds.x + bounds.width;
var bottom = bounds.y + bounds.height;
var box;
for ( var i = 1; i < this._items.length; i++ ) {
box = this._items[i].getBounds();
this._contentFactor = Math.max(this._contentFactor, this._items[i].getContentSize().x / box.width);
left = Math.min( left, box.x );
top = Math.min( top, box.y );
right = Math.max( right, box.x + box.width );
bottom = Math.max( bottom, box.y + box.height );
}
this._homeBounds = new $.Rect( left, top, right - left, bottom - top );
this._contentSize = new $.Point(this._homeBounds.width * this._contentFactor,
this._homeBounds.height * this._contentFactor);
}
if (this._contentFactor !== oldContentFactor || !this._homeBounds.equals(oldHomeBounds) ||
!this._contentSize.equals(oldContentSize)) {
/**
* Raised when the home bounds or content factor change.
* @event metrics-change
* @memberOf OpenSeadragon.World
* @type {object}
* @property {OpenSeadragon.World} eventSource - A reference to the World which raised the event.
* @property {?Object} userData - Arbitrary subscriber-defined object.
*/
this.raiseEvent('metrics-change', {});
}
},
// private
_raiseRemoveItem: function(item) {
/**
* Raised when an item is removed.
* @event remove-item
* @memberOf OpenSeadragon.World
* @type {object}
* @property {OpenSeadragon.World} eventSource - A reference to the World which raised the event.
* @property {OpenSeadragon.TiledImage} item - The item's underlying item.
* @property {?Object} userData - Arbitrary subscriber-defined object.
*/
this.raiseEvent( 'remove-item', { item: item } );
}
});
}( OpenSeadragon ));

79
test/coverage.html Normal file
View File

@ -0,0 +1,79 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>OpenSeadragon QUnit</title>
<link rel="stylesheet" href="/node_modules/qunitjs/qunit/qunit.css">
<link rel="stylesheet" href="/test/lib/jquery-ui-1.10.2/css/smoothness/jquery-ui-1.10.2.min.css">
<link rel="stylesheet" href="/test/helpers/test.css">
</head>
<body>
<div id="qunit"></div>
<div id="qunit-fixture"></div>
<script src="/node_modules/qunitjs/qunit/qunit.js"></script>
<script src="/test/lib/jquery-1.9.1.min.js"></script>
<script src="/test/lib/jquery-ui-1.10.2/js/jquery-ui-1.10.2.min.js"></script>
<script src="/test/lib/jquery.simulate.js"></script>
<!-- OpenSeadragon sources -->
<script src="/src/openseadragon.js"></script>
<script src="/src/fullscreen.js"></script>
<script src="/src/eventsource.js"></script>
<script src="/src/mousetracker.js"></script>
<script src="/src/control.js"></script>
<script src="/src/controldock.js"></script>
<script src="/src/viewer.js"></script>
<script src="/src/navigator.js"></script>
<script src="/src/strings.js"></script>
<script src="/src/point.js"></script>
<script src="/src/tilesource.js"></script>
<script src="/src/dzitilesource.js"></script>
<script src="/src/iiiftilesource.js"></script>
<script src="/src/osmtilesource.js"></script>
<script src="/src/tmstilesource.js"></script>
<script src="/src/legacytilesource.js"></script>
<script src="/src/tilesourcecollection.js"></script>
<script src="/src/button.js"></script>
<script src="/src/buttongroup.js"></script>
<script src="/src/rectangle.js"></script>
<script src="/src/referencestrip.js"></script>
<script src="/src/displayrectangle.js"></script>
<script src="/src/spring.js"></script>
<script src="/src/imageLoader.js"></script>
<script src="/src/tile.js"></script>
<script src="/src/overlay.js"></script>
<script src="/src/drawer.js"></script>
<script src="/src/viewport.js"></script>
<script src="/src/tiledimage.js"></script>
<script src="/src/tilecache.js"></script>
<script src="/src/world.js"></script>
<!-- Helpers -->
<script src="/test/helpers/legacy.mouse.shim.js"></script>
<script src="/test/helpers/test.js"></script>
<!-- Modules -->
<!-- Polyfill must be inserted first because it is testing functions
reassignments which could be done by other test. -->
<script src="/test/modules/polyfills.js"></script>
<script src="/test/modules/basic.js"></script>
<script src="/test/modules/strings.js"></script>
<script src="/test/modules/formats.js"></script>
<script src="/test/modules/utils.js"></script>
<script src="/test/modules/events.js"></script>
<script src="/test/modules/units.js"></script>
<script src="/test/modules/multi-image.js"></script>
<script src="/test/modules/overlays.js"></script>
<script src="/test/modules/controls.js"></script>
<script src="/test/modules/viewport.js"></script>
<script src="/test/modules/world.js"></script>
<script src="/test/modules/drawer.js"></script>
<script src="/test/modules/tiledimage.js"></script>
<script src="/test/modules/tilecache.js"></script>
<script src="/test/modules/referencestrip.js"></script>
<script src="/test/modules/tilesourcecollection.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>
</body>
</html>

View File

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html>
<head>
<title>OpenSeadragon Collections 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>
<script type="text/javascript" src='main.js'></script>
<style type="text/css">
html,
body,
.openseadragon1 {
width: 100%;
height: 100%;
margin: 0;
}
.openseadragon1.small {
width: 50%;
}
.openseadragon-overlay {
background-color: rgba(255, 0, 0, 0.3);
}
</style>
</head>
<body>
<div id="contentDiv" class="openseadragon1"></div>
</body>
</html>

View File

@ -0,0 +1,381 @@
/* globals $, App */
(function() {
window.App = {
init: function() {
var self = this;
var testInitialOpen = true;
var testOverlays = false;
var testMargins = false;
var testNavigator = false;
var margins;
var config = {
// debugMode: true,
zoomPerScroll: 1.02,
showNavigator: testNavigator,
useCanvas: true,
// defaultZoomLevel: 2,
// homeFillsViewer: true,
// sequenceMode: true,
// showReferenceStrip: true,
// referenceStripScroll: 'vertical',
navPrevNextWrap: false,
preserveViewport: false,
// collectionMode: true,
// collectionRows: 1,
// collectionLayout: 'vertical',
// collectionTileSize: 10,
// collectionTileMargin: 10,
// wrapHorizontal: true,
// wrapVertical: true,
id: "contentDiv",
prefixUrl: "../../../build/openseadragon/images/"
};
var highsmith = {
Image: {
xmlns: "http://schemas.microsoft.com/deepzoom/2008",
Url: "http://openseadragon.github.io/example-images/highsmith/highsmith_files/",
Format: "jpg",
Overlap: "2",
TileSize: "256",
Size: {
Height: "9221",
Width: "7026"
}
}
};
if (testInitialOpen) {
config.tileSources = [
{
tileSource: "../../data/testpattern.dzi",
x: 4,
y: 2,
width: 2
},
{
tileSource: "../../data/tall.dzi",
x: 1.5,
y: 0,
width: 1
},
{
tileSource: '../../data/wide.dzi',
opacity: 1,
x: 0,
y: 1.5,
height: 1
}
];
// config.tileSources = {
// tileSource: highsmith,
// width: 1
// };
}
if (testOverlays) {
config.overlays = [
{
id: "overlay1",
x: 2,
y: 0,
width: 0.25,
height: 0.25
},
{
px: 13,
py: 120,
width: 124,
height: 132,
id: "overlay"
},
{
px: 400,
py: 500,
width: 400,
height: 400,
id: "fixed-overlay",
placement: "TOP_LEFT"
}
];
}
if (testMargins) {
margins = {
top: 250,
left: 250,
right: 250,
bottom: 250
};
config.viewportMargins = margins;
}
this.viewer = OpenSeadragon(config);
if (testInitialOpen) {
function openHandler() {
self.viewer.removeHandler('open', openHandler);
}
this.viewer.addHandler( "open", openHandler);
}
if (testMargins) {
this.viewer.addHandler('animation', function() {
var box = new OpenSeadragon.Rect(margins.left, margins.top,
$('#contentDiv').width() - (margins.left + margins.right),
$('#contentDiv').height() - (margins.top + margins.bottom));
self.viewer.drawer.debugRect(box);
});
}
if (!testInitialOpen) {
this.basicTest();
}
},
// ----------
shrink: function(index) {
index = index || 0;
var image = this.viewer.world.getItemAt(index);
image.setWidth(image.getBounds().width * 0.3);
},
// ----------
move: function(index) {
index = index || 0;
var image = this.viewer.world.getItemAt(index);
var point = image.getBounds().getTopLeft();
point.x += image.getBounds().width * 0.3;
image.setPosition(point);
},
// ----------
add: function() {
var self = this;
this.viewer.addTiledImage({
tileSource: "../../data/testpattern.dzi",
width: 1,
success: function() {
self.viewer.viewport.goHome();
}
});
},
// ----------
toggle: function() {
var $el = $(this.viewer.element);
$el.toggleClass('small');
},
// ----------
basicTest: function() {
var self = this;
this.viewer.addHandler('open', function() {
});
this.viewer.open({
tileSource: "../../data/testpattern.dzi",
width: 1
});
},
// ----------
crossTest: function() {
var self = this;
this.viewer.addHandler( "open", function() {
var options = {
tileSource: '../../data/wide.dzi',
opacity: 1,
x: 0,
y: 1.5,
height: 1
};
var addItemHandler = function( event ) {
if ( event.options === options ) {
self.viewer.world.removeHandler( "add-item", addItemHandler );
self.viewer.viewport.goHome();
}
};
self.viewer.world.addHandler( "add-item", addItemHandler );
self.viewer.addTiledImage( options );
});
this.viewer.open({
tileSource: "../../data/tall.dzi",
x: 1.5,
y: 0,
width: 1
});
},
// ----------
crossTest2: function() {
this.viewer.open([
{
tileSource: "../../data/tall.dzi",
x: 1.5,
y: 0,
width: 1
},
{
tileSource: '../../data/wide.dzi',
x: 0,
y: 1.5,
height: 1
}
]);
},
// ----------
crossTest3: function() {
var self = this;
var expected = 2;
var loaded = 0;
this.viewer.world.addHandler('add-item', function() {
loaded++;
if (loaded === expected) {
// self.viewer.viewport.goHome();
}
});
this.viewer.addTiledImage({
tileSource: "../../data/tall.dzi",
x: 1.5,
y: 0,
width: 1
});
this.viewer.addTiledImage({
tileSource: '../../data/wide.dzi',
opacity: 1,
x: 0,
y: 1.5,
height: 1
});
},
// ----------
collectionTest: function() {
var tileSources = [];
var random;
for (var i = 0; i < 10; i++) {
random = Math.random();
if (random < 0.33) {
tileSources.push('../../data/testpattern.dzi');
} else if (random < 0.66) {
tileSources.push('../../data/tall.dzi');
} else {
tileSources.push('../../data/wide.dzi');
}
}
this.viewer.open(tileSources);
},
// ----------
gridTest: function() {
var self = this;
var startX = -3;
var expected = 0;
var loaded = 0;
this.viewer.addHandler( "open", function() {
self.viewer.world.addHandler('add-item', function() {
loaded++;
if (loaded === expected) {
self.viewer.viewport.goHome(true);
}
});
var x, y;
for (y = 0; y < 6; y++) {
for (x = 0; x < 6; x++) {
if (!x && !y) {
continue;
}
var options = {
tileSource: '../../data/testpattern.dzi',
x: startX + x,
y: y,
width: 1
};
expected++;
self.viewer.addTiledImage( options );
}
}
});
this.viewer.open({
tileSource: "../../data/testpattern.dzi",
x: startX,
y: 0,
width: 1
});
},
// ----------
bigTest: function() {
this.viewer.open({
tileSource: "../../data/testpattern.dzi",
x: -2,
y: -2,
width: 6
});
},
// ----------
cjTest: function() {
var imageKey = "e-pluribus-unum";
var imageXML = '<?xml version="1.0" encoding="UTF-8"?><Image TileSize="254" Overlap="1" Format="png" xmlns="http://schemas.microsoft.com/deepzoom/2008"><Size Width="88560" Height="88560"/></Image>';
var $xml = $($.parseXML(imageXML));
var $image = $xml.find('Image');
var $size = $xml.find('Size');
var dzi = {
Image: {
xmlns: $image.attr('xmlns'),
Url: "http://chrisjordan.com/dzi/" + imageKey + '_files/',
Format: $image.attr('Format'),
Overlap: $image.attr('Overlap'),
TileSize: $image.attr('TileSize'),
Size: {
Height: $size.attr('Height'),
Width: $size.attr('Width')
}
}
};
this.viewer.open({
tileSource: dzi,
width: 100
});
},
// ----------
stanfordTest: function() {
var info = {"@context":"http://library.stanford.edu/iiif/image-api/1.1/context.json","@id":"http://ids.lib.harvard.edu/ids/iiif/48530377","width":6251,"height":109517,"scale_factors":[1,2,4,8,16,32],"tile_width":256,"tile_height":256,"formats":["jpg"],"qualities":["native"],"profile":"http://library.stanford.edu/iiif/image-api/1.1/compliance.html#level1"};
this.viewer.open(info);
}
};
$(document).ready(function() {
App.init();
});
})();

View File

@ -0,0 +1,135 @@
<!DOCTYPE html>
<html>
<head>
<title>OpenSeadragon Collections 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">
html,
body,
.openseadragon1 {
width: 100%;
height: 100%;
margin: 0;
}
.controls {
position: absolute;
right: 10px;
top: 10px;
}
</style>
<script>
// ----------
App = {
itemCount: 5,
positionRange: 100,
sizeRange: 30,
delay: 100,
// ----------
init: function() {
var self = this;
this.index = 0;
this.paused = false;
var tileSource = {
Image: {
xmlns: "http://schemas.microsoft.com/deepzoom/2008",
Url: "http://openseadragon.github.io/example-images/highsmith/highsmith_files/",
Format: "jpg",
Overlap: "2",
TileSize: "256",
Size: {
Height: "9221",
Width: "7026"
}
}
};
var tileSources = [];
for (var i = 0; i < this.itemCount; i++) {
tileSources.push(tileSource);
}
this.viewer = OpenSeadragon({
id: "contentDiv",
prefixUrl: "../../build/openseadragon/images/",
tileSources: tileSources
});
this.viewer.addHandler('open', function() {
self.updateCount();
self.viewer.viewport.fitBounds(new OpenSeadragon.Rect(0, 0,
self.positionRange + self.sizeRange,
self.positionRange + self.sizeRange));
self.animate();
});
var $pause = $('.toggle-pause').click(function() {
self.paused = !self.paused;
$pause.text(self.paused ? 'Play' : 'Pause');
});
$('.add').click(function() {
for (var i = 0; i < self.itemCount; i++) {
self.viewer.addTiledImage({
tileSource: tileSource,
x: Math.random() * self.positionRange,
y: Math.random() * self.positionRange,
width: Math.random() * self.sizeRange,
success: function() {
self.updateCount();
}
});
}
});
},
// ----------
animate: function() {
var self = this;
if (!this.paused) {
var item = this.viewer.world.getItemAt(this.index);
item.setPosition(new OpenSeadragon.Point(Math.random() * this.positionRange,
Math.random() * this.positionRange));
item.setWidth(Math.random() * this.sizeRange);
this.index = (this.index + 1) % this.viewer.world.getItemCount();
}
setTimeout(function() {
self.animate();
}, this.delay);
},
// ----------
updateCount: function() {
$('.count').text(this.viewer.world.getItemCount());
}
};
// ----------
$(document).ready(function() {
App.init();
});
</script>
</head>
<body>
<div id="contentDiv" class="openseadragon1"></div>
<div class="controls">
<button class="toggle-pause">Pause</button>
<button class="add">Add More</button>
Image Count: <span class="count"></span>
</div>
</body>
</html>

20
test/demo/m2/README.md Normal file
View File

@ -0,0 +1,20 @@
# M2 Demo
This is an advanced demo/testbed, for proposed improvements to the new version of the Mirador project:
https://github.com/IIIF/m2/
You can see a previous version of Mirador here:
http://showcase.iiif.io/viewer/mirador/
## To Do
* Choosing between multiple versions of a page
* Detail images overlaid on the page
* Cropped images
### Maybe
* Show/hide pages?
* Lazyloading tilesources?

78
test/demo/m2/index.html Normal file
View File

@ -0,0 +1,78 @@
<!DOCTYPE html>
<html>
<head>
<title>Mirador POC</title>
<script type="text/javascript" src='../../../build/openseadragon/openseadragon.js'></script>
<script type="text/javascript" src='../../lib/jquery-1.9.1.min.js'></script>
<script src="//d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script src="js/main.js"></script>
<!-- <script src="js/harvard-tilesources.js"></script> -->
<script src="js/openseadragon-svg-overlay.js"></script>
<style type="text/css">
html,
body {
width: 100%;
height: 100%;
margin: 0;
}
body {
background: #eee;
overflow: scroll;
}
.header {
height: 30px;
}
.nav {
display: inline-block;
margin-left: 50px;
}
.active {
background: #ddf;
}
.hidden {
visibility: hidden;
}
.openseadragon1 {
background: white;
}
.viewer-position {
position: absolute;
left: 0;
top: 30px;
right: 0;
bottom: 0;
}
.scroll-cover {
display: none;
overflow: scroll;
z-index: 100;
}
</style>
</head>
<body>
<div class="header">
<button class="thumbs">Thumbnails</button>
<button class="scroll">Scroll</button>
<button class="book">Book</button>
<button class="page">Page</button>
<div class="nav">
<button class="previous">Previous</button>
<button class="next">Next</button>
</div>
</div>
<div id="contentDiv" class="openseadragon1 viewer-position"></div>
<div class="scroll-cover viewer-position">
<div class="scroll-inner"></div>
</div>
</body>
</html>

1
test/demo/m2/js/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
harvard-tilesources.js

670
test/demo/m2/js/main.js Normal file
View File

@ -0,0 +1,670 @@
/* globals $, App, d3 */
(function() {
// ----------
window.App = {
// ----------
init: function() {
var self = this;
this.maxImages = 500;
this.mode = 'none';
this.pageBuffer = 0.05;
this.bigBuffer = 0.2;
this.page = 0;
this.modeNames = [
'thumbs',
'scroll',
'book',
'page'
];
this.viewer = OpenSeadragon({
id: "contentDiv",
prefixUrl: "../../../build/openseadragon/images/",
autoResize: false,
showHomeControl: false,
tileSources: this.getTileSources()
});
this.viewer.addHandler('open', function() {
self.$el = $(self.viewer.element);
self.setMode({
mode: 'thumbs',
immediately: true
});
});
this.viewer.addHandler('canvas-drag', function() {
if (self.mode === 'scroll') {
var result = self.hitTest(self.viewer.viewport.getCenter());
if (result) {
self.page = result.index;
}
}
});
this.viewer.addHandler('viewport-change', function(event) {
self.applyConstraints();
});
$.each(this.modeNames, function(i, v) {
$('.' + v).click(function() {
self.setMode({
mode: v
});
});
});
$('.next').click(function() {
self.next();
});
$('.previous').click(function() {
self.previous();
});
$(window).keyup(function(event) {
if (self.mode === 'thumbs') {
return;
}
if (event.which === 39) { // Right arrow
self.next();
} else if (event.which === 37) { // Left arrow
self.previous();
}
});
this.$scrollInner = $('.scroll-inner');
this.$scrollCover = $('.scroll-cover')
.scroll(function(event) {
var info = self.getScrollInfo();
if (!info || self.ignoreScroll) {
return;
}
var pos = new OpenSeadragon.Point(info.thumbBounds.getCenter().x,
info.thumbBounds.y + (info.viewportHeight / 2) +
(info.viewportMax * info.scrollFactor));
self.viewer.viewport.panTo(pos, true);
})
.mousemove(function(event) {
var pixel = new OpenSeadragon.Point(event.clientX, event.clientY);
var result = self.hitTest(self.viewer.viewport.pointFromPixel(pixel));
self.updateHover(result ? result.index : -1);
})
.click(function(event) {
var pixel = new OpenSeadragon.Point(event.clientX, event.clientY);
var result = self.hitTest(self.viewer.viewport.pointFromPixel(pixel));
if (result) {
self.setMode({
mode: 'page',
page: result.index
});
}
});
var svgNode = this.viewer.svgOverlay();
this.highlight = d3.select(svgNode).append("rect")
.style('fill', 'none')
.style('stroke', '#08f')
.style('opacity', 0)
.style('stroke-width', 0.05)
.attr("pointer-events", "none");
this.hover = d3.select(svgNode).append("rect")
.style('fill', 'none')
.style('stroke', '#08f')
.style('opacity', 0)
.style('stroke-width', 0.05)
.attr("pointer-events", "none");
$(window).resize(function() {
var newSize = new OpenSeadragon.Point(self.$el.width(), self.$el.height());
self.viewer.viewport.resize(newSize, false);
self.setMode({
mode: self.mode,
immediately: true
});
self.viewer.forceRedraw();
self.viewer.svgOverlay('resize');
});
this.update();
},
// ----------
next: function() {
var page = this.page + (this.mode === 'book' ? 2 : 1);
if (this.mode === 'book' && page % 2 === 0 && page !== 0) {
page --;
}
this.goToPage({
page: page
});
},
// ----------
previous: function() {
var page = this.page - (this.mode === 'book' ? 2 : 1);
if (this.mode === 'book' && page % 2 === 0 && page !== 0) {
page --;
}
this.goToPage({
page: page
});
},
// ----------
hitTest: function(pos) {
var count = this.viewer.world.getItemCount();
var item, box;
for (var i = 0; i < count; i++) {
item = this.viewer.world.getItemAt(i);
box = item.getBounds();
if (pos.x > box.x && pos.y > box.y && pos.x < box.x + box.width &&
pos.y < box.y + box.height) {
return {
item: item,
index: i
};
}
}
return null;
},
// ----------
getScrollInfo: function() {
if (!this.thumbBounds) {
return null;
}
var output = {};
var viewerWidth = this.$el.width();
var viewerHeight = this.$el.height();
var scrollTop = this.$scrollCover.scrollTop();
output.scrollMax = this.$scrollInner.height() - this.$scrollCover.height();
output.scrollFactor = (output.scrollMax > 0 ? scrollTop / output.scrollMax : 0);
output.thumbBounds = this.thumbBounds;
output.viewportHeight = output.thumbBounds.width * (viewerHeight / viewerWidth);
output.viewportMax = Math.max(0, output.thumbBounds.height - output.viewportHeight);
return output;
},
// ----------
update: function() {
var self = this;
$('.nav').toggle(this.mode === 'scroll' || this.mode === 'book' || this.mode === 'page');
$('.previous').toggleClass('hidden', this.page <= 0);
$('.next').toggleClass('hidden', this.page >= this.viewer.world.getItemCount() - 1);
$.each(this.modeNames, function(i, v) {
$('.' + v).toggleClass('active', v === self.mode);
});
},
// ----------
applyConstraints: function() {
if (this.mode === 'thumbs') {
return;
}
if (this.panBounds) {
var center = this.viewer.viewport.getCenter(true);
var viewBounds = this.viewer.viewport.getBounds(true);
var bounds = this.panBounds.clone();
var left = bounds.x + (viewBounds.width / 2);
var top = bounds.y + (viewBounds.height / 2);
var right = (bounds.x + bounds.width) - (viewBounds.width / 2);
var bottom = (bounds.y + bounds.height) - (viewBounds.height / 2);
var x;
if (left <= right) {
x = Math.max(left, Math.min(right, center.x));
} else {
x = bounds.x + (bounds.width / 2);
}
var y;
if (top <= bottom) {
y = Math.max(top, Math.min(bottom, center.y));
} else {
y = bounds.y + (bounds.height / 2);
}
if (x !== center.x || y !== center.y) {
this.viewer.viewport.centerSpringX.current.value = x;
this.viewer.viewport.centerSpringY.current.value = y;
}
}
},
// ----------
setMode: function(config) {
var self = this;
this.mode = config.mode;
if (config.page !== undefined) {
this.page = config.page; // Need to do this before layout
}
this.ignoreScroll = true;
this.thumbBounds = null;
var layout = this.createLayout();
if (this.mode === 'thumbs') {
this.viewer.gestureSettingsMouse.scrollToZoom = false;
this.viewer.zoomPerClick = 1;
this.viewer.panHorizontal = false;
this.viewer.panVertical = false;
var viewerWidth = this.$el.width();
var width = layout.bounds.width + (this.bigBuffer * 2);
var height = layout.bounds.height + (this.bigBuffer * 2);
var newHeight = viewerWidth * (height / width);
this.$scrollCover.show();
this.$scrollInner
.css({
height: newHeight
});
} else {
this.viewer.gestureSettingsMouse.scrollToZoom = true;
this.viewer.zoomPerClick = 2;
this.viewer.panHorizontal = true;
this.viewer.panVertical = true;
this.$scrollCover.hide();
}
this.setLayout({
layout: layout,
immediately: config.immediately
});
if (this.mode === 'thumbs') {
// Set up thumbBounds
this.thumbBounds = this.viewer.world.getHomeBounds();
this.thumbBounds.x -= this.bigBuffer;
this.thumbBounds.y -= this.bigBuffer;
this.thumbBounds.width += (this.bigBuffer * 2);
this.thumbBounds.height += (this.bigBuffer * 2);
// Scroll to the appropriate location
var info = this.getScrollInfo();
var viewportBounds = this.thumbBounds.clone();
viewportBounds.y += info.viewportMax * info.scrollFactor;
viewportBounds.height = info.viewportHeight;
var itemBounds = this.viewer.world.getItemAt(this.page).getBounds();
var top = itemBounds.y - this.bigBuffer;
var bottom = top + itemBounds.height + (this.bigBuffer * 2);
var normalY;
if (top < viewportBounds.y) {
normalY = top - this.thumbBounds.y;
} else if (bottom > viewportBounds.y + viewportBounds.height) {
normalY = (bottom - info.viewportHeight) - this.thumbBounds.y;
}
if (normalY !== undefined) {
var viewportFactor = normalY / info.viewportMax;
this.$scrollCover.scrollTop(info.scrollMax * viewportFactor);
}
}
this.goHome({
immediately: config.immediately
});
this.viewer.viewport.minZoomLevel = this.viewer.viewport.getZoom();
this.update();
this.updateHighlight();
this.updateHover(-1);
clearTimeout(this.scrollTimeout);
this.scrollTimeout = setTimeout(function() {
self.ignoreScroll = false;
}, this.viewer.animationTime * 1000);
},
// ----------
updateHighlight: function() {
if (this.mode !== 'thumbs') {
this.highlight.style('opacity', 0);
return;
}
var item = this.viewer.world.getItemAt(this.page);
var box = item.getBounds();
this.highlight
.style('opacity', 1)
.attr("x", box.x)
.attr("width", box.width)
.attr("y", box.y)
.attr("height", box.height);
},
// ----------
updateHover: function(page) {
if (page === -1 || this.mode !== 'thumbs') {
this.hover.style('opacity', 0);
this.$scrollCover.css({
'cursor': 'default'
});
return;
}
this.$scrollCover.css({
'cursor': 'pointer'
});
var item = this.viewer.world.getItemAt(page);
var box = item.getBounds();
this.hover
.style('opacity', 0.3)
.attr("x", box.x)
.attr("width", box.width)
.attr("y", box.y)
.attr("height", box.height);
},
// ----------
goToPage: function(config) {
var self = this;
var itemCount = this.viewer.world.getItemCount();
this.page = Math.max(0, Math.min(itemCount - 1, config.page));
var viewerWidth = this.$el.width();
var viewerHeight = this.$el.height();
var bounds = this.viewer.world.getItemAt(this.page).getBounds();
var x = bounds.x;
var y = bounds.y;
var width = bounds.width;
var height = bounds.height;
var box;
if (this.mode === 'book') {
var item;
if (this.page % 2) { // First in a pair
item = this.viewer.world.getItemAt(this.page + 1);
if (item) {
width += item.getBounds().width;
}
} else {
item = this.viewer.world.getItemAt(this.page - 1);
if (item) {
box = item.getBounds();
x -= width;
width += box.width;
}
}
}
x -= this.pageBuffer;
y -= this.pageBuffer;
width += (this.pageBuffer * 2);
height += (this.pageBuffer * 2);
if (this.mode === 'scroll') {
if (this.page === 0) {
x = bounds.x - this.pageBuffer;
width = height * (viewerWidth / viewerHeight);
} else if (this.page === this.viewer.world.getItemCount() - 1) {
width = height * (viewerWidth / viewerHeight);
x = (bounds.x + bounds.width + this.pageBuffer) - width;
}
}
box = new OpenSeadragon.Rect(x, y, width, height);
this.viewer.viewport.fitBounds(box, config.immediately);
this.panBounds = null;
var setPanBounds = function() {
if (self.mode === 'page' || self.mode === 'book') {
self.panBounds = box;
} else if (self.mode === 'scroll') {
self.panBounds = self.viewer.world.getItemAt(0).getBounds()
.union(self.viewer.world.getItemAt(itemCount - 1).getBounds());
self.panBounds.x -= self.pageBuffer;
self.panBounds.y -= self.pageBuffer;
self.panBounds.width += (self.pageBuffer * 2);
self.panBounds.height += (self.pageBuffer * 2);
}
};
clearTimeout(this.panBoundsTimeout);
if (config.immediately) {
setPanBounds();
} else {
this.panBoundsTimeout = setTimeout(setPanBounds, this.viewer.animationTime * 1000);
}
this.viewer.viewport.minZoomLevel = this.viewer.viewport.getZoom();
this.update();
},
// ----------
createLayout: function() {
var viewerWidth = this.$el.width();
var viewerHeight = this.$el.height();
var layoutConfig = {};
if (this.mode === 'thumbs') {
layoutConfig.columns = Math.floor(viewerWidth / 150);
layoutConfig.buffer = this.bigBuffer;
layoutConfig.sameWidth = true;
} else if (this.mode === 'scroll') {
layoutConfig.buffer = this.pageBuffer;
} else if (this.mode === 'book' || this.mode === 'page') {
layoutConfig.book = (this.mode === 'book');
var height = 1 + (this.pageBuffer * 2);
// Note that using window here is approximate, but that's close enough.
// We can't use viewer, because it may be stretched for the thumbs view.
layoutConfig.buffer = (height * ($(window).width() / $(window).height())) / 2;
}
var layout = {
bounds: null,
specs: []
};
var count = this.viewer.world.getItemCount();
var x = 0;
var y = 0;
var offset = new OpenSeadragon.Point();
var rowHeight = 0;
var item, box;
for (var i = 0; i < count; i++) {
item = this.viewer.world.getItemAt(i);
box = item.getBounds();
if (i === this.page) {
offset = box.getTopLeft().minus(new OpenSeadragon.Point(x, y));
}
box.x = x;
box.y = y;
if (layoutConfig.sameWidth) {
box.height = box.height / box.width;
box.width = 1;
} else {
box.width = box.width / box.height;
box.height = 1;
}
rowHeight = Math.max(rowHeight, box.height);
layout.specs.push({
item: item,
bounds: box
});
if (layoutConfig.columns && i % layoutConfig.columns === layoutConfig.columns - 1) {
x = 0;
y += rowHeight + layoutConfig.buffer;
rowHeight = 0;
} else {
if (!layoutConfig.book || i % 2 === 0) {
x += layoutConfig.buffer;
}
x += box.width;
}
}
var pos, spec;
for (i = 0; i < count; i++) {
spec = layout.specs[i];
pos = spec.bounds.getTopLeft().plus(offset);
spec.bounds.x = pos.x;
spec.bounds.y = pos.y;
if (layout.bounds) {
layout.bounds = layout.bounds.union(spec.bounds);
} else {
layout.bounds = spec.bounds.clone();
}
}
return layout;
},
// ----------
setLayout: function(config) {
var spec;
for (var i = 0; i < config.layout.specs.length; i++) {
spec = config.layout.specs[i];
spec.item.setPosition(spec.bounds.getTopLeft(), config.immediately);
spec.item.setWidth(spec.bounds.width, config.immediately);
}
},
// ----------
goHome: function(config) {
var viewerWidth = this.$el.width();
var viewerHeight = this.$el.height();
var layoutConfig = {};
if (this.mode === 'thumbs') {
var info = this.getScrollInfo();
var box = this.thumbBounds.clone();
box.height = box.width * (viewerHeight / viewerWidth);
box.y += info.viewportMax * info.scrollFactor;
this.viewer.viewport.fitBounds(box, config.immediately);
} else {
this.goToPage({
page: this.page,
immediately: config.immediately
});
}
},
// ----------
getTileSources: function() {
if (this.tileSources) {
return $.map(this.tileSources.slice(0, this.maxImages), function(v, i) {
return new OpenSeadragon.IIIFTileSource(v);
});
}
var inputs = [
{
Image: {
xmlns: "http://schemas.microsoft.com/deepzoom/2008",
Url: "http://openseadragon.github.io/example-images/highsmith/highsmith_files/",
Format: "jpg",
Overlap: "2",
TileSize: "256",
Size: {
Width: "7026",
Height: "9221"
}
}
}, {
Image: {
xmlns: "http://schemas.microsoft.com/deepzoom/2008",
Url: "http://openseadragon.github.io/example-images/duomo/duomo_files/",
Format: "jpg",
Overlap: "2",
TileSize: "256",
Size: {
Width: "13920",
Height: "10200"
}
}
}, {
// Image: {
// xmlns: "http://schemas.microsoft.com/deepzoom/2008",
// Url: "../../data/tall_files/",
// Format: "jpg",
// Overlap: "1",
// TileSize: "254",
// Size: {
// Width: "500",
// Height: "2000"
// }
// }
// }, {
// Image: {
// xmlns: "http://schemas.microsoft.com/deepzoom/2008",
// Url: "../../data/wide_files/",
// Format: "jpg",
// Overlap: "1",
// TileSize: "254",
// Size: {
// Width: "2000",
// Height: "500"
// }
// }
// }, {
Image: {
xmlns: "http://schemas.microsoft.com/deepzoom/2008",
Url: "../../data/testpattern_files/",
Format: "jpg",
Overlap: "1",
TileSize: "254",
Size: {
Width: "1000",
Height: "1000"
}
}
}
];
var outputs = [];
for (var i = 0; i < this.maxImages; i++) {
outputs.push(inputs[Math.floor(Math.random() * inputs.length)]);
}
return outputs;
}
};
// ----------
$(document).ready(function() {
App.init();
});
})();

View File

@ -0,0 +1,73 @@
(function() {
if (!window.OpenSeadragon) {
console.error('[openseadragon-svg-overlay] requires OpenSeadragon');
return;
}
var svgNS = 'http://www.w3.org/2000/svg';
var update = function(viewer) {
var info = viewer._svgOverlayInfo;
if (info.containerWidth !== viewer.container.clientWidth) {
info.containerWidth = viewer.container.clientWidth;
info.svg.setAttribute('width', info.containerWidth);
}
if (info.containerHeight !== viewer.container.clientHeight) {
info.containerHeight = viewer.container.clientHeight;
info.svg.setAttribute('height', info.containerHeight);
}
var p = viewer.viewport.pixelFromPoint(new OpenSeadragon.Point(0, 0), true);
var zoom = viewer.viewport.getZoom(true);
var scale = viewer.container.clientWidth * zoom;
info.node.setAttribute('transform',
'translate(' + p.x + ',' + p.y + ') scale(' + scale + ')');
};
OpenSeadragon.Viewer.prototype.svgOverlay = function(command) {
var self = this;
if (command === undefined) {
if (this._svgOverlayInfo) {
console.error('[openseadragon-svg-overlay] already initialized on this viewer');
return;
}
var info = this._svgOverlayInfo = {
containerWidth: 0,
containerHeight: 0
};
info.svg = document.createElementNS(svgNS, 'svg');
info.svg.setAttribute('pointer-events', 'none');
info.svg.style.position = 'absolute';
info.svg.style.left = 0;
info.svg.style.top = 0;
info.svg.style.width = '100%';
info.svg.style.height = '100%';
this.container.insertBefore(info.svg, this.canvas.nextSibling);
info.node = document.createElementNS(svgNS, 'g');
info.svg.appendChild(info.node);
this.addHandler('animation', function() {
update(self);
});
this.addHandler('open', function() {
update(self);
});
update(this);
return info.node;
} else if (command === 'resize') {
update(this);
} else {
console.error('[openseadragon-svg-overlay] unknown command: ' + command);
}
};
})();

View File

@ -50,6 +50,7 @@
.simulate( OpenSeadragon.MouseTracker.haveMouseEnter ? 'mouseleave' : 'mouseout', event ); .simulate( OpenSeadragon.MouseTracker.haveMouseEnter ? 'mouseleave' : 'mouseout', event );
}, },
// ----------
initializeTestDOM: function () { initializeTestDOM: function () {
$( "#qunit-fixture" ) $( "#qunit-fixture" )
.append( '<div><div id="example"></div><div id="exampleNavigator"></div></div>' ) .append( '<div><div id="example"></div><div id="exampleNavigator"></div></div>' )
@ -57,14 +58,17 @@
.append( '<div id="tallexample"></div>' ); .append( '<div id="tallexample"></div>' );
}, },
// ----------
equalsWithVariance: function ( value1, value2, variance ) { equalsWithVariance: function ( value1, value2, variance ) {
return Math.abs( value1 - value2 ) <= variance; return Math.abs( value1 - value2 ) <= variance;
}, },
// ----------
assessNumericValue: function ( value1, value2, variance, message ) { assessNumericValue: function ( value1, value2, variance, message ) {
ok( Util.equalsWithVariance( value1, value2, variance ), message + " Expected:" + value1 + " Found: " + value2 + " Variance: " + variance ); ok( Util.equalsWithVariance( value1, value2, variance ), message + " Expected:" + value1 + " Found: " + value2 + " Variance: " + variance );
}, },
// ----------
timeWatcher: function ( time ) { timeWatcher: function ( time ) {
time = time || 2000; time = time || 2000;
var finished = false; var finished = false;
@ -85,8 +89,46 @@
} }
} }
}; };
},
// ----------
spyOnce: function(obj, functionName, callback) {
var original = obj[functionName];
obj[functionName] = function() {
obj[functionName] = original;
var result = callback.apply(this, arguments);
if (result === undefined) {
result = original.apply(this, arguments);
} }
return result;
};
},
// ----------
testDeprecation: function(obj0, member0, obj1, member1) {
var called = false;
var errored = false;
if (obj1 && member1) {
this.spyOnce(obj1, member1, function() {
called = true;
return false;
});
} else {
called = true;
}
this.spyOnce(OpenSeadragon.console, 'error', function(message) {
if (/deprecated/.test(message)) {
errored = true;
}
});
obj0[member0]();
equal(called, true, 'called through for ' + member0);
equal(errored, true, 'errored for ' + member0);
}
}; };
/* /*
@ -134,6 +176,12 @@
} }
} }
testConsole.assert = function(condition, message) {
if (condition) {
testConsole.error(message);
}
};
OpenSeadragon.console = testConsole; OpenSeadragon.console = testConsole;
} )(); } )();

View File

@ -1,278 +0,0 @@
/* global module, asyncTest, $, ok, equal, notEqual, start, test, Util, testLog */
( function() {
var viewer;
module( 'Layers', {
setup: function() {
$( '<div id="layersexample"></div>' ).appendTo( "#qunit-fixture" );
testLog.reset();
viewer = OpenSeadragon( {
id: 'layersexample',
prefixUrl: '/build/openseadragon/images/',
springStiffness: 100 // Faster animation = faster tests
});
},
teardown: function() {
if ( viewer && viewer.close ) {
viewer.close();
}
viewer = null;
$( "#layersexample" ).remove();
}
} );
// ----------
asyncTest( 'Layers operations', function() {
expect( 23 );
viewer.addHandler( "open", function( ) {
equal( 1, viewer.getLayersCount( ),
"One layer should be present after opening." );
var options = {
tileSource: {
type: 'legacy-image-pyramid',
levels: [ {
url: "data/A.png",
width: 1000,
height: 1000
} ]
}
};
viewer.addLayer( options );
viewer.addHandler( "add-layer", function addFirstLayerHandler( event ) {
viewer.removeHandler( "add-layer", addFirstLayerHandler );
var layer1 = event.drawer;
equal( viewer.getLayersCount( ), 2,
"2 layers should be present after adding a layer." );
equal( options, event.options,
"The options should be transmitted via the event." );
equal( viewer.getLevelOfLayer( layer1 ), 1,
"The first added layer should have a level of 1" );
equal( viewer.getLayerAtLevel( 1 ), layer1,
"The layer at level 1 should be the first added layer." );
viewer.addLayer( options );
viewer.addHandler( "add-layer", function addSecondLayerHandler( event ) {
viewer.removeHandler( "add-layer", addSecondLayerHandler );
var layer2 = event.drawer;
equal( viewer.getLayersCount( ), 3,
"3 layers should be present after adding a second layer." );
equal( viewer.getLevelOfLayer( layer2 ), 2,
"If not specified, a layer should be added with the highest level." );
equal( viewer.getLayerAtLevel( 2 ), layer2,
"The layer at level 2 should be the second added layer." );
viewer.addHandler( "layer-level-changed",
function layerLevelChangedHandler( event ) {
viewer.removeHandler( "layer-level-changed",
layerLevelChangedHandler );
equal( event.drawer, layer2,
"The layer which changed level should be layer2" );
equal( event.previousLevel, 2, "Previous level should be 2." );
equal( event.newLevel, 1, "New level should be 1." );
});
viewer.setLayerLevel( layer2, 1 );
equal( viewer.getLevelOfLayer( layer2 ), 1,
"Layer2 level should be 1 after setLayerLevel." );
equal( viewer.getLevelOfLayer( layer1 ), 2,
"Layer1 level should be 2 after setLayerLevel." );
equal( viewer.getLayerAtLevel( 1 ), layer2,
"The layer at level 1 should be layer2." );
equal( viewer.getLayerAtLevel( 2 ), layer1,
"The layer at level 2 should be layer1." );
options.level = 2;
options.tileSource.levels[0].url = "data/CCyan.png";
options.opacity = 0.5;
viewer.addLayer( options );
viewer.addHandler( "add-layer", function addThirdLayerHandler( event ) {
viewer.removeHandler( "add-layer", addThirdLayerHandler );
var layer3 = event.drawer;
equal( viewer.getLayersCount( ), 4,
"4 layers should be present after adding a third layer." );
equal( viewer.getLevelOfLayer( layer3 ), 2,
"Layer 3 should be added with level 2." );
equal( viewer.getLevelOfLayer( layer2 ), 1,
"Layer 2 should stay at level 1." );
viewer.addHandler( "remove-layer", function removeLayerHandler( event ) {
viewer.removeHandler( "remove-layer", removeLayerHandler );
equal( layer2, event.drawer, "Removed layer should be layer2." );
equal( viewer.getLevelOfLayer( layer1 ), 2,
"Layer 1 should be at level 2." );
equal( viewer.getLevelOfLayer( layer2 ), -1,
"Layer 2 should be at level -1." );
equal( viewer.getLevelOfLayer( layer3 ), 1,
"Layer 3 should be at level 1." );
});
viewer.removeLayer( layer2 );
options.tileSource.levels[0].width = 500;
viewer.addHandler( "add-layer-failed", function addLayerFailed( event ) {
viewer.removeHandler( "add-layer-failed", addLayerFailed );
equal( viewer.getLayersCount(), 3 );
start();
});
viewer.addLayer( options );
});
});
});
});
viewer.open( '/test/data/testpattern.dzi' );
});
asyncTest( 'Sequences as layers', function() {
var options = {
tileSource: [{
type: 'legacy-image-pyramid',
levels: [{
url: "data/A.png",
width: 1000,
height: 1000
}]
}, {
type: 'legacy-image-pyramid',
levels: [{
url: "data/BBlue.png",
width: 1000,
height: 1000
}]
}]
};
viewer.addHandler( "open", function openHandler() {
viewer.removeHandler( "open", openHandler );
viewer.addHandler( "add-layer-failed",
function addLayerFailedHandler( event ) {
viewer.removeHandler( "add-layer-failed", addLayerFailedHandler );
equal( event.message, "Sequences can not be added as layers." );
equal( event.options, options, "Layer failed event should give the options." );
start();
} );
viewer.addLayer( options );
});
viewer.open( '/test/data/testpattern.dzi' );
});
asyncTest( 'Reassign base layer', function() {
var options = {
tileSource: {
type: 'legacy-image-pyramid',
levels: [{
url: "data/A.png",
width: 1000,
height: 1000
}]
},
level: 0
};
viewer.addHandler( "open", function openHandler( ) {
viewer.removeHandler( "open", openHandler );
var testPatternDrawer = viewer.drawer;
equal( viewer.drawer, testPatternDrawer, "Viewer.drawer should be set to testPatternDrawer." );
viewer.addHandler( "add-layer", function addLayerHandler( event ) {
viewer.removeHandler( "add-layer", addLayerHandler );
var aDrawer = event.drawer;
equal( viewer.drawer, aDrawer, "Viewer.drawer should be set to aDrawer." );
viewer.setLayerLevel( aDrawer, 1 );
equal( viewer.drawer, testPatternDrawer, "Viewer.drawer should be set back to testPatternDrawer." );
viewer.removeLayer( viewer.drawer );
equal( viewer.drawer, aDrawer, "Viewer.drawer must be reassigned when removing base layer." );
viewer.removeLayer( viewer.drawer );
ok( !viewer.isOpen(), "Viewer should be closed when removing last layer." );
start();
});
viewer.addLayer( options );
});
viewer.open( '/test/data/testpattern.dzi' );
});
asyncTest( 'Layers and sequences', function() {
expect( 1 );
// TODO: Remove workaround when issue #321 is fixed.
// https://github.com/openseadragon/openseadragon/issues/321
// viewer.open( [{
// type: 'legacy-image-pyramid',
// levels: [ {
// url: "data/A.png",
// width: 1000,
// height: 1000
// }]
// },
// {
// type: 'legacy-image-pyramid',
// levels: [ {
// url: "data/BBlue.png",
// width: 1000,
// height: 1000
// }]}] );
viewer.close();
viewer = OpenSeadragon({
id: 'layersexample',
prefixUrl: '/build/openseadragon/images/',
springStiffness: 100, // Faster animation = faster tests
tileSources: [{
type: 'legacy-image-pyramid',
levels: [{
url: "data/A.png",
width: 1000,
height: 1000
}]
},
{
type: 'legacy-image-pyramid',
levels: [{
url: "data/BBlue.png",
width: 1000,
height: 1000
}]
}]
});
// End workaround
var options = {
tileSource: {
type: 'legacy-image-pyramid',
levels: [{
url: "data/CCyan.png",
width: 1000,
height: 1000
}]
}
};
viewer.addHandler( "open", function openHandler() {
viewer.addHandler( "add-layer", function addLayerHandler( event ) {
viewer.removeHandler( "add-layer", addLayerHandler );
var layer = event.drawer;
try {
viewer.setLayerLevel( layer, 0 );
} catch (e) {
ok( true );
}
start();
} );
viewer.addLayer( options );
});
});
})();

View File

@ -95,7 +95,8 @@
var panHandler = function() { var panHandler = function() {
viewer.removeHandler('animation-finish', panHandler); viewer.removeHandler('animation-finish', panHandler);
center = viewport.getCenter(); center = viewport.getCenter();
ok(center.x === 0.1 && center.y === 0.1, 'Panned correctly'); Util.assessNumericValue(center.x, 0.1, 0.00001, 'panned horizontally');
Util.assessNumericValue(center.y, 0.1, 0.00001, 'panned vertically');
start(); start();
}; };
@ -133,7 +134,7 @@
}; };
viewer.addHandler('animation-finish', homeHandler); viewer.addHandler('animation-finish', homeHandler);
viewport.goHome(true); viewer.viewport.goHome(true);
} }
viewer.addHandler("open", opener); viewer.addHandler("open", opener);
@ -260,9 +261,8 @@
viewer.removeHandler('close', closeHandler); viewer.removeHandler('close', closeHandler);
ok(!viewer.source, 'no source'); ok(!viewer.source, 'no source');
ok(true, 'Close event was sent'); ok(true, 'Close event was sent');
ok(!viewer._updateRequestId, 'timer is off');
setTimeout(function() { setTimeout(function() {
ok(!viewer._updateRequestId, 'timer is still off'); ok(!viewer._updateRequestId, 'timer is off');
start(); start();
}, 100); }, 100);
}; };

View File

@ -5,7 +5,12 @@
module('Controls', { module('Controls', {
setup: function () { setup: function () {
var example = $('<div id="controlsTests"></div>').appendTo("#qunit-fixture"); var example = $('<div id="controlsTests"></div>')
.css({
width: 1000,
height: 1000
})
.appendTo("#qunit-fixture");
testLog.reset(); testLog.reset();
@ -324,6 +329,7 @@
], ],
springStiffness: 100, // Faster animation = faster tests springStiffness: 100, // Faster animation = faster tests
showSequenceControl: true, showSequenceControl: true,
sequenceMode: true,
navPrevNextWrap: false navPrevNextWrap: false
}); });
viewer.addHandler('open', openHandler); viewer.addHandler('open', openHandler);
@ -375,6 +381,7 @@
], ],
springStiffness: 100, // Faster animation = faster tests springStiffness: 100, // Faster animation = faster tests
showSequenceControl: true, showSequenceControl: true,
sequenceMode: true,
navPrevNextWrap: true navPrevNextWrap: true
}); });
viewer.addHandler('open', openHandler); viewer.addHandler('open', openHandler);

84
test/modules/drawer.js Normal file
View File

@ -0,0 +1,84 @@
/* global module, asyncTest, $, ok, equal, notEqual, start, test, Util, testLog */
(function() {
var viewer;
module('Drawer', {
setup: function () {
var example = $('<div id="example"></div>').appendTo("#qunit-fixture");
testLog.reset();
},
teardown: function () {
if (viewer && viewer.close) {
viewer.close();
}
viewer = null;
}
});
// ----------
var createViewer = function(options) {
options = options || {};
viewer = OpenSeadragon(OpenSeadragon.extend({
id: 'example',
prefixUrl: '/build/openseadragon/images/',
springStiffness: 100 // Faster animation = faster tests
}, options));
};
// ----------
asyncTest('basics', function() {
createViewer();
ok(viewer.drawer, 'Drawer exists');
equal(viewer.drawer.canRotate(), OpenSeadragon.supportsCanvas, 'we can rotate if we have canvas');
equal(viewer.drawer.getOpacity(), 1, 'starts with full opacity');
viewer.drawer.setOpacity(0.4);
equal(viewer.drawer.getOpacity(), 0.4, 'setting opacity works');
start();
});
// ----------
asyncTest('rotation', function() {
createViewer({
tileSources: '/test/data/testpattern.dzi'
});
viewer.addHandler('open', function handler(event) {
viewer.viewport.setRotation(30);
Util.spyOnce(viewer.drawer.context, 'rotate', function() {
ok(true, 'drawing with new rotation');
start();
});
});
});
// ----------
asyncTest('debug', function() {
createViewer({
tileSources: '/test/data/testpattern.dzi',
debugMode: true
});
Util.spyOnce(viewer.drawer, 'drawDebugInfo', function() {
ok(true, 'drawDebugInfo is called');
start();
});
});
// ----------
asyncTest('deprecations', function() {
createViewer();
Util.testDeprecation(viewer.drawer, 'addOverlay', viewer, 'addOverlay');
Util.testDeprecation(viewer.drawer, 'updateOverlay', viewer, 'updateOverlay');
Util.testDeprecation(viewer.drawer, 'removeOverlay', viewer, 'removeOverlay');
Util.testDeprecation(viewer.drawer, 'clearOverlays', viewer, 'clearOverlays');
Util.testDeprecation(viewer.drawer, 'needsUpdate', viewer.world, 'needsDraw');
Util.testDeprecation(viewer.drawer, 'numTilesLoaded', viewer.tileCache, 'numTilesLoaded');
Util.testDeprecation(viewer.drawer, 'reset', viewer.world, 'resetItems');
Util.testDeprecation(viewer.drawer, 'update', viewer.world, 'draw');
start();
});
})();

178
test/modules/multi-image.js Normal file
View File

@ -0,0 +1,178 @@
/* global module, asyncTest, $, ok, equal, notEqual, start, test, Util, testLog, expect */
( function() {
var viewer;
module( 'Multi-Image', {
setup: function() {
$( '<div id="itemsexample"></div>' ).appendTo( "#qunit-fixture" );
testLog.reset();
viewer = OpenSeadragon( {
id: 'itemsexample',
prefixUrl: '/build/openseadragon/images/',
springStiffness: 100 // Faster animation = faster tests
});
},
teardown: function() {
if ( viewer && viewer.close ) {
viewer.close();
}
viewer = null;
$( "#itemsexample" ).remove();
}
} );
// ----------
asyncTest( 'Multi-image operations', function() {
expect( 21 );
viewer.addHandler( "open", function( ) {
equal( 1, viewer.world.getItemCount( ),
"One item should be present after opening." );
var options = {
tileSource: {
type: 'legacy-image-pyramid',
levels: [ {
url: "data/A.png",
width: 1000,
height: 1000
} ]
}
};
viewer.addTiledImage( options );
viewer.world.addHandler( "add-item", function addFirstItemHandler( event ) {
viewer.world.removeHandler( "add-item", addFirstItemHandler );
var item1 = event.item;
equal( viewer.world.getItemCount( ), 2,
"2 items should be present after adding a item." );
equal( viewer.world.getIndexOfItem( item1 ), 1,
"The first added item should have a index of 1" );
equal( viewer.world.getItemAt( 1 ), item1,
"The item at index 1 should be the first added item." );
viewer.addTiledImage( options );
viewer.world.addHandler( "add-item", function addSecondItemHandler( event ) {
viewer.world.removeHandler( "add-item", addSecondItemHandler );
var item2 = event.item;
equal( viewer.world.getItemCount( ), 3,
"3 items should be present after adding a second item." );
equal( viewer.world.getIndexOfItem( item2 ), 2,
"If not specified, a item should be added with the highest index." );
equal( viewer.world.getItemAt( 2 ), item2,
"The item at index 2 should be the second added item." );
viewer.world.addHandler( "item-index-change",
function itemIndexChangedHandler( event ) {
viewer.world.removeHandler( "item-index-change",
itemIndexChangedHandler );
equal( event.item, item2,
"The item which changed index should be item2" );
equal( event.previousIndex, 2, "Previous index should be 2." );
equal( event.newIndex, 1, "New index should be 1." );
});
viewer.world.setItemIndex( item2, 1 );
equal( viewer.world.getIndexOfItem( item2 ), 1,
"Item2 index should be 1 after setItemIndex." );
equal( viewer.world.getIndexOfItem( item1 ), 2,
"Item1 index should be 2 after setItemIndex." );
equal( viewer.world.getItemAt( 1 ), item2,
"The item at index 1 should be item2." );
equal( viewer.world.getItemAt( 2 ), item1,
"The item at index 2 should be item1." );
options.index = 2;
options.tileSource.levels[0].url = "data/CCyan.png";
viewer.addTiledImage( options );
viewer.world.addHandler( "add-item", function addThirdItemHandler( event ) {
viewer.world.removeHandler( "add-item", addThirdItemHandler );
var item3 = event.item;
equal( viewer.world.getItemCount( ), 4,
"4 items should be present after adding a third item." );
equal( viewer.world.getIndexOfItem( item3 ), 2,
"Item 3 should be added with index 2." );
equal( viewer.world.getIndexOfItem( item2 ), 1,
"Item 2 should stay at index 1." );
viewer.world.addHandler( "remove-item", function removeItemHandler( event ) {
viewer.world.removeHandler( "remove-item", removeItemHandler );
equal( item2, event.item, "Removed item should be item2." );
equal( viewer.world.getIndexOfItem( item1 ), 2,
"Item 1 should be at index 2." );
equal( viewer.world.getIndexOfItem( item2 ), -1,
"Item 2 should be at index -1." );
equal( viewer.world.getIndexOfItem( item3 ), 1,
"Item 3 should be at index 1." );
start();
});
viewer.world.removeItem( item2 );
});
});
});
});
viewer.open( '/test/data/testpattern.dzi' );
});
// ----------
asyncTest( 'Sequences as items', function() {
var options = {
tileSource: [{
type: 'legacy-image-pyramid',
levels: [{
url: "data/A.png",
width: 1000,
height: 1000
}]
}, {
type: 'legacy-image-pyramid',
levels: [{
url: "data/BBlue.png",
width: 1000,
height: 1000
}]
}]
};
viewer.addHandler( "open", function openHandler() {
viewer.removeHandler( "open", openHandler );
viewer.addHandler( "add-item-failed",
function addItemFailedHandler( event ) {
viewer.removeHandler( "add-item-failed", addItemFailedHandler );
equal( event.message, "[Viewer.addTiledImage] Sequences can not be added; add them one at a time instead." );
equal( event.options, options, "Item failed event should give the options." );
start();
} );
viewer.addTiledImage( options );
});
viewer.open( '/test/data/testpattern.dzi' );
});
// ----------
asyncTest('items are added in order', function() {
viewer.addHandler('open', function(event) {
equal(viewer.world.getItemAt(0).getContentSize().y, 2000, 'first image is tall');
equal(viewer.world.getItemAt(0).getBounds().width, 4, 'first image has 4 width');
equal(viewer.world.getItemAt(1).getContentSize().x, 2000, 'second image is wide');
equal(viewer.world.getItemAt(1).getBounds().width, 2, 'second image has 2 width');
start();
});
viewer.open([
{
tileSource: '/test/data/tall.dzi',
width: 4
}, {
tileSource: '/test/data/wide.dzi',
width: 2
}
]);
});
})();

View File

@ -1,6 +1,4 @@
/* global QUnit, module, Util, $, console, test, asyncTest, start, ok, equal */ /* global QUnit, module, Util, $, console, test, asyncTest, start, ok, equal, propEqual */
QUnit.config.autostart = false;
(function () { (function () {
var debug = false, var debug = false,
@ -31,10 +29,6 @@ QUnit.config.autostart = false;
} }
}); });
$(document).ready(function () {
start();
});
var resetTestVariables = function () { var resetTestVariables = function () {
if (viewer) { if (viewer) {
viewer.close(); viewer.close();
@ -146,7 +140,7 @@ QUnit.config.autostart = false;
currentDisplayRegionLeft = displayRegion.position().left; currentDisplayRegionLeft = displayRegion.position().left;
currentDisplayWidth = displayRegion.width(); currentDisplayWidth = displayRegion.width();
viewerAndNavigatorDisplayReady = viewer.drawer !== null && viewerAndNavigatorDisplayReady = viewer.drawer !== null &&
!viewer.drawer.needsUpdate() && !viewer.world.needsDraw() &&
currentDisplayWidth > 0 && currentDisplayWidth > 0 &&
Util.equalsWithVariance(lastDisplayRegionLeft, currentDisplayRegionLeft, 0.0001) && Util.equalsWithVariance(lastDisplayRegionLeft, currentDisplayRegionLeft, 0.0001) &&
Util.equalsWithVariance(lastDisplayWidth, currentDisplayWidth, 0.0001) && Util.equalsWithVariance(lastDisplayWidth, currentDisplayWidth, 0.0001) &&
@ -166,7 +160,7 @@ QUnit.config.autostart = false;
else { else {
if (count === 40) { if (count === 40) {
console.log("waitForViewer:" + console.log("waitForViewer:" +
viewer.drawer + ":" + viewer.drawer.needsUpdate() + ":" + viewer.drawer + ":" + viewer.world.needsDraw() + ":" +
viewerAndNavigatorDisplayReady + ":" + viewerAndNavigatorDisplayReady + ":" +
lastDisplayRegionLeft + ":" + currentDisplayRegionLeft + ":" + lastDisplayRegionLeft + ":" + currentDisplayRegionLeft + ":" +
lastDisplayWidth + ":" + currentDisplayWidth + ":" + lastDisplayWidth + ":" + currentDisplayWidth + ":" +
@ -765,11 +759,11 @@ QUnit.config.autostart = false;
var openHandler1 = function(event) { var openHandler1 = function(event) {
viewer.removeHandler('open', openHandler1); viewer.removeHandler('open', openHandler1);
ok(viewer.navigator, 'navigator exists'); ok(viewer.navigator, 'navigator exists');
viewer.navigator.addHandler('open', navOpenHandler1); viewer.navigator.world.addHandler('add-item', navOpenHandler1);
}; };
var navOpenHandler1 = function(event) { var navOpenHandler1 = function(event) {
viewer.navigator.removeHandler('open', navOpenHandler1); viewer.navigator.world.removeHandler('add-item', navOpenHandler1);
equal(viewer.navigator.source, viewer.source, 'viewer and navigator have the same source'); equal(viewer.navigator.source, viewer.source, 'viewer and navigator have the same source');
ok(viewer.navigator._updateRequestId, 'navigator timer is on'); ok(viewer.navigator._updateRequestId, 'navigator timer is on');
viewer.addHandler('close', closeHandler1); viewer.addHandler('close', closeHandler1);
@ -785,11 +779,11 @@ QUnit.config.autostart = false;
var openHandler2 = function(event) { var openHandler2 = function(event) {
viewer.removeHandler('open', openHandler2); viewer.removeHandler('open', openHandler2);
viewer.navigator.addHandler('open', navOpenHandler2); viewer.navigator.world.addHandler('add-item', navOpenHandler2);
}; };
var navOpenHandler2 = function(event) { var navOpenHandler2 = function(event) {
viewer.navigator.removeHandler('open', navOpenHandler2); viewer.navigator.world.removeHandler('add-item', navOpenHandler2);
equal(viewer.navigator.source, viewer.source, 'viewer and navigator have the same source'); equal(viewer.navigator.source, viewer.source, 'viewer and navigator have the same source');
viewer.addHandler('close', closeHandler2); viewer.addHandler('close', closeHandler2);
viewer.close(); viewer.close();
@ -797,13 +791,56 @@ QUnit.config.autostart = false;
var closeHandler2 = function(event) { var closeHandler2 = function(event) {
viewer.removeHandler('close', closeHandler2); viewer.removeHandler('close', closeHandler2);
ok(!viewer.navigator._updateRequestId, 'navigator timer is off');
setTimeout(function() { setTimeout(function() {
ok(!viewer.navigator._updateRequestId, 'navigator timer is still off'); ok(!viewer.navigator._updateRequestId, 'navigator timer is off');
timeWatcher.done(); timeWatcher.done();
}, 100); }, 100);
}; };
viewer.addHandler('open', openHandler1); viewer.addHandler('open', openHandler1);
}); });
asyncTest('Item positions including collection mode', function() {
var navAddCount = 0;
viewer = OpenSeadragon({
id: 'example',
prefixUrl: '/build/openseadragon/images/',
tileSources: ['/test/data/testpattern.dzi', '/test/data/testpattern.dzi'],
springStiffness: 100, // Faster animation = faster tests
showNavigator: true,
collectionMode: true
});
var openHandler = function() {
viewer.removeHandler('open', openHandler);
viewer.navigator.world.addHandler('add-item', navOpenHandler);
};
var navOpenHandler = function(event) {
navAddCount++;
if (navAddCount === 2) {
viewer.navigator.world.removeHandler('add-item', navOpenHandler);
setTimeout(function() {
// Test initial formation
equal(viewer.navigator.world.getItemCount(), 2, 'navigator has both items');
for (var i = 0; i < 2; i++) {
propEqual(viewer.navigator.world.getItemAt(i).getBounds(),
viewer.world.getItemAt(i).getBounds(), 'bounds are the same');
}
// Try moving one
viewer.world.getItemAt(0).setPosition(new OpenSeadragon.Point(-200, -200));
propEqual(viewer.navigator.world.getItemAt(0).getBounds(),
viewer.world.getItemAt(0).getBounds(), 'bounds are the same after move');
start();
}, 1);
}
};
viewer.addHandler('open', openHandler);
});
})(); })();

View File

@ -1,4 +1,4 @@
/* global QUnit, module, Util, $, console, test, asyncTest, start, ok, equal */ /* global QUnit, module, Util, $, console, test, asyncTest, start, ok, equal, testLog */
( function() { ( function() {
var viewer; var viewer;
@ -29,7 +29,7 @@
} }
var ready = viewer.isOpen() && var ready = viewer.isOpen() &&
viewer.drawer !== null && viewer.drawer !== null &&
!viewer.drawer.needsUpdate() && !viewer.world.needsDraw() &&
Util.equalsWithVariance( viewer.viewport.getBounds( true ).x, Util.equalsWithVariance( viewer.viewport.getBounds( true ).x,
viewer.viewport.getBounds().x, 0.000 ) && viewer.viewport.getBounds().x, 0.000 ) &&
Util.equalsWithVariance( viewer.viewport.getBounds( true ).y, Util.equalsWithVariance( viewer.viewport.getBounds( true ).y,
@ -46,7 +46,7 @@
}, 100 ); }, 100 );
} else { } else {
console.log( "waitForViewer:" + viewer.isOpen( ) + ":" + viewer.drawer + console.log( "waitForViewer:" + viewer.isOpen( ) + ":" + viewer.drawer +
":" + viewer.drawer.needsUpdate() ); ":" + viewer.world.needsDraw() );
handler(); handler();
} }
} }
@ -532,4 +532,35 @@
} ); } );
// ----------
asyncTest('overlays appear immediately', function() {
equal($('#immediate-overlay0').length, 0, 'overlay 0 does not exist');
equal($('#immediate-overlay1').length, 0, 'overlay 1 does not exist');
viewer = OpenSeadragon( {
id: 'example-overlays',
prefixUrl: '/build/openseadragon/images/',
tileSources: '/test/data/testpattern.dzi',
springStiffness: 100, // Faster animation = faster tests
overlays: [ {
x: 0,
y: 0,
id: "immediate-overlay0"
} ]
} );
viewer.addHandler('open', function() {
equal($('#immediate-overlay0').length, 1, 'overlay 0 exists');
viewer.addOverlay( {
x: 0,
y: 0,
id: "immediate-overlay1"
} );
equal($('#immediate-overlay1').length, 1, 'overlay 1 exists');
start();
});
});
} )( ); } )( );

View File

@ -0,0 +1,46 @@
/* global module, asyncTest, $, ok, equal, notEqual, start, test, Util, testLog */
(function() {
var viewer;
module('ReferenceStrip', {
setup: function () {
var example = $('<div id="example"></div>').appendTo("#qunit-fixture");
testLog.reset();
},
teardown: function () {
if (viewer && viewer.close) {
viewer.close();
}
viewer = null;
}
});
// ----------
var createViewer = function(options) {
options = options || {};
viewer = OpenSeadragon(OpenSeadragon.extend({
id: 'example',
prefixUrl: '/build/openseadragon/images/',
springStiffness: 100 // Faster animation = faster tests
}, options));
};
// ----------
asyncTest('basics', function() {
createViewer({
sequenceMode: true,
showReferenceStrip: true,
tileSources: [
'/test/data/tall.dzi',
'/test/data/wide.dzi',
]
});
ok(viewer.referenceStrip, 'referenceStrip exists');
start();
});
})();

111
test/modules/tilecache.js Normal file
View File

@ -0,0 +1,111 @@
/* global module, asyncTest, $, ok, equal, notEqual, start, test, Util, testLog */
(function() {
// ----------
module('TileCache', {
setup: function () {
testLog.reset();
},
teardown: function () {
}
});
// ----------
asyncTest('basics', function() {
var fakeTiledImage0 = {};
var fakeTiledImage1 = {};
var fakeTile0 = {
url: 'foo.jpg',
image: {},
unload: function() {}
};
var fakeTile1 = {
url: 'foo.jpg',
image: {},
unload: function() {}
};
var cache = new OpenSeadragon.TileCache();
equal(cache.numTilesLoaded(), 0, 'no tiles to begin with');
cache.cacheTile({
tile: fakeTile0,
tiledImage: fakeTiledImage0
});
equal(cache.numTilesLoaded(), 1, 'tile count after cache');
cache.cacheTile({
tile: fakeTile1,
tiledImage: fakeTiledImage1
});
equal(cache.numTilesLoaded(), 2, 'tile count after second cache');
cache.clearTilesFor(fakeTiledImage0);
equal(cache.numTilesLoaded(), 1, 'tile count after first clear');
cache.clearTilesFor(fakeTiledImage1);
equal(cache.numTilesLoaded(), 0, 'tile count after second clear');
start();
});
// ----------
asyncTest('maxImageCacheCount', function() {
var fakeTiledImage0 = {};
var fakeTile0 = {
url: 'different.jpg',
image: {},
unload: function() {}
};
var fakeTile1 = {
url: 'same.jpg',
image: {},
unload: function() {}
};
var fakeTile2 = {
url: 'same.jpg',
image: {},
unload: function() {}
};
var cache = new OpenSeadragon.TileCache({
maxImageCacheCount: 1
});
equal(cache.numTilesLoaded(), 0, 'no tiles to begin with');
cache.cacheTile({
tile: fakeTile0,
tiledImage: fakeTiledImage0
});
equal(cache.numTilesLoaded(), 1, 'tile count after add');
cache.cacheTile({
tile: fakeTile1,
tiledImage: fakeTiledImage0
});
equal(cache.numTilesLoaded(), 1, 'tile count after add of second image');
cache.cacheTile({
tile: fakeTile2,
tiledImage: fakeTiledImage0
});
equal(cache.numTilesLoaded(), 2, 'tile count after additional same image');
start();
});
})();

194
test/modules/tiledimage.js Normal file
View File

@ -0,0 +1,194 @@
/* global module, asyncTest, $, ok, equal, notEqual, start, test, Util, testLog, propEqual */
(function() {
var viewer;
module('TiledImage', {
setup: function () {
var example = $('<div id="example"></div>').appendTo("#qunit-fixture");
testLog.reset();
viewer = OpenSeadragon({
id: 'example',
prefixUrl: '/build/openseadragon/images/',
springStiffness: 100 // Faster animation = faster tests
});
},
teardown: function () {
if (viewer && viewer.close) {
viewer.close();
}
viewer = null;
}
});
// ----------
var checkBounds = function(image, expected, message) {
var bounds = image.getBounds();
equal(bounds.x, expected.x, message + ' x');
equal(bounds.y, expected.y, message + ' y');
equal(bounds.width, expected.width, message + ' width');
equal(bounds.height, expected.height, message + ' height');
};
// ----------
asyncTest('metrics', function() {
var handlerCount = 0;
viewer.addHandler('open', function(event) {
var image = viewer.world.getItemAt(0);
var contentSize = image.getContentSize();
equal(contentSize.x, 500, 'contentSize.x');
equal(contentSize.y, 2000, 'contentSize.y');
checkBounds(image, new OpenSeadragon.Rect(5, 6, 10, 40), 'initial bounds');
var scale = image.getContentSize().x / image.getBounds().width;
var viewportPoint = new OpenSeadragon.Point(10, 11);
var imagePoint = viewportPoint.minus(image.getBounds().getTopLeft()).times(scale);
propEqual(image.viewportToImageCoordinates(viewportPoint), imagePoint, 'viewportToImageCoordinates');
propEqual(image.imageToViewportCoordinates(imagePoint), viewportPoint, 'imageToViewportCoordinates');
var viewportRect = new OpenSeadragon.Rect(viewportPoint.x, viewportPoint.y, 6, 7);
var imageRect = new OpenSeadragon.Rect(imagePoint.x, imagePoint.y,
viewportRect.width * scale, viewportRect.height * scale);
propEqual(image.viewportToImageRectangle(viewportRect), imageRect, 'viewportToImageRectangle');
propEqual(image.imageToViewportRectangle(imageRect), viewportRect, 'imageToViewportRectangle');
image.addHandler('bounds-change', function boundsChangeHandler(event) {
image.removeHandler('bounds-change', boundsChangeHandler);
handlerCount++;
});
image.setPosition(new OpenSeadragon.Point(7, 8));
checkBounds(image, new OpenSeadragon.Rect(7, 8, 10, 40), 'bounds after position');
image.setWidth(5);
checkBounds(image, new OpenSeadragon.Rect(7, 8, 5, 20), 'bounds after width');
image.setHeight(4);
checkBounds(image, new OpenSeadragon.Rect(7, 8, 1, 4), 'bounds after width');
equal(handlerCount, 1, 'correct number of handlers called');
start();
});
viewer.open({
tileSource: '/test/data/tall.dzi',
x: 5,
y: 6,
width: 10
});
});
// ----------
asyncTest('animation', function() {
viewer.addHandler("open", function () {
var image = viewer.world.getItemAt(0);
propEqual(image.getBounds(), new OpenSeadragon.Rect(0, 0, 1, 1), 'target bounds on open');
propEqual(image.getBounds(true), new OpenSeadragon.Rect(0, 0, 1, 1), 'current bounds on open');
image.setPosition(new OpenSeadragon.Point(1, 2));
propEqual(image.getBounds(), new OpenSeadragon.Rect(1, 2, 1, 1), 'target bounds after position');
propEqual(image.getBounds(true), new OpenSeadragon.Rect(0, 0, 1, 1), 'current bounds after position');
image.setWidth(3);
propEqual(image.getBounds(), new OpenSeadragon.Rect(1, 2, 3, 3), 'target bounds after width');
propEqual(image.getBounds(true), new OpenSeadragon.Rect(0, 0, 1, 1), 'current bounds after width');
viewer.addHandler('animation-finish', function animationHandler() {
viewer.removeHandler('animation-finish', animationHandler);
propEqual(image.getBounds(), new OpenSeadragon.Rect(1, 2, 3, 3), 'target bounds after animation');
propEqual(image.getBounds(true), new OpenSeadragon.Rect(1, 2, 3, 3), 'current bounds after animation');
start();
});
});
viewer.open('/test/data/testpattern.dzi');
});
// ----------
asyncTest('update', function() {
var handlerCount = 0;
viewer.addHandler('open', function(event) {
var image = viewer.world.getItemAt(0);
equal(image.needsDraw(), true, 'needs draw after open');
viewer.addHandler('update-level', function updateLevelHandler(event) {
viewer.removeHandler('update-level', updateLevelHandler);
handlerCount++;
equal(event.eventSource, viewer, 'sender of update-level event was viewer');
equal(event.tiledImage, image, 'tiledImage of update-level event is correct');
ok('havedrawn' in event, 'update-level event includes havedrawn');
ok('level' in event, 'update-level event includes level');
ok('opacity' in event, 'update-level event includes opacity');
ok('visibility' in event, 'update-level event includes visibility');
ok('topleft' in event, 'update-level event includes topleft');
ok('bottomright' in event, 'update-level event includes bottomright');
ok('currenttime' in event, 'update-level event includes currenttime');
ok('best' in event, 'update-level event includes best');
});
viewer.addHandler('update-tile', function updateTileHandler(event) {
viewer.removeHandler('update-tile', updateTileHandler);
handlerCount++;
equal(event.eventSource, viewer, 'sender of update-tile event was viewer');
equal(event.tiledImage, image, 'tiledImage of update-level event is correct');
ok(event.tile, 'update-tile event includes tile');
});
viewer.addHandler('tile-drawing', function tileDrawingHandler(event) {
viewer.removeHandler('tile-drawing', tileDrawingHandler);
handlerCount++;
equal(event.eventSource, viewer, 'sender of tile-drawing event was viewer');
equal(event.tiledImage, image, 'tiledImage of update-level event is correct');
ok(event.tile, 'tile-drawing event includes a tile');
ok(event.context, 'tile-drawing event includes a context');
ok(event.rendered, 'tile-drawing event includes a rendered');
});
viewer.addHandler('tile-drawn', function tileDrawnHandler(event) {
viewer.removeHandler('tile-drawn', tileDrawnHandler);
handlerCount++;
equal(event.eventSource, viewer, 'sender of tile-drawn event was viewer');
equal(event.tiledImage, image, 'tiledImage of update-level event is correct');
ok(event.tile, 'tile-drawn event includes tile');
equal(handlerCount, 4, 'correct number of handlers called');
start();
});
image.draw();
});
viewer.open('/test/data/testpattern.dzi');
});
// ----------
asyncTest('reset', function() {
viewer.addHandler('tile-drawn', function updateHandler() {
viewer.removeHandler('tile-drawn', updateHandler);
ok(viewer.tileCache.numTilesLoaded() > 0, 'we have tiles after tile-drawn');
viewer.world.getItemAt(0).reset();
equal(viewer.tileCache.numTilesLoaded(), 0, 'no tiles after reset');
viewer.addHandler('tile-drawn', function updateHandler2() {
viewer.removeHandler('tile-drawn', updateHandler2);
ok(viewer.tileCache.numTilesLoaded() > 0, 'more tiles load');
viewer.world.getItemAt(0).destroy();
equal(viewer.tileCache.numTilesLoaded(), 0, 'no tiles after destroy');
start();
});
});
equal(viewer.tileCache.numTilesLoaded(), 0, 'no tiles at start');
viewer.open('/test/data/testpattern.dzi');
});
})();

View File

@ -0,0 +1,20 @@
/* global module, asyncTest, $, ok, equal, notEqual, start, test, Util, testLog */
(function() {
var viewer;
module('TileSourceCollection', {
setup: function () {
testLog.reset();
},
teardown: function () {
}
});
// ----------
asyncTest('deprecation', function() {
Util.testDeprecation(OpenSeadragon, 'TileSourceCollection');
start();
});
})();

View File

@ -3,7 +3,7 @@
(function () { (function () {
var viewer; var viewer;
var VIEWER_ID = "example"; var VIEWER_ID = "example";
var PREFIX_URL = "/build/openseadragon/images"; var PREFIX_URL = "/build/openseadragon/images/";
var SPRING_STIFFNESS = 100; // Faster animation = faster tests var SPRING_STIFFNESS = 100; // Faster animation = faster tests
module("viewport", { module("viewport", {
@ -209,7 +209,7 @@
method: 'getHomeBounds', method: 'getHomeBounds',
processExpected: function(level, expected) { processExpected: function(level, expected) {
// Have to special case this to avoid dividing by 0 // Have to special case this to avoid dividing by 0
if(level === 0){ if(level === -1 || level === 0){
expected = new OpenSeadragon.Rect(0, 0, 1, 1); expected = new OpenSeadragon.Rect(0, 0, 1, 1);
} else { } else {
var sideLength = 1.0 / viewer.defaultZoomLevel; // it's a square in this case var sideLength = 1.0 / viewer.defaultZoomLevel; // it's a square in this case
@ -245,8 +245,10 @@
viewport.zoomTo(ZOOM_FACTOR, null, true); viewport.zoomTo(ZOOM_FACTOR, null, true);
viewport.update(); // need to call this even with immediately=true viewport.update(); // need to call this even with immediately=true
// If the default zoom level is set to 0, then we expect the home zoom to be 1. // Special cases for oddball levels
if(level === 0){ if (level === -1) {
expected = 0.25;
} else if(level === 0){
expected = 1; expected = 1;
} }
@ -285,11 +287,11 @@
for(var i = 0; i < testRects.length; i++){ for(var i = 0; i < testRects.length; i++){
var rect = testRects[i].times(viewport.getContainerSize()); var rect = testRects[i].times(viewport.getContainerSize());
viewport.resetContentSize(rect); viewport.resetContentSize(rect.getSize());
viewport.update(); viewport.update();
propEqual( propEqual(
viewport.contentSize, viewport.contentSize,
rect, rect.getSize(),
"Reset content size correctly." "Reset content size correctly."
); );
} }
@ -407,7 +409,7 @@
viewport.update(); viewport.update();
propEqual( propEqual(
viewport.getBounds(), viewport.getBounds(),
new OpenSeadragon.Rect(-1.5,0,4,4), new OpenSeadragon.Rect(0, 1.5, 1, 1),
"Viewport fit a tall image horizontally." "Viewport fit a tall image horizontally."
); );
start(); start();
@ -424,7 +426,7 @@
viewport.update(); viewport.update();
propEqual( propEqual(
viewport.getBounds(), viewport.getBounds(),
new OpenSeadragon.Rect(0,0,0.25,0.25), new OpenSeadragon.Rect(0.375, 0, 0.25, 0.25),
"Viewport fit a wide image vertically." "Viewport fit a wide image vertically."
); );
start(); start();
@ -719,9 +721,7 @@
return el.times(window_boundary); return el.times(window_boundary);
}, },
getExpected: function(orig, viewport) { getExpected: function(orig, viewport) {
var position, pos_point; var pos_point = OpenSeadragon.getElementOffset(viewer.element);
position = viewer.element.getBoundingClientRect();
pos_point = new OpenSeadragon.Point(position.top, position.left);
return orig.minus(pos_point).divide(viewport.getContainerSize().x * ZOOM_FACTOR).plus(VIEWER_PADDING); return orig.minus(pos_point).divide(viewport.getContainerSize().x * ZOOM_FACTOR).plus(VIEWER_PADDING);
}, },
method: 'windowToViewportCoordinates' method: 'windowToViewportCoordinates'
@ -735,9 +735,7 @@
return el.times(viewer.source.dimensions.x); return el.times(viewer.source.dimensions.x);
}, },
getExpected: function(orig, viewport) { getExpected: function(orig, viewport) {
var position, pos_point; var pos_point = OpenSeadragon.getElementOffset(viewer.element);
position = viewer.element.getBoundingClientRect();
pos_point = new OpenSeadragon.Point(position.top, position.left);
return orig.plus(pos_point).minus(VIEWER_PADDING.times(viewport.getContainerSize().x * ZOOM_FACTOR)); return orig.plus(pos_point).minus(VIEWER_PADDING.times(viewport.getContainerSize().x * ZOOM_FACTOR));
}, },
method: 'imageToWindowCoordinates' method: 'imageToWindowCoordinates'
@ -752,9 +750,7 @@
return el.times(window_boundary); return el.times(window_boundary);
}, },
getExpected: function(orig, viewport) { getExpected: function(orig, viewport) {
var position, pos_point; var pos_point = OpenSeadragon.getElementOffset(viewer.element);
position = viewer.element.getBoundingClientRect();
pos_point = new OpenSeadragon.Point(position.top, position.left);
return orig.minus(pos_point).divide(viewport.getContainerSize().x * ZOOM_FACTOR).plus(VIEWER_PADDING); return orig.minus(pos_point).divide(viewport.getContainerSize().x * ZOOM_FACTOR).plus(VIEWER_PADDING);
}, },
method: 'windowToViewportCoordinates' method: 'windowToViewportCoordinates'
@ -768,9 +764,7 @@
return el.times(viewer.source.dimensions.x); return el.times(viewer.source.dimensions.x);
}, },
getExpected: function(orig, viewport) { getExpected: function(orig, viewport) {
var position, pos_point; var pos_point = OpenSeadragon.getElementOffset(viewer.element);
position = viewer.element.getBoundingClientRect();
pos_point = new OpenSeadragon.Point(position.top, position.left);
return orig.minus(VIEWER_PADDING).times(viewport.getContainerSize().x * ZOOM_FACTOR).plus(pos_point); return orig.minus(VIEWER_PADDING).times(viewport.getContainerSize().x * ZOOM_FACTOR).plus(pos_point);
}, },
method: 'viewportToWindowCoordinates' method: 'viewportToWindowCoordinates'

226
test/modules/world.js Normal file
View File

@ -0,0 +1,226 @@
/* global module, asyncTest, $, ok, equal, notEqual, start, test, Util, testLog */
(function() {
var viewer;
module('World', {
setup: function () {
var example = $('<div id="example"></div>').appendTo("#qunit-fixture");
testLog.reset();
viewer = OpenSeadragon({
id: 'example',
prefixUrl: '/build/openseadragon/images/',
springStiffness: 100 // Faster animation = faster tests
});
},
teardown: function () {
if (viewer && viewer.close) {
viewer.close();
}
viewer = null;
}
});
// ----------
var checkBounds = function(expected, message) {
var bounds = viewer.world.getHomeBounds();
ok(bounds.equals(expected), message + ' ' + bounds.toString());
};
// ----------
asyncTest('adding a tiled image', function() {
ok(viewer.world, 'World exists');
viewer.world.addHandler('add-item', function(event) {
ok(event, 'add-item handler received event data');
equal(event.eventSource, viewer.world, 'sender of add-item event was world');
ok(event.item, 'add-item event includes item');
equal(viewer.world.getItemCount(), 1, 'there is now 1 item');
equal(event.item, viewer.world.getItemAt(0), 'item is accessible via getItemAt');
equal(viewer.world.getIndexOfItem(event.item), 0, 'item index is 0');
start();
});
equal(viewer.world.getItemCount(), 0, 'no items to start with');
viewer.open('/test/data/testpattern.dzi');
});
// ----------
asyncTest('metrics', function() {
viewer.addHandler('open', function(event) {
checkBounds(new OpenSeadragon.Rect(0, 0, 4, 4), 'bounds after open');
var expectedContentFactor = viewer.world.getItemAt(1).getContentSize().x / 2;
equal(viewer.world.getContentFactor(), expectedContentFactor, 'content factor has changed');
viewer.world.addHandler('metrics-change', function metricsChangeHandler(event) {
viewer.world.removeHandler('metrics-change', metricsChangeHandler);
ok(event, 'metrics-change handler received event data');
equal(event.eventSource, viewer.world, 'sender of metrics-change event was world');
checkBounds(new OpenSeadragon.Rect(0, 0, 7, 12), 'bounds after position');
viewer.world.getItemAt(0).setWidth(20);
checkBounds(new OpenSeadragon.Rect(0, 0, 20, 20), 'bounds after size');
start();
});
viewer.world.getItemAt(1).setPosition(new OpenSeadragon.Point(5, 10));
});
checkBounds(new OpenSeadragon.Rect(0, 0, 1, 1), 'default bounds');
equal(viewer.world.getContentFactor(), 1, 'default content factor');
viewer.open([
{
tileSource: '/test/data/testpattern.dzi',
width: 4
}, {
tileSource: '/test/data/testpattern.dzi',
width: 2
}
]);
});
// ----------
asyncTest('remove/reorder tiled images', function() {
var handlerCount = 0;
viewer.addHandler('open', function(event) {
equal(viewer.world.getItemCount(), 3, 'there are now 3 items');
var item0 = viewer.world.getItemAt(0);
var item1 = viewer.world.getItemAt(1);
viewer.world.addHandler('item-index-change', function(event) {
handlerCount++;
ok(event, 'item-index-change handler received event data');
equal(event.eventSource, viewer.world, 'sender of item-index-change event was world');
equal(event.item, item0, 'item-index-change event includes correct item');
equal(event.newIndex, 1, 'item-index-change event includes correct newIndex');
equal(event.previousIndex, 0, 'item-index-change event includes correct previousIndex');
equal(viewer.world.getItemAt(0), item1, 'item1 is now at index 0');
equal(viewer.world.getItemAt(1), item0, 'item0 is now at index 1');
});
viewer.world.setItemIndex(item0, 1);
viewer.world.addHandler('remove-item', function removeHandler(event) {
viewer.world.removeHandler('remove-item', removeHandler);
handlerCount++;
ok(event, 'remove-item handler received event data');
equal(event.eventSource, viewer.world, 'sender of remove-item event was world');
equal(event.item, item1, 'remove-item event includes correct item');
equal(viewer.world.getItemCount(), 2, 'after removal, only two items remain');
equal(viewer.world.getItemAt(0), item0, 'item0 is now at index 0');
});
viewer.world.removeItem(item1);
var removeCount = 0;
viewer.world.addHandler('remove-item', function() {
removeCount++;
if (removeCount === 2) {
handlerCount++;
equal(viewer.world.getItemCount(), 0, 'after removeAll, no items remain');
}
});
viewer.world.removeAll();
equal(handlerCount, 3, 'correct number of handlers called');
start();
});
equal(viewer.world.getItemCount(), 0, 'no items to start with');
viewer.open([
'/test/data/testpattern.dzi',
'/test/data/testpattern.dzi',
'/test/data/testpattern.dzi'
]);
});
// ----------
asyncTest('draw', function() {
var handlerCount = 0;
viewer.addHandler('open', function(event) {
equal(viewer.world.needsDraw(), true, 'needs draw after open');
viewer.addHandler('update-level', function updateHandler() {
viewer.removeHandler('update-level', updateHandler);
handlerCount++;
});
viewer.world.draw();
equal(handlerCount, 1, 'correct number of handlers called');
start();
});
equal(viewer.world.needsDraw(), false, 'needs no draw at first');
viewer.open('/test/data/testpattern.dzi');
});
// ----------
asyncTest('resetItems', function() {
viewer.addHandler('tile-drawn', function updateHandler() {
viewer.removeHandler('tile-drawn', updateHandler);
ok(viewer.tileCache.numTilesLoaded() > 0, 'we have tiles after tile-drawn');
viewer.world.resetItems();
equal(viewer.tileCache.numTilesLoaded(), 0, 'no tiles after reset');
start();
});
equal(viewer.tileCache.numTilesLoaded(), 0, 'no tiles at start');
viewer.open('/test/data/testpattern.dzi');
});
// ----------
asyncTest('arrange', function() {
viewer.addHandler('open', function(event) {
checkBounds(new OpenSeadragon.Rect(0, 0, 1, 1), 'all stacked');
viewer.world.arrange({
layout: 'horizontal',
rows: 1,
tileSize: 1,
tileMargin: 0.5
});
checkBounds(new OpenSeadragon.Rect(0, 0, 4, 1), 'one horizontal row');
viewer.world.arrange({
layout: 'horizontal',
rows: 2,
tileSize: 1,
tileMargin: 0.5
});
checkBounds(new OpenSeadragon.Rect(0, 0, 2.5, 2.5), 'grid');
viewer.world.arrange({
layout: 'vertical',
rows: 1,
tileSize: 1,
tileMargin: 0.5
});
checkBounds(new OpenSeadragon.Rect(0, 0, 1, 4), 'one vertical column');
start();
});
viewer.open([
'/test/data/testpattern.dzi',
'/test/data/testpattern.dzi',
'/test/data/testpattern.dzi'
]);
});
})();

View File

@ -5,7 +5,7 @@
<title>OpenSeadragon QUnit</title> <title>OpenSeadragon QUnit</title>
<link rel="stylesheet" href="/node_modules/qunitjs/qunit/qunit.css"> <link rel="stylesheet" href="/node_modules/qunitjs/qunit/qunit.css">
<link rel="stylesheet" href="/test/lib/jquery-ui-1.10.2/css/smoothness/jquery-ui-1.10.2.min.css"> <link rel="stylesheet" href="/test/lib/jquery-ui-1.10.2/css/smoothness/jquery-ui-1.10.2.min.css">
<link rel="stylesheet" href="/test/test.css"> <link rel="stylesheet" href="/test/helpers/test.css">
</head> </head>
<body> <body>
<div id="qunit"></div> <div id="qunit"></div>
@ -14,23 +14,31 @@
<script src="/test/lib/jquery-1.9.1.min.js"></script> <script src="/test/lib/jquery-1.9.1.min.js"></script>
<script src="/test/lib/jquery-ui-1.10.2/js/jquery-ui-1.10.2.min.js"></script> <script src="/test/lib/jquery-ui-1.10.2/js/jquery-ui-1.10.2.min.js"></script>
<script src="/test/lib/jquery.simulate.js"></script> <script src="/test/lib/jquery.simulate.js"></script>
<script src="/build/openseadragon/openseadragon.min.js"></script> <script src="/build/openseadragon/openseadragon.js"></script>
<script src="/test/legacy.mouse.shim.js"></script> <script src="/test/helpers/legacy.mouse.shim.js"></script>
<script src="/test/test.js"></script> <script src="/test/helpers/test.js"></script>
<!-- Polyfill must be inserted first because it is testing functions <!-- Polyfill must be inserted first because it is testing functions
reassignments which could be done by other test. --> reassignments which could be done by other test. -->
<script src="/test/polyfills.js"></script> <script src="/test/modules/polyfills.js"></script>
<script src="/test/basic.js"></script> <script src="/test/modules/basic.js"></script>
<script src="/test/navigator.js"></script> <script src="/test/modules/strings.js"></script>
<script src="/test/strings.js"></script> <script src="/test/modules/formats.js"></script>
<script src="/test/formats.js"></script> <script src="/test/modules/utils.js"></script>
<script src="/test/utils.js"></script> <script src="/test/modules/events.js"></script>
<script src="/test/events.js"></script> <script src="/test/modules/units.js"></script>
<script src="/test/units.js"></script> <script src="/test/modules/multi-image.js"></script>
<script src="/test/layers.js"></script> <script src="/test/modules/overlays.js"></script>
<script src="/test/overlays.js"></script> <script src="/test/modules/controls.js"></script>
<script src="/test/controls.js"></script> <script src="/test/modules/viewport.js"></script>
<script src="/test/viewport.js"></script> <script src="/test/modules/world.js"></script>
<script src="/test/modules/drawer.js"></script>
<script src="/test/modules/tiledimage.js"></script>
<script src="/test/modules/tilecache.js"></script>
<script src="/test/modules/referencestrip.js"></script>
<script src="/test/modules/tilesourcecollection.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>
</body> </body>
</html> </html>