Merge branch 'master' into loaded

This commit is contained in:
Ian Gilman 2016-08-10 09:49:59 -07:00
commit 03b2a86a9d
36 changed files with 3593 additions and 1256 deletions

View File

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

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",

View File

@ -1,5 +1,5 @@
# 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) [![Build Status](https://secure.travis-ci.org/openseadragon/openseadragon.png?branch=master)](http://travis-ci.org/openseadragon/openseadragon)
[![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.

View File

@ -1,8 +1,30 @@
OPENSEADRAGON CHANGELOG
=======================
2.1.1: (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)
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)
@ -15,6 +37,29 @@ OPENSEADRAGON CHANGELOG
* 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:

View File

@ -1,6 +1,6 @@
{
"name": "openseadragon",
"version": "2.1.0",
"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/",

View File

@ -259,13 +259,17 @@ $.Drawer.prototype = {
}
},
_clear: function ( useSketch ) {
_clear: function (useSketch, bounds) {
if (!this.useCanvas) {
return;
}
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);
}
},
/**
@ -382,21 +386,59 @@ $.Drawer.prototype = {
/**
* Blends the sketch canvas in the main canvas.
* @param {Float} opacity The opacity of the blending.
* @param {Float} [scale=1] The scale at which tiles were drawn on the sketch. Default is 1.
* @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} [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.
* @returns {undefined}
* @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, scale, translate, compositeOperation) {
var options = opacity;
if (!$.isPlainObject(options)) {
options = {
opacity: opacity,
scale: scale,
translate: translate,
compositeOperation: compositeOperation
};
}
if (!this.useCanvas || !this.sketchCanvas) {
return;
}
scale = scale || 1;
opacity = options.opacity;
compositeOperation = options.compositeOperation;
var bounds = options.bounds;
this.context.save();
this.context.globalAlpha = opacity;
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);
translate : new $.Point(0, 0);
var widthExt = 0;
var heightExt = 0;
@ -406,12 +448,6 @@ $.Drawer.prototype = {
widthExt = Math.round(widthDiff / 2);
heightExt = Math.round(heightDiff / 2);
}
this.context.save();
this.context.globalAlpha = opacity;
if (compositeOperation) {
this.context.globalCompositeOperation = compositeOperation;
}
this.context.drawImage(
this.sketchCanvas,
position.x - widthExt * scale,
@ -423,6 +459,7 @@ $.Drawer.prototype = {
this.canvas.width + 2 * widthExt,
this.canvas.height + 2 * heightExt
);
}
this.context.restore();
},

View File

@ -301,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

@ -56,7 +56,31 @@ $.EventSource = function() {
/** @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

@ -166,14 +166,6 @@
return new $.Point(0, 0);
}
},
/**
* @function
* @param {Number} level
* @param {OpenSeadragon.Point} point
*/
getTileAtPoint: function (level, point) {
return new $.Point(0, 0);
},
/**
* Retrieves a tile url
* @function

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

View File

@ -1357,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 ) {
@ -1400,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 ) {
@ -2074,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 );
@ -2128,7 +2128,7 @@
}
if ( updatePointersUp( tracker, event, gPoints, 0 ) ) {
releasePointer( tracker, 'touch' );
releasePointer( tracker, 'touch', touchCount );
}
// simulate touchleave on our tracked element

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);
});
}
@ -300,7 +307,7 @@ $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /*
}
if (viewport && this.viewport) {
bounds = viewport.getBounds( true );
bounds = viewport.getBoundsNoRotate(true);
topleft = this.viewport.pixelFromPointNoRotate(bounds.getTopLeft(), false);
bottomright = this.viewport.pixelFromPointNoRotate(bounds.getBottomRight(), false)
.minus( this.totalBorderWidths );

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.
@ -258,6 +239,12 @@
* 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.
@ -373,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.
@ -405,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.
*
@ -691,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( $ ){
@ -842,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
@ -983,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 */{
/**
@ -1033,6 +1040,7 @@ if (typeof define === 'function' && define.amd) {
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
@ -1065,6 +1073,7 @@ if (typeof define === 'function' && define.amd) {
navigatorHeight: null,
navigatorWidth: null,
navigatorAutoResize: true,
navigatorAutoFade: true,
navigatorRotate: true,
// INITIAL ROTATION
@ -1322,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).
@ -2058,12 +2110,13 @@ if (typeof define === 'function' && define.amd) {
}
};
try {
request.open( "GET", url, true );
if (withCredentials) {
request.withCredentials = true;
}
try {
request.open( "GET", url, true );
request.send( null );
} catch (e) {
var msg = e.message;
@ -2533,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;
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 );
}
/**
* @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));
// 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();
}
}(this, function () {
return OpenSeadragon;
}));

View File

@ -37,9 +37,12 @@
/**
* 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,17 +84,25 @@
* @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) {
@ -108,72 +128,66 @@
}
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._init(options);
};
/** @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:
var properties = $.Placement.properties[this.placement];
if (!properties) {
return;
}
if (properties.isHorizontallyCentered) {
position.x -= size.x / 2;
break;
case $.OverlayPlacement.TOP_RIGHT:
position.x -= size.x;
break;
case $.OverlayPlacement.RIGHT:
} else if (properties.isRight) {
position.x -= size.x;
}
if (properties.isVerticallyCentered) {
position.y -= size.y / 2;
break;
case $.OverlayPlacement.BOTTOM_RIGHT:
position.x -= size.x;
} else if (properties.isBottom) {
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;
}
},
@ -181,8 +195,8 @@
* @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);
@ -205,10 +219,20 @@
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] = "";
}
},
/**
@ -216,104 +240,237 @@
* @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 ) {
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 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 );
this.onDraw(position, size, this.element);
} else {
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') {
if (style.display !== 'none') {
style.display = 'block';
}
if ( scales ) {
style.width = size.x + "px";
style.height = size.y + "px";
}
}
},
/**
* @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));

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

@ -110,6 +110,33 @@ $.Rect = function(x, y, width, height, degrees) {
}
};
/**
* 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 = {
/**
@ -280,11 +307,137 @@ $.Rect.prototype = {
bottom - top);
},
/**
* 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}
*/
@ -340,6 +493,51 @@ $.Rect.prototype = {
maxY - minY);
},
/**
* 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);
},
/**
* Provides a string representation of the rectangle which is useful for
* debugging.

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 } );
}
},

View File

@ -234,6 +234,15 @@ $.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

@ -193,6 +193,11 @@ $.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
@ -280,27 +285,6 @@ $.Tile.prototype = {
context.globalAlpha = this.opacity;
//if we are supposed to be rendering fully opaque rectangle,
//ie its done fading or fading is turned off, and if we are drawing
//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.context2D || this.url.match('.png'))) {
//clearing only the inside of the rectangle occupied
//by the png prevents edge flikering
context.clearRect(
position.x + 1,
position.y + 1,
size.x - 2,
size.y - 2
);
}
// This gives the application a chance to make image manipulation
// changes as we are rendering the image
drawingHandler({context: context, tile: this, rendered: rendered});
if (typeof scale === 'number' && scale !== 1) {
// draw tile at a different scale
position = position.times(scale);
@ -312,6 +296,25 @@ $.Tile.prototype = {
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._hasTransparencyChannel()) {
//clearing only the inside of the rectangle occupied
//by the png prevents edge flikering
context.clearRect(
position.x + 1,
position.y + 1,
size.x - 2,
size.y - 2
);
}
// This gives the application a chance to make image manipulation
// changes as we are rendering the image
drawingHandler({context: context, tile: this, rendered: rendered});
context.drawImage(
rendered.canvas,
0,
@ -333,15 +336,18 @@ $.Tile.prototype = {
* @return {Float}
*/
getScaleForEdgeSmoothing: function() {
if (!this.cacheImageRecord) {
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;
}
var rendered = this.cacheImageRecord.getRenderedContext();
return rendered.canvas.width / this.size.times($.pixelDensityRatio).x;
return context.canvas.width / (this.size.x * $.pixelDensityRatio);
},
/**

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.
@ -65,6 +69,7 @@
* @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}.
@ -122,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
@ -144,6 +154,7 @@ $.TiledImage = function( options ) {
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,
@ -172,6 +183,10 @@ $.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) {
/**
@ -240,9 +255,11 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
* Draws the TiledImage to its Drawer.
*/
draw: function() {
if (this.opacity !== 0) {
this._midDraw = true;
updateViewport(this);
this._midDraw = false;
}
},
/**
@ -272,6 +289,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.
*/
@ -541,6 +578,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.
@ -648,6 +746,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;
}
});
@ -686,7 +789,6 @@ function updateViewport( tiledImage ) {
Math.log( 2 )
))
),
degrees = tiledImage.viewport.degrees,
renderPixelRatioC,
renderPixelRatioT,
zeroRatioT,
@ -694,26 +796,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) {
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();
@ -879,10 +978,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 );
}
@ -1333,24 +1436,25 @@ function compareTiles( previousBest, tile ) {
}
function drawTiles( tiledImage, lastDrawn ) {
var i,
tile = lastDrawn[0];
if ( tiledImage.opacity <= 0 ) {
drawDebugInfo( tiledImage, lastDrawn );
if (lastDrawn.length === 0) {
return;
}
var tile = lastDrawn[0];
var useSketch = tiledImage.opacity < 1 ||
(tiledImage.compositeOperation && tiledImage.compositeOperation !== 'source-over');
(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 && tile) {
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,
@ -1358,8 +1462,17 @@ function drawTiles( tiledImage, lastDrawn ) {
tiledImage._drawer.getCanvasSize(true));
}
var bounds;
if (useSketch) {
tiledImage._drawer._clear( true );
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
@ -1405,7 +1518,7 @@ 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, sketchScale, sketchTranslate );
tile.beingDrawn = true;
@ -1442,7 +1555,13 @@ function drawTiles( tiledImage, lastDrawn ) {
if (offsetForRotation) {
tiledImage._drawer._offsetForRotation(tiledImage.viewport.degrees, false);
}
tiledImage._drawer.blendSketch(tiledImage.opacity, sketchScale, sketchTranslate, tiledImage.compositeOperation);
tiledImage._drawer.blendSketch({
opacity: tiledImage.opacity,
scale: sketchScale,
translate: sketchTranslate,
compositeOperation: tiledImage.compositeOperation,
bounds: bounds
});
if (offsetForRotation) {
tiledImage._drawer._restoreRotationChanges(false);
}

View File

@ -345,11 +345,11 @@ $.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 = {
/**
* 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.
*
@ -234,7 +239,9 @@ $.Viewer = function( options ) {
style.left = "0px";
}(this.canvas.style));
$.setElementTouchActionNone( this.canvas );
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";
@ -330,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());
}
});
@ -359,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({
@ -410,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,
@ -779,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}).
*
@ -910,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
@ -973,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;
@ -1203,11 +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.
@ -1243,6 +1266,9 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
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
@ -1305,7 +1331,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
this._loadQueue.push(myQueueItem);
getTileSourceImplementation( this, options.tileSource, function( tileSource ) {
getTileSourceImplementation( this, options.tileSource, options, function( tileSource ) {
myQueueItem.tileSource = tileSource;
@ -1338,6 +1364,8 @@ $.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,
@ -1352,7 +1380,8 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
alwaysBlend: _this.alwaysBlend,
minPixelRatio: _this.minPixelRatio,
smoothTileEdgesMinZoom: _this.smoothTileEdgesMinZoom,
crossOriginPolicy: _this.crossOriginPolicy,
iOSDevice: _this.iOSDevice,
crossOriginPolicy: queueItem.options.crossOriginPolicy,
debugMode: _this.debugMode
});
@ -1374,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
});
@ -1779,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
@ -1824,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', {
@ -1842,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.
@ -1869,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', {
@ -1944,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
@ -2062,7 +2115,7 @@ function _getSafeElemSize (oElement) {
* @function
* @private
*/
function getTileSourceImplementation( viewer, tileSource, successCallback,
function getTileSourceImplementation( viewer, tileSource, imgOptions, successCallback,
failCallback ) {
var _this = viewer;
@ -2096,7 +2149,8 @@ function getTileSourceImplementation( viewer, tileSource, successCallback,
//If its still a string it means it must be a url at this point
tileSource = new $.TileSource({
url: tileSource,
crossOriginPolicy: viewer.crossOriginPolicy,
crossOriginPolicy: imgOptions.crossOriginPolicy !== undefined ?
imgOptions.crossOriginPolicy : viewer.crossOriginPolicy,
ajaxWithCredentials: viewer.ajaxWithCredentials,
useCanvas: viewer.useCanvas,
success: function( event ) {
@ -2108,8 +2162,10 @@ function getTileSourceImplementation( viewer, tileSource, successCallback,
} );
} else if ($.isPlainObject(tileSource) || tileSource.nodeType) {
if (!tileSource.crossOriginPolicy && viewer.crossOriginPolicy) {
tileSource.crossOriginPolicy = viewer.crossOriginPolicy;
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;
@ -2169,37 +2225,28 @@ function getOverlayObject( viewer, overlay ) {
}
var location = overlay.location;
var width = overlay.width;
var height = overlay.height;
if (!location) {
if ( overlay.width && overlay.height ) {
location = overlay.px !== undefined ?
viewer.viewport.imageToViewportRectangle( new $.Rect(
var x = overlay.x;
var y = overlay.y;
if (overlay.px !== undefined) {
var rect = 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
);
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({
@ -2207,7 +2254,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
});
}
@ -2520,21 +2570,24 @@ 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;
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);
}
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();
@ -2846,9 +2899,12 @@ function onCanvasScroll( event ) {
}
}
else {
gestureSettings = this.gestureSettingsByDeviceType( event.pointerType );
if (gestureSettings && gestureSettings.scrollToZoom) {
return false; // We are swallowing this event
}
}
}
function onContainerEnter( event ) {
THIS[ this.hash ].mouseInside = true;
@ -2938,30 +2994,23 @@ function updateOnce( viewer ) {
return;
}
var containerSize;
if (viewer.autoResize) {
containerSize = _getSafeElemSize( viewer.container );
if ( !containerSize.equals( THIS[ viewer.hash ].prevContainerSize ) ) {
if ( viewer.preserveImageSizeOnResize ) {
var containerSize = _getSafeElemSize(viewer.container);
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 (!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;
@ -3049,27 +3098,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();

View File

@ -134,11 +134,7 @@ $.Viewport = function( options ) {
this._oldCenterY = this.centerSpringY.current.value;
this._oldZoom = this.zoomSpring.current.value;
if (this.contentSize) {
this.resetContentSize( this.contentSize );
} else {
this.setHomeBounds(new $.Rect(0, 0, 1, 1), 1);
}
this._setContentBounds(new $.Rect(0, 0, 1, 1), 1);
this.goHome(true);
this.update();
@ -159,83 +155,102 @@ $.Viewport.prototype = {
$.console.assert(contentSize.x > 0, "[Viewport.resetContentSize] contentSize.x must be greater than 0");
$.console.assert(contentSize.y > 0, "[Viewport.resetContentSize] contentSize.y must be greater than 0");
this.setHomeBounds(new $.Rect(0, 0, 1, contentSize.y / contentSize.x), contentSize.x);
this._setContentBounds(new $.Rect(0, 0, 1, contentSize.y / contentSize.x), contentSize.x);
return this;
},
/**
* Updates the viewport's home bounds and constraints.
* @function
* @param {OpenSeadragon.Rect} bounds - the new bounds in viewport coordinates
* @param {Number} contentFactor - how many content units per viewport unit
* @fires OpenSeadragon.Viewer.event:reset-size
*/
// deprecated
setHomeBounds: function(bounds, contentFactor) {
$.console.assert(bounds, "[Viewport.setHomeBounds] bounds is required");
$.console.assert(bounds instanceof $.Rect, "[Viewport.setHomeBounds] bounds must be an OpenSeadragon.Rect");
$.console.assert(bounds.width > 0, "[Viewport.setHomeBounds] bounds.width must be greater than 0");
$.console.assert(bounds.height > 0, "[Viewport.setHomeBounds] bounds.height must be greater than 0");
$.console.error("[Viewport.setHomeBounds] this function is deprecated; The content bounds should not be set manually.");
this._setContentBounds(bounds, contentFactor);
},
this.homeBounds = bounds.clone().rotate(this.degrees).getBoundingBox();
this.contentSize = this.homeBounds.getSize().times(contentFactor);
this.contentAspectX = this.contentSize.x / this.contentSize.y;
this.contentAspectY = this.contentSize.y / this.contentSize.x;
// Set the viewport's content bounds
// @param {OpenSeadragon.Rect} bounds - the new bounds in viewport coordinates
// without rotation
// @param {Number} contentFactor - how many content units per viewport unit
// @fires OpenSeadragon.Viewer.event:reset-size
// @private
_setContentBounds: function(bounds, contentFactor) {
$.console.assert(bounds, "[Viewport._setContentBounds] bounds is required");
$.console.assert(bounds instanceof $.Rect, "[Viewport._setContentBounds] bounds must be an OpenSeadragon.Rect");
$.console.assert(bounds.width > 0, "[Viewport._setContentBounds] bounds.width must be greater than 0");
$.console.assert(bounds.height > 0, "[Viewport._setContentBounds] bounds.height must be greater than 0");
this._contentBoundsNoRotate = bounds.clone();
this._contentSizeNoRotate = this._contentBoundsNoRotate.getSize().times(
contentFactor);
this._contentBounds = bounds.rotate(this.degrees).getBoundingBox();
this._contentSize = this._contentBounds.getSize().times(contentFactor);
this._contentAspectRatio = this._contentSize.x / this._contentSize.y;
if (this.viewer) {
/**
* Raised when the viewer's content size or home bounds are reset
* (see {@link OpenSeadragon.Viewport#resetContentSize},
* {@link OpenSeadragon.Viewport#setHomeBounds}).
* (see {@link OpenSeadragon.Viewport#resetContentSize}).
*
* @event reset-size
* @memberof OpenSeadragon.Viewer
* @type {object}
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
* @property {OpenSeadragon.Point} contentSize
* @property {OpenSeadragon.Rect} homeBounds
* @property {OpenSeadragon.Rect} contentBounds - Content bounds.
* @property {OpenSeadragon.Rect} homeBounds - Content bounds.
* Deprecated use contentBounds instead.
* @property {Number} contentFactor
* @property {?Object} userData - Arbitrary subscriber-defined object.
*/
this.viewer.raiseEvent('reset-size', {
contentSize: this.contentSize.clone(),
contentSize: this._contentSizeNoRotate.clone(),
contentFactor: contentFactor,
homeBounds: this.homeBounds.clone()
homeBounds: this._contentBoundsNoRotate.clone(),
contentBounds: this._contentBounds.clone()
});
}
},
/**
* Returns the home zoom in "viewport zoom" value.
* @function
* @returns {Number} The home zoom in "viewport zoom".
*/
getHomeZoom: function() {
if (this.defaultZoomLevel) {
return this.defaultZoomLevel;
} else {
var aspectFactor =
this.contentAspectX / this.getAspectRatio();
}
var aspectFactor = this._contentAspectRatio / this.getAspectRatio();
var output;
if (this.homeFillsViewer) { // fill the viewer and clip the image
output = ( aspectFactor >= 1) ?
aspectFactor :
1;
output = aspectFactor >= 1 ? aspectFactor : 1;
} else {
output = ( aspectFactor >= 1 ) ?
1 :
aspectFactor;
output = aspectFactor >= 1 ? 1 : aspectFactor;
}
return output / this.homeBounds.width;
}
return output / this._contentBounds.width;
},
/**
* Returns the home bounds in viewport coordinates.
* @function
* @returns {OpenSeadragon.Rect} The home bounds in vewport coordinates.
*/
getHomeBounds: function() {
var center = this.homeBounds.getCenter( ),
width = 1.0 / this.getHomeZoom( ),
height = width / this.getAspectRatio();
return this.getHomeBoundsNoRotate().rotate(-this.getRotation());
},
/**
* Returns the home bounds in viewport coordinates.
* This method ignores the viewport rotation. Use
* {@link OpenSeadragon.Viewport#getHomeBounds} to take it into account.
* @function
* @returns {OpenSeadragon.Rect} The home bounds in vewport coordinates.
*/
getHomeBoundsNoRotate: function() {
var center = this._contentBounds.getCenter();
var width = 1.0 / this.getHomeZoom();
var height = width / this.getAspectRatio();
return new $.Rect(
center.x - (width / 2.0),
@ -287,8 +302,8 @@ $.Viewport.prototype = {
getMaxZoom: function() {
var zoom = this.maxZoomLevel;
if (!zoom) {
zoom = this.contentSize.x * this.maxZoomPixelRatio / this._containerInnerSize.x;
zoom /= this.homeBounds.width;
zoom = this._contentSize.x * this.maxZoomPixelRatio / this._containerInnerSize.x;
zoom /= this._contentBounds.width;
}
return Math.max( zoom, this.getHomeZoom() );
@ -313,8 +328,8 @@ $.Viewport.prototype = {
},
/**
* @function
* The margins push the "home" region in from the sides by the specified amounts.
* @function
* @returns {Object} Properties (Numbers, in screen coordinates): left, top, right, bottom.
*/
getMargins: function() {
@ -322,8 +337,8 @@ $.Viewport.prototype = {
},
/**
* @function
* The margins push the "home" region in from the sides by the specified amounts.
* @function
* @param {Object} margins - Properties (Numbers, in screen coordinates): left, top, right, bottom.
*/
setMargins: function(margins) {
@ -337,18 +352,33 @@ $.Viewport.prototype = {
}, margins);
this._updateContainerInnerSize();
if (this.viewer) {
this.viewer.forceRedraw();
}
},
/**
* Returns the bounds of the visible area in viewport coordinates.
* @function
* @param {Boolean} current - Pass true for the current location; defaults to false (target location).
* @returns {OpenSeadragon.Rect} The location you are zoomed/panned to, in viewport coordinates.
*/
getBounds: function(current) {
var center = this.getCenter( current ),
width = 1.0 / this.getZoom( current ),
height = width / this.getAspectRatio();
return this.getBoundsNoRotate(current).rotate(-this.getRotation());
},
/**
* Returns the bounds of the visible area in viewport coordinates.
* This method ignores the viewport rotation. Use
* {@link OpenSeadragon.Viewport#getBounds} to take it into account.
* @function
* @param {Boolean} current - Pass true for the current location; defaults to false (target location).
* @returns {OpenSeadragon.Rect} The location you are zoomed/panned to, in viewport coordinates.
*/
getBoundsNoRotate: function(current) {
var center = this.getCenter(current);
var width = 1.0 / this.getZoom(current);
var height = width / this.getAspectRatio();
return new $.Rect(
center.x - (width / 2.0),
@ -365,7 +395,18 @@ $.Viewport.prototype = {
* including the space taken by margins, in viewport coordinates.
*/
getBoundsWithMargins: function(current) {
var bounds = this.getBounds(current);
return this.getBoundsNoRotateWithMargins(current).rotate(
-this.getRotation(), this.getCenter(current));
},
/**
* @function
* @param {Boolean} current - Pass true for the current location; defaults to false (target location).
* @returns {OpenSeadragon.Rect} The location you are zoomed/panned to,
* including the space taken by margins, in viewport coordinates.
*/
getBoundsNoRotateWithMargins: function(current) {
var bounds = this.getBoundsNoRotate(current);
var factor = this._containerInnerSize.x * this.getZoom(current);
bounds.x -= this._margins.left / factor;
bounds.y -= this._margins.top / factor;
@ -433,6 +474,13 @@ $.Viewport.prototype = {
}
},
// private
_applyZoomConstraints: function(zoom) {
return Math.max(
Math.min(zoom, this.getMaxZoom()),
this.getMinZoom());
},
/**
* @function
* @private
@ -441,62 +489,47 @@ $.Viewport.prototype = {
* @return {OpenSeadragon.Rect} constrained bounds.
*/
_applyBoundaryConstraints: function(bounds, immediately) {
var dx = 0,
dy = 0,
newBounds = new $.Rect(
var newBounds = new $.Rect(
bounds.x,
bounds.y,
bounds.width,
bounds.height
);
var horizontalThreshold = this.visibilityRatio * newBounds.width;
var verticalThreshold = this.visibilityRatio * newBounds.height;
bounds.height);
if (this.wrapHorizontal) {
//do nothing
} else {
var thresholdLeft = newBounds.x + (newBounds.width - horizontalThreshold);
if (this.homeBounds.x > thresholdLeft) {
dx = this.homeBounds.x - thresholdLeft;
}
var horizontalThreshold = this.visibilityRatio * newBounds.width;
var boundsRight = newBounds.x + newBounds.width;
var contentRight = this._contentBoundsNoRotate.x + this._contentBoundsNoRotate.width;
var leftDx = this._contentBoundsNoRotate.x - boundsRight + horizontalThreshold;
var rightDx = contentRight - newBounds.x - horizontalThreshold;
var homeRight = this.homeBounds.x + this.homeBounds.width;
var thresholdRight = newBounds.x + horizontalThreshold;
if (homeRight < thresholdRight) {
var newDx = homeRight - thresholdRight;
if (dx) {
dx = (dx + newDx) / 2;
} else {
dx = newDx;
}
if (horizontalThreshold > this._contentBoundsNoRotate.width) {
newBounds.x += (leftDx + rightDx) / 2;
} else if (rightDx < 0) {
newBounds.x += rightDx;
} else if (leftDx > 0) {
newBounds.x += leftDx;
}
}
if (this.wrapVertical) {
//do nothing
} else {
var thresholdTop = newBounds.y + (newBounds.height - verticalThreshold);
if (this.homeBounds.y > thresholdTop) {
dy = this.homeBounds.y - thresholdTop;
}
var verticalThreshold = this.visibilityRatio * newBounds.height;
var boundsBottom = newBounds.y + newBounds.height;
var contentBottom = this._contentBoundsNoRotate.y + this._contentBoundsNoRotate.height;
var topDy = this._contentBoundsNoRotate.y - boundsBottom + verticalThreshold;
var bottomDy = contentBottom - newBounds.y - verticalThreshold;
var homeBottom = this.homeBounds.y + this.homeBounds.height;
var thresholdBottom = newBounds.y + verticalThreshold;
if (homeBottom < thresholdBottom) {
var newDy = homeBottom - thresholdBottom;
if (dy) {
dy = (dy + newDy) / 2;
} else {
dy = newDy;
if (verticalThreshold > this._contentBoundsNoRotate.height) {
newBounds.y += (topDy + bottomDy) / 2;
} else if (bottomDy < 0) {
newBounds.y += bottomDy;
} else if (topDy > 0) {
newBounds.y += topDy;
}
}
}
if ( dx || dy ) {
newBounds.x += dx;
newBounds.y += dy;
}
if (this.viewer) {
/**
@ -518,37 +551,41 @@ $.Viewport.prototype = {
},
/**
* Enforces the minZoom, maxZoom and visibilityRatio constraints by
* zooming and panning to the closest acceptable zoom and location.
* @function
* @param {Boolean} [immediately=false]
* @return {OpenSeadragon.Viewport} Chainable.
* @fires OpenSeadragon.Viewer.event:constrain
*/
applyConstraints: function(immediately) {
var actualZoom = this.getZoom(),
constrainedZoom = Math.max(
Math.min( actualZoom, this.getMaxZoom() ),
this.getMinZoom()
),
bounds,
constrainedBounds;
var actualZoom = this.getZoom();
var constrainedZoom = this._applyZoomConstraints(actualZoom);
if ( actualZoom != constrainedZoom ) {
if (actualZoom !== constrainedZoom) {
this.zoomTo(constrainedZoom, this.zoomPoint, immediately);
}
bounds = this.getBounds();
var bounds = this.getBoundsNoRotate();
var constrainedBounds = this._applyBoundaryConstraints(
bounds, immediately);
constrainedBounds = this._applyBoundaryConstraints( bounds, immediately );
if ( bounds.x !== constrainedBounds.x || bounds.y !== constrainedBounds.y || immediately ){
this.fitBounds( constrainedBounds, immediately );
if (bounds.x !== constrainedBounds.x ||
bounds.y !== constrainedBounds.y ||
immediately) {
this.fitBounds(
constrainedBounds.rotate(-this.getRotation()),
immediately);
}
return this;
},
/**
* Equivalent to {@link OpenSeadragon.Viewport#applyConstraints}
* @function
* @param {Boolean} immediately
* @param {Boolean} [immediately=false]
* @return {OpenSeadragon.Viewport} Chainable.
* @fires OpenSeadragon.Viewer.event:constrain
*/
ensureVisible: function(immediately) {
return this.applyConstraints(immediately);
@ -566,45 +603,32 @@ $.Viewport.prototype = {
var immediately = options.immediately || false;
var constraints = options.constraints || false;
var aspect = this.getAspectRatio(),
center = bounds.getCenter(),
newBounds = new $.Rect(
var aspect = this.getAspectRatio();
var center = bounds.getCenter();
// Compute width and height of bounding box.
var newBounds = new $.Rect(
bounds.x,
bounds.y,
bounds.width,
bounds.height
),
oldBounds,
oldZoom,
newZoom,
referencePoint,
newBoundsAspectRatio,
newConstrainedZoom;
bounds.height,
bounds.degrees + this.getRotation())
.getBoundingBox();
if (newBounds.getAspectRatio() >= aspect) {
newBounds.height = bounds.width / aspect;
newBounds.y = center.y - newBounds.height / 2;
newBounds.height = newBounds.width / aspect;
} else {
newBounds.width = bounds.height * aspect;
newBounds.width = newBounds.height * aspect;
}
// Compute x and y from width, height and center position
newBounds.x = center.x - newBounds.width / 2;
}
newBounds.y = center.y - newBounds.height / 2;
var newZoom = 1.0 / newBounds.width;
if (constraints) {
newBoundsAspectRatio = newBounds.getAspectRatio();
}
this.panTo( this.getCenter( true ), true );
this.zoomTo( this.getZoom( true ), null, true );
oldBounds = this.getBounds();
oldZoom = this.getZoom();
newZoom = 1.0 / newBounds.width;
if ( constraints ) {
newConstrainedZoom = Math.max(
Math.min(newZoom, this.getMaxZoom() ),
this.getMinZoom()
);
var newBoundsAspectRatio = newBounds.getAspectRatio();
var newConstrainedZoom = this._applyZoomConstraints(newZoom);
if (newZoom !== newConstrainedZoom) {
newZoom = newConstrainedZoom;
@ -623,29 +647,35 @@ $.Viewport.prototype = {
return this.zoomTo(newZoom, null, true);
}
if (Math.abs(newZoom - oldZoom) < 0.00000001 ||
Math.abs(newBounds.width - oldBounds.width) < 0.00000001) {
this.panTo(this.getCenter(true), true);
this.zoomTo(this.getZoom(true), null, true);
var oldBounds = this.getBounds();
var oldZoom = this.getZoom();
if (oldZoom === 0 || Math.abs(newZoom / oldZoom - 1) < 0.00000001) {
this.zoomTo(newZoom, true);
return this.panTo(center, immediately);
}
referencePoint = oldBounds.getTopLeft().times(
this._containerInnerSize.x / oldBounds.width
).minus(
newBounds.getTopLeft().times(
this._containerInnerSize.x / newBounds.width
)
).divide(
this._containerInnerSize.x / oldBounds.width -
this._containerInnerSize.x / newBounds.width
);
newBounds = newBounds.rotate(-this.getRotation());
var referencePoint = newBounds.getTopLeft().times(newZoom)
.minus(oldBounds.getTopLeft().times(oldZoom))
.divide(newZoom - oldZoom);
return this.zoomTo(newZoom, referencePoint, immediately);
},
/**
* Makes the viewport zoom and pan so that the specified bounds take
* as much space as possible in the viewport.
* Note: this method ignores the constraints (minZoom, maxZoom and
* visibilityRatio).
* Use {@link OpenSeadragon.Viewport#fitBoundsWithConstraints} to enforce
* them.
* @function
* @param {OpenSeadragon.Rect} bounds
* @param {Boolean} immediately
* @param {Boolean} [immediately=false]
* @return {OpenSeadragon.Viewport} Chainable.
*/
fitBounds: function(bounds, immediately) {
@ -656,9 +686,15 @@ $.Viewport.prototype = {
},
/**
* Makes the viewport zoom and pan so that the specified bounds take
* as much space as possible in the viewport while enforcing the constraints
* (minZoom, maxZoom and visibilityRatio).
* Note: because this method enforces the constraints, part of the
* provided bounds may end up outside of the viewport.
* Use {@link OpenSeadragon.Viewport#fitBounds} to ignore them.
* @function
* @param {OpenSeadragon.Rect} bounds
* @param {Boolean} immediately
* @param {Boolean} [immediately=false]
* @return {OpenSeadragon.Viewport} Chainable.
*/
fitBoundsWithConstraints: function(bounds, immediately) {
@ -674,9 +710,11 @@ $.Viewport.prototype = {
* @return {OpenSeadragon.Viewport} Chainable.
*/
fitVertically: function(immediately) {
var box = new $.Rect(this.homeBounds.x + (this.homeBounds.width / 2), this.homeBounds.y,
0, this.homeBounds.height);
var box = new $.Rect(
this._contentBounds.x + (this._contentBounds.width / 2),
this._contentBounds.y,
0,
this._contentBounds.height);
return this.fitBounds(box, immediately);
},
@ -686,9 +724,11 @@ $.Viewport.prototype = {
* @return {OpenSeadragon.Viewport} Chainable.
*/
fitHorizontally: function(immediately) {
var box = new $.Rect(this.homeBounds.x, this.homeBounds.y + (this.homeBounds.height / 2),
this.homeBounds.width, 0);
var box = new $.Rect(
this._contentBounds.x,
this._contentBounds.y + (this._contentBounds.height / 2),
this._contentBounds.width,
0);
return this.fitBounds(box, immediately);
},
@ -756,7 +796,12 @@ $.Viewport.prototype = {
},
/**
* Zooms to the specified zoom level
* @function
* @param {Number} zoom The zoom level to zoom to.
* @param {OpenSeadragon.Point} [refPoint] The point which will stay at
* the same screen location. Defaults to the viewport center.
* @param {Boolean} [immediately=false]
* @return {OpenSeadragon.Viewport} Chainable.
* @fires OpenSeadragon.Viewer.event:zoom
*/
@ -812,7 +857,7 @@ $.Viewport.prototype = {
degrees += 360;
}
this.degrees = degrees;
this.setHomeBounds(
this._setContentBounds(
this.viewer.world.getHomeBounds(),
this.viewer.world.getContentFactor());
this.viewer.forceRedraw();
@ -846,7 +891,7 @@ $.Viewport.prototype = {
* @fires OpenSeadragon.Viewer.event:resize
*/
resize: function( newContainerSize, maintain ) {
var oldBounds = this.getBounds(),
var oldBounds = this.getBoundsNoRotate(),
newBounds = oldBounds,
widthDeltaFactor;
@ -892,37 +937,37 @@ $.Viewport.prototype = {
},
/**
* Update the zoom and center (X and Y) springs.
* @function
* @returns {Boolean} True if any change has been made, false otherwise.
*/
update: function() {
var oldZoomPixel,
newZoomPixel,
deltaZoomPixels,
deltaZoomPoints;
if (this.zoomPoint) {
oldZoomPixel = this.pixelFromPoint( this.zoomPoint, true );
}
var oldZoomPixel = this.pixelFromPoint(this.zoomPoint, true);
this.zoomSpring.update();
var newZoomPixel = this.pixelFromPoint(this.zoomPoint, true);
if (this.zoomPoint && this.zoomSpring.current.value != this._oldZoom) {
newZoomPixel = this.pixelFromPoint( this.zoomPoint, true );
deltaZoomPixels = newZoomPixel.minus( oldZoomPixel );
deltaZoomPoints = this.deltaPointsFromPixels( deltaZoomPixels, true );
var deltaZoomPixels = newZoomPixel.minus(oldZoomPixel);
var deltaZoomPoints = this.deltaPointsFromPixels(
deltaZoomPixels, true);
this.centerSpringX.shiftBy(deltaZoomPoints.x);
this.centerSpringY.shiftBy(deltaZoomPoints.y);
} else {
if (this.zoomSpring.isAtTargetValue()) {
this.zoomPoint = null;
}
} else {
this.zoomSpring.update();
}
this.centerSpringX.update();
this.centerSpringY.update();
var changed = this.centerSpringX.current.value != this._oldCenterX ||
this.centerSpringY.current.value != this._oldCenterY ||
this.zoomSpring.current.value != this._oldZoom;
var changed = this.centerSpringX.current.value !== this._oldCenterX ||
this.centerSpringY.current.value !== this._oldCenterY ||
this.zoomSpring.current.value !== this._oldZoom;
this._oldCenterX = this.centerSpringX.current.value;
this._oldCenterY = this.centerSpringY.current.value;
@ -998,7 +1043,8 @@ $.Viewport.prototype = {
* @returns {OpenSeadragon.Point}
*/
pixelFromPointNoRotate: function(point, current) {
return this._pixelFromPointNoRotate(point, this.getBounds(current));
return this._pixelFromPointNoRotate(
point, this.getBoundsNoRotate(current));
},
/**
@ -1009,7 +1055,7 @@ $.Viewport.prototype = {
* @returns {OpenSeadragon.Point}
*/
pixelFromPoint: function(point, current) {
return this._pixelFromPoint(point, this.getBounds(current));
return this._pixelFromPoint(point, this.getBoundsNoRotate(current));
},
// private
@ -1040,7 +1086,7 @@ $.Viewport.prototype = {
* @returns {OpenSeadragon.Point}
*/
pointFromPixelNoRotate: function(pixel, current) {
var bounds = this.getBounds( current );
var bounds = this.getBoundsNoRotate(current);
return pixel.minus(
new $.Point(this._margins.left, this._margins.top)
).divide(
@ -1066,9 +1112,10 @@ $.Viewport.prototype = {
// private
_viewportToImageDelta: function( viewerX, viewerY ) {
var scale = this.homeBounds.width;
return new $.Point(viewerX * (this.contentSize.x / scale),
viewerY * ((this.contentSize.y * this.contentAspectX) / scale));
var scale = this._contentBoundsNoRotate.width;
return new $.Point(
viewerX * this._contentSizeNoRotate.x / scale,
viewerY * this._contentSizeNoRotate.x / scale);
},
/**
@ -1088,18 +1135,31 @@ $.Viewport.prototype = {
return this.viewportToImageCoordinates(viewerX.x, viewerX.y);
}
if (this.viewer && this.viewer.world.getItemCount() > 1) {
$.console.error('[Viewport.viewportToImageCoordinates] is not accurate with multi-image; use TiledImage.viewportToImageCoordinates instead.');
if (this.viewer) {
var count = this.viewer.world.getItemCount();
if (count > 1) {
$.console.error('[Viewport.viewportToImageCoordinates] is not accurate ' +
'with multi-image; use TiledImage.viewportToImageCoordinates instead.');
} else if (count === 1) {
// It is better to use TiledImage.viewportToImageCoordinates
// because this._contentBoundsNoRotate can not be relied on
// with clipping.
var item = this.viewer.world.getItemAt(0);
return item.viewportToImageCoordinates(viewerX, viewerY, true);
}
}
return this._viewportToImageDelta(viewerX - this.homeBounds.x, viewerY - this.homeBounds.y);
return this._viewportToImageDelta(
viewerX - this._contentBoundsNoRotate.x,
viewerY - this._contentBoundsNoRotate.y);
},
// private
_imageToViewportDelta: function( imageX, imageY ) {
var scale = this.homeBounds.width;
return new $.Point((imageX / this.contentSize.x) * scale,
(imageY / this.contentSize.y / this.contentAspectX) * scale);
var scale = this._contentBoundsNoRotate.width;
return new $.Point(
imageX / this._contentSizeNoRotate.x * scale,
imageY / this._contentSizeNoRotate.x * scale);
},
/**
@ -1119,13 +1179,23 @@ $.Viewport.prototype = {
return this.imageToViewportCoordinates(imageX.x, imageX.y);
}
if (this.viewer && this.viewer.world.getItemCount() > 1) {
$.console.error('[Viewport.imageToViewportCoordinates] is not accurate with multi-image; use TiledImage.imageToViewportCoordinates instead.');
if (this.viewer) {
var count = this.viewer.world.getItemCount();
if (count > 1) {
$.console.error('[Viewport.imageToViewportCoordinates] is not accurate ' +
'with multi-image; use TiledImage.imageToViewportCoordinates instead.');
} else if (count === 1) {
// It is better to use TiledImage.viewportToImageCoordinates
// because this._contentBoundsNoRotate can not be relied on
// with clipping.
var item = this.viewer.world.getItemAt(0);
return item.imageToViewportCoordinates(imageX, imageY, true);
}
}
var point = this._imageToViewportDelta(imageX, imageY);
point.x += this.homeBounds.x;
point.y += this.homeBounds.y;
point.x += this._contentBoundsNoRotate.x;
point.y += this._contentBoundsNoRotate.y;
return point;
},
@ -1150,6 +1220,21 @@ $.Viewport.prototype = {
rect = new $.Rect(imageX, imageY, pixelWidth, pixelHeight);
}
if (this.viewer) {
var count = this.viewer.world.getItemCount();
if (count > 1) {
$.console.error('[Viewport.imageToViewportRectangle] is not accurate ' +
'with multi-image; use TiledImage.imageToViewportRectangle instead.');
} else if (count === 1) {
// It is better to use TiledImage.imageToViewportRectangle
// because this._contentBoundsNoRotate can not be relied on
// with clipping.
var item = this.viewer.world.getItemAt(0);
return item.imageToViewportRectangle(
imageX, imageY, pixelWidth, pixelHeight, true);
}
}
var coordA = this.imageToViewportCoordinates(rect.x, rect.y);
var coordB = this._imageToViewportDelta(rect.width, rect.height);
return new $.Rect(
@ -1183,6 +1268,21 @@ $.Viewport.prototype = {
rect = new $.Rect(viewerX, viewerY, pointWidth, pointHeight);
}
if (this.viewer) {
var count = this.viewer.world.getItemCount();
if (count > 1) {
$.console.error('[Viewport.viewportToImageRectangle] is not accurate ' +
'with multi-image; use TiledImage.viewportToImageRectangle instead.');
} else if (count === 1) {
// It is better to use TiledImage.viewportToImageCoordinates
// because this._contentBoundsNoRotate can not be relied on
// with clipping.
var item = this.viewer.world.getItemAt(0);
return item.viewportToImageRectangle(
viewerX, viewerY, pointWidth, pointHeight, true);
}
}
var coordA = this.viewportToImageCoordinates(rect.x, rect.y);
var coordB = this._viewportToImageDelta(rect.width, rect.height);
return new $.Rect(
@ -1225,8 +1325,10 @@ $.Viewport.prototype = {
* @returns {OpenSeadragon.Point}
*/
windowToImageCoordinates: function(pixel) {
$.console.assert(this.viewer,
"[Viewport.windowToImageCoordinates] the viewport must have a viewer.");
var viewerCoordinates = pixel.minus(
OpenSeadragon.getElementPosition( this.viewer.element ));
$.getElementPosition(this.viewer.element));
return this.viewerElementToImageCoordinates(viewerCoordinates);
},
@ -1237,9 +1339,11 @@ $.Viewport.prototype = {
* @returns {OpenSeadragon.Point}
*/
imageToWindowCoordinates: function(pixel) {
$.console.assert(this.viewer,
"[Viewport.imageToWindowCoordinates] the viewport must have a viewer.");
var viewerCoordinates = this.imageToViewerElementCoordinates(pixel);
return viewerCoordinates.plus(
OpenSeadragon.getElementPosition( this.viewer.element ));
$.getElementPosition(this.viewer.element));
},
/**
@ -1262,14 +1366,44 @@ $.Viewport.prototype = {
return this.pixelFromPoint( point, true );
},
/**
* Convert a rectangle in pixel coordinates relative to the viewer element
* to viewport coordinates.
* @param {OpenSeadragon.Rect} rectangle the rectangle to convert
* @returns {OpenSeadragon.Rect} the converted rectangle
*/
viewerElementToViewportRectangle: function(rectangle) {
return $.Rect.fromSummits(
this.pointFromPixel(rectangle.getTopLeft(), true),
this.pointFromPixel(rectangle.getTopRight(), true),
this.pointFromPixel(rectangle.getBottomLeft(), true)
);
},
/**
* Convert a rectangle in viewport coordinates to pixel coordinates relative
* to the viewer element.
* @param {OpenSeadragon.Rect} rectangle the rectangle to convert
* @returns {OpenSeadragon.Rect} the converted rectangle
*/
viewportToViewerElementRectangle: function(rectangle) {
return $.Rect.fromSummits(
this.pixelFromPoint(rectangle.getTopLeft(), true),
this.pixelFromPoint(rectangle.getTopRight(), true),
this.pixelFromPoint(rectangle.getBottomLeft(), true)
);
},
/**
* Convert pixel coordinates relative to the window to viewport coordinates.
* @param {OpenSeadragon.Point} pixel
* @returns {OpenSeadragon.Point}
*/
windowToViewportCoordinates: function(pixel) {
$.console.assert(this.viewer,
"[Viewport.windowToViewportCoordinates] the viewport must have a viewer.");
var viewerCoordinates = pixel.minus(
OpenSeadragon.getElementPosition( this.viewer.element ));
$.getElementPosition(this.viewer.element));
return this.viewerElementToViewportCoordinates(viewerCoordinates);
},
@ -1279,9 +1413,11 @@ $.Viewport.prototype = {
* @returns {OpenSeadragon.Point}
*/
viewportToWindowCoordinates: function(point) {
$.console.assert(this.viewer,
"[Viewport.viewportToWindowCoordinates] the viewport must have a viewer.");
var viewerCoordinates = this.viewportToViewerElementCoordinates(point);
return viewerCoordinates.plus(
OpenSeadragon.getElementPosition( this.viewer.element ));
$.getElementPosition(this.viewer.element));
},
/**
@ -1297,13 +1433,23 @@ $.Viewport.prototype = {
* @returns {Number} imageZoom The image zoom
*/
viewportToImageZoom: function(viewportZoom) {
if (this.viewer && this.viewer.world.getItemCount() > 1) {
$.console.error('[Viewport.viewportToImageZoom] is not accurate with multi-image.');
if (this.viewer) {
var count = this.viewer.world.getItemCount();
if (count > 1) {
$.console.error('[Viewport.viewportToImageZoom] is not ' +
'accurate with multi-image.');
} else if (count === 1) {
// It is better to use TiledImage.viewportToImageZoom
// because this._contentBoundsNoRotate can not be relied on
// with clipping.
var item = this.viewer.world.getItemAt(0);
return item.viewportToImageZoom(viewportZoom);
}
}
var imageWidth = this.contentSize.x;
var imageWidth = this._contentSizeNoRotate.x;
var containerWidth = this._containerInnerSize.x;
var scale = this.homeBounds.width;
var scale = this._contentBoundsNoRotate.width;
var viewportToImageZoomRatio = (containerWidth / imageWidth) * scale;
return viewportZoom * viewportToImageZoomRatio;
},
@ -1321,13 +1467,23 @@ $.Viewport.prototype = {
* @returns {Number} viewportZoom The viewport zoom
*/
imageToViewportZoom: function(imageZoom) {
if (this.viewer && this.viewer.world.getItemCount() > 1) {
$.console.error('[Viewport.imageToViewportZoom] is not accurate with multi-image.');
if (this.viewer) {
var count = this.viewer.world.getItemCount();
if (count > 1) {
$.console.error('[Viewport.imageToViewportZoom] is not accurate ' +
'with multi-image.');
} else if (count === 1) {
// It is better to use TiledImage.imageToViewportZoom
// because this._contentBoundsNoRotate can not be relied on
// with clipping.
var item = this.viewer.world.getItemAt(0);
return item.imageToViewportZoom(imageZoom);
}
}
var imageWidth = this.contentSize.x;
var imageWidth = this._contentSizeNoRotate.x;
var containerWidth = this._containerInnerSize.x;
var scale = this.homeBounds.width;
var scale = this._contentBoundsNoRotate.width;
var viewportToImageZoomRatio = (imageWidth / containerWidth) / scale;
return imageZoom * viewportToImageZoomRatio;
}

View File

@ -380,28 +380,34 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W
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;
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++) {
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 );
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._contentSize = new $.Point(
this._homeBounds.width * this._contentFactor,
this._homeBounds.height * this._contentFactor);
}
if (this._contentFactor !== oldContentFactor || !this._homeBounds.equals(oldHomeBounds) ||
if (this._contentFactor !== oldContentFactor ||
!this._homeBounds.equals(oldHomeBounds) ||
!this._contentSize.equals(oldContentSize)) {
/**
* Raised when the home bounds or content factor change.

View File

@ -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>
@ -52,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

View File

@ -14,7 +14,9 @@
<body>
<div id="contentDiv" class="openseadragon1"></div>
<div id="annotation-div">
<input type="button" value="Hide Overlay" id="hideOverlay">
<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">
@ -22,19 +24,74 @@
var viewer = OpenSeadragon({
id: "contentDiv",
prefixUrl: "../../build/openseadragon/images/",
tileSources: "../data/testpattern.dzi"
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.id = "runtime-overlay";
elt.style.border = "1px solid red";
viewer.addOverlay( elt, new OpenSeadragon.Rect(0.2, 0.2, 0.75, 0.75) );
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
});
$("#hideOverlay").click(function(){
$("#runtime-overlay").toggle();
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>

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

@ -365,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

@ -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 () {
if ('TouchEvent' in window) {
asyncTest( 'MouseTracker: touch events', function () {
var $canvas = $( viewer.element ).find( '.openseadragon-canvas' ).not( '.navigator .openseadragon-canvas' ),
tracker = viewer.innerTracker,
origClickHandler,
origDragHandler,
dragCount = 10,
originalZoom = 0,
originalBounds = null;
touches;
var reset = function () {
touches = [];
TouchUtil.reset();
};
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 + ')' );
}
};
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 );
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();
};
originalZoom = viewer.viewport.getZoom();
originalBounds = viewer.viewport.getBounds();
viewer.addHandler( 'open', onOpen );
viewer.open( '/test/data/testpattern.dzi' );
} );
}
var event = {
clientX:1,
clientY:1
};
// ----------
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', event );
// Drag to pan
Util.simulateViewerClickWithDrag( {
viewer: viewer,
widthFactor: 0.25,
heightFactor: 0.25,
dragCount: dragCount,
dragCount: 10,
dragDx: 1,
dragDy: 1
} );
@ -730,12 +781,49 @@
dragDy: 0
} );
$canvas.simulate( 'blur', event );
}
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();
@ -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

@ -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();
}
} );
@ -208,4 +208,57 @@
viewer.open('/test/data/testpattern.dzi');
});
asyncTest('Transparent image on top of others', function() {
viewer.open('/test/data/testpattern.dzi');
// TODO: replace with fully-loaded event listener when available.
setTimeout(function() {
var imageData = viewer.drawer.context.getImageData(0, 0, 500, 500);
// Pixel 250,250 will be in the hole of the A
var expectedVal = getPixelValue(imageData, 250, 250);
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'
});
// TODO: replace with fully-loaded event listener when available.
setTimeout(function() {
var imageData = viewer.drawer.context.getImageData(0, 0, 500, 500);
var actualVal = getPixelValue(imageData, 250, 250);
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, 250);
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();
}, 500);
}, 500);
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' +
@ -867,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();
});
});
})();

View File

@ -2,6 +2,8 @@
(function() {
var viewer;
// jQuery.position can give results quite different than what set in style.left
var epsilon = 1;
module("Overlays", {
setup: function() {
@ -241,26 +243,34 @@
var viewport = viewer.viewport;
var expPosition = viewport.imageToViewerElementCoordinates(
new OpenSeadragon.Point( 13, 120 ) ).apply( Math.round );
new OpenSeadragon.Point(13, 120));
var actPosition = $("#overlay").position();
equal( actPosition.left, expPosition.x, "X position mismatch " + contextMessage );
equal( actPosition.top, expPosition.y, "Y position mismatch " + contextMessage );
Util.assessNumericValue(actPosition.left, expPosition.x, epsilon,
"X position mismatch " + contextMessage);
Util.assessNumericValue(actPosition.top, expPosition.y, epsilon,
"Y position mismatch " + contextMessage);
var zoom = viewport.viewportToImageZoom(viewport.getZoom(true));
var expectedWidth = Math.round( 124 * zoom );
var expectedHeight = Math.round( 132 * zoom );
equal( $( "#overlay" ).width(), expectedWidth, "Width mismatch " + contextMessage );
equal( $( "#overlay" ).height( ), expectedHeight, "Height mismatch " + contextMessage );
var expectedWidth = 124 * zoom;
var expectedHeight = 132 * zoom;
Util.assessNumericValue($("#overlay").width(), expectedWidth, epsilon,
"Width mismatch " + contextMessage);
Util.assessNumericValue($("#overlay").height(), expectedHeight, epsilon,
"Height mismatch " + contextMessage);
expPosition = viewport.imageToViewerElementCoordinates(
new OpenSeadragon.Point( 400, 500 ) ).apply( Math.round );
new OpenSeadragon.Point(400, 500));
actPosition = $("#fixed-overlay").position();
equal( actPosition.left, expPosition.x, "Fixed overlay X position mismatch " + contextMessage );
equal( actPosition.top, expPosition.y, "Fixed overlay Y position mismatch " + contextMessage );
Util.assessNumericValue(actPosition.left, expPosition.x, epsilon,
"Fixed overlay X position mismatch " + contextMessage);
Util.assessNumericValue(actPosition.top, expPosition.y, epsilon,
"Fixed overlay Y position mismatch " + contextMessage);
equal( $( "#fixed-overlay" ).width(), 70, "Fixed overlay width mismatch " + contextMessage );
equal( $( "#fixed-overlay" ).height( ), 60, "Fixed overlay height mismatch " + contextMessage );
Util.assessNumericValue($("#fixed-overlay").width(), 70, epsilon,
"Fixed overlay width mismatch " + contextMessage);
Util.assessNumericValue($("#fixed-overlay").height(), 60, epsilon,
"Fixed overlay height mismatch " + contextMessage);
}
waitForViewer(function() {
@ -305,25 +315,33 @@
var viewport = viewer.viewport;
var expPosition = viewport.viewportToViewerElementCoordinates(
new OpenSeadragon.Point( 0.2, 0.1 ) ).apply( Math.round );
new OpenSeadragon.Point(0.2, 0.1));
var actPosition = $("#overlay").position();
equal( actPosition.left, expPosition.x, "X position mismatch " + contextMessage );
equal( actPosition.top, expPosition.y, "Y position mismatch " + contextMessage );
Util.assessNumericValue(actPosition.left, expPosition.x, epsilon,
"X position mismatch " + contextMessage);
Util.assessNumericValue(actPosition.top, expPosition.y, epsilon,
"Y position mismatch " + contextMessage);
var expectedSize = viewport.deltaPixelsFromPoints(
new OpenSeadragon.Point(0.5, 0.1));
equal( $( "#overlay" ).width(), expectedSize.x, "Width mismatch " + contextMessage );
equal( $( "#overlay" ).height(), expectedSize.y, "Height mismatch " + contextMessage );
Util.assessNumericValue($("#overlay").width(), expectedSize.x, epsilon,
"Width mismatch " + contextMessage);
Util.assessNumericValue($("#overlay").height(), expectedSize.y, epsilon,
"Height mismatch " + contextMessage);
expPosition = viewport.viewportToViewerElementCoordinates(
new OpenSeadragon.Point( 0.5, 0.6 ) ).apply( Math.round );
new OpenSeadragon.Point(0.5, 0.6));
actPosition = $("#fixed-overlay").position();
equal( actPosition.left, expPosition.x, "Fixed overlay X position mismatch " + contextMessage );
equal( actPosition.top, expPosition.y, "Fixed overlay Y position mismatch " + contextMessage );
Util.assessNumericValue(actPosition.left, expPosition.x, epsilon,
"Fixed overlay X position mismatch " + contextMessage);
Util.assessNumericValue(actPosition.top, expPosition.y, epsilon,
"Fixed overlay Y position mismatch " + contextMessage);
equal( $( "#fixed-overlay" ).width(), 70, "Fixed overlay width mismatch " + contextMessage );
equal( $( "#fixed-overlay" ).height( ), 60, "Fixed overlay height mismatch " + contextMessage );
Util.assessNumericValue($("#fixed-overlay").width(), 70, epsilon,
"Fixed overlay width mismatch " + contextMessage);
Util.assessNumericValue($("#fixed-overlay").height(), 60, epsilon,
"Fixed overlay height mismatch " + contextMessage);
}
waitForViewer(function() {
@ -373,10 +391,12 @@
var viewport = viewer.viewport;
var expPosition = viewport.viewportToViewerElementCoordinates(
new OpenSeadragon.Point( 0.2, 0.1 ) ).apply( Math.round );
new OpenSeadragon.Point(0.2, 0.1));
var actPosition = $("#overlay").position();
equal( actPosition.left, expPosition.x, "X position mismatch " + contextMessage );
equal( actPosition.top, expPosition.y, "Y position mismatch " + contextMessage );
Util.assessNumericValue(actPosition.left, expPosition.x, epsilon,
"X position mismatch " + contextMessage);
Util.assessNumericValue(actPosition.top, expPosition.y, epsilon,
"Y position mismatch " + contextMessage);
}
function checkFixedOverlayPosition(expectedOffset, contextMessage) {
@ -384,11 +404,12 @@
var expPosition = viewport.viewportToViewerElementCoordinates(
new OpenSeadragon.Point(0.5, 0.6))
.apply( Math.round )
.plus(expectedOffset);
var actPosition = $("#fixed-overlay").position();
equal( actPosition.left, expPosition.x, "Fixed overlay X position mismatch " + contextMessage );
equal( actPosition.top, expPosition.y, "Fixed overlay Y position mismatch " + contextMessage );
Util.assessNumericValue(actPosition.left, expPosition.x, epsilon,
"Fixed overlay X position mismatch " + contextMessage);
Util.assessNumericValue(actPosition.top, expPosition.y, epsilon,
"Fixed overlay Y position mismatch " + contextMessage);
}
waitForViewer(function() {
@ -397,6 +418,7 @@
checkFixedOverlayPosition(new OpenSeadragon.Point(0, 0),
"with TOP_LEFT placement.");
// Check that legacy OpenSeadragon.OverlayPlacement is still working
viewer.updateOverlay("overlay", scalableOverlayLocation,
OpenSeadragon.OverlayPlacement.CENTER);
viewer.updateOverlay("fixed-overlay", fixedOverlayLocation,
@ -407,10 +429,11 @@
checkFixedOverlayPosition(new OpenSeadragon.Point(-35, -30),
"with CENTER placement.");
// Check that new OpenSeadragon.Placement is working
viewer.updateOverlay("overlay", scalableOverlayLocation,
OpenSeadragon.OverlayPlacement.BOTTOM_RIGHT );
OpenSeadragon.Placement.BOTTOM_RIGHT);
viewer.updateOverlay("fixed-overlay", fixedOverlayLocation,
OpenSeadragon.OverlayPlacement.BOTTOM_RIGHT );
OpenSeadragon.Placement.BOTTOM_RIGHT);
setTimeout(function() {
checkScalableOverlayPosition("with BOTTOM_RIGHT placement.");
checkFixedOverlayPosition(new OpenSeadragon.Point(-70, -60),
@ -447,11 +470,12 @@
var expPosition = viewport.viewportToViewerElementCoordinates(
new OpenSeadragon.Point(0.5, 0.6))
.apply( Math.round )
.plus(expectedOffset);
var actPosition = $("#fixed-overlay").position();
equal( actPosition.left, expPosition.x, "Fixed overlay X position mismatch " + contextMessage );
equal( actPosition.top, expPosition.y, "Fixed overlay Y position mismatch " + contextMessage );
Util.assessNumericValue(actPosition.left, expPosition.x, epsilon,
"Fixed overlay X position mismatch " + contextMessage);
Util.assessNumericValue(actPosition.top, expPosition.y, epsilon,
"Fixed overlay Y position mismatch " + contextMessage);
}
waitForViewer(function() {
@ -501,11 +525,12 @@
var expPosition = viewport.viewportToViewerElementCoordinates(
new OpenSeadragon.Point(0.5, 0.6))
.apply( Math.round )
.plus(expectedOffset);
var actPosition = $("#fixed-overlay").position();
equal( actPosition.left, expPosition.x, "Fixed overlay X position mismatch " + contextMessage );
equal( actPosition.top, expPosition.y, "Fixed overlay Y position mismatch " + contextMessage );
Util.assessNumericValue(actPosition.left, expPosition.x, epsilon,
"Fixed overlay X position mismatch " + contextMessage);
Util.assessNumericValue(actPosition.top, expPosition.y, epsilon,
"Fixed overlay Y position mismatch " + contextMessage);
}
waitForViewer(function() {
@ -563,4 +588,522 @@
});
});
// ----------
asyncTest('Overlay scaled horizontally only', function() {
viewer = OpenSeadragon({
id: 'example-overlays',
prefixUrl: '/build/openseadragon/images/',
tileSources: '/test/data/testpattern.dzi',
springStiffness: 100 // Faster animation = faster tests
});
viewer.addHandler('open', function() {
viewer.addOverlay({
id: "horizontally-scaled-overlay",
x: 0,
y: 0,
width: 1
});
var width = $("#horizontally-scaled-overlay").width();
var height = 100;
var zoom = 1.1;
$("#horizontally-scaled-overlay").get(0).style.height = height + "px";
viewer.viewport.zoomBy(zoom);
waitForViewer(function() {
var newWidth = $("#horizontally-scaled-overlay").width();
var newHeight = $("#horizontally-scaled-overlay").height();
equal(newWidth, width * zoom, "Width should be scaled.");
equal(newHeight, height, "Height should not be scaled.");
start();
});
});
});
// ----------
asyncTest('Overlay scaled vertically only', function() {
viewer = OpenSeadragon({
id: 'example-overlays',
prefixUrl: '/build/openseadragon/images/',
tileSources: '/test/data/testpattern.dzi',
springStiffness: 100 // Faster animation = faster tests
});
viewer.addHandler('open', function() {
viewer.addOverlay({
id: "vertically-scaled-overlay",
x: 0,
y: 0,
height: 1
});
var width = 100;
var height = $("#vertically-scaled-overlay").height();
var zoom = 1.1;
$("#vertically-scaled-overlay").get(0).style.width = width + "px";
viewer.viewport.zoomBy(zoom);
waitForViewer(function() {
var newWidth = $("#vertically-scaled-overlay").width();
var newHeight = $("#vertically-scaled-overlay").height();
equal(newWidth, width, "Width should not be scaled.");
equal(newHeight, height * zoom, "Height should be scaled.");
start();
});
});
});
// ----------
asyncTest('Overlay.getBounds', function() {
viewer = OpenSeadragon({
id: 'example-overlays',
prefixUrl: '/build/openseadragon/images/',
tileSources: '/test/data/testpattern.dzi',
springStiffness: 100 // Faster animation = faster tests
});
viewer.addHandler('open', function() {
viewer.addOverlay({
id: "fully-scaled-overlay",
x: 1,
y: 1,
width: 1,
height: 1,
placement: OpenSeadragon.Placement.BOTTOM_RIGHT
});
viewer.addOverlay({
id: "horizontally-scaled-overlay",
x: 0.5,
y: 0.5,
width: 1,
placement: OpenSeadragon.Placement.CENTER
});
viewer.addOverlay({
id: "vertically-scaled-overlay",
x: 0,
y: 0.5,
height: 1,
placement: OpenSeadragon.Placement.LEFT
});
viewer.addOverlay({
id: "not-scaled-overlay",
x: 1,
y: 0,
placement: OpenSeadragon.Placement.TOP_RIGHT
});
var notScaledWidth = 100;
var notScaledHeight = 100;
$("#horizontally-scaled-overlay").get(0).style.height = notScaledHeight + "px";
$("#vertically-scaled-overlay").get(0).style.width = notScaledWidth + "px";
$("#not-scaled-overlay").get(0).style.width = notScaledWidth + "px";
$("#not-scaled-overlay").get(0).style.height = notScaledHeight + "px";
var notScaledSize = viewer.viewport.deltaPointsFromPixelsNoRotate(
new OpenSeadragon.Point(notScaledWidth, notScaledHeight));
// Force refresh to takes new dimensions into account.
viewer._drawOverlays();
var actualBounds = viewer.getOverlayById("fully-scaled-overlay")
.getBounds(viewer.viewport);
var expectedBounds = new OpenSeadragon.Rect(0, 0, 1, 1);
ok(expectedBounds.equals(actualBounds),
"The fully scaled overlay should have bounds " +
expectedBounds + " but found " + actualBounds);
actualBounds = viewer.getOverlayById("horizontally-scaled-overlay")
.getBounds(viewer.viewport);
expectedBounds = new OpenSeadragon.Rect(
0, 0.5 - notScaledSize.y / 2, 1, notScaledSize.y);
ok(expectedBounds.equals(actualBounds),
"The horizontally scaled overlay should have bounds " +
expectedBounds + " but found " + actualBounds);
actualBounds = viewer.getOverlayById("vertically-scaled-overlay")
.getBounds(viewer.viewport);
expectedBounds = new OpenSeadragon.Rect(
0, 0, notScaledSize.x, 1);
ok(expectedBounds.equals(actualBounds),
"The vertically scaled overlay should have bounds " +
expectedBounds + " but found " + actualBounds);
actualBounds = viewer.getOverlayById("not-scaled-overlay")
.getBounds(viewer.viewport);
expectedBounds = new OpenSeadragon.Rect(
1 - notScaledSize.x, 0, notScaledSize.x, notScaledSize.y);
ok(expectedBounds.equals(actualBounds),
"The not scaled overlay should have bounds " +
expectedBounds + " but found " + actualBounds);
start();
});
});
// ----------
asyncTest('Fully scaled overlay rotation mode NO_ROTATION', function() {
viewer = OpenSeadragon({
id: 'example-overlays',
prefixUrl: '/build/openseadragon/images/',
tileSources: '/test/data/testpattern.dzi',
springStiffness: 100, // Faster animation = faster tests
degrees: 45,
overlays: [{
id: "fully-scaled-overlay",
x: 1,
y: 1,
width: 1,
height: 1,
placement: OpenSeadragon.Placement.BOTTOM_RIGHT,
rotationMode: OpenSeadragon.OverlayRotationMode.NO_ROTATION
}]
});
viewer.addOnceHandler('open', function() {
var viewport = viewer.viewport;
var $overlay = $("#fully-scaled-overlay");
var expectedSize = viewport.deltaPixelsFromPointsNoRotate(
new OpenSeadragon.Point(1, 1));
var expectedPosition = viewport.viewportToViewerElementCoordinates(
new OpenSeadragon.Point(1, 1))
.minus(expectedSize);
var actualPosition = $overlay.position();
Util.assessNumericValue(actualPosition.left, expectedPosition.x, epsilon,
"Scaled overlay position.x should adjust to rotation.");
Util.assessNumericValue(actualPosition.top, expectedPosition.y, epsilon,
"Scaled overlay position.y should adjust to rotation.");
var actualWidth = $overlay.width();
var actualHeight = $overlay.height();
Util.assessNumericValue(actualWidth, expectedSize.x, epsilon,
"Scaled overlay width should not adjust to rotation.");
Util.assessNumericValue(actualHeight, expectedSize.y, epsilon,
"Scaled overlay height should not adjust to rotation.");
var actualBounds = viewer.getOverlayById("fully-scaled-overlay")
.getBounds(viewport);
var expectedBounds = new OpenSeadragon.Rect(0, 0, 1, 1)
.rotate(-45, new OpenSeadragon.Point(1, 1));
ok(expectedBounds.equals(actualBounds),
"The fully scaled overlay should have bounds " +
expectedBounds + " but found " + actualBounds);
start();
});
});
// ----------
asyncTest('Horizontally scaled overlay rotation mode NO_ROTATION', function() {
viewer = OpenSeadragon({
id: 'example-overlays',
prefixUrl: '/build/openseadragon/images/',
tileSources: '/test/data/testpattern.dzi',
springStiffness: 100, // Faster animation = faster tests
degrees: 45,
overlays: [{
id: "horizontally-scaled-overlay",
x: 0.5,
y: 0.5,
width: 1,
placement: OpenSeadragon.Placement.CENTER,
rotationMode: OpenSeadragon.OverlayRotationMode.NO_ROTATION
}]
});
viewer.addOnceHandler('open', function() {
var $overlay = $("#horizontally-scaled-overlay");
var notScaledWidth = 100;
var notScaledHeight = 100;
$overlay.get(0).style.height = notScaledHeight + "px";
var viewport = viewer.viewport;
var notScaledSize = viewport.deltaPointsFromPixelsNoRotate(
new OpenSeadragon.Point(notScaledWidth, notScaledHeight));
// Force refresh to takes new dimensions into account.
viewer._drawOverlays();
var expectedWidth = viewport.deltaPixelsFromPointsNoRotate(
new OpenSeadragon.Point(1, 1)).x;
var expectedPosition = viewport.viewportToViewerElementCoordinates(
new OpenSeadragon.Point(0.5, 0.5))
.minus(new OpenSeadragon.Point(expectedWidth / 2, notScaledHeight / 2));
var actualPosition = $overlay.position();
Util.assessNumericValue(actualPosition.left, expectedPosition.x, epsilon,
"Horizontally scaled overlay position.x should adjust to rotation.");
Util.assessNumericValue(actualPosition.top, expectedPosition.y, epsilon,
"Horizontally scaled overlay position.y should adjust to rotation.");
var actualWidth = $overlay.width();
var actualHeight = $overlay.height();
Util.assessNumericValue(actualWidth, expectedWidth, epsilon,
"Horizontally scaled overlay width should not adjust to rotation.");
Util.assessNumericValue(actualHeight, notScaledHeight, epsilon,
"Horizontally scaled overlay height should not adjust to rotation.");
var actualBounds = viewer.getOverlayById("horizontally-scaled-overlay")
.getBounds(viewport);
var expectedBounds = new OpenSeadragon.Rect(
0, 0.5 - notScaledSize.y / 2, 1, notScaledSize.y)
.rotate(-45, new OpenSeadragon.Point(0.5, 0.5));
ok(expectedBounds.equals(actualBounds),
"The horizontally scaled overlay should have bounds " +
expectedBounds + " but found " + actualBounds);
start();
});
});
// ----------
asyncTest('Vertically scaled overlay rotation mode NO_ROTATION', function() {
viewer = OpenSeadragon({
id: 'example-overlays',
prefixUrl: '/build/openseadragon/images/',
tileSources: '/test/data/testpattern.dzi',
springStiffness: 100, // Faster animation = faster tests
degrees: 45,
overlays: [{
id: "vertically-scaled-overlay",
x: 0,
y: 0.5,
height: 1,
placement: OpenSeadragon.Placement.LEFT,
rotationMode: OpenSeadragon.OverlayRotationMode.NO_ROTATION
}]
});
viewer.addOnceHandler('open', function() {
var $overlay = $("#vertically-scaled-overlay");
var notScaledWidth = 100;
var notScaledHeight = 100;
$overlay.get(0).style.width = notScaledWidth + "px";
var viewport = viewer.viewport;
var notScaledSize = viewport.deltaPointsFromPixelsNoRotate(
new OpenSeadragon.Point(notScaledWidth, notScaledHeight));
// Force refresh to takes new dimensions into account.
viewer._drawOverlays();
var expectedHeight = viewport.deltaPixelsFromPointsNoRotate(
new OpenSeadragon.Point(1, 1)).y;
var expectedPosition = viewport.viewportToViewerElementCoordinates(
new OpenSeadragon.Point(0, 0.5))
.minus(new OpenSeadragon.Point(0, expectedHeight / 2));
var actualPosition = $overlay.position();
Util.assessNumericValue(actualPosition.left, expectedPosition.x, epsilon,
"Vertically scaled overlay position.x should adjust to rotation.");
Util.assessNumericValue(actualPosition.top, expectedPosition.y, epsilon,
"Vertically scaled overlay position.y should adjust to rotation.");
var actualWidth = $overlay.width();
var actualHeight = $overlay.height();
Util.assessNumericValue(actualWidth, notScaledWidth, epsilon,
"Vertically scaled overlay width should not adjust to rotation.");
Util.assessNumericValue(actualHeight, expectedHeight, epsilon,
"Vertically scaled overlay height should not adjust to rotation.");
var actualBounds = viewer.getOverlayById("vertically-scaled-overlay")
.getBounds(viewport);
var expectedBounds = new OpenSeadragon.Rect(
0, 0, notScaledSize.x, 1)
.rotate(-45, new OpenSeadragon.Point(0, 0.5));
ok(expectedBounds.equals(actualBounds),
"The vertically scaled overlay should have bounds " +
expectedBounds + " but found " + actualBounds);
start();
});
});
// ----------
asyncTest('Not scaled overlay rotation mode NO_ROTATION', function() {
viewer = OpenSeadragon({
id: 'example-overlays',
prefixUrl: '/build/openseadragon/images/',
tileSources: '/test/data/testpattern.dzi',
springStiffness: 100, // Faster animation = faster tests
degrees: 45,
overlays: [{
id: "not-scaled-overlay",
x: 1,
y: 0,
placement: OpenSeadragon.Placement.TOP_RIGHT,
rotationMode: OpenSeadragon.OverlayRotationMode.NO_ROTATION
}]
});
viewer.addOnceHandler('open', function() {
var $overlay = $("#not-scaled-overlay");
var notScaledWidth = 100;
var notScaledHeight = 100;
$overlay.get(0).style.width = notScaledWidth + "px";
$overlay.get(0).style.height = notScaledHeight + "px";
var viewport = viewer.viewport;
var notScaledSize = viewport.deltaPointsFromPixelsNoRotate(
new OpenSeadragon.Point(notScaledWidth, notScaledHeight));
// Force refresh to takes new dimensions into account.
viewer._drawOverlays();
var expectedPosition = viewport.viewportToViewerElementCoordinates(
new OpenSeadragon.Point(1, 0))
.minus(new OpenSeadragon.Point(notScaledWidth, 0));
var actualPosition = $overlay.position();
Util.assessNumericValue(actualPosition.left, expectedPosition.x, epsilon,
"Not scaled overlay position.x should adjust to rotation.");
Util.assessNumericValue(actualPosition.top, expectedPosition.y, epsilon,
"Not scaled overlay position.y should adjust to rotation.");
var actualWidth = $overlay.width();
var actualHeight = $overlay.height();
Util.assessNumericValue(actualWidth, notScaledWidth, epsilon,
"Not scaled overlay width should not adjust to rotation.");
Util.assessNumericValue(actualHeight, notScaledHeight, epsilon,
"Not scaled overlay height should not adjust to rotation.");
var actualBounds = viewer.getOverlayById("not-scaled-overlay")
.getBounds(viewport);
var expectedBounds = new OpenSeadragon.Rect(
1 - notScaledSize.x, 0, notScaledSize.x, notScaledSize.y)
.rotate(-45, new OpenSeadragon.Point(1, 0));
ok(expectedBounds.equals(actualBounds),
"Not scaled overlay should have bounds " +
expectedBounds + " but found " + actualBounds);
start();
});
});
// ----------
asyncTest('Fully scaled overlay rotation mode BOUNDING_BOX', function() {
viewer = OpenSeadragon({
id: 'example-overlays',
prefixUrl: '/build/openseadragon/images/',
tileSources: '/test/data/testpattern.dzi',
springStiffness: 100, // Faster animation = faster tests
degrees: 45,
overlays: [{
id: "fully-scaled-overlay",
x: 1,
y: 1,
width: 1,
height: 1,
placement: OpenSeadragon.Placement.BOTTOM_RIGHT,
rotationMode: OpenSeadragon.OverlayRotationMode.BOUNDING_BOX
}]
});
viewer.addOnceHandler('open', function() {
var viewport = viewer.viewport;
var $overlay = $("#fully-scaled-overlay");
var expectedRect = viewport.viewportToViewerElementRectangle(
new OpenSeadragon.Rect(0, 0, 1, 1)).getBoundingBox();
var actualPosition = $overlay.position();
Util.assessNumericValue(actualPosition.left, expectedRect.x, epsilon,
"Scaled overlay position.x should adjust to rotation.");
Util.assessNumericValue(actualPosition.top, expectedRect.y, epsilon,
"Scaled overlay position.y should adjust to rotation.");
var actualWidth = $overlay.width();
var actualHeight = $overlay.height();
Util.assessNumericValue(actualWidth, expectedRect.width, epsilon,
"Scaled overlay width should not adjust to rotation.");
Util.assessNumericValue(actualHeight, expectedRect.height, epsilon,
"Scaled overlay height should not adjust to rotation.");
var actualBounds = viewer.getOverlayById("fully-scaled-overlay")
.getBounds(viewport);
var expectedBounds = new OpenSeadragon.Rect(
0.5, -0.5, Math.sqrt(2), Math.sqrt(2), 45);
var boundsEpsilon = 0.000001;
Util.assessNumericValue(actualBounds.x, expectedBounds.x, boundsEpsilon,
"The fully scaled overlay should have adjusted bounds.x");
Util.assessNumericValue(actualBounds.y, expectedBounds.y, boundsEpsilon,
"The fully scaled overlay should have adjusted bounds.y");
Util.assessNumericValue(actualBounds.width, expectedBounds.width, boundsEpsilon,
"The fully scaled overlay should have adjusted bounds.width");
Util.assessNumericValue(actualBounds.height, expectedBounds.height, boundsEpsilon,
"The fully scaled overlay should have adjusted bounds.height");
Util.assessNumericValue(actualBounds.degrees, expectedBounds.degrees, boundsEpsilon,
"The fully scaled overlay should have adjusted bounds.degrees");
start();
});
});
// ----------
asyncTest('Fully scaled overlay rotation mode EXACT', function() {
viewer = OpenSeadragon({
id: 'example-overlays',
prefixUrl: '/build/openseadragon/images/',
tileSources: '/test/data/testpattern.dzi',
springStiffness: 100, // Faster animation = faster tests
degrees: 45,
overlays: [{
id: "fully-scaled-overlay",
x: 1,
y: 1,
width: 1,
height: 1,
placement: OpenSeadragon.Placement.BOTTOM_RIGHT,
rotationMode: OpenSeadragon.OverlayRotationMode.EXACT
}]
});
viewer.addOnceHandler('open', function() {
var viewport = viewer.viewport;
var $overlay = $("#fully-scaled-overlay");
var expectedSize = viewport.deltaPixelsFromPointsNoRotate(
new OpenSeadragon.Point(1, 1));
var expectedPosition = viewport.pixelFromPoint(
new OpenSeadragon.Point(1, 1))
.minus(expectedSize);
// We can't rely on jQuery.position with transforms.
var actualStyle = $overlay.get(0).style;
var left = Number(actualStyle.left.replace("px", ""));
var top = Number(actualStyle.top.replace("px", ""));
Util.assessNumericValue(left, expectedPosition.x, epsilon,
"Scaled overlay position.x should adjust to rotation.");
Util.assessNumericValue(top, expectedPosition.y, epsilon,
"Scaled overlay position.y should adjust to rotation.");
var actualWidth = $overlay.width();
var actualHeight = $overlay.height();
Util.assessNumericValue(actualWidth, expectedSize.x, epsilon,
"Scaled overlay width should not adjust to rotation.");
Util.assessNumericValue(actualHeight, expectedSize.y, epsilon,
"Scaled overlay height should not adjust to rotation.");
var transformOriginProp = OpenSeadragon.getCssPropertyWithVendorPrefix(
'transformOrigin');
var transformProp = OpenSeadragon.getCssPropertyWithVendorPrefix(
'transform');
var transformOrigin = actualStyle[transformOriginProp];
// Some browsers replace "right bottom" by "100% 100%"
ok(transformOrigin.match(/(100% 100%)|(right bottom)/),
"Transform origin should be right bottom. Got: " + transformOrigin);
equal(actualStyle[transformProp], "rotate(45deg)",
"Transform should be rotate(45deg).");
var actualBounds = viewer.getOverlayById("fully-scaled-overlay")
.getBounds(viewport);
var expectedBounds = new OpenSeadragon.Rect(0, 0, 1, 1);
ok(expectedBounds.equals(actualBounds),
"The fully scaled overlay should have bounds " +
expectedBounds + " but found " + actualBounds);
start();
});
});
})();

View File

@ -161,6 +161,65 @@
"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);
@ -218,4 +277,27 @@
"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

@ -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.viewport
.viewportToViewerElementRectangle(homeBounds);
var precision = 0.00000001;
Util.assertRectangleEquals(rect, canvasClip, precision,
'clipping should be ' + canvasClip);
start();
});
});
@ -220,6 +222,39 @@
});
});
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 +292,138 @@
});
});
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
}]);
});
})();

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 () {
@ -218,7 +219,27 @@
});
});
asyncTest('getHomeBoundsWithRotation', function() {
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;
@ -226,10 +247,11 @@
Util.assertRectangleEquals(
viewport.getHomeBounds(),
new OpenSeadragon.Rect(
(1 - Math.sqrt(2)) / 2,
(1 - Math.sqrt(2)) / 2,
0.5,
-0.5,
Math.sqrt(2),
Math.sqrt(2)),
Math.sqrt(2),
45),
0.00000001,
"Test getHomeBounds with degrees = -675");
start();
@ -238,6 +260,57 @@
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',
@ -305,7 +378,7 @@
var rect = testRects[i].times(viewport.getContainerSize());
viewport.resetContentSize(rect.getSize());
propEqual(
viewport.contentSize,
viewport._contentSize,
rect.getSize(),
"Reset content size correctly."
);
@ -354,17 +427,135 @@
viewer.open(DZI_PATH);
});
asyncTest('applyConstraints', function() {
var openHandler = function() {
viewer.removeHandler('open', openHandler);
var viewport = viewer.viewport;
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.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)
];
asyncTest('fitBounds', function(){
var openHandler = function(event) {
viewer.removeHandler('open', openHandler);
var viewport = viewer.viewport;
for(var i = 0; i < testRects.length; i++){
var rect = testRects[i].times(viewport.getContainerSize());
for(var i = 0; i < testRectsFitBounds.length; i++){
var rect = testRectsFitBounds[i];
viewport.fitBounds(rect, true);
propEqual(
viewport.getBounds(),
rect,
expectedRectsFitBounds[i],
"Fit bounds correctly."
);
}
@ -374,19 +565,27 @@
viewer.open(DZI_PATH);
});
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)
];
asyncTest('fitBounds with viewport rotation', function(){
var openHandler = function(event) {
viewer.removeHandler('open', openHandler);
var viewport = viewer.viewport;
viewport.setRotation(45);
var expectedRectsFitBounds = [
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)
];
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) {
@ -399,7 +598,7 @@
viewport.fitBoundsWithConstraints(rect, true);
propEqual(
viewport.getBounds(),
expectedRectsFitBounds[i],
expectedRectsFitBoundsWithConstraints[i],
"Fit bounds correctly."
);
}
@ -409,6 +608,44 @@
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);
@ -440,6 +677,7 @@
viewer.addHandler('open', openHandler);
viewer.open(WIDE_PATH);
});
// End fitBounds tests.
asyncTest('panBy', function(){
var openHandler = function(event) {

View File

@ -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. -->