Merge remote-tracking branch 'refs/remotes/openseadragon/master'

This commit is contained in:
Peter 2017-07-26 10:39:20 -04:00
commit d8f761f509
72 changed files with 1678 additions and 300 deletions

View File

@ -8,10 +8,6 @@
"off", "off",
4 4
], ],
"linebreak-style": [
"error",
"unix"
],
"quotes": [ "quotes": [
"off", "off",
"double" "double"

View File

@ -75,3 +75,12 @@ You can also get a report of the tests' code coverage:
The report shows up at `coverage/html/index.html` viewable in a browser. The report shows up at `coverage/html/index.html` viewable in a browser.
### Installing from forked Github repo/branch
This project is now compatible with direct installation of forked Github repos/branches via npm/yarn (possible because of the new [prepare](https://docs.npmjs.com/misc/scripts) command). This enables quick testing of a bugfix or feature addition via a forked repo. In order to do this:
1. Install the Grunt command line runner (if you haven't already); on the command line, run `npm install -g grunt-cli` (or `yarn global add grunt-cli`)
1. Remove any currently installed openseadragon package via `npm uninstall openseadragon` or `yarn remove openseadragon`
1. Add the specific forked repo/branch by running `npm install git://github.com/username/openseadragon.git#branch-name` or `yarn add git://github.com/username/openseadragon.git#branch-name`. Make sure to replace username and branch-name with proper targets.
During installation, the package should be correctly built via grunt and can then be used via `import Openseadragon from 'openseadragon'` or `var Openseadragon = require('openseadragon')` statements as if the official package were installed.

View File

@ -194,8 +194,12 @@ module.exports = function(grunt) {
target: sources target: sources
}, },
"git-describe": { "git-describe": {
"options": {
failOnError: false
},
build: {} build: {}
} },
gitInfo: "unknown"
}); });
// ---------- // ----------

View File

