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
build/
sftp-config.json
coverage/
temp/

View File

@ -1,3 +1,5 @@
/* global module */
module.exports = function(grunt) {
// ----------
@ -5,7 +7,7 @@ module.exports = function(grunt) {
grunt.loadNpmTasks("grunt-contrib-concat");
grunt.loadNpmTasks("grunt-contrib-jshint");
grunt.loadNpmTasks("grunt-contrib-uglify");
grunt.loadNpmTasks("grunt-contrib-qunit");
grunt.loadNpmTasks("grunt-qunit-istanbul");
grunt.loadNpmTasks("grunt-contrib-connect");
grunt.loadNpmTasks("grunt-contrib-watch");
grunt.loadNpmTasks("grunt-contrib-clean");
@ -44,11 +46,14 @@ module.exports = function(grunt) {
"src/referencestrip.js",
"src/displayrectangle.js",
"src/spring.js",
"src/imageLoader.js",
"src/imageloader.js",
"src/tile.js",
"src/overlay.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: {
build: ["build"],
package: [packageDir],
coverage: ["coverage"],
release: {
src: [releaseRoot],
options: {
@ -134,10 +140,26 @@ module.exports = function(grunt) {
}
},
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: {
options: {
timeout: 10000,
urls: [ "http://localhost:8000/test/test.html" ]
timeout: 10000
}
}
},
@ -151,7 +173,7 @@ module.exports = function(grunt) {
},
watch: {
files: [ "Gruntfile.js", "src/*.js", "images/*" ],
tasks: "build"
tasks: "watchTask"
},
jshint: {
options: {
@ -206,6 +228,8 @@ module.exports = function(grunt) {
});
// ----------
// Bower task.
// Generates the Bower file for site-build.
grunt.registerTask("bower", function() {
var path = "../site-build/bower.json";
var data = grunt.file.readJSON(path);
@ -213,6 +237,18 @@ module.exports = function(grunt) {
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.
// 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"
]);
// ----------
// Minimal build task.
// For use during development as desired. Creates only the unminified version.
grunt.registerTask("minbuild", [
"git-describe", "concat", "copy:build"
]);
// ----------
// Test task.
// 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.

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`.
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
OpenSeadragon is truly a community project; we welcome your involvement!

View File

@ -1,6 +1,54 @@
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)
* Corrected IIIF tile source to use canonical syntax (#586)

View File

@ -8,7 +8,8 @@
"*.sublime-workspace"
],
"folder_exclude_patterns": [
"node_modules"
"node_modules",
"coverage"
]
}
],
@ -17,5 +18,5 @@
"tab_size": 4,
"translate_tabs_to_spaces": true,
"trim_trailing_white_space_on_save": true
}
}
}

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,7 @@
*
* 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:
@ -34,23 +34,14 @@
(function( $ ){
/**
* @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.
*/
// private class
function ImageJob ( options ) {
$.extend( true, this, {
timeout: $.DEFAULT_SETTINGS.timeout,
jobId: null
}, options );
/**
* Image object which will contain downloaded image.
* @member {Image} image
@ -60,11 +51,6 @@ function ImageJob ( options ) {
}
ImageJob.prototype = {
/**
* Initiates downloading of associated image.
* @method
*/
start: function(){
var _this = this;
@ -104,21 +90,25 @@ ImageJob.prototype = {
};
/**
* @class
* @class ImageLoader
* @memberof OpenSeadragon
* @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, {
jobLimit: $.DEFAULT_SETTINGS.imageLoaderLimit,
jobQueue: [],
jobsInProgress: 0
});
}, options );
};
$.ImageLoader.prototype = {
$.ImageLoader.prototype = /** @lends OpenSeadragon.ImageLoader.prototype */{
/**
* Add an unloaded image to the loader queue.
* @method
@ -143,7 +133,7 @@ $.ImageLoader.prototype = {
this.jobsInProgress++;
}
else {
this.jobQueue.push( newJob );
this.jobQueue.push( newJob );
}
},
@ -172,10 +162,10 @@ function completeJob( loader, job, callback ) {
if ( (!loader.jobLimit || loader.jobsInProgress < loader.jobLimit) && loader.jobQueue.length > 0) {
nextJob = loader.jobQueue.shift();
nextJob.start();
loader.jobsInProgress++;
}
callback( job.image );
}
}( OpenSeadragon ));

View File

@ -82,7 +82,7 @@ $.Navigator = function( options ){
options.controlOptions.width = options.width;
}
}
} else {
this.element = document.getElementById( options.id );
options.controlOptions = {
@ -173,7 +173,10 @@ $.Navigator = function( options ){
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 ) {
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;
@ -194,8 +197,7 @@ $.Navigator = function( options ){
this.displayRegionContainer.appendChild(this.displayRegion);
this.element.getElementsByTagName('div')[0].appendChild(this.displayRegionContainer);
if (options.navigatorRotate)
{
if (options.navigatorRotate) {
options.viewer.addHandler("rotate", function (args) {
_setTransformRotate(_this.displayRegionContainer, args.degrees);
_setTransformRotate(_this.displayRegion, -args.degrees);
@ -213,12 +215,38 @@ $.Navigator = function( options ){
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 */{
/**
* Used to notify the navigator when its size has changed.
* Used to notify the navigator when its size has changed.
* Especially useful when {@link OpenSeadragon.Options}.navigatorAutoResize is set to false and the navigator is resizable.
* @function
*/
@ -228,23 +256,13 @@ $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /*
(this.container.clientWidth === 0 ? 1 : this.container.clientWidth),
(this.container.clientHeight === 0 ? 1 : this.container.clientHeight)
);
if ( !containerSize.equals( this.oldContainerSize ) ) {
var oldBounds = this.viewport.getBounds();
var oldCenter = this.viewport.getCenter();
this.viewport.resize( containerSize, true );
var imageHeight = 1 / this.source.aspectRatio;
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.viewport.goHome(true);
this.oldContainerSize = containerSize;
this.drawer.update();
this.drawer.clear();
this.world.draw();
}
}
},
@ -264,55 +282,91 @@ $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /*
bottomright;
viewerSize = $.getElementSize( this.viewer.element );
if ( !viewerSize.equals( this.oldViewerSize ) ) {
if ( this._resizeWithViewer && !viewerSize.equals( this.oldViewerSize ) ) {
this.oldViewerSize = viewerSize;
if ( this.maintainSizeRatio ) {
if ( this.maintainSizeRatio || !this.elementArea) {
newWidth = viewerSize.x * this.sizeRatio;
newHeight = viewerSize.y * this.sizeRatio;
}
else {
} else {
newWidth = Math.sqrt(this.elementArea * (viewerSize.x / viewerSize.y));
newHeight = this.elementArea / newWidth;
}
this.element.style.width = Math.round( newWidth ) + 'px';
this.element.style.height = Math.round( newHeight ) + 'px';
if (!this.elementArea) {
this.elementArea = newWidth * newHeight;
}
this.updateSize();
}
if( viewport && this.viewport ) {
bounds = viewport.getBounds( true );
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
(function(style) {
var style = this.displayRegion.style;
style.display = this.world.getItemCount() ? 'block' : 'none';
style.top = Math.round( topleft.y ) + 'px';
style.left = Math.round( topleft.x ) + 'px';
style.top = Math.round( topleft.y ) + 'px';
style.left = Math.round( topleft.x ) + 'px';
var width = Math.abs( topleft.x - bottomright.x );
var height = Math.abs( topleft.y - bottomright.y );
// make sure width and height are non-negative so IE doesn't throw
style.width = Math.round( Math.max( width, 0 ) ) + 'px';
style.height = Math.round( Math.max( height, 0 ) ) + 'px';
}( this.displayRegion.style ));
var width = Math.abs( topleft.x - bottomright.x );
var height = Math.abs( topleft.y - bottomright.y );
// make sure width and height are non-negative so IE doesn't throw
style.width = Math.round( Math.max( width, 0 ) ) + 'px';
style.height = Math.round( Math.max( height, 0 ) ) + 'px';
}
},
open: function( source ) {
this.updateSize();
var containerSize = this.viewer.viewport.containerSize.times( this.sizeRatio );
var ts = source.getTileSize(source.maxLevel);
if ( ts > containerSize.x || ts > containerSize.y ) {
this.minPixelRatio = Math.min( containerSize.x, containerSize.y ) / ts;
} else {
this.minPixelRatio = this.viewer.minPixelRatio;
}
return $.Viewer.prototype.open.apply( this, [ source ] );
}
// overrides Viewer.addTiledImage
addTiledImage: function(options) {
var _this = this;
var original = options.originalTiledImage;
delete options.original;
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 null;
},
// private
_matchBounds: function(myItem, theirItem, immediately) {
var bounds = theirItem.getBounds();
myItem.setPosition(bounds.getTopLeft(), immediately);
myItem.setWidth(bounds.width, immediately);
}
});
/**

View File

@ -126,19 +126,13 @@
* 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.
*
* @property {Number} [tabIndex=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.
* @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 {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 {Number} [tabIndex=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.
*
* @property {Array} overlays Array of objects defining permanent overlays of
* the viewer. The overlays added via this option and later removed with
@ -215,9 +209,6 @@
* @property {Number} [opacity=1]
* Opacity of the drawer (1=opaque, 0=transparent)
*
* @property {Number} [layersAspectRatioEpsilon=0.0001]
* Maximum aspectRatio mismatch between 2 layers.
*
* @property {Number} [degrees=0]
* Initial rotation.
*
@ -265,10 +256,14 @@
* @property {Number} [visibilityRatio=0.5]
* 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
* 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
* 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]
* 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
@ -455,8 +450,8 @@
* this setting when set to false.
*
* @property {Boolean} [showSequenceControl=true]
* If the viewer has been configured with a sequence of tile sources, then
* provide buttons for navigating forward and backward through the images.
* If sequenceMode is true, then provide buttons for navigating forward and
* backward through the images.
*
* @property {OpenSeadragon.ControlAnchor} [sequenceControlAnchor=TOP_LEFT]
* Placement of the default sequence controls.
@ -514,26 +509,29 @@
* To only change the button images, consider using
* {@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]
* 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]
* If the viewer has been configured with a sequence of tile sources, then
* normally navigating through each image resets the viewport to 'home'
* position. If preserveViewport is set to true, then the viewport position
* is preserved when navigating between images in the sequence.
* If sequenceMode is true, then normally navigating to through each image resets the
* viewport to 'home' position. If preserveViewport is set to true, then the viewport
* position is preserved when navigating between images in the sequence.
*
* @property {Boolean} [preserveOverlays=false]
* If the viewer has been configured with a sequence of tile sources, then
* normally navigating through each image resets the overlays.
* If sequenceMode is true, then normally navigating to through each image
* resets the overlays.
* If preserveOverlays is set to true, then the overlays
* are preserved when navigating between images in the sequence.
* Note: setting preserveOverlays overrides any overlays specified in the
* "overlays" property.
*
* @property {Boolean} [showReferenceStrip=false]
* If the viewer has been configured with a sequence of tile sources, then
* display a scrolling strip of image thumbnails for navigating through the images.
* If sequenceMode is true, then display a scrolling strip of image thumbnails for
* navigating through the images.
*
* @property {String} [referenceStripScroll='horizontal']
*
@ -548,16 +546,29 @@
* @property {Number} [referenceStripSizeRatio=0.2]
*
* @property {Boolean} [collectionMode=false]
* Set to true to have the viewer arrange your TiledImages in a grid or line.
*
* @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']
* If collectionMode is true, specifies whether to arrange vertically or horizontally.
*
* @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]
* Valid values are 'Anonymous', 'use-credentials', and false. If false, canvas requests will
* not use CORS, and the canvas will be tainted.
* Valid values are 'Anonymous', 'use-credentials', and false. If false, canvas requests will
* 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' ) );
}());
/**
* 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 ));
@ -916,6 +946,7 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
tileHost: null,
initialPage: 0,
crossOriginPolicy: false,
ajaxWithCredentials: false,
//PAN AND ZOOM SETTINGS AND CONSTRAINTS
panHorizontal: true,
@ -988,9 +1019,6 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
// APPEARANCE
opacity: 1,
// LAYERS SETTINGS
layersAspectRatioEpsilon: 0.0001,
//REFERENCE STRIP SETTINGS
showReferenceStrip: false,
referenceStripScroll: 'horizontal',
@ -1005,6 +1033,7 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
collectionLayout: 'horizontal', //vertical
collectionMode: false,
collectionTileSize: 800,
collectionTileMargin: 80,
//PERFORMANCE SETTINGS
imageLoaderLimit: 0,
@ -1924,13 +1953,25 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
/**
* Makes an AJAX request.
* @function
* @param {String} url - the url to request
* @param {Function} onSuccess - a function to call on a successful response
* @param {Function} onError - a function to call on when an error occurs
* @param {Object} options
* @param {String} options.url - the url to request
* @param {Function} options.success - a function to call on a successful response
* @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}
*/
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 request = $.createAjaxRequest( protocol === "file:" );
@ -1957,6 +1998,10 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
}
};
if (withCredentials) {
request.withCredentials = true;
}
try {
request.open( "GET", url, true );
request.send( null );
@ -2271,7 +2316,8 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
debug: nullfunction,
info: nullfunction,
warn: nullfunction,
error: nullfunction
error: nullfunction,
assert: nullfunction
};

View File

@ -106,7 +106,7 @@
placement: placement
};
}
this.element = options.element;
this.scales = options.location instanceof $.Rect;
this.bounds = new $.Rect(

View File

@ -60,6 +60,13 @@ $.Point = function( x, y ) {
};
$.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.
@ -189,7 +196,7 @@ $.Point.prototype = /** @lends OpenSeadragon.Point.prototype */{
* @returns {String} A string representation of this point.
*/
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 */{
/**
* @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.
@ -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
* degrees are supported.
@ -257,10 +279,10 @@ $.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{
*/
toString: function() {
return "[" +
Math.round(this.x*100) + "," +
Math.round(this.y*100) + "," +
Math.round(this.width*100) + "x" +
Math.round(this.height*100) +
(Math.round(this.x*100) / 100) + "," +
(Math.round(this.y*100) / 100) + "," +
(Math.round(this.width*100) / 100) + "x" +
(Math.round(this.height*100) / 100) +
"]";
}
};

View File

@ -290,6 +290,13 @@ $.extend( $.ReferenceStrip.prototype, $.EventSource.prototype, $.Viewer.prototyp
return true;
}
return false;
},
// Overrides Viewer.destroy
destroy: function() {
if (this.element) {
this.element.parentNode.removeChild(this.element);
}
}
} );
@ -470,7 +477,7 @@ function loadPanels( strip, viewerSize, scroll ) {
*/
function onStripEnter( event ) {
var element = event.eventSource.element;
//$.setElementOpacity(element, 0.8);
//element.style.border = '1px solid #555';
@ -498,7 +505,7 @@ function onStripEnter( event ) {
*/
function onStripExit( event ) {
var element = event.eventSource.element;
if ( 'horizontal' == this.scroll ) {
//element.style.paddingTop = "10px";

View File

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

View File

@ -33,7 +33,7 @@
*/
(function( $ ){
var TILE_CACHE = {};
/**
* @class Tile
* @memberof OpenSeadragon
@ -231,7 +231,8 @@ $.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{
* Renders the tile in a canvas-based context.
* @function
* @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.
*/
drawCanvas: function( context, drawingHandler ) {
@ -241,16 +242,23 @@ $.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{
rendered,
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(
"Attempting to draw tile %s when it's not yet loaded.",
this.toString()
);
return;
}
context.globalAlpha = this.opacity;
//context.save();
context.globalAlpha = this.opacity;
//if we are supposed to be rendering fully opaque rectangle,
//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
//by the png prevents edge flikering
context.clearRect(
position.x+1,
position.y+1,
size.x-2,
size.y-2
(position.x * $.pixelDensityRatio)+1,
(position.y * $.pixelDensityRatio)+1,
(size.x * $.pixelDensityRatio)-2,
(size.y * $.pixelDensityRatio)-2
);
}
if( !TILE_CACHE[ this.url ] ){
if(!rendered){
canvas = document.createElement( 'canvas' );
canvas.width = this.image.width;
canvas.height = this.image.height;
rendered = canvas.getContext('2d');
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
//allow the image to not be held in memory
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
drawingHandler({context: context, tile: this, rendered: rendered});
//rendered.save();
context.drawImage(
rendered.canvas,
0,
0,
rendered.canvas.width,
rendered.canvas.height,
position.x,
position.y,
size.x,
size.y
position.x * $.pixelDensityRatio,
position.y * $.pixelDensityRatio,
size.x * $.pixelDensityRatio,
size.y * $.pixelDensityRatio
);
//rendered.restore();
//context.restore();
},
/**
@ -313,9 +315,6 @@ $.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{
if ( this.element && this.element.parentNode ) {
this.element.parentNode.removeChild( this.element );
}
if ( TILE_CACHE[ this.url ]){
delete TILE_CACHE[ this.url ];
}
this.element = 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
* @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
* interface that must be implemented to complete it key functionality:
* smooth transition between layers in an image pyramid. It has only a single key
* interface that must be implemented to complete its key functionality:
* 'getTileUrl'. It also has several optional interfaces that can be
* implemented if a new TileSource wishes to support configuration via a simple
* 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').
* <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
* <strong>2^(N+1) >= M</strong>.
*
* @memberof OpenSeadragon
* @extends OpenSeadragon.EventSource
* @param {Number|Object|Array|String} width
* If more than a single argument is supplied, the traditional use of
* positional parameters is supplied and width is expected to be the width
* source image at its max resolution in pixels. If a single argument is supplied and
* it is an Object or Array, the construction is assumed to occur through
* the extending classes implementation of 'configure'. Finally if only a
* single argument is supplied and it is a String, the extending class is
* expected to implement 'getImageInfo' and 'configure'.
* @param {Number} height
* @param {Object} options
* You can either specify a URL, or literally define the TileSource (by specifying
* width, height, tileSize, tileOverlap, minLevel, and maxLevel). For the former,
* the extending class is expected to implement 'getImageInfo' and 'configure'.
* For the latter, the construction is assumed to occur through
* the extending classes implementation of 'configure'.
* @param {String} [options.url]
* The URL for the data necessary for this TileSource.
* @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.
* @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.
* Tile size determines the point at which the image pyramid must be
* 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.
* @param {Number} minLevel
* @param {Number} [options.minLevel]
* The minimum level to attempt to load.
* @param {Number} maxLevel
* @param {Number} [options.maxLevel]
* The maximum level to attempt to load.
*/
$.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLevel ) {
var callback = null,
args = arguments,
var _this = this;
var args = arguments,
options,
i;
@ -102,19 +110,23 @@ $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLeve
//source
$.extend( true, this, options );
//Any functions that are passed as arguments are bound to the ready callback
/*jshint loopfunc:true*/
for ( i = 0; i < arguments.length; i++ ) {
if ( $.isFunction( arguments[ i ] ) ) {
callback = arguments[ i ];
this.addHandler( 'ready', function ( event ) {
callback( event );
} );
//only one callback per constructor
break;
if (!this.success) {
//Any functions that are passed as arguments are bound to the ready callback
for ( i = 0; i < arguments.length; i++ ) {
if ( $.isFunction( arguments[ i ] ) ) {
this.success = arguments[ i ];
//only one callback per constructor
break;
}
}
}
if (this.success) {
this.addHandler( 'ready', function ( event ) {
_this.success( event );
} );
}
/**
* Ratio of width to height
* @member {Number} aspectRatio
@ -127,7 +139,7 @@ $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLeve
*/
/**
* The size of the image tiles used to compose the image.
* Please note that tileSize may be deprecated in a future release.
* Please note that tileSize may be deprecated in a future release.
* Instead the getTileSize(level) function should be used.
* @member {Number} tileSize
* @memberof OpenSeadragon.TileSource#
@ -148,12 +160,16 @@ $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLeve
* @memberof OpenSeadragon.TileSource#
*/
/**
*
*
* @member {Boolean} ready
* @memberof OpenSeadragon.TileSource#
*/
if( 'string' == $.type( arguments[ 0 ] ) ){
this.url = arguments[0];
}
if (this.url) {
//in case the getImageInfo method is overriden and/or implies an
//async mechanism set some safe defaults first
this.aspectRatio = 1;
@ -165,7 +181,7 @@ $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLeve
this.ready = false;
//configuration via url implies the extending class
//implements and 'configure'
this.getImageInfo( arguments[ 0 ] );
this.getImageInfo( this.url );
} else {
@ -185,8 +201,8 @@ $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLeve
Math.log( 2 )
) : 0
);
if( callback && $.isFunction( callback ) ){
callback( this );
if( this.success && $.isFunction( this.success ) ){
this.success( this );
}
}
@ -197,7 +213,7 @@ $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLeve
$.TileSource.prototype = /** @lends OpenSeadragon.TileSource.prototype */{
/**
* Return the tileSize for a given level.
* Return the tileSize for a given level.
* Subclasses should override this if tileSizes can be different at different levels
* such as in IIIFTileSource. Code should use this function rather than reading
* from .tileSize directly. tileSize may be deprecated in a future release.
@ -355,6 +371,10 @@ $.TileSource.prototype = /** @lends OpenSeadragon.TileSource.prototype */{
}
options = $TileSource.prototype.configure.apply( _this, [ data, url ]);
if (options.ajaxWithCredentials === undefined) {
options.ajaxWithCredentials = _this.ajaxWithCredentials;
}
readySource = new $TileSource( options );
_this.ready = true;
/**
@ -383,45 +403,50 @@ $.TileSource.prototype = /** @lends OpenSeadragon.TileSource.prototype */{
});
} else {
// request info via xhr asynchronously.
$.makeAjaxRequest( url, function( xhr ) {
var data = processResponse( xhr );
callback( data );
}, function ( xhr, exc ) {
var msg;
$.makeAjaxRequest( {
url: url,
withCredentials: this.ajaxWithCredentials,
success: function( xhr ) {
var data = processResponse( xhr );
callback( data );
},
error: function ( xhr, exc ) {
var msg;
/*
IE < 10 will block XHR requests to different origins. Any property access on the request
object will raise an exception which we'll attempt to handle by formatting the original
exception rather than the second one raised when we try to access xhr.status
*/
try {
msg = "HTTP " + xhr.status + " attempting to load TileSource";
} catch ( e ) {
var formattedExc;
if ( typeof( exc ) == "undefined" || !exc.toString ) {
formattedExc = "Unknown error";
} else {
formattedExc = exc.toString();
/*
IE < 10 will block XHR requests to different origins. Any property access on the request
object will raise an exception which we'll attempt to handle by formatting the original
exception rather than the second one raised when we try to access xhr.status
*/
try {
msg = "HTTP " + xhr.status + " attempting to load TileSource";
} catch ( e ) {
var formattedExc;
if ( typeof( exc ) == "undefined" || !exc.toString ) {
formattedExc = "Unknown error";
} else {
formattedExc = exc.toString();
}
msg = formattedExc + " attempting to load TileSource";
}
msg = formattedExc + " attempting to load TileSource";
/***
* Raised when an error occurs loading a TileSource.
*
* @event open-failed
* @memberof OpenSeadragon.TileSource
* @type {object}
* @property {OpenSeadragon.TileSource} eventSource - A reference to the TileSource which raised the event.
* @property {String} message
* @property {String} source
* @property {?Object} userData - Arbitrary subscriber-defined object.
*/
_this.raiseEvent( 'open-failed', {
message: msg,
source: url
});
}
/***
* Raised when an error occurs loading a TileSource.
*
* @event open-failed
* @memberof OpenSeadragon.TileSource
* @type {object}
* @property {OpenSeadragon.TileSource} eventSource - A reference to the TileSource which raised the event.
* @property {String} message
* @property {String} source
* @property {?Object} userData - Arbitrary subscriber-defined object.
*/
_this.raiseEvent( 'open-failed', {
message: msg,
source: url
});
});
}

View File

@ -34,110 +34,9 @@
(function( $ ){
/**
* @class TileSourceCollection
* @memberof OpenSeadragon
* @extends OpenSeadragon.TileSource
*/
// deprecated
$.TileSourceCollection = function( tileSize, tileSources, rows, layout ) {
var options;
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 ] );
$.console.error('TileSourceCollection is deprecated; use World instead');
};
$.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 ));

File diff suppressed because it is too large Load Diff

View File

@ -37,10 +37,23 @@
/**
* @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
* @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 ) {
@ -63,6 +76,15 @@ $.Viewport = function( options ) {
delete options.config;
}
this._margins = $.extend({
left: 0,
top: 0,
right: 0,
bottom: 0
}, options.margins || {});
delete options.margins;
$.extend( true, this, {
//required settings
@ -89,6 +111,11 @@ $.Viewport = function( 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({
initial: 0,
springStiffness: this.springStiffness,
@ -105,44 +132,77 @@ $.Viewport = function( options ) {
animationTime: this.animationTime
});
this.resetContentSize( this.contentSize );
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 );
} else {
this.setHomeBounds(new $.Rect(0, 0, 1, 1), 1);
}
this.goHome( true );
this.update();
};
$.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
/**
* Updates the viewport's home bounds and constraints for the given content size.
* @function
* @param {OpenSeadragon.Point} contentSize - size of the content in content units
* @return {OpenSeadragon.Viewport} Chainable.
* @fires OpenSeadragon.Viewer.event:reset-size
*/
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.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 ){
/**
* 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
* @memberof OpenSeadragon.Viewer
* @type {object}
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
* @property {OpenSeadragon.Point} contentSize
* @property {OpenSeadragon.Rect} homeBounds
* @property {Number} contentFactor
* @property {?Object} userData - Arbitrary subscriber-defined object.
*/
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 =
this.contentAspectX / this.getAspectRatio();
var output;
if( this.homeFillsViewer ){ // fill the viewer and clip the image
return ( aspectFactor >= 1) ?
output = ( aspectFactor >= 1) ?
aspectFactor :
1;
} else {
return ( aspectFactor >= 1 ) ?
output = ( aspectFactor >= 1 ) ?
1 :
aspectFactor;
}
return output / this.homeBounds.width;
}
},
@ -216,16 +279,18 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
this.minZoomLevel :
this.minZoomImageRatio * homeZoom;
return Math.min( zoom, homeZoom );
return zoom;
},
/**
* @function
*/
getMaxZoom: function() {
var zoom = this.maxZoomLevel ?
this.maxZoomLevel :
( this.contentSize.x * this.maxZoomPixelRatio / this.containerSize.x );
var zoom = this.maxZoomLevel;
if (!zoom) {
zoom = this.contentSize.x * this.maxZoomPixelRatio / this._containerInnerSize.x;
zoom /= this.homeBounds.width;
}
return Math.max( zoom, this.getHomeZoom() );
},
@ -234,11 +299,12 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
* @function
*/
getAspectRatio: function() {
return this.containerSize.x / this.containerSize.y;
return this._containerInnerSize.x / this._containerInnerSize.y;
},
/**
* @function
* @returns {OpenSeadragon.Point} The size of the container, in screen coordinates.
*/
getContainerSize: function() {
return new $.Point(
@ -250,6 +316,7 @@ $.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, in viewport coordinates.
*/
getBounds: function( 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
* @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
);
newZoomPixel = this.zoomPoint.minus(
bounds.getTopLeft()
).times(
this.containerSize.x / bounds.width
);
newZoomPixel = this._pixelFromPoint(this.zoomPoint, bounds);
deltaZoomPixels = newZoomPixel.minus( oldZoomPixel );
deltaZoomPoints = deltaZoomPixels.divide( this.containerSize.x * zoom );
deltaZoomPoints = deltaZoomPixels.divide( this._containerInnerSize.x * zoom );
return centerTarget.plus( deltaZoomPoints );
},
@ -335,13 +414,7 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
* @return {OpenSeadragon.Rect} constrained bounds.
*/
_applyBoundaryConstraints: function( bounds, immediately ) {
var horizontalThreshold,
verticalThreshold,
left,
right,
top,
bottom,
dx = 0,
var dx = 0,
dy = 0,
newBounds = new $.Rect(
bounds.x,
@ -350,49 +423,52 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
bounds.height
);
horizontalThreshold = this.visibilityRatio * newBounds.width;
verticalThreshold = this.visibilityRatio * newBounds.height;
left = newBounds.x + newBounds.width;
right = 1 - newBounds.x;
top = newBounds.y + newBounds.height;
bottom = this.contentAspectY - newBounds.y;
var horizontalThreshold = this.visibilityRatio * newBounds.width;
var verticalThreshold = this.visibilityRatio * newBounds.height;
if ( this.wrapHorizontal ) {
//do nothing
} else {
if ( left < horizontalThreshold ) {
dx = horizontalThreshold - left;
var thresholdLeft = newBounds.x + (newBounds.width - horizontalThreshold);
if (this.homeBounds.x > thresholdLeft) {
dx = this.homeBounds.x - thresholdLeft;
}
if ( right < horizontalThreshold ) {
dx = dx ?
( dx + right - horizontalThreshold ) / 2 :
( right - horizontalThreshold );
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 ( this.wrapVertical ) {
//do nothing
} else {
if ( top < verticalThreshold ) {
dy = ( verticalThreshold - top );
var thresholdTop = newBounds.y + (newBounds.height - verticalThreshold);
if (this.homeBounds.y > thresholdTop) {
dy = this.homeBounds.y - thresholdTop;
}
if ( bottom < verticalThreshold ) {
dy = dy ?
( dy + bottom - verticalThreshold ) / 2 :
( bottom - verticalThreshold );
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 ( dx || dy || immediately ) {
if ( dx || dy ) {
newBounds.x += dx;
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 ){
@ -512,21 +588,28 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
}
newBounds = this._applyBoundaryConstraints( newBounds, immediately );
center = newBounds.getCenter();
}
if ( newZoom == oldZoom || newBounds.width == oldBounds.width ) {
return this.panTo( constraints ? newBounds.getCenter() : center, immediately );
if (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(
this.containerSize.x / oldBounds.width
this._containerInnerSize.x / oldBounds.width
).minus(
newBounds.getTopLeft().times(
this.containerSize.x / newBounds.width
this._containerInnerSize.x / newBounds.width
)
).divide(
this.containerSize.x / oldBounds.width -
this.containerSize.x / newBounds.width
this._containerInnerSize.x / oldBounds.width -
this._containerInnerSize.x / newBounds.width
);
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
* @return {OpenSeadragon.Viewport} Chainable.
*/
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 ) {
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 );
return this.fitBounds( box, immediately );
},
/**
* @function
* Zooms so the image just fills the viewer horizontally.
* @param {Boolean} immediately
* @return {OpenSeadragon.Viewport} Chainable.
*/
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 ) {
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 );
return this.fitBounds( box, immediately );
},
@ -736,7 +793,7 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
/**
* Raised when rotation has been changed.
*
* @event update-viewport
* @event rotate
* @memberof OpenSeadragon.Viewer
* @type {object}
* @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,
widthDeltaFactor;
this.containerSize = new $.Point(
newContainerSize.x,
newContainerSize.y
this.containerSize.x = newContainerSize.x;
this.containerSize.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 ) {
// TODO: widthDeltaFactor will always be 1; probably not what's intended
widthDeltaFactor = newContainerSize.x / this.containerSize.x;
newBounds.width = oldBounds.width * widthDeltaFactor;
newBounds.height = newBounds.width / this.getAspectRatio();
@ -805,10 +866,7 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
* @function
*/
update: function() {
var oldCenterX = this.centerSpringX.current.value,
oldCenterY = this.centerSpringY.current.value,
oldZoom = this.zoomSpring.current.value,
oldZoomPixel,
var oldZoomPixel,
newZoomPixel,
deltaZoomPixels,
deltaZoomPoints;
@ -819,7 +877,7 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
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 );
deltaZoomPixels = newZoomPixel.minus( oldZoomPixel );
deltaZoomPoints = this.deltaPointsFromPixels( deltaZoomPixels, true );
@ -833,9 +891,15 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
this.centerSpringX.update();
this.centerSpringY.update();
return this.centerSpringX.current.value != oldCenterX ||
this.centerSpringY.current.value != oldCenterY ||
this.zoomSpring.current.value != oldZoom;
var changed = this.centerSpringX.current.value != this._oldCenterX ||
this.centerSpringY.current.value != this._oldCenterY ||
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 ) {
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 ) {
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).
*/
pixelFromPoint: function( point, current ) {
var bounds = this.getBounds( current );
return this._pixelFromPoint(point, this.getBounds( current ));
},
// private
_pixelFromPoint: function( point, bounds ) {
return point.minus(
bounds.getTopLeft()
).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 ) {
var bounds = this.getBounds( current );
return pixel.divide(
this.containerSize.x / bounds.width
return pixel.minus(
new $.Point(this._margins.left, this._margins.top)
).divide(
this._containerInnerSize.x / bounds.width
).plus(
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.
* This method can be called either by passing X,Y coordinates or an
* OpenSeadragon.Point
* Note: not accurate with multi-image; use TiledImage.viewportToImageCoordinates instead.
* @function
* @param {OpenSeadragon.Point} viewerX the point 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
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
* This method can be called either by passing X,Y coordinates or an
* OpenSeadragon.Point
* Note: not accurate with multi-image; use TiledImage.imageToViewportCoordinates instead.
* @function
* @param {OpenSeadragon.Point} imageX the point 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
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.
* This method can be called either by passing X,Y,width,height or an
* OpenSeadragon.Rect
* Note: not accurate with multi-image; use TiledImage.imageToViewportRectangle instead.
* @function
* @param {OpenSeadragon.Rect} imageX the rectangle in image coordinate system.
* @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
);
}
coordA = this.imageToViewportCoordinates(
imageX, imageY
);
coordB = this.imageToViewportCoordinates(
coordB = this._imageToViewportDelta(
pixelWidth, pixelHeight
);
return new $.Rect(
@ -969,6 +1072,7 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
* the viewport in point coordinates to image rectangle coordinates.
* This method can be called either by passing X,Y,width,height or an
* OpenSeadragon.Rect
* Note: not accurate with multi-image; use TiledImage.viewportToImageRectangle instead.
* @function
* @param {OpenSeadragon.Rect} viewerX the rectangle in viewport coordinate system.
* @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
);
}
coordA = this.viewportToImageCoordinates( viewerX, viewerY );
coordB = this.viewportToImageCoordinates( pointWidth, pointHeight );
coordB = this._viewportToImageDelta(pointWidth, pointHeight);
return new $.Rect(
coordA.x,
coordA.y,
@ -1002,6 +1107,7 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
/**
* Convert pixel coordinates relative to the viewer element to image
* coordinates.
* Note: not accurate with multi-image.
* @param {OpenSeadragon.Point} pixel
* @returns {OpenSeadragon.Point}
*/
@ -1013,6 +1119,7 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
/**
* Convert pixel coordinates relative to the image to
* viewer element coordinates.
* Note: not accurate with multi-image.
* @param {OpenSeadragon.Point} pixel
* @returns {OpenSeadragon.Point}
*/
@ -1023,6 +1130,7 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
/**
* Convert pixel coordinates relative to the window to image coordinates.
* Note: not accurate with multi-image.
* @param {OpenSeadragon.Point} pixel
* @returns {OpenSeadragon.Point}
*/
@ -1034,6 +1142,7 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
/**
* Convert image coordinates to pixel coordinates relative to the window.
* Note: not accurate with multi-image.
* @param {OpenSeadragon.Point} pixel
* @returns {OpenSeadragon.Point}
*/
@ -1091,15 +1200,21 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
* 1 means original image size, 0.5 half size...
* 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...
* Note: not accurate with multi-image.
* @function
* @param {Number} viewportZoom The viewport zoom
* target zoom.
* @returns {Number} imageZoom The image zoom
*/
viewportToImageZoom: function( viewportZoom ) {
var imageWidth = this.viewer.source.dimensions.x;
var containerWidth = this.getContainerSize().x;
var viewportToImageZoomRatio = containerWidth / imageWidth;
if (this.viewer && this.viewer.world.getItemCount() > 1) {
$.console.error('[Viewport.viewportToImageZoom] is not accurate with multi-image.');
}
var imageWidth = this.contentSize.x;
var containerWidth = this._containerInnerSize.x;
var scale = this.homeBounds.width;
var viewportToImageZoomRatio = (containerWidth / imageWidth) * scale;
return viewportZoom * viewportToImageZoomRatio;
},
@ -1109,15 +1224,21 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
* 1 means original image size, 0.5 half size...
* 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...
* Note: not accurate with multi-image.
* @function
* @param {Number} imageZoom The image zoom
* target zoom.
* @returns {Number} viewportZoom The viewport zoom
*/
imageToViewportZoom: function( imageZoom ) {
var imageWidth = this.viewer.source.dimensions.x;
var containerWidth = this.getContainerSize().x;
var viewportToImageZoomRatio = imageWidth / containerWidth;
if (this.viewer && this.viewer.world.getItemCount() > 1) {
$.console.error('[Viewport.imageToViewportZoom] is not accurate with multi-image.');
}
var imageWidth = this.contentSize.x;
var containerWidth = this._containerInnerSize.x;
var scale = this.homeBounds.width;
var viewportToImageZoomRatio = (imageWidth / containerWidth) / scale;
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

@ -13,7 +13,7 @@
#highlights li {
cursor: pointer;
}
</style>
</head>
<body>
@ -23,33 +23,33 @@
<div id="contentDiv" class="openseadragon1"></div>
<div id="highlights"></div>
<select onchange="changeMethod(this.value);">
<option value=0>viewport.fitBoundsWithConstraints(bounds);</option>
<option value=1>viewport.fitBounds(bounds);</option>
<option value=2>viewport.fitBounds(bounds).applyConstraints();</option>
</select>
<input type="button" value="Go home" onclick="goHome()"/>
<script type="text/javascript">
var _viewer;
var _fittingMethod = 0;
var _highlights = [
{"queryPoint":[0.13789887359998443,0.43710575899579285], "radius":0.004479581945070337,"text":"Pipe"},
{"queryPoint":[0.5923298766583593,0.6461653354541856], "radius":0.013175241014912752,"text":"Fuel here"},
{"queryPoint":[0.43920338711232304,0.7483181389302148], "radius":0.09222668710438928, "text":"Wheel"},
{"queryPoint":[0.07341677959486298,0.9028719921872319], "radius":0.08996845561083797, "text":"Nothing special"}
];
];
var generateUniqueHash = (function() {
var counter = 0;
return function() {
return "openseadragon_" + (counter++);
};
})();
var _viewer = OpenSeadragon({
element: document.getElementById("contentDiv"),
showNavigationControl: false,
@ -59,8 +59,8 @@
Image: {
xmlns: "http://schemas.microsoft.com/deepzoom/2008",
Url: 'http://cdn.photosynth.net/ps2/19d5cf2b-77ed-439f-ac21-d3046320384c/packet/undistorted/img0043/',
Format: "jpg",
Overlap: 1,
Format: "jpg",
Overlap: 1,
TileSize: 510,
Size: {
Width: 4592,
@ -78,17 +78,17 @@
str += "</ul>";
document.getElementById("highlights").innerHTML = str;
});
function gotoHighlight(index) {
var highlight = _highlights[index];
var viewport = _viewer.viewport;
var contentSize = viewport.contentSize;
var scaling = 1.0 / viewport.viewportToImageZoom(viewport.getZoom());
var radius = highlight.radius*Math.min(contentSize.x, contentSize.y);/*annotation.accurateRadius*scaling;*/
var center = new OpenSeadragon.Point(contentSize.x*highlight.queryPoint[0], contentSize.y*highlight.queryPoint[1]);
var center = new OpenSeadragon.Point(contentSize.x*highlight.queryPoint[0], contentSize.y*highlight.queryPoint[1]);
var bounds = viewport.imageToViewportRectangle(new OpenSeadragon.Rect(center.x-radius, center.y-radius, radius*2, radius*2));
if (_fittingMethod === 0) {
viewport.fitBoundsWithConstraints(bounds, false);
}
@ -97,13 +97,13 @@
}
else if (_fittingMethod === 2) {
viewport.fitBounds(bounds, false).applyConstraints();
}
}
}
function changeMethod(value) {
_fittingMethod = parseInt(value, 10);
}
function goHome() {
_viewer.viewport.goHome();
}

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 );
},
// ----------
initializeTestDOM: function () {
$( "#qunit-fixture" )
.append( '<div><div id="example"></div><div id="exampleNavigator"></div></div>' )
@ -57,14 +58,17 @@
.append( '<div id="tallexample"></div>' );
},
// ----------
equalsWithVariance: function ( value1, value2, variance ) {
return Math.abs( value1 - value2 ) <= variance;
},
// ----------
assessNumericValue: function ( value1, value2, variance, message ) {
ok( Util.equalsWithVariance( value1, value2, variance ), message + " Expected:" + value1 + " Found: " + value2 + " Variance: " + variance );
},
// ----------
timeWatcher: function ( time ) {
time = time || 2000;
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;
} )();

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() {
viewer.removeHandler('animation-finish', panHandler);
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();
};
@ -133,7 +134,7 @@
};
viewer.addHandler('animation-finish', homeHandler);
viewport.goHome(true);
viewer.viewport.goHome(true);
}
viewer.addHandler("open", opener);
@ -260,9 +261,8 @@
viewer.removeHandler('close', closeHandler);
ok(!viewer.source, 'no source');
ok(true, 'Close event was sent');
ok(!viewer._updateRequestId, 'timer is off');
setTimeout(function() {
ok(!viewer._updateRequestId, 'timer is still off');
ok(!viewer._updateRequestId, 'timer is off');
start();
}, 100);
};

View File

@ -5,7 +5,12 @@
module('Controls', {
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();
@ -324,6 +329,7 @@
],
springStiffness: 100, // Faster animation = faster tests
showSequenceControl: true,
sequenceMode: true,
navPrevNextWrap: false
});
viewer.addHandler('open', openHandler);
@ -375,6 +381,7 @@
],
springStiffness: 100, // Faster animation = faster tests
showSequenceControl: true,
sequenceMode: true,
navPrevNextWrap: true
});
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 */
QUnit.config.autostart = false;
/* global QUnit, module, Util, $, console, test, asyncTest, start, ok, equal, propEqual */
(function () {
var debug = false,
@ -31,10 +29,6 @@ QUnit.config.autostart = false;
}
});
$(document).ready(function () {
start();
});
var resetTestVariables = function () {
if (viewer) {
viewer.close();
@ -146,7 +140,7 @@ QUnit.config.autostart = false;
currentDisplayRegionLeft = displayRegion.position().left;
currentDisplayWidth = displayRegion.width();
viewerAndNavigatorDisplayReady = viewer.drawer !== null &&
!viewer.drawer.needsUpdate() &&
!viewer.world.needsDraw() &&
currentDisplayWidth > 0 &&
Util.equalsWithVariance(lastDisplayRegionLeft, currentDisplayRegionLeft, 0.0001) &&
Util.equalsWithVariance(lastDisplayWidth, currentDisplayWidth, 0.0001) &&
@ -166,7 +160,7 @@ QUnit.config.autostart = false;
else {
if (count === 40) {
console.log("waitForViewer:" +
viewer.drawer + ":" + viewer.drawer.needsUpdate() + ":" +
viewer.drawer + ":" + viewer.world.needsDraw() + ":" +
viewerAndNavigatorDisplayReady + ":" +
lastDisplayRegionLeft + ":" + currentDisplayRegionLeft + ":" +
lastDisplayWidth + ":" + currentDisplayWidth + ":" +
@ -765,11 +759,11 @@ QUnit.config.autostart = false;
var openHandler1 = function(event) {
viewer.removeHandler('open', openHandler1);
ok(viewer.navigator, 'navigator exists');
viewer.navigator.addHandler('open', navOpenHandler1);
viewer.navigator.world.addHandler('add-item', navOpenHandler1);
};
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');
ok(viewer.navigator._updateRequestId, 'navigator timer is on');
viewer.addHandler('close', closeHandler1);
@ -785,11 +779,11 @@ QUnit.config.autostart = false;
var openHandler2 = function(event) {
viewer.removeHandler('open', openHandler2);
viewer.navigator.addHandler('open', navOpenHandler2);
viewer.navigator.world.addHandler('add-item', navOpenHandler2);
};
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');
viewer.addHandler('close', closeHandler2);
viewer.close();
@ -797,13 +791,56 @@ QUnit.config.autostart = false;
var closeHandler2 = function(event) {
viewer.removeHandler('close', closeHandler2);
ok(!viewer.navigator._updateRequestId, 'navigator timer is off');
setTimeout(function() {
ok(!viewer.navigator._updateRequestId, 'navigator timer is still off');
ok(!viewer.navigator._updateRequestId, 'navigator timer is off');
timeWatcher.done();
}, 100);
};
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() {
var viewer;
@ -29,7 +29,7 @@
}
var ready = viewer.isOpen() &&
viewer.drawer !== null &&
!viewer.drawer.needsUpdate() &&
!viewer.world.needsDraw() &&
Util.equalsWithVariance( viewer.viewport.getBounds( true ).x,
viewer.viewport.getBounds().x, 0.000 ) &&
Util.equalsWithVariance( viewer.viewport.getBounds( true ).y,
@ -46,7 +46,7 @@
}, 100 );
} else {
console.log( "waitForViewer:" + viewer.isOpen( ) + ":" + viewer.drawer +
":" + viewer.drawer.needsUpdate() );
":" + viewer.world.needsDraw() );
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 () {
var viewer;
var VIEWER_ID = "example";
var PREFIX_URL = "/build/openseadragon/images";
var PREFIX_URL = "/build/openseadragon/images/";
var SPRING_STIFFNESS = 100; // Faster animation = faster tests
module("viewport", {
@ -209,7 +209,7 @@
method: 'getHomeBounds',
processExpected: function(level, expected) {
// 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);
} else {
var sideLength = 1.0 / viewer.defaultZoomLevel; // it's a square in this case
@ -245,8 +245,10 @@
viewport.zoomTo(ZOOM_FACTOR, null, 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.
if(level === 0){
// Special cases for oddball levels
if (level === -1) {
expected = 0.25;
} else if(level === 0){
expected = 1;
}
@ -285,11 +287,11 @@
for(var i = 0; i < testRects.length; i++){
var rect = testRects[i].times(viewport.getContainerSize());
viewport.resetContentSize(rect);
viewport.resetContentSize(rect.getSize());
viewport.update();
propEqual(
viewport.contentSize,
rect,
rect.getSize(),
"Reset content size correctly."
);
}
@ -407,7 +409,7 @@
viewport.update();
propEqual(
viewport.getBounds(),
new OpenSeadragon.Rect(-1.5,0,4,4),
new OpenSeadragon.Rect(0, 1.5, 1, 1),
"Viewport fit a tall image horizontally."
);
start();
@ -424,7 +426,7 @@
viewport.update();
propEqual(
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."
);
start();
@ -719,9 +721,7 @@
return el.times(window_boundary);
},
getExpected: function(orig, viewport) {
var position, pos_point;
position = viewer.element.getBoundingClientRect();
pos_point = new OpenSeadragon.Point(position.top, position.left);
var pos_point = OpenSeadragon.getElementOffset(viewer.element);
return orig.minus(pos_point).divide(viewport.getContainerSize().x * ZOOM_FACTOR).plus(VIEWER_PADDING);
},
method: 'windowToViewportCoordinates'
@ -735,9 +735,7 @@
return el.times(viewer.source.dimensions.x);
},
getExpected: function(orig, viewport) {
var position, pos_point;
position = viewer.element.getBoundingClientRect();
pos_point = new OpenSeadragon.Point(position.top, position.left);
var pos_point = OpenSeadragon.getElementOffset(viewer.element);
return orig.plus(pos_point).minus(VIEWER_PADDING.times(viewport.getContainerSize().x * ZOOM_FACTOR));
},
method: 'imageToWindowCoordinates'
@ -752,9 +750,7 @@
return el.times(window_boundary);
},
getExpected: function(orig, viewport) {
var position, pos_point;
position = viewer.element.getBoundingClientRect();
pos_point = new OpenSeadragon.Point(position.top, position.left);
var pos_point = OpenSeadragon.getElementOffset(viewer.element);
return orig.minus(pos_point).divide(viewport.getContainerSize().x * ZOOM_FACTOR).plus(VIEWER_PADDING);
},
method: 'windowToViewportCoordinates'
@ -768,9 +764,7 @@
return el.times(viewer.source.dimensions.x);
},
getExpected: function(orig, viewport) {
var position, pos_point;
position = viewer.element.getBoundingClientRect();
pos_point = new OpenSeadragon.Point(position.top, position.left);
var pos_point = OpenSeadragon.getElementOffset(viewer.element);
return orig.minus(VIEWER_PADDING).times(viewport.getContainerSize().x * ZOOM_FACTOR).plus(pos_point);
},
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>
<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/test.css">
<link rel="stylesheet" href="/test/helpers/test.css">
</head>
<body>
<div id="qunit"></div>
@ -14,23 +14,31 @@
<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>
<script src="/build/openseadragon/openseadragon.min.js"></script>
<script src="/test/legacy.mouse.shim.js"></script>
<script src="/test/test.js"></script>
<script src="/build/openseadragon/openseadragon.js"></script>
<script src="/test/helpers/legacy.mouse.shim.js"></script>
<script src="/test/helpers/test.js"></script>
<!-- Polyfill must be inserted first because it is testing functions
reassignments which could be done by other test. -->
<script src="/test/polyfills.js"></script>
<script src="/test/basic.js"></script>
<script src="/test/navigator.js"></script>
<script src="/test/strings.js"></script>
<script src="/test/formats.js"></script>
<script src="/test/utils.js"></script>
<script src="/test/events.js"></script>
<script src="/test/units.js"></script>
<script src="/test/layers.js"></script>
<script src="/test/overlays.js"></script>
<script src="/test/controls.js"></script>
<script src="/test/viewport.js"></script>
<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>