Merge remote-tracking branch 'origin/master' into ms-vscode

* origin/master: (249 commits)
  Fixed test issues
  Changelog for #837
  Changelog for #1004
  Use control anchor configuration for custom toolbar also
  changelog for #999
  Fix tests on Firefox.
  Make tileSources option smarter about detecting when a json string or xml string has been passed in
  Updated with improved API
  Changelog for #987
  [-] remove ref to getTileAtPoint - becasue this are subclass of TileSource
  [=] fix for image source type image and in legacytilesource
  [=] Dropped useless calculations
  [=] Wrap fix for #555
  Changelog for #981
  Passed options.crossOriginPolicy into getTileSource. Also changed checks on crossOriginPolicy to compare to undefined, and added tests for the addTiledImage crossOriginPolicy api.
  Changelog for #984
  Add support for commonjs.
  Added an option to addTiledImage to change the crossOriginPolicy. addTiledImage will also check the tileSource for crossOriginPolicy.
  Changelog for #967
  Moved setting of withCredentials to after the request is opened to fix IE 10 bug.
  ...
This commit is contained in:
Mark Salsbery 2016-08-25 18:27:07 -07:00
commit 8e2358288b
64 changed files with 5862 additions and 1815 deletions

4
.gitignore vendored
View File

@ -4,4 +4,6 @@ build/
sftp-config.json
coverage/
temp/
.idea
.idea
/nbproject/private/
.directory

View File

@ -10,6 +10,7 @@
"globals": {
"OpenSeadragon": true,
"define": false
"define": false,
"module": false
}
}

View File

@ -1,5 +1,6 @@
language: node_js
sudo: false
node_js:
- 0.10
before_script:
- "stable"
before_install:
- npm install -g grunt-cli