@ -1,32 +1,65 @@
OPENSEADRAGON CHANGELOG OPENSEADRAGON CHANGELOG
======================= =======================
2.2.2: (in progress) 2.3.1: (In Progress)
2.3.0:
* BREAKING CHANGE: Tile.distance has been removed (#1027) * BREAKING CHANGE: Tile.distance has been removed (#1027)
* BREAKING CHANGE: Viewer's canvas-click event is now fired before it initiates the zoom (#1148)
* BREAKING CHANGE: Viewer's canvas-drag event is now fired before it pans (#1149)
* Added Zoomify tile source (#863)
* You can now set the rotation of individual tiled images (#1006) * You can now set the rotation of individual tiled images (#1006)
* Fixed CORS bug in IE 10 (#967) * Added getFullyLoaded method and "fully-loaded-change" event to TiledImage to know when tiles are fully loaded (#837, #1073)
* You can now preload images without drawing them to the screen (#1071)
* Added support for commonjs (#984) * Added support for commonjs (#984)
* Added an option to addTiledImage to change the crossOriginPolicy (#981) * Added an option to addTiledImage to change the crossOriginPolicy (#981)
* Fixed issue with tiles not appearing with wrapHorizontal/wrapVertical if you pan too far away from the origin (#987, #1066) * You can now load tiles via AJAX and custom AJAX request headers (#1055)
* The Viewer's tileSources option is now smarter about detecting JSON vs XML vs URL (#999) * Added ability to provide thumbnail URLs for reference strip (#1241)
* Improved panning constraints for constrainDuringPan (#1133 and #1245)
* You can now prevent canvas-click events from zooming on a per-event basis (#1148)
* You can now prevent canvas-drag events from panning on a per-event basis (#1149)
* The navigationControlAnchor option now works for custom toolbar as well (#1004) * The navigationControlAnchor option now works for custom toolbar as well (#1004)
* Added getFullyLoaded method and "fully-loaded-change" event to TiledImage to know when tiles are fully loaded (#837, #1073)
* Fixed: Initial tile load wasn't happening in parallel (#1014)
* Added Zoomify tile source (#863)
* Fixed problem with "sparse image" DZI files (#995)
* Optimization: Use the squared distance when comparing tiles (#1027)
* Fix IndexSizeError on IE and Edge that occurred under certain circumstances (e.g. multi-image with transparency) (#1035)
* ImageTileSource now works in IE8 (#1041)
* LegacyTileSource now allows any image URLs regardless of type (#1056) * LegacyTileSource now allows any image URLs regardless of type (#1056)
* Fixed error in IE8 when zooming in (due to edge smoothing) (#1064) * Enabled configuration of ImageLoader timeout (#1192)
* Viewer.open() now supports an initialPage argument for sequenceMode (#1196)
* New events for opacity and compositeOperation changes (#1203)
* Added support for setting debug mode after the Viewer object has been constructed (#1224)
* Added functions for dynamically adding and removing the reference strip in sequence mode (#1213)
* Better calculation for TileCache release cutoff (#1214)
* The navigator now picks up opacity and compositeOperation changes (#1203)
* Improved calculation for determining which level to load first (#1198)
* Added fix for supporting weird filenames that look like JSONs (#1189)
* Improved DziTileSource guessing of tilesUrl (#1074) * Improved DziTileSource guessing of tilesUrl (#1074)
* The Viewer's tileSources option is now smarter about detecting JSON vs XML vs URL (#999)
* Better compression for our UI images (#1134)
* Optimization: Use the squared distance when comparing tiles (#1027)
* Now clamping pixel ratio density to a minimum of 1, fixing display issues on low density devices (#1200)
* More forgiving check for DZI schema (#1249)
* ImageTileSource now works in IE8 (#1041)
* Fixed CORS bug in IE 10 (#967)
* Fixed issue with tiles not appearing with wrapHorizontal/wrapVertical if you pan too far away from the origin (#987, #1066)
* Fixed: Initial tile load wasn't happening in parallel (#1014)
* Fixed problem with "sparse image" DZI files (#995)
* Fix IndexSizeError on IE and Edge that occurred under certain circumstances (e.g. multi-image with transparency) (#1035)
* Fixed error in IE8 when zooming in (due to edge smoothing) (#1064)
* Fixed issue with OpenSeadragon.version in the minified JavaScript (#1099) * Fixed issue with OpenSeadragon.version in the minified JavaScript (#1099)
* Fixed smoothTileEdgesMinZoom performance degradation on single-tile images (#1101) * Fixed smoothTileEdgesMinZoom performance degradation on single-tile images (#1101)
* Fixed issue with tiles not appearing after rotation (#1102) * Fixed issue with tiles not appearing after rotation (#1102)
* Fixed: The navigator wasn't respecting the constrainDuringPan setting (#1104) * Fixed: The navigator wasn't respecting the constrainDuringPan setting (#1104)
* Fixed an issue causing overlays to be mis-positioned in some circumstances (#1119) * Fixed an issue causing overlays to be mis-positioned in some circumstances (#1119)
* Fixed: ImageTileSource would sometimes produce a double image (#1123) * Fixed: ImageTileSource would sometimes produce a double image (#1123)
* Fixed: console.debug caused exceptions on IE10 (#1129)
* Fixed: the reference strip would leak memory when opening new sets of images (#1175)
* Fixed: zoomTo/zoomBy ignore refPoint if immediately is true (#1184)
* Fixed: IIPImageServer didn't work with the latest OSD release (#1199)
* Fixed: setItemIndex method not working with navigator inside "open" event (#1201)
* Fixed: The reference strip didn't show the initial page if it wasn't the first page (#1208)
* Fixed: Sometimes the image would stick to the mouse when right-clicking and left-clicking simultaneously (#1223)
* Fixed issue with transparent images sometimes disappearing on Safari (#1222)
* Fixed: One image failing to load could cause the others to never load (#1229)
* Fixed: Mouse up outside map will cause "canvas-drag" event to stick (#1133)
* Fixed more issues with tracking multiple pointers (#1244)
2.2.1: 2.2.1:

BIN
images/button_grouphover.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
images/button_hover.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
images/button_pressed.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
images/button_rest.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 977 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,6 +1,6 @@
{ {
"name": "openseadragon", "name": "openseadragon",
"version": "2.2.1", "version": "2.3.0",
"description": "Provides a smooth, zoomable user interface for HTML/Javascript.", "description": "Provides a smooth, zoomable user interface for HTML/Javascript.",
"keywords": ["image", "zoom", "pan", "openseadragon", "seadragon", "deepzoom", "dzi", "iiif", "osm", "tms"], "keywords": ["image", "zoom", "pan", "openseadragon", "seadragon", "deepzoom", "dzi", "iiif", "osm", "tms"],
"homepage": "http://openseadragon.github.io/", "homepage": "http://openseadragon.github.io/",
@ -17,7 +17,7 @@
"devDependencies": { "devDependencies": {
"grunt": "^0.4.5", "grunt": "^0.4.5",
"grunt-contrib-clean": "^0.7.0", "grunt-contrib-clean": "^0.7.0",
"grunt-contrib-compress": "^0.13.0", "grunt-contrib-compress": "^1.4.3",
"grunt-contrib-concat": "^1.0.1", "grunt-contrib-concat": "^1.0.1",
"grunt-contrib-connect": "^0.11.2", "grunt-contrib-connect": "^0.11.2",
"grunt-contrib-uglify": "^2.0.0", "grunt-contrib-uglify": "^2.0.0",
@ -30,6 +30,6 @@
}, },
"scripts": { "scripts": {
"test": "grunt test", "test": "grunt test",
"prepublish": "grunt build" "prepare": "grunt build"
} }
} }

BIN
psd/button.psd Executable file

Binary file not shown.

View File

@ -426,22 +426,22 @@ $.Drawer.prototype = {
this.context.globalCompositeOperation = compositeOperation; this.context.globalCompositeOperation = compositeOperation;
} }
if (bounds) { if (bounds) {
// Internet Explorer and Microsoft Edge throw IndexSizeError // Internet Explorer, Microsoft Edge, and Safari have problems
// when you call context.drawImage with negative x or y // when you call context.drawImage with negative x or y
// or width or height greater than the canvas width or height respectively // or x + width or y + height greater than the canvas width or height respectively.
if (bounds.x < 0) { if (bounds.x < 0) {
bounds.width += bounds.x; bounds.width += bounds.x;
bounds.x = 0; bounds.x = 0;
} }
if (bounds.width > this.canvas.width) { if (bounds.x + bounds.width > this.canvas.width) {
bounds.width = this.canvas.width; bounds.width = this.canvas.width - bounds.x;
} }
if (bounds.y < 0) { if (bounds.y < 0) {
bounds.height += bounds.y; bounds.height += bounds.y;
bounds.y = 0; bounds.y = 0;
} }
if (bounds.height > this.canvas.height) { if (bounds.y + bounds.height > this.canvas.height) {
bounds.height = this.canvas.height; bounds.height = this.canvas.height - bounds.y;
} }
this.context.drawImage( this.context.drawImage(

View File

@ -113,8 +113,10 @@ $.extend( $.DziTileSource.prototype, $.TileSource.prototype, /** @lends OpenSead
} }
} }
return ( "http://schemas.microsoft.com/deepzoom/2008" == ns || ns = (ns || '').toLowerCase();
"http://schemas.microsoft.com/deepzoom/2009" == ns );
return (ns.indexOf('schemas.microsoft.com/deepzoom/2008') !== -1 ||
ns.indexOf('schemas.microsoft.com/deepzoom/2009') !== -1);
}, },
/** /**
@ -140,7 +142,7 @@ $.extend( $.DziTileSource.prototype, $.TileSource.prototype, /** @lends OpenSead
if (url && !options.tilesUrl) { if (url && !options.tilesUrl) {
options.tilesUrl = url.replace( options.tilesUrl = url.replace(
/([^\/]+?)(\.(dzi|xml|js))?(\?[^\/]*)?\/?$/, '$1_files/'); /([^\/]+?)(\.(dzi|xml|js)?(\?[^\/]*)?)?\/?$/, '$1_files/');
if (url.search(/\.(dzi|xml|js)\?/) != -1) { if (url.search(/\.(dzi|xml|js)\?/) != -1) {
options.queryParams = url.match(/\?.*/); options.queryParams = url.match(/\?.*/);

View File

@ -34,7 +34,19 @@
(function($){ (function($){
// private class /**
* @private
* @class ImageJob
* @classdesc Handles downloading of a single image.
* @param {Object} options - Options for this ImageJob.
* @param {String} [options.src] - URL of image to download.
* @param {String} [options.loadWithAjax] - Whether to load this image with AJAX.
* @param {String} [options.ajaxHeaders] - Headers to add to the image request if using AJAX.
* @param {String} [options.crossOriginPolicy] - CORS policy to use for downloads
* @param {Function} [options.callback] - Called once image has been downloaded.
* @param {Function} [options.abort] - Called when this image job is aborted.
* @param {Number} [options.timeout] - The max number of milliseconds that this image job may take to complete.
*/
function ImageJob (options) { function ImageJob (options) {
$.extend(true, this, { $.extend(true, this, {
@ -52,29 +64,90 @@ function ImageJob ( options ) {
ImageJob.prototype = { ImageJob.prototype = {
errorMsg: null, errorMsg: null,
/**
* Starts the image job.
* @method
*/
start: function(){ start: function(){
var _this = this; var self = this;
var selfAbort = this.abort;
this.image = new Image(); this.image = new Image();
this.image.onload = function(){
self.finish(true);
};
this.image.onabort = this.image.onerror = function() {
self.errorMsg = "Image load aborted";
self.finish(false);
};
this.jobId = window.setTimeout(function(){
self.errorMsg = "Image load exceeded timeout";
self.finish(false);
}, this.timeout);
// Load the tile with an AJAX request if the loadWithAjax option is
// set. Otherwise load the image by setting the source proprety of the image object.
if (this.loadWithAjax) {
this.request = $.makeAjaxRequest({
url: this.src,
withCredentials: this.ajaxWithCredentials,
headers: this.ajaxHeaders,
responseType: "arraybuffer",
success: function(request) {
var blb;
// Make the raw data into a blob.
// BlobBuilder fallback adapted from
// http://stackoverflow.com/questions/15293694/blob-constructor-browser-compatibility
try {
blb = new window.Blob([request.response]);
} catch (e) {
var BlobBuilder = (
window.BlobBuilder ||
window.WebKitBlobBuilder ||
window.MozBlobBuilder ||
window.MSBlobBuilder
);
if (e.name === 'TypeError' && BlobBuilder) {
var bb = new BlobBuilder();
bb.append(request.response);
blb = bb.getBlob();
}
}
// If the blob is empty for some reason consider the image load a failure.
if (blb.size === 0) {
self.errorMsg = "Empty image response.";
self.finish(false);
}
// Create a URL for the blob data and make it the source of the image object.
// This will still trigger Image.onload to indicate a successful tile load.
var url = (window.URL || window.webkitURL).createObjectURL(blb);
self.image.src = url;
},
error: function(request) {
self.errorMsg = "Image load aborted - XHR error";
self.finish(false);
}
});
// Provide a function to properly abort the request.
this.abort = function() {
self.request.abort();
// Call the existing abort function if available
if (typeof selfAbort === "function") {
selfAbort();
}
};
} else {
if (this.crossOriginPolicy !== false) { if (this.crossOriginPolicy !== false) {
this.image.crossOrigin = this.crossOriginPolicy; this.image.crossOrigin = this.crossOriginPolicy;
} }
this.image.onload = function(){
_this.finish( true );
};
this.image.onabort = this.image.onerror = function(){
_this.errorMsg = "Image load aborted";
_this.finish( false );
};
this.jobId = window.setTimeout( function(){
_this.errorMsg = "Image load exceeded timeout";
_this.finish( false );
}, this.timeout);
this.image.src = this.src; this.image.src = this.src;
}
}, },
finish: function(successful) { finish: function(successful) {
@ -99,11 +172,13 @@ ImageJob.prototype = {
* You generally won't have to interact with the ImageLoader directly. * You generally won't have to interact with the ImageLoader directly.
* @param {Object} options - Options for this ImageLoader. * @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. * @param {Number} [options.jobLimit] - The number of concurrent image requests. See imageLoaderLimit in {@link OpenSeadragon.Options} for details.
* @param {Number} [options.timeout] - The max number of milliseconds that an image job may take to complete.
*/ */
$.ImageLoader = function(options) { $.ImageLoader = function(options) {
$.extend(true, this, { $.extend(true, this, {
jobLimit: $.DEFAULT_SETTINGS.imageLoaderLimit, jobLimit: $.DEFAULT_SETTINGS.imageLoaderLimit,
timeout: $.DEFAULT_SETTINGS.timeout,
jobQueue: [], jobQueue: [],
jobsInProgress: 0 jobsInProgress: 0
}, options); }, options);
@ -116,9 +191,15 @@ $.ImageLoader.prototype = {
/** /**
* Add an unloaded image to the loader queue. * Add an unloaded image to the loader queue.
* @method * @method
* @param {String} src - URL of image to download. * @param {Object} options - Options for this job.
* @param {String} crossOriginPolicy - CORS policy to use for downloads * @param {String} [options.src] - URL of image to download.
* @param {Function} callback - Called once image has been downloaded. * @param {String} [options.loadWithAjax] - Whether to load this image with AJAX.
* @param {String} [options.ajaxHeaders] - Headers to add to the image request if using AJAX.
* @param {String|Boolean} [options.crossOriginPolicy] - CORS policy to use for downloads
* @param {Boolean} [options.ajaxWithCredentials] - Whether to set withCredentials on AJAX
* requests.
* @param {Function} [options.callback] - Called once image has been downloaded.
* @param {Function} [options.abort] - Called when this image job is aborted.
*/ */
addJob: function(options) { addJob: function(options) {
var _this = this, var _this = this,
@ -127,9 +208,13 @@ $.ImageLoader.prototype = {
}, },
jobOptions = { jobOptions = {
src: options.src, src: options.src,
loadWithAjax: options.loadWithAjax,
ajaxHeaders: options.loadWithAjax ? options.ajaxHeaders : null,
crossOriginPolicy: options.crossOriginPolicy, crossOriginPolicy: options.crossOriginPolicy,
ajaxWithCredentials: options.ajaxWithCredentials,
callback: complete, callback: complete,
abort: options.abort abort: options.abort,
timeout: this.timeout
}, },
newJob = new ImageJob(jobOptions); newJob = new ImageJob(jobOptions);
@ -177,7 +262,7 @@ function completeJob( loader, job, callback ) {
loader.jobsInProgress++; loader.jobsInProgress++;
} }
callback( job.image, job.errorMsg ); callback(job.image, job.errorMsg, job.request);
} }
}(OpenSeadragon)); }(OpenSeadragon));

View File

@ -317,6 +317,25 @@
return this; return this;
}, },
/**
* Returns the {@link OpenSeadragon.MouseTracker.GesturePointList|GesturePointList} for all but the given pointer device type.
* @function
* @param {String} type - The pointer device type: "mouse", "touch", "pen", etc.
* @returns {Array.<OpenSeadragon.MouseTracker.GesturePointList>}
*/
getActivePointersListsExceptType: function ( type ) {
var delegate = THIS[ this.hash ];
var listArray = [];
for (var i = 0; i < delegate.activePointersLists.length; ++i) {
if (delegate.activePointersLists[i].type !== type) {
listArray.push(delegate.activePointersLists[i]);
}
}
return listArray;
},
/** /**
* Returns the {@link OpenSeadragon.MouseTracker.GesturePointList|GesturePointList} for the given pointer device type, * Returns the {@link OpenSeadragon.MouseTracker.GesturePointList|GesturePointList} for the given pointer device type,
* creating and caching a new {@link OpenSeadragon.MouseTracker.GesturePointList|GesturePointList} if one doesn't already exist for the type. * creating and caching a new {@link OpenSeadragon.MouseTracker.GesturePointList|GesturePointList} if one doesn't already exist for the type.
@ -862,6 +881,21 @@
blurHandler: function () { } blurHandler: function () { }
}; };
/**
* Resets all active mousetrakers. (Added to patch issue #697 "Mouse up outside map will cause "canvas-drag" event to stick")
*
* @private
* @member resetAllMouseTrackers
* @memberof OpenSeadragon.MouseTracker
*/
$.MouseTracker.resetAllMouseTrackers = function(){
for(var i = 0; i < MOUSETRACKERS.length; i++){
if (MOUSETRACKERS[i].isTracking()){
MOUSETRACKERS[i].setTracking(false);
MOUSETRACKERS[i].setTracking(true);
}
}
};
/** /**
* Provides continuous computation of velocity (speed and direction) of active pointers. * Provides continuous computation of velocity (speed and direction) of active pointers.
@ -1201,6 +1235,32 @@
} }
} }
return null; return null;
},
/**
* Increment this pointer's contact count.
* It will evaluate whether this pointer type is allowed to have multiple contacts.
* @function
*/
addContact: function() {
++this.contacts;
if (this.contacts > 1 && (this.type === "mouse" || this.type === "pen")) {
this.contacts = 1;
}
},
/**
* Decrement this pointer's contact count.
* It will make sure the count does not go below 0.
* @function
*/
removeContact: function() {
--this.contacts;
if (this.contacts < 0) {
this.contacts = 0;
}
} }
}; };
@ -2005,25 +2065,28 @@
* @private * @private
* @inner * @inner
*/ */
function abortTouchContacts( tracker, event, pointsList ) { function abortContacts( tracker, event, pointsList ) {
var i, var i,
gPointCount = pointsList.getLength(), gPointCount = pointsList.getLength(),
abortGPoints = []; abortGPoints = [];
// Check contact count for hoverable pointer types before aborting
if (pointsList.type === 'touch' || pointsList.contacts > 0) {
for ( i = 0; i < gPointCount; i++ ) { for ( i = 0; i < gPointCount; i++ ) {
abortGPoints.push( pointsList.getByIndex( i ) ); abortGPoints.push( pointsList.getByIndex( i ) );
} }
if ( abortGPoints.length > 0 ) { if ( abortGPoints.length > 0 ) {
// simulate touchend // simulate touchend/mouseup
updatePointersUp( tracker, event, abortGPoints, 0 ); // 0 means primary button press/release or touch contact updatePointersUp( tracker, event, abortGPoints, 0 ); // 0 means primary button press/release or touch contact
// release pointer capture // release pointer capture
pointsList.captureCount = 1; pointsList.captureCount = 1;
releasePointer( tracker, 'touch' ); releasePointer( tracker, pointsList.type );
// simulate touchleave // simulate touchleave/mouseout
updatePointersExit( tracker, event, abortGPoints ); updatePointersExit( tracker, event, abortGPoints );
} }
} }
}
/** /**
@ -2043,7 +2106,7 @@
if ( pointsList.getLength() > event.touches.length - touchCount ) { if ( pointsList.getLength() > event.touches.length - touchCount ) {
$.console.warn('Tracked touch contact count doesn\'t match event.touches.length. Removing all tracked touch pointers.'); $.console.warn('Tracked touch contact count doesn\'t match event.touches.length. Removing all tracked touch pointers.');
abortTouchContacts( tracker, event, pointsList ); abortContacts( tracker, event, pointsList );
} }
for ( i = 0; i < touchCount; i++ ) { for ( i = 0; i < touchCount; i++ ) {
@ -2213,7 +2276,7 @@
function onTouchCancel( tracker, event ) { function onTouchCancel( tracker, event ) {
var pointsList = tracker.getActivePointersListByType('touch'); var pointsList = tracker.getActivePointersListByType('touch');
abortTouchContacts( tracker, event, pointsList ); abortContacts( tracker, event, pointsList );
} }
@ -2690,6 +2753,14 @@
} }
} }
// Some pointers may steal control from another pointer without firing the appropriate release events
// e.g. Touching a screen while click-dragging with certain mice.
var otherPointsLists = tracker.getActivePointersListsExceptType(gPoints[ 0 ].type);
for (i = 0; i < otherPointsLists.length; i++) {
//If another pointer has contact, simulate the release
abortContacts(tracker, event, otherPointsLists[i]); // No-op if no active pointer
}
// Only capture and track primary button, pen, and touch contacts // Only capture and track primary button, pen, and touch contacts
if ( buttonChanged !== 0 ) { if ( buttonChanged !== 0 ) {
// Aux Press // Aux Press
@ -2740,7 +2811,7 @@
startTrackingPointer( pointsList, curGPoint ); startTrackingPointer( pointsList, curGPoint );
} }
pointsList.contacts++; pointsList.addContact();
//$.console.log('contacts++ ', pointsList.contacts); //$.console.log('contacts++ ', pointsList.contacts);
if ( tracker.dragHandler || tracker.dragEndHandler || tracker.pinchHandler ) { if ( tracker.dragHandler || tracker.dragEndHandler || tracker.pinchHandler ) {
@ -2879,6 +2950,11 @@
} }
} }
// A primary mouse button may have been released while the non-primary button was down
var otherPointsList = tracker.getActivePointersListByType("mouse");
// Stop tracking the mouse; see https://github.com/openseadragon/openseadragon/pull/1223
abortContacts(tracker, event, otherPointsList); // No-op if no active pointer
return false; return false;
} }
@ -2907,7 +2983,7 @@
if ( wasCaptured ) { if ( wasCaptured ) {
// Pointer was activated in our element but could have been removed in any element since events are captured to our element // Pointer was activated in our element but could have been removed in any element since events are captured to our element
pointsList.contacts--; pointsList.removeContact();
//$.console.log('contacts-- ', pointsList.contacts); //$.console.log('contacts-- ', pointsList.contacts);
if ( tracker.dragHandler || tracker.dragEndHandler || tracker.pinchHandler ) { if ( tracker.dragHandler || tracker.dragEndHandler || tracker.pinchHandler ) {
@ -3267,10 +3343,12 @@
} }
} }
// True if inside an iframe, otherwise false. /**
// @member {Boolean} isInIframe * True if inside an iframe, otherwise false.
// @private * @member {Boolean} isInIframe
// @inner * @private
* @inner
*/
var isInIframe = (function() { var isInIframe = (function() {
try { try {
return window.self !== window.top; return window.self !== window.top;
@ -3279,10 +3357,12 @@
} }
})(); })();
// @function /**
// @private * @function
// @inner * @private
// @returns {Boolean} True if the target has access rights to events, otherwise false. * @inner
* @returns {Boolean} True if the target has access rights to events, otherwise false.
*/
function canAccessEvents (target) { function canAccessEvents (target) {
try { try {
return target.addEventListener && target.removeEventListener; return target.addEventListener && target.removeEventListener;

View File

@ -231,8 +231,10 @@ $.Navigator = function( options ){
}); });
viewer.world.addHandler("item-index-change", function(event) { viewer.world.addHandler("item-index-change", function(event) {
window.setTimeout(function(){
var item = _this.world.getItemAt(event.previousIndex); var item = _this.world.getItemAt(event.previousIndex);
_this.world.setItemIndex(item, event.newIndex); _this.world.setItemIndex(item, event.newIndex);
}, 1);
}); });
viewer.world.addHandler("remove-item", function(event) { viewer.world.addHandler("remove-item", function(event) {
@ -345,8 +347,18 @@ $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /*
_this._matchBounds(myItem, original); _this._matchBounds(myItem, original);
} }
function matchOpacity() {
_this._matchOpacity(myItem, original);
}
function matchCompositeOperation() {
_this._matchCompositeOperation(myItem, original);
}
original.addHandler('bounds-change', matchBounds); original.addHandler('bounds-change', matchBounds);
original.addHandler('clip-change', matchBounds); original.addHandler('clip-change', matchBounds);
original.addHandler('opacity-change', matchOpacity);
original.addHandler('composite-operation-change', matchCompositeOperation);
} }
}); });
@ -374,6 +386,16 @@ $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /*
myItem.setWidth(bounds.width, immediately); myItem.setWidth(bounds.width, immediately);
myItem.setRotation(theirItem.getRotation(), immediately); myItem.setRotation(theirItem.getRotation(), immediately);
myItem.setClip(theirItem.getClip()); myItem.setClip(theirItem.getClip());
},
// private
_matchOpacity: function(myItem, theirItem) {
myItem.setOpacity(theirItem.opacity);
},
// private
_matchCompositeOperation: function(myItem, theirItem) {
myItem.setCompositeOperation(theirItem.compositeOperation);
} }
}); });

View File

@ -185,7 +185,11 @@
* If 0, adjusts to fit viewer. * If 0, adjusts to fit viewer.
* *
* @property {Number} [opacity=1] * @property {Number} [opacity=1]
* Default opacity of the tiled images (1=opaque, 0=transparent) * Default proportional opacity of the tiled images (1=opaque, 0=hidden)
* Hidden images do not draw and only load when preloading is allowed.
*
* @property {Boolean} [preload=false]
* Default switch for loading hidden images (true loads, false blocks)
* *
* @property {String} [compositeOperation=null] * @property {String} [compositeOperation=null]
* Valid values are 'source-over', 'source-atop', 'source-in', 'source-out', * Valid values are 'source-over', 'source-atop', 'source-in', 'source-out',
@ -411,6 +415,7 @@
* The max number of images we should keep in memory (per drawer). * The max number of images we should keep in memory (per drawer).
* *
* @property {Number} [timeout=30000] * @property {Number} [timeout=30000]
* The max number of milliseconds that an image job may take to complete.
* *
* @property {Boolean} [useCanvas=true] * @property {Boolean} [useCanvas=true]
* Set to false to not use an HTML canvas element for image rendering even if canvas is supported. * Set to false to not use an HTML canvas element for image rendering even if canvas is supported.
@ -584,9 +589,16 @@
* not use CORS, and the canvas will be tainted. * not use CORS, and the canvas will be tainted.
* *
* @property {Boolean} [ajaxWithCredentials=false] * @property {Boolean} [ajaxWithCredentials=false]
* Whether to set the withCredentials XHR flag for AJAX requests (when loading tile sources). * Whether to set the withCredentials XHR flag for AJAX requests.
* Note that this can be overridden at the {@link OpenSeadragon.TileSource} level. * Note that this can be overridden at the {@link OpenSeadragon.TileSource} level.
* *
* @property {Boolean} [loadTilesWithAjax=false]
* Whether to load tile data using AJAX requests.
* Note that this can be overridden at the {@link OpenSeadragon.TileSource} level.
*
* @property {Object} [ajaxHeaders={}]
* A set of headers to include when making AJAX requests for tile sources or tiles.
*
*/ */
/** /**
@ -678,13 +690,6 @@
*/ */
/**
* This function serves as a single point of instantiation for an {@link OpenSeadragon.Viewer}, including all
* combinations of out-of-the-box configurable features.
*
* @param {OpenSeadragon.Options} options - Viewer options.
* @returns {OpenSeadragon.Viewer}
*/
function OpenSeadragon( options ){ function OpenSeadragon( options ){
return new OpenSeadragon.Viewer( options ); return new OpenSeadragon.Viewer( options );
} }
@ -867,7 +872,8 @@ function OpenSeadragon( options ){
}; };
/** /**
* 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. * A ratio comparing the device screen's pixel density to the canvas's backing store pixel density,
* clamped to a minimum of 1. Defaults to 1 if canvas isn't supported by the browser.
* @member {Number} pixelDensityRatio * @member {Number} pixelDensityRatio
* @memberof OpenSeadragon * @memberof OpenSeadragon
*/ */
@ -880,7 +886,7 @@ function OpenSeadragon( options ){
context.msBackingStorePixelRatio || context.msBackingStorePixelRatio ||
context.oBackingStorePixelRatio || context.oBackingStorePixelRatio ||
context.backingStorePixelRatio || 1; context.backingStorePixelRatio || 1;
return devicePixelRatio / backingStoreRatio; return Math.max(devicePixelRatio, 1) / backingStoreRatio;
} else { } else {
return 1; return 1;
} }
@ -1005,6 +1011,8 @@ function OpenSeadragon( options ){
initialPage: 0, initialPage: 0,
crossOriginPolicy: false, crossOriginPolicy: false,
ajaxWithCredentials: false, ajaxWithCredentials: false,
loadTilesWithAjax: false,
ajaxHeaders: {},
//PAN AND ZOOM SETTINGS AND CONSTRAINTS //PAN AND ZOOM SETTINGS AND CONSTRAINTS
panHorizontal: true, panHorizontal: true,
@ -1117,6 +1125,7 @@ function OpenSeadragon( options ){
// APPEARANCE // APPEARANCE
opacity: 1, opacity: 1,
preload: false,
compositeOperation: null, compositeOperation: null,
placeholderFillStyle: null, placeholderFillStyle: null,
@ -2120,11 +2129,16 @@ function OpenSeadragon( options ){
* @param {String} options.url - the url to request * @param {String} options.url - the url to request
* @param {Function} options.success - a function to call on a successful response * @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 {Function} options.error - a function to call on when an error occurs
* @param {Object} options.headers - headers to add to the AJAX request
* @param {String} options.responseType - the response type of the the AJAX request
* @param {Boolean} [options.withCredentials=false] - whether to set the XHR's withCredentials * @param {Boolean} [options.withCredentials=false] - whether to set the XHR's withCredentials
* @throws {Error} * @throws {Error}
* @returns {XMLHttpRequest}
*/ */
makeAjaxRequest: function( url, onSuccess, onError ) { makeAjaxRequest: function( url, onSuccess, onError ) {
var withCredentials; var withCredentials;
var headers;
var responseType;
// Note that our preferred API is that you pass in a single object; the named // Note that our preferred API is that you pass in a single object; the named
// arguments are for legacy support. // arguments are for legacy support.
@ -2132,6 +2146,8 @@ function OpenSeadragon( options ){
onSuccess = url.success; onSuccess = url.success;
onError = url.error; onError = url.error;
withCredentials = url.withCredentials; withCredentials = url.withCredentials;
headers = url.headers;
responseType = url.responseType || null;
url = url.url; url = url.url;
} }
@ -2147,9 +2163,9 @@ function OpenSeadragon( options ){
if ( request.readyState == 4 ) { if ( request.readyState == 4 ) {
request.onreadystatechange = function(){}; request.onreadystatechange = function(){};
// With protocols other than http/https, the status is 200 // With protocols other than http/https, a successful request status is in
// on Firefox and 0 on other browsers // the 200's on Firefox and 0 on other browsers
if ( request.status === 200 || if ( (request.status >= 200 && request.status < 300) ||
( request.status === 0 && ( request.status === 0 &&
protocol !== "http:" && protocol !== "http:" &&
protocol !== "https:" )) { protocol !== "https:" )) {
@ -2167,6 +2183,18 @@ function OpenSeadragon( options ){
try { try {
request.open( "GET", url, true ); request.open( "GET", url, true );
if (responseType) {
request.responseType = responseType;
}
if (headers) {
for (var headerName in headers) {
if (headers.hasOwnProperty(headerName) && headers[headerName]) {
request.setRequestHeader(headerName, headers[headerName]);
}
}
}
if (withCredentials) { if (withCredentials) {
request.withCredentials = true; request.withCredentials = true;
} }
@ -2231,6 +2259,8 @@ function OpenSeadragon( options ){
} }
} }
} }
return request;
}, },
/** /**

View File

@ -178,6 +178,7 @@ $.ReferenceStrip = function ( options ) {
this.panelWidth = ( viewerSize.x * this.sizeRatio ) + 8; this.panelWidth = ( viewerSize.x * this.sizeRatio ) + 8;
this.panelHeight = ( viewerSize.y * this.sizeRatio ) + 8; this.panelHeight = ( viewerSize.y * this.sizeRatio ) + 8;
this.panels = []; this.panels = [];
this.miniViewers = {};
/*jshint loopfunc:true*/ /*jshint loopfunc:true*/
for ( i = 0; i < viewer.tileSources.length; i++ ) { for ( i = 0; i < viewer.tileSources.length; i++ ) {
@ -293,6 +294,12 @@ $.extend( $.ReferenceStrip.prototype, $.EventSource.prototype, $.Viewer.prototyp
// Overrides Viewer.destroy // Overrides Viewer.destroy
destroy: function() { destroy: function() {
if (this.miniViewers) {
for (var key in this.miniViewers) {
this.miniViewers[key].destroy();
}
}
if (this.element) { if (this.element) {
this.element.parentNode.removeChild(this.element); this.element.parentNode.removeChild(this.element);
} }
@ -421,9 +428,19 @@ function loadPanels( strip, viewerSize, scroll ) {
for ( i = activePanelsStart; i < activePanelsEnd && i < strip.panels.length; i++ ) { for ( i = activePanelsStart; i < activePanelsEnd && i < strip.panels.length; i++ ) {
element = strip.panels[i]; element = strip.panels[i];
if ( !element.activePanel ) { if ( !element.activePanel ) {
var miniTileSource;
var originalTileSource = strip.viewer.tileSources[i];
if (originalTileSource.referenceStripThumbnailUrl) {
miniTileSource = {
type: 'image',
url: originalTileSource.referenceStripThumbnailUrl
};
} else {
miniTileSource = originalTileSource;
}
miniViewer = new $.Viewer( { miniViewer = new $.Viewer( {
id: element.id, id: element.id,
tileSources: [strip.viewer.tileSources[i]], tileSources: [miniTileSource],
element: element, element: element,
navigatorSizeRatio: strip.sizeRatio, navigatorSizeRatio: strip.sizeRatio,
showNavigator: false, showNavigator: false,
@ -463,6 +480,8 @@ function loadPanels( strip, viewerSize, scroll ) {
miniViewer.displayRegion miniViewer.displayRegion
); );
strip.miniViewers[element.id] = miniViewer;
element.activePanel = true; element.activePanel = true;
} }
} }

View File

@ -47,8 +47,10 @@
* @param {String} url The URL of this tile's image. * @param {String} url The URL of this tile's image.
* @param {CanvasRenderingContext2D} context2D The context2D of this tile if it * @param {CanvasRenderingContext2D} context2D The context2D of this tile if it
* is provided directly by the tile source. * is provided directly by the tile source.
* @param {Boolean} loadWithAjax Whether this tile image should be loaded with an AJAX request .
* @param {Object} ajaxHeaders The headers to send with this tile's AJAX request (if applicable).
*/ */
$.Tile = function(level, x, y, bounds, exists, url, context2D) { $.Tile = function(level, x, y, bounds, exists, url, context2D, loadWithAjax, ajaxHeaders) {
/** /**
* The zoom level this tile belongs to. * The zoom level this tile belongs to.
* @member {Number} level * @member {Number} level
@ -91,6 +93,29 @@ $.Tile = function(level, x, y, bounds, exists, url, context2D) {
* @memberOf OpenSeadragon.Tile# * @memberOf OpenSeadragon.Tile#
*/ */
this.context2D = context2D; this.context2D = context2D;
/**
* Whether to load this tile's image with an AJAX request.
* @member {Boolean} loadWithAjax
* @memberof OpenSeadragon.Tile#
*/
this.loadWithAjax = loadWithAjax;
/**
* The headers to be used in requesting this tile's image.
* Only used if loadWithAjax is set to true.
* @member {Object} ajaxHeaders
* @memberof OpenSeadragon.Tile#
*/
this.ajaxHeaders = ajaxHeaders;
/**
* The unique cache key for this tile.
* @member {String} cacheKey
* @memberof OpenSeadragon.Tile#
*/
if (this.ajaxHeaders) {
this.cacheKey = this.url + "+" + JSON.stringify(this.ajaxHeaders);
} else {
this.cacheKey = this.url;
}
/** /**
* Is this tile loaded? * Is this tile loaded?
* @member {Boolean} loaded * @member {Boolean} loaded

View File

@ -140,6 +140,7 @@ $.TileCache.prototype = {
* may temporarily surpass that number, but should eventually come back down to the max specified. * may temporarily surpass that number, but should eventually come back down to the max specified.
* @param {Object} options - Tile info. * @param {Object} options - Tile info.
* @param {OpenSeadragon.Tile} options.tile - The tile to cache. * @param {OpenSeadragon.Tile} options.tile - The tile to cache.
* @param {String} options.tile.cacheKey - The unique key used to identify this tile in the cache.
* @param {Image} options.image - The image of the tile to cache. * @param {Image} options.image - The image of the tile to cache.
* @param {OpenSeadragon.TiledImage} options.tiledImage - The TiledImage that owns that tile. * @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 * @param {Number} [options.cutoff=0] - If adding this tile goes over the cache max count, this
@ -149,16 +150,16 @@ $.TileCache.prototype = {
cacheTile: function( options ) { cacheTile: function( options ) {
$.console.assert( options, "[TileCache.cacheTile] options is required" ); $.console.assert( options, "[TileCache.cacheTile] options is required" );
$.console.assert( options.tile, "[TileCache.cacheTile] options.tile 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.cacheKey, "[TileCache.cacheTile] options.tile.cacheKey is required" );
$.console.assert( options.tiledImage, "[TileCache.cacheTile] options.tiledImage is required" ); $.console.assert( options.tiledImage, "[TileCache.cacheTile] options.tiledImage is required" );
var cutoff = options.cutoff || 0; var cutoff = options.cutoff || 0;
var insertionIndex = this._tilesLoaded.length; var insertionIndex = this._tilesLoaded.length;
var imageRecord = this._imagesLoaded[options.tile.url]; var imageRecord = this._imagesLoaded[options.tile.cacheKey];
if (!imageRecord) { if (!imageRecord) {
$.console.assert( options.image, "[TileCache.cacheTile] options.image is required to create an ImageRecord" ); $.console.assert( options.image, "[TileCache.cacheTile] options.image is required to create an ImageRecord" );
imageRecord = this._imagesLoaded[options.tile.url] = new ImageRecord({ imageRecord = this._imagesLoaded[options.tile.cacheKey] = new ImageRecord({
image: options.image image: options.image
}); });
@ -232,9 +233,9 @@ $.TileCache.prototype = {
}, },
// private // private
getImageRecord: function(url) { getImageRecord: function(cacheKey) {
$.console.assert(url, '[TileCache.getImageRecord] url is required'); $.console.assert(cacheKey, '[TileCache.getImageRecord] cacheKey is required');
return this._imagesLoaded[url]; return this._imagesLoaded[cacheKey];
}, },
// private // private
@ -246,11 +247,11 @@ $.TileCache.prototype = {
tile.unload(); tile.unload();
tile.cacheImageRecord = null; tile.cacheImageRecord = null;
var imageRecord = this._imagesLoaded[tile.url]; var imageRecord = this._imagesLoaded[tile.cacheKey];
imageRecord.removeTile(tile); imageRecord.removeTile(tile);
if (!imageRecord.getTileCount()) { if (!imageRecord.getTileCount()) {
imageRecord.destroy(); imageRecord.destroy();
delete this._imagesLoaded[tile.url]; delete this._imagesLoaded[tile.cacheKey];
this._imagesLoadedCount--; this._imagesLoadedCount--;
} }

View File

@ -70,11 +70,18 @@
* @param {Number} [options.minPixelRatio] - See {@link OpenSeadragon.Options}. * @param {Number} [options.minPixelRatio] - See {@link OpenSeadragon.Options}.
* @param {Number} [options.smoothTileEdgesMinZoom] - See {@link OpenSeadragon.Options}. * @param {Number} [options.smoothTileEdgesMinZoom] - See {@link OpenSeadragon.Options}.
* @param {Boolean} [options.iOSDevice] - See {@link OpenSeadragon.Options}. * @param {Boolean} [options.iOSDevice] - See {@link OpenSeadragon.Options}.
* @param {Number} [options.opacity=1] - Opacity the tiled image should be drawn at. * @param {Number} [options.opacity=1] - Set to draw at proportional opacity. If zero, images will not draw.
* @param {Boolean} [options.preload=false] - Set true to load even when the image is hidden by zero opacity.
* @param {String} [options.compositeOperation] - How the image is composited onto other images; see compositeOperation in {@link OpenSeadragon.Options} for possible values. * @param {String} [options.compositeOperation] - How the image is composited onto other images; see compositeOperation in {@link OpenSeadragon.Options} for possible values.
* @param {Boolean} [options.debugMode] - See {@link OpenSeadragon.Options}. * @param {Boolean} [options.debugMode] - See {@link OpenSeadragon.Options}.
* @param {String|CanvasGradient|CanvasPattern|Function} [options.placeholderFillStyle] - See {@link OpenSeadragon.Options}. * @param {String|CanvasGradient|CanvasPattern|Function} [options.placeholderFillStyle] - See {@link OpenSeadragon.Options}.
* @param {String|Boolean} [options.crossOriginPolicy] - See {@link OpenSeadragon.Options}. * @param {String|Boolean} [options.crossOriginPolicy] - See {@link OpenSeadragon.Options}.
* @param {Boolean} [options.ajaxWithCredentials] - See {@link OpenSeadragon.Options}.
* @param {Boolean} [options.loadTilesWithAjax]
* Whether to load tile data using AJAX requests.
* Defaults to the setting in {@link OpenSeadragon.Options}.
* @param {Object} [options.ajaxHeaders={}]
* A set of headers to include when making tile AJAX requests.
*/ */
$.TiledImage = function( options ) { $.TiledImage = function( options ) {
var _this = this; var _this = this;
@ -140,7 +147,8 @@ $.TiledImage = function( options ) {
//internal state properties //internal state properties
viewer: null, viewer: null,
tilesMatrix: {}, // A '3d' dictionary [level][x][y] --> Tile. tilesMatrix: {}, // A '3d' dictionary [level][x][y] --> Tile.
coverage: {}, // A '3d' dictionary [level][x][y] --> Boolean. coverage: {}, // A '3d' dictionary [level][x][y] --> Boolean; shows what areas have been drawn.
loadingCoverage: {}, // A '3d' dictionary [level][x][y] --> Boolean; shows what areas are loaded or are being loaded/blended.
lastDrawn: [], // An unordered list of Tiles drawn last frame. lastDrawn: [], // An unordered list of Tiles drawn last frame.
lastResetTime: 0, // Last time for which the tiledImage was reset. lastResetTime: 0, // Last time for which the tiledImage was reset.
_midDraw: false, // Is the tiledImage currently updating the viewport? _midDraw: false, // Is the tiledImage currently updating the viewport?
@ -161,11 +169,16 @@ $.TiledImage = function( options ) {
iOSDevice: $.DEFAULT_SETTINGS.iOSDevice, iOSDevice: $.DEFAULT_SETTINGS.iOSDevice,
debugMode: $.DEFAULT_SETTINGS.debugMode, debugMode: $.DEFAULT_SETTINGS.debugMode,
crossOriginPolicy: $.DEFAULT_SETTINGS.crossOriginPolicy, crossOriginPolicy: $.DEFAULT_SETTINGS.crossOriginPolicy,
ajaxWithCredentials: $.DEFAULT_SETTINGS.ajaxWithCredentials,
placeholderFillStyle: $.DEFAULT_SETTINGS.placeholderFillStyle, placeholderFillStyle: $.DEFAULT_SETTINGS.placeholderFillStyle,
opacity: $.DEFAULT_SETTINGS.opacity, opacity: $.DEFAULT_SETTINGS.opacity,
preload: $.DEFAULT_SETTINGS.preload,
compositeOperation: $.DEFAULT_SETTINGS.compositeOperation compositeOperation: $.DEFAULT_SETTINGS.compositeOperation
}, options ); }, options );
this._preload = this.preload;
delete this.preload;
this._fullyLoaded = false; this._fullyLoaded = false;
this._xSpring = new $.Spring({ this._xSpring = new $.Spring({
@ -293,7 +306,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
* Draws the TiledImage to its Drawer. * Draws the TiledImage to its Drawer.
*/ */
draw: function() { draw: function() {
if (this.opacity !== 0) { if (this.opacity !== 0 || this._preload) {
this._midDraw = true; this._midDraw = true;
this._updateViewport(); this._updateViewport();
this._midDraw = false; this._midDraw = false;
@ -764,10 +777,43 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
/** /**
* @param {Number} opacity Opacity the tiled image should be drawn at. * @param {Number} opacity Opacity the tiled image should be drawn at.
* @fires OpenSeadragon.TiledImage.event:opacity-change
*/ */
setOpacity: function(opacity) { setOpacity: function(opacity) {
if (opacity === this.opacity) {
return;
}
this.opacity = opacity; this.opacity = opacity;
this._needsDraw = true; this._needsDraw = true;
/**
* Raised when the TiledImage's opacity is changed.
* @event opacity-change
* @memberOf OpenSeadragon.TiledImage
* @type {object}
* @property {Number} opacity - The new opacity value.
* @property {OpenSeadragon.TiledImage} eventSource - A reference to the
* TiledImage which raised the event.
* @property {?Object} userData - Arbitrary subscriber-defined object.
*/
this.raiseEvent('opacity-change', {
opacity: this.opacity
});
},
/**
* @returns {Boolean} whether the tiledImage can load its tiles even when it has zero opacity.
*/
getPreload: function() {
return this._preload;
},
/**
* Set true to load even when hidden. Set false to block loading when hidden.
*/
setPreload: function(preload) {
this._preload = !!preload;
this._needsDraw = true;
}, },
/** /**
@ -803,8 +849,8 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
}, },
/** /**
* @private
* Get the point around which this tiled image is rotated * Get the point around which this tiled image is rotated
* @private
* @param {Boolean} current True for current rotation point, false for target. * @param {Boolean} current True for current rotation point, false for target.
* @returns {OpenSeadragon.Point} * @returns {OpenSeadragon.Point}
*/ */
@ -821,10 +867,28 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
/** /**
* @param {String} compositeOperation the tiled image should be drawn with this globalCompositeOperation. * @param {String} compositeOperation the tiled image should be drawn with this globalCompositeOperation.
* @fires OpenSeadragon.TiledImage.event:composite-operation-change
*/ */
setCompositeOperation: function(compositeOperation) { setCompositeOperation: function(compositeOperation) {
if (compositeOperation === this.compositeOperation) {
return;
}
this.compositeOperation = compositeOperation; this.compositeOperation = compositeOperation;
this._needsDraw = true; this._needsDraw = true;
/**
* Raised when the TiledImage's opacity is changed.
* @event composite-operation-change
* @memberOf OpenSeadragon.TiledImage
* @type {object}
* @property {String} compositeOperation - The new compositeOperation value.
* @property {OpenSeadragon.TiledImage} eventSource - A reference to the
* TiledImage which raised the event.
* @property {?Object} userData - Arbitrary subscriber-defined object.
*/
this.raiseEvent('composite-operation-change', {
compositeOperation: this.compositeOperation
});
}, },
// private // private
@ -917,6 +981,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
_updateViewport: function() { _updateViewport: function() {
this._needsDraw = false; this._needsDraw = false;
this._tilesLoading = 0; this._tilesLoading = 0;
this.loadingCoverage = {};
// Reset tile's internal drawn state // Reset tile's internal drawn state
while (this.lastDrawn.length > 0) { while (this.lastDrawn.length > 0) {
@ -971,7 +1036,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
var targetZeroRatio = viewport.deltaPixelsFromPointsNoRotate( var targetZeroRatio = viewport.deltaPixelsFromPointsNoRotate(
this.source.getPixelRatio( this.source.getPixelRatio(
Math.max( Math.max(
this.source.getClosestLevel(viewport.containerSize) - 1, this.source.getClosestLevel(),
0 0
) )
), ),
@ -1115,6 +1180,7 @@ function updateLevel(tiledImage, haveDrawn, drawLevel, level, levelOpacity,
} }
resetCoverage(tiledImage.coverage, level); resetCoverage(tiledImage.coverage, level);
resetCoverage(tiledImage.loadingCoverage, level);
//OK, a new drawing so do your calculations //OK, a new drawing so do your calculations
var cornerTiles = tiledImage._getCornerTiles(level, topLeftBound, bottomRightBound); var cornerTiles = tiledImage._getCornerTiles(level, topLeftBound, bottomRightBound);
@ -1179,6 +1245,7 @@ function updateTile( tiledImage, haveDrawn, drawLevel, x, y, level, levelOpacity
var tile = getTile( var tile = getTile(
x, y, x, y,
level, level,
tiledImage,
tiledImage.source, tiledImage.source,
tiledImage.tilesMatrix, tiledImage.tilesMatrix,
currentTime, currentTime,
@ -1208,6 +1275,9 @@ function updateTile( tiledImage, haveDrawn, drawLevel, x, y, level, levelOpacity
setCoverage( tiledImage.coverage, level, x, y, false ); setCoverage( tiledImage.coverage, level, x, y, false );
var loadingCoverage = tile.loaded || tile.loading || isCovered(tiledImage.loadingCoverage, level, x, y);
setCoverage(tiledImage.loadingCoverage, level, x, y, loadingCoverage);
if ( !tile.exists ) { if ( !tile.exists ) {
return best; return best;
} }
@ -1237,7 +1307,7 @@ function updateTile( tiledImage, haveDrawn, drawLevel, x, y, level, levelOpacity
if (tile.context2D) { if (tile.context2D) {
setTileLoaded(tiledImage, tile); setTileLoaded(tiledImage, tile);
} else { } else {
var imageRecord = tiledImage._tileCache.getImageRecord(tile.url); var imageRecord = tiledImage._tileCache.getImageRecord(tile.cacheKey);
if (imageRecord) { if (imageRecord) {
var image = imageRecord.getImage(); var image = imageRecord.getImage();
setTileLoaded(tiledImage, tile, image); setTileLoaded(tiledImage, tile, image);
@ -1261,7 +1331,7 @@ function updateTile( tiledImage, haveDrawn, drawLevel, x, y, level, levelOpacity
} else if ( tile.loading ) { } else if ( tile.loading ) {
// the tile is already in the download queue // the tile is already in the download queue
tiledImage._tilesLoading++; tiledImage._tilesLoading++;
} else { } else if (!loadingCoverage) {
best = compareTiles( best, tile ); best = compareTiles( best, tile );
} }
@ -1275,6 +1345,7 @@ function updateTile( tiledImage, haveDrawn, drawLevel, x, y, level, levelOpacity
* @param {Number} x * @param {Number} x
* @param {Number} y * @param {Number} y
* @param {Number} level * @param {Number} level
* @param {OpenSeadragon.TiledImage} tiledImage
* @param {OpenSeadragon.TileSource} tileSource * @param {OpenSeadragon.TileSource} tileSource
* @param {Object} tilesMatrix - A '3d' dictionary [level][x][y] --> Tile. * @param {Object} tilesMatrix - A '3d' dictionary [level][x][y] --> Tile.
* @param {Number} time * @param {Number} time
@ -1283,12 +1354,23 @@ function updateTile( tiledImage, haveDrawn, drawLevel, x, y, level, levelOpacity
* @param {Number} worldHeight * @param {Number} worldHeight
* @returns {OpenSeadragon.Tile} * @returns {OpenSeadragon.Tile}
*/ */
function getTile( x, y, level, tileSource, tilesMatrix, time, numTiles, worldWidth, worldHeight ) { function getTile(
x, y,
level,
tiledImage,
tileSource,
tilesMatrix,
time,
numTiles,
worldWidth,
worldHeight
) {
var xMod, var xMod,
yMod, yMod,
bounds, bounds,
exists, exists,
url, url,
ajaxHeaders,
context2D, context2D,
tile; tile;
@ -1305,6 +1387,18 @@ function getTile( x, y, level, tileSource, tilesMatrix, time, numTiles, worldWid
bounds = tileSource.getTileBounds( level, xMod, yMod ); bounds = tileSource.getTileBounds( level, xMod, yMod );
exists = tileSource.tileExists( level, xMod, yMod ); exists = tileSource.tileExists( level, xMod, yMod );
url = tileSource.getTileUrl( level, xMod, yMod ); url = tileSource.getTileUrl( level, xMod, yMod );
// Headers are only applicable if loadTilesWithAjax is set
if (tiledImage.loadTilesWithAjax) {
ajaxHeaders = tileSource.getTileAjaxHeaders( level, xMod, yMod );
// Combine tile AJAX headers with tiled image AJAX headers (if applicable)
if ($.isPlainObject(tiledImage.ajaxHeaders)) {
ajaxHeaders = $.extend({}, tiledImage.ajaxHeaders, ajaxHeaders);
}
} else {
ajaxHeaders = null;
}
context2D = tileSource.getContext2D ? context2D = tileSource.getContext2D ?
tileSource.getContext2D(level, xMod, yMod) : undefined; tileSource.getContext2D(level, xMod, yMod) : undefined;
@ -1318,7 +1412,9 @@ function getTile( x, y, level, tileSource, tilesMatrix, time, numTiles, worldWid
bounds, bounds,
exists, exists,
url, url,
context2D context2D,
tiledImage.loadTilesWithAjax,
ajaxHeaders
); );
} }
@ -1340,9 +1436,12 @@ function loadTile( tiledImage, tile, time ) {
tile.loading = true; tile.loading = true;
tiledImage._imageLoader.addJob({ tiledImage._imageLoader.addJob({
src: tile.url, src: tile.url,
loadWithAjax: tile.loadWithAjax,
ajaxHeaders: tile.ajaxHeaders,
crossOriginPolicy: tiledImage.crossOriginPolicy, crossOriginPolicy: tiledImage.crossOriginPolicy,
callback: function( image, errorMsg ){ ajaxWithCredentials: tiledImage.ajaxWithCredentials,
onTileLoad( tiledImage, tile, time, image, errorMsg ); callback: function( image, errorMsg, tileRequest ){
onTileLoad( tiledImage, tile, time, image, errorMsg, tileRequest );
}, },
abort: function() { abort: function() {
tile.loading = false; tile.loading = false;
@ -1359,8 +1458,9 @@ function loadTile( tiledImage, tile, time ) {
* @param {Number} time * @param {Number} time
* @param {Image} image * @param {Image} image
* @param {String} errorMsg * @param {String} errorMsg
* @param {XMLHttpRequest} tileRequest
*/ */
function onTileLoad( tiledImage, tile, time, image, errorMsg ) { function onTileLoad( tiledImage, tile, time, image, errorMsg, tileRequest ) {
if ( !image ) { if ( !image ) {
$.console.log( "Tile %s failed to load: %s - error: %s", tile, tile.url, errorMsg ); $.console.log( "Tile %s failed to load: %s - error: %s", tile, tile.url, errorMsg );
/** /**
@ -1373,8 +1473,15 @@ function onTileLoad( tiledImage, tile, time, image, errorMsg ) {
* @property {OpenSeadragon.TiledImage} tiledImage - The tiled image the tile belongs to. * @property {OpenSeadragon.TiledImage} tiledImage - The tiled image the tile belongs to.
* @property {number} time - The time in milliseconds when the tile load began. * @property {number} time - The time in milliseconds when the tile load began.
* @property {string} message - The error message. * @property {string} message - The error message.
* @property {XMLHttpRequest} tileRequest - The XMLHttpRequest used to load the tile if available.
*/ */
tiledImage.viewer.raiseEvent("tile-load-failed", {tile: tile, tiledImage: tiledImage, time: time, message: errorMsg}); tiledImage.viewer.raiseEvent("tile-load-failed", {
tile: tile,
tiledImage: tiledImage,
time: time,
message: errorMsg,
tileRequest: tileRequest
});
tile.loading = false; tile.loading = false;
tile.exists = false; tile.exists = false;
return; return;
@ -1387,9 +1494,8 @@ function onTileLoad( tiledImage, tile, time, image, errorMsg ) {
} }
var finish = function() { var finish = function() {
var cutoff = Math.ceil( Math.log( var cutoff = tiledImage.source.getClosestLevel();
tiledImage.source.getTileWidth(tile.level) ) / Math.log( 2 ) ); setTileLoaded(tiledImage, tile, image, cutoff, tileRequest);
setTileLoaded(tiledImage, tile, image, cutoff);
}; };
// Check if we're mid-update; this can happen on IE8 because image load events for // Check if we're mid-update; this can happen on IE8 because image load events for
@ -1410,7 +1516,7 @@ function onTileLoad( tiledImage, tile, time, image, errorMsg ) {
* @param {Image} image * @param {Image} image
* @param {Number} cutoff * @param {Number} cutoff
*/ */
function setTileLoaded(tiledImage, tile, image, cutoff) { function setTileLoaded(tiledImage, tile, image, cutoff, tileRequest) {
var increment = 0; var increment = 0;
function getCompletionCallback() { function getCompletionCallback() {
@ -1445,6 +1551,7 @@ function setTileLoaded(tiledImage, tile, image, cutoff) {
* @property {Image} image - The image of the tile. * @property {Image} image - The image of the tile.
* @property {OpenSeadragon.TiledImage} tiledImage - The tiled image of the loaded tile. * @property {OpenSeadragon.TiledImage} tiledImage - The tiled image of the loaded tile.
* @property {OpenSeadragon.Tile} tile - The tile which has been loaded. * @property {OpenSeadragon.Tile} tile - The tile which has been loaded.
* @property {XMLHttpRequest} tiledImage - The AJAX request that loaded this tile (if applicable).
* @property {function} getCompletionCallback - A function giving a callback to call * @property {function} getCompletionCallback - A function giving a callback to call
* when the asynchronous processing of the image is done. The image will be * when the asynchronous processing of the image is done. The image will be
* marked as entirely loaded when the callback has been called once for each * marked as entirely loaded when the callback has been called once for each
@ -1453,6 +1560,7 @@ function setTileLoaded(tiledImage, tile, image, cutoff) {
tiledImage.viewer.raiseEvent("tile-loaded", { tiledImage.viewer.raiseEvent("tile-loaded", {
tile: tile, tile: tile,
tiledImage: tiledImage, tiledImage: tiledImage,
tileRequest: tileRequest,
image: image, image: image,
getCompletionCallback: getCompletionCallback getCompletionCallback: getCompletionCallback
}); });
@ -1697,7 +1805,7 @@ function compareTiles( previousBest, tile ) {
* @param {OpenSeadragon.Tile[]} lastDrawn - An unordered list of Tiles drawn last frame. * @param {OpenSeadragon.Tile[]} lastDrawn - An unordered list of Tiles drawn last frame.
*/ */
function drawTiles( tiledImage, lastDrawn ) { function drawTiles( tiledImage, lastDrawn ) {
if (lastDrawn.length === 0) { if (tiledImage.opacity === 0 || lastDrawn.length === 0) {
return; return;
} }
var tile = lastDrawn[0]; var tile = lastDrawn[0];

View File

@ -60,11 +60,15 @@
* the extending classes implementation of 'configure'. * the extending classes implementation of 'configure'.
* @param {String} [options.url] * @param {String} [options.url]
* The URL for the data necessary for this TileSource. * The URL for the data necessary for this TileSource.
* @param {String} [options.referenceStripThumbnailUrl]
* The URL for a thumbnail image to be used by the reference strip
* @param {Function} [options.success] * @param {Function} [options.success]
* A function to be called upon successful creation. * A function to be called upon successful creation.
* @param {Boolean} [options.ajaxWithCredentials] * @param {Boolean} [options.ajaxWithCredentials]
* If this TileSource needs to make an AJAX call, this specifies whether to set * If this TileSource needs to make an AJAX call, this specifies whether to set
* the XHR's withCredentials (for accessing secure data). * the XHR's withCredentials (for accessing secure data).
* @param {Object} [options.ajaxHeaders]
* A set of headers to include in AJAX requests.
* @param {Number} [options.width] * @param {Number} [options.width]
* Width of the source image at max resolution in pixels. * Width of the source image at max resolution in pixels.
* @param {Number} [options.height] * @param {Number} [options.height]
@ -318,25 +322,20 @@ $.TileSource.prototype = {
/** /**
* @function * @function
* @param {Number} level * @returns {Number} The highest level in this tile source that can be contained in a single tile.
*/ */
getClosestLevel: function( rect ) { getClosestLevel: function() {
var i, var i,
tilesPerSide,
tiles; tiles;
for( i = this.minLevel; i < this.maxLevel; i++ ){ for (i = this.minLevel + 1; i <= this.maxLevel; i++){
tiles = this.getNumTiles(i); tiles = this.getNumTiles(i);
tilesPerSide = new $.Point( if (tiles.x > 1 || tiles.y > 1) {
Math.floor( rect.x / this.getTileWidth(i) ),
Math.floor( rect.y / this.getTileHeight(i) )
);
if( tiles.x + 1 >= tilesPerSide.x && tiles.y + 1 >= tilesPerSide.y ){
break; break;
} }
} }
return Math.max( 0, i - 1 );
return i - 1;
}, },
/** /**
@ -475,6 +474,7 @@ $.TileSource.prototype = {
$.makeAjaxRequest( { $.makeAjaxRequest( {
url: url, url: url,
withCredentials: this.ajaxWithCredentials, withCredentials: this.ajaxWithCredentials,
headers: this.ajaxHeaders,
success: function( xhr ) { success: function( xhr ) {
var data = processResponse( xhr ); var data = processResponse( xhr );
callback( data ); callback( data );
@ -559,7 +559,7 @@ $.TileSource.prototype = {
}, },
/** /**
* Responsible for retriving the url which will return an image for the * Responsible for retrieving the url which will return an image for the
* region specified by the given x, y, and level components. * region specified by the given x, y, and level components.
* This method is not implemented by this class other than to throw an Error * This method is not implemented by this class other than to throw an Error
* announcing you have to implement it. Because of the variety of tile * announcing you have to implement it. Because of the variety of tile
@ -575,6 +575,23 @@ $.TileSource.prototype = {
throw new Error( "Method not implemented." ); throw new Error( "Method not implemented." );
}, },
/**
* Responsible for retrieving the headers which will be attached to the image request for the
* region specified by the given x, y, and level components.
* This option is only relevant if {@link OpenSeadragon.Options}.loadTilesWithAjax is set to true.
* The headers returned here will override headers specified at the Viewer or TiledImage level.
* Specifying a falsy value for a header will clear its existing value set at the Viewer or
* TiledImage level (if any).
* @function
* @param {Number} level
* @param {Number} x
* @param {Number} y
* @returns {Object}
*/
getTileAjaxHeaders: function( level, x, y ) {
return {};
},
/** /**
* @function * @function
* @param {Number} level * @param {Number} level
@ -629,7 +646,11 @@ function processResponse( xhr ){
data = xhr.responseText; data = xhr.responseText;
} }
}else if( responseText.match(/\s*[\{\[].*/) ){ }else if( responseText.match(/\s*[\{\[].*/) ){
try{
data = $.parseJSON(responseText); data = $.parseJSON(responseText);
} catch(e){
data = responseText;
}
}else{ }else{
data = responseText; data = responseText;
} }

View File

@ -96,6 +96,12 @@ $.Viewer = function( options ) {
//internal state and dom identifiers //internal state and dom identifiers
id: options.id, id: options.id,
hash: options.hash || nextHash++, hash: options.hash || nextHash++,
/**
* Index for page to be shown first next time open() is called (only used in sequenceMode).
* @member {Number} initialPage
* @memberof OpenSeadragon.Viewer#
*/
initialPage: 0,
//dom nodes //dom nodes
/** /**
@ -370,7 +376,8 @@ $.Viewer = function( options ) {
// Create the image loader // Create the image loader
this.imageLoader = new $.ImageLoader({ this.imageLoader = new $.ImageLoader({
jobLimit: this.imageLoaderLimit jobLimit: this.imageLoaderLimit,
timeout: options.timeout
}); });
// Create the tile cache // Create the tile cache
@ -481,11 +488,13 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
* except for the index property; images are added in sequence. * except for the index property; images are added in sequence.
* A TileSource specifier is anything you could pass as the tileSource property * A TileSource specifier is anything you could pass as the tileSource property
* of the options parameter for {@link OpenSeadragon.Viewer#addTiledImage}. * of the options parameter for {@link OpenSeadragon.Viewer#addTiledImage}.
* @param {Number} initialPage - If sequenceMode is true, display this page initially
* for the given tileSources. If specified, will overwrite the Viewer's existing initialPage property.
* @return {OpenSeadragon.Viewer} Chainable. * @return {OpenSeadragon.Viewer} Chainable.
* @fires OpenSeadragon.Viewer.event:open * @fires OpenSeadragon.Viewer.event:open
* @fires OpenSeadragon.Viewer.event:open-failed * @fires OpenSeadragon.Viewer.event:open-failed
*/ */
open: function (tileSources) { open: function (tileSources, initialPage) {
var _this = this; var _this = this;
this.close(); this.close();
@ -500,23 +509,17 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
this.referenceStrip = null; this.referenceStrip = null;
} }
if (typeof initialPage != 'undefined' && !isNaN(initialPage)) {
this.initialPage = initialPage;
}
this.tileSources = tileSources; this.tileSources = tileSources;
this._sequenceIndex = Math.max(0, Math.min(this.tileSources.length - 1, this.initialPage)); this._sequenceIndex = Math.max(0, Math.min(this.tileSources.length - 1, this.initialPage));
if (this.tileSources.length) { if (this.tileSources.length) {
this.open(this.tileSources[this._sequenceIndex]); this.open(this.tileSources[this._sequenceIndex]);
if ( this.showReferenceStrip ){ if ( this.showReferenceStrip ){
this.referenceStrip = new $.ReferenceStrip({ this.addReferenceStrip();
id: this.referenceStripElement,
position: this.referenceStripPosition,
sizeRatio: this.referenceStripSizeRatio,
scroll: this.referenceStripScroll,
height: this.referenceStripHeight,
width: this.referenceStripWidth,
tileSources: this.tileSources,
prefixUrl: this.prefixUrl,
viewer: this
});
} }
} }
@ -845,6 +848,22 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
return this; return this;
}, },
/**
* Turns debugging mode on or off for this viewer.
*
* @function
* @param {Boolean} true to turn debug on, false to turn debug off.
*/
setDebugMode: function(debugMode){
for (var i = 0; i < this.world.getItemCount(); i++) {
this.world.getItemAt(i).debugMode = debugMode;
}
this.debugMode = debugMode;
this.forceRedraw();
},
/** /**
* @function * @function
* @return {Boolean} * @return {Boolean}
@ -1226,12 +1245,22 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
* @param {OpenSeadragon.Rect} [options.clip] - An area, in image pixels, to clip to * @param {OpenSeadragon.Rect} [options.clip] - An area, in image pixels, to clip to
* (portions of the image outside of this area will not be visible). Only works on * (portions of the image outside of this area will not be visible). Only works on
* browsers that support the HTML5 canvas. * browsers that support the HTML5 canvas.
* @param {Number} [options.opacity] Opacity the tiled image should be drawn at by default. * @param {Number} [options.opacity=1] Proportional opacity of the tiled images (1=opaque, 0=hidden)
* @param {Boolean} [options.preload=false] Default switch for loading hidden images (true loads, false blocks)
* @param {Number} [options.degrees=0] Initial rotation of the tiled image around * @param {Number} [options.degrees=0] Initial rotation of the tiled image around
* its top left corner in degrees. * its top left corner in degrees.
* @param {String} [options.compositeOperation] How the image is composited onto other images. * @param {String} [options.compositeOperation] How the image is composited onto other images.
* @param {String} [options.crossOriginPolicy] The crossOriginPolicy for this specific image, * @param {String} [options.crossOriginPolicy] The crossOriginPolicy for this specific image,
* overriding viewer.crossOriginPolicy. * overriding viewer.crossOriginPolicy.
* @param {Boolean} [options.ajaxWithCredentials] Whether to set withCredentials on tile AJAX
* @param {Boolean} [options.loadTilesWithAjax]
* Whether to load tile data using AJAX requests.
* Defaults to the setting in {@link OpenSeadragon.Options}.
* @param {Object} [options.ajaxHeaders]
* A set of headers to include when making tile AJAX requests.
* Note that these headers will be merged over any headers specified in {@link OpenSeadragon.Options}.
* Specifying a falsy value for a header will clear its existing value set at the Viewer level (if any).
* requests.
* @param {Function} [options.success] A function that gets called when the image is * @param {Function} [options.success] A function that gets called when the image is
* successfully added. It's passed the event object which contains a single property: * successfully added. It's passed the event object which contains a single property:
* "item", the resulting TiledImage. * "item", the resulting TiledImage.
@ -1264,12 +1293,26 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
if (options.opacity === undefined) { if (options.opacity === undefined) {
options.opacity = this.opacity; options.opacity = this.opacity;
} }
if (options.preload === undefined) {
options.preload = this.preload;
}
if (options.compositeOperation === undefined) { if (options.compositeOperation === undefined) {
options.compositeOperation = this.compositeOperation; options.compositeOperation = this.compositeOperation;
} }
if (options.crossOriginPolicy === undefined) { if (options.crossOriginPolicy === undefined) {
options.crossOriginPolicy = options.tileSource.crossOriginPolicy !== undefined ? options.tileSource.crossOriginPolicy : this.crossOriginPolicy; options.crossOriginPolicy = options.tileSource.crossOriginPolicy !== undefined ? options.tileSource.crossOriginPolicy : this.crossOriginPolicy;
} }
if (options.ajaxWithCredentials === undefined) {
options.ajaxWithCredentials = this.ajaxWithCredentials;
}
if (options.loadTilesWithAjax === undefined) {
options.loadTilesWithAjax = this.loadTilesWithAjax;
}
if (options.ajaxHeaders === undefined || options.ajaxHeaders === null) {
options.ajaxHeaders = this.ajaxHeaders;
} else if ($.isPlainObject(options.ajaxHeaders) && $.isPlainObject(this.ajaxHeaders)) {
options.ajaxHeaders = $.extend({}, this.ajaxHeaders, options.ajaxHeaders);
}
var myQueueItem = { var myQueueItem = {
options: options options: options
@ -1332,11 +1375,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
this._loadQueue.push(myQueueItem); this._loadQueue.push(myQueueItem);
getTileSourceImplementation( this, options.tileSource, options, function( tileSource ) { function processReadyItems() {
myQueueItem.tileSource = tileSource;
// add everybody at the front of the queue that's ready to go
var queueItem, tiledImage, optionsClone; var queueItem, tiledImage, optionsClone;
while (_this._loadQueue.length) { while (_this._loadQueue.length) {
queueItem = _this._loadQueue[0]; queueItem = _this._loadQueue[0];
@ -1370,6 +1409,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
clip: queueItem.options.clip, clip: queueItem.options.clip,
placeholderFillStyle: queueItem.options.placeholderFillStyle, placeholderFillStyle: queueItem.options.placeholderFillStyle,
opacity: queueItem.options.opacity, opacity: queueItem.options.opacity,
preload: queueItem.options.preload,
degrees: queueItem.options.degrees, degrees: queueItem.options.degrees,
compositeOperation: queueItem.options.compositeOperation, compositeOperation: queueItem.options.compositeOperation,
springStiffness: _this.springStiffness, springStiffness: _this.springStiffness,
@ -1384,6 +1424,9 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
smoothTileEdgesMinZoom: _this.smoothTileEdgesMinZoom, smoothTileEdgesMinZoom: _this.smoothTileEdgesMinZoom,
iOSDevice: _this.iOSDevice, iOSDevice: _this.iOSDevice,
crossOriginPolicy: queueItem.options.crossOriginPolicy, crossOriginPolicy: queueItem.options.crossOriginPolicy,
ajaxWithCredentials: queueItem.options.ajaxWithCredentials,
loadTilesWithAjax: queueItem.options.loadTilesWithAjax,
ajaxHeaders: queueItem.options.ajaxHeaders,
debugMode: _this.debugMode debugMode: _this.debugMode
}); });
@ -1419,9 +1462,20 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
}); });
} }
} }
}
getTileSourceImplementation( this, options.tileSource, options, function( tileSource ) {
myQueueItem.tileSource = tileSource;
// add everybody at the front of the queue that's ready to go
processReadyItems();
}, function( event ) { }, function( event ) {
event.options = options; event.options = options;
raiseAddItemFailed(event); raiseAddItemFailed(event);
// add everybody at the front of the queue that's ready to go
processReadyItems();
} ); } );
}, },
@ -2094,6 +2148,52 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
*/ */
_cancelPendingImages: function() { _cancelPendingImages: function() {
this._loadQueue = []; this._loadQueue = [];
},
/**
* Removes the reference strip and disables displaying it.
* @function
*/
removeReferenceStrip: function() {
this.showReferenceStrip = false;
if (this.referenceStrip) {
this.referenceStrip.destroy();
this.referenceStrip = null;
}
},
/**
* Enables and displays the reference strip based on the currently set tileSources.
* Works only when the Viewer has sequenceMode set to true.
* @function
*/
addReferenceStrip: function() {
this.showReferenceStrip = true;
if (this.sequenceMode) {
if (this.referenceStrip) {
return;
}
if (this.tileSources.length && this.tileSources.length > 1) {
this.referenceStrip = new $.ReferenceStrip({
id: this.referenceStripElement,
position: this.referenceStripPosition,
sizeRatio: this.referenceStripSizeRatio,
scroll: this.referenceStripScroll,
height: this.referenceStripHeight,
width: this.referenceStripWidth,
tileSources: this.tileSources,
prefixUrl: this.prefixUrl,
viewer: this
});
this.referenceStrip.setFocus( this._sequenceIndex );
}
} else {
$.console.warn('Attempting to display a reference strip while "sequenceMode" is off.');
}
} }
}); });
@ -2113,6 +2213,7 @@ function _getSafeElemSize (oElement) {
); );
} }
/** /**
* @function * @function
* @private * @private
@ -2128,7 +2229,12 @@ function getTileSourceImplementation( viewer, tileSource, imgOptions, successCal
tileSource = $.parseXml( tileSource ); tileSource = $.parseXml( tileSource );
//json should start with "{" or "[" and end with "}" or "]" //json should start with "{" or "[" and end with "}" or "]"
} else if ( tileSource.match(/^\s*[\{\[].*[\}\]]\s*$/ ) ) { } else if ( tileSource.match(/^\s*[\{\[].*[\}\]]\s*$/ ) ) {
tileSource = $.parseJSON(tileSource); try {
var tileSourceJ = $.parseJSON(tileSource);
tileSource = tileSourceJ;
} catch (e) {
//tileSource = tileSource;
}
} }
} }
@ -2156,6 +2262,7 @@ function getTileSourceImplementation( viewer, tileSource, imgOptions, successCal
crossOriginPolicy: imgOptions.crossOriginPolicy !== undefined ? crossOriginPolicy: imgOptions.crossOriginPolicy !== undefined ?
imgOptions.crossOriginPolicy : viewer.crossOriginPolicy, imgOptions.crossOriginPolicy : viewer.crossOriginPolicy,
ajaxWithCredentials: viewer.ajaxWithCredentials, ajaxWithCredentials: viewer.ajaxWithCredentials,
ajaxHeaders: viewer.ajaxHeaders,
useCanvas: viewer.useCanvas, useCanvas: viewer.useCanvas,
success: function( event ) { success: function( event ) {
successCallback( event.tileSource ); successCallback( event.tileSource );
@ -2463,16 +2570,15 @@ function onCanvasClick( event ) {
this.canvas.focus(); this.canvas.focus();
} }
if ( !event.preventDefaultAction && this.viewport && event.quick ) { var canvasClickEventArgs = {
gestureSettings = this.gestureSettingsByDeviceType( event.pointerType ); tracker: event.eventSource,
if ( gestureSettings.clickToZoom ) { position: event.position,
this.viewport.zoomBy( quick: event.quick,
event.shift ? 1.0 / this.zoomPerClick : this.zoomPerClick, shift: event.shift,
this.viewport.pointFromPixel( event.position, true ) originalEvent: event.originalEvent,
); preventDefaultAction: event.preventDefaultAction
this.viewport.applyConstraints(); };
}
}
/** /**
* Raised when a mouse press/release or touch/remove occurs on the {@link OpenSeadragon.Viewer#canvas} element. * Raised when a mouse press/release or touch/remove occurs on the {@link OpenSeadragon.Viewer#canvas} element.
* *
@ -2485,15 +2591,21 @@ function onCanvasClick( event ) {
* @property {Boolean} quick - True only if the clickDistThreshold and clickTimeThreshold are both passed. Useful for differentiating between clicks and drags. * @property {Boolean} quick - True only if the clickDistThreshold and clickTimeThreshold are both passed. Useful for differentiating between clicks and drags.
* @property {Boolean} shift - True if the shift key was pressed during this event. * @property {Boolean} shift - True if the shift key was pressed during this event.
* @property {Object} originalEvent - The original DOM event. * @property {Object} originalEvent - The original DOM event.
* @property {Boolean} preventDefaultAction - Set to true to prevent default click to zoom behaviour. Default: false.
* @property {?Object} userData - Arbitrary subscriber-defined object. * @property {?Object} userData - Arbitrary subscriber-defined object.
*/ */
this.raiseEvent( 'canvas-click', { this.raiseEvent( 'canvas-click', canvasClickEventArgs);
tracker: event.eventSource,
position: event.position, if ( !canvasClickEventArgs.preventDefaultAction && this.viewport && event.quick ) {
quick: event.quick, gestureSettings = this.gestureSettingsByDeviceType( event.pointerType );
shift: event.shift, if ( gestureSettings.clickToZoom ) {
originalEvent: event.originalEvent this.viewport.zoomBy(
}); event.shift ? 1.0 / this.zoomPerClick : this.zoomPerClick,
this.viewport.pointFromPixel( event.position, true )
);
this.viewport.applyConstraints();
}
}
} }
function onCanvasDblClick( event ) { function onCanvasDblClick( event ) {
@ -2533,19 +2645,16 @@ function onCanvasDblClick( event ) {
function onCanvasDrag( event ) { function onCanvasDrag( event ) {
var gestureSettings; var gestureSettings;
if ( !event.preventDefaultAction && this.viewport ) { var canvasDragEventArgs = {
gestureSettings = this.gestureSettingsByDeviceType( event.pointerType ); tracker: event.eventSource,
if( !this.panHorizontal ){ position: event.position,
event.delta.x = 0; delta: event.delta,
} speed: event.speed,
if( !this.panVertical ){ direction: event.direction,
event.delta.y = 0; shift: event.shift,
} originalEvent: event.originalEvent,
this.viewport.panBy( this.viewport.deltaPointsFromPixels( event.delta.negate() ), gestureSettings.flickEnabled ); preventDefaultAction: event.preventDefaultAction
if( this.constrainDuringPan ){ };
this.viewport.applyConstraints();
}
}
/** /**
* Raised when a mouse or touch drag operation occurs on the {@link OpenSeadragon.Viewer#canvas} element. * Raised when a mouse or touch drag operation occurs on the {@link OpenSeadragon.Viewer#canvas} element.
* *
@ -2560,17 +2669,43 @@ function onCanvasDrag( event ) {
* @property {Number} direction - Current computed direction, expressed as an angle counterclockwise relative to the positive X axis (-pi to pi, in radians). Only valid if speed > 0. * @property {Number} direction - Current computed direction, expressed as an angle counterclockwise relative to the positive X axis (-pi to pi, in radians). Only valid if speed > 0.
* @property {Boolean} shift - True if the shift key was pressed during this event. * @property {Boolean} shift - True if the shift key was pressed during this event.
* @property {Object} originalEvent - The original DOM event. * @property {Object} originalEvent - The original DOM event.
* @property {Boolean} preventDefaultAction - Set to true to prevent default drag behaviour. Default: false.
* @property {?Object} userData - Arbitrary subscriber-defined object. * @property {?Object} userData - Arbitrary subscriber-defined object.
*/ */
this.raiseEvent( 'canvas-drag', { this.raiseEvent( 'canvas-drag', canvasDragEventArgs);
tracker: event.eventSource,
position: event.position, if ( !event.preventDefaultAction && this.viewport ) {
delta: event.delta, gestureSettings = this.gestureSettingsByDeviceType( event.pointerType );
speed: event.speed, if( !this.panHorizontal ){
direction: event.direction, event.delta.x = 0;
shift: event.shift, }
originalEvent: event.originalEvent if( !this.panVertical ){
}); event.delta.y = 0;
}
if( this.constrainDuringPan ){
var delta = this.viewport.deltaPointsFromPixels( event.delta.negate() );
this.viewport.centerSpringX.target.value += delta.x;
this.viewport.centerSpringY.target.value += delta.y;
var bounds = this.viewport.getBounds();
var constrainedBounds = this.viewport.getConstrainedBounds();
this.viewport.centerSpringX.target.value -= delta.x;
this.viewport.centerSpringY.target.value -= delta.y;
if (bounds.x != constrainedBounds.x) {
event.delta.x = 0;
}
if (bounds.y != constrainedBounds.y) {
event.delta.y = 0;
}
}
this.viewport.panBy( this.viewport.deltaPointsFromPixels( event.delta.negate() ), gestureSettings.flickEnabled && !this.constrainDuringPan);
}
} }
function onCanvasDragEnd( event ) { function onCanvasDragEnd( event ) {
@ -2652,6 +2787,11 @@ function onCanvasEnter( event ) {
} }
function onCanvasExit( event ) { function onCanvasExit( event ) {
if (window.location != window.parent.location){
$.MouseTracker.resetAllMouseTrackers();
}
/** /**
* Raised when a pointer leaves the {@link OpenSeadragon.Viewer#canvas} element. * Raised when a pointer leaves the {@link OpenSeadragon.Viewer#canvas} element.
* *

View File

@ -485,10 +485,9 @@ $.Viewport.prototype = {
* @function * @function
* @private * @private
* @param {OpenSeadragon.Rect} bounds * @param {OpenSeadragon.Rect} bounds
* @param {Boolean} immediately
* @return {OpenSeadragon.Rect} constrained bounds. * @return {OpenSeadragon.Rect} constrained bounds.
*/ */
_applyBoundaryConstraints: function(bounds, immediately) { _applyBoundaryConstraints: function(bounds) {
var newBounds = new $.Rect( var newBounds = new $.Rect(
bounds.x, bounds.x,
bounds.y, bounds.y,
@ -531,6 +530,16 @@ $.Viewport.prototype = {
} }
} }
return newBounds;
},
/**
* @function
* @private
* @param {Boolean} [immediately=false] - whether the function that triggered this event was
* called with the "immediately" flag
*/
_raiseConstraintsEvent: function(immediately) {
if (this.viewer) { if (this.viewer) {
/** /**
* Raised when the viewport constraints are applied (see {@link OpenSeadragon.Viewport#applyConstraints}). * Raised when the viewport constraints are applied (see {@link OpenSeadragon.Viewport#applyConstraints}).
@ -539,15 +548,14 @@ $.Viewport.prototype = {
* @memberof OpenSeadragon.Viewer * @memberof OpenSeadragon.Viewer
* @type {object} * @type {object}
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
* @property {Boolean} immediately * @property {Boolean} immediately - whether the function that triggered this event was
* called with the "immediately" flag
* @property {?Object} userData - Arbitrary subscriber-defined object. * @property {?Object} userData - Arbitrary subscriber-defined object.
*/ */
this.viewer.raiseEvent( 'constrain', { this.viewer.raiseEvent( 'constrain', {
immediately: immediately immediately: immediately
}); });
} }
return newBounds;
}, },
/** /**
@ -567,8 +575,8 @@ $.Viewport.prototype = {
} }
var bounds = this.getBoundsNoRotate(); var bounds = this.getBoundsNoRotate();
var constrainedBounds = this._applyBoundaryConstraints( var constrainedBounds = this._applyBoundaryConstraints(bounds);
bounds, immediately); this._raiseConstraintsEvent(immediately);
if (bounds.x !== constrainedBounds.x || if (bounds.x !== constrainedBounds.x ||
bounds.y !== constrainedBounds.y || bounds.y !== constrainedBounds.y ||
@ -638,8 +646,9 @@ $.Viewport.prototype = {
newBounds.y = center.y - newBounds.height / 2; newBounds.y = center.y - newBounds.height / 2;
} }
newBounds = this._applyBoundaryConstraints(newBounds, immediately); newBounds = this._applyBoundaryConstraints(newBounds);
center = newBounds.getCenter(); center = newBounds.getCenter();
this._raiseConstraintsEvent(immediately);
} }
if (immediately) { if (immediately) {
@ -733,6 +742,23 @@ $.Viewport.prototype = {
}, },
/**
* Returns bounds taking constraints into account
* Added to improve constrained panning
* @param {Boolean} current - Pass true for the current location; defaults to false (target location).
* @return {OpenSeadragon.Viewport} Chainable.
*/
getConstrainedBounds: function(current) {
var bounds,
constrainedBounds;
bounds = this.getBounds(current);
constrainedBounds = this._applyBoundaryConstraints(bounds);
return constrainedBounds;
},
/** /**
* @function * @function
* @param {OpenSeadragon.Point} delta * @param {OpenSeadragon.Point} delta
@ -806,6 +832,7 @@ $.Viewport.prototype = {
* @fires OpenSeadragon.Viewer.event:zoom * @fires OpenSeadragon.Viewer.event:zoom
*/ */
zoomTo: function(zoom, refPoint, immediately) { zoomTo: function(zoom, refPoint, immediately) {
var _this = this;
this.zoomPoint = refPoint instanceof $.Point && this.zoomPoint = refPoint instanceof $.Point &&
!isNaN(refPoint.x) && !isNaN(refPoint.x) &&
@ -814,7 +841,9 @@ $.Viewport.prototype = {
null; null;
if (immediately) { if (immediately) {
this.zoomSpring.resetTo( zoom ); this._adjustCenterSpringsForZoomPoint(function() {
_this.zoomSpring.resetTo(zoom);
});
} else { } else {
this.zoomSpring.springTo(zoom); this.zoomSpring.springTo(zoom);
} }
@ -938,25 +967,10 @@ $.Viewport.prototype = {
* @returns {Boolean} True if any change has been made, false otherwise. * @returns {Boolean} True if any change has been made, false otherwise.
*/ */
update: function() { update: function() {
var _this = this;
if (this.zoomPoint) { this._adjustCenterSpringsForZoomPoint(function() {
var oldZoomPixel = this.pixelFromPoint(this.zoomPoint, true); _this.zoomSpring.update();
this.zoomSpring.update(); });
var newZoomPixel = this.pixelFromPoint(this.zoomPoint, true);
var deltaZoomPixels = newZoomPixel.minus(oldZoomPixel);
var deltaZoomPoints = this.deltaPointsFromPixels(
deltaZoomPixels, true);
this.centerSpringX.shiftBy(deltaZoomPoints.x);
this.centerSpringY.shiftBy(deltaZoomPoints.y);
if (this.zoomSpring.isAtTargetValue()) {
this.zoomPoint = null;
}
} else {
this.zoomSpring.update();
}
this.centerSpringX.update(); this.centerSpringX.update();
this.centerSpringY.update(); this.centerSpringY.update();
@ -972,6 +986,27 @@ $.Viewport.prototype = {
return changed; return changed;
}, },
_adjustCenterSpringsForZoomPoint: function(zoomSpringHandler) {
if (this.zoomPoint) {
var oldZoomPixel = this.pixelFromPoint(this.zoomPoint, true);
zoomSpringHandler();
var newZoomPixel = this.pixelFromPoint(this.zoomPoint, true);
var deltaZoomPixels = newZoomPixel.minus(oldZoomPixel);
var deltaZoomPoints = this.deltaPointsFromPixels(
deltaZoomPixels, true);
this.centerSpringX.shiftBy(deltaZoomPoints.x);
this.centerSpringY.shiftBy(deltaZoomPoints.y);
if (this.zoomSpring.isAtTargetValue()) {
this.zoomPoint = null;
}
} else {
zoomSpringHandler();
}
},
/** /**
* Convert a delta (translation vector) from viewport coordinates to pixels * Convert a delta (translation vector) from viewport coordinates to pixels
* coordinates. This method does not take rotation into account. * coordinates. This method does not take rotation into account.
@ -1208,6 +1243,7 @@ $.Viewport.prototype = {
* in image coordinate system. * in image coordinate system.
* @param {Number} [pixelWidth] the width in pixel of the rectangle. * @param {Number} [pixelWidth] the width in pixel of the rectangle.
* @param {Number} [pixelHeight] the height in pixel of the rectangle. * @param {Number} [pixelHeight] the height in pixel of the rectangle.
* @returns {OpenSeadragon.Rect} This image's bounds in viewport coordinates
*/ */
imageToViewportRectangle: function(imageX, imageY, pixelWidth, pixelHeight) { imageToViewportRectangle: function(imageX, imageY, pixelWidth, pixelHeight) {
var rect = imageX; var rect = imageX;

View File

@ -18,7 +18,7 @@
* tilesUrl: "/test/data/zoomify/" * tilesUrl: "/test/data/zoomify/"
* } * }
* *
* The tileSize is currently hardcoded to 256 (the usual Zoomify default). The tileUrl must the the path to the image _directory_. * The tileSize is currently hardcoded to 256 (the usual Zoomify default). The tileUrl must the path to the image _directory_.
* *
* 2) Loading image metadata from xml file: (CURRENTLY NOT SUPPORTED) * 2) Loading image metadata from xml file: (CURRENTLY NOT SUPPORTED)
* *

View File

@ -10,6 +10,11 @@
<body> <body>
<div id="qunit"></div> <div id="qunit"></div>
<div id="qunit-fixture"></div> <div id="qunit-fixture"></div>
<script>
var isCoverageTest = true;
</script>
<script src="/node_modules/qunitjs/qunit/qunit.js"></script> <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-1.9.1.min.js"></script>
<script src="/test/lib/jquery-ui-1.10.2/js/jquery-ui-1.10.2.min.js"></script> <script src="/test/lib/jquery-ui-1.10.2/js/jquery-ui-1.10.2.min.js"></script>
@ -80,6 +85,8 @@
<script src="/test/modules/tilesourcecollection.js"></script> <script src="/test/modules/tilesourcecollection.js"></script>
<script src="/test/modules/spring.js"></script> <script src="/test/modules/spring.js"></script>
<script src="/test/modules/rectangle.js"></script> <script src="/test/modules/rectangle.js"></script>
<script src="/test/modules/ajax-tiles.js"></script>
<script src="/test/modules/imageloader.js"></script>
<!-- The navigator tests are the slowest (for now; hopefully they can be sped up) <!-- The navigator tests are the slowest (for now; hopefully they can be sped up)
so we put them last. --> so we put them last. -->
<script src="/test/modules/navigator.js"></script> <script src="/test/modules/navigator.js"></script>

BIN
test/data/testpattern.blob Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 508 KiB

View File

@ -0,0 +1,48 @@
<!DOCTYPE html>
<html>
<head>
<title>OpenSeadragon fitBoundsWithConstraints() Demo</title>
<script type="text/javascript" src='../../build/openseadragon/openseadragon.js'></script>
<style type="text/css">
.openseadragon1 {
width: 800px;
height: 600px;
}
#highlights li {
cursor: pointer;
}
</style>
</head>
<body>
<div>
<p>Simple demo to see panning improvements using the following settings:</p>
<ul>
<li>homeFillsViewer: true</li>
<li>showZoomControl: false,</li>
<li>constrainDuringPan: true,</li>
<li>visibilityRatio: 1,</li>
</ul>
</div>
<div id="contentDiv" class="openseadragon1"></div>
<script type="text/javascript">
var _viewer;
var _viewer = OpenSeadragon({
element: document.getElementById("contentDiv"),
tileSources: "https://openseadragon.github.io/example-images/duomo/duomo.dzi",
homeFillsViewer: true,
showZoomControl: false,
constrainDuringPan: true,
visibilityRatio: 1,
prefixUrl: "../../build/openseadragon/images/",
minZoomImageRatio: 1
});
</script>
</body>
</html>

View File

@ -0,0 +1,75 @@
<!DOCTYPE html>
<html>
<head>
<title>OpenSeadragon Custom Request Headers 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">
.osd-viewer {
width: 800px;
height: 600px;
}
</style>
</head>
<body>
<p>
Demo of how the loadTilesWithAjax and ajaxHeaders options as well as the getTileHeaders() method on TileSource can be applied.
</p>
<p>
Examine the network requests in your browser developer tools to see the custom headers sent with each request.
</p>
<div id="contentDiv" class="osd-viewer"></div>
<script type="text/javascript">
// These values are generated by a script that concatenates all the tile files and records
// their byte ranges in a multi-dimensional array.
var tileManifest = {"tileRanges":[[[[0,3467]]],[[[3467,6954]]],[[[344916,348425]]],[[[348425,351948]]],[[[351948,355576]]],[[[355576,359520]]],[[[359520,364663]]],[[[364663,374196]]],[[[374196,407307]]],[[[407307,435465],[435465,463663]],[[463663,491839],[491839,520078]]],[[[6954,29582],[29582,50315],[50315,71936],[71936,92703]],[[92703,113385],[113385,133265],[133265,154763],[154763,175710]],[[175710,197306],[197306,218807],[218807,242177],[242177,263007]],[[263007,283790],[283790,304822],[304822,325691],[325691,344916]]]],"totalSize":520078}
// This tile source demonstrates how you can retrieve individual tiles from a single file
// using the Range header.
var myCustomTileSource = {
width: 1000,
height: 1000,
tileWidth: 254,
tileHeight: 254,
tileOverlap: 1,
maxLevel: 10,
minLevel: 0,
// The tile URL is always the same. Only the Range header changes
getTileUrl: function () {
return "/test/data/testpattern.blob";
},
// This method will send the appropriate range header for this tile based on the data
// in tileByteRanges.
getTileAjaxHeaders: function(level, x, y) {
return {
Range: "bytes=" + tileManifest.tileRanges[level][x][y].join("-") + "/" + tileManifest.totalSize
};
},
};
var viewer = OpenSeadragon({
id: "contentDiv",
prefixUrl: "../../build/openseadragon/images/",
showNavigator: true,
loadTilesWithAjax: true,
ajaxHeaders: {
// Example of using the viewer-level ajaxHeaders option
// for providing an authentication token.
"Authentication": "Bearer MY_AUTH_TOKEN"
}
});
viewer.addTiledImage({
// The headers specified here will be combined with those in the Viewer object (if any)
ajaxHeaders: {
"X-My-TiledImage-Header": "Something"
},
tileSource: myCustomTileSource
});
</script>
</body>
</html>

View File

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html>
<head>
<title>Iframe example embed</title>
<script type="text/javascript" src='../../build/openseadragon/openseadragon.js'></script>
<meta name="viewport" content="initial-scale=1.0">
<meta charset="utf-8">
<style>
html, body { height: 100%; margin: 0; padding: 0; background-color: white;}
</style>
</head>
<body>
<div id="contentDiv" allowfullscreen style="height: 100%"> </div>
<script type="text/javascript">
var _viewer = OpenSeadragon({
element: document.getElementById("contentDiv"),
tileSources: "https://openseadragon.github.io/example-images/duomo/duomo.dzi",
homeFillsViewer: true,
showZoomControl: false,
constrainDuringPan: true,
visibilityRatio: 1,
prefixUrl: "../../build/openseadragon/images/",
minZoomImageRatio: 1
});
</script>
</body>
</html>

View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<title>Iframe example</title>
<script type="text/javascript" src='../../build/openseadragon/openseadragon.js'></script>
<meta name="viewport" content="initial-scale=1.0">
<meta charset="utf-8">
<style>
html, body { height: 100%; margin: 0; padding: 0; background-color: white;}
</style>
</head>
<body>
<iframe src="iframe-embed.html" frameborder="0" width="560" height="315" allowfullscreen></iframe>
</body>
</html>

View File

@ -0,0 +1,44 @@
<!DOCTYPE html>
<html>
<head>
<title>OpenSeadragon Basic Demo</title>
<script type="text/javascript" src='../../build/openseadragon/openseadragon.js'></script>
<script type="text/javascript" src='../lib/jquery-1.9.1.min.js'></script>
<style type="text/css">
.openseadragon1 {
width: 800px;
height: 600px;
}
</style>
</head>
<body>
<div>
Turn debug mode on and off after viewer has been created.
<button onclick="debugModeOn()">Debug Mode On</button>
<button onclick="debugModeOff()">Debug Mode Off</button>
</div>
<div id="contentDiv" class="openseadragon1"></div>
<script type="text/javascript">
var viewer = OpenSeadragon({
// debugMode: true,
id: "contentDiv",
prefixUrl: "../../build/openseadragon/images/",
tileSources: "../data/testpattern.dzi",
showNavigator: true,
debugMode: true
});
function debugModeOn() {
viewer.setDebugMode(true);
}
function debugModeOff() {
viewer.setDebugMode(false);
}
</script>
</body>
</html>

38
test/demo/zoomify.html Normal file
View File

@ -0,0 +1,38 @@
<!DOCTYPE html>
<html>
<head>
<title>OpenSeadragon Zoomify Demo</title>
<script type="text/javascript" src='../../build/openseadragon/openseadragon.js'></script>
<script type="text/javascript" src='../lib/jquery-1.9.1.min.js'></script>
<style type="text/css">
.openseadragon1 {
width: 800px;
height: 600px;
}
</style>
</head>
<body>
<div>
Simple demo page to show a default OpenSeadragon viewer with a Zoomify tile source.
</div>
<div id="contentDiv" class="openseadragon1"></div>
<script type="text/javascript">
var viewer = OpenSeadragon({
debugMode: true,
id: "contentDiv",
prefixUrl: "../../build/openseadragon/images/",
tileSources: [{
type: "zoomifytileservice",
width: 1000,
height: 1000,
tilesUrl: "../data/zoomify/"
}],
showNavigator:true
});
</script>
</body>
</html>

242
test/modules/ajax-tiles.js Normal file
View File

@ -0,0 +1,242 @@
/* global module, asyncTest, start, $, ok, equal, deepEqual, testLog */
(function() {
var viewer;
// These values are generated by a script that concatenates all the tile files and records
// their byte ranges in a multi-dimensional array.
// eslint-disable-next-line
var tileManifest = {"tileRanges":[[[[0,3467]]],[[[3467,6954]]],[[[344916,348425]]],[[[348425,351948]]],[[[351948,355576]]],[[[355576,359520]]],[[[359520,364663]]],[[[364663,374196]]],[[[374196,407307]]],[[[407307,435465],[435465,463663]],[[463663,491839],[491839,520078]]],[[[6954,29582],[29582,50315],[50315,71936],[71936,92703]],[[92703,113385],[113385,133265],[133265,154763],[154763,175710]],[[175710,197306],[197306,218807],[218807,242177],[242177,263007]],[[263007,283790],[283790,304822],[304822,325691],[325691,344916]]]],"totalSize":520078}
function getTileRangeHeader(level, x, y) {
return 'bytes=' + tileManifest.tileRanges[level][x][y].join('-') + '/' + tileManifest.totalSize;
}
// This tile source demonstrates how you can retrieve individual tiles from a single file
// using the Range header.
var customTileSource = {
width: 1000,
height: 1000,
tileWidth: 254,
tileHeight: 254,
tileOverlap: 1,
maxLevel: 10,
minLevel: 0,
// The tile URL is always the same. Only the Range header changes
getTileUrl: function () {
return '/test/data/testpattern.blob';
},
// This method will send the appropriate range header for this tile based on the data
// in tileByteRanges.
getTileAjaxHeaders: function(level, x, y) {
return {
Range: getTileRangeHeader(level, x, y)
};
},
};
module('AJAX-Tiles', {
setup: function() {
$('<div id="example"></div>').appendTo('#qunit-fixture');
testLog.reset();
viewer = OpenSeadragon({
id: 'example',
prefixUrl: '/build/openseadragon/images/',
springStiffness: 100, // Faster animation = faster tests,
loadTilesWithAjax: true,
ajaxHeaders: {
'X-Viewer-Header': 'ViewerHeaderValue'
}
});
},
teardown: function() {
if (viewer && viewer.close) {
viewer.close();
}
viewer = null;
}
});
asyncTest('tile-loaded event includes AJAX request object', function() {
var tileLoaded = function tileLoaded(evt) {
viewer.removeHandler('tile-loaded', tileLoaded);
ok(evt.tileRequest, 'Event includes tileRequest property');
equal(evt.tileRequest.readyState, XMLHttpRequest.DONE, 'tileRequest is in completed state');
start();
};
viewer.addHandler('tile-loaded', tileLoaded);
viewer.open(customTileSource);
});
asyncTest('withCredentials is set in tile AJAX requests', function() {
var tileLoaded = function tileLoaded(evt) {
viewer.removeHandler('tile-loaded', tileLoaded);
ok(evt.tileRequest, 'Event includes tileRequest property');
equal(evt.tileRequest.readyState, XMLHttpRequest.DONE, 'tileRequest is in completed state');
equal(evt.tileRequest.withCredentials, true, 'withCredentials is set in tile request');
start();
};
viewer.addHandler('tile-loaded', tileLoaded);
viewer.addTiledImage({
tileSource: customTileSource,
ajaxWithCredentials: true
});
});
asyncTest('tile-load-failed event includes AJAX request object', function() {
// Create a tile source that points to a broken URL
var brokenTileSource = OpenSeadragon.extend({}, customTileSource, {
getTileUrl: function () {
return '/test/data/testpattern.blob.invalid';
}
});
var tileLoadFailed = function tileLoadFailed(evt) {
viewer.removeHandler('tile-load-failed', tileLoadFailed);
ok(evt.tileRequest, 'Event includes tileRequest property');
equal(evt.tileRequest.readyState, XMLHttpRequest.DONE, 'tileRequest is in completed state');
start();
};
viewer.addHandler('tile-load-failed', tileLoadFailed);
viewer.open(brokenTileSource);
});
asyncTest('Headers can be set per-tile', function() {
var tileLoaded = function tileLoaded(evt) {
viewer.removeHandler('tile-loaded', tileLoaded);
var tile = evt.tile;
ok(tile, 'tile property exists on event');
ok(tile.ajaxHeaders, 'Tile has ajaxHeaders property');
equal(tile.ajaxHeaders.Range, getTileRangeHeader(tile.level, tile.x, tile.y), 'Tile has correct range header.');
start();
};
viewer.addHandler('tile-loaded', tileLoaded);
viewer.open(customTileSource);
});
asyncTest('Headers are propagated correctly', function() {
// Create a tile source that sets a static header for tiles
var staticHeaderTileSource = OpenSeadragon.extend({}, customTileSource, {
getTileAjaxHeaders: function() {
return {
'X-Tile-Header': 'TileHeaderValue'
};
}
});
var expectedHeaders = {
'X-Viewer-Header': 'ViewerHeaderValue',
'X-TiledImage-Header': 'TiledImageHeaderValue',
'X-Tile-Header': 'TileHeaderValue'
};
var tileLoaded = function tileLoaded(evt) {
viewer.removeHandler('tile-loaded', tileLoaded);
var tile = evt.tile;
ok(tile, 'tile property exists on event');
ok(tile.ajaxHeaders, 'Tile has ajaxHeaders property');
deepEqual(
tile.ajaxHeaders, expectedHeaders,
'Tile headers include headers set on Viewer and TiledImage'
);
start();
};
viewer.addHandler('tile-loaded', tileLoaded);
viewer.addTiledImage({
ajaxHeaders: {
'X-TiledImage-Header': 'TiledImageHeaderValue'
},
tileSource: staticHeaderTileSource
});
});
asyncTest('Viewer headers are overwritten by TiledImage', function() {
// Create a tile source that sets a static header for tiles
var staticHeaderTileSource = OpenSeadragon.extend({}, customTileSource, {
getTileAjaxHeaders: function() {
return {
'X-Tile-Header': 'TileHeaderValue'
};
}
});
var expectedHeaders = {
'X-Viewer-Header': 'ViewerHeaderValue-Overwritten',
'X-TiledImage-Header': 'TiledImageHeaderValue',
'X-Tile-Header': 'TileHeaderValue'
};
var tileLoaded = function tileLoaded(evt) {
viewer.removeHandler('tile-loaded', tileLoaded);
var tile = evt.tile;
ok(tile, 'tile property exists on event');
ok(tile.ajaxHeaders, 'Tile has ajaxHeaders property');
deepEqual(
tile.ajaxHeaders, expectedHeaders,
'TiledImage header overwrites viewer header'
);
start();
};
viewer.addHandler('tile-loaded', tileLoaded);
viewer.addTiledImage({
ajaxHeaders: {
'X-TiledImage-Header': 'TiledImageHeaderValue',
'X-Viewer-Header': 'ViewerHeaderValue-Overwritten'
},
tileSource: staticHeaderTileSource
});
});
asyncTest('TiledImage headers are overwritten by Tile', function() {
var expectedHeaders = {
'X-Viewer-Header': 'ViewerHeaderValue',
'X-TiledImage-Header': 'TiledImageHeaderValue-Overwritten',
'X-Tile-Header': 'TileHeaderValue'
};
var tileLoaded = function tileLoaded(evt) {
viewer.removeHandler('tile-loaded', tileLoaded);
var tile = evt.tile;
ok(tile, 'tile property exists on event');
ok(tile.ajaxHeaders, 'Tile has ajaxHeaders property');
deepEqual(
tile.ajaxHeaders, expectedHeaders,
'Tile header overwrites TiledImage header'
);
start();
};
viewer.addHandler('tile-loaded', tileLoaded);
// Create a tile source that sets a static header for tiles
var staticHeaderTileSource = OpenSeadragon.extend({}, customTileSource, {
getTileAjaxHeaders: function() {
return {
'X-TiledImage-Header': 'TiledImageHeaderValue-Overwritten',
'X-Tile-Header': 'TileHeaderValue'
};
}
});
viewer.addTiledImage({
ajaxHeaders: {
'X-TiledImage-Header': 'TiledImageHeaderValue'
},
tileSource: staticHeaderTileSource
});
});
})();

View File

@ -424,11 +424,47 @@
} ); } );
asyncTest('SetDebugMode', function() {
ok(viewer, 'Viewer exists');
var checkImageTilesDebugState = function (expectedState) {
for (var i = 0; i < viewer.world.getItemCount(); i++) {
if(viewer.world.getItemAt(i).debugMode != expectedState) {
return false;
}
}
return true;
};
var openHandler = function(event) {
viewer.removeHandler('open', openHandler);
//Ensure we start with debug mode turned off
viewer.setDebugMode(false);
ok(checkImageTilesDebugState(false), "All image tiles have debug mode turned off.");
ok(!viewer.debugMode, "Viewer debug mode is turned off.");
//Turn debug mode on and check that the Viewer and all tiled images are in debug mode.
viewer.setDebugMode(true);
ok(checkImageTilesDebugState(true), "All image tiles have debug mode turned on.");
ok(viewer.debugMode, "Viewer debug mode is turned on.");
start();
};
viewer.addHandler('open', openHandler);
viewer.open('/test/data/testpattern.dzi');
});
//Version numbers are injected by the build process, so skip version tests if we are only running code coverage
if(!window.isCoverageTest ){
test('version object', function() { test('version object', function() {
equal(typeof OpenSeadragon.version.versionStr, "string", "versionStr should be a string"); equal(typeof OpenSeadragon.version.versionStr, "string", "versionStr should be a string");
ok(OpenSeadragon.version.major >= 0, "major should be a positive number"); ok(OpenSeadragon.version.major >= 0, "major should be a positive number");
ok(OpenSeadragon.version.minor >= 0, "minor should be a positive number"); ok(OpenSeadragon.version.minor >= 0, "minor should be a positive number");
ok(OpenSeadragon.version.revision >= 0, "revision should be a positive number"); ok(OpenSeadragon.version.revision >= 0, "revision should be a positive number");
}); });
}
})(); })();

View File

@ -36,6 +36,9 @@
testImplicitTilesUrl( testImplicitTilesUrl(
'/iiipsrv?DeepZoom=/path/my.dzi', '/iiipsrv?DeepZoom=/path/my_files/', '/iiipsrv?DeepZoom=/path/my.dzi', '/iiipsrv?DeepZoom=/path/my_files/',
'querystring in dzi url should not be ignored before slashes'); 'querystring in dzi url should not be ignored before slashes');
testImplicitTilesUrl(
'/fcg-bin/iipsrv.fcgi?Deepzoom=123test.tif.dzi', '/fcg-bin/iipsrv.fcgi?Deepzoom=123test.tif_files/',
'filename in querystring does not have to contain slash');
}); });
}()); }());

View File

@ -0,0 +1,88 @@
/* global module, asyncTest, $, ok, equal, notEqual, start, test, Util, testLog */
(function() {
var viewer,
baseOptions = {
id: 'example',
prefixUrl: '/build/openseadragon/images/',
springStiffness: 100 // Faster animation = faster tests
};
module('ImageLoader', {
setup: function () {
var example = $('<div id="example"></div>').appendTo("#qunit-fixture");
testLog.reset();
},
teardown: function () {
if (viewer && viewer.close) {
viewer.close();
}
viewer = null;
}
});
// ----------
test('Default timeout', function() {
var actual,
expected = OpenSeadragon.DEFAULT_SETTINGS.timeout,
message,
options = OpenSeadragon.extend(true, baseOptions, {
imageLoaderLimit: 1
}),
viewer = OpenSeadragon(options),
imageLoader = viewer.imageLoader;
message = 'ImageLoader timeout should be set to the default value of ' + expected + ' when none is specified';
actual = imageLoader.timeout;
equal(actual, expected, message);
// Manually seize the ImageLoader
imageLoader.jobsInProgress = imageLoader.jobLimit;
imageLoader.addJob({
src: 'test',
loadWithAjax: false,
crossOriginPolicy: 'test',
ajaxWithCredentials: false,
abort: function() {}
});
message = 'ImageJob should inherit the ImageLoader timeout value';
actual = imageLoader.jobQueue.shift().timeout;
equal(actual, expected, message);
});
// ----------
test('Configure timeout', function() {
var actual,
expected = 123456,
message,
options = OpenSeadragon.extend(true, baseOptions, {
imageLoaderLimit: 1,
timeout: expected
}),
viewer = OpenSeadragon(options),
imageLoader = viewer.imageLoader;
message = 'ImageLoader timeout should be configurable';
actual = imageLoader.timeout;
equal(actual, expected, message);
imageLoader.jobsInProgress = imageLoader.jobLimit;
imageLoader.addJob({
src: 'test',
loadWithAjax: false,
crossOriginPolicy: 'test',
ajaxWithCredentials: false,
abort: function() {}
});
message = 'ImageJob should inherit the ImageLoader timeout value';
actual = imageLoader.jobQueue.shift().timeout;
equal(actual, expected, message);
});
})();

View File

@ -844,6 +844,88 @@
viewer.addHandler('open', openHandler); viewer.addHandler('open', openHandler);
}); });
asyncTest('Item opacity is synchronized', function() {
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
});
var navOpenHandler = function(event) {
if (viewer.navigator.world.getItemCount() === 2) {
viewer.navigator.world.removeHandler('add-item', navOpenHandler);
setTimeout(function() {
// Test initial formation
for (var i = 0; i < 2; i++) {
equal(viewer.navigator.world.getItemAt(i).getOpacity(),
viewer.world.getItemAt(i).getOpacity(), 'opacity is the same');
}
// Try changing the opacity of one
viewer.world.getItemAt(1).setOpacity(0.5);
equal(viewer.navigator.world.getItemAt(1).getOpacity(),
viewer.world.getItemAt(1).getOpacity(), 'opacity is the same after change');
start();
}, 1);
}
};
var openHandler = function() {
viewer.removeHandler('open', openHandler);
viewer.navigator.world.addHandler('add-item', navOpenHandler);
// The navigator may already have added the items.
navOpenHandler();
};
viewer.addHandler('open', openHandler);
});
asyncTest('Item composite operation is synchronized', function() {
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
});
var navOpenHandler = function(event) {
if (viewer.navigator.world.getItemCount() === 2) {
viewer.navigator.world.removeHandler('add-item', navOpenHandler);
setTimeout(function() {
// Test initial formation
for (var i = 0; i < 2; i++) {
equal(viewer.navigator.world.getItemAt(i).getCompositeOperation(),
viewer.world.getItemAt(i).getCompositeOperation(), 'composite operation is the same');
}
// Try changing the composite operation of one
viewer.world.getItemAt(1).setCompositeOperation('multiply');
equal(viewer.navigator.world.getItemAt(1).getCompositeOperation(),
viewer.world.getItemAt(1).getCompositeOperation(), 'composite operation is the same after change');
start();
}, 1);
}
};
var openHandler = function() {
viewer.removeHandler('open', openHandler);
viewer.navigator.world.addHandler('add-item', navOpenHandler);
// The navigator may already have added the items.
navOpenHandler();
};
viewer.addHandler('open', openHandler);
});
asyncTest('Viewer options transmitted to navigator', function() { asyncTest('Viewer options transmitted to navigator', function() {
viewer = OpenSeadragon({ viewer = OpenSeadragon({

View File

@ -25,12 +25,14 @@
var fakeTile0 = { var fakeTile0 = {
url: 'foo.jpg', url: 'foo.jpg',
cacheKey: 'foo.jpg',
image: {}, image: {},
unload: function() {} unload: function() {}
}; };
var fakeTile1 = { var fakeTile1 = {
url: 'foo.jpg', url: 'foo.jpg',
cacheKey: 'foo.jpg',
image: {}, image: {},
unload: function() {} unload: function() {}
}; };
@ -74,18 +76,21 @@
var fakeTile0 = { var fakeTile0 = {
url: 'different.jpg', url: 'different.jpg',
cacheKey: 'different.jpg',
image: {}, image: {},
unload: function() {} unload: function() {}
}; };
var fakeTile1 = { var fakeTile1 = {
url: 'same.jpg', url: 'same.jpg',
cacheKey: 'same.jpg',
image: {}, image: {},
unload: function() {} unload: function() {}
}; };
var fakeTile2 = { var fakeTile2 = {
url: 'same.jpg', url: 'same.jpg',
cacheKey: 'same.jpg',
image: {}, image: {},
unload: function() {} unload: function() {}
}; };

View File

@ -36,7 +36,7 @@
var TALL_PATH = '/test/data/tall.dzi'; var TALL_PATH = '/test/data/tall.dzi';
var WIDE_PATH = '/test/data/wide.dzi'; var WIDE_PATH = '/test/data/wide.dzi';
var testZoomLevels = [-1, 0, 0.1, 0.5, 4, 10]; var testZoomLevels = [0.1, 0.2, 0.5, 1, 4, 10];
var testPoints = [ var testPoints = [
new OpenSeadragon.Point(0, 0), new OpenSeadragon.Point(0, 0),
@ -59,7 +59,6 @@
var reopenViewerHelper = function(config) { var reopenViewerHelper = function(config) {
var expected, level, actual, i = 0; var expected, level, actual, i = 0;
var openHandler = function(event) { var openHandler = function(event) {
viewer.removeHandler('open', openHandler);
var viewport = viewer.viewport; var viewport = viewer.viewport;
expected = config.processExpected(level, expected); expected = config.processExpected(level, expected);
actual = viewport[config.method](); actual = viewport[config.method]();
@ -80,15 +79,22 @@
viewerConfig[config.property] = level; viewerConfig[config.property] = level;
viewer = OpenSeadragon(viewerConfig); viewer = OpenSeadragon(viewerConfig);
viewer.addHandler('open', openHandler); viewer.addOnceHandler('open', openHandler);
viewer.open(DZI_PATH); viewer.open(DZI_PATH);
} else { } else {
start(); start();
} }
}; };
viewer.addHandler('open', openHandler);
level = expected = testZoomLevels[i]; level = expected = testZoomLevels[i];
viewer[config.property] = level; var viewerConfig = {
id: VIEWER_ID,
prefixUrl: PREFIX_URL,
springStiffness: SPRING_STIFFNESS
};
viewerConfig[config.property] = level;
viewer = OpenSeadragon(viewerConfig);
viewer.addOnceHandler('open', openHandler);
viewer.open(DZI_PATH); viewer.open(DZI_PATH);
}; };
@ -211,15 +217,9 @@
property: 'defaultZoomLevel', property: 'defaultZoomLevel',
method: 'getHomeBounds', method: 'getHomeBounds',
processExpected: function(level, expected) { processExpected: function(level, expected) {
// Have to special case this to avoid dividing by 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 var sideLength = 1.0 / viewer.defaultZoomLevel; // it's a square in this case
var position = 0.5 - (sideLength / 2.0); var position = 0.5 - (sideLength / 2.0);
expected = new OpenSeadragon.Rect(position, position, sideLength, sideLength); return new OpenSeadragon.Rect(position, position, sideLength, sideLength);
}
return expected;
} }
}); });
}); });
@ -333,44 +333,39 @@
// I don't use the helper for this one because it sets a couple more // I don't use the helper for this one because it sets a couple more
// properties that would need special casing. // properties that would need special casing.
asyncTest('getHomeZoomWithHomeFillsViewer', function() { asyncTest('getHomeZoomWithHomeFillsViewer', function() {
var expected, level, i = 0; var i = 0;
var openHandler = function(event) { var openHandler = function(event) {
viewer.removeHandler('open', openHandler);
var viewport = viewer.viewport; var viewport = viewer.viewport;
viewport.zoomTo(ZOOM_FACTOR, null, true); viewport.zoomTo(ZOOM_FACTOR, null, true);
// Special cases for oddball levels
if (level === -1) {
expected = 0.25;
} else if(level === 0){
expected = 1;
}
equal( equal(
viewport.getHomeZoom(), viewport.getHomeZoom(),
expected, testZoomLevels[i],
"Test getHomeZoom with homeFillsViewer = true and default zoom level of " + expected "Test getHomeZoom with homeFillsViewer = true and default zoom level of " + testZoomLevels[i]
); );
i++; i++;
if (i < testZoomLevels.length) { if (i < testZoomLevels.length) {
level = expected = testZoomLevels[i];
viewer = OpenSeadragon({ viewer = OpenSeadragon({
id: VIEWER_ID, id: VIEWER_ID,
prefixUrl: PREFIX_URL, prefixUrl: PREFIX_URL,
springStiffness: SPRING_STIFFNESS, springStiffness: SPRING_STIFFNESS,
defaultZoomLevel: level, defaultZoomLevel: testZoomLevels[i],
homeFillsViewer: true homeFillsViewer: true
}); });
viewer.addHandler('open', openHandler); viewer.addOnceHandler('open', openHandler);
viewer.open(TALL_PATH); // use a different image for homeFillsViewer viewer.open(TALL_PATH); // use a different image for homeFillsViewer
} else { } else {
start(); start();
} }
}; };
viewer.addHandler('open', openHandler); viewer = OpenSeadragon({
level = expected = testZoomLevels[i]; id: VIEWER_ID,
viewer.homeFillsViewer = true; prefixUrl: PREFIX_URL,
viewer.defaultZoomLevel = expected; springStiffness: SPRING_STIFFNESS,
defaultZoomLevel: testZoomLevels[i],
homeFillsViewer: true
});
viewer.addOnceHandler('open', openHandler);
viewer.open(TALL_PATH); // use a different image for homeFillsViewer viewer.open(TALL_PATH); // use a different image for homeFillsViewer
}); });
@ -725,7 +720,7 @@
viewer.open(DZI_PATH); viewer.open(DZI_PATH);
}); });
asyncTest('zoomBy', function(){ asyncTest('zoomBy no ref point', function() {
var openHandler = function(event) { var openHandler = function(event) {
viewer.removeHandler('open', openHandler); viewer.removeHandler('open', openHandler);
var viewport = viewer.viewport; var viewport = viewer.viewport;
@ -737,15 +732,6 @@
testZoomLevels[i], testZoomLevels[i],
"Zoomed by the correct amount." "Zoomed by the correct amount."
); );
// now use a ref point
// TODO: check the ending position due to ref point
viewport.zoomBy(testZoomLevels[i], testPoints[i], true);
propEqual(
viewport.getZoom(),
testZoomLevels[i],
"Zoomed by the correct amount."
);
} }
start(); start();
@ -754,7 +740,42 @@
viewer.open(DZI_PATH); viewer.open(DZI_PATH);
}); });
asyncTest('zoomTo', function(){ asyncTest('zoomBy with ref point', function() {
var openHandler = function(event) {
viewer.removeHandler('open', openHandler);
var viewport = viewer.viewport;
var expectedCenters = [
new OpenSeadragon.Point(5, 5),
new OpenSeadragon.Point(6.996, 6.996),
new OpenSeadragon.Point(7.246, 6.996),
new OpenSeadragon.Point(7.246, 6.996),
new OpenSeadragon.Point(7.621, 7.371),
new OpenSeadragon.Point(7.621, 7.371),
];
for (var i = 0; i < testZoomLevels.length; i++) {
viewport.zoomBy(testZoomLevels[i], testPoints[i], true);
propEqual(
viewport.getZoom(),
testZoomLevels[i],
"Zoomed by the correct amount."
);
assertPointsEquals(
viewport.getCenter(),
expectedCenters[i],
1e-14,
"Panned to the correct location."
);
}
start();
};
viewer.addHandler('open', openHandler);
viewer.open(DZI_PATH);
});
asyncTest('zoomTo no ref point', function() {
var openHandler = function(event) { var openHandler = function(event) {
viewer.removeHandler('open', openHandler); viewer.removeHandler('open', openHandler);
var viewport = viewer.viewport; var viewport = viewer.viewport;
@ -766,15 +787,41 @@
testZoomLevels[i], testZoomLevels[i],
"Zoomed to the correct level." "Zoomed to the correct level."
); );
}
// now use a ref point start();
// TODO: check the ending position due to ref point };
viewer.addHandler('open', openHandler);
viewer.open(DZI_PATH);
});
asyncTest('zoomTo with ref point', function() {
var openHandler = function(event) {
viewer.removeHandler('open', openHandler);
var viewport = viewer.viewport;
var expectedCenters = [
new OpenSeadragon.Point(5, 5),
new OpenSeadragon.Point(4.7505, 4.7505),
new OpenSeadragon.Point(4.6005, 4.7505),
new OpenSeadragon.Point(4.8455, 4.9955),
new OpenSeadragon.Point(5.2205, 5.3705),
new OpenSeadragon.Point(5.2205, 5.3705),
];
for (var i = 0; i < testZoomLevels.length; i++) {
viewport.zoomTo(testZoomLevels[i], testPoints[i], true); viewport.zoomTo(testZoomLevels[i], testPoints[i], true);
propEqual( propEqual(
viewport.getZoom(), viewport.getZoom(),
testZoomLevels[i], testZoomLevels[i],
"Zoomed to the correct level." "Zoomed to the correct level."
); );
assertPointsEquals(
viewport.getCenter(),
expectedCenters[i],
1e-14,
"Panned to the correct location."
);
} }
start(); start();

View File

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