77
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,77 @@
### Contributing
OpenSeadragon is truly a community project; we welcome your involvement!
When contributing, please attempt to match the code style already in the codebase.
However, we are in the process of changing our code style (see issue [#456](https://github.com/openseadragon/openseadragon/issues/456)), so avoid spaces inside parentheses and square brackets. Note that we use four spaces per indentation stop. For easier setup you can also install [EditorConfig](http://editorconfig.org/) if your IDE is supported. For more thoughts on code style, see [idiomatic.js](https://github.com/rwldrn/idiomatic.js/).
When fixing bugs and adding features, when appropriate please also:
* Update related doc comments (we use [JSDoc 3](http://usejsdoc.org/))
* Add/update related unit tests
If you're new to the project, check out our [good first bug](https://github.com/openseadragon/openseadragon/issues?labels=good+first+bug&page=1&state=open) issues for some places to dip your toe in the water.
If you're new to open source in general, check out [GitHub's open source intro guide](https://guides.github.com/overviews/os-contributing/).
### First Time Setup
All command-line operations for building and testing OpenSeadragon are scripted using [Grunt](http://gruntjs.com/) which is based on [Node.js](http://nodejs.org/). To get set up:
1. Install Node, if you haven't already (available at the link above)
1. Install the Grunt command line runner (if you haven't already); on the command line, run `npm install -g grunt-cli`
1. Clone the openseadragon repository
1. On the command line, go in to the openseadragon folder
1. Run `npm install`
You're set, all development dependencies should have been installed and the project built...
continue reading for build and test instructions.
### Building from Source
To build, just run (on the command line, in the openseadragon folder):
grunt
If you want Grunt to watch your source files and rebuild every time you change one, use:
grunt watch
To have it watch your source files and also run a server for you to test in:
grunt dev
The built files appear in the `build` folder.
If you want to build tar and zip files for distribution (they will also appear in the `build` folder), use:
grunt package
Note that the `build` folder is masked with .gitignore; it's just for your local use, and won't be checked in to the repository.
You can also publish the built version to the site-build repository. This assumes you have cloned it next to this repository. The command is:
grunt publish
... which will delete the existing openseadragon folder, along with the .zip and .tar.gz files, out of the site-build folder and replace them with newly built ones from the source in this repository; you'll then need to commit the changes to site-build.
### Testing
Our tests are based on [QUnit](http://qunitjs.com/) and [PhantomJS](http://phantomjs.org/); they're both installed when you run `npm install`. To run on the command line:
grunt test
If you wish to work interactively with the tests or test your changes:
grunt connect watch
and open `http://localhost:8000/test/test.html` in your browser.
Another good page, if you want to interactively test out your changes, is `http://localhost:8000/test/demo/basic.html`.
You can also get a report of the tests' code coverage:
grunt coverage
The report shows up at `coverage/html/index.html` viewable in a browser.

View File

@ -28,6 +28,7 @@ module.exports = function(grunt) {
"src/mousetracker.js",
"src/control.js",
"src/controldock.js",
"src/placement.js",
"src/viewer.js",
"src/navigator.js",
"src/strings.js",
@ -39,6 +40,7 @@ module.exports = function(grunt) {
"src/osmtilesource.js",
"src/tmstilesource.js",
"src/legacytilesource.js",
"src/imagetilesource.js",
"src/tilesourcecollection.js",
"src/button.js",
"src/buttongroup.js",
@ -56,6 +58,12 @@ module.exports = function(grunt) {
"src/world.js"
];
var banner = "//! <%= pkg.name %> <%= pkg.version %>\n" +
"//! Built on <%= grunt.template.today('yyyy-mm-dd') %>\n" +
"//! Git commit: <%= gitInfo %>\n" +
"//! http://openseadragon.github.io\n" +
"//! License: http://openseadragon.github.io/license/\n\n";
// ----------
grunt.event.once('git-describe', function (rev) {
grunt.config.set('gitInfo', rev);
@ -84,12 +92,9 @@ module.exports = function(grunt) {
},
concat: {
options: {
banner: "//! <%= pkg.name %> <%= pkg.version %>\n" +
"//! Built on <%= grunt.template.today('yyyy-mm-dd') %>\n" +
"//! Git commit: <%= gitInfo %>\n" +
"//! http://openseadragon.github.io\n" +
"//! License: http://openseadragon.github.io/license/\n\n",
process: true
banner: banner,
process: true,
sourceMap: true
},
dist: {
src: [ "<banner>" ].concat(sources),
@ -110,12 +115,17 @@ module.exports = function(grunt) {
},
uglify: {
options: {
preserveComments: "some",
preserveComments: false,
banner: banner,
compress: {
sequences: false,
join_vars: false
},
sourceMap: true,
sourceMapName: 'build/openseadragon/openseadragon.min.js.map'
},
openseadragon: {
src: [ distribution ],
src: sources,
dest: minified
}
},

View File

@ -1,9 +1,9 @@
# OpenSeadragon
[![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/openseadragon/openseadragon?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/openseadragon/openseadragon?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Build Status](https://secure.travis-ci.org/openseadragon/openseadragon.png?branch=master)](http://travis-ci.org/openseadragon/openseadragon)
An open-source, web-based viewer for zoomable images, implemented in pure JavaScript.
See it in action and get started using it at http://openseadragon.github.io/.
See it in action and get started using it at [http://openseadragon.github.io/](http://openseadragon.github.io/).
## Stable Builds
@ -11,85 +11,8 @@ See the [GitHub releases page](https://github.com/openseadragon/openseadragon/re
## Development
If you want to use OpenSeadragon in your own projects, you can find the latest stable build, API documentation, and example code at http://openseadragon.github.io/. If you want to modify OpenSeadragon and/or contribute to its development, read on.
### First Time Setup
All command-line operations for building and testing OpenSeadragon are scripted using [Grunt](http://gruntjs.com/) which is based on [Node.js](http://nodejs.org/). To get set up:
1. Install Node, if you haven't already (available at the link above)
1. Install the Grunt command line runner (if you haven't already); on the command line, run `npm install -g grunt-cli`
1. Clone the openseadragon repository
1. On the command line, go in to the openseadragon folder
1. Run `npm install`
You're set... continue reading for build and test instructions.
### Building from Source
To build, just run (on the command line, in the openseadragon folder):
grunt
If you want Grunt to watch your source files and rebuild every time you change one, use:
grunt watch
To have it watch your source files and also run a server for you to test in:
grunt dev
The built files appear in the `build` folder.
If you want to build tar and zip files for distribution (they will also appear in the `build` folder), use:
grunt package
Note that the `build` folder is masked with .gitignore; it's just for your local use, and won't be checked in to the repository.
You can also publish the built version to the site-build repository. This assumes you have cloned it next to this repository. The command is:
grunt publish
... which will delete the existing openseadragon folder, along with the .zip and .tar.gz files, out of the site-build folder and replace them with newly built ones from the source in this repository; you'll then need to commit the changes to site-build.
### Testing
Our tests are based on [QUnit](http://qunitjs.com/) and [PhantomJS](http://phantomjs.org/); they're both installed when you run `npm install`. At the moment we don't have much in the way of tests, but we're working to fix that. To run on the command line:
grunt test
If you wish to work interactively with the tests or test your changes:
grunt connect watch
and open `http://localhost:8000/test/test.html` in your browser.
Another good page, if you want to interactively test out your changes, is `http://localhost:8000/test/demo/basic.html`.
You can also get a report of the tests' code coverage:
grunt coverage
The report shows up at `coverage/html/index.html` viewable in a browser.
### Contributing
OpenSeadragon is truly a community project; we welcome your involvement!
When contributing, please attempt to match the code style already in the codebase. Note that we use four spaces per indentation stop. For easier setup you can also install [EditorConfig](http://editorconfig.org/) if your IDE is supported. For more thoughts on code style, see [idiomatic.js](https://github.com/rwldrn/idiomatic.js/).
When fixing bugs and adding features, when appropriate please also:
* Update related doc comments (we use [JSDoc 3](http://usejsdoc.org/))
* Add/update related unit tests
If you're new to the project, check out our [good first bug](https://github.com/openseadragon/openseadragon/issues?labels=good+first+bug&page=1&state=open) issues for some places to dip your toe in the water.
If you're new to open source in general, check out [GitHub's open source intro guide](https://guides.github.com/overviews/os-contributing/).
If you want to use OpenSeadragon in your own projects, you can find the latest stable build, API documentation, and example code at [http://openseadragon.github.io/](http://openseadragon.github.io/). If you want to modify OpenSeadragon and/or contribute to its development, read the [contributing guide](https://github.com/openseadragon/openseadragon/blob/master/CONTRIBUTING.md) for instructions.
## License
OpenSeadragon is released under the New BSD license. For details, see the file LICENSE.txt.
[![Build Status](https://secure.travis-ci.org/openseadragon/openseadragon.png?branch=master)](http://travis-ci.org/openseadragon/openseadragon)
OpenSeadragon is released under the New BSD license. For details, see the [LICENSE.txt file](https://github.com/openseadragon/openseadragon/blob/master/LICENSE.txt).

View File

@ -1,36 +1,107 @@
OPENSEADRAGON CHANGELOG
=======================
2.1.0: (in progress)
2.2.2: (in progress)
* Fixed CORS bug in IE 10 (#967)
* Added support for commonjs (#984)
* 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)
* The Viewer's tileSources option is now smarter about detecting JSON vs XML vs URL (#999)
* 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)
2.2.1:
* Fixed problems with zoom/pan constraints with certain extreme settings (#965)
* Fixed an issue causing the browser to crash on iOS (#966)
2.2.0:
* BREAKING CHANGE: Viewport.homeBounds, Viewport.contentSize, Viewport.contentAspectX and
Viewport.contentAspectY have been removed. (#846)
* BREAKING CHANGE: The Overlay.getBounds method now takes the viewport as parameter. (#896)
* DEPRECATION: Overlay.scales, Overlay.bounds and Overlay.position have been deprecated. (#896)
* Overlay.width !== null should be used to test whether the overlay scales horizontally
* Overlay.height !== null should be used to test whether the overlay scales vertically
* The Overlay.getBounds method should be used to get the bounds of the overlay in viewport coordinates
* Overlay.location replaces Overlay.position
* DEPRECATION: Viewport.setHomeBounds has been deprecated (#846)
* DEPRECATION: the Viewport constructor is now ignoring the contentSize option (#846)
* Tile edge smoothing at high zoom (#764)
* Fixed issue with reference strip popping up virtual keyboard on mobile devices (#779)
* Now supporting rotation in the Rect class (#782)
* Drag outside of iframe now works better, as long as both pages are on the same domain (#790)
* Coordinate conversion now takes rotation into account (#796)
* Support tile-less IIIF as per LegacyTileSource (#816)
* You can now give an empty string to the tabIndex option (#805)
* Fixed issue with rotation and clicking in the navigator (#807)
* Broadened the check for mime type in LegacyTileSource URLs to allow query strings (#819)
* Added globalCompositeOperation option for tiledImage, to allow for different transfer modes (#814)
* Added Viewer.addSimpleImage method for easily adding non-tiled images (#827)
* DziTileSource now works properly with DZI files that have no extension (#835)
* Fixed content clipping with rotation (#463, #567 and #833)
* Fixed navigator not being rotated when viewport rotation is set in constructor (#840)
* Fixed: Viewer.setMouseNavEnabled wasn't affecting all of the viewer's trackers (#845)
* Fixed: with scrollToZoom disabled, the viewer caused page scrolling to slow down (#858)
* Added Viewer.getOverlayById and Overlay.getBounds functions (#853)
* Tiled images with 0 opacity no longer load their tiles or do drawing calculations (#859)
* Fixed issue with edge smoothing with PNG tiles at high zoom (#860)
* Fixed: Images with transparency were clearing images layered below them (#861)
* Fixed issue causing HTML pages to jump unwantedly to the reference strip upon loading (#872)
* Added addOnceHandler method to EventSource (#887)
* Added TiledImage.fitBounds method (#888)
* Overlays can now be scaled in a single dimension by providing a point location and either width or height (#896)
* Added full rotation support to overlays (#729, #193)
* Viewport.goHome() now takes clipping into account (#910)
* Improved zoom to point (#923)
* Optimized sketch canvas clearing and blending for images with opacity or transfer modes (#927)
* Now taking rotation into account in viewport getBounds and fitBounds methods (#934)
* Added option to disable navigator auto-fade (#935)
* Fixed issue with maintaining viewport position with full screen (#940)
* Fixed an issue with simultaneous touch events (#930)
* Avoid loading clipped out tiles (#939)
* Improved precision for subtle moves with fitBounds (#939)
* Fixed an issue in viewer.addTiledImage with replace:true when viewer has navigator (#948)
2.1.0:
* BREAKING CHANGE: the tile does not hold a reference to its image anymore. Only the tile cache keep a reference to images.
* BREAKING CHANGE: TileSource.tileSize no longer exists; use TileSource.getTileWidth() and TileSource.getTileHeight() instead.
* DEPRECATION: let ImageRecord.getRenderedContext create the rendered context instead of using ImageRecord.setRenderedContext
* DEPRECATION: TileSource.getTileSize() is deprecated. Use TileSource.getTileWidth() and TileSource.getTileHeight() instead.
* Added "tile-loaded" event on the viewer allowing to modify a tile before it is marked ready to be drawn (#659)
* Added "tile-unloaded" event on the viewer allowing to free up memory one has allocated on a tile (#659)
* Fixed flickering tiles with useCanvas=false when no cache is used (#661)
* Added additional coordinates conversion methods to TiledImage (#662)
* 'display: none' no longer gets reset on overlays during draw (#668)
* Added `preserveImageSizeOnResize` option (#666)
* Better error reporting for tile load failures (#679)
* Added collectionColumns as a configuration parameter (#680)
* Changed resize behaviour to prevent "snapping" to world bounds when constraints allow more space (#711)
* Added support for non-square tiles (#673)
* TileSource.Options objects can now optionally provide tileWidth/tileHeight instead of tileSize for non-square tile support.
* IIIFTileSources will now respect non-square tiles if available.
* Added new tile source for simple images: ImageTileSource (#760)
* Optimized adding large numbers of items to the world with collectionMode (#735)
* Registers as an AMD module where possible (#719)
* Added "tile-loaded" event on the viewer allowing to modify a tile before it is marked ready to be drawn (#659)
* Added "tile-unloaded" event on the viewer allowing to free up memory one has allocated on a tile (#659)
* Added 'tile-load-failed' event (#725)
* Added additional coordinates conversion methods to TiledImage (#662)
* Added `preserveImageSizeOnResize` option (#666)
* Added collectionColumns as a configuration parameter (#680)
* Added option in addTiledImage to replace tiledImage at index (#706)
* Added autoRefigureSizes flag to World for optimizing mass rearrangements (#715)
* You can now change viewport margins after the viewer is created (#721)
* Added a patch to help slow down the scroll devices that fire too fast (#754)
* Fixed flickering tiles with useCanvas=false when no cache is used (#661)
* 'display: none' no longer gets reset on overlays during draw (#668)
* Better error reporting for tile load failures (#679)
* Added XDomainRequest as fallback method for ajax requests if XMLHttpRequest fails (for IE < 10) (#693)
* Now avoiding using eval when JSON.parse is available (#696)
* Rotation now works properly on retina display (#708)
* Added option in addTiledImage to replace tiledImage at index (#706)
* Changed resize behaviour to prevent "snapping" to world bounds when constraints allow more space (#711)
* Registers as an AMD module where possible (#719)
* Added autoRefigureSizes flag to World for optimizing mass rearrangements (#715)
* Added 'tile-load-failed' event (#725)
* Fixed issue with tiledImages loading tiles at every level instead of just the best level (#728)
* Fixed placeholderFillStyle flicker (#727)
* Fix for Chrome (v45) issue that key is sometimes undefined outside of the for-in loop (#730)
* World.removeAll now cancels any in-flight image loads; same for Viewer.open and Viewer.close (#734)
* Optimized adding large numbers of items to the world with collectionMode (#735)
* Fixed overlays position (use rounding instead of flooring and ceiling) (#741)
* Fixed issue with including overlays in your tileSources array when creating/opening in the viewer (#745)
* Fixed issue in iOS devices that would cause all touch events to fail after a Multitasking Gesture was triggered (#744)
* Fixed an issue with TiledImage setPosition/setWidth/setHeight not reliably triggering a redraw (#720)
* Fixed zooming in with plus key on a Swedish keyboard (#763)
2.0.0:

View File

@ -0,0 +1,20 @@
auxiliary.org-netbeans-modules-editor-indent.CodeStyle.project.expand-tabs=true
auxiliary.org-netbeans-modules-editor-indent.CodeStyle.project.indent-shift-width=4
auxiliary.org-netbeans-modules-editor-indent.CodeStyle.project.spaces-per-tab=4
auxiliary.org-netbeans-modules-editor-indent.CodeStyle.project.tab-size=8
auxiliary.org-netbeans-modules-editor-indent.CodeStyle.project.text-limit-width=80
auxiliary.org-netbeans-modules-editor-indent.CodeStyle.project.text-line-wrap=none
auxiliary.org-netbeans-modules-editor-indent.CodeStyle.usedProfile=project
auxiliary.org-netbeans-modules-editor-indent.text.javascript.CodeStyle.project.continuationIndentSize=4
auxiliary.org-netbeans-modules-editor-indent.text.javascript.CodeStyle.project.indent-shift-width=4
auxiliary.org-netbeans-modules-editor-indent.text.javascript.CodeStyle.project.spaceBeforeAnonMethodDeclParen=false
auxiliary.org-netbeans-modules-editor-indent.text.x-json.CodeStyle.project.indent-shift-width=2
auxiliary.org-netbeans-modules-editor-indent.text.x-json.CodeStyle.project.spaces-per-tab=2
auxiliary.org-netbeans-modules-web-clientproject-api.js_2e_libs_2e_folder=src
browser.autorefresh.Chromium.INTEGRATED=true
browser.highlightselection.Chromium.INTEGRATED=true
file.reference.openseadragon-openseadragon=.
files.encoding=UTF-8
site.root.folder=${file.reference.openseadragon-openseadragon}
start.file=test/demo/basic.html
web.context.root=/

9
nbproject/project.xml Normal file
View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://www.netbeans.org/ns/project/1">
<type>org.netbeans.modules.web.clientproject</type>
<configuration>
<data xmlns="http://www.netbeans.org/ns/clientside-project/1">
<name>openseadragon</name>
</data>
</configuration>
</project>

View File

@ -1,22 +1,35 @@
{
"name": "OpenSeadragon",
"version": "2.0.0",
"name": "openseadragon",
"version": "2.2.1",
"description": "Provides a smooth, zoomable user interface for HTML/Javascript.",
"keywords": ["image", "zoom", "pan", "openseadragon", "seadragon", "deepzoom", "dzi", "iiif", "osm", "tms"],
"homepage": "http://openseadragon.github.io/",
"bugs": {
"url": "https://github.com/openseadragon/openseadragon/issues"
},
"license": "BSD-3-Clause",
"files": ["build/openseadragon/"],
"main": "build/openseadragon/openseadragon.js",
"repository": {
"type" : "git",
"url" : "https://github.com/openseadragon/openseadragon.git"
},
"devDependencies": {
"grunt": "^0.4.5",
"grunt-contrib-clean": "^0.5.0",
"grunt-contrib-compress": "^0.9.1",
"grunt-contrib-concat": "^0.4.0",
"grunt-contrib-connect": "^0.7.1",
"grunt-contrib-jshint": "^0.10.0",
"grunt-contrib-uglify": "^0.4.0",
"grunt-contrib-clean": "^0.7.0",
"grunt-contrib-compress": "^0.13.0",
"grunt-contrib-concat": "^0.5.1",
"grunt-contrib-connect": "^0.11.2",
"grunt-contrib-jshint": "^0.11.0",
"grunt-contrib-uglify": "^0.11.0",
"grunt-contrib-watch": "^0.6.1",
"grunt-git-describe": "^2.3.2",
"grunt-qunit-istanbul": "^0.5.0",
"grunt-text-replace": "^0.3.11",
"qunitjs": "^1.18.0"
"grunt-qunit-istanbul": "^0.6.0",
"grunt-text-replace": "^0.4.0",
"qunitjs": "^1.20.0"
},
"scripts": {
"test": "grunt test"
"test": "grunt test",
"prepublish": "grunt build"
}
}

View File

@ -108,7 +108,8 @@ $.ButtonGroup = function( options ) {
});
};
$.ButtonGroup.prototype = /** @lends OpenSeadragon.ButtonGroup.prototype */{
/** @lends OpenSeadragon.ButtonGroup.prototype */
$.ButtonGroup.prototype = {
/**
* TODO: Figure out why this is used on the public API and if a more useful

View File

@ -152,7 +152,8 @@ $.Control = function ( element, options, container ) {
}
};
$.Control.prototype = /** @lends OpenSeadragon.Control.prototype */{
/** @lends OpenSeadragon.Control.prototype */
$.Control.prototype = {
/**
* Removes the control from the container.

View File

@ -88,7 +88,8 @@
this.container.appendChild( this.controls.bottomleft );
};
$.ControlDock.prototype = /** @lends OpenSeadragon.ControlDock.prototype */{
/** @lends OpenSeadragon.ControlDock.prototype */
$.ControlDock.prototype = {
/**
* @function

View File

@ -132,7 +132,8 @@ $.Drawer = function( options ) {
this.container.appendChild( this.canvas );
};
$.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{
/** @lends OpenSeadragon.Drawer.prototype */
$.Drawer.prototype = {
// deprecated
addOverlay: function( element, location, placement, onDraw ) {
$.console.error("drawer.addOverlay is deprecated. Use viewer.addOverlay instead.");
@ -249,31 +250,37 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{
this.canvas.width = viewportSize.x;
this.canvas.height = viewportSize.y;
if ( this.sketchCanvas !== null ) {
this.sketchCanvas.width = this.canvas.width;
this.sketchCanvas.height = this.canvas.height;
var sketchCanvasSize = this._calculateSketchCanvasSize();
this.sketchCanvas.width = sketchCanvasSize.x;
this.sketchCanvas.height = sketchCanvasSize.y;
}
}
this._clear();
}
},
_clear: function ( useSketch ) {
if ( !this.useCanvas ) {
_clear: function (useSketch, bounds) {
if (!this.useCanvas) {
return;
}
var context = this._getContext( useSketch );
var canvas = context.canvas;
context.clearRect( 0, 0, canvas.width, canvas.height );
var context = this._getContext(useSketch);
if (bounds) {
context.clearRect(bounds.x, bounds.y, bounds.width, bounds.height);
} else {
var canvas = context.canvas;
context.clearRect(0, 0, canvas.width, canvas.height);
}
},
/**
* Translates from OpenSeadragon viewer rectangle to drawer rectangle.
* Scale from OpenSeadragon viewer rectangle to drawer rectangle
* (ignoring rotation)
* @param {OpenSeadragon.Rect} rectangle - The rectangle in viewport coordinate system.
* @return {OpenSeadragon.Rect} Rectangle in drawer coordinate system.
*/
viewportToDrawerRectangle: function(rectangle) {
var topLeft = this.viewport.pixelFromPoint(rectangle.getTopLeft(), true);
var size = this.viewport.deltaPixelsFromPoints(rectangle.getSize(), true);
var topLeft = this.viewport.pixelFromPointNoRotate(rectangle.getTopLeft(), true);
var size = this.viewport.deltaPixelsFromPointsNoRotate(rectangle.getSize(), true);
return new $.Rect(
topLeft.x * $.pixelDensityRatio,
@ -290,22 +297,17 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{
* drawingHandler({context, tile, rendered})
* @param {Boolean} useSketch - Whether to use the sketch canvas or not.
* where <code>rendered</code> is the context with the pre-drawn image.
* @param {Float} [scale=1] - Apply a scale to tile position and size. Defaults to 1.
* @param {OpenSeadragon.Point} [translate] A translation vector to offset tile position
*/
drawTile: function( tile, drawingHandler, useSketch ) {
drawTile: function(tile, drawingHandler, useSketch, scale, translate) {
$.console.assert(tile, '[Drawer.drawTile] tile is required');
$.console.assert(drawingHandler, '[Drawer.drawTile] drawingHandler is required');
if ( this.useCanvas ) {
var context = this._getContext( useSketch );
// TODO do this in a more performant way
// specifically, don't save,rotate,restore every time we draw a tile
if( this.viewport.degrees !== 0 ) {
this._offsetForRotation( tile, this.viewport.degrees, useSketch );
tile.drawCanvas( context, drawingHandler );
this._restoreRotationChanges( tile, useSketch );
} else {
tile.drawCanvas( context, drawingHandler );
}
if (this.useCanvas) {
var context = this._getContext(useSketch);
scale = scale || 1;
tile.drawCanvas(context, drawingHandler, scale, translate);
} else {
tile.drawHTML( this.canvas );
}
@ -316,9 +318,23 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{
if ( useSketch ) {
if (this.sketchCanvas === null) {
this.sketchCanvas = document.createElement( "canvas" );
this.sketchCanvas.width = this.canvas.width;
this.sketchCanvas.height = this.canvas.height;
var sketchCanvasSize = this._calculateSketchCanvasSize();
this.sketchCanvas.width = sketchCanvasSize.x;
this.sketchCanvas.height = sketchCanvasSize.y;
this.sketchContext = this.sketchCanvas.getContext( "2d" );
// If the viewport is not currently rotated, the sketchCanvas
// will have the same size as the main canvas. However, if
// the viewport get rotated later on, we will need to resize it.
if (this.viewport.getRotation() === 0) {
var self = this;
this.viewer.addHandler('rotate', function resizeSketchCanvas() {
self.viewer.removeHandler('rotate', resizeSketchCanvas);
var sketchCanvasSize = self._calculateSketchCanvasSize();
self.sketchCanvas.width = sketchCanvasSize.x;
self.sketchCanvas.height = sketchCanvasSize.y;
});
}
}
context = this.sketchContext;
}
@ -370,17 +386,80 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{
/**
* Blends the sketch canvas in the main canvas.
* @param {Float} opacity The opacity of the blending.
* @returns {undefined}
* @param {Object} options The options
* @param {Float} options.opacity The opacity of the blending.
* @param {Float} [options.scale=1] The scale at which tiles were drawn on
* the sketch. Default is 1.
* Use scale to draw at a lower scale and then enlarge onto the main canvas.
* @param {OpenSeadragon.Point} [options.translate] A translation vector
* that was used to draw the tiles
* @param {String} [options.compositeOperation] - How the image is
* composited onto other images; see compositeOperation in
* {@link OpenSeadragon.Options} for possible values.
* @param {OpenSeadragon.Rect} [options.bounds] The part of the sketch
* canvas to blend in the main canvas. If specified, options.scale and
* options.translate get ignored.
*/
blendSketch: function(opacity) {
blendSketch: function(opacity, scale, translate, compositeOperation) {
var options = opacity;
if (!$.isPlainObject(options)) {
options = {
opacity: opacity,
scale: scale,
translate: translate,
compositeOperation: compositeOperation
};
}
if (!this.useCanvas || !this.sketchCanvas) {
return;
}
opacity = options.opacity;
compositeOperation = options.compositeOperation;
var bounds = options.bounds;
this.context.save();
this.context.globalAlpha = opacity;
this.context.drawImage(this.sketchCanvas, 0, 0);
if (compositeOperation) {
this.context.globalCompositeOperation = compositeOperation;
}
if (bounds) {
this.context.drawImage(
this.sketchCanvas,
bounds.x,
bounds.y,
bounds.width,
bounds.height,
bounds.x,
bounds.y,
bounds.width,
bounds.height
);
} else {
scale = options.scale || 1;
translate = options.translate;
var position = translate instanceof $.Point ?
translate : new $.Point(0, 0);
var widthExt = 0;
var heightExt = 0;
if (translate) {
var widthDiff = this.sketchCanvas.width - this.canvas.width;
var heightDiff = this.sketchCanvas.height - this.canvas.height;
widthExt = Math.round(widthDiff / 2);
heightExt = Math.round(heightDiff / 2);
}
this.context.drawImage(
this.sketchCanvas,
position.x - widthExt * scale,
position.y - heightExt * scale,
(this.canvas.width + 2 * widthExt) * scale,
(this.canvas.height + 2 * heightExt) * scale,
-widthExt,
-heightExt,
this.canvas.width + 2 * widthExt,
this.canvas.height + 2 * heightExt
);
}
this.context.restore();
},
@ -398,7 +477,7 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{
context.fillStyle = this.debugGridColor;
if ( this.viewport.degrees !== 0 ) {
this._offsetForRotation( tile, this.viewport.degrees );
this._offsetForRotation(this.viewport.degrees);
}
context.strokeRect(
@ -460,7 +539,7 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{
);
if ( this.viewport.degrees !== 0 ) {
this._restoreRotationChanges( tile );
this._restoreRotationChanges();
}
context.restore();
},
@ -485,22 +564,32 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{
}
},
// private
_offsetForRotation: function( tile, degrees, useSketch ){
var cx = this.canvas.width / 2,
cy = this.canvas.height / 2;
/**
* Get the canvas size
* @param {Boolean} sketch If set to true return the size of the sketch canvas
* @returns {OpenSeadragon.Point} The size of the canvas
*/
getCanvasSize: function(sketch) {
var canvas = this._getContext(sketch).canvas;
return new $.Point(canvas.width, canvas.height);
},
var context = this._getContext( useSketch );
// private
_offsetForRotation: function(degrees, useSketch) {
var cx = this.canvas.width / 2;
var cy = this.canvas.height / 2;
var context = this._getContext(useSketch);
context.save();
context.translate(cx, cy);
context.rotate( Math.PI / 180 * degrees);
context.rotate(Math.PI / 180 * degrees);
context.translate(-cx, -cy);
},
// private
_restoreRotationChanges: function( tile, useSketch ){
var context = this._getContext( useSketch );
_restoreRotationChanges: function(useSketch) {
var context = this._getContext(useSketch);
context.restore();
},
@ -512,6 +601,23 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{
x: viewportSize.x * pixelDensityRatio,
y: viewportSize.y * pixelDensityRatio
};
},
// private
_calculateSketchCanvasSize: function() {
var canvasSize = this._calculateCanvasSize();
if (this.viewport.getRotation() === 0) {
return canvasSize;
}
// If the viewport is rotated, we need a larger sketch canvas in order
// to support edge smoothing.
var sketchCanvasSize = Math.ceil(Math.sqrt(
canvasSize.x * canvasSize.x +
canvasSize.y * canvasSize.y));
return {
x: sketchCanvasSize,
y: sketchCanvasSize
};
}
};

View File

@ -139,7 +139,8 @@ $.extend( $.DziTileSource.prototype, $.TileSource.prototype, /** @lends OpenSead
}
if (url && !options.tilesUrl) {
options.tilesUrl = url.replace(/([^\/]+)\.(dzi|xml|js)(\?.*|$)/, '$1_files/');
options.tilesUrl = url.replace(
/([^\/]+?)(\.(dzi|xml|js))?\/?(\?.*)?$/, '$1_files/');
if (url.search(/\.(dzi|xml|js)\?/) != -1) {
options.queryParams = url.match(/\?.*/);
@ -300,7 +301,9 @@ function configureFromXML( tileSource, xmlDoc ){
} else if ( rootName == "Collection" ) {
throw new Error( $.getString( "Errors.Dzc" ) );
} else if ( rootName == "Error" ) {
return $._processDZIError( root );
var messageNode = root.getElementsByTagName("Message")[0];
var message = messageNode.firstChild.nodeValue;
throw new Error(message);
}
throw new Error( $.getString( "Errors.Dzi" ) );

View File

@ -53,9 +53,34 @@ $.EventSource = function() {
this.events = {};
};
$.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
/** @lends OpenSeadragon.EventSource.prototype */
$.EventSource.prototype = {
// TODO: Add a method 'one' which automatically unbinds a listener after the first triggered event that matches.
/**
* Add an event handler to be triggered only once (or a given number of times)
* for a given event.
* @function
* @param {String} eventName - Name of event to register.
* @param {OpenSeadragon.EventHandler} handler - Function to call when event
* is triggered.
* @param {Object} [userData=null] - Arbitrary object to be passed unchanged
* to the handler.
* @param {Number} [times=1] - The number of times to handle the event
* before removing it.
*/
addOnceHandler: function(eventName, handler, userData, times) {
var self = this;
times = times || 1;
var count = 0;
var onceHandler = function(event) {
count++;
if (count === times) {
self.removeHandler(eventName, onceHandler);
}
handler(event);
};
this.addHandler(eventName, onceHandler, userData);
},
/**
* Add an event handler for a given event.

View File

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

View File

@ -110,7 +110,8 @@ $.ImageLoader = function( options ) {
};
$.ImageLoader.prototype = /** @lends OpenSeadragon.ImageLoader.prototype */{
/** @lends OpenSeadragon.ImageLoader.prototype */
$.ImageLoader.prototype = {
/**
* Add an unloaded image to the loader queue.

260
src/imagetilesource.js Normal file
View File

@ -0,0 +1,260 @@
/*
* OpenSeadragon - ImageTileSource
*
* Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2013 OpenSeadragon contributors
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of CodePlex Foundation nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
(function ($) {
/**
* @class ImageTileSource
* @classdesc The ImageTileSource allows a simple image to be loaded
* into an OpenSeadragon Viewer.
* There are 2 ways to open an ImageTileSource:
* 1. viewer.open({type: 'image', url: fooUrl});
* 2. viewer.open(new OpenSeadragon.ImageTileSource({url: fooUrl}));
*
* With the first syntax, the crossOriginPolicy, ajaxWithCredentials and
* useCanvas options are inherited from the viewer if they are not
* specified directly in the options object.
*
* @memberof OpenSeadragon
* @extends OpenSeadragon.TileSource
* @param {Object} options Options object.
* @param {String} options.url URL of the image
* @param {Boolean} [options.buildPyramid=true] If set to true (default), a
* pyramid will be built internally to provide a better downsampling.
* @param {String|Boolean} [options.crossOriginPolicy=false] Valid values are
* 'Anonymous', 'use-credentials', and false. If false, image requests will
* not use CORS preventing internal pyramid building for images from other
* domains.
* @param {String|Boolean} [options.ajaxWithCredentials=false] Whether to set
* the withCredentials XHR flag for AJAX requests (when loading tile sources).
* @param {Boolean} [options.useCanvas=true] Set to false to prevent any use
* of the canvas API.
*/
$.ImageTileSource = function (options) {
options = $.extend({
buildPyramid: true,
crossOriginPolicy: false,
ajaxWithCredentials: false,
useCanvas: true
}, options);
$.TileSource.apply(this, [options]);
};
$.extend($.ImageTileSource.prototype, $.TileSource.prototype, /** @lends OpenSeadragon.ImageTileSource.prototype */{
/**
* Determine if the data and/or url imply the image service is supported by
* this tile source.
* @function
* @param {Object|Array} data
* @param {String} optional - url
*/
supports: function (data, url) {
return data.type && data.type === "image";
},
/**
*
* @function
* @param {Object} options - the options
* @param {String} dataUrl - the url the image was retreived from, if any.
* @return {Object} options - A dictionary of keyword arguments sufficient
* to configure this tile sources constructor.
*/
configure: function (options, dataUrl) {
return options;
},
/**
* Responsible for retrieving, and caching the
* image metadata pertinent to this TileSources implementation.
* @function
* @param {String} url
* @throws {Error}
*/
getImageInfo: function (url) {
var image = this._image = new Image();
var _this = this;
if (this.crossOriginPolicy) {
image.crossOrigin = this.crossOriginPolicy;
}
if (this.ajaxWithCredentials) {
image.useCredentials = this.ajaxWithCredentials;
}
$.addEvent(image, 'load', function () {
_this.width = image.naturalWidth;
_this.height = image.naturalHeight;
_this.aspectRatio = _this.width / _this.height;
_this.dimensions = new $.Point(_this.width, _this.height);
_this._tileWidth = _this.width;
_this._tileHeight = _this.height;
_this.tileOverlap = 0;
_this.minLevel = 0;
_this.levels = _this._buildLevels();
_this.maxLevel = _this.levels.length - 1;
_this.ready = true;
// Note: this event is documented elsewhere, in TileSource
_this.raiseEvent('ready', {tileSource: _this});
});
$.addEvent(image, 'error', function () {
// Note: this event is documented elsewhere, in TileSource
_this.raiseEvent('open-failed', {
message: "Error loading image at " + url,
source: url
});
});
image.src = url;
},
/**
* @function
* @param {Number} level
*/
getLevelScale: function (level) {
var levelScale = NaN;
if (level >= this.minLevel && level <= this.maxLevel) {
levelScale =
this.levels[level].width /
this.levels[this.maxLevel].width;
}
return levelScale;
},
/**
* @function
* @param {Number} level
*/
getNumTiles: function (level) {
var scale = this.getLevelScale(level);
if (scale) {
return new $.Point(1, 1);
} else {
return new $.Point(0, 0);
}
},
/**
* Retrieves a tile url
* @function
* @param {Number} level Level of the tile
* @param {Number} x x coordinate of the tile
* @param {Number} y y coordinate of the tile
*/
getTileUrl: function (level, x, y) {
var url = null;
if (level >= this.minLevel && level <= this.maxLevel) {
url = this.levels[level].url;
}
return url;
},
/**
* Retrieves a tile context 2D
* @function
* @param {Number} level Level of the tile
* @param {Number} x x coordinate of the tile
* @param {Number} y y coordinate of the tile
*/
getContext2D: function (level, x, y) {
var context = null;
if (level >= this.minLevel && level <= this.maxLevel) {
context = this.levels[level].context2D;
}
return context;
},
// private
//
// Builds the differents levels of the pyramid if possible
// (i.e. if canvas API enabled and no canvas tainting issue).
_buildLevels: function () {
var levels = [{
url: this._image.src,
width: this._image.naturalWidth,
height: this._image.naturalHeight
}];
if (!this.buildPyramid || !$.supportsCanvas || !this.useCanvas) {
// We don't need the image anymore. Allows it to be GC.
delete this._image;
return levels;
}
var currentWidth = this._image.naturalWidth;
var currentHeight = this._image.naturalHeight;
var bigCanvas = document.createElement("canvas");
var bigContext = bigCanvas.getContext("2d");
bigCanvas.width = currentWidth;
bigCanvas.height = currentHeight;
bigContext.drawImage(this._image, 0, 0, currentWidth, currentHeight);
// We cache the context of the highest level because the browser
// is a lot faster at downsampling something it already has
// downsampled before.
levels[0].context2D = bigContext;
// We don't need the image anymore. Allows it to be GC.
delete this._image;
if ($.isCanvasTainted(bigCanvas)) {
// If the canvas is tainted, we can't compute the pyramid.
return levels;
}
// We build smaller levels until either width or height becomes
// 1 pixel wide.
while (currentWidth >= 2 && currentHeight >= 2) {
currentWidth = Math.floor(currentWidth / 2);
currentHeight = Math.floor(currentHeight / 2);
var smallCanvas = document.createElement("canvas");
var smallContext = smallCanvas.getContext("2d");
smallCanvas.width = currentWidth;
smallCanvas.height = currentHeight;
smallContext.drawImage(bigCanvas, 0, 0, currentWidth, currentHeight);
levels.splice(0, 0, {
context2D: smallContext,
width: currentWidth,
height: currentHeight
});
bigCanvas = smallCanvas;
bigContext = smallContext;
}
return levels;
}
});
}(OpenSeadragon));

View File

@ -169,16 +169,6 @@ $.extend( $.LegacyTileSource.prototype, $.TileSource.prototype, /** @lends OpenS
}
},
/**
* @function
* @param {Number} level
* @param {OpenSeadragon.Point} point
*/
getTileAtPoint: function( level, point ) {
return new $.Point( 0, 0 );
},
/**
* 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
@ -215,7 +205,7 @@ function filterFiles( files ){
if( file.height &&
file.width &&
file.url && (
file.url.toLowerCase().match(/^.*\.(png|jpg|jpeg|gif)$/) || (
file.url.toLowerCase().match(/^.*\.(png|jpg|jpeg|gif)(?:\?.*)?$/) || (
file.mimetype &&
file.mimetype.toLowerCase().match(/^.*\/(png|jpg|jpeg|gif)$/)
)

View File

@ -265,7 +265,8 @@
}
};
$.MouseTracker.prototype = /** @lends OpenSeadragon.MouseTracker.prototype */{
/** @lends OpenSeadragon.MouseTracker.prototype */
$.MouseTracker.prototype = {
/**
* Clean up any events or objects created by the tracker.
@ -1117,7 +1118,9 @@
*/
this.captureCount = 0;
};
$.MouseTracker.GesturePointList.prototype = /** @lends OpenSeadragon.MouseTracker.GesturePointList.prototype */{
/** @lends OpenSeadragon.MouseTracker.GesturePointList.prototype */
$.MouseTracker.GesturePointList.prototype = {
/**
* @function
* @returns {Number} Number of gesture points in the list.
@ -1354,11 +1357,11 @@
* @private
* @inner
*/
function capturePointer( tracker, pointerType ) {
function capturePointer( tracker, pointerType, pointerCount ) {
var pointsList = tracker.getActivePointersListByType( pointerType ),
eventParams;
pointsList.captureCount++;
pointsList.captureCount += (pointerCount || 1);
if ( pointsList.captureCount === 1 ) {
if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) {
@ -1367,6 +1370,14 @@
eventParams = getCaptureEventParams( tracker, $.MouseTracker.havePointerEvents ? 'pointerevent' : pointerType );
// We emulate mouse capture by hanging listeners on the document object.
// (Note we listen on the capture phase so the captured handlers will get called first)
if (isInIframe && canAccessEvents(window.top)) {
$.addEvent(
window.top,
eventParams.upName,
eventParams.upHandler,
true
);
}
$.addEvent(
$.MouseTracker.captureElement,
eventParams.upName,
@ -1389,11 +1400,11 @@
* @private
* @inner
*/
function releasePointer( tracker, pointerType ) {
function releasePointer( tracker, pointerType, pointerCount ) {
var pointsList = tracker.getActivePointersListByType( pointerType ),
eventParams;
pointsList.captureCount--;
pointsList.captureCount -= (pointerCount || 1);
if ( pointsList.captureCount === 0 ) {
if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) {
@ -1402,6 +1413,14 @@
eventParams = getCaptureEventParams( tracker, $.MouseTracker.havePointerEvents ? 'pointerevent' : pointerType );
// We emulate mouse capture by hanging listeners on the document object.
// (Note we listen on the capture phase so the captured handlers will get called first)
if (isInIframe && canAccessEvents(window.top)) {
$.removeEvent(
window.top,
eventParams.upName,
eventParams.upHandler,
true
);
}
$.removeEvent(
$.MouseTracker.captureElement,
eventParams.moveName,
@ -2055,7 +2074,7 @@
if ( updatePointersDown( tracker, event, gPoints, 0 ) ) { // 0 means primary button press/release or touch contact
$.stopEvent( event );
capturePointer( tracker, 'touch' );
capturePointer( tracker, 'touch', touchCount );
}
$.cancelEvent( event );
@ -2109,7 +2128,7 @@
}
if ( updatePointersUp( tracker, event, gPoints, 0 ) ) {
releasePointer( tracker, 'touch' );
releasePointer( tracker, 'touch', touchCount );
}
// simulate touchleave on our tracked element
@ -2190,16 +2209,10 @@
function onTouchCancel( tracker, event ) {
var i,
touchCount = event.changedTouches.length,
gPoints = [];
for ( i = 0; i < touchCount; i++ ) {
gPoints.push( {
id: event.changedTouches[ i ].identifier,
type: 'touch'
} );
}
updatePointersCancel( tracker, event, gPoints );
gPoints = [],
pointsList = tracker.getActivePointersListByType( 'touch' );
abortTouchContacts( tracker, event, pointsList );
}
@ -3254,5 +3267,29 @@
} );
}
}
// True if inside an iframe, otherwise false.
// @member {Boolean} isInIframe
// @private
// @inner
var isInIframe = (function() {
try {
return window.self !== window.top;
} catch (e) {
return true;
}
})();
// @function
// @private
// @inner
// @returns {Boolean} True if the target has access rights to events, otherwise false.
function canAccessEvents (target) {
try {
return target.addEventListener && target.removeEventListener;
} catch (e) {
return false;
}
}
} ( OpenSeadragon ) );

View File

@ -62,7 +62,7 @@ $.Navigator = function( options ){
options.controlOptions = {
anchor: $.ControlAnchor.TOP_RIGHT,
attachToViewer: true,
autoFade: true
autoFade: options.autoFade
};
if( options.position ){
@ -199,11 +199,18 @@ $.Navigator = function( options ){
this.displayRegionContainer.appendChild(this.displayRegion);
this.element.getElementsByTagName('div')[0].appendChild(this.displayRegionContainer);
function rotate(degrees) {
_setTransformRotate(_this.displayRegionContainer, degrees);
_setTransformRotate(_this.displayRegion, -degrees);
_this.viewport.setRotation(degrees);
}
if (options.navigatorRotate) {
var degrees = options.viewer.viewport ?
options.viewer.viewport.getRotation() :
options.viewer.degrees || 0;
rotate(degrees);
options.viewer.addHandler("rotate", function (args) {
_setTransformRotate(_this.displayRegionContainer, args.degrees);
_setTransformRotate(_this.displayRegion, -args.degrees);
_this.viewport.setRotation(args.degrees);
rotate(args.degrees);
});
}
@ -223,12 +230,6 @@ $.Navigator = function( options ){
}
});
this.addHandler("reset-size", function() {
if (_this.viewport) {
_this.viewport.goHome(true);
}
});
viewer.world.addHandler("item-index-change", function(event) {
var item = _this.world.getItemAt(event.previousIndex);
_this.world.setItemIndex(item, event.newIndex);
@ -305,10 +306,10 @@ $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /*
this.updateSize();
}
if( viewport && this.viewport ) {
bounds = viewport.getBounds( true );
topleft = this.viewport.pixelFromPoint( bounds.getTopLeft(), false );
bottomright = this.viewport.pixelFromPoint( bounds.getBottomRight(), false )
if (viewport && this.viewport) {
bounds = viewport.getBoundsNoRotate(true);
topleft = this.viewport.pixelFromPointNoRotate(bounds.getTopLeft(), false);
bottomright = this.viewport.pixelFromPointNoRotate(bounds.getBottomRight(), false)
.minus( this.totalBorderWidths );
//update style for navigator-box
@ -378,7 +379,7 @@ $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /*
*/
function onCanvasClick( event ) {
if ( event.quick && this.viewer.viewport ) {
this.viewer.viewport.panTo( this.viewport.pointFromPixel( event.position ).rotate( -this.viewer.viewport.degrees, this.viewer.viewport.getHomeBounds().getCenter() ) );
this.viewer.viewport.panTo(this.viewport.pointFromPixel(event.position));
this.viewer.viewport.applyConstraints();
}
}

View File

@ -82,28 +82,9 @@
*/
/**
* @version <%= pkg.name %> <%= pkg.version %>
*
* @file
* <h2><strong>OpenSeadragon - Javascript Deep Zooming</strong></h2>
* <p>
* OpenSeadragon provides an html interface for creating
* deep zoom user interfaces. The simplest examples include deep
* zoom for large resolution images, and complex examples include
* zoomable map interfaces driven by SVG files.
* </p>
*
*/
/**
* @module OpenSeadragon
*
*/
/**
* @namespace OpenSeadragon
*
* @version <%= pkg.name %> <%= pkg.version %>
* @classdesc The root namespace for OpenSeadragon. All utility methods
* and classes are defined on or below this namespace.
*
@ -154,7 +135,7 @@
* created.
* * placement a string to define the relative position to the viewport.
* Only used if no width and height are specified. Default: 'TOP_LEFT'.
* See {@link OpenSeadragon.OverlayPlacement} for possible values.
* See {@link OpenSeadragon.Placement} for possible values.
*
* @property {String} [xmlPath=null]
* <strong>DEPRECATED</strong>. A relative path to load a DZI file from the server.
@ -206,6 +187,11 @@
* @property {Number} [opacity=1]
* Default opacity of the tiled images (1=opaque, 0=transparent)
*
* @property {String} [compositeOperation=null]
* Valid values are 'source-over', 'source-atop', 'source-in', 'source-out',
* 'destination-over', 'destination-atop', 'destination-in',
* 'destination-out', 'lighter', 'copy' or 'xor'
*
* @property {String|CanvasGradient|CanvasPattern|Function} [placeholderFillStyle=null]
* Draws a colored rectangle behind the tile if it is not loaded yet.
* You can pass a CSS color value like "#FF8800".
@ -249,12 +235,28 @@
* image though it is less effective visually if the HTML5 Canvas is not
* availble on the viewing device.
*
* @property {Number} [smoothTileEdgesMinZoom=1.1]
* A zoom percentage ( where 1 is 100% ) of the highest resolution level.
* When zoomed in beyond this value alternative compositing will be used to
* smooth out the edges between tiles. This will have a performance impact.
* Can be set to Infinity to turn it off.
* Note: This setting is ignored on iOS devices due to a known bug (See {@link https://github.com/openseadragon/openseadragon/issues/952})
*
* @property {Boolean} [iOSDevice=?]
* True if running on an iOS device, false otherwise.
* Used to disable certain features that behave differently on iOS devices.
*
* @property {Boolean} [autoResize=true]
* Set to false to prevent polling for viewer size changes. Useful for providing custom resize behavior.
*
* @property {Boolean} [preserveImageSizeOnResize=false]
* Set to true to have the image size preserved when the viewer is resized. This requires autoResize=true (default).
*
* @property {Number} [minScrollDeltaTime=50]
* Number of milliseconds between canvas-scroll events. This value helps normalize the rate of canvas-scroll
* events between different devices, causing the faster devices to slow down enough to make the zoom control
* more manageable.
*
* @property {Number} [pixelsPerWheelLine=40]
* For pixel-resolution scrolling devices, the number of pixels equal to one scroll line.
*
@ -358,16 +360,16 @@
*
* @property {String} [navigatorId=navigator-GENERATED DATE]
* The ID of a div to hold the navigator minimap.
* If an ID is specified, the navigatorPosition, navigatorSizeRatio, navigatorMaintainSizeRatio, and navigatorTop|Left|Height|Width options will be ignored.
* If an ID is specified, the navigatorPosition, navigatorSizeRatio, navigatorMaintainSizeRatio, navigator[Top|Left|Height|Width] and navigatorAutoFade options will be ignored.
* If an ID is not specified, a div element will be generated and placed on top of the main image.
*
* @property {String} [navigatorPosition='TOP_RIGHT']
* Valid values are 'TOP_LEFT', 'TOP_RIGHT', 'BOTTOM_LEFT', 'BOTTOM_RIGHT', or 'ABSOLUTE'.<br>
* If 'ABSOLUTE' is specified, then navigatorTop|Left|Height|Width determines the size and position of the navigator minimap in the viewer, and navigatorSizeRatio and navigatorMaintainSizeRatio are ignored.<br>
* For 'TOP_LEFT', 'TOP_RIGHT', 'BOTTOM_LEFT', and 'BOTTOM_RIGHT', the navigatorSizeRatio or navigatorHeight|Width values determine the size of the navigator minimap.
* If 'ABSOLUTE' is specified, then navigator[Top|Left|Height|Width] determines the size and position of the navigator minimap in the viewer, and navigatorSizeRatio and navigatorMaintainSizeRatio are ignored.<br>
* For 'TOP_LEFT', 'TOP_RIGHT', 'BOTTOM_LEFT', and 'BOTTOM_RIGHT', the navigatorSizeRatio or navigator[Height|Width] values determine the size of the navigator minimap.
*
* @property {Number} [navigatorSizeRatio=0.2]
* Ratio of navigator size to viewer size. Ignored if navigatorHeight|Width are specified.
* Ratio of navigator size to viewer size. Ignored if navigator[Height|Width] are specified.
*
* @property {Boolean} [navigatorMaintainSizeRatio=false]
* If true, the navigator minimap is resized (using navigatorSizeRatio) when the viewer size changes.
@ -390,6 +392,10 @@
* Set to false to prevent polling for navigator size changes. Useful for providing custom resize behavior.
* Setting to false can also improve performance when the navigator is configured to a fixed size.
*
* @property {Boolean} [navigatorAutoFade=true]
* If the user stops interacting with the viewport, fade the navigator minimap.
* Setting to false will make the navigator minimap always visible.
*
* @property {Boolean} [navigatorRotate=true]
* If true, the navigator will be rotated together with the viewer.
*
@ -676,24 +682,13 @@
* This function serves as a single point of instantiation for an {@link OpenSeadragon.Viewer}, including all
* combinations of out-of-the-box configurable features.
*
* @function OpenSeadragon
* @memberof module:OpenSeadragon
* @param {OpenSeadragon.Options} options - Viewer options.
* @returns {OpenSeadragon.Viewer}
*/
window.OpenSeadragon = window.OpenSeadragon || function( options ){
function OpenSeadragon( options ){
return new OpenSeadragon.Viewer( options );
};
if (typeof define === 'function' && define.amd) {
define(function () {
return (window.OpenSeadragon);
});
}
(function( $ ){
@ -827,6 +822,21 @@ if (typeof define === 'function' && define.amd) {
return true;
};
/**
* Shim around Object.freeze. Does nothing if Object.freeze is not supported.
* @param {Object} obj The object to freeze.
* @return {Object} obj The frozen object.
*/
$.freezeObject = function(obj) {
if (Object.freeze) {
$.freezeObject = Object.freeze;
} else {
$.freezeObject = function(obj) {
return obj;
};
}
return $.freezeObject(obj);
};
/**
* True if the browser supports the HTML5 canvas element
@ -839,6 +849,23 @@ if (typeof define === 'function' && define.amd) {
canvasElement.getContext( '2d' ) );
}());
/**
* Test whether the submitted canvas is tainted or not.
* @argument {Canvas} canvas The canvas to test.
* @returns {Boolean} True if the canvas is tainted.
*/
$.isCanvasTainted = function(canvas) {
var isTainted = false;
try {
// We test if the canvas is tainted by retrieving data from it.
// An exception will be raised if the canvas is tainted.
var data = canvas.getContext('2d').getImageData(0, 0, 1, 1);
} catch (e) {
isTainted = true;
}
return isTainted;
};
/**
* A ratio comparing the device screen's pixel density to the canvas's backing store pixel density. Defaults to 1 if canvas isn't supported by the browser.
* @member {Number} pixelDensityRatio
@ -951,6 +978,18 @@ if (typeof define === 'function' && define.amd) {
return target;
};
var isIOSDevice = function () {
if (typeof navigator !== 'object') {
return false;
}
var userAgent = navigator.userAgent;
if (typeof userAgent !== 'string') {
return false;
}
return userAgent.indexOf('iPhone') !== -1 ||
userAgent.indexOf('iPad') !== -1 ||
userAgent.indexOf('iPod') !== -1;
};
$.extend( $, /** @lends OpenSeadragon */{
/**
@ -1000,9 +1039,12 @@ if (typeof define === 'function' && define.amd) {
immediateRender: false,
minZoomImageRatio: 0.9, //-> closer to 0 allows zoom out to infinity
maxZoomPixelRatio: 1.1, //-> higher allows 'over zoom' into pixels
smoothTileEdgesMinZoom: 1.1, //-> higher than maxZoomPixelRatio disables it
iOSDevice: isIOSDevice(),
pixelsPerWheelLine: 40,
autoResize: true,
preserveImageSizeOnResize: false, // requires autoResize=true
minScrollDeltaTime: 50,
//DEFAULT CONTROL SETTINGS
showSequenceControl: true, //SEQUENCE
@ -1031,6 +1073,7 @@ if (typeof define === 'function' && define.amd) {
navigatorHeight: null,
navigatorWidth: null,
navigatorAutoResize: true,
navigatorAutoFade: true,
navigatorRotate: true,
// INITIAL ROTATION
@ -1038,6 +1081,7 @@ if (typeof define === 'function' && define.amd) {
// APPEARANCE
opacity: 1,
compositeOperation: null,
placeholderFillStyle: null,
//REFERENCE STRIP SETTINGS
@ -1287,6 +1331,49 @@ if (typeof define === 'function' && define.amd) {
return window.getComputedStyle( element, "" );
},
/**
* Returns the property with the correct vendor prefix appended.
* @param {String} property the property name
* @returns {String} the property with the correct prefix or null if not
* supported.
*/
getCssPropertyWithVendorPrefix: function(property) {
var memo = {};
$.getCssPropertyWithVendorPrefix = function(property) {
if (memo[property] !== undefined) {
return memo[property];
}
var style = document.createElement('div').style;
var result = null;
if (style[property] !== undefined) {
result = property;
} else {
var prefixes = ['Webkit', 'Moz', 'MS', 'O',
'webkit', 'moz', 'ms', 'o'];
var suffix = $.capitalizeFirstLetter(property);
for (var i = 0; i < prefixes.length; i++) {
var prop = prefixes[i] + suffix;
if (style[prop] !== undefined) {
result = prop;
break;
}
}
}
memo[property] = result;
return result;
};
return $.getCssPropertyWithVendorPrefix(property);
},
/**
* Capitalizes the first letter of a string
* @param {String} string
* @returns {String} The string with the first letter capitalized
*/
capitalizeFirstLetter: function(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
},
/**
* Determines if a point is within the bounding rectangle of the given element (hit-test).
@ -2023,12 +2110,13 @@ if (typeof define === 'function' && define.amd) {
}
};
if (withCredentials) {
request.withCredentials = true;
}
try {
request.open( "GET", url, true );
if (withCredentials) {
request.withCredentials = true;
}
request.send( null );
} catch (e) {
var msg = e.message;
@ -2498,185 +2586,21 @@ if (typeof define === 'function' && define.amd) {
}
}
/**
* @private
* @inner
* @function
* @param {XMLHttpRequest} xhr
* @param {String} tilesUrl
* @deprecated
*/
function processDZIResponse( xhr, tilesUrl ) {
var status,
statusText,
doc = null;
}(OpenSeadragon));
if ( !xhr ) {
throw new Error( $.getString( "Errors.Security" ) );
} else if ( xhr.status !== 200 && xhr.status !== 0 ) {
status = xhr.status;
statusText = ( status == 404 ) ?
"Not Found" :
xhr.statusText;
throw new Error( $.getString( "Errors.Status", status, statusText ) );
}
if ( xhr.responseXML && xhr.responseXML.documentElement ) {
doc = xhr.responseXML;
} else if ( xhr.responseText ) {
doc = $.parseXml( xhr.responseText );
}
return processDZIXml( doc, tilesUrl );
// Universal Module Definition, supports CommonJS, AMD and simple script tag
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// expose as amd module
define([], factory);
} else if (typeof module === 'object' && module.exports) {
// expose as commonjs module
module.exports = factory();
} else {
// expose as window.OpenSeadragon
root.OpenSeadragon = factory();
}
/**
* @private
* @inner
* @function
* @param {Document} xmlDoc
* @param {String} tilesUrl
* @deprecated
*/
function processDZIXml( xmlDoc, tilesUrl ) {
if ( !xmlDoc || !xmlDoc.documentElement ) {
throw new Error( $.getString( "Errors.Xml" ) );
}
var root = xmlDoc.documentElement,
rootName = root.tagName;
if ( rootName == "Image" ) {
try {
return processDZI( root, tilesUrl );
} catch ( e ) {
throw (e instanceof Error) ?
e :
new Error( $.getString("Errors.Dzi") );
}
} else if ( rootName == "Collection" ) {
throw new Error( $.getString( "Errors.Dzc" ) );
} else if ( rootName == "Error" ) {
return $._processDZIError( root );
}
throw new Error( $.getString( "Errors.Dzi" ) );
}
/**
* @private
* @inner
* @function
* @param {Element} imageNode
* @param {String} tilesUrl
* @deprecated
*/
function processDZI( imageNode, tilesUrl ) {
var fileFormat = imageNode.getAttribute( "Format" ),
sizeNode = imageNode.getElementsByTagName( "Size" )[ 0 ],
dispRectNodes = imageNode.getElementsByTagName( "DisplayRect" ),
width = parseInt( sizeNode.getAttribute( "Width" ), 10 ),
height = parseInt( sizeNode.getAttribute( "Height" ), 10 ),
tileSize = parseInt( imageNode.getAttribute( "TileSize" ), 10 ),
tileOverlap = parseInt( imageNode.getAttribute( "Overlap" ), 10 ),
dispRects = [],
dispRectNode,
rectNode,
i;
if ( !$.imageFormatSupported( fileFormat ) ) {
throw new Error(
$.getString( "Errors.ImageFormat", fileFormat.toUpperCase() )
);
}
for ( i = 0; i < dispRectNodes.length; i++ ) {
dispRectNode = dispRectNodes[ i ];
rectNode = dispRectNode.getElementsByTagName( "Rect" )[ 0 ];
dispRects.push( new $.DisplayRect(
parseInt( rectNode.getAttribute( "X" ), 10 ),
parseInt( rectNode.getAttribute( "Y" ), 10 ),
parseInt( rectNode.getAttribute( "Width" ), 10 ),
parseInt( rectNode.getAttribute( "Height" ), 10 ),
0, // ignore MinLevel attribute, bug in Deep Zoom Composer
parseInt( dispRectNode.getAttribute( "MaxLevel" ), 10 )
));
}
return new $.DziTileSource(
width,
height,
tileSize,
tileOverlap,
tilesUrl,
fileFormat,
dispRects
);
}
/**
* @private
* @inner
* @function
* @param {Element} imageNode
* @param {String} tilesUrl
* @deprecated
*/
function processDZIJSON( imageData, tilesUrl ) {
var fileFormat = imageData.Format,
sizeData = imageData.Size,
dispRectData = imageData.DisplayRect || [],
width = parseInt( sizeData.Width, 10 ),
height = parseInt( sizeData.Height, 10 ),
tileSize = parseInt( imageData.TileSize, 10 ),
tileOverlap = parseInt( imageData.Overlap, 10 ),
dispRects = [],
rectData,
i;
if ( !$.imageFormatSupported( fileFormat ) ) {
throw new Error(
$.getString( "Errors.ImageFormat", fileFormat.toUpperCase() )
);
}
for ( i = 0; i < dispRectData.length; i++ ) {
rectData = dispRectData[ i ].Rect;
dispRects.push( new $.DisplayRect(
parseInt( rectData.X, 10 ),
parseInt( rectData.Y, 10 ),
parseInt( rectData.Width, 10 ),
parseInt( rectData.Height, 10 ),
0, // ignore MinLevel attribute, bug in Deep Zoom Composer
parseInt( rectData.MaxLevel, 10 )
));
}
return new $.DziTileSource(
width,
height,
tileSize,
tileOverlap,
tilesUrl,
fileFormat,
dispRects
);
}
/**
* @private
* @inner
* @function
* @param {Document} errorNode
* @throws {Error}
* @deprecated
*/
$._processDZIError = function ( errorNode ) {
var messageNode = errorNode.getElementsByTagName( "Message" )[ 0 ],
message = messageNode.firstChild.nodeValue;
throw new Error(message);
};
}( OpenSeadragon ));
}(this, function () {
return OpenSeadragon;
}));

View File

@ -32,14 +32,17 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
(function( $ ){
(function($) {
/**
* An enumeration of positions that an overlay may be assigned relative to
* the viewport.
* It is identical to OpenSeadragon.Placement but is kept for backward
* compatibility.
* @member OverlayPlacement
* @memberof OpenSeadragon
* @static
* @readonly
* @type {Object}
* @property {Number} CENTER
* @property {Number} TOP_LEFT
@ -51,17 +54,26 @@
* @property {Number} BOTTOM_LEFT
* @property {Number} LEFT
*/
$.OverlayPlacement = {
CENTER: 0,
TOP_LEFT: 1,
TOP: 2,
TOP_RIGHT: 3,
RIGHT: 4,
BOTTOM_RIGHT: 5,
BOTTOM: 6,
BOTTOM_LEFT: 7,
LEFT: 8
};
$.OverlayPlacement = $.Placement;
/**
* An enumeration of possible ways to handle overlays rotation
* @member OverlayRotationMode
* @memberOf OpenSeadragon
* @static
* @readonly
* @property {Number} NO_ROTATION The overlay ignore the viewport rotation.
* @property {Number} EXACT The overlay use CSS 3 transforms to rotate with
* the viewport. If the overlay contains text, it will get rotated as well.
* @property {Number} BOUNDING_BOX The overlay adjusts for rotation by
* taking the size of the bounding box of the rotated bounds.
* Only valid for overlays with Rect location and scalable in both directions.
*/
$.OverlayRotationMode = $.freezeObject({
NO_ROTATION: 1,
EXACT: 2,
BOUNDING_BOX: 3
});
/**
* @class Overlay
@ -72,19 +84,27 @@
* @param {Element} options.element
* @param {OpenSeadragon.Point|OpenSeadragon.Rect} options.location - The
* location of the overlay on the image. If a {@link OpenSeadragon.Point}
* is specified, the overlay will keep a constant size independently of the
* zoom. If a {@link OpenSeadragon.Rect} is specified, the overlay size will
* be adjusted when the zoom changes.
* @param {OpenSeadragon.OverlayPlacement} [options.placement=OpenSeadragon.OverlayPlacement.TOP_LEFT]
* Relative position to the viewport.
* Only used if location is a {@link OpenSeadragon.Point}.
* is specified, the overlay will be located at this location with respect
* to the placement option. If a {@link OpenSeadragon.Rect} is specified,
* the overlay will be placed at this location with the corresponding width
* and height and placement TOP_LEFT.
* @param {OpenSeadragon.Placement} [options.placement=OpenSeadragon.Placement.TOP_LEFT]
* Defines what part of the overlay should be at the specified options.location
* @param {OpenSeadragon.Overlay.OnDrawCallback} [options.onDraw]
* @param {Boolean} [options.checkResize=true] Set to false to avoid to
* check the size of the overlay everytime it is drawn when using a
* {@link OpenSeadragon.Point} as options.location. It will improve
* performances but will cause a misalignment if the overlay size changes.
* check the size of the overlay everytime it is drawn in the directions
* which are not scaled. It will improve performances but will cause a
* misalignment if the overlay size changes.
* @param {Number} [options.width] The width of the overlay in viewport
* coordinates. If specified, the width of the overlay will be adjusted when
* the zoom changes.
* @param {Number} [options.height] The height of the overlay in viewport
* coordinates. If specified, the height of the overlay will be adjusted when
* the zoom changes.
* @param {Boolean} [options.rotationMode=OpenSeadragon.OverlayRotationMode.EXACT]
* How to handle the rotation of the viewport.
*/
$.Overlay = function( element, location, placement ) {
$.Overlay = function(element, location, placement) {
/**
* onDraw callback signature used by {@link OpenSeadragon.Overlay}.
@ -97,7 +117,7 @@
*/
var options;
if ( $.isPlainObject( element ) ) {
if ($.isPlainObject(element)) {
options = element;
} else {
options = {
@ -107,72 +127,67 @@
};
}
this.element = options.element;
this.scales = options.location instanceof $.Rect;
this.bounds = new $.Rect(
options.location.x,
options.location.y,
options.location.width,
options.location.height
);
this.position = new $.Point(
options.location.x,
options.location.y
);
this.size = new $.Point(
options.location.width,
options.location.height
);
this.style = options.element.style;
// rects are always top-left
this.placement = options.location instanceof $.Point ?
options.placement :
$.OverlayPlacement.TOP_LEFT;
this.onDraw = options.onDraw;
this.checkResize = options.checkResize === undefined ?
true : options.checkResize;
this.element = options.element;
this.style = options.element.style;
this._init(options);
};
$.Overlay.prototype = /** @lends OpenSeadragon.Overlay.prototype */{
/** @lends OpenSeadragon.Overlay.prototype */
$.Overlay.prototype = {
// private
_init: function(options) {
this.location = options.location;
this.placement = options.placement === undefined ?
$.Placement.TOP_LEFT : options.placement;
this.onDraw = options.onDraw;
this.checkResize = options.checkResize === undefined ?
true : options.checkResize;
// When this.width is not null, the overlay get scaled horizontally
this.width = options.width === undefined ? null : options.width;
// When this.height is not null, the overlay get scaled vertically
this.height = options.height === undefined ? null : options.height;
this.rotationMode = options.rotationMode || $.OverlayRotationMode.EXACT;
// Having a rect as location is a syntactic sugar
if (this.location instanceof $.Rect) {
this.width = this.location.width;
this.height = this.location.height;
this.location = this.location.getTopLeft();
this.placement = $.Placement.TOP_LEFT;
}
// Deprecated properties kept for backward compatibility.
this.scales = this.width !== null && this.height !== null;
this.bounds = new $.Rect(
this.location.x, this.location.y, this.width, this.height);
this.position = this.location;
},
/**
* Internal function to adjust the position of an overlay
* depending on it size and placement.
* @function
* @param {OpenSeadragon.OverlayPlacement} position
* @param {OpenSeadragon.Point} position
* @param {OpenSeadragon.Point} size
*/
adjust: function( position, size ) {
switch ( this.placement ) {
case $.OverlayPlacement.TOP_LEFT:
break;
case $.OverlayPlacement.TOP:
position.x -= size.x / 2;
break;
case $.OverlayPlacement.TOP_RIGHT:
position.x -= size.x;
break;
case $.OverlayPlacement.RIGHT:
position.x -= size.x;
position.y -= size.y / 2;
break;
case $.OverlayPlacement.BOTTOM_RIGHT:
position.x -= size.x;
position.y -= size.y;
break;
case $.OverlayPlacement.BOTTOM:
position.x -= size.x / 2;
position.y -= size.y;
break;
case $.OverlayPlacement.BOTTOM_LEFT:
position.y -= size.y;
break;
case $.OverlayPlacement.LEFT:
position.y -= size.y / 2;
break;
default:
case $.OverlayPlacement.CENTER:
position.x -= size.x / 2;
position.y -= size.y / 2;
break;
adjust: function(position, size) {
var properties = $.Placement.properties[this.placement];
if (!properties) {
return;
}
if (properties.isHorizontallyCentered) {
position.x -= size.x / 2;
} else if (properties.isRight) {
position.x -= size.x;
}
if (properties.isVerticallyCentered) {
position.y -= size.y / 2;
} else if (properties.isBottom) {
position.y -= size.y;
}
},
@ -180,20 +195,20 @@
* @function
*/
destroy: function() {
var element = this.element,
style = this.style;
var element = this.element;
var style = this.style;
if ( element.parentNode ) {
element.parentNode.removeChild( element );
if (element.parentNode) {
element.parentNode.removeChild(element);
//this should allow us to preserve overlays when required between
//pages
if ( element.prevElementParent ) {
if (element.prevElementParent) {
style.display = 'none';
//element.prevElementParent.insertBefore(
// element,
// element.prevNextSibling
//);
document.body.appendChild( element );
document.body.appendChild(element);
}
}
@ -204,115 +219,258 @@
style.left = "";
style.position = "";
if ( this.scales ) {
if (this.width !== null) {
style.width = "";
}
if (this.height !== null) {
style.height = "";
}
var transformOriginProp = $.getCssPropertyWithVendorPrefix(
'transformOrigin');
var transformProp = $.getCssPropertyWithVendorPrefix(
'transform');
if (transformOriginProp && transformProp) {
style[transformOriginProp] = "";
style[transformProp] = "";
}
},
/**
* @function
* @param {Element} container
*/
drawHTML: function( container, viewport ) {
var element = this.element,
style = this.style,
scales = this.scales,
degrees = viewport.degrees,
position = viewport.pixelFromPoint(
this.bounds.getTopLeft(),
true
),
size,
overlayCenter;
if ( element.parentNode != container ) {
drawHTML: function(container, viewport) {
var element = this.element;
if (element.parentNode !== container) {
//save the source parent for later if we need it
element.prevElementParent = element.parentNode;
element.prevNextSibling = element.nextSibling;
container.appendChild( element );
this.size = $.getElementSize( element );
element.prevElementParent = element.parentNode;
element.prevNextSibling = element.nextSibling;
container.appendChild(element);
// this.size is used by overlays which don't get scaled in at
// least one direction when this.checkResize is set to false.
this.size = $.getElementSize(element);
}
if ( scales ) {
size = viewport.deltaPixelsFromPoints(
this.bounds.getSize(),
true
);
} else if ( this.checkResize ) {
size = $.getElementSize( element );
} else {
size = this.size;
}
var positionAndSize = this._getOverlayPositionAndSize(viewport);
this.position = position;
this.size = size;
this.adjust( position, size );
position = position.apply( Math.round );
size = size.apply( Math.round );
// rotate the position of the overlay
// TODO only rotate overlays if in canvas mode
// TODO replace the size rotation with CSS3 transforms
// TODO add an option to overlays to not rotate with the image
// Currently only rotates position and size
if( degrees !== 0 && this.scales ) {
overlayCenter = new $.Point( size.x / 2, size.y / 2 );
var drawerCenter = new $.Point(
viewport.viewer.drawer.canvas.width / 2,
viewport.viewer.drawer.canvas.height / 2
);
position = position.plus( overlayCenter ).rotate(
degrees,
drawerCenter
).minus( overlayCenter );
size = size.rotate( degrees, new $.Point( 0, 0 ) );
size = new $.Point( Math.abs( size.x ), Math.abs( size.y ) );
}
var position = positionAndSize.position;
var size = this.size = positionAndSize.size;
var rotate = positionAndSize.rotate;
// call the onDraw callback if it exists to allow one to overwrite
// the drawing/positioning/sizing of the overlay
if ( this.onDraw ) {
this.onDraw( position, size, element );
if (this.onDraw) {
this.onDraw(position, size, this.element);
} else {
style.left = position.x + "px";
style.top = position.y + "px";
var style = this.style;
style.left = position.x + "px";
style.top = position.y + "px";
if (this.width !== null) {
style.width = size.x + "px";
}
if (this.height !== null) {
style.height = size.y + "px";
}
var transformOriginProp = $.getCssPropertyWithVendorPrefix(
'transformOrigin');
var transformProp = $.getCssPropertyWithVendorPrefix(
'transform');
if (transformOriginProp && transformProp) {
if (rotate) {
style[transformOriginProp] = this._getTransformOrigin();
style[transformProp] = "rotate(" + rotate + "deg)";
} else {
style[transformOriginProp] = "";
style[transformProp] = "";
}
}
style.position = "absolute";
if (style.display != 'none') {
style.display = 'block';
}
if ( scales ) {
style.width = size.x + "px";
style.height = size.y + "px";
if (style.display !== 'none') {
style.display = 'block';
}
}
},
/**
* @function
* @param {OpenSeadragon.Point|OpenSeadragon.Rect} location
* @param {OpenSeadragon.OverlayPlacement} position
*/
update: function( location, placement ) {
this.scales = location instanceof $.Rect;
this.bounds = new $.Rect(
location.x,
location.y,
location.width,
location.height
);
// rects are always top-left
this.placement = location instanceof $.Point ?
placement :
$.OverlayPlacement.TOP_LEFT;
}
// private
_getOverlayPositionAndSize: function(viewport) {
var position = viewport.pixelFromPoint(this.location, true);
var size = this._getSizeInPixels(viewport);
this.adjust(position, size);
var rotate = 0;
if (viewport.degrees &&
this.rotationMode !== $.OverlayRotationMode.NO_ROTATION) {
// BOUNDING_BOX is only valid if both directions get scaled.
// Get replaced by EXACT otherwise.
if (this.rotationMode === $.OverlayRotationMode.BOUNDING_BOX &&
this.width !== null && this.height !== null) {
var rect = new $.Rect(position.x, position.y, size.x, size.y);
var boundingBox = this._getBoundingBox(rect, viewport.degrees);
position = boundingBox.getTopLeft();
size = boundingBox.getSize();
} else {
rotate = viewport.degrees;
}
}
return {
position: position,
size: size,
rotate: rotate
};
},
// private
_getSizeInPixels: function(viewport) {
var width = this.size.x;
var height = this.size.y;
if (this.width !== null || this.height !== null) {
var scaledSize = viewport.deltaPixelsFromPointsNoRotate(
new $.Point(this.width || 0, this.height || 0), true);
if (this.width !== null) {
width = scaledSize.x;
}
if (this.height !== null) {
height = scaledSize.y;
}
}
if (this.checkResize &&
(this.width === null || this.height === null)) {
var eltSize = this.size = $.getElementSize(this.element);
if (this.width === null) {
width = eltSize.x;
}
if (this.height === null) {
height = eltSize.y;
}
}
return new $.Point(width, height);
},
// private
_getBoundingBox: function(rect, degrees) {
var refPoint = this._getPlacementPoint(rect);
return rect.rotate(degrees, refPoint).getBoundingBox();
},
// private
_getPlacementPoint: function(rect) {
var result = new $.Point(rect.x, rect.y);
var properties = $.Placement.properties[this.placement];
if (properties) {
if (properties.isHorizontallyCentered) {
result.x += rect.width / 2;
} else if (properties.isRight) {
result.x += rect.width;
}
if (properties.isVerticallyCentered) {
result.y += rect.height / 2;
} else if (properties.isBottom) {
result.y += rect.height;
}
}
return result;
},
// private
_getTransformOrigin: function() {
var result = "";
var properties = $.Placement.properties[this.placement];
if (!properties) {
return result;
}
if (properties.isLeft) {
result = "left";
} else if (properties.isRight) {
result = "right";
}
if (properties.isTop) {
result += " top";
} else if (properties.isBottom) {
result += " bottom";
}
return result;
},
/**
* Changes the overlay settings.
* @function
* @param {OpenSeadragon.Point|OpenSeadragon.Rect|Object} location
* If an object is specified, the options are the same than the constructor
* except for the element which can not be changed.
* @param {OpenSeadragon.Placement} position
*/
update: function(location, placement) {
var options = $.isPlainObject(location) ? location : {
location: location,
placement: placement
};
this._init({
location: options.location || this.location,
placement: options.placement !== undefined ?
options.placement : this.placement,
onDraw: options.onDraw || this.onDraw,
checkResize: options.checkResize || this.checkResize,
width: options.width !== undefined ? options.width : this.width,
height: options.height !== undefined ? options.height : this.height,
rotationMode: options.rotationMode || this.rotationMode
});
},
/**
* Returns the current bounds of the overlay in viewport coordinates
* @function
* @param {OpenSeadragon.Viewport} viewport the viewport
* @returns {OpenSeadragon.Rect} overlay bounds
*/
getBounds: function(viewport) {
$.console.assert(viewport,
'A viewport must now be passed to Overlay.getBounds.');
var width = this.width;
var height = this.height;
if (width === null || height === null) {
var size = viewport.deltaPointsFromPixelsNoRotate(this.size, true);
if (width === null) {
width = size.x;
}
if (height === null) {
height = size.y;
}
}
var location = this.location.clone();
this.adjust(location, new $.Point(width, height));
return this._adjustBoundsForRotation(
viewport, new $.Rect(location.x, location.y, width, height));
},
// private
_adjustBoundsForRotation: function(viewport, bounds) {
if (!viewport ||
viewport.degrees === 0 ||
this.rotationMode === $.OverlayRotationMode.EXACT) {
return bounds;
}
if (this.rotationMode === $.OverlayRotationMode.BOUNDING_BOX) {
// If overlay not fully scalable, BOUNDING_BOX falls back to EXACT
if (this.width === null || this.height === null) {
return bounds;
}
// It is easier to just compute the position and size and
// convert to viewport coordinates.
var positionAndSize = this._getOverlayPositionAndSize(viewport);
return viewport.viewerElementToViewportRectangle(new $.Rect(
positionAndSize.position.x,
positionAndSize.position.y,
positionAndSize.size.x,
positionAndSize.size.y));
}
// NO_ROTATION case
return bounds.rotate(-viewport.degrees,
this._getPlacementPoint(bounds));
}
};
}( OpenSeadragon ));
}(OpenSeadragon));

138
src/placement.js Normal file
View File

@ -0,0 +1,138 @@
/*
* OpenSeadragon - Placement
*
* Copyright (C) 2010-2016 OpenSeadragon contributors
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of CodePlex Foundation nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
(function($) {
/**
* An enumeration of positions to anchor an element.
* @member Placement
* @memberOf OpenSeadragon
* @static
* @readonly
* @property {OpenSeadragon.Placement} CENTER
* @property {OpenSeadragon.Placement} TOP_LEFT
* @property {OpenSeadragon.Placement} TOP
* @property {OpenSeadragon.Placement} TOP_RIGHT
* @property {OpenSeadragon.Placement} RIGHT
* @property {OpenSeadragon.Placement} BOTTOM_RIGHT
* @property {OpenSeadragon.Placement} BOTTOM
* @property {OpenSeadragon.Placement} BOTTOM_LEFT
* @property {OpenSeadragon.Placement} LEFT
*/
$.Placement = $.freezeObject({
CENTER: 0,
TOP_LEFT: 1,
TOP: 2,
TOP_RIGHT: 3,
RIGHT: 4,
BOTTOM_RIGHT: 5,
BOTTOM: 6,
BOTTOM_LEFT: 7,
LEFT: 8,
properties: {
0: {
isLeft: false,
isHorizontallyCentered: true,
isRight: false,
isTop: false,
isVerticallyCentered: true,
isBottom: false
},
1: {
isLeft: true,
isHorizontallyCentered: false,
isRight: false,
isTop: true,
isVerticallyCentered: false,
isBottom: false
},
2: {
isLeft: false,
isHorizontallyCentered: true,
isRight: false,
isTop: true,
isVerticallyCentered: false,
isBottom: false
},
3: {
isLeft: false,
isHorizontallyCentered: false,
isRight: true,
isTop: true,
isVerticallyCentered: false,
isBottom: false
},
4: {
isLeft: false,
isHorizontallyCentered: false,
isRight: true,
isTop: false,
isVerticallyCentered: true,
isBottom: false
},
5: {
isLeft: false,
isHorizontallyCentered: false,
isRight: true,
isTop: false,
isVerticallyCentered: false,
isBottom: true
},
6: {
isLeft: false,
isHorizontallyCentered: true,
isRight: false,
isTop: false,
isVerticallyCentered: false,
isBottom: true
},
7: {
isLeft: true,
isHorizontallyCentered: false,
isRight: false,
isTop: false,
isVerticallyCentered: false,
isBottom: true
},
8: {
isLeft: true,
isHorizontallyCentered: false,
isRight: false,
isTop: false,
isVerticallyCentered: true,
isBottom: false
}
}
});
}(OpenSeadragon));

View File

@ -59,7 +59,8 @@ $.Point = function( x, y ) {
this.y = typeof ( y ) == "number" ? y : 0;
};
$.Point.prototype = /** @lends OpenSeadragon.Point.prototype */{
/** @lends OpenSeadragon.Point.prototype */
$.Point.prototype = {
/**
* @function
* @returns {OpenSeadragon.Point} a duplicate of this Point
@ -179,14 +180,46 @@ $.Point.prototype = /** @lends OpenSeadragon.Point.prototype */{
* From http://stackoverflow.com/questions/4465931/rotate-rectangle-around-a-point
* @function
* @param {Number} degress to rotate around the pivot.
* @param {OpenSeadragon.Point} pivot Point about which to rotate.
* @param {OpenSeadragon.Point} [pivot=(0,0)] Point around which to rotate.
* Defaults to the origin.
* @returns {OpenSeadragon.Point}. A new point representing the point rotated around the specified pivot
*/
rotate: function ( degrees, pivot ) {
var angle = degrees * Math.PI / 180.0,
x = Math.cos( angle ) * ( this.x - pivot.x ) - Math.sin( angle ) * ( this.y - pivot.y ) + pivot.x,
y = Math.sin( angle ) * ( this.x - pivot.x ) + Math.cos( angle ) * ( this.y - pivot.y ) + pivot.y;
return new $.Point( x, y );
rotate: function (degrees, pivot) {
pivot = pivot || new $.Point(0, 0);
var cos;
var sin;
// Avoid float computations when possible
if (degrees % 90 === 0) {
var d = degrees % 360;
if (d < 0) {
d += 360;
}
switch (d) {
case 0:
cos = 1;
sin = 0;
break;
case 90:
cos = 0;
sin = 1;
break;
case 180:
cos = -1;
sin = 0;
break;
case 270:
cos = 0;
sin = -1;
break;
}
} else {
var angle = degrees * Math.PI / 180.0;
cos = Math.cos(angle);
sin = Math.sin(angle);
}
var x = cos * (this.x - pivot.x) - sin * (this.y - pivot.y) + pivot.x;
var y = sin * (this.x - pivot.x) + cos * (this.y - pivot.y) + pivot.y;
return new $.Point(x, y);
},
/**

View File

@ -68,7 +68,8 @@ $.Profiler = function() {
this.maxIdleTime = 0;
};
$.Profiler.prototype = /** @lends OpenSeadragon.Profiler.prototype */{
/** @lends OpenSeadragon.Profiler.prototype */
$.Profiler.prototype = {
/**
* @function

View File

@ -32,55 +32,124 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
(function( $ ){
(function($) {
/**
* @class Rect
* @classdesc A Rectangle really represents a 2x2 matrix where each row represents a
* 2 dimensional vector component, the first is (x,y) and the second is
* (width, height). The latter component implies the equation of a simple
* plane.
* @classdesc A Rectangle is described by it top left coordinates (x, y), width,
* height and degrees of rotation around (x, y).
* Note that the coordinate system used is the one commonly used with images:
* x increases when going to the right
* y increases when going to the bottom
* degrees increases clockwise with 0 being the horizontal
*
* The constructor normalizes the rectangle to always have 0 <= degrees < 90
*
* @memberof OpenSeadragon
* @param {Number} x The vector component 'x'.
* @param {Number} y The vector component 'y'.
* @param {Number} width The vector component 'height'.
* @param {Number} height The vector component 'width'.
* @param {Number} [x=0] The vector component 'x'.
* @param {Number} [y=0] The vector component 'y'.
* @param {Number} [width=0] The vector component 'width'.
* @param {Number} [height=0] The vector component 'height'.
* @param {Number} [degrees=0] Rotation of the rectangle around (x,y) in degrees.
*/
$.Rect = function( x, y, width, height ) {
$.Rect = function(x, y, width, height, degrees) {
/**
* The vector component 'x'.
* @member {Number} x
* @memberof OpenSeadragon.Rect#
*/
this.x = typeof ( x ) == "number" ? x : 0;
this.x = typeof(x) === "number" ? x : 0;
/**
* The vector component 'y'.
* @member {Number} y
* @memberof OpenSeadragon.Rect#
*/
this.y = typeof ( y ) == "number" ? y : 0;
this.y = typeof(y) === "number" ? y : 0;
/**
* The vector component 'width'.
* @member {Number} width
* @memberof OpenSeadragon.Rect#
*/
this.width = typeof ( width ) == "number" ? width : 0;
this.width = typeof(width) === "number" ? width : 0;
/**
* The vector component 'height'.
* @member {Number} height
* @memberof OpenSeadragon.Rect#
*/
this.height = typeof ( height ) == "number" ? height : 0;
this.height = typeof(height) === "number" ? height : 0;
this.degrees = typeof(degrees) === "number" ? degrees : 0;
// Normalizes the rectangle.
this.degrees = this.degrees % 360;
if (this.degrees < 0) {
this.degrees += 360;
}
var newTopLeft, newWidth;
if (this.degrees >= 270) {
newTopLeft = this.getTopRight();
this.x = newTopLeft.x;
this.y = newTopLeft.y;
newWidth = this.height;
this.height = this.width;
this.width = newWidth;
this.degrees -= 270;
} else if (this.degrees >= 180) {
newTopLeft = this.getBottomRight();
this.x = newTopLeft.x;
this.y = newTopLeft.y;
this.degrees -= 180;
} else if (this.degrees >= 90) {
newTopLeft = this.getBottomLeft();
this.x = newTopLeft.x;
this.y = newTopLeft.y;
newWidth = this.height;
this.height = this.width;
this.width = newWidth;
this.degrees -= 90;
}
};
$.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{
/**
* Builds a rectangle having the 3 specified points as summits.
* @static
* @memberof OpenSeadragon.Rect
* @param {OpenSeadragon.Point} topLeft
* @param {OpenSeadragon.Point} topRight
* @param {OpenSeadragon.Point} bottomLeft
* @returns {OpenSeadragon.Rect}
*/
$.Rect.fromSummits = function(topLeft, topRight, bottomLeft) {
var width = topLeft.distanceTo(topRight);
var height = topLeft.distanceTo(bottomLeft);
var diff = topRight.minus(topLeft);
var radians = Math.atan(diff.y / diff.x);
if (diff.x < 0) {
radians += Math.PI;
} else if (diff.y < 0) {
radians += 2 * Math.PI;
}
return new $.Rect(
topLeft.x,
topLeft.y,
width,
height,
radians / Math.PI * 180);
};
/** @lends OpenSeadragon.Rect.prototype */
$.Rect.prototype = {
/**
* @function
* @returns {OpenSeadragon.Rect} a duplicate of this Rect
*/
clone: function() {
return new $.Rect(this.x, this.y, this.width, this.height);
return new $.Rect(
this.x,
this.y,
this.width,
this.height,
this.degrees);
},
/**
@ -114,10 +183,8 @@ $.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{
* the rectangle.
*/
getBottomRight: function() {
return new $.Point(
this.x + this.width,
this.y + this.height
);
return new $.Point(this.x + this.width, this.y + this.height)
.rotate(this.degrees, this.getTopLeft());
},
/**
@ -128,10 +195,8 @@ $.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{
* the rectangle.
*/
getTopRight: function() {
return new $.Point(
this.x + this.width,
this.y
);
return new $.Point(this.x + this.width, this.y)
.rotate(this.degrees, this.getTopLeft());
},
/**
@ -142,10 +207,8 @@ $.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{
* the rectangle.
*/
getBottomLeft: function() {
return new $.Point(
this.x,
this.y + this.height
);
return new $.Point(this.x, this.y + this.height)
.rotate(this.degrees, this.getTopLeft());
},
/**
@ -158,7 +221,7 @@ $.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{
return new $.Point(
this.x + this.width / 2.0,
this.y + this.height / 2.0
);
).rotate(this.degrees, this.getTopLeft());
},
/**
@ -168,7 +231,7 @@ $.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{
* the width and height of the rectangle.
*/
getSize: function() {
return new $.Point( this.width, this.height );
return new $.Point(this.width, this.height);
},
/**
@ -177,98 +240,302 @@ $.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{
* @param {OpenSeadragon.Rect} rectangle The Rectangle to compare to.
* @return {Boolean} 'true' if all components are equal, otherwise 'false'.
*/
equals: function( other ) {
return ( other instanceof $.Rect ) &&
( this.x === other.x ) &&
( this.y === other.y ) &&
( this.width === other.width ) &&
( this.height === other.height );
equals: function(other) {
return (other instanceof $.Rect) &&
this.x === other.x &&
this.y === other.y &&
this.width === other.width &&
this.height === other.height &&
this.degrees === other.degrees;
},
/**
* Multiply all dimensions in this Rect by a factor and return a new Rect.
* Multiply all dimensions (except degrees) in this Rect by a factor and
* return a new Rect.
* @function
* @param {Number} factor The factor to multiply vector components.
* @returns {OpenSeadragon.Rect} A new rect representing the multiplication
* of the vector components by the factor
*/
times: function( factor ) {
return new OpenSeadragon.Rect(
times: function(factor) {
return new $.Rect(
this.x * factor,
this.y * factor,
this.width * factor,
this.height * factor
);
this.height * factor,
this.degrees);
},
/**
* Returns the smallest rectangle that will contain this and the given rectangle.
* Translate/move this Rect by a vector and return new Rect.
* @function
* @param {OpenSeadragon.Point} delta The translation vector.
* @returns {OpenSeadragon.Rect} A new rect with altered position
*/
translate: function(delta) {
return new $.Rect(
this.x + delta.x,
this.y + delta.y,
this.width,
this.height,
this.degrees);
},
/**
* Returns the smallest rectangle that will contain this and the given
* rectangle bounding boxes.
* @param {OpenSeadragon.Rect} rect
* @return {OpenSeadragon.Rect} The new rectangle.
*/
// ----------
union: function(rect) {
var left = Math.min(this.x, rect.x);
var top = Math.min(this.y, rect.y);
var right = Math.max(this.x + this.width, rect.x + rect.width);
var bottom = Math.max(this.y + this.height, rect.y + rect.height);
var thisBoundingBox = this.getBoundingBox();
var otherBoundingBox = rect.getBoundingBox();
return new OpenSeadragon.Rect(left, top, right - left, bottom - top);
var left = Math.min(thisBoundingBox.x, otherBoundingBox.x);
var top = Math.min(thisBoundingBox.y, otherBoundingBox.y);
var right = Math.max(
thisBoundingBox.x + thisBoundingBox.width,
otherBoundingBox.x + otherBoundingBox.width);
var bottom = Math.max(
thisBoundingBox.y + thisBoundingBox.height,
otherBoundingBox.y + otherBoundingBox.height);
return new $.Rect(
left,
top,
right - left,
bottom - top);
},
/**
* Rotates a rectangle around a point. Currently only 90, 180, and 270
* degrees are supported.
* Returns the bounding box of the intersection of this rectangle with the
* given rectangle.
* @param {OpenSeadragon.Rect} rect
* @return {OpenSeadragon.Rect} the bounding box of the intersection
* or null if the rectangles don't intersect.
*/
intersection: function(rect) {
// Simplified version of Weiler Atherton clipping algorithm
// https://en.wikipedia.org/wiki/Weiler%E2%80%93Atherton_clipping_algorithm
// Because we just want the bounding box of the intersection,
// we can just compute the bounding box of:
// 1. all the summits of this which are inside rect
// 2. all the summits of rect which are inside this
// 3. all the intersections of rect and this
var EPSILON = 0.0000000001;
var intersectionPoints = [];
var thisTopLeft = this.getTopLeft();
if (rect.containsPoint(thisTopLeft, EPSILON)) {
intersectionPoints.push(thisTopLeft);
}
var thisTopRight = this.getTopRight();
if (rect.containsPoint(thisTopRight, EPSILON)) {
intersectionPoints.push(thisTopRight);
}
var thisBottomLeft = this.getBottomLeft();
if (rect.containsPoint(thisBottomLeft, EPSILON)) {
intersectionPoints.push(thisBottomLeft);
}
var thisBottomRight = this.getBottomRight();
if (rect.containsPoint(thisBottomRight, EPSILON)) {
intersectionPoints.push(thisBottomRight);
}
var rectTopLeft = rect.getTopLeft();
if (this.containsPoint(rectTopLeft, EPSILON)) {
intersectionPoints.push(rectTopLeft);
}
var rectTopRight = rect.getTopRight();
if (this.containsPoint(rectTopRight, EPSILON)) {
intersectionPoints.push(rectTopRight);
}
var rectBottomLeft = rect.getBottomLeft();
if (this.containsPoint(rectBottomLeft, EPSILON)) {
intersectionPoints.push(rectBottomLeft);
}
var rectBottomRight = rect.getBottomRight();
if (this.containsPoint(rectBottomRight, EPSILON)) {
intersectionPoints.push(rectBottomRight);
}
var thisSegments = this._getSegments();
var rectSegments = rect._getSegments();
for (var i = 0; i < thisSegments.length; i++) {
var thisSegment = thisSegments[i];
for (var j = 0; j < rectSegments.length; j++) {
var rectSegment = rectSegments[j];
var intersect = getIntersection(thisSegment[0], thisSegment[1],
rectSegment[0], rectSegment[1]);
if (intersect) {
intersectionPoints.push(intersect);
}
}
}
// Get intersection point of segments [a,b] and [c,d]
function getIntersection(a, b, c, d) {
// http://stackoverflow.com/a/1968345/1440403
var abVector = b.minus(a);
var cdVector = d.minus(c);
var denom = -cdVector.x * abVector.y + abVector.x * cdVector.y;
if (denom === 0) {
return null;
}
var s = (abVector.x * (a.y - c.y) - abVector.y * (a.x - c.x)) / denom;
var t = (cdVector.x * (a.y - c.y) - cdVector.y * (a.x - c.x)) / denom;
if (-EPSILON <= s && s <= 1 - EPSILON &&
-EPSILON <= t && t <= 1 - EPSILON) {
return new $.Point(a.x + t * abVector.x, a.y + t * abVector.y);
}
return null;
}
if (intersectionPoints.length === 0) {
return null;
}
var minX = intersectionPoints[0].x;
var maxX = intersectionPoints[0].x;
var minY = intersectionPoints[0].y;
var maxY = intersectionPoints[0].y;
for (var k = 1; k < intersectionPoints.length; k++) {
var point = intersectionPoints[k];
if (point.x < minX) {
minX = point.x;
}
if (point.x > maxX) {
maxX = point.x;
}
if (point.y < minY) {
minY = point.y;
}
if (point.y > maxY) {
maxY = point.y;
}
}
return new $.Rect(minX, minY, maxX - minX, maxY - minY);
},
// private
_getSegments: function() {
var topLeft = this.getTopLeft();
var topRight = this.getTopRight();
var bottomLeft = this.getBottomLeft();
var bottomRight = this.getBottomRight();
return [[topLeft, topRight],
[topRight, bottomRight],
[bottomRight, bottomLeft],
[bottomLeft, topLeft]];
},
/**
* Rotates a rectangle around a point.
* @function
* @param {Number} degrees The angle in degrees to rotate.
* @param {OpenSeadragon.Point} pivot The point about which to rotate.
* @param {OpenSeadragon.Point} [pivot] The point about which to rotate.
* Defaults to the center of the rectangle.
* @return {OpenSeadragon.Rect}
*/
rotate: function( degrees, pivot ) {
// TODO support arbitrary rotation
var width = this.width,
height = this.height,
newTopLeft;
degrees = ( degrees + 360 ) % 360;
if (degrees % 90 !== 0) {
throw new Error('Currently only 0, 90, 180, and 270 degrees are supported.');
rotate: function(degrees, pivot) {
degrees = degrees % 360;
if (degrees === 0) {
return this.clone();
}
if( degrees === 0 ){
return new $.Rect(
this.x,
this.y,
this.width,
this.height
);
if (degrees < 0) {
degrees += 360;
}
pivot = pivot || this.getCenter();
var newTopLeft = this.getTopLeft().rotate(degrees, pivot);
var newTopRight = this.getTopRight().rotate(degrees, pivot);
switch ( degrees ) {
case 90:
newTopLeft = this.getBottomLeft();
width = this.height;
height = this.width;
break;
case 180:
newTopLeft = this.getBottomRight();
break;
case 270:
newTopLeft = this.getTopRight();
width = this.height;
height = this.width;
break;
default:
newTopLeft = this.getTopLeft();
break;
var diff = newTopRight.minus(newTopLeft);
var radians = Math.atan(diff.y / diff.x);
if (diff.x < 0) {
radians += Math.PI;
} else if (diff.y < 0) {
radians += 2 * Math.PI;
}
return new $.Rect(
newTopLeft.x,
newTopLeft.y,
this.width,
this.height,
radians / Math.PI * 180);
},
newTopLeft = newTopLeft.rotate(degrees, pivot);
/**
* Retrieves the smallest horizontal (degrees=0) rectangle which contains
* this rectangle.
* @returns {OpenSeadragon.Rect}
*/
getBoundingBox: function() {
if (this.degrees === 0) {
return this.clone();
}
var topLeft = this.getTopLeft();
var topRight = this.getTopRight();
var bottomLeft = this.getBottomLeft();
var bottomRight = this.getBottomRight();
var minX = Math.min(topLeft.x, topRight.x, bottomLeft.x, bottomRight.x);
var maxX = Math.max(topLeft.x, topRight.x, bottomLeft.x, bottomRight.x);
var minY = Math.min(topLeft.y, topRight.y, bottomLeft.y, bottomRight.y);
var maxY = Math.max(topLeft.y, topRight.y, bottomLeft.y, bottomRight.y);
return new $.Rect(
minX,
minY,
maxX - minX,
maxY - minY);
},
return new $.Rect(newTopLeft.x, newTopLeft.y, width, height);
/**
* Retrieves the smallest horizontal (degrees=0) rectangle which contains
* this rectangle and has integers x, y, width and height
* @returns {OpenSeadragon.Rect}
*/
getIntegerBoundingBox: function() {
var boundingBox = this.getBoundingBox();
var x = Math.floor(boundingBox.x);
var y = Math.floor(boundingBox.y);
var width = Math.ceil(boundingBox.width + boundingBox.x - x);
var height = Math.ceil(boundingBox.height + boundingBox.y - y);
return new $.Rect(x, y, width, height);
},
/**
* Determines whether a point is inside this rectangle (edge included).
* @function
* @param {OpenSeadragon.Point} point
* @param {Number} [epsilon=0] the margin of error allowed
* @returns {Boolean} true if the point is inside this rectangle, false
* otherwise.
*/
containsPoint: function(point, epsilon) {
epsilon = epsilon || 0;
// See http://stackoverflow.com/a/2752754/1440403 for explanation
var topLeft = this.getTopLeft();
var topRight = this.getTopRight();
var bottomLeft = this.getBottomLeft();
var topDiff = topRight.minus(topLeft);
var leftDiff = bottomLeft.minus(topLeft);
return ((point.x - topLeft.x) * topDiff.x +
(point.y - topLeft.y) * topDiff.y >= -epsilon) &&
((point.x - topRight.x) * topDiff.x +
(point.y - topRight.y) * topDiff.y <= epsilon) &&
((point.x - topLeft.x) * leftDiff.x +
(point.y - topLeft.y) * leftDiff.y >= -epsilon) &&
((point.x - bottomLeft.x) * leftDiff.x +
(point.y - bottomLeft.y) * leftDiff.y <= epsilon);
},
/**
@ -279,13 +546,14 @@ $.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{
*/
toString: function() {
return "[" +
(Math.round(this.x*100) / 100) + "," +
(Math.round(this.y*100) / 100) + "," +
(Math.round(this.width*100) / 100) + "x" +
(Math.round(this.height*100) / 100) +
"]";
(Math.round(this.x * 100) / 100) + ", " +
(Math.round(this.y * 100) / 100) + ", " +
(Math.round(this.width * 100) / 100) + "x" +
(Math.round(this.height * 100) / 100) + ", " +
(Math.round(this.degrees * 100) / 100) + "deg" +
"]";
}
};
}( OpenSeadragon ));
}(OpenSeadragon));

View File

@ -276,7 +276,6 @@ $.extend( $.ReferenceStrip.prototype, $.EventSource.prototype, $.Viewer.prototyp
}
this.currentPage = page;
$.getElement( element.id + '-displayregion' ).focus();
onStripEnter.call( this, { eventSource: this.innerTracker } );
}
},
@ -436,7 +435,7 @@ function loadPanels( strip, viewerSize, scroll ) {
animationTime: 0
} );
miniViewer.displayRegion = $.makeNeutralElement( "textarea" );
miniViewer.displayRegion = $.makeNeutralElement( "div" );
miniViewer.displayRegion.id = element.id + '-displayregion';
miniViewer.displayRegion.className = 'displayregion';

View File

@ -41,7 +41,7 @@
* @param {Number} options.springStiffness - Spring stiffness. Must be greater than zero.
* The closer to zero, the closer to linear animation.
* @param {Number} options.animationTime - Animation duration per spring, in seconds.
* Must be greater than zero.
* Must be zero or greater.
* @param {Number} [options.initial=0] - Initial value of spring.
* @param {Boolean} [options.exponential=false] - Whether this spring represents
* an exponential scale (such as zoom) and should be animated accordingly. Note that
@ -79,8 +79,8 @@ $.Spring = function( options ) {
$.console.assert(typeof options.springStiffness === "number" && options.springStiffness !== 0,
"[OpenSeadragon.Spring] options.springStiffness must be a non-zero number");
$.console.assert(typeof options.animationTime === "number" && options.springStiffness !== 0,
"[OpenSeadragon.Spring] options.animationTime must be a non-zero number");
$.console.assert(typeof options.animationTime === "number" && options.animationTime >= 0,
"[OpenSeadragon.Spring] options.animationTime must be a number greater than or equal to 0");
if (options.exponential) {
this._exponential = true;
@ -134,7 +134,8 @@ $.Spring = function( options ) {
}
};
$.Spring.prototype = /** @lends OpenSeadragon.Spring.prototype */{
/** @lends OpenSeadragon.Spring.prototype */
$.Spring.prototype = {
/**
* @function
@ -233,6 +234,15 @@ $.Spring.prototype = /** @lends OpenSeadragon.Spring.prototype */{
} else {
this.current.value = currentValue;
}
},
/**
* Returns whether the spring is at the target value
* @function
* @returns {Boolean} True if at target value, false otherwise
*/
isAtTargetValue: function() {
return this.current.value === this.target.value;
}
};

View File

@ -45,8 +45,10 @@
* @param {Boolean} exists Is this tile a part of a sparse image? ( Also has
* this tile failed to load? )
* @param {String} url The URL of this tile's image.
* @param {CanvasRenderingContext2D} context2D The context2D of this tile if it
* is provided directly by the tile source.
*/
$.Tile = function(level, x, y, bounds, exists, url) {
$.Tile = function(level, x, y, bounds, exists, url, context2D) {
/**
* The zoom level this tile belongs to.
* @member {Number} level
@ -83,6 +85,12 @@ $.Tile = function(level, x, y, bounds, exists, url) {
* @memberof OpenSeadragon.Tile#
*/
this.url = url;
/**
* The context2D of this tile if it is provided directly by the tile source.
* @member {CanvasRenderingContext2D} context2D
* @memberOf OpenSeadragon.Tile#
*/
this.context2D = context2D;
/**
* Is this tile loaded?
* @member {Boolean} loaded
@ -172,7 +180,8 @@ $.Tile = function(level, x, y, bounds, exists, url) {
this.lastTouchTime = 0;
};
$.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{
/** @lends OpenSeadragon.Tile.prototype */
$.Tile.prototype = {
/**
* Provides a string representation of this tiles level and (x,y)
@ -184,6 +193,11 @@ $.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{
return this.level + "/" + this.x + "_" + this.y;
},
// private
_hasTransparencyChannel: function() {
return !!this.context2D || this.url.match('.png');
},
/**
* Renders the tile in an html container.
* @function
@ -240,21 +254,23 @@ $.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{
* @param {Function} drawingHandler - Method for firing the drawing event.
* drawingHandler({context, tile, rendered})
* where <code>rendered</code> is the context with the pre-drawn image.
* @param {Number} [scale=1] - Apply a scale to position and size
* @param {OpenSeadragon.Point} [translate] - A translation vector
*/
drawCanvas: function( context, drawingHandler ) {
drawCanvas: function( context, drawingHandler, scale, translate ) {
var position = this.position,
size = this.size,
var position = this.position.times($.pixelDensityRatio),
size = this.size.times($.pixelDensityRatio),
rendered;
if (!this.cacheImageRecord) {
if (!this.context2D && !this.cacheImageRecord) {
$.console.warn(
'[Tile.drawCanvas] attempting to draw tile %s when it\'s not cached',
this.toString());
return;
}
rendered = this.cacheImageRecord.getRenderedContext();
rendered = this.context2D || this.cacheImageRecord.getRenderedContext();
if ( !this.loaded || !rendered ){
$.console.warn(
@ -269,20 +285,30 @@ $.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{
context.globalAlpha = this.opacity;
if (typeof scale === 'number' && scale !== 1) {
// draw tile at a different scale
position = position.times(scale);
size = size.times(scale);
}
if (translate instanceof $.Point) {
// shift tile position slightly
position = position.plus(translate);
}
//if we are supposed to be rendering fully opaque rectangle,
//ie its done fading or fading is turned off, and if we are drawing
//an image with an alpha channel, then the only way
//to avoid seeing the tile underneath is to clear the rectangle
if( context.globalAlpha == 1 && this.url.match('.png') ){
if (context.globalAlpha === 1 && this._hasTransparencyChannel()) {
//clearing only the inside of the rectangle occupied
//by the png prevents edge flikering
context.clearRect(
(position.x * $.pixelDensityRatio)+1,
(position.y * $.pixelDensityRatio)+1,
(size.x * $.pixelDensityRatio)-2,
(size.y * $.pixelDensityRatio)-2
position.x + 1,
position.y + 1,
size.x - 2,
size.y - 2
);
}
// This gives the application a chance to make image manipulation
@ -295,15 +321,59 @@ $.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{
0,
rendered.canvas.width,
rendered.canvas.height,
position.x * $.pixelDensityRatio,
position.y * $.pixelDensityRatio,
size.x * $.pixelDensityRatio,
size.y * $.pixelDensityRatio
position.x,
position.y,
size.x,
size.y
);
context.restore();
},
/**
* Get the ratio between current and original size.
* @function
* @return {Float}
*/
getScaleForEdgeSmoothing: function() {
var context;
if (this.cacheImageRecord) {
context = this.cacheImageRecord.getRenderedContext();
} else if (this.context2D) {
context = this.context2D;
} else {
$.console.warn(
'[Tile.drawCanvas] attempting to get tile scale %s when tile\'s not cached',
this.toString());
return 1;
}
return context.canvas.width / (this.size.x * $.pixelDensityRatio);
},
/**
* Get a translation vector that when applied to the tile position produces integer coordinates.
* Needed to avoid swimming and twitching.
* @function
* @param {Number} [scale=1] - Scale to be applied to position.
* @return {OpenSeadragon.Point}
*/
getTranslationForEdgeSmoothing: function(scale, canvasSize, sketchCanvasSize) {
// The translation vector must have positive values, otherwise the image goes a bit off
// the sketch canvas to the top and left and we must use negative coordinates to repaint it
// to the main canvas. In that case, some browsers throw:
// INDEX_SIZE_ERR: DOM Exception 1: Index or size was negative, or greater than the allowed value.
var x = Math.max(1, Math.ceil((sketchCanvasSize.x - canvasSize.x) / 2));
var y = Math.max(1, Math.ceil((sketchCanvasSize.y - canvasSize.y) / 2));
return new $.Point(x, y).minus(
this.position
.times($.pixelDensityRatio)
.times(scale || 1)
.apply(function(x) {
return x % 1;
})
);
},
/**
* Removes tile from its container.
* @function

View File

@ -122,7 +122,8 @@ $.TileCache = function( options ) {
this._imagesLoadedCount = 0;
};
$.TileCache.prototype = /** @lends OpenSeadragon.TileCache.prototype */{
/** @lends OpenSeadragon.TileCache.prototype */
$.TileCache.prototype = {
/**
* @returns {Number} The total number of tiles that have been loaded by
* this TileCache.

View File

@ -52,6 +52,10 @@
* @param {Number} [options.y=0] - Top position, in viewport coordinates.
* @param {Number} [options.width=1] - Width, in viewport coordinates.
* @param {Number} [options.height] - Height, in viewport coordinates.
* @param {OpenSeadragon.Rect} [options.fitBounds] The bounds in viewport coordinates
* to fit the image into. If specified, x, y, width and height get ignored.
* @param {OpenSeadragon.Placement} [options.fitBoundsPlacement=OpenSeadragon.Placement.CENTER]
* How to anchor the image in the bounds if options.fitBounds is set.
* @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
* browsers that support the HTML5 canvas.
@ -64,7 +68,10 @@
* @param {Number} [options.blendTime] - See {@link OpenSeadragon.Options}.
* @param {Boolean} [options.alwaysBlend] - See {@link OpenSeadragon.Options}.
* @param {Number} [options.minPixelRatio] - See {@link OpenSeadragon.Options}.
* @param {Number} [options.smoothTileEdgesMinZoom] - 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 {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 {String|CanvasGradient|CanvasPattern|Function} [options.placeholderFillStyle] - See {@link OpenSeadragon.Options}.
* @param {String|Boolean} [options.crossOriginPolicy] - See {@link OpenSeadragon.Options}.
@ -120,6 +127,11 @@ $.TiledImage = function( options ) {
delete options.height;
}
var fitBounds = options.fitBounds;
delete options.fitBounds;
var fitBoundsPlacement = options.fitBoundsPlacement || OpenSeadragon.Placement.CENTER;
delete options.fitBoundsPlacement;
$.extend( true, this, {
//internal state properties
@ -130,25 +142,29 @@ $.TiledImage = function( options ) {
lastResetTime: 0, // Last time for which the tiledImage was reset.
_midDraw: false, // Is the tiledImage currently updating the viewport?
_needsDraw: true, // Does the tiledImage need to update the viewport again?
_hasOpaqueTile: false, // Do we have even one fully opaque tile?
_hasOpaqueTile: false, // Do we have even one fully opaque tile?
//configurable settings
springStiffness: $.DEFAULT_SETTINGS.springStiffness,
animationTime: $.DEFAULT_SETTINGS.animationTime,
minZoomImageRatio: $.DEFAULT_SETTINGS.minZoomImageRatio,
wrapHorizontal: $.DEFAULT_SETTINGS.wrapHorizontal,
wrapVertical: $.DEFAULT_SETTINGS.wrapVertical,
immediateRender: $.DEFAULT_SETTINGS.immediateRender,
blendTime: $.DEFAULT_SETTINGS.blendTime,
alwaysBlend: $.DEFAULT_SETTINGS.alwaysBlend,
minPixelRatio: $.DEFAULT_SETTINGS.minPixelRatio,
debugMode: $.DEFAULT_SETTINGS.debugMode,
crossOriginPolicy: $.DEFAULT_SETTINGS.crossOriginPolicy,
placeholderFillStyle: $.DEFAULT_SETTINGS.placeholderFillStyle,
opacity: $.DEFAULT_SETTINGS.opacity
springStiffness: $.DEFAULT_SETTINGS.springStiffness,
animationTime: $.DEFAULT_SETTINGS.animationTime,
minZoomImageRatio: $.DEFAULT_SETTINGS.minZoomImageRatio,
wrapHorizontal: $.DEFAULT_SETTINGS.wrapHorizontal,
wrapVertical: $.DEFAULT_SETTINGS.wrapVertical,
immediateRender: $.DEFAULT_SETTINGS.immediateRender,
blendTime: $.DEFAULT_SETTINGS.blendTime,
alwaysBlend: $.DEFAULT_SETTINGS.alwaysBlend,
minPixelRatio: $.DEFAULT_SETTINGS.minPixelRatio,
smoothTileEdgesMinZoom: $.DEFAULT_SETTINGS.smoothTileEdgesMinZoom,
iOSDevice: $.DEFAULT_SETTINGS.iOSDevice,
debugMode: $.DEFAULT_SETTINGS.debugMode,
crossOriginPolicy: $.DEFAULT_SETTINGS.crossOriginPolicy,
placeholderFillStyle: $.DEFAULT_SETTINGS.placeholderFillStyle,
opacity: $.DEFAULT_SETTINGS.opacity,
compositeOperation: $.DEFAULT_SETTINGS.compositeOperation
}, options );
this._fullyLoaded = false;
this._xSpring = new $.Spring({
initial: x,
springStiffness: this.springStiffness,
@ -169,12 +185,16 @@ $.TiledImage = function( options ) {
this._updateForScale();
if (fitBounds) {
this.fitBounds(fitBounds, fitBoundsPlacement, true);
}
// We need a callback to give image manipulation a chance to happen
this._drawingHandler = function(args) {
/**
* This event is fired just before the tile is drawn giving the application a chance to alter the image.
*
* NOTE: This event is only fired when the drawer is using a <canvas>.
* NOTE: This event is only fired when the drawer is using a &lt;canvas&gt;.
*
* @event tile-drawing
* @memberof OpenSeadragon.Viewer
@ -200,6 +220,37 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
return this._needsDraw;
},
/**
* @returns {Boolean} Whether all tiles necessary for this TiledImage to draw at the current view have been loaded.
*/
getFullyLoaded: function() {
return this._fullyLoaded;
},
// private
_setFullyLoaded: function(flag) {
if (flag === this._fullyLoaded) {
return;
}
this._fullyLoaded = flag;
/**
* Fired when the TiledImage's "fully loaded" flag (whether all tiles necessary for this TiledImage
* to draw at the current view have been loaded) changes.
*
* @event fully-loaded-change
* @memberof OpenSeadragon.TiledImage
* @type {object}
* @property {Boolean} fullyLoaded - The new "fully loaded" value.
* @property {OpenSeadragon.TiledImage} eventSource - A reference to the TiledImage which raised the event.
* @property {?Object} userData - Arbitrary subscriber-defined object.
*/
this.raiseEvent('fully-loaded-change', {
fullyLoaded: this._fullyLoaded
});
},
/**
* Clears all tiles and triggers an update on the next call to
* {@link OpenSeadragon.TiledImage#update}.
@ -237,9 +288,11 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
* Draws the TiledImage to its Drawer.
*/
draw: function() {
this._midDraw = true;
updateViewport( this );
this._midDraw = false;
if (this.opacity !== 0) {
this._midDraw = true;
updateViewport(this);
this._midDraw = false;
}
},
/**
@ -269,6 +322,26 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
return this.getBounds();
},
/**
* Get the bounds of the displayed part of the tiled image.
* @param {Boolean} [current=false] Pass true for the current location,
* false for the target location.
* @returns {$.Rect} The clipped bounds in viewport coordinates.
*/
getClippedBounds: function(current) {
var bounds = this.getBounds(current);
if (this._clip) {
var ratio = this._worldWidthCurrent / this.source.dimensions.x;
var clip = this._clip.times(ratio);
bounds = new $.Rect(
bounds.x + clip.x,
bounds.y + clip.y,
clip.width,
clip.height);
}
return bounds;
},
/**
* @returns {OpenSeadragon.Point} This TiledImage's content size, in original pixels.
*/
@ -355,23 +428,23 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
* @return {OpenSeadragon.Rect} A rect representing the coordinates in the viewport.
*/
imageToViewportRectangle: function( imageX, imageY, pixelWidth, pixelHeight, current ) {
if (imageX instanceof $.Rect) {
var rect = imageX;
if (rect instanceof $.Rect) {
//they passed a rect instead of individual components
current = imageY;
pixelWidth = imageX.width;
pixelHeight = imageX.height;
imageY = imageX.y;
imageX = imageX.x;
} else {
rect = new $.Rect(imageX, imageY, pixelWidth, pixelHeight);
}
var coordA = this.imageToViewportCoordinates(imageX, imageY, current);
var coordB = this._imageToViewportDelta(pixelWidth, pixelHeight, current);
var coordA = this.imageToViewportCoordinates(rect.getTopLeft(), current);
var coordB = this._imageToViewportDelta(rect.width, rect.height, current);
return new $.Rect(
coordA.x,
coordA.y,
coordB.x,
coordB.y
coordB.y,
rect.degrees
);
},
@ -387,23 +460,23 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
* @return {OpenSeadragon.Rect} A rect representing the coordinates in the image.
*/
viewportToImageRectangle: function( viewerX, viewerY, pointWidth, pointHeight, current ) {
var rect = viewerX;
if (viewerX instanceof $.Rect) {
//they passed a rect instead of individual components
current = viewerY;
pointWidth = viewerX.width;
pointHeight = viewerX.height;
viewerY = viewerX.y;
viewerX = viewerX.x;
} else {
rect = new $.Rect(viewerX, viewerY, pointWidth, pointHeight);
}
var coordA = this.viewportToImageCoordinates(viewerX, viewerY, current);
var coordB = this._viewportToImageDelta(pointWidth, pointHeight, current);
var coordA = this.viewportToImageCoordinates(rect.getTopLeft(), current);
var coordB = this._viewportToImageDelta(rect.width, rect.height, current);
return new $.Rect(
coordA.x,
coordA.y,
coordB.x,
coordB.y
coordB.y,
rect.degrees
);
},
@ -502,6 +575,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
this._xSpring.resetTo(position.x);
this._ySpring.resetTo(position.y);
this._needsDraw = true;
} else {
if (sameTarget) {
return;
@ -509,6 +583,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
this._xSpring.springTo(position.x);
this._ySpring.springTo(position.y);
this._needsDraw = true;
}
if (!sameTarget) {
@ -536,6 +611,67 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
this._setScale(height / this.normHeight, immediately);
},
/**
* Positions and scales the TiledImage to fit in the specified bounds.
* Note: this method fires OpenSeadragon.TiledImage.event:bounds-change
* twice
* @param {OpenSeadragon.Rect} bounds The bounds to fit the image into.
* @param {OpenSeadragon.Placement} [anchor=OpenSeadragon.Placement.CENTER]
* How to anchor the image in the bounds.
* @param {Boolean} [immediately=false] Whether to animate to the new size
* or snap immediately.
* @fires OpenSeadragon.TiledImage.event:bounds-change
*/
fitBounds: function(bounds, anchor, immediately) {
anchor = anchor || $.Placement.CENTER;
var anchorProperties = $.Placement.properties[anchor];
var aspectRatio = this.contentAspectX;
var xOffset = 0;
var yOffset = 0;
var displayedWidthRatio = 1;
var displayedHeightRatio = 1;
if (this._clip) {
aspectRatio = this._clip.getAspectRatio();
displayedWidthRatio = this._clip.width / this.source.dimensions.x;
displayedHeightRatio = this._clip.height / this.source.dimensions.y;
if (bounds.getAspectRatio() > aspectRatio) {
xOffset = this._clip.x / this._clip.height * bounds.height;
yOffset = this._clip.y / this._clip.height * bounds.height;
} else {
xOffset = this._clip.x / this._clip.width * bounds.width;
yOffset = this._clip.y / this._clip.width * bounds.width;
}
}
if (bounds.getAspectRatio() > aspectRatio) {
// We will have margins on the X axis
var height = bounds.height / displayedHeightRatio;
var marginLeft = 0;
if (anchorProperties.isHorizontallyCentered) {
marginLeft = (bounds.width - bounds.height * aspectRatio) / 2;
} else if (anchorProperties.isRight) {
marginLeft = bounds.width - bounds.height * aspectRatio;
}
this.setPosition(
new $.Point(bounds.x - xOffset + marginLeft, bounds.y - yOffset),
immediately);
this.setHeight(height, immediately);
} else {
// We will have margins on the Y axis
var width = bounds.width / displayedWidthRatio;
var marginTop = 0;
if (anchorProperties.isVerticallyCentered) {
marginTop = (bounds.height - bounds.width / aspectRatio) / 2;
} else if (anchorProperties.isBottom) {
marginTop = bounds.height - bounds.width / aspectRatio;
}
this.setPosition(
new $.Point(bounds.x - xOffset, bounds.y - yOffset + marginTop),
immediately);
this.setWidth(width, immediately);
}
},
/**
* @returns {OpenSeadragon.Rect|null} The TiledImage's current clip rectangle,
* in image pixels, or null if none.
@ -581,6 +717,21 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
this._needsDraw = true;
},
/**
* @returns {String} The TiledImage's current compositeOperation.
*/
getCompositeOperation: function() {
return this.compositeOperation;
},
/**
* @param {String} compositeOperation the tiled image should be drawn with this globalCompositeOperation.
*/
setCompositeOperation: function(compositeOperation) {
this.compositeOperation = compositeOperation;
this._needsDraw = true;
},
// private
_setScale: function(scale, immediately) {
var sameTarget = (this._scaleSpring.target.value === scale);
@ -591,6 +742,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
this._scaleSpring.resetTo(scale);
this._updateForScale();
this._needsDraw = true;
} else {
if (sameTarget) {
return;
@ -598,6 +750,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
this._scaleSpring.springTo(scale);
this._updateForScale();
this._needsDraw = true;
}
if (!sameTarget) {
@ -626,6 +779,11 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
* @property {?Object} userData - Arbitrary subscriber-defined object.
*/
this.raiseEvent('bounds-change');
},
// private
_isBottomItem: function() {
return this.viewer.world.getItemAt(0) === this;
}
});
@ -646,7 +804,7 @@ function updateViewport( tiledImage ) {
haveDrawn = false,
currentTime = $.now(),
viewportBounds = tiledImage.viewport.getBoundsWithMargins( true ),
zeroRatioC = tiledImage.viewport.deltaPixelsFromPoints(
zeroRatioC = tiledImage.viewport.deltaPixelsFromPointsNoRotate(
tiledImage.source.getPixelRatio( 0 ),
true
).x * tiledImage._scaleSpring.current.value,
@ -664,7 +822,6 @@ function updateViewport( tiledImage ) {
Math.log( 2 )
))
),
degrees = tiledImage.viewport.degrees,
renderPixelRatioC,
renderPixelRatioT,
zeroRatioT,
@ -672,26 +829,23 @@ function updateViewport( tiledImage ) {
levelOpacity,
levelVisibility;
viewportBounds.x -= tiledImage._xSpring.current.value;
viewportBounds.y -= tiledImage._ySpring.current.value;
// Reset tile's internal drawn state
while ( tiledImage.lastDrawn.length > 0 ) {
while (tiledImage.lastDrawn.length > 0) {
tile = tiledImage.lastDrawn.pop();
tile.beingDrawn = false;
}
//Change bounds for rotation
if (degrees === 90 || degrees === 270) {
viewportBounds = viewportBounds.rotate( degrees );
} else if (degrees !== 0 && degrees !== 180) {
// This is just an approximation.
var orthBounds = viewportBounds.rotate(90);
viewportBounds.x -= orthBounds.width / 2;
viewportBounds.y -= orthBounds.height / 2;
viewportBounds.width += orthBounds.width;
viewportBounds.height += orthBounds.height;
if (!tiledImage.wrapHorizontal && !tiledImage.wrapVertical) {
var tiledImageBounds = tiledImage.getClippedBounds(true);
var intersection = viewportBounds.intersection(tiledImageBounds);
if (intersection === null) {
return;
}
viewportBounds = intersection;
}
viewportBounds = viewportBounds.getBoundingBox();
viewportBounds.x -= tiledImage._xSpring.current.value;
viewportBounds.y -= tiledImage._ySpring.current.value;
var viewportTL = viewportBounds.getTopLeft();
var viewportBR = viewportBounds.getBottomRight();
@ -727,7 +881,7 @@ function updateViewport( tiledImage ) {
drawLevel = false;
//Avoid calculations for draw if we have already drawn this
renderPixelRatioC = tiledImage.viewport.deltaPixelsFromPoints(
renderPixelRatioC = tiledImage.viewport.deltaPixelsFromPointsNoRotate(
tiledImage.source.getPixelRatio( level ),
true
).x * tiledImage._scaleSpring.current.value;
@ -741,12 +895,12 @@ function updateViewport( tiledImage ) {
}
//Perform calculations for draw if we haven't drawn this
renderPixelRatioT = tiledImage.viewport.deltaPixelsFromPoints(
renderPixelRatioT = tiledImage.viewport.deltaPixelsFromPointsNoRotate(
tiledImage.source.getPixelRatio( level ),
false
).x * tiledImage._scaleSpring.current.value;
zeroRatioT = tiledImage.viewport.deltaPixelsFromPoints(
zeroRatioT = tiledImage.viewport.deltaPixelsFromPointsNoRotate(
tiledImage.source.getPixelRatio(
Math.max(
tiledImage.source.getClosestLevel( tiledImage.viewport.containerSize ) - 1,
@ -791,10 +945,12 @@ function updateViewport( tiledImage ) {
drawTiles( tiledImage, tiledImage.lastDrawn );
// Load the new 'best' tile
if ( best ) {
if (best && !best.context2D) {
loadTile( tiledImage, best, currentTime );
tiledImage._setFullyLoaded(false);
} else {
tiledImage._setFullyLoaded(true);
}
}
@ -846,10 +1002,14 @@ function updateLevel( tiledImage, haveDrawn, drawLevel, level, levelOpacity, lev
resetCoverage( tiledImage.coverage, level );
if ( !tiledImage.wrapHorizontal ) {
if ( tiledImage.wrapHorizontal ) {
tileTL.x -= 1; // left invisible column (othervise we will have empty space after scroll at left)
} else {
tileBR.x = Math.min( tileBR.x, numberOfTiles.x - 1 );
}
if ( !tiledImage.wrapVertical ) {
if ( tiledImage.wrapVertical ) {
tileTL.y -= 1; // top invisible row (othervise we will have empty space after scroll at top)
} else {
tileBR.y = Math.min( tileBR.y, numberOfTiles.y - 1 );
}
@ -936,10 +1096,14 @@ function updateTile( tiledImage, drawLevel, haveDrawn, x, y, level, levelOpacity
);
if (!tile.loaded) {
var imageRecord = tiledImage._tileCache.getImageRecord(tile.url);
if (imageRecord) {
var image = imageRecord.getImage();
setTileLoaded(tiledImage, tile, image);
if (tile.context2D) {
setTileLoaded(tiledImage, tile);
} else {
var imageRecord = tiledImage._tileCache.getImageRecord(tile.url);
if (imageRecord) {
var image = imageRecord.getImage();
setTileLoaded(tiledImage, tile, image);
}
}
}
@ -972,6 +1136,7 @@ function getTile( x, y, level, tileSource, tilesMatrix, time, numTiles, worldWid
bounds,
exists,
url,
context2D,
tile;
if ( !tilesMatrix[ level ] ) {
@ -987,6 +1152,8 @@ function getTile( x, y, level, tileSource, tilesMatrix, time, numTiles, worldWid
bounds = tileSource.getTileBounds( level, xMod, yMod );
exists = tileSource.tileExists( level, xMod, yMod );
url = tileSource.getTileUrl( level, xMod, yMod );
context2D = tileSource.getContext2D ?
tileSource.getContext2D(level, xMod, yMod) : undefined;
bounds.x += ( x - xMod ) / numTiles.x;
bounds.y += (worldHeight / worldWidth) * (( y - yMod ) / numTiles.y);
@ -997,7 +1164,8 @@ function getTile( x, y, level, tileSource, tilesMatrix, time, numTiles, worldWid
y,
bounds,
exists,
url
url,
context2D
);
}
@ -1036,12 +1204,12 @@ function onTileLoad( tiledImage, tile, time, image, errorMsg ) {
* @property {string} message - The error message.
*/
tiledImage.viewer.raiseEvent("tile-load-failed", {tile: tile, tiledImage: tiledImage, time: time, message: errorMsg});
if( !tiledImage.debugMode ){
tile.loading = false;
tile.exists = false;
return;
}
} else if ( time < tiledImage.lastResetTime ) {
tile.loading = false;
tile.exists = false;
return;
}
if ( time < tiledImage.lastResetTime ) {
$.console.log( "Ignoring tile %s loaded before reset: %s", tile, tile.url );
tile.loading = false;
return;
@ -1076,12 +1244,14 @@ function setTileLoaded(tiledImage, tile, image, cutoff) {
if (increment === 0) {
tile.loading = false;
tile.loaded = true;
tiledImage._tileCache.cacheTile({
image: image,
tile: tile,
cutoff: cutoff,
tiledImage: tiledImage
});
if (!tile.context2D) {
tiledImage._tileCache.cacheTile({
image: image,
tile: tile,
cutoff: cutoff,
tiledImage: tiledImage
});
}
tiledImage._needsDraw = true;
}
}
@ -1124,10 +1294,10 @@ function positionTile( tile, overlap, viewport, viewportCenter, levelVisibility,
boundsSize.x *= tiledImage._scaleSpring.current.value;
boundsSize.y *= tiledImage._scaleSpring.current.value;
var positionC = viewport.pixelFromPoint( boundsTL, true ),
positionT = viewport.pixelFromPoint( boundsTL, false ),
sizeC = viewport.deltaPixelsFromPoints( boundsSize, true ),
sizeT = viewport.deltaPixelsFromPoints( boundsSize, false ),
var positionC = viewport.pixelFromPointNoRotate(boundsTL, true),
positionT = viewport.pixelFromPointNoRotate(boundsTL, false),
sizeC = viewport.deltaPixelsFromPointsNoRotate(boundsSize, true),
sizeT = viewport.deltaPixelsFromPointsNoRotate(boundsSize, false),
tileCenter = positionT.plus( sizeT.divide( 2 ) ),
tileDistance = viewportCenter.distanceTo( tileCenter );
@ -1290,16 +1460,49 @@ function compareTiles( previousBest, tile ) {
}
function drawTiles( tiledImage, lastDrawn ) {
var i,
tile;
if ( tiledImage.opacity <= 0 ) {
drawDebugInfo( tiledImage, lastDrawn );
if (lastDrawn.length === 0) {
return;
}
var useSketch = tiledImage.opacity < 1;
if ( useSketch ) {
tiledImage._drawer._clear( true );
var tile = lastDrawn[0];
var useSketch = tiledImage.opacity < 1 ||
(tiledImage.compositeOperation &&
tiledImage.compositeOperation !== 'source-over') ||
(!tiledImage._isBottomItem() && tile._hasTransparencyChannel());
var sketchScale;
var sketchTranslate;
var zoom = tiledImage.viewport.getZoom(true);
var imageZoom = tiledImage.viewportToImageZoom(zoom);
if (imageZoom > tiledImage.smoothTileEdgesMinZoom && !tiledImage.iOSDevice) {
// When zoomed in a lot (>100%) the tile edges are visible.
// So we have to composite them at ~100% and scale them up together.
// Note: Disabled on iOS devices per default as it causes a native crash
useSketch = true;
sketchScale = tile.getScaleForEdgeSmoothing();
sketchTranslate = tile.getTranslationForEdgeSmoothing(sketchScale,
tiledImage._drawer.getCanvasSize(false),
tiledImage._drawer.getCanvasSize(true));
}
var bounds;
if (useSketch) {
if (!sketchScale) {
// Except when edge smoothing, we only clean the part of the
// sketch canvas we are going to use for performance reasons.
bounds = tiledImage.viewport.viewportToViewerElementRectangle(
tiledImage.getClippedBounds(true))
.getIntegerBoundingBox()
.times($.pixelDensityRatio);
}
tiledImage._drawer._clear(true, bounds);
}
// When scaling, we must rotate only when blending the sketch canvas to avoid
// interpolation
if (tiledImage.viewport.degrees !== 0 && !sketchScale) {
tiledImage._drawer._offsetForRotation(tiledImage.viewport.degrees, useSketch);
}
var usedClip = false;
@ -1308,6 +1511,12 @@ function drawTiles( tiledImage, lastDrawn ) {
var box = tiledImage.imageToViewportRectangle(tiledImage._clip, true);
var clipRect = tiledImage._drawer.viewportToDrawerRectangle(box);
if (sketchScale) {
clipRect = clipRect.times(sketchScale);
}
if (sketchTranslate) {
clipRect = clipRect.translate(sketchTranslate);
}
tiledImage._drawer.setClip(clipRect, useSketch);
usedClip = true;
@ -1315,6 +1524,12 @@ function drawTiles( tiledImage, lastDrawn ) {
if ( tiledImage.placeholderFillStyle && tiledImage._hasOpaqueTile === false ) {
var placeholderRect = tiledImage._drawer.viewportToDrawerRectangle(tiledImage.getBounds(true));
if (sketchScale) {
placeholderRect = placeholderRect.times(sketchScale);
}
if (sketchTranslate) {
placeholderRect = placeholderRect.translate(sketchTranslate);
}
var fillStyle = null;
if ( typeof tiledImage.placeholderFillStyle === "function" ) {
@ -1327,9 +1542,9 @@ function drawTiles( tiledImage, lastDrawn ) {
tiledImage._drawer.drawRectangle(placeholderRect, fillStyle, useSketch);
}
for ( i = lastDrawn.length - 1; i >= 0; i-- ) {
for (var i = lastDrawn.length - 1; i >= 0; i--) {
tile = lastDrawn[ i ];
tiledImage._drawer.drawTile( tile, tiledImage._drawingHandler, useSketch );
tiledImage._drawer.drawTile( tile, tiledImage._drawingHandler, useSketch, sketchScale, sketchTranslate );
tile.beingDrawn = true;
if( tiledImage.viewer ){
@ -1355,8 +1570,25 @@ function drawTiles( tiledImage, lastDrawn ) {
tiledImage._drawer.restoreContext( useSketch );
}
if ( useSketch ) {
tiledImage._drawer.blendSketch( tiledImage.opacity );
if (tiledImage.viewport.degrees !== 0 && !sketchScale) {
tiledImage._drawer._restoreRotationChanges(useSketch);
}
if (useSketch) {
var offsetForRotation = tiledImage.viewport.degrees !== 0 && sketchScale;
if (offsetForRotation) {
tiledImage._drawer._offsetForRotation(tiledImage.viewport.degrees, false);
}
tiledImage._drawer.blendSketch({
opacity: tiledImage.opacity,
scale: sketchScale,
translate: sketchTranslate,
compositeOperation: tiledImage.compositeOperation,
bounds: bounds
});
if (offsetForRotation) {
tiledImage._drawer._restoreRotationChanges(false);
}
}
drawDebugInfo( tiledImage, lastDrawn );
}

View File

@ -190,7 +190,7 @@ $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLeve
this.aspectRatio = ( options.width && options.height ) ?
( options.width / options.height ) : 1;
this.dimensions = new $.Point( options.width, options.height );
if ( this.tileSize ){
this._tileWidth = this._tileHeight = this.tileSize;
delete this.tileSize;
@ -212,7 +212,7 @@ $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLeve
this._tileHeight = 0;
}
}
this.tileOverlap = options.tileOverlap ? options.tileOverlap : 0;
this.minLevel = options.minLevel ? options.minLevel : 0;
this.maxLevel = ( undefined !== options.maxLevel && null !== options.maxLevel ) ?
@ -230,8 +230,8 @@ $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLeve
};
$.TileSource.prototype = /** @lends OpenSeadragon.TileSource.prototype */{
/** @lends OpenSeadragon.TileSource.prototype */
$.TileSource.prototype = {
getTileSize: function( level ) {
$.console.error(
@ -240,7 +240,7 @@ $.TileSource.prototype = /** @lends OpenSeadragon.TileSource.prototype */{
);
return this._tileWidth;
},
/**
* Return the tileWidth for a given level.
* Subclasses should override this if tileWidth can be different at different levels
@ -331,7 +331,7 @@ $.TileSource.prototype = /** @lends OpenSeadragon.TileSource.prototype */{
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;
}
@ -345,11 +345,11 @@ $.TileSource.prototype = /** @lends OpenSeadragon.TileSource.prototype */{
* @param {OpenSeadragon.Point} point
*/
getTileAtPoint: function( level, point ) {
var pixel = point.times( this.dimensions.x ).times( this.getLevelScale(level) ),
tx = Math.floor( pixel.x / this.getTileWidth(level) ),
ty = Math.floor( pixel.y / this.getTileHeight(level) );
return new $.Point( tx, ty );
var numTiles = this.getNumTiles( level );
return new $.Point(
Math.floor( (point.x * numTiles.x) / 1 ),
Math.floor( (point.y * numTiles.y * this.dimensions.x) / this.dimensions.y )
);
},
/**
@ -544,7 +544,7 @@ $.TileSource.prototype = /** @lends OpenSeadragon.TileSource.prototype */{
/**
* Responsible for retriving the url which will return an image for the
* region speified 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
* announcing you have to implement it. Because of the variety of tile
* server technologies, and various specifications for building image

View File

@ -40,14 +40,19 @@ var nextHash = 1;
/**
*
* The main point of entry into creating a zoomable image on the page.
*
* The main point of entry into creating a zoomable image on the page.<br>
* <br>
* We have provided an idiomatic javascript constructor which takes
* a single object, but still support the legacy positional arguments.
*
* a single object, but still support the legacy positional arguments.<br>
* <br>
* The options below are given in order that they appeared in the constructor
* as arguments and we translate a positional call into an idiomatic call.
*
* as arguments and we translate a positional call into an idiomatic call.<br>
* <br>
* To create a viewer, you can use either of this methods:<br>
* <ul>
* <li><code>var viewer = new OpenSeadragon.Viewer(options);</code></li>
* <li><code>var viewer = OpenSeadragon(options);</code></li>
* </ul>
* @class Viewer
* @classdesc The main OpenSeadragon viewer class.
*
@ -203,6 +208,8 @@ $.Viewer = function( options ) {
this._loadQueue = [];
this.currentOverlays = [];
this._lastScrollTime = $.now(); // variable used to help normalize the scroll event speed of different devices
//Inherit some behaviors and properties
$.EventSource.call( this );
@ -232,7 +239,9 @@ $.Viewer = function( options ) {
style.left = "0px";
}(this.canvas.style));
$.setElementTouchActionNone( this.canvas );
this.canvas.tabIndex = options.tabIndex || 0;
if (options.tabIndex !== "") {
this.canvas.tabIndex = (options.tabIndex === undefined ? 0 : options.tabIndex);
}
//the container is created through applying the ControlDock constructor above
this.container.className = "openseadragon-container";
@ -328,7 +337,7 @@ $.Viewer = function( options ) {
this.world.addHandler('metrics-change', function(event) {
if (_this.viewport) {
_this.viewport.setHomeBounds(_this.world.getHomeBounds(), _this.world.getContentFactor());
_this.viewport._setContentBounds(_this.world.getHomeBounds(), _this.world.getContentFactor());
}
});
@ -357,7 +366,7 @@ $.Viewer = function( options ) {
margins: this.viewportMargins
});
this.viewport.setHomeBounds(this.world.getHomeBounds(), this.world.getContentFactor());
this.viewport._setContentBounds(this.world.getHomeBounds(), this.world.getContentFactor());
// Create the image loader
this.imageLoader = new $.ImageLoader({
@ -408,6 +417,7 @@ $.Viewer = function( options ) {
width: this.navigatorWidth,
height: this.navigatorHeight,
autoResize: this.navigatorAutoResize,
autoFade: this.navigatorAutoFade,
prefixUrl: this.prefixUrl,
viewer: this,
navigatorRotate: this.navigatorRotate,
@ -605,6 +615,14 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
options.success = function(event) {
successes++;
// TODO: now that options has other things besides tileSource, the overlays
// should probably be at the options level, not the tileSource level.
if (options.tileSource.overlays) {
for (var i = 0; i < options.tileSource.overlays.length; i++) {
_this.addOverlay(options.tileSource.overlays[i]);
}
}
if (originalSuccess) {
originalSuccess(event);
}
@ -628,14 +646,6 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
};
_this.addTiledImage(options);
// TODO: now that options has other things besides tileSource, the overlays
// should probably be at the options level, not the tileSource level.
if (options.tileSource.overlays) {
for (var i = 0; i < options.tileSource.overlays.length; i++) {
_this.addOverlay(options.tileSource.overlays[i]);
}
}
};
// TileSources
@ -777,6 +787,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
*/
setMouseNavEnabled: function( enabled ){
this.innerTracker.setTracking( enabled );
this.outerTracker.setTracking( enabled );
/**
* Raised when mouse/touch navigation is enabled or disabled (see {@link OpenSeadragon.Viewer#setMouseNavEnabled}).
*
@ -908,9 +919,14 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
docStyle.padding = "0";
this.bodyWidth = bodyStyle.width;
this.bodyHeight = bodyStyle.height;
this.docWidth = docStyle.width;
bodyStyle.width = "100%";
docStyle.width = "100%";
this.bodyHeight = bodyStyle.height;
this.docHeight = docStyle.height;
bodyStyle.height = "100%";
docStyle.height = "100%";
//when entering full screen on the ipad it wasnt sufficient to leave
//the body intact as only only the top half of the screen would
@ -971,7 +987,10 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
docStyle.padding = this.docPadding;
bodyStyle.width = this.bodyWidth;
docStyle.width = this.docWidth;
bodyStyle.height = this.bodyHeight;
docStyle.height = this.docHeight;
body.removeChild( this.element );
nodes = this.previousBody.length;
@ -1201,10 +1220,17 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
* @param {Number} [options.y=0] The Y position for the image in viewport coordinates.
* @param {Number} [options.width=1] The width for the image in viewport coordinates.
* @param {Number} [options.height] The height for the image in viewport coordinates.
* @param {OpenSeadragon.Rect} [options.fitBounds] The bounds in viewport coordinates
* to fit the image into. If specified, x, y, width and height get ignored.
* @param {OpenSeadragon.Placement} [options.fitBoundsPlacement=OpenSeadragon.Placement.CENTER]
* How to anchor the image in the bounds if options.fitBounds is set.
* @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
* browsers that support the HTML5 canvas.
* @param {Number} [options.opacity] Opacity the tiled image should be drawn at by default.
* @param {String} [options.compositeOperation] How the image is composited onto other images.
* @param {String} [options.crossOriginPolicy] The crossOriginPolicy for this specific image,
* overriding viewer.crossOriginPolicy.
* @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:
* "item", the resulting TiledImage.
@ -1237,6 +1263,12 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
if (options.opacity === undefined) {
options.opacity = this.opacity;
}
if (options.compositeOperation === undefined) {
options.compositeOperation = this.compositeOperation;
}
if (options.crossOriginPolicy === undefined) {
options.crossOriginPolicy = options.tileSource.crossOriginPolicy !== undefined ? options.tileSource.crossOriginPolicy : this.crossOriginPolicy;
}
var myQueueItem = {
options: options
@ -1286,18 +1318,20 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
}
}
this._loadQueue.push(myQueueItem);
getTileSourceImplementation( this, options.tileSource, function( tileSource ) {
if ( tileSource instanceof Array ) {
if ($.isArray(options.tileSource)) {
setTimeout(function() {
raiseAddItemFailed({
message: "[Viewer.addTiledImage] Sequences can not be added; add them one at a time instead.",
source: tileSource,
source: options.tileSource,
options: options
});
return;
}
});
return;
}
this._loadQueue.push(myQueueItem);
getTileSourceImplementation( this, options.tileSource, options, function( tileSource ) {
myQueueItem.tileSource = tileSource;
@ -1330,9 +1364,12 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
y: queueItem.options.y,
width: queueItem.options.width,
height: queueItem.options.height,
fitBounds: queueItem.options.fitBounds,
fitBoundsPlacement: queueItem.options.fitBoundsPlacement,
clip: queueItem.options.clip,
placeholderFillStyle: queueItem.options.placeholderFillStyle,
opacity: queueItem.options.opacity,
compositeOperation: queueItem.options.compositeOperation,
springStiffness: _this.springStiffness,
animationTime: _this.animationTime,
minZoomImageRatio: _this.minZoomImageRatio,
@ -1342,7 +1379,9 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
blendTime: _this.blendTime,
alwaysBlend: _this.alwaysBlend,
minPixelRatio: _this.minPixelRatio,
crossOriginPolicy: _this.crossOriginPolicy,
smoothTileEdgesMinZoom: _this.smoothTileEdgesMinZoom,
iOSDevice: _this.iOSDevice,
crossOriginPolicy: queueItem.options.crossOriginPolicy,
debugMode: _this.debugMode
});
@ -1364,6 +1403,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
if (_this.navigator) {
optionsClone = $.extend({}, queueItem.options, {
replace: false, // navigator already removed the layer, nothing to replace
originalTiledImage: tiledImage,
tileSource: queueItem.tileSource
});
@ -1383,6 +1423,31 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
} );
},
/**
* Add a simple image to the viewer.
* The options are the same as the ones in {@link OpenSeadragon.Viewer#addTiledImage}
* except for options.tileSource which is replaced by options.url.
* @function
* @param {Object} options - See {@link OpenSeadragon.Viewer#addTiledImage}
* for all the options
* @param {String} options.url - The URL of the image to add.
* @fires OpenSeadragon.World.event:add-item
* @fires OpenSeadragon.Viewer.event:add-item-failed
*/
addSimpleImage: function(options) {
$.console.assert(options, "[Viewer.addSimpleImage] options is required");
$.console.assert(options.url, "[Viewer.addSimpleImage] options.url is required");
var opts = $.extend({}, options, {
tileSource: {
type: 'image',
url: options.url
}
});
delete opts.url;
this.addTiledImage(opts);
},
// deprecated
addLayer: function( options ) {
var _this = this;
@ -1681,7 +1746,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
if( this.toolbar ){
this.toolbar.addControl(
this.navControl,
{anchor: $.ControlAnchor.TOP_LEFT}
{anchor: this.navigationControlAnchor || $.ControlAnchor.TOP_LEFT}
);
} else {
this.addControl(
@ -1744,10 +1809,12 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
* is closed which include when changing page.
* @method
* @param {Element|String|Object} element - A reference to an element or an id for
* the element which will be overlayed. Or an Object specifying the configuration for the overlay
* the element which will be overlayed. Or an Object specifying the configuration for the overlay.
* If using an object, see {@link OpenSeadragon.Overlay} for a list of
* all available options.
* @param {OpenSeadragon.Point|OpenSeadragon.Rect} location - The point or
* rectangle which will be overlayed.
* @param {OpenSeadragon.OverlayPlacement} placement - The position of the
* rectangle which will be overlayed. This is a viewport relative location.
* @param {OpenSeadragon.Placement} placement - The position of the
* viewport which the location coordinates will be treated as relative
* to.
* @param {function} onDraw - If supplied the callback is called when the overlay
@ -1789,7 +1856,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
* @property {Element} element - The overlay element.
* @property {OpenSeadragon.Point|OpenSeadragon.Rect} location
* @property {OpenSeadragon.OverlayPlacement} placement
* @property {OpenSeadragon.Placement} placement
* @property {?Object} userData - Arbitrary subscriber-defined object.
*/
this.raiseEvent( 'add-overlay', {
@ -1807,8 +1874,8 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
* @param {Element|String} element - A reference to an element or an id for
* the element which is overlayed.
* @param {OpenSeadragon.Point|OpenSeadragon.Rect} location - The point or
* rectangle which will be overlayed.
* @param {OpenSeadragon.OverlayPlacement} placement - The position of the
* rectangle which will be overlayed. This is a viewport relative location.
* @param {OpenSeadragon.Placement} placement - The position of the
* viewport which the location coordinates will be treated as relative
* to.
* @return {OpenSeadragon.Viewer} Chainable.
@ -1834,7 +1901,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
* Viewer which raised the event.
* @property {Element} element
* @property {OpenSeadragon.Point|OpenSeadragon.Rect} location
* @property {OpenSeadragon.OverlayPlacement} placement
* @property {OpenSeadragon.Placement} placement
* @property {?Object} userData - Arbitrary subscriber-defined object.
*/
this.raiseEvent( 'update-overlay', {
@ -1909,6 +1976,27 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
return this;
},
/**
* Finds an overlay identified by the reference element or element id
* and returns it as an object, return null if not found.
* @method
* @param {Element|String} element - A reference to the element or an
* element id which represents the overlay content.
* @return {OpenSeadragon.Overlay} the matching overlay or null if none found.
*/
getOverlayById: function( element ) {
var i;
element = $.getElement( element );
i = getOverlayIndex( this.currentOverlays, element );
if (i>=0) {
return this.currentOverlays[i];
} else {
return null;
}
},
/**
* Updates the sequence buttons.
* @function OpenSeadragon.Viewer.prototype._updateSequenceButtons
@ -2027,25 +2115,46 @@ function _getSafeElemSize (oElement) {
* @function
* @private
*/
function getTileSourceImplementation( viewer, tileSource, successCallback,
function getTileSourceImplementation( viewer, tileSource, imgOptions, successCallback,
failCallback ) {
var _this = viewer;
//allow plain xml strings or json strings to be parsed here
if ( $.type( tileSource ) == 'string' ) {
if ( tileSource.match( /\s*<.*/ ) ) {
//xml should start with "<" and end with ">"
if ( tileSource.match( /^\s*<.*>\s*$/ ) ) {
tileSource = $.parseXml( tileSource );
} else if ( tileSource.match( /\s*[\{\[].*/ ) ) {
//json should start with "{" or "[" and end with "}" or "]"
} else if ( tileSource.match(/^\s*[\{\[].*[\}\]]\s*$/ ) ) {
tileSource = $.parseJSON(tileSource);
}
}
function waitUntilReady(tileSource, originalTileSource) {
if (tileSource.ready) {
successCallback(tileSource);
} else {
tileSource.addHandler('ready', function () {
successCallback(tileSource);
});
tileSource.addHandler('open-failed', function (event) {
failCallback({
message: event.message,
source: originalTileSource
});
});
}
}
setTimeout( function() {
if ( $.type( tileSource ) == 'string' ) {
//If its still a string it means it must be a url at this point
tileSource = new $.TileSource({
url: tileSource,
crossOriginPolicy: imgOptions.crossOriginPolicy !== undefined ?
imgOptions.crossOriginPolicy : viewer.crossOriginPolicy,
ajaxWithCredentials: viewer.ajaxWithCredentials,
useCanvas: viewer.useCanvas,
success: function( event ) {
successCallback( event.tileSource );
}
@ -2054,10 +2163,18 @@ function getTileSourceImplementation( viewer, tileSource, successCallback,
failCallback( event );
} );
} else if ( $.isPlainObject( tileSource ) || tileSource.nodeType ) {
} else if ($.isPlainObject(tileSource) || tileSource.nodeType) {
if (tileSource.crossOriginPolicy === undefined &&
(imgOptions.crossOriginPolicy !== undefined || viewer.crossOriginPolicy !== undefined)) {
tileSource.crossOriginPolicy = imgOptions.crossOriginPolicy !== undefined ?
imgOptions.crossOriginPolicy : viewer.crossOriginPolicy;
}
if (tileSource.ajaxWithCredentials === undefined) {
tileSource.ajaxWithCredentials = viewer.ajaxWithCredentials;
}
if (tileSource.useCanvas === undefined) {
tileSource.useCanvas = viewer.useCanvas;
}
if ( $.isFunction( tileSource.getTileUrl ) ) {
//Custom tile source
@ -2075,14 +2192,13 @@ function getTileSourceImplementation( viewer, tileSource, successCallback,
return;
}
var options = $TileSource.prototype.configure.apply( _this, [ tileSource ] );
var readySource = new $TileSource( options );
successCallback( readySource );
waitUntilReady(new $TileSource(options), tileSource);
}
} else {
//can assume it's already a tile source implementation
successCallback( tileSource );
waitUntilReady(tileSource, tileSource);
}
}, 1 );
});
}
function getOverlayObject( viewer, overlay ) {
@ -2111,37 +2227,28 @@ function getOverlayObject( viewer, overlay ) {
}
var location = overlay.location;
if ( !location ) {
if ( overlay.width && overlay.height ) {
location = overlay.px !== undefined ?
viewer.viewport.imageToViewportRectangle( new $.Rect(
overlay.px,
overlay.py,
overlay.width,
overlay.height
) ) :
new $.Rect(
overlay.x,
overlay.y,
overlay.width,
overlay.height
);
} else {
location = overlay.px !== undefined ?
viewer.viewport.imageToViewportCoordinates( new $.Point(
overlay.px,
overlay.py
) ) :
new $.Point(
overlay.x,
overlay.y
);
var width = overlay.width;
var height = overlay.height;
if (!location) {
var x = overlay.x;
var y = overlay.y;
if (overlay.px !== undefined) {
var rect = viewer.viewport.imageToViewportRectangle(new $.Rect(
overlay.px,
overlay.py,
width || 0,
height || 0));
x = rect.x;
y = rect.y;
width = width !== undefined ? rect.width : undefined;
height = height !== undefined ? rect.height : undefined;
}
location = new $.Point(x, y);
}
var placement = overlay.placement;
if ( placement && ( $.type( placement ) === "string" ) ) {
placement = $.OverlayPlacement[ overlay.placement.toUpperCase() ];
if (placement && $.type(placement) === "string") {
placement = $.Placement[overlay.placement.toUpperCase()];
}
return new $.Overlay({
@ -2149,7 +2256,10 @@ function getOverlayObject( viewer, overlay ) {
location: location,
placement: placement,
onDraw: overlay.onDraw,
checkResize: overlay.checkResize
checkResize: overlay.checkResize,
width: width,
height: height,
rotationMode: overlay.rotationMode
});
}
@ -2293,6 +2403,7 @@ function onCanvasKeyDown( event ) {
function onCanvasKeyPress( event ) {
if ( !event.preventDefaultAction && !event.ctrl && !event.alt && !event.meta ) {
switch( event.keyCode ){
case 43://=|+
case 61://=|+
this.viewport.zoomBy(1.1);
this.viewport.applyConstraints();
@ -2461,22 +2572,25 @@ function onCanvasDrag( event ) {
}
function onCanvasDragEnd( event ) {
var gestureSettings;
if ( !event.preventDefaultAction && this.viewport ) {
gestureSettings = this.gestureSettingsByDeviceType( event.pointerType );
if ( gestureSettings.flickEnabled && event.speed >= gestureSettings.flickMinSpeed ) {
var amplitudeX = gestureSettings.flickMomentum * ( event.speed * Math.cos( event.direction - (Math.PI / 180 * this.viewport.degrees) ) ),
amplitudeY = gestureSettings.flickMomentum * ( event.speed * Math.sin( event.direction - (Math.PI / 180 * this.viewport.degrees) ) ),
center = this.viewport.pixelFromPoint( this.viewport.getCenter( true ) ),
target = this.viewport.pointFromPixel( new $.Point( center.x - amplitudeX, center.y - amplitudeY ) );
if( !this.panHorizontal ) {
target.x = center.x;
if (!event.preventDefaultAction && this.viewport) {
var gestureSettings = this.gestureSettingsByDeviceType(event.pointerType);
if (gestureSettings.flickEnabled &&
event.speed >= gestureSettings.flickMinSpeed) {
var amplitudeX = 0;
if (this.panHorizontal) {
amplitudeX = gestureSettings.flickMomentum * event.speed *
Math.cos(event.direction);
}
if( !this.panVertical ) {
target.y = center.y;
var amplitudeY = 0;
if (this.panVertical) {
amplitudeY = gestureSettings.flickMomentum * event.speed *
Math.sin(event.direction);
}
this.viewport.panTo( target, false );
var center = this.viewport.pixelFromPoint(
this.viewport.getCenter(true));
var target = this.viewport.pointFromPixel(
new $.Point(center.x - amplitudeX, center.y - amplitudeY));
this.viewport.panTo(target, false);
}
this.viewport.applyConstraints();
}
@ -2495,7 +2609,7 @@ function onCanvasDragEnd( event ) {
* @property {Object} originalEvent - The original DOM event.
* @property {?Object} userData - Arbitrary subscriber-defined object.
*/
this.raiseEvent( 'canvas-drag-end', {
this.raiseEvent('canvas-drag-end', {
tracker: event.eventSource,
position: event.position,
speed: event.speed,
@ -2737,44 +2851,60 @@ function onCanvasPinch( event ) {
function onCanvasScroll( event ) {
var gestureSettings,
factor;
factor,
thisScrollTime,
deltaScrollTime;
if ( !event.preventDefaultAction && this.viewport ) {
gestureSettings = this.gestureSettingsByDeviceType( event.pointerType );
if ( gestureSettings.scrollToZoom ) {
factor = Math.pow( this.zoomPerScroll, event.scroll );
this.viewport.zoomBy(
factor,
this.viewport.pointFromPixel( event.position, true )
);
this.viewport.applyConstraints();
/* Certain scroll devices fire the scroll event way too fast so we are injecting a simple adjustment to keep things
* partially normalized. If we have already fired an event within the last 'minScrollDelta' milliseconds we skip
* this one and wait for the next event. */
thisScrollTime = $.now();
deltaScrollTime = thisScrollTime - this._lastScrollTime;
if (deltaScrollTime > this.minScrollDeltaTime) {
this._lastScrollTime = thisScrollTime;
if ( !event.preventDefaultAction && this.viewport ) {
gestureSettings = this.gestureSettingsByDeviceType( event.pointerType );
if ( gestureSettings.scrollToZoom ) {
factor = Math.pow( this.zoomPerScroll, event.scroll );
this.viewport.zoomBy(
factor,
this.viewport.pointFromPixel( event.position, true )
);
this.viewport.applyConstraints();
}
}
/**
* Raised when a scroll event occurs on the {@link OpenSeadragon.Viewer#canvas} element (mouse wheel).
*
* @event canvas-scroll
* @memberof OpenSeadragon.Viewer
* @type {object}
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
* @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
* @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
* @property {Number} scroll - The scroll delta for the event.
* @property {Boolean} shift - True if the shift key was pressed during this event.
* @property {Object} originalEvent - The original DOM event.
* @property {?Object} userData - Arbitrary subscriber-defined object.
*/
this.raiseEvent( 'canvas-scroll', {
tracker: event.eventSource,
position: event.position,
scroll: event.scroll,
shift: event.shift,
originalEvent: event.originalEvent
});
if (gestureSettings && gestureSettings.scrollToZoom) {
//cancels event
return false;
}
}
/**
* Raised when a scroll event occurs on the {@link OpenSeadragon.Viewer#canvas} element (mouse wheel).
*
* @event canvas-scroll
* @memberof OpenSeadragon.Viewer
* @type {object}
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
* @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
* @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
* @property {Number} scroll - The scroll delta for the event.
* @property {Boolean} shift - True if the shift key was pressed during this event.
* @property {Object} originalEvent - The original DOM event.
* @property {?Object} userData - Arbitrary subscriber-defined object.
*/
this.raiseEvent( 'canvas-scroll', {
tracker: event.eventSource,
position: event.position,
scroll: event.scroll,
shift: event.shift,
originalEvent: event.originalEvent
});
if (gestureSettings && gestureSettings.scrollToZoom) {
//cancels event
return false;
else {
gestureSettings = this.gestureSettingsByDeviceType( event.pointerType );
if (gestureSettings && gestureSettings.scrollToZoom) {
return false; // We are swallowing this event
}
}
}
@ -2866,33 +2996,26 @@ function updateOnce( viewer ) {
return;
}
var containerSize;
if ( viewer.autoResize ) {
containerSize = _getSafeElemSize( viewer.container );
if ( !containerSize.equals( THIS[ viewer.hash ].prevContainerSize ) ) {
if ( viewer.preserveImageSizeOnResize ) {
var prevContainerSize = THIS[ viewer.hash ].prevContainerSize;
var bounds = viewer.viewport.getBounds(true);
var deltaX = (containerSize.x - prevContainerSize.x);
var deltaY = (containerSize.y - prevContainerSize.y);
var viewportDiff = viewer.viewport.deltaPointsFromPixels(new OpenSeadragon.Point(deltaX, deltaY), true);
viewer.viewport.resize(new OpenSeadragon.Point(containerSize.x, containerSize.y), false);
// Keep the center of the image in the center and just adjust the amount of image shown
bounds.width += viewportDiff.x;
bounds.height += viewportDiff.y;
bounds.x -= (viewportDiff.x / 2);
bounds.y -= (viewportDiff.y / 2);
viewer.viewport.fitBoundsWithConstraints(bounds, true);
}
else {
if (viewer.autoResize) {
var containerSize = _getSafeElemSize(viewer.container);
var prevContainerSize = THIS[viewer.hash].prevContainerSize;
if (!containerSize.equals(prevContainerSize)) {
var viewport = viewer.viewport;
if (viewer.preserveImageSizeOnResize) {
var resizeRatio = prevContainerSize.x / containerSize.x;
var zoom = viewport.getZoom() * resizeRatio;
var center = viewport.getCenter();
viewport.resize(containerSize, false);
viewport.zoomTo(zoom, null, true);
viewport.panTo(center, true);
} else {
// maintain image position
var oldBounds = viewer.viewport.getBounds();
var oldCenter = viewer.viewport.getCenter();
resizeViewportAndRecenter(viewer, containerSize, oldBounds, oldCenter);
var oldBounds = viewport.getBounds();
viewport.resize(containerSize, true);
viewport.fitBoundsWithConstraints(oldBounds, true);
}
THIS[ viewer.hash ].prevContainerSize = containerSize;
THIS[ viewer.hash ].forceRedraw = true;
THIS[viewer.hash].prevContainerSize = containerSize;
THIS[viewer.hash].forceRedraw = true;
}
}
@ -2977,27 +3100,6 @@ function updateOnce( viewer ) {
//viewer.profiler.endUpdate();
}
// This function resizes the viewport and recenters the image
// as it was before resizing.
// TODO: better adjust width and height. The new width and height
// should depend on the image dimensions and on the dimensions
// of the viewport before and after switching mode.
function resizeViewportAndRecenter( viewer, containerSize, oldBounds, oldCenter ) {
var viewport = viewer.viewport;
viewport.resize( containerSize, true );
var newBounds = new $.Rect(
oldCenter.x - ( oldBounds.width / 2.0 ),
oldCenter.y - ( oldBounds.height / 2.0 ),
oldBounds.width,
oldBounds.height
);
// let the viewport decide if the bounds are too big or too small
viewport.fitBoundsWithConstraints( newBounds, true );
}
function drawWorld( viewer ) {
viewer.imageLoader.clear();
viewer.drawer.clear();

File diff suppressed because it is too large Load Diff

View File

@ -375,34 +375,40 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W
var oldContentSize = this._contentSize ? this._contentSize.clone() : null;
var oldContentFactor = this._contentFactor || 0;
if ( !this._items.length ) {
if (!this._items.length) {
this._homeBounds = new $.Rect(0, 0, 1, 1);
this._contentSize = new $.Point(1, 1);
this._contentFactor = 1;
} else {
var bounds = this._items[0].getBounds();
this._contentFactor = this._items[0].getContentSize().x / bounds.width;
var left = bounds.x;
var top = bounds.y;
var right = bounds.x + bounds.width;
var bottom = bounds.y + bounds.height;
var box;
for ( var i = 1; i < this._items.length; i++ ) {
box = this._items[i].getBounds();
this._contentFactor = Math.max(this._contentFactor, this._items[i].getContentSize().x / box.width);
left = Math.min( left, box.x );
top = Math.min( top, box.y );
right = Math.max( right, box.x + box.width );
bottom = Math.max( bottom, box.y + box.height );
var item = this._items[0];
var bounds = item.getBounds();
this._contentFactor = item.getContentSize().x / bounds.width;
var clippedBounds = item.getClippedBounds();
var left = clippedBounds.x;
var top = clippedBounds.y;
var right = clippedBounds.x + clippedBounds.width;
var bottom = clippedBounds.y + clippedBounds.height;
for (var i = 1; i < this._items.length; i++) {
item = this._items[i];
bounds = item.getBounds();
this._contentFactor = Math.max(this._contentFactor,
item.getContentSize().x / bounds.width);
clippedBounds = item.getClippedBounds();
left = Math.min(left, clippedBounds.x);
top = Math.min(top, clippedBounds.y);
right = Math.max(right, clippedBounds.x + clippedBounds.width);
bottom = Math.max(bottom, clippedBounds.y + clippedBounds.height);
}
this._homeBounds = new $.Rect( left, top, right - left, bottom - top );
this._contentSize = new $.Point(this._homeBounds.width * this._contentFactor,
this._homeBounds = new $.Rect(left, top, right - left, bottom - top);
this._contentSize = new $.Point(
this._homeBounds.width * this._contentFactor,
this._homeBounds.height * this._contentFactor);
}
if (this._contentFactor !== oldContentFactor || !this._homeBounds.equals(oldHomeBounds) ||
!this._contentSize.equals(oldContentSize)) {
if (this._contentFactor !== oldContentFactor ||
!this._homeBounds.equals(oldHomeBounds) ||
!this._contentSize.equals(oldContentSize)) {
/**
* Raised when the home bounds or content factor change.
* @event metrics-change

View File

@ -1,4 +1,4 @@
<!DOCTYPE html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
@ -22,6 +22,7 @@
<script src="/src/mousetracker.js"></script>
<script src="/src/control.js"></script>
<script src="/src/controldock.js"></script>
<script src="/src/placement.js"></script>
<script src="/src/viewer.js"></script>
<script src="/src/navigator.js"></script>
<script src="/src/strings.js"></script>
@ -32,6 +33,7 @@
<script src="/src/osmtilesource.js"></script>
<script src="/src/tmstilesource.js"></script>
<script src="/src/legacytilesource.js"></script>
<script src="/src/imagetilesource.js"></script>
<script src="/src/tilesourcecollection.js"></script>
<script src="/src/button.js"></script>
<script src="/src/buttongroup.js"></script>
@ -51,6 +53,7 @@
<!-- Helpers -->
<script src="/test/helpers/legacy.mouse.shim.js"></script>
<script src="/test/helpers/test.js"></script>
<script src="/test/helpers/touch.js"></script>
<!-- Modules -->
<!-- Polyfill must be inserted first because it is testing functions
@ -72,8 +75,10 @@
<script src="/test/modules/tilecache.js"></script>
<script src="/test/modules/referencestrip.js"></script>
<script src="/test/modules/tilesource.js"></script>
<script src="/test/modules/dzitilesource.js"></script>
<script src="/test/modules/tilesourcecollection.js"></script>
<script src="/test/modules/spring.js"></script>
<script src="/test/modules/rectangle.js"></script>
<!-- The navigator tests are the slowest (for now; hopefully they can be sped up)
so we put them last. -->
<script src="/test/modules/navigator.js"></script>

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 678 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

View File

@ -0,0 +1,15 @@
{
"@context": "http://iiif.io/api/image/2/context.json",
"@id": "http://localhost:8000/test/data/iiif_2_0_sizes",
"protocol": "http://iiif.io/api/image",
"width": 6976,
"height": 5074,
"profile": ["http://iiif.io/api/image/2/level0.json"],
"sizes" : [
{"width" : 400, "height" : 291},
{"width" : 800, "height" : 582},
{"width" : 1600, "height" : 1164},
{"width" : 3200, "height": 2328},
{"width" : 6976, "height": 5074}
]
}

View File

@ -6,10 +6,10 @@
<script type="text/javascript" src='../lib/jquery-1.9.1.min.js'></script>
<style type="text/css">
.openseadragon1 {
width: 800px;
height: 600px;
}
.openseadragon1 {
width: 800px;
height: 600px;
}
</style>
</head>

View File

@ -24,38 +24,55 @@
<th></th>
<th>Window (pixel)</th>
<th>Container (pixel)</th>
<th>Image 1 - top left (pixel)</th>
<th>Image 2 - bottom right (pixel)</th>
<th>Viewport (point)</th>
<th>Big Image (pixel)</th>
<th>Small Image (pixel)</th>
</tr>
<tr>
<th>Cursor position</th>
<td id="windowPosition"></td>
<td id="containerPosition"></td>
<td id="image1Position"></td>
<td id="image2Position"></td>
<td id="viewportPosition"></td>
<td id="cursorWindowPosition"></td>
<td id="cursorContainerPosition"></td>
<td id="cursorViewportPosition"></td>
<td id="cursorImage1Position"></td>
<td id="cursorImage2Position"></td>
</tr>
<tr>
<th>Big Image top left position</th>
<td id="image1WindowPosition"></td>
<td id="image1ContainerPosition"></td>
<td id="image1ViewportPosition"></td>
<td id="image1Image1Position"></td>
<td id="image1Image2Position"></td>
</tr>
<tr>
<th>Small Image top left position</th>
<td id="image2WindowPosition"></td>
<td id="image2ContainerPosition"></td>
<td id="image2ViewportPosition"></td>
<td id="image2Image1Position"></td>
<td id="image2Image2Position"></td>
</tr>
<tr>
<th>Zoom</th>
<td>-</td>
<td>-</td>
<td id="viewportZoom"></td>
<td id="image1Zoom"></td>
<td id="image2Zoom"></td>
<td id="viewportZoom"></td>
</tr>
</table>
</div>
<button onclick="rotate();">Rotate</button>
<script type="text/javascript">
var viewer = OpenSeadragon({
// debugMode: true,
debugMode: true,
id: "contentDiv",
prefixUrl: "../../build/openseadragon/images/",
tileSources: [{
tileSource: "../data/testpattern.dzi"
},
{
tileSource: "../data/testpattern.dzi",
clip: new OpenSeadragon.Rect(0, 0, 500, 1000)
}, {
tileSource: "../data/testpattern.dzi",
x: 1,
y: 1,
@ -64,28 +81,29 @@
],
showNavigator: true
});
viewer.addHandler("open", onAnimation);
function pointToString(point) {
return point.x.toPrecision(4) + "," + point.y.toPrecision(4);
}
var onMouseTrackerMove = function (event) {
function onMouseTrackerMove(event) {
var viewerX = event.position.x;
var viewerY = event.position.y;
var windowPoint = new OpenSeadragon.Point(viewerX, viewerY);
$("#windowPosition").text(pointToString(windowPoint));
$("#cursorWindowPosition").text(pointToString(windowPoint));
var containerPoint = windowPoint.minus(
OpenSeadragon.getElementPosition(viewer.element));
$("#containerPosition").text(pointToString(containerPoint));
$("#cursorContainerPosition").text(pointToString(containerPoint));
var image1 = viewer.world.getItemAt(0);
var imagePoint = image1.windowToImageCoordinates(windowPoint);
$("#image1Position").text(pointToString(imagePoint));
$("#cursorImage1Position").text(pointToString(imagePoint));
var image2 = viewer.world.getItemAt(1);
var imagePoint = image2.windowToImageCoordinates(windowPoint);
$("#image2Position").text(pointToString(imagePoint));
$("#cursorImage2Position").text(pointToString(imagePoint));
var viewportPoint = viewer.viewport.windowToViewportCoordinates(windowPoint);
$("#viewportPosition").text(pointToString(viewportPoint));
};
$("#cursorViewportPosition").text(pointToString(viewportPoint));
}
mouseTracker = new OpenSeadragon.MouseTracker({
element: document,
moveHandler: onMouseTrackerMove
@ -100,9 +118,40 @@
var image2 = viewer.world.getItemAt(1);
var image2Zoom = image2.viewportToImageZoom(viewportZoom);
$("#image2Zoom").text(image2Zoom.toFixed(3));
var origin = new OpenSeadragon.Point(0, 0);
var image1WindowPoint = image1.imageToWindowCoordinates(origin);
$("#image1WindowPosition").text(pointToString(image1WindowPoint));
var image1ContainerPoint = image1.imageToViewerElementCoordinates(origin);
$("#image1ContainerPosition").text(pointToString(image1ContainerPoint));
var image1Image1Position = image1.viewportToImageCoordinates(
image1.getBounds(true).getTopLeft());
$("#image1Image1Position").text(pointToString(image1Image1Position));
var image1Image2Position = image1.viewportToImageCoordinates(
image2.getBounds(true).getTopLeft());
$("#image1Image2Position").text(pointToString(image1Image2Position));
var image1ViewportPoint = image1.imageToViewportCoordinates(origin);
$("#image1ViewportPosition").text(pointToString(image1ViewportPoint));
var image2WindowPoint = image2.imageToWindowCoordinates(origin);
$("#image2WindowPosition").text(pointToString(image2WindowPoint));
var image2ContainerPoint = image2.imageToViewerElementCoordinates(origin);
$("#image2ContainerPosition").text(pointToString(image2ContainerPoint));
var image2Image1Position = image2.viewportToImageCoordinates(
image1.getBounds(true).getTopLeft());
$("#image2Image1Position").text(pointToString(image2Image1Position));
var image2Image2Position = image2.viewportToImageCoordinates(
image2.getBounds(true).getTopLeft());
$("#image2Image2Position").text(pointToString(image2Image2Position));
var image2ViewportPoint = image2.imageToViewportCoordinates(origin);
$("#image2ViewportPosition").text(pointToString(image2ViewportPoint));
}
viewer.addHandler("animation", onAnimation);
function rotate() {
viewer.viewport.setRotation(viewer.viewport.getRotation() + 45);
onAnimation();
}
</script>
</body>
</html>

51
test/demo/iiif-sizes.html Normal file
View File

@ -0,0 +1,51 @@
<!DOCTYPE html>
<html>
<head>
<title>OpenSeadragon Demo - IIIF emulation of legacy image pyramid</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>
<p>Default OpenSeadragon viewer from IIIF Source</p>
<p>This allows IIIF even if you only have a handful of static image sizes.</p>
</div>
<!--
Notes:
legacy iiif
supports
configure
getTileWidth
getTileHeight
getLevelScale
getNumTiles
getTileAtPoint
getTileUrl getTileUrl
-->
<div id="contentDiv" class="openseadragon1"></div>
<script type="text/javascript">
var viewer = OpenSeadragon({
// debugMode: true,
id: "contentDiv",
prefixUrl: "../../build/openseadragon/images/",
tileSources: "../data/iiif_2_0_sizes/info.json",
showNavigator:true
});
</script>
</body>
</html>

34
test/demo/iiif.html Normal file
View File

@ -0,0 +1,34 @@
<!DOCTYPE html>
<html>
<head>
<title>OpenSeadragon Demo - IIIF Tiled</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>
<p>Default OpenSeadragon viewer from IIIF Tile Source.</p>
<p>This depends on a remote server providing a IIIF Image API endpoint.</p>
</div>
<div id="contentDiv" class="openseadragon1"></div>
<script type="text/javascript">
var viewer = OpenSeadragon({
// debugMode: true,
id: "contentDiv",
prefixUrl: "../../build/openseadragon/images/",
tileSources: "http://wellcomelibrary.org/iiif-img/b11768265-0/a6801943-b8b4-4674-908c-7d5b27e70569/info.json",
showNavigator:true
});
</script>
</body>
</html>

56
test/demo/legacy.html Normal file
View File

@ -0,0 +1,56 @@
<!DOCTYPE html>
<html>
<head>
<title>OpenSeadragon Demo - Legacy image pyramid</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>
Use an array of full images at different sizes.
</div>
<div id="contentDiv" class="openseadragon1"></div>
<script type="text/javascript">
var viewer = OpenSeadragon({
// debugMode: true,
id: "contentDiv",
prefixUrl: "../../build/openseadragon/images/",
tileSources: {
type: 'legacy-image-pyramid',
levels:[{
url: '/test/data/iiif_2_0_sizes/full/400,/0/default.jpg',
height: 291,
width: 400
},{
url: '/test/data/iiif_2_0_sizes/full/800,/0/default.jpg',
height: 582,
width: 800
},{
url: '/test/data/iiif_2_0_sizes/full/1600,/0/default.jpg',
height: 1164,
width: 1600
},{
url: '/test/data/iiif_2_0_sizes/full/3200,/0/default.jpg',
height: 2328,
width: 3200
},{
url: '/test/data/iiif_2_0_sizes/full/6976,/0/default.jpg',
height: 5074,
width: 6976
}]
},
showNavigator:true
});
</script>
</body>
</html>

View File

@ -1,41 +1,99 @@
<html>
<title>OpenSeadragon Overlay 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>
<title>OpenSeadragon Overlay 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 id="contentDiv" class="openseadragon1"></div>
<div id="annotation-div">
<input type="button" value="Hide Overlay" id="hideOverlay">
<div id="contentDiv" class="openseadragon1"></div>
<div id="annotation-div">
<input type="button" value="Hide Overlays" id="hideOverlays">
<input type="button" value="Rotate" id="rotate">
<span id="degrees">0deg</span>
</div>
<script type="text/javascript">
var viewer = OpenSeadragon({
id: "contentDiv",
prefixUrl: "../../build/openseadragon/images/",
tileSources: "../data/testpattern.dzi",
});
viewer.addHandler("open", function(event) {
var elt = document.createElement("div");
elt.style.background = "green";
elt.id = "runtime-overlay";
elt.style.border = "1px solid red";
viewer.addOverlay( elt, new OpenSeadragon.Rect(0.2, 0.2, 0.75, 0.75) );
});
$("#hideOverlay").click(function(){
$("#runtime-overlay").toggle();
});
var viewer = OpenSeadragon({
id: "contentDiv",
prefixUrl: "../../build/openseadragon/images/",
tileSources: "../data/testpattern.dzi",
minZoomImageRatio: 0,
maxZoomPixelRatio: 10
});
viewer.addHandler("open", function(event) {
var elt = document.createElement("div");
elt.className = "runtime-overlay";
elt.style.background = "green";
elt.style.outline = "3px solid red";
elt.style.opacity = "0.7";
elt.textContent = "Scaled overlay";
viewer.addOverlay({
element: elt,
location: new OpenSeadragon.Rect(0.21, 0.21, 0.099, 0.099),
rotationMode: OpenSeadragon.OverlayRotationMode.BOUNDING_BOX
});
elt = document.createElement("div");
elt.className = "runtime-overlay";
elt.style.background = "white";
elt.style.outline = "3px solid red";
elt.style.width = "100px";
elt.textContent = "Scaled vertically";
viewer.addOverlay({
element: elt,
location: new OpenSeadragon.Point(0.6, 0.6),
height: 0.1,
placement: OpenSeadragon.Placement.TOP_LEFT,
rotationMode: OpenSeadragon.OverlayRotationMode.NO_ROTATION
});
elt = document.createElement("div");
elt.className = "runtime-overlay";
elt.style.background = "white";
elt.style.opacity = "0.5";
elt.style.outline = "1px solid blue";
elt.style.height = "100px";
elt.textContent = "Scaled horizontally";
viewer.addOverlay({
element: elt,
location: new OpenSeadragon.Point(0.1, 0.5),
width: 0.1
});
elt = document.createElement("div");
elt.className = "runtime-overlay";
elt.style.background = "white";
elt.style.opacity = "0.5";
elt.style.outline = "5px solid pink";
elt.style.width = "100px";
elt.style.height = "100px";
elt.textContent = "Not scaled, centered in the middle";
viewer.addOverlay({
element: elt,
location: new OpenSeadragon.Point(0.5, 0.5),
placement: OpenSeadragon.Placement.CENTER,
checkResize: false,
rotationMode: OpenSeadragon.OverlayRotationMode.EXACT
});
});
$("#hideOverlays").click(function(){
$(".runtime-overlay").toggle();
});
$("#rotate").click(function() {
viewer.viewport.setRotation(viewer.viewport.getRotation() + 22.5);
$("#degrees").text(viewer.viewport.getRotation() + "deg");
});
</script>
</body>
</html>

View File

@ -68,6 +68,24 @@
ok( Util.equalsWithVariance( value1, value2, variance ), message + " Expected:" + value1 + " Found: " + value2 + " Variance: " + variance );
},
// ----------
assertPointsEquals: function (pointA, pointB, precision, message) {
Util.assessNumericValue(pointA.x, pointB.x, precision, message + " x: ");
Util.assessNumericValue(pointA.y, pointB.y, precision, message + " y: ");
},
// ----------
assertRectangleEquals: function (rectA, rectB, precision, message) {
Util.assessNumericValue(rectA.x, rectB.x, precision, message + " x: ");
Util.assessNumericValue(rectA.y, rectB.y, precision, message + " y: ");
Util.assessNumericValue(rectA.width, rectB.width, precision,
message + " width: ");
Util.assessNumericValue(rectA.height, rectB.height, precision,
message + " height: ");
Util.assessNumericValue(rectA.degrees, rectB.degrees, precision,
message + " degrees: ");
},
// ----------
timeWatcher: function ( time ) {
time = time || 2000;

134
test/helpers/touch.js Normal file
View File

@ -0,0 +1,134 @@
/* global TouchUtil, $ */
(function () {
var touches,
identifier,
target;
// ----------
window.TouchUtil = {
reset: function () {
touches = [];
identifier = 0;
},
initTracker: function ( tracker ) {
// for testing in other touch-enabled browsers
if ( !('ontouchstart' in window) ) {
tracker.setTracking( false );
OpenSeadragon.MouseTracker.subscribeEvents.push( 'touchstart', 'touchend' );
tracker.setTracking( true );
}
target = tracker.element;
},
resetTracker: function ( tracker ) {
// for testing in other touch-enabled browsers
if ( !('ontouchstart' in window) ) {
tracker.setTracking( false );
['touchstart', 'touchend'].forEach(function ( type ) {
var index = OpenSeadragon.MouseTracker.subscribeEvents.indexOf( type );
if ( index > -1 ) {
OpenSeadragon.MouseTracker.subscribeEvents.splice( index, 1 );
}
});
tracker.setTracking( true );
}
target = null;
},
start: function () {
var touch,
event,
newTouches = [];
for ( var i = 0; i < arguments.length; i++ ) {
touch = createTouch(
target.offsetLeft + arguments[ i ][ 0 ],
target.offsetTop + arguments[ i ][ 1 ]
);
touches.push( touch );
newTouches.push( touch );
}
event = createTouchEvent( 'touchstart', newTouches );
target.dispatchEvent( event );
return newTouches.length === 1 ? newTouches[ 0 ] : newTouches;
},
end: function ( changedTouches ) {
if ( !$.isArray( changedTouches ) ) {
changedTouches = [ changedTouches ];
}
var event;
touches = touches.filter(function ( touch ) {
return changedTouches.indexOf( touch ) === -1;
});
event = createTouchEvent( 'touchend', changedTouches );
target.dispatchEvent( event );
}
};
// ----------
function createTouch( x, y ) {
try {
// new spec
return new Touch({
identifier: identifier++,
target: target,
pageX: target.offsetLeft + x,
pageY: target.offsetTop + y
} );
} catch (e) {
// legacy
return document.createTouch( window, target, identifier++, x, y, x, y );
}
}
function createTouchList( touches ) {
// legacy
return document.createTouchList.apply( document, touches );
}
function createTouchEvent( type, changedTouches ) {
try {
// new spec
return new TouchEvent( type, {
view: window,
bubbles: true,
cancelable: true,
touches: touches,
targetTouches: touches,
changedTouches: changedTouches
} );
} catch (e) {
// legacy
var touchEvent = document.createEvent( 'TouchEvent' );
var touch1 = changedTouches[ 0 ];
touchEvent.initTouchEvent(
createTouchList( touches ), // touches
createTouchList( touches ), // targetTouches
createTouchList( changedTouches ), // changedTouches
type, // type
window, // view
touch1.screenX, // screenX
touch1.screenY, // screenY
touch1.clientX, // clientX
touch1.clientY, // clientY
false, // ctrlKey
false, // altKey
false, // shiftKey
false // metaKey
);
return touchEvent;
}
}
})();

View File

@ -314,26 +314,15 @@
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
callback(!isCanvasTainted(ctx));
callback(!OpenSeadragon.isCanvasTainted(canvas));
};
img.src = corsImg;
}
function isCanvasTainted(context) {
var isTainted = false;
try {
// We test if the canvas is tainted by retrieving data from it.
// An exception will be raised if the canvas is tainted.
var url = context.getImageData(0, 0, 1, 1);
} catch (e) {
isTainted = true;
}
return isTainted;
}
asyncTest( 'CrossOriginPolicyMissing', function () {
viewer.crossOriginPolicy = false;
viewer.smoothTileEdgesMinZoom = Infinity;
viewer.open( {
type: 'legacy-image-pyramid',
levels: [ {
@ -343,7 +332,8 @@
} ]
} );
viewer.addHandler('tile-drawn', function() {
ok(isCanvasTainted(viewer.drawer.context), "Canvas should be tainted.");
ok(OpenSeadragon.isCanvasTainted(viewer.drawer.context.canvas),
"Canvas should be tainted.");
start();
});
@ -366,7 +356,8 @@
} ]
} );
viewer.addHandler('tile-drawn', function() {
ok(!isCanvasTainted(viewer.drawer.context), "Canvas should not be tainted.");
ok(!OpenSeadragon.isCanvasTainted(viewer.drawer.context.canvas),
"Canvas should not be tainted.");
start();
});
}
@ -374,4 +365,62 @@
} );
asyncTest( 'CrossOriginPolicyOption', function () {
browserSupportsImgCrossOrigin(function(supported) {
if (!supported) {
expect(0);
start();
} else {
viewer.crossOriginPolicy = "Anonymous";
viewer.smoothTileEdgesMinZoom = Infinity;
viewer.addTiledImage( {
tileSource: {
type: 'legacy-image-pyramid',
levels: [ {
url: corsImg,
width: 135,
height: 155
} ]
},
crossOriginPolicy : false
} );
viewer.addHandler('tile-drawn', function() {
ok(OpenSeadragon.isCanvasTainted(viewer.drawer.context.canvas),
"Canvas should be tainted.");
start();
});
}
});
} );
asyncTest( 'CrossOriginPolicyTileSource', function () {
browserSupportsImgCrossOrigin(function(supported) {
if (!supported) {
expect(0);
start();
} else {
viewer.crossOriginPolicy = false;
viewer.smoothTileEdgesMinZoom = Infinity;
viewer.addTiledImage( {
tileSource: {
type: 'legacy-image-pyramid',
levels: [ {
url: corsImg,
width: 135,
height: 155
} ],
crossOriginPolicy : "Anonymous"
}
} );
viewer.addHandler('tile-drawn', function() {
ok(!OpenSeadragon.isCanvasTainted(viewer.drawer.context.canvas),
"Canvas should not be tainted.");
start();
});
}
});
} );
})();

View File

@ -0,0 +1,38 @@
/*global module:true, test:true, equal:true, OpenSeadragon:true*/
(function() {
module('DziTileSource', {
setup: function() {
testLog.reset();
}
});
function testImplicitTilesUrl(dziUrl, expected, msg) {
var source = new OpenSeadragon.DziTileSource();
var options = source.configure({
Image: {Size: {Width:0, Height: 0}}
}, dziUrl);
equal(options.tilesUrl, expected, msg);
}
test('test implicit tilesUrl guessed from dzi url', function() {
testImplicitTilesUrl(
'/path/my.dzi', '/path/my_files/',
'dzi extension should be stripped');
testImplicitTilesUrl(
'/path/my', '/path/my_files/',
'no extension should still produce _files path');
testImplicitTilesUrl(
'/my/', '/my_files/',
'no extension with trailing slash should preserve slash');
testImplicitTilesUrl(
'my.xml', 'my_files/',
'relative link should stay the same');
testImplicitTilesUrl(
'/p/foo.dzi?a=1&b=2', '/p/foo_files/',
'querystring in dzi url should be ignored');
});
}());

View File

@ -1,4 +1,4 @@
/* global module, asyncTest, $, ok, equal, notEqual, start, test, Util, testLog */
/* global module, asyncTest, $, ok, equal, notEqual, start, test, TouchUtil, Util, testLog */
(function () {
var viewer;
@ -678,45 +678,96 @@
} );
// ----------
asyncTest( 'Viewer: preventDefaultAction', function () {
var $canvas = $( viewer.element ).find( '.openseadragon-canvas' ).not( '.navigator .openseadragon-canvas' ),
tracker = viewer.innerTracker,
origClickHandler,
origDragHandler,
dragCount = 10,
originalZoom = 0,
originalBounds = null;
if ('TouchEvent' in window) {
asyncTest( 'MouseTracker: touch events', function () {
var $canvas = $( viewer.element ).find( '.openseadragon-canvas' ).not( '.navigator .openseadragon-canvas' ),
tracker = viewer.innerTracker,
touches;
var onOpen = function ( event ) {
viewer.removeHandler( 'open', onOpen );
// Hook viewer events to set preventDefaultAction
origClickHandler = tracker.clickHandler;
tracker.clickHandler = function ( event ) {
event.preventDefaultAction = true;
return origClickHandler( event );
};
origDragHandler = tracker.dragHandler;
tracker.dragHandler = function ( event ) {
event.preventDefaultAction = true;
return origDragHandler( event );
var reset = function () {
touches = [];
TouchUtil.reset();
};
originalZoom = viewer.viewport.getZoom();
originalBounds = viewer.viewport.getBounds();
var event = {
clientX:1,
clientY:1
var assessTouchExpectations = function ( expected ) {
var pointersList = tracker.getActivePointersListByType( 'touch' );
if ('captureCount' in expected) {
equal( pointersList.captureCount, expected.captureCount, expected.description + 'Pointer capture count matches expected (' + expected.captureCount + ')' );
}
if ('contacts' in expected) {
equal( pointersList.contacts, expected.contacts, expected.description + 'Pointer contact count matches expected (' + expected.contacts + ')' );
}
if ('trackedPointers' in expected) {
equal( pointersList.getLength(), expected.trackedPointers, expected.description + 'Tracked pointer count matches expected (' + expected.trackedPointers + ')' );
}
};
$canvas.simulate( 'focus', event );
var onOpen = function ( event ) {
viewer.removeHandler( 'open', onOpen );
TouchUtil.initTracker( tracker );
// start-end-end (multi-touch start event)
reset();
touches = TouchUtil.start( [0,0], [20,20] );
assessTouchExpectations({
description: 'start-end-end (multi-touch start event) [capture]: ',
captureCount: 2,
contacts: 2,
trackedPointers: 2
});
TouchUtil.end( touches[1] );
TouchUtil.end( touches[0] );
assessTouchExpectations({
description: 'start-end-end (multi-touch start event) [release]: ',
captureCount: 0,
contacts: 0,
trackedPointers: 0
});
// start-start-end (multi-touch end event)
reset();
touches.push( TouchUtil.start([0, 0]) );
touches.push( TouchUtil.start([20, 20]) );
assessTouchExpectations({
description: 'start-start-end (multi-touch end event) [capture]: ',
captureCount: 2,
contacts: 2,
trackedPointers: 2
});
TouchUtil.end( touches );
assessTouchExpectations({
description: 'start-start-end (multi-touch end event) [release]: ',
captureCount: 0,
contacts: 0,
trackedPointers: 0
});
TouchUtil.resetTracker( tracker );
viewer.close();
start();
};
viewer.addHandler( 'open', onOpen );
viewer.open( '/test/data/testpattern.dzi' );
} );
}
// ----------
asyncTest('Viewer: preventDefaultAction', function() {
var $canvas = $(viewer.element).find('.openseadragon-canvas')
.not('.navigator .openseadragon-canvas');
var tracker = viewer.innerTracker;
var epsilon = 0.0000001;
function simulateClickAndDrag() {
$canvas.simulate('focus');
// Drag to pan
Util.simulateViewerClickWithDrag( {
viewer: viewer,
widthFactor: 0.25,
heightFactor: 0.25,
dragCount: dragCount,
dragCount: 10,
dragDx: 1,
dragDy: 1
} );
@ -729,21 +780,58 @@
dragDx: 0,
dragDy: 0
} );
$canvas.simulate( 'blur', event );
$canvas.simulate('blur');
}
var zoom = viewer.viewport.getZoom(),
bounds = viewer.viewport.getBounds();
var onOpen = function() {
viewer.removeHandler('open', onOpen);
equal( zoom, originalZoom, "Zoom prevented" );
ok( bounds.x == originalBounds.x && bounds.y == originalBounds.y, 'Pan prevented' );
// Hook viewer events to set preventDefaultAction
var origClickHandler = tracker.clickHandler;
tracker.clickHandler = function(event) {
event.preventDefaultAction = true;
return origClickHandler(event);
};
var origDragHandler = tracker.dragHandler;
tracker.dragHandler = function(event) {
event.preventDefaultAction = true;
return origDragHandler(event);
};
var originalZoom = viewer.viewport.getZoom();
var originalBounds = viewer.viewport.getBounds();
simulateClickAndDrag();
var zoom = viewer.viewport.getZoom();
var bounds = viewer.viewport.getBounds();
Util.assessNumericValue(zoom, originalZoom, epsilon,
"Zoom should be prevented");
Util.assertRectangleEquals(bounds, originalBounds, epsilon,
'Pan should be prevented');
tracker.clickHandler = origClickHandler;
tracker.dragHandler = origDragHandler;
simulateClickAndDrag();
var zoom = viewer.viewport.getZoom();
var bounds = viewer.viewport.getBounds();
Util.assessNumericValue(zoom, 0.002, epsilon,
"Zoom should not be prevented");
Util.assertRectangleEquals(
new OpenSeadragon.Rect(-249.5, -0.25, 500, 0.5),
bounds,
epsilon,
'Pan should not be prevented');
viewer.close();
start();
};
viewer.addHandler( 'open', onOpen );
viewer.open( '/test/data/testpattern.dzi' );
} );
viewer.addHandler('open', onOpen);
viewer.open('/test/data/testpattern.dzi');
});
// ----------
asyncTest( 'EventSource/MouseTracker/Viewer: event.originalEvent event.userData canvas-drag canvas-drag-end canvas-release canvas-click', function () {
@ -949,6 +1037,59 @@
viewer.open( '/test/data/testpattern.dzi' );
} );
// ----------
test('EventSource: addOnceHandler', function() {
var eventSource = new OpenSeadragon.EventSource();
var userData = 'data';
var eventData = {
foo: 1
};
var handlerCalledCount = 0;
eventSource.addOnceHandler('test-event', function(event) {
handlerCalledCount++;
strictEqual(event.foo, eventData.foo,
'Event data should be transmitted to the event.');
strictEqual(event.userData, userData,
'User data should be transmitted to the event.');
}, userData);
strictEqual(0, handlerCalledCount,
'Handler should not have been called yet.');
eventSource.raiseEvent('test-event', eventData);
strictEqual(1, handlerCalledCount,
'Handler should have been called once.');
eventSource.raiseEvent('test-event', eventData);
strictEqual(1, handlerCalledCount,
'Handler should still have been called once.');
});
// ----------
test('EventSource: addOnceHandler 2 times', function() {
var eventSource = new OpenSeadragon.EventSource();
var userData = 'data';
var eventData = {
foo: 1
};
var handlerCalledCount = 0;
eventSource.addOnceHandler('test-event', function(event) {
handlerCalledCount++;
strictEqual(event.foo, eventData.foo,
'Event data should be transmitted to the event.');
strictEqual(event.userData, userData,
'User data should be transmitted to the event.');
}, userData, 2);
strictEqual(0, handlerCalledCount,
'Handler should not have been called yet.');
eventSource.raiseEvent('test-event', eventData);
strictEqual(1, handlerCalledCount,
'Handler should have been called once.');
eventSource.raiseEvent('test-event', eventData);
strictEqual(2, handlerCalledCount,
'Handler should have been called twice.');
eventSource.raiseEvent('test-event', eventData);
strictEqual(2, handlerCalledCount,
'Handler should still have been called twice.');
});
// ----------
asyncTest( 'Viewer: tile-drawing event', function () {
var tileDrawing = function ( event ) {

View File

@ -114,6 +114,11 @@
testOpenUrl('iiif_2_0_tiled/info.json');
});
// ----------
asyncTest('IIIF 2.0 JSON, sizes array only', function() {
testOpenUrl('iiif_2_0_sizes/info.json');
});
// ----------
asyncTest('IIIF 2.0 JSON String', function() {
testOpen(
@ -142,4 +147,45 @@
'}');
});
// ----------
asyncTest('ImageTileSource', function () {
testOpen({
type: "image",
url: "/test/data/A.png"
});
});
// ----------
asyncTest('Legacy Image Pyramid', function() {
// Although it is using image paths that happen to be in IIIF format, this is not a IIIFTileSource.
// The url values are opaque, just image locations.
// When emulating a legacy pyramid, IIIFTileSource calls functions from LegacyTileSource, so this
// adds a test for the legacy functionality too.
testOpen({
type: 'legacy-image-pyramid',
levels:[{
url: '/test/data/iiif_2_0_sizes/full/400,/0/default.jpg',
height: 291,
width: 400
},{
url: '/test/data/iiif_2_0_sizes/full/800,/0/default.jpg',
height: 582,
width: 800
},{
url: '/test/data/iiif_2_0_sizes/full/1600,/0/default.jpg',
height: 1164,
width: 1600
},{
url: '/test/data/iiif_2_0_sizes/full/3200,/0/default.jpg',
height: 2328,
width: 3200
},{
url: '/test/data/iiif_2_0_sizes/full/6976,/0/default.jpg',
height: 5074,
width: 6976
}]
});
});
})();

View File

@ -5,12 +5,12 @@
module( 'Multi-Image', {
setup: function() {
$( '<div id="itemsexample"></div>' ).appendTo( "#qunit-fixture" );
$( '<div id="example"></div>' ).appendTo( "#qunit-fixture" );
testLog.reset();
viewer = OpenSeadragon( {
id: 'itemsexample',
id: 'example',
prefixUrl: '/build/openseadragon/images/',
springStiffness: 100 // Faster animation = faster tests
});
@ -21,7 +21,7 @@
}
viewer = null;
$( "#itemsexample" ).remove();
$("#example").remove();
}
} );
@ -189,4 +189,83 @@
]);
});
asyncTest('Viewer.addSimpleImage', function() {
viewer.addHandler("open", function openHandler() {
viewer.removeHandler("open", openHandler);
viewer.world.addHandler('add-item', function itemAdded(event) {
viewer.world.removeHandler('add-item', itemAdded);
equal(event.item.opacity, 0.5,
'Opacity option should be set when using addSimpleImage');
start();
});
viewer.addSimpleImage({
url: '/test/data/A.png',
opacity: 0.5
});
});
viewer.open('/test/data/testpattern.dzi');
});
asyncTest('Transparent image on top of others', function() {
viewer.open('/test/data/testpattern.dzi');
var density = OpenSeadragon.pixelDensityRatio;
viewer.addHandler('open', function() {
var firstImage = viewer.world.getItemAt(0);
firstImage.addHandler('fully-loaded-change', function() {
var imageData = viewer.drawer.context.getImageData(0, 0,
500 * OpenSeadragon.pixelDensityRatio, 500 * density);
// Pixel 250,250 will be in the hole of the A
var expectedVal = getPixelValue(imageData, 250 * density, 250 * density);
notEqual(expectedVal.r, 0, 'Red channel should not be 0');
notEqual(expectedVal.g, 0, 'Green channel should not be 0');
notEqual(expectedVal.b, 0, 'Blue channel should not be 0');
notEqual(expectedVal.a, 0, 'Alpha channel should not be 0');
viewer.addSimpleImage({
url: '/test/data/A.png',
success: function() {
var secondImage = viewer.world.getItemAt(1);
secondImage.addHandler('fully-loaded-change', function() {
var imageData = viewer.drawer.context.getImageData(0, 0, 500 * density, 500 * density);
var actualVal = getPixelValue(imageData, 250 * density, 250 * density);
equal(actualVal.r, expectedVal.r,
'Red channel should not change in transparent part of the A');
equal(actualVal.g, expectedVal.g,
'Green channel should not change in transparent part of the A');
equal(actualVal.b, expectedVal.b,
'Blue channel should not change in transparent part of the A');
equal(actualVal.a, expectedVal.a,
'Alpha channel should not change in transparent part of the A');
var onAVal = getPixelValue(imageData, 333 * density, 250 * density);
equal(onAVal.r, 0, 'Red channel should be null on the A');
equal(onAVal.g, 0, 'Green channel should be null on the A');
equal(onAVal.b, 0, 'Blue channel should be null on the A');
equal(onAVal.a, 255, 'Alpha channel should be 255 on the A');
start();
});
}
});
});
});
function getPixelValue(imageData, x, y) {
var offset = 4 * (y * imageData.width + x);
return {
r: imageData.data[offset],
g: imageData.data[offset + 1],
b: imageData.data[offset + 2],
a: imageData.data[offset + 3]
};
}
});
})();

View File

@ -78,9 +78,11 @@
if (navigator === null) {
navigator = $(".navigator");
navigatorScaleFactor = Math.min(navigator.width() / viewer.viewport.contentSize.x, navigator.height() / viewer.viewport.contentSize.y);
displayRegionWidth = viewer.viewport.contentSize.x * navigatorScaleFactor;
displayRegionHeight = viewer.viewport.contentSize.y * navigatorScaleFactor;
navigatorScaleFactor = Math.min(
navigator.width() / viewer.viewport._contentSize.x,
navigator.height() / viewer.viewport._contentSize.y);
displayRegionWidth = viewer.viewport._contentSize.x * navigatorScaleFactor;
displayRegionHeight = viewer.viewport._contentSize.y * navigatorScaleFactor;
contentStartFromLeft = (navigator.width() - displayRegionWidth) / 2;
contentStartFromTop = (navigator.height() - displayRegionHeight) / 2;
}
@ -91,8 +93,8 @@
regionBoundsInPoints = new OpenSeadragon.Rect(expectedDisplayRegionXLocation, expectedDisplayRegionYLocation, expectedDisplayRegionWidth, expectedDisplayRegionHeight);
if (debug) {
console.log('Image width: ' + viewer.viewport.contentSize.x + '\n' +
'Image height: ' + viewer.viewport.contentSize.y + '\n' +
console.log('Image width: ' + viewer.viewport._contentSize.x + '\n' +
'Image height: ' + viewer.viewport._contentSize.y + '\n' +
'navigator.width(): ' + navigator.width() + '\n' +
'navigator.height(): ' + navigator.height() + '\n' +
'navigatorScaleFactor: ' + navigatorScaleFactor + '\n' +
@ -200,28 +202,28 @@
var assessViewerInCorner = function (theContentCorner) {
return function () {
var expectedXCoordinate, expecteYCoordinate;
var expectedXCoordinate, expectedYCoordinate;
if (theContentCorner === "TOPLEFT") {
expectedXCoordinate = 0;
expecteYCoordinate = 0;
expectedYCoordinate = 0;
}
else if (theContentCorner === "TOPRIGHT") {
expectedXCoordinate = 1 - viewer.viewport.getBounds().width;
expecteYCoordinate = 0;
expectedYCoordinate = 0;
}
else if (theContentCorner === "BOTTOMRIGHT") {
expectedXCoordinate = 1 - viewer.viewport.getBounds().width;
expecteYCoordinate = 1 / viewer.source.aspectRatio - viewer.viewport.getBounds().height;
expectedYCoordinate = 1 / viewer.source.aspectRatio - viewer.viewport.getBounds().height;
}
else if (theContentCorner === "BOTTOMLEFT") {
expectedXCoordinate = 0;
expecteYCoordinate = 1 / viewer.source.aspectRatio - viewer.viewport.getBounds().height;
expectedYCoordinate = 1 / viewer.source.aspectRatio - viewer.viewport.getBounds().height;
}
if (viewer.viewport.getBounds().width < 1) {
Util.assessNumericValue(expectedXCoordinate, viewer.viewport.getBounds().x, 0.04, ' Viewer at ' + theContentCorner + ', x coord');
}
if (viewer.viewport.getBounds().height < 1 / viewer.source.aspectRatio) {
Util.assessNumericValue(expecteYCoordinate, viewer.viewport.getBounds().y, 0.04, ' Viewer at ' + theContentCorner + ', y coord');
Util.assessNumericValue(expectedYCoordinate, viewer.viewport.getBounds().y, 0.04, ' Viewer at ' + theContentCorner + ', y coord');
}
};
};
@ -801,7 +803,6 @@
});
asyncTest('Item positions including collection mode', function() {
var navAddCount = 0;
viewer = OpenSeadragon({
id: 'example',
@ -815,16 +816,16 @@
var openHandler = function() {
viewer.removeHandler('open', openHandler);
viewer.navigator.world.addHandler('add-item', navOpenHandler);
// The navigator may already have added the items.
navOpenHandler();
};
var navOpenHandler = function(event) {
navAddCount++;
if (navAddCount === 2) {
if (viewer.navigator.world.getItemCount() === 2) {
viewer.navigator.world.removeHandler('add-item', navOpenHandler);
setTimeout(function() {
// Test initial formation
equal(viewer.navigator.world.getItemCount(), 2, 'navigator has both items');
for (var i = 0; i < 2; i++) {
propEqual(viewer.navigator.world.getItemAt(i).getBounds(),
viewer.world.getItemAt(i).getBounds(), 'bounds are the same');
@ -868,4 +869,57 @@
});
asyncTest('Viewer rotation applied to navigator by default', function() {
viewer = OpenSeadragon({
id: 'example',
prefixUrl: '/build/openseadragon/images/',
tileSources: '/test/data/tall.dzi',
springStiffness: 100, // Faster animation = faster tests
showNavigator: true,
degrees: 45
});
viewer.addHandler('open', function openHandler() {
viewer.removeHandler('open', openHandler);
var navigator = viewer.navigator;
equal(navigator.viewport.getRotation(), 45,
"Rotation set in constructor should be applied to navigator by default.");
viewer.viewport.setRotation(90);
equal(navigator.viewport.getRotation(), 90,
"Rotation set by setRotation should be applied to navigator by default.");
start();
});
});
asyncTest('Viewer rotation not applied to navigator when navigatorRotate=false', function() {
viewer = OpenSeadragon({
id: 'example',
prefixUrl: '/build/openseadragon/images/',
tileSources: '/test/data/tall.dzi',
springStiffness: 100, // Faster animation = faster tests
showNavigator: true,
degrees: 45,
navigatorRotate: false
});
viewer.addHandler('open', function openHandler() {
viewer.removeHandler('open', openHandler);
var navigator = viewer.navigator;
equal(navigator.viewport.getRotation(), 0,
"Rotation set in constructor should not be applied to navigator when navigatorRotate is false.");
viewer.viewport.setRotation(90);
equal(navigator.viewport.getRotation(), 0,
"Rotation set by setRotation should not be applied to navigator when navigatorRotate is false.");
start();
});
});
})();

File diff suppressed because it is too large Load Diff

303
test/modules/rectangle.js Normal file
View File

@ -0,0 +1,303 @@
/* global module, asyncTest, $, ok, equal, notEqual, start, test, Util, testLog */
(function() {
module('Rectangle', {});
var precision = 0.000000001;
test('Constructor', function() {
var rect = new OpenSeadragon.Rect(1, 2, 3, 4, 5);
strictEqual(rect.x, 1, 'rect.x should be 1');
strictEqual(rect.y, 2, 'rect.y should be 2');
strictEqual(rect.width, 3, 'rect.width should be 3');
strictEqual(rect.height, 4, 'rect.height should be 4');
strictEqual(rect.degrees, 5, 'rect.degrees should be 5');
rect = new OpenSeadragon.Rect();
strictEqual(rect.x, 0, 'rect.x should be 0');
strictEqual(rect.y, 0, 'rect.y should be 0');
strictEqual(rect.width, 0, 'rect.width should be 0');
strictEqual(rect.height, 0, 'rect.height should be 0');
strictEqual(rect.degrees, 0, 'rect.degrees should be 0');
rect = new OpenSeadragon.Rect(0, 0, 1, 2, -405);
Util.assessNumericValue(Math.sqrt(2) / 2, rect.x, precision,
'rect.x should be sqrt(2)/2');
Util.assessNumericValue(-Math.sqrt(2) / 2, rect.y, precision,
'rect.y should be -sqrt(2)/2');
Util.assessNumericValue(2, rect.width, precision,
'rect.width should be 2');
Util.assessNumericValue(1, rect.height, precision,
'rect.height should be 1');
strictEqual(45, rect.degrees, 'rect.degrees should be 45');
rect = new OpenSeadragon.Rect(0, 0, 1, 2, 135);
Util.assessNumericValue(-Math.sqrt(2), rect.x, precision,
'rect.x should be -sqrt(2)');
Util.assessNumericValue(-Math.sqrt(2), rect.y, precision,
'rect.y should be -sqrt(2)');
Util.assessNumericValue(2, rect.width, precision,
'rect.width should be 2');
Util.assessNumericValue(1, rect.height, precision,
'rect.height should be 1');
strictEqual(45, rect.degrees, 'rect.degrees should be 45');
rect = new OpenSeadragon.Rect(0, 0, 1, 1, 585);
Util.assessNumericValue(0, rect.x, precision,
'rect.x should be 0');
Util.assessNumericValue(-Math.sqrt(2), rect.y, precision,
'rect.y should be -sqrt(2)');
Util.assessNumericValue(1, rect.width, precision,
'rect.width should be 1');
Util.assessNumericValue(1, rect.height, precision,
'rect.height should be 1');
strictEqual(45, rect.degrees, 'rect.degrees should be 45');
});
test('getTopLeft', function() {
var rect = new OpenSeadragon.Rect(1, 2, 3, 4, 5);
var expected = new OpenSeadragon.Point(1, 2);
ok(expected.equals(rect.getTopLeft()), "Incorrect top left point.");
});
test('getTopRight', function() {
var rect = new OpenSeadragon.Rect(0, 0, 1, 3);
var expected = new OpenSeadragon.Point(1, 0);
ok(expected.equals(rect.getTopRight()), "Incorrect top right point.");
rect.degrees = 45;
expected = new OpenSeadragon.Point(1 / Math.sqrt(2), 1 / Math.sqrt(2));
Util.assertPointsEquals(expected, rect.getTopRight(), precision,
"Incorrect top right point with rotation.");
});
test('getBottomLeft', function() {
var rect = new OpenSeadragon.Rect(0, 0, 3, 1);
var expected = new OpenSeadragon.Point(0, 1);
ok(expected.equals(rect.getBottomLeft()), "Incorrect bottom left point.");
rect.degrees = 45;
expected = new OpenSeadragon.Point(-1 / Math.sqrt(2), 1 / Math.sqrt(2));
Util.assertPointsEquals(expected, rect.getBottomLeft(), precision,
"Incorrect bottom left point with rotation.");
});
test('getBottomRight', function() {
var rect = new OpenSeadragon.Rect(0, 0, 1, 1);
var expected = new OpenSeadragon.Point(1, 1);
ok(expected.equals(rect.getBottomRight()), "Incorrect bottom right point.");
rect.degrees = 45;
expected = new OpenSeadragon.Point(0, Math.sqrt(2));
Util.assertPointsEquals(expected, rect.getBottomRight(), precision,
"Incorrect bottom right point with 45 rotation.");
rect.degrees = 90;
expected = new OpenSeadragon.Point(-1, 1);
Util.assertPointsEquals(expected, rect.getBottomRight(), precision,
"Incorrect bottom right point with 90 rotation.");
rect.degrees = 135;
expected = new OpenSeadragon.Point(-Math.sqrt(2), 0);
Util.assertPointsEquals(expected, rect.getBottomRight(), precision,
"Incorrect bottom right point with 135 rotation.");
});
test('getCenter', function() {
var rect = new OpenSeadragon.Rect(0, 0, 1, 1);
var expected = new OpenSeadragon.Point(0.5, 0.5);
ok(expected.equals(rect.getCenter()), "Incorrect center point.");
rect.degrees = 45;
expected = new OpenSeadragon.Point(0, 0.5 * Math.sqrt(2));
Util.assertPointsEquals(expected, rect.getCenter(), precision,
"Incorrect bottom right point with 45 rotation.");
rect.degrees = 90;
expected = new OpenSeadragon.Point(-0.5, 0.5);
Util.assertPointsEquals(expected, rect.getCenter(), precision,
"Incorrect bottom right point with 90 rotation.");
rect.degrees = 135;
expected = new OpenSeadragon.Point(-0.5 * Math.sqrt(2), 0);
Util.assertPointsEquals(expected, rect.getCenter(), precision,
"Incorrect bottom right point with 135 rotation.");
});
test('times', function() {
var rect = new OpenSeadragon.Rect(1, 2, 3, 4, 45);
var expected = new OpenSeadragon.Rect(2, 4, 6, 8, 45);
var actual = rect.times(2);
Util.assertRectangleEquals(expected, actual, precision,
"Incorrect x2 rectangles.");
});
test('translate', function() {
var rect = new OpenSeadragon.Rect(1, 2, 3, 4, 45);
var expected = new OpenSeadragon.Rect(2, 4, 3, 4, 45);
var actual = rect.translate(new OpenSeadragon.Point(1, 2));
Util.assertRectangleEquals(expected, actual, precision,
"Incorrect translation.");
});
test('union', function() {
var rect1 = new OpenSeadragon.Rect(2, 2, 2, 3);
var rect2 = new OpenSeadragon.Rect(0, 1, 1, 1);
var expected = new OpenSeadragon.Rect(0, 1, 4, 4);
var actual = rect1.union(rect2);
Util.assertRectangleEquals(expected, actual, precision,
"Incorrect union with horizontal rectangles.");
rect1 = new OpenSeadragon.Rect(0, -Math.sqrt(2), 2, 2, 45);
rect2 = new OpenSeadragon.Rect(1, 0, 2, 2, 0);
expected = new OpenSeadragon.Rect(
-Math.sqrt(2),
-Math.sqrt(2),
3 + Math.sqrt(2),
2 + Math.sqrt(2));
actual = rect1.union(rect2);
Util.assertRectangleEquals(expected, actual, precision,
"Incorrect union with non horizontal rectangles.");
});
test('intersection', function() {
var rect1 = new OpenSeadragon.Rect(2, 2, 2, 3);
var rect2 = new OpenSeadragon.Rect(0, 1, 1, 1);
var expected = null;
var actual = rect1.intersection(rect2);
equal(expected, actual,
"Rectangle " + rect2 + " should not intersect " + rect1);
actual = rect2.intersection(rect1);
equal(expected, actual,
"Rectangle " + rect1 + " should not intersect " + rect2);
rect1 = new OpenSeadragon.Rect(0, 0, 2, 1);
rect2 = new OpenSeadragon.Rect(1, 0, 2, 2);
expected = new OpenSeadragon.Rect(1, 0, 1, 1);
actual = rect1.intersection(rect2);
Util.assertRectangleEquals(expected, actual, precision,
"Intersection of " + rect2 + " with " + rect1 + " should be " +
expected);
actual = rect2.intersection(rect1);
Util.assertRectangleEquals(expected, actual, precision,
"Intersection of " + rect1 + " with " + rect2 + " should be " +
expected);
rect1 = new OpenSeadragon.Rect(0, 0, 3, 3);
rect2 = new OpenSeadragon.Rect(1, 1, 1, 1);
expected = new OpenSeadragon.Rect(1, 1, 1, 1);
actual = rect1.intersection(rect2);
Util.assertRectangleEquals(expected, actual, precision,
"Intersection of " + rect2 + " with " + rect1 + " should be " +
expected);
actual = rect2.intersection(rect1);
Util.assertRectangleEquals(expected, actual, precision,
"Intersection of " + rect1 + " with " + rect2 + " should be " +
expected);
rect1 = new OpenSeadragon.Rect(2, 2, 2, 3, 45);
rect2 = new OpenSeadragon.Rect(0, 1, 1, 1);
expected = null;
actual = rect1.intersection(rect2);
equal(expected, actual,
"Rectangle " + rect2 + " should not intersect " + rect1);
actual = rect2.intersection(rect1);
equal(expected, actual,
"Rectangle " + rect1 + " should not intersect " + rect2);
rect1 = new OpenSeadragon.Rect(2, 0, 2, 3, 45);
rect2 = new OpenSeadragon.Rect(0, 1, 1, 1);
expected = new OpenSeadragon.Rect(0, 1, 1, 1);
actual = rect1.intersection(rect2);
Util.assertRectangleEquals(expected, actual, precision,
"Intersection of " + rect2 + " with " + rect1 + " should be " +
expected);
actual = rect2.intersection(rect1);
Util.assertRectangleEquals(expected, actual, precision,
"Intersection of " + rect1 + " with " + rect2 + " should be " +
expected);
});
test('rotate', function() {
var rect = new OpenSeadragon.Rect(0, 0, 2, 1);
var expected = new OpenSeadragon.Rect(
1 - 1 / (2 * Math.sqrt(2)),
0.5 - 3 / (2 * Math.sqrt(2)),
2,
1,
45);
var actual = rect.rotate(-675);
Util.assertRectangleEquals(expected, actual, precision,
"Incorrect rectangle after rotation of -675deg around center.");
expected = new OpenSeadragon.Rect(0, 0, 2, 1, 33);
actual = rect.rotate(33, rect.getTopLeft());
Util.assertRectangleEquals(expected, actual, precision,
"Incorrect rectangle after rotation of 33deg around topLeft.");
expected = new OpenSeadragon.Rect(0, 0, 2, 1, 101);
actual = rect.rotate(101, rect.getTopLeft());
Util.assertRectangleEquals(expected, actual, precision,
"Incorrect rectangle after rotation of 187deg around topLeft.");
expected = new OpenSeadragon.Rect(0, 0, 2, 1, 187);
actual = rect.rotate(187, rect.getTopLeft());
Util.assertRectangleEquals(expected, actual, precision,
"Incorrect rectangle after rotation of 187deg around topLeft.");
expected = new OpenSeadragon.Rect(0, 0, 2, 1, 300);
actual = rect.rotate(300, rect.getTopLeft());
Util.assertRectangleEquals(expected, actual, precision,
"Incorrect rectangle after rotation of 300deg around topLeft.");
});
test('getBoundingBox', function() {
var rect = new OpenSeadragon.Rect(0, 0, 2, 3);
var bb = rect.getBoundingBox();
ok(rect.equals(bb), "Bounding box of horizontal rectangle should be " +
"identical to rectangle.");
rect.degrees = 90;
var expected = new OpenSeadragon.Rect(-3, 0, 3, 2);
Util.assertRectangleEquals(expected, rect.getBoundingBox(), precision,
"Bounding box of rect rotated 90deg.");
rect.degrees = 180;
var expected = new OpenSeadragon.Rect(-2, -3, 2, 3);
Util.assertRectangleEquals(expected, rect.getBoundingBox(), precision,
"Bounding box of rect rotated 180deg.");
rect.degrees = 270;
var expected = new OpenSeadragon.Rect(0, -2, 3, 2);
Util.assertRectangleEquals(expected, rect.getBoundingBox(), precision,
"Bounding box of rect rotated 270deg.");
});
test('containsPoint', function() {
var rect = new OpenSeadragon.Rect(0, 0, 1, 1, 45);
ok(rect.containsPoint(new OpenSeadragon.Point(0, 0)),
'Point 0,0 should be inside ' + rect);
ok(rect.containsPoint(rect.getTopRight()),
'Top right vertex should be inside ' + rect);
ok(rect.containsPoint(rect.getBottomRight()),
'Bottom right vertex should be inside ' + rect);
ok(rect.containsPoint(rect.getBottomLeft()),
'Bottom left vertex should be inside ' + rect);
ok(rect.containsPoint(rect.getCenter()),
'Center should be inside ' + rect);
notOk(rect.containsPoint(new OpenSeadragon.Point(1, 0)),
'Point 1,0 should not be inside ' + rect);
ok(rect.containsPoint(new OpenSeadragon.Point(0.5, 0.5)),
'Point 0.5,0.5 should be inside ' + rect);
ok(rect.containsPoint(new OpenSeadragon.Point(0.4, 0.5)),
'Point 0.4,0.5 should be inside ' + rect);
notOk(rect.containsPoint(new OpenSeadragon.Point(0.6, 0.5)),
'Point 0.6,0.5 should not be inside ' + rect);
});
})();

View File

@ -4,18 +4,18 @@
var viewer;
module('TiledImage', {
setup: function () {
setup: function() {
var example = $('<div id="example"></div>').appendTo("#qunit-fixture");
testLog.reset();
viewer = OpenSeadragon({
id: 'example',
prefixUrl: '/build/openseadragon/images/',
id: 'example',
prefixUrl: '/build/openseadragon/images/',
springStiffness: 100 // Faster animation = faster tests
});
},
teardown: function () {
teardown: function() {
if (viewer && viewer.close) {
viewer.close();
}
@ -87,7 +87,7 @@
// ----------
asyncTest('animation', function() {
viewer.addHandler("open", function () {
viewer.addHandler("open", function() {
var image = viewer.world.getItemAt(0);
propEqual(image.getBounds(), new OpenSeadragon.Rect(0, 0, 1, 1), 'target bounds on open');
propEqual(image.getBounds(true), new OpenSeadragon.Rect(0, 0, 1, 1), 'current bounds on open');
@ -206,10 +206,12 @@
propEqual(image.getClip(), clip, 'clip is set correctly');
Util.spyOnce(viewer.drawer, 'setClip', function(rect) {
ok(true, 'drawer.setClip is called');
var pixelRatio = viewer.viewport.getContainerSize().x / image.getContentSize().x;
var canvasClip = clip.times(pixelRatio * OpenSeadragon.pixelDensityRatio);
propEqual(rect, canvasClip, 'clipping to correct rect');
var homeBounds = viewer.viewport.getHomeBounds();
var canvasClip = viewer.drawer
.viewportToDrawerRectangle(homeBounds);
var precision = 0.00000001;
Util.assertRectangleEquals(rect, canvasClip, precision,
'clipping should be ' + canvasClip);
start();
});
});
@ -220,6 +222,40 @@
});
});
// ----------
asyncTest('getClipBounds', function() {
var clip = new OpenSeadragon.Rect(100, 200, 800, 500);
viewer.addHandler('open', function() {
var image = viewer.world.getItemAt(0);
var bounds = image.getClippedBounds();
var expectedBounds = new OpenSeadragon.Rect(1.2, 1.4, 1.6, 1);
propEqual(bounds, expectedBounds,
'getClipBounds should take clipping into account.');
image = viewer.world.getItemAt(1);
bounds = image.getClippedBounds();
expectedBounds = new OpenSeadragon.Rect(1, 2, 2, 2);
propEqual(bounds, expectedBounds,
'getClipBounds should work when no clipping set.');
start();
});
viewer.open([{
tileSource: '/test/data/testpattern.dzi',
clip: clip,
x: 1,
y: 1,
width: 2
}, {
tileSource: '/test/data/testpattern.dzi',
x: 1,
y: 2,
width: 2
}]);
});
// ----------
asyncTest('opacity', function() {
@ -257,4 +293,177 @@
});
});
// ----------
asyncTest('fitBounds', function() {
function assertRectEquals(actual, expected, message) {
ok(actual.equals(expected), message + ' should be ' +
expected.toString() + ', found ' + actual.toString());
}
viewer.addHandler('open', function openHandler() {
viewer.removeHandler('open', openHandler);
var squareImage = viewer.world.getItemAt(0);
squareImage.fitBounds(
new OpenSeadragon.Rect(0, 0, 1, 2),
OpenSeadragon.Placement.CENTER,
true);
var actualBounds = squareImage.getBounds(true);
var expectedBounds = new OpenSeadragon.Rect(0, 0.5, 1, 1);
assertRectEquals(actualBounds, expectedBounds, 'Square image bounds');
var tallImage = viewer.world.getItemAt(1);
tallImage.fitBounds(
new OpenSeadragon.Rect(0, 0, 1, 2),
OpenSeadragon.Placement.TOP_LEFT,
true);
actualBounds = tallImage.getBounds(true);
expectedBounds = new OpenSeadragon.Rect(0, 0, 0.5, 2);
assertRectEquals(actualBounds, expectedBounds, 'Tall image bounds');
var wideImage = viewer.world.getItemAt(2);
wideImage.fitBounds(
new OpenSeadragon.Rect(0, 0, 1, 2),
OpenSeadragon.Placement.BOTTOM_RIGHT,
true);
actualBounds = wideImage.getBounds(true);
expectedBounds = new OpenSeadragon.Rect(0, 1.75, 1, 0.25);
assertRectEquals(actualBounds, expectedBounds, 'Wide image bounds');
start();
});
viewer.open([
'/test/data/testpattern.dzi',
'/test/data/tall.dzi',
'/test/data/wide.dzi'
]);
});
// ----------
asyncTest('fitBounds in constructor', function() {
function assertRectEquals(actual, expected, message) {
ok(actual.equals(expected), message + ' should be ' +
expected.toString() + ', found ' + actual.toString());
}
viewer.addHandler('open', function openHandler() {
viewer.removeHandler('open', openHandler);
var squareImage = viewer.world.getItemAt(0);
var actualBounds = squareImage.getBounds(true);
var expectedBounds = new OpenSeadragon.Rect(0, 0.5, 1, 1);
assertRectEquals(actualBounds, expectedBounds, 'Square image bounds');
var tallImage = viewer.world.getItemAt(1);
actualBounds = tallImage.getBounds(true);
expectedBounds = new OpenSeadragon.Rect(0, 0, 0.5, 2);
assertRectEquals(actualBounds, expectedBounds, 'Tall image bounds');
var wideImage = viewer.world.getItemAt(2);
actualBounds = wideImage.getBounds(true);
expectedBounds = new OpenSeadragon.Rect(0, 1.75, 1, 0.25);
assertRectEquals(actualBounds, expectedBounds, 'Wide image bounds');
start();
});
viewer.open([{
tileSource: '/test/data/testpattern.dzi',
x: 1, // should be ignored
y: 1, // should be ignored
width: 2, // should be ignored
fitBounds: new OpenSeadragon.Rect(0, 0, 1, 2)
// No placement specified, should default to CENTER
}, {
tileSource: '/test/data/tall.dzi',
fitBounds: new OpenSeadragon.Rect(0, 0, 1, 2),
fitBoundsPlacement: OpenSeadragon.Placement.TOP_LEFT
}, {
tileSource: '/test/data/wide.dzi',
fitBounds: new OpenSeadragon.Rect(0, 0, 1, 2),
fitBoundsPlacement: OpenSeadragon.Placement.BOTTOM_RIGHT
}]);
});
// ----------
asyncTest('fitBounds with clipping', function() {
function assertRectEquals(actual, expected, message) {
ok(actual.equals(expected), message + ' should be ' +
expected.toString() + ', found ' + actual.toString());
}
viewer.addHandler('open', function openHandler() {
viewer.removeHandler('open', openHandler);
var squareImage = viewer.world.getItemAt(0);
var actualBounds = squareImage.getBounds(true);
var expectedBounds = new OpenSeadragon.Rect(-1, -1, 2, 2);
assertRectEquals(actualBounds, expectedBounds, 'Square image bounds');
var tallImage = viewer.world.getItemAt(1);
actualBounds = tallImage.getBounds(true);
expectedBounds = new OpenSeadragon.Rect(1, 1, 2, 8);
assertRectEquals(actualBounds, expectedBounds, 'Tall image bounds');
var wideImage = viewer.world.getItemAt(2);
actualBounds = wideImage.getBounds(true);
expectedBounds = new OpenSeadragon.Rect(1, 1, 16, 4);
assertRectEquals(actualBounds, expectedBounds, 'Wide image bounds');
start();
});
viewer.open([{
tileSource: '/test/data/testpattern.dzi',
clip: new OpenSeadragon.Rect(500, 500, 500, 500),
fitBounds: new OpenSeadragon.Rect(0, 0, 1, 1)
}, {
tileSource: '/test/data/tall.dzi',
clip: new OpenSeadragon.Rect(0, 0, 250, 100),
fitBounds: new OpenSeadragon.Rect(1, 1, 1, 2),
fitBoundsPlacement: OpenSeadragon.Placement.TOP
}, {
tileSource: '/test/data/wide.dzi',
clip: new OpenSeadragon.Rect(0, 0, 100, 250),
fitBounds: new OpenSeadragon.Rect(1, 1, 1, 2),
fitBoundsPlacement: OpenSeadragon.Placement.TOP_LEFT
}]);
});
// ----------
asyncTest('fullyLoaded', function() {
viewer.addHandler('open', function openHandler() {
viewer.removeHandler('open', openHandler);
var image = viewer.world.getItemAt(0);
equal(image.getFullyLoaded(), false, 'not fully loaded at first');
var count = 0;
var fullyLoadedChangeHandler = function(event) {
if (count === 0) {
equal(event.fullyLoaded, true, 'event includes true fullyLoaded property');
equal(image.getFullyLoaded(), true, 'image is fully loaded after event');
viewer.viewport.zoomBy(5, null, true);
} else if (count === 1) {
equal(event.fullyLoaded, false, 'event includes false fullyLoaded property');
equal(image.getFullyLoaded(), false, 'image is not fully loaded after zoom');
} else {
image.removeHandler('fully-loaded-change', fullyLoadedChangeHandler);
equal(image.getFullyLoaded(), true, 'image is once again fully loaded');
start();
}
count++;
};
image.addHandler('fully-loaded-change', fullyLoadedChangeHandler);
});
viewer.open([{
tileSource: '/test/data/tall.dzi',
}]);
});
})();

View File

@ -2,6 +2,7 @@
(function () {
var viewer;
var precision = 0.00000001;
module('Units', {
setup: function () {
@ -26,8 +27,8 @@
function pointEqual(a, b, message) {
Util.assessNumericValue(a.x, b.x, 0.00000001, message);
Util.assessNumericValue(a.y, b.y, 0.00000001, message);
Util.assessNumericValue(a.x, b.x, precision, message);
Util.assessNumericValue(a.y, b.y, precision, message);
}
// Check that f^-1 ( f(x) ) = x
@ -151,8 +152,6 @@
start();
});
viewer.viewport.zoomTo(0.8).panTo(new OpenSeadragon.Point(0.1, 0.2));
start();
});
viewer.open([{
@ -167,6 +166,68 @@
});
// ---------
asyncTest('Multiple images coordinates conversion with viewport rotation', function () {
viewer.addHandler("open", function () {
var viewport = viewer.viewport;
var tiledImage1 = viewer.world.getItemAt(0);
var tiledImage2 = viewer.world.getItemAt(1);
var imageWidth = viewer.source.dimensions.x;
var imageHeight = viewer.source.dimensions.y;
var viewerWidth = $(viewer.element).width();
var viewerHeight = $(viewer.element).height();
var viewerMiddleTop = new OpenSeadragon.Point(viewerWidth / 2, 0);
var viewerMiddleBottom = new OpenSeadragon.Point(viewerWidth / 2, viewerHeight);
var point0_0 = new OpenSeadragon.Point(0, 0);
var point = viewport.viewerElementToViewportCoordinates(viewerMiddleTop);
pointEqual(point, point0_0, 'When opening, viewer middle top is also viewport 0,0');
var image1Pixel = tiledImage1.viewerElementToImageCoordinates(viewerMiddleTop);
pointEqual(image1Pixel, point0_0, 'When opening, viewer middle top is also image 1 pixel 0,0');
var image2Pixel = tiledImage2.viewerElementToImageCoordinates(viewerMiddleTop);
pointEqual(image2Pixel,
new OpenSeadragon.Point(-2 * imageWidth, -2 * imageHeight),
'When opening, viewer middle top is also image 2 pixel -2*imageWidth, -2*imageHeight');
point = viewport.viewerElementToViewportCoordinates(viewerMiddleBottom);
pointEqual(point, new OpenSeadragon.Point(1.5, 1.5),
'Viewer middle bottom has viewport coordinates 1.5,1.5.');
image1Pixel = tiledImage1.viewerElementToImageCoordinates(viewerMiddleBottom);
pointEqual(image1Pixel,
new OpenSeadragon.Point(imageWidth * 1.5, imageHeight * 1.5),
'Viewer middle bottom has image 1 pixel coordinates imageWidth * 1.5, imageHeight * 1.5');
image2Pixel = tiledImage2.viewerElementToImageCoordinates(viewerMiddleBottom);
pointEqual(image2Pixel,
new OpenSeadragon.Point(imageWidth, imageHeight),
'Viewer middle bottom has image 2 pixel coordinates imageWidth,imageHeight.');
checkPoint(' after opening');
viewer.addHandler('animation-finish', function animationHandler() {
viewer.removeHandler('animation-finish', animationHandler);
checkPoint(' after zoom and pan');
//Restore rotation
viewer.viewport.setRotation(0);
start();
});
viewer.viewport.zoomTo(0.8).panTo(new OpenSeadragon.Point(0.1, 0.2));
});
viewer.viewport.setRotation(45);
viewer.open([{
tileSource: "/test/data/testpattern.dzi"
}, {
tileSource: "/test/data/testpattern.dzi",
x: 1,
y: 1,
width: 0.5
}
]);
});
// ----------
asyncTest('ZoomRatio 1 image', function () {
viewer.addHandler("open", function () {
@ -188,10 +249,10 @@
var expectedViewportZoom = viewport.getZoom(true);
var actualImageZoom = viewport.viewportToImageZoom(
expectedViewportZoom);
equal(actualImageZoom, expectedImageZoom);
Util.assessNumericValue(actualImageZoom, expectedImageZoom, precision);
var actualViewportZoom = viewport.imageToViewportZoom(actualImageZoom);
equal(actualViewportZoom, expectedViewportZoom);
Util.assessNumericValue(actualViewportZoom, expectedViewportZoom, precision);
}
checkZoom();
@ -234,11 +295,11 @@
var actualImageZoom = image.viewportToImageZoom(
expectedViewportZoom);
Util.assessNumericValue(actualImageZoom, expectedImageZoom,
0.00000001);
precision);
var actualViewportImage1Zoom = image.imageToViewportZoom(actualImageZoom);
Util.assessNumericValue(
actualViewportImage1Zoom, expectedViewportZoom, 0.00000001);
actualViewportImage1Zoom, expectedViewportZoom, precision);
}
checkZoom(image1);

View File

@ -5,6 +5,7 @@
var VIEWER_ID = "example";
var PREFIX_URL = "/build/openseadragon/images/";
var SPRING_STIFFNESS = 100; // Faster animation = faster tests
var EPSILON = 0.0000000001;
module("viewport", {
setup: function () {
@ -98,7 +99,6 @@
viewer.removeHandler('open', openHandler);
var viewport = viewer.viewport;
viewport.zoomTo(ZOOM_FACTOR, null, true);
viewport.update(); // need to call this even with immediately=true
var orig, expected, actual;
for (var i = 0; i < config.testArray.length; i++){
@ -125,7 +125,6 @@
viewer.removeHandler('open', openHandler);
var viewport = viewer.viewport;
viewport.zoomTo(ZOOM_FACTOR, null, true);
viewport.update(); // need to call this even with immediately=true
propEqual(viewport.getContainerSize(), new OpenSeadragon.Point(500, 500), "Test container size");
start();
@ -139,7 +138,6 @@
viewer.removeHandler('open', openHandler);
var viewport = viewer.viewport;
viewport.zoomTo(ZOOM_FACTOR, null, true);
viewport.update(); // need to call this even with immediately=true
equal(viewport.getAspectRatio(), 1, "Test aspect ratio");
start();
@ -221,6 +219,98 @@
});
});
asyncTest('getHomeBoundsNoRotate with rotation', function() {
function openHandler() {
viewer.removeHandler('open', openHandler);
var viewport = viewer.viewport;
viewport.setRotation(-675);
Util.assertRectangleEquals(
viewport.getHomeBoundsNoRotate(),
new OpenSeadragon.Rect(
(1 - Math.sqrt(2)) / 2,
(1 - Math.sqrt(2)) / 2,
Math.sqrt(2),
Math.sqrt(2)),
0.00000001,
"Test getHomeBoundsNoRotate with degrees = -675");
start();
}
viewer.addHandler('open', openHandler);
viewer.open(DZI_PATH);
});
asyncTest('getHomeBounds with rotation', function() {
function openHandler() {
viewer.removeHandler('open', openHandler);
var viewport = viewer.viewport;
viewport.setRotation(-675);
Util.assertRectangleEquals(
viewport.getHomeBounds(),
new OpenSeadragon.Rect(
0.5,
-0.5,
Math.sqrt(2),
Math.sqrt(2),
45),
0.00000001,
"Test getHomeBounds with degrees = -675");
start();
}
viewer.addHandler('open', openHandler);
viewer.open(DZI_PATH);
});
asyncTest('getHomeBoundsWithMultiImages', function() {
function openHandler() {
viewer.removeHandler('open', openHandler);
var viewport = viewer.viewport;
Util.assertRectangleEquals(
new OpenSeadragon.Rect(0, 0, 4, 4),
viewport.getHomeBounds(),
0.00000001,
"Test getHomeBoundsWithMultiImages");
start();
}
viewer.addHandler('open', openHandler);
viewer.open([{
tileSource: DZI_PATH,
x: 0,
y: 0,
width: 2
}, {
tileSource: DZI_PATH,
x: 3,
y: 3,
width: 1
}]);
});
asyncTest('getHomeBoundsWithMultiImagesAndClipping', function() {
function openHandler() {
viewer.removeHandler('open', openHandler);
var viewport = viewer.viewport;
Util.assertRectangleEquals(
new OpenSeadragon.Rect(1, 1, 4, 4),
viewport.getHomeBounds(),
0.00000001,
"Test getHomeBoundsWithMultiImagesAndClipping");
start();
}
viewer.addHandler('open', openHandler);
viewer.open([{
tileSource: DZI_PATH,
x: 0,
y: 0,
width: 2,
clip: new OpenSeadragon.Rect(500, 500, 500, 500)
}, {
tileSource: DZI_PATH,
x: 4,
y: 4,
width: 1
}]);
});
asyncTest('getHomeZoom', function() {
reopenViewerHelper({
property: 'defaultZoomLevel',
@ -243,7 +333,6 @@
viewer.removeHandler('open', openHandler);
var viewport = viewer.viewport;
viewport.zoomTo(ZOOM_FACTOR, null, true);
viewport.update(); // need to call this even with immediately=true
// Special cases for oddball levels
if (level === -1) {
@ -288,9 +377,8 @@
for(var i = 0; i < testRects.length; i++){
var rect = testRects[i].times(viewport.getContainerSize());
viewport.resetContentSize(rect.getSize());
viewport.update();
propEqual(
viewport.contentSize,
viewport._contentSize,
rect.getSize(),
"Reset content size correctly."
);
@ -308,10 +396,8 @@
// zoom/pan somewhere
viewport.zoomTo(ZOOM_FACTOR, true);
viewport.update();
viewport.goHome(true);
viewport.update();
propEqual(
viewport.getBounds(),
viewport.getHomeBounds(),
@ -323,7 +409,7 @@
viewer.open(DZI_PATH);
});
asyncTest('ensureVisible', function(){
asyncTest('ensureVisible', function() {
var openHandler = function(event) {
viewer.removeHandler('open', openHandler);
var viewport = viewer.viewport;
@ -331,10 +417,8 @@
// zoom/pan so that the image is out of view
viewport.zoomTo(ZOOM_FACTOR * -50, true);
viewport.panBy(new OpenSeadragon.Point(5000, 5000), null, true);
viewport.update();
viewport.ensureVisible(true);
viewport.update();
var bounds = viewport.getBounds();
ok(bounds.getSize().x > 1 && bounds.getSize().y > 1, "Moved viewport so that image is visible.");
start();
@ -343,52 +427,132 @@
viewer.open(DZI_PATH);
});
asyncTest('fitBounds', function(){
var openHandler = function(event) {
asyncTest('applyConstraints', function() {
var openHandler = function() {
viewer.removeHandler('open', openHandler);
var viewport = viewer.viewport;
for(var i = 0; i < testRects.length; i++){
var rect = testRects[i].times(viewport.getContainerSize());
viewport.fitBounds(rect, true);
viewport.update();
propEqual(
viewport.getBounds(),
rect,
"Fit bounds correctly."
);
}
viewport.fitBounds(new OpenSeadragon.Rect(1, 1, 1, 1), true);
viewport.visibilityRatio = 0.3;
viewport.applyConstraints(true);
var bounds = viewport.getBounds();
Util.assertRectangleEquals(
new OpenSeadragon.Rect(0.7, 0.7, 1, 1),
bounds,
EPSILON,
"Viewport.applyConstraints should move viewport.");
start();
};
viewer.addHandler('open', openHandler);
viewer.open(DZI_PATH);
});
asyncTest('applyConstraints with visibilityRatio = 1 shouldn\'t bounce around', function() {
var openHandler = function() {
viewer.removeHandler('open', openHandler);
var viewport = viewer.viewport;
viewport.visibilityRatio = 1;
viewport.zoomTo(0.5, undefined, true);
viewport.panBy(new OpenSeadragon.Point(0.75, 0), true);
viewport.applyConstraints(true);
var bounds = viewport.getBounds();
Util.assertRectangleEquals(
new OpenSeadragon.Rect(-0.5, 1, 2, 2),
bounds,
EPSILON,
"Viewport.applyConstraints should move viewport to the center, not to a side.");
start();
};
viewer.addHandler('open', openHandler);
viewer.open(TALL_PATH);
});
asyncTest('applyConstraints with rotation', function() {
var openHandler = function() {
viewer.removeHandler('open', openHandler);
var viewport = viewer.viewport;
viewport.setRotation(45);
viewport.fitBounds(new OpenSeadragon.Rect(1, 1, 1, 1), true);
viewport.applyConstraints(true);
var bounds = viewport.getBounds();
Util.assertRectangleEquals(
bounds,
new OpenSeadragon.Rect(1, 0, Math.sqrt(2), Math.sqrt(2), 45),
EPSILON,
"Viewport.applyConstraints with rotation should move viewport.");
start();
};
viewer.addHandler('open', openHandler);
viewer.open(DZI_PATH);
});
// Fit bounds tests
var testRectsFitBounds = [
new OpenSeadragon.Rect(0, -0.75, 0.5, 1),
new OpenSeadragon.Rect(0.5, 0, 0.5, 0.8),
new OpenSeadragon.Rect(0.75, 0.75, 0.5, 0.5),
new OpenSeadragon.Rect(-0.3, -0.3, 0.5, 0.5)
new OpenSeadragon.Rect(-0.3, -0.3, 0.5, 0.5),
new OpenSeadragon.Rect(0.5, 0.25, Math.sqrt(0.125), Math.sqrt(0.125), 45)
];
var expectedRectsFitBounds = [
new OpenSeadragon.Rect(-0.25, -0.75, 1, 1),
new OpenSeadragon.Rect(0.35, 0, 0.8, 0.8),
new OpenSeadragon.Rect(0.75, 0.75, 0.5, 0.5),
new OpenSeadragon.Rect(-0.3, -0.3, 0.5, 0.5),
new OpenSeadragon.Rect(0.25, 0.25, 0.5, 0.5)
];
var expectedRectsFitBoundsWithRotation = [
new OpenSeadragon.Rect(
0.25,
-1,
Math.sqrt(0.125) + Math.sqrt(0.5),
Math.sqrt(0.125) + Math.sqrt(0.5),
45),
new OpenSeadragon.Rect(
0.75,
-0.25,
Math.sqrt(0.125) + Math.sqrt(8 / 25),
Math.sqrt(0.125) + Math.sqrt(8 / 25),
45),
new OpenSeadragon.Rect(
1,
0.5,
Math.sqrt(0.125) * 2,
Math.sqrt(0.125) * 2,
45),
new OpenSeadragon.Rect(
-0.05,
-0.55,
Math.sqrt(0.125) * 2,
Math.sqrt(0.125) * 2,
45),
new OpenSeadragon.Rect(
0.5,
0.25,
Math.sqrt(0.125),
Math.sqrt(0.125),
45)
];
var expectedRectsFitBoundsWithConstraints = [
new OpenSeadragon.Rect(-0.25, -0.5, 1, 1),
new OpenSeadragon.Rect(0.35, 0, 0.8, 0.8),
new OpenSeadragon.Rect(0.75, 0.75, 0.5, 0.5),
new OpenSeadragon.Rect(-0.25, -0.25, 0.5, 0.5)
new OpenSeadragon.Rect(-0.25, -0.25, 0.5, 0.5),
new OpenSeadragon.Rect(0.25, 0.25, 0.5, 0.5)
];
asyncTest('fitBoundsWithConstraints', function(){
asyncTest('fitBounds', function(){
var openHandler = function(event) {
viewer.removeHandler('open', openHandler);
var viewport = viewer.viewport;
viewport.zoomTo(ZOOM_FACTOR, null, true);
viewport.update();
for(var i = 0; i < testRectsFitBounds.length; i++){
var rect = testRectsFitBounds[i];
viewport.fitBoundsWithConstraints(rect, true);
viewport.update();
viewport.fitBounds(rect, true);
propEqual(
viewport.getBounds(),
expectedRectsFitBounds[i],
@ -401,12 +565,92 @@
viewer.open(DZI_PATH);
});
asyncTest('fitBounds with viewport rotation', function(){
var openHandler = function(event) {
viewer.removeHandler('open', openHandler);
var viewport = viewer.viewport;
viewport.setRotation(45);
for(var i = 0; i < testRectsFitBounds.length; i++){
var rect = testRectsFitBounds[i];
viewport.fitBounds(rect, true);
Util.assertRectangleEquals(
viewport.getBounds(),
expectedRectsFitBoundsWithRotation[i],
EPSILON,
"Fit bounds correctly."
);
}
start();
};
viewer.addHandler('open', openHandler);
viewer.open(DZI_PATH);
});
asyncTest('fitBoundsWithConstraints', function(){
var openHandler = function(event) {
viewer.removeHandler('open', openHandler);
var viewport = viewer.viewport;
viewport.zoomTo(ZOOM_FACTOR, null, true);
for(var i = 0; i < testRectsFitBounds.length; i++){
var rect = testRectsFitBounds[i];
viewport.fitBoundsWithConstraints(rect, true);
propEqual(
viewport.getBounds(),
expectedRectsFitBoundsWithConstraints[i],
"Fit bounds correctly."
);
}
start();
};
viewer.addHandler('open', openHandler);
viewer.open(DZI_PATH);
});
asyncTest('fitBounds with almost same zoom', function() {
var openHandler = function() {
var viewport = viewer.viewport;
var rect1 = new OpenSeadragon.Rect(0, 0, 1, 1);
viewport.fitBounds(rect1, true);
Util.assertRectangleEquals(rect1, viewport.getBounds(), 1e-6,
'Bounds should be ' + rect1);
// Zoom and pan
var rect2 = new OpenSeadragon.Rect(1, 1, 1 + 1e-8, 1 + 1e-8);
viewport.fitBounds(rect2);
Util.assertRectangleEquals(rect2, viewport.getBounds(), 1e-6,
'Bounds should be ' + rect2);
start();
};
viewer.addOnceHandler('open', openHandler);
viewer.open(DZI_PATH);
});
asyncTest('fitBounds with big rectangle', function() {
var openHandler = function() {
var viewport = viewer.viewport;
var rect1 = new OpenSeadragon.Rect(0, 0, 1e9, 1e9);
viewport.fitBounds(rect1, true);
Util.assertRectangleEquals(rect1, viewport.getBounds(), 1e-6,
'Bounds should be ' + rect1);
// Zoom and pan
var rect2 = new OpenSeadragon.Rect(1, 1, 2e9, 2e9);
viewport.fitBounds(rect2);
Util.assertRectangleEquals(rect2, viewport.getBounds(), 1e-6,
'Bounds should be ' + rect2);
start();
};
viewer.addOnceHandler('open', openHandler);
viewer.open(DZI_PATH);
});
asyncTest('fitHorizontally', function(){
var openHandler = function(event) {
viewer.removeHandler('open', openHandler);
var viewport = viewer.viewport;
viewport.fitHorizontally(true);
viewport.update();
propEqual(
viewport.getBounds(),
new OpenSeadragon.Rect(0, 1.5, 1, 1),
@ -423,7 +667,6 @@
viewer.removeHandler('open', openHandler);
var viewport = viewer.viewport;
viewport.fitVertically(true);
viewport.update();
propEqual(
viewport.getBounds(),
new OpenSeadragon.Rect(0.375, 0, 0.25, 0.25),
@ -434,6 +677,7 @@
viewer.addHandler('open', openHandler);
viewer.open(WIDE_PATH);
});
// End fitBounds tests.
asyncTest('panBy', function(){
var openHandler = function(event) {
@ -443,7 +687,6 @@
for (var i = 0; i < testPoints.length; i++){
var expected = viewport.getCenter().plus(testPoints[i]);
viewport.panBy(testPoints[i], true);
viewport.update(); // need to call this even with immediately=true
propEqual(
viewport.getCenter(),
expected,
@ -464,7 +707,6 @@
for (var i = 0; i < testPoints.length; i++){
viewport.panTo(testPoints[i], true);
viewport.update(); // need to call this even with immediately=true
propEqual(
viewport.getCenter(),
testPoints[i],
@ -485,7 +727,6 @@
for (var i = 0; i < testZoomLevels.length; i++){
viewport.zoomBy(testZoomLevels[i], null, true);
viewport.update(); // need to call this even with immediately=true
propEqual(
viewport.getZoom(),
testZoomLevels[i],
@ -495,7 +736,6 @@
// now use a ref point
// TODO: check the ending position due to ref point
viewport.zoomBy(testZoomLevels[i], testPoints[i], true);
viewport.update();
propEqual(
viewport.getZoom(),
testZoomLevels[i],
@ -516,7 +756,6 @@
for (var i = 0; i < testZoomLevels.length; i++){
viewport.zoomTo(testZoomLevels[i], null, true);
viewport.update(); // need to call this even with immediately=true
propEqual(
viewport.getZoom(),
testZoomLevels[i],
@ -526,7 +765,6 @@
// now use a ref point
// TODO: check the ending position due to ref point
viewport.zoomTo(testZoomLevels[i], testPoints[i], true);
viewport.update(); // need to call this even with immediately=true
propEqual(
viewport.getZoom(),
testZoomLevels[i],
@ -565,7 +803,6 @@
for(var i = 0; i < testPoints.length; i++){
var new_size = testPoints[i].times(viewer.source.dimensions.x);
viewport.resize(new_size);
viewport.update();
propEqual(viewport.getContainerSize(), new_size, "Viewport resized successfully.");
}
start();

View File

@ -1,4 +1,4 @@
<!DOCTYPE html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
@ -17,6 +17,7 @@
<script src="/build/openseadragon/openseadragon.js"></script>
<script src="/test/helpers/legacy.mouse.shim.js"></script>
<script src="/test/helpers/test.js"></script>
<script src="/test/helpers/touch.js"></script>
<!-- Polyfill must be inserted first because it is testing functions
reassignments which could be done by other test. -->
@ -37,8 +38,10 @@
<script src="/test/modules/tilecache.js"></script>
<script src="/test/modules/referencestrip.js"></script>
<script src="/test/modules/tilesource.js"></script>
<script src="/test/modules/dzitilesource.js"></script>
<script src="/test/modules/tilesourcecollection.js"></script>
<script src="/test/modules/spring.js"></script>
<script src="/test/modules/rectangle.js"></script>
<!-- The navigator tests are the slowest (for now; hopefully they can be sped up)
so we put them last. -->
<script src="/test/modules/navigator.js"></script>