mirror of
https://github.com/openseadragon/openseadragon.git
synced 2024-11-21 20:56:09 +03:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
96a4de7b87
@ -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",
|
||||
|
@ -1,12 +1,18 @@
|
||||
OPENSEADRAGON CHANGELOG
|
||||
=======================
|
||||
|
||||
2.1.1: (in progress)
|
||||
2.2.0: (in progress)
|
||||
|
||||
* BREAKING CHANGE: Viewport.homeBounds, Viewport.contentSize, Viewport.contentAspectX and
|
||||
Viewport.contentAspectY have been removed.
|
||||
* DEPRECATION: Viewport.setHomeBounds has been deprecated
|
||||
* DEPRECATION: the Viewport constructor is now ignoring the contentSize option
|
||||
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)
|
||||
@ -25,6 +31,22 @@ Viewport.contentAspectY have been removed.
|
||||
* 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)
|
||||
|
||||
2.1.0:
|
||||
|
||||
|
@ -259,13 +259,17 @@ $.Drawer.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
_clear: function ( useSketch ) {
|
||||
if ( !this.useCanvas ) {
|
||||
_clear: function (useSketch, bounds) {
|
||||
if (!this.useCanvas) {
|
||||
return;
|
||||
}
|
||||
var context = this._getContext( useSketch );
|
||||
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 );
|
||||
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();
|
||||
},
|
||||
|
||||
|
@ -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" ) );
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -62,7 +62,7 @@ $.Navigator = function( options ){
|
||||
options.controlOptions = {
|
||||
anchor: $.ControlAnchor.TOP_RIGHT,
|
||||
attachToViewer: true,
|
||||
autoFade: true
|
||||
autoFade: options.autoFade
|
||||
};
|
||||
|
||||
if( options.position ){
|
||||
@ -306,8 +306,8 @@ $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /*
|
||||
this.updateSize();
|
||||
}
|
||||
|
||||
if( viewport && this.viewport ) {
|
||||
bounds = viewport.getBounds( true );
|
||||
if (viewport && this.viewport) {
|
||||
bounds = viewport.getBoundsNoRotate(true);
|
||||
topleft = this.viewport.pixelFromPointNoRotate(bounds.getTopLeft(), false);
|
||||
bottomright = this.viewport.pixelFromPointNoRotate(bounds.getBottomRight(), false)
|
||||
.minus( this.totalBorderWidths );
|
||||
|
@ -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.
|
||||
@ -373,16 +354,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 +386,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,8 +676,6 @@
|
||||
* This function serves as a single point of instantiation for an {@link OpenSeadragon.Viewer}, including all
|
||||
* combinations of out-of-the-box configurable features.
|
||||
*
|
||||
* @function OpenSeadragon
|
||||
* @memberof module:OpenSeadragon
|
||||
* @param {OpenSeadragon.Options} options - Viewer options.
|
||||
* @returns {OpenSeadragon.Viewer}
|
||||
*/
|
||||
@ -842,6 +825,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
|
||||
@ -1065,6 +1063,7 @@ if (typeof define === 'function' && define.amd) {
|
||||
navigatorHeight: null,
|
||||
navigatorWidth: null,
|
||||
navigatorAutoResize: true,
|
||||
navigatorAutoFade: true,
|
||||
navigatorRotate: true,
|
||||
|
||||
// INITIAL ROTATION
|
||||
@ -1322,6 +1321,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).
|
||||
@ -2533,185 +2575,4 @@ 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 ));
|
||||
}(OpenSeadragon));
|
||||
|
478
src/overlay.js
478
src/overlay.js
@ -32,14 +32,17 @@
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
(function( $ ){
|
||||
(function($) {
|
||||
|
||||
/**
|
||||
* An enumeration of positions that an overlay may be assigned relative to
|
||||
* the viewport.
|
||||
* It is identical to OpenSeadragon.Placement but is kept for backward
|
||||
* compatibility.
|
||||
* @member OverlayPlacement
|
||||
* @memberof OpenSeadragon
|
||||
* @static
|
||||
* @readonly
|
||||
* @type {Object}
|
||||
* @property {Number} CENTER
|
||||
* @property {Number} TOP_LEFT
|
||||
@ -51,17 +54,26 @@
|
||||
* @property {Number} BOTTOM_LEFT
|
||||
* @property {Number} LEFT
|
||||
*/
|
||||
$.OverlayPlacement = {
|
||||
CENTER: 0,
|
||||
TOP_LEFT: 1,
|
||||
TOP: 2,
|
||||
TOP_RIGHT: 3,
|
||||
RIGHT: 4,
|
||||
BOTTOM_RIGHT: 5,
|
||||
BOTTOM: 6,
|
||||
BOTTOM_LEFT: 7,
|
||||
LEFT: 8
|
||||
};
|
||||
$.OverlayPlacement = $.Placement;
|
||||
|
||||
/**
|
||||
* An enumeration of possible ways to handle overlays rotation
|
||||
* @member OverlayRotationMode
|
||||
* @memberOf OpenSeadragon
|
||||
* @static
|
||||
* @readonly
|
||||
* @property {Number} NO_ROTATION The overlay ignore the viewport rotation.
|
||||
* @property {Number} EXACT The overlay use CSS 3 transforms to rotate with
|
||||
* the viewport. If the overlay contains text, it will get rotated as well.
|
||||
* @property {Number} BOUNDING_BOX The overlay adjusts for rotation by
|
||||
* taking the size of the bounding box of the rotated bounds.
|
||||
* Only valid for overlays with Rect location and scalable in both directions.
|
||||
*/
|
||||
$.OverlayRotationMode = $.freezeObject({
|
||||
NO_ROTATION: 1,
|
||||
EXACT: 2,
|
||||
BOUNDING_BOX: 3
|
||||
});
|
||||
|
||||
/**
|
||||
* @class Overlay
|
||||
@ -72,19 +84,27 @@
|
||||
* @param {Element} options.element
|
||||
* @param {OpenSeadragon.Point|OpenSeadragon.Rect} options.location - The
|
||||
* location of the overlay on the image. If a {@link OpenSeadragon.Point}
|
||||
* is specified, the overlay will keep a constant size independently of the
|
||||
* zoom. If a {@link OpenSeadragon.Rect} is specified, the overlay size will
|
||||
* be adjusted when the zoom changes.
|
||||
* @param {OpenSeadragon.OverlayPlacement} [options.placement=OpenSeadragon.OverlayPlacement.TOP_LEFT]
|
||||
* Relative position to the viewport.
|
||||
* Only used if location is a {@link OpenSeadragon.Point}.
|
||||
* is specified, the overlay will be located at this location with respect
|
||||
* to the placement option. If a {@link OpenSeadragon.Rect} is specified,
|
||||
* the overlay will be placed at this location with the corresponding width
|
||||
* and height and placement TOP_LEFT.
|
||||
* @param {OpenSeadragon.Placement} [options.placement=OpenSeadragon.Placement.TOP_LEFT]
|
||||
* Defines what part of the overlay should be at the specified options.location
|
||||
* @param {OpenSeadragon.Overlay.OnDrawCallback} [options.onDraw]
|
||||
* @param {Boolean} [options.checkResize=true] Set to false to avoid to
|
||||
* check the size of the overlay everytime it is drawn when using a
|
||||
* {@link OpenSeadragon.Point} as options.location. It will improve
|
||||
* performances but will cause a misalignment if the overlay size changes.
|
||||
* check the size of the overlay everytime it is drawn in the directions
|
||||
* which are not scaled. It will improve performances but will cause a
|
||||
* misalignment if the overlay size changes.
|
||||
* @param {Number} [options.width] The width of the overlay in viewport
|
||||
* coordinates. If specified, the width of the overlay will be adjusted when
|
||||
* the zoom changes.
|
||||
* @param {Number} [options.height] The height of the overlay in viewport
|
||||
* coordinates. If specified, the height of the overlay will be adjusted when
|
||||
* the zoom changes.
|
||||
* @param {Boolean} [options.rotationMode=OpenSeadragon.OverlayRotationMode.EXACT]
|
||||
* How to handle the rotation of the viewport.
|
||||
*/
|
||||
$.Overlay = function( element, location, placement ) {
|
||||
$.Overlay = function(element, location, placement) {
|
||||
|
||||
/**
|
||||
* onDraw callback signature used by {@link OpenSeadragon.Overlay}.
|
||||
@ -97,7 +117,7 @@
|
||||
*/
|
||||
|
||||
var options;
|
||||
if ( $.isPlainObject( element ) ) {
|
||||
if ($.isPlainObject(element)) {
|
||||
options = element;
|
||||
} else {
|
||||
options = {
|
||||
@ -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:
|
||||
adjust: function(position, size) {
|
||||
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,20 +195,20 @@
|
||||
* @function
|
||||
*/
|
||||
destroy: function() {
|
||||
var element = this.element,
|
||||
style = this.style;
|
||||
var element = this.element;
|
||||
var style = this.style;
|
||||
|
||||
if ( element.parentNode ) {
|
||||
element.parentNode.removeChild( element );
|
||||
if (element.parentNode) {
|
||||
element.parentNode.removeChild(element);
|
||||
//this should allow us to preserve overlays when required between
|
||||
//pages
|
||||
if ( element.prevElementParent ) {
|
||||
if (element.prevElementParent) {
|
||||
style.display = 'none';
|
||||
//element.prevElementParent.insertBefore(
|
||||
// element,
|
||||
// element.prevNextSibling
|
||||
//);
|
||||
document.body.appendChild( element );
|
||||
document.body.appendChild(element);
|
||||
}
|
||||
}
|
||||
|
||||
@ -205,122 +219,258 @@
|
||||
style.left = "";
|
||||
style.position = "";
|
||||
|
||||
if ( this.scales ) {
|
||||
if (this.width !== null) {
|
||||
style.width = "";
|
||||
}
|
||||
if (this.height !== null) {
|
||||
style.height = "";
|
||||
}
|
||||
var transformOriginProp = $.getCssPropertyWithVendorPrefix(
|
||||
'transformOrigin');
|
||||
var transformProp = $.getCssPropertyWithVendorPrefix(
|
||||
'transform');
|
||||
if (transformOriginProp && transformProp) {
|
||||
style[transformOriginProp] = "";
|
||||
style[transformProp] = "";
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @function
|
||||
* @param {Element} container
|
||||
*/
|
||||
drawHTML: function( container, viewport ) {
|
||||
var element = this.element,
|
||||
style = this.style,
|
||||
scales = this.scales,
|
||||
degrees = viewport.degrees,
|
||||
position = viewport.pixelFromPoint(
|
||||
this.bounds.getTopLeft(),
|
||||
true
|
||||
),
|
||||
size,
|
||||
overlayCenter;
|
||||
|
||||
if ( element.parentNode != container ) {
|
||||
drawHTML: function(container, viewport) {
|
||||
var element = this.element;
|
||||
if (element.parentNode !== container) {
|
||||
//save the source parent for later if we need it
|
||||
element.prevElementParent = element.parentNode;
|
||||
element.prevNextSibling = element.nextSibling;
|
||||
container.appendChild( element );
|
||||
this.size = $.getElementSize( element );
|
||||
container.appendChild(element);
|
||||
|
||||
// this.size is used by overlays which don't get scaled in at
|
||||
// least one direction when this.checkResize is set to false.
|
||||
this.size = $.getElementSize(element);
|
||||
}
|
||||
|
||||
if ( scales ) {
|
||||
size = viewport.deltaPixelsFromPoints(
|
||||
this.bounds.getSize(),
|
||||
true
|
||||
);
|
||||
} else if ( this.checkResize ) {
|
||||
size = $.getElementSize( element );
|
||||
} else {
|
||||
size = this.size;
|
||||
}
|
||||
var positionAndSize = this._getOverlayPositionAndSize(viewport);
|
||||
|
||||
this.position = position;
|
||||
this.size = size;
|
||||
|
||||
this.adjust( position, size );
|
||||
|
||||
position = position.apply( Math.round );
|
||||
size = size.apply( Math.round );
|
||||
|
||||
// rotate the position of the overlay
|
||||
// TODO only rotate overlays if in canvas mode
|
||||
// TODO replace the size rotation with CSS3 transforms
|
||||
// TODO add an option to overlays to not rotate with the image
|
||||
// Currently only rotates position and size
|
||||
if( degrees !== 0 && this.scales ) {
|
||||
overlayCenter = new $.Point( size.x / 2, size.y / 2 );
|
||||
|
||||
var drawerCenter = new $.Point(
|
||||
viewport.viewer.drawer.canvas.width / 2,
|
||||
viewport.viewer.drawer.canvas.height / 2
|
||||
);
|
||||
position = position.plus( overlayCenter ).rotate(
|
||||
degrees,
|
||||
drawerCenter
|
||||
).minus( overlayCenter );
|
||||
|
||||
size = size.rotate( degrees, new $.Point( 0, 0 ) );
|
||||
size = new $.Point( Math.abs( size.x ), Math.abs( size.y ) );
|
||||
}
|
||||
var position = positionAndSize.position;
|
||||
var size = this.size = positionAndSize.size;
|
||||
var rotate = positionAndSize.rotate;
|
||||
|
||||
// call the onDraw callback if it exists to allow one to overwrite
|
||||
// the drawing/positioning/sizing of the overlay
|
||||
if ( this.onDraw ) {
|
||||
this.onDraw( position, size, element );
|
||||
if (this.onDraw) {
|
||||
this.onDraw(position, size, this.element);
|
||||
} else {
|
||||
var style = this.style;
|
||||
style.left = position.x + "px";
|
||||
style.top = position.y + "px";
|
||||
style.position = "absolute";
|
||||
|
||||
if (style.display != 'none') {
|
||||
style.display = 'block';
|
||||
}
|
||||
|
||||
if ( scales ) {
|
||||
if (this.width !== null) {
|
||||
style.width = size.x + "px";
|
||||
}
|
||||
if (this.height !== null) {
|
||||
style.height = size.y + "px";
|
||||
}
|
||||
var transformOriginProp = $.getCssPropertyWithVendorPrefix(
|
||||
'transformOrigin');
|
||||
var transformProp = $.getCssPropertyWithVendorPrefix(
|
||||
'transform');
|
||||
if (transformOriginProp && transformProp) {
|
||||
if (rotate) {
|
||||
style[transformOriginProp] = this._getTransformOrigin();
|
||||
style[transformProp] = "rotate(" + rotate + "deg)";
|
||||
} else {
|
||||
style[transformOriginProp] = "";
|
||||
style[transformProp] = "";
|
||||
}
|
||||
}
|
||||
style.position = "absolute";
|
||||
|
||||
if (style.display !== 'none') {
|
||||
style.display = 'block';
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @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() {
|
||||
return this.bounds.clone();
|
||||
getBounds: function(viewport) {
|
||||
$.console.assert(viewport,
|
||||
'A viewport must now be passed to Overlay.getBounds.');
|
||||
var width = this.width;
|
||||
var height = this.height;
|
||||
if (width === null || height === null) {
|
||||
var size = viewport.deltaPointsFromPixelsNoRotate(this.size, true);
|
||||
if (width === null) {
|
||||
width = size.x;
|
||||
}
|
||||
if (height === null) {
|
||||
height = size.y;
|
||||
}
|
||||
}
|
||||
var location = this.location.clone();
|
||||
this.adjust(location, new $.Point(width, height));
|
||||
return this._adjustBoundsForRotation(
|
||||
viewport, new $.Rect(location.x, location.y, width, height));
|
||||
},
|
||||
|
||||
// private
|
||||
_adjustBoundsForRotation: function(viewport, bounds) {
|
||||
if (!viewport ||
|
||||
viewport.degrees === 0 ||
|
||||
this.rotationMode === $.OverlayRotationMode.EXACT) {
|
||||
return bounds;
|
||||
}
|
||||
if (this.rotationMode === $.OverlayRotationMode.BOUNDING_BOX) {
|
||||
// If overlay not fully scalable, BOUNDING_BOX falls back to EXACT
|
||||
if (this.width === null || this.height === null) {
|
||||
return bounds;
|
||||
}
|
||||
// It is easier to just compute the position and size and
|
||||
// convert to viewport coordinates.
|
||||
var positionAndSize = this._getOverlayPositionAndSize(viewport);
|
||||
return viewport.viewerElementToViewportRectangle(new $.Rect(
|
||||
positionAndSize.position.x,
|
||||
positionAndSize.position.y,
|
||||
positionAndSize.size.x,
|
||||
positionAndSize.size.y));
|
||||
}
|
||||
|
||||
// NO_ROTATION case
|
||||
return bounds.rotate(-viewport.degrees,
|
||||
this._getPlacementPoint(bounds));
|
||||
}
|
||||
};
|
||||
|
||||
}( OpenSeadragon ));
|
||||
}(OpenSeadragon));
|
||||
|
138
src/placement.js
Normal file
138
src/placement.js
Normal 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));
|
206
src/rectangle.js
206
src/rectangle.js
@ -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.
|
||||
@ -348,10 +546,10 @@ $.Rect.prototype = {
|
||||
*/
|
||||
toString: function() {
|
||||
return "[" +
|
||||
(Math.round(this.x * 100) / 100) + "," +
|
||||
(Math.round(this.y * 100) / 100) + "," +
|
||||
(Math.round(this.x * 100) / 100) + ", " +
|
||||
(Math.round(this.y * 100) / 100) + ", " +
|
||||
(Math.round(this.width * 100) / 100) + "x" +
|
||||
(Math.round(this.height * 100) / 100) + "," +
|
||||
(Math.round(this.height * 100) / 100) + ", " +
|
||||
(Math.round(this.degrees * 100) / 100) + "deg" +
|
||||
"]";
|
||||
}
|
||||
|
@ -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 } );
|
||||
}
|
||||
},
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
56
src/tile.js
56
src/tile.js
@ -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);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -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.
|
||||
@ -122,6 +126,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
|
||||
@ -172,6 +181,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) {
|
||||
/**
|
||||
@ -274,6 +287,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.
|
||||
*/
|
||||
@ -543,6 +576,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.
|
||||
@ -650,6 +744,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;
|
||||
}
|
||||
});
|
||||
|
||||
@ -688,7 +787,6 @@ function updateViewport( tiledImage ) {
|
||||
Math.log( 2 )
|
||||
))
|
||||
),
|
||||
degrees = tiledImage.viewport.degrees,
|
||||
renderPixelRatioC,
|
||||
renderPixelRatioT,
|
||||
zeroRatioT,
|
||||
@ -696,26 +794,23 @@ function updateViewport( tiledImage ) {
|
||||
levelOpacity,
|
||||
levelVisibility;
|
||||
|
||||
viewportBounds.x -= tiledImage._xSpring.current.value;
|
||||
viewportBounds.y -= tiledImage._ySpring.current.value;
|
||||
|
||||
// Reset tile's internal drawn state
|
||||
while ( tiledImage.lastDrawn.length > 0 ) {
|
||||
while (tiledImage.lastDrawn.length > 0) {
|
||||
tile = tiledImage.lastDrawn.pop();
|
||||
tile.beingDrawn = false;
|
||||
}
|
||||
|
||||
//Change bounds for rotation
|
||||
if (degrees === 90 || degrees === 270) {
|
||||
viewportBounds = viewportBounds.rotate( degrees );
|
||||
} else if (degrees !== 0 && degrees !== 180) {
|
||||
// This is just an approximation.
|
||||
var orthBounds = viewportBounds.rotate(90);
|
||||
viewportBounds.x -= orthBounds.width / 2;
|
||||
viewportBounds.y -= orthBounds.height / 2;
|
||||
viewportBounds.width += orthBounds.width;
|
||||
viewportBounds.height += orthBounds.height;
|
||||
if (!tiledImage.wrapHorizontal && !tiledImage.wrapVertical) {
|
||||
var tiledImageBounds = tiledImage.getClippedBounds(true);
|
||||
var intersection = viewportBounds.intersection(tiledImageBounds);
|
||||
if (intersection === null) {
|
||||
return;
|
||||
}
|
||||
viewportBounds = intersection;
|
||||
}
|
||||
viewportBounds = viewportBounds.getBoundingBox();
|
||||
viewportBounds.x -= tiledImage._xSpring.current.value;
|
||||
viewportBounds.y -= tiledImage._ySpring.current.value;
|
||||
|
||||
var viewportTL = viewportBounds.getTopLeft();
|
||||
var viewportBR = viewportBounds.getBottomRight();
|
||||
@ -1324,22 +1419,22 @@ 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) {
|
||||
// When zoomed in a lot (>100%) the tile edges are visible.
|
||||
// So we have to composite them at ~100% and scale them up together.
|
||||
useSketch = true;
|
||||
@ -1349,8 +1444,17 @@ function drawTiles( tiledImage, lastDrawn ) {
|
||||
tiledImage._drawer.getCanvasSize(true));
|
||||
}
|
||||
|
||||
if ( useSketch ) {
|
||||
tiledImage._drawer._clear( true );
|
||||
var bounds;
|
||||
if (useSketch) {
|
||||
if (!sketchScale) {
|
||||
// Except when edge smoothing, we only clean the part of the
|
||||
// sketch canvas we are going to use for performance reasons.
|
||||
bounds = tiledImage.viewport.viewportToViewerElementRectangle(
|
||||
tiledImage.getClippedBounds(true))
|
||||
.getIntegerBoundingBox()
|
||||
.times($.pixelDensityRatio);
|
||||
}
|
||||
tiledImage._drawer._clear(true, bounds);
|
||||
}
|
||||
|
||||
// When scaling, we must rotate only when blending the sketch canvas to avoid
|
||||
@ -1396,7 +1500,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;
|
||||
@ -1433,7 +1537,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);
|
||||
}
|
||||
|
@ -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
|
||||
|
195
src/viewer.js
195
src/viewer.js
@ -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";
|
||||
@ -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,
|
||||
@ -911,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
|
||||
@ -974,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;
|
||||
@ -1204,6 +1220,10 @@ $.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.
|
||||
@ -1339,6 +1359,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,
|
||||
@ -1780,10 +1802,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
|
||||
@ -1825,7 +1849,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', {
|
||||
@ -1843,8 +1867,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.
|
||||
@ -1870,7 +1894,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', {
|
||||
@ -2191,37 +2215,28 @@ function getOverlayObject( viewer, overlay ) {
|
||||
}
|
||||
|
||||
var location = overlay.location;
|
||||
if ( !location ) {
|
||||
if ( overlay.width && overlay.height ) {
|
||||
location = overlay.px !== undefined ?
|
||||
viewer.viewport.imageToViewportRectangle( new $.Rect(
|
||||
var width = overlay.width;
|
||||
var height = overlay.height;
|
||||
if (!location) {
|
||||
var x = overlay.x;
|
||||
var y = overlay.y;
|
||||
if (overlay.px !== undefined) {
|
||||
var rect = viewer.viewport.imageToViewportRectangle(new $.Rect(
|
||||
overlay.px,
|
||||
overlay.py,
|
||||
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({
|
||||
@ -2229,7 +2244,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
|
||||
});
|
||||
}
|
||||
|
||||
@ -2542,22 +2560,25 @@ function onCanvasDrag( event ) {
|
||||
}
|
||||
|
||||
function onCanvasDragEnd( event ) {
|
||||
var gestureSettings;
|
||||
|
||||
if ( !event.preventDefaultAction && this.viewport ) {
|
||||
gestureSettings = this.gestureSettingsByDeviceType( event.pointerType );
|
||||
if ( gestureSettings.flickEnabled && event.speed >= gestureSettings.flickMinSpeed ) {
|
||||
var amplitudeX = gestureSettings.flickMomentum * ( event.speed * Math.cos( event.direction - (Math.PI / 180 * this.viewport.degrees) ) ),
|
||||
amplitudeY = gestureSettings.flickMomentum * ( event.speed * Math.sin( event.direction - (Math.PI / 180 * this.viewport.degrees) ) ),
|
||||
center = this.viewport.pixelFromPoint( this.viewport.getCenter( true ) ),
|
||||
target = this.viewport.pointFromPixel( new $.Point( center.x - amplitudeX, center.y - amplitudeY ) );
|
||||
if( !this.panHorizontal ) {
|
||||
target.x = center.x;
|
||||
if (!event.preventDefaultAction && this.viewport) {
|
||||
var gestureSettings = this.gestureSettingsByDeviceType(event.pointerType);
|
||||
if (gestureSettings.flickEnabled &&
|
||||
event.speed >= gestureSettings.flickMinSpeed) {
|
||||
var amplitudeX = 0;
|
||||
if (this.panHorizontal) {
|
||||
amplitudeX = gestureSettings.flickMomentum * event.speed *
|
||||
Math.cos(event.direction);
|
||||
}
|
||||
if( !this.panVertical ) {
|
||||
target.y = center.y;
|
||||
var amplitudeY = 0;
|
||||
if (this.panVertical) {
|
||||
amplitudeY = gestureSettings.flickMomentum * event.speed *
|
||||
Math.sin(event.direction);
|
||||
}
|
||||
this.viewport.panTo( target, false );
|
||||
var center = this.viewport.pixelFromPoint(
|
||||
this.viewport.getCenter(true));
|
||||
var target = this.viewport.pointFromPixel(
|
||||
new $.Point(center.x - amplitudeX, center.y - amplitudeY));
|
||||
this.viewport.panTo(target, false);
|
||||
}
|
||||
this.viewport.applyConstraints();
|
||||
}
|
||||
@ -2576,7 +2597,7 @@ function onCanvasDragEnd( event ) {
|
||||
* @property {Object} originalEvent - The original DOM event.
|
||||
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
||||
*/
|
||||
this.raiseEvent( 'canvas-drag-end', {
|
||||
this.raiseEvent('canvas-drag-end', {
|
||||
tracker: event.eventSource,
|
||||
position: event.position,
|
||||
speed: event.speed,
|
||||
@ -2963,33 +2984,26 @@ function updateOnce( viewer ) {
|
||||
return;
|
||||
}
|
||||
|
||||
var containerSize;
|
||||
if ( viewer.autoResize ) {
|
||||
containerSize = _getSafeElemSize( viewer.container );
|
||||
if ( !containerSize.equals( THIS[ viewer.hash ].prevContainerSize ) ) {
|
||||
if ( viewer.preserveImageSizeOnResize ) {
|
||||
var prevContainerSize = THIS[ viewer.hash ].prevContainerSize;
|
||||
var bounds = viewer.viewport.getBounds(true);
|
||||
var deltaX = (containerSize.x - prevContainerSize.x);
|
||||
var deltaY = (containerSize.y - prevContainerSize.y);
|
||||
var viewportDiff = viewer.viewport.deltaPointsFromPixels(new OpenSeadragon.Point(deltaX, deltaY), true);
|
||||
viewer.viewport.resize(new OpenSeadragon.Point(containerSize.x, containerSize.y), false);
|
||||
|
||||
// Keep the center of the image in the center and just adjust the amount of image shown
|
||||
bounds.width += viewportDiff.x;
|
||||
bounds.height += viewportDiff.y;
|
||||
bounds.x -= (viewportDiff.x / 2);
|
||||
bounds.y -= (viewportDiff.y / 2);
|
||||
viewer.viewport.fitBoundsWithConstraints(bounds, true);
|
||||
}
|
||||
else {
|
||||
if (viewer.autoResize) {
|
||||
var containerSize = _getSafeElemSize(viewer.container);
|
||||
var prevContainerSize = THIS[viewer.hash].prevContainerSize;
|
||||
if (!containerSize.equals(prevContainerSize)) {
|
||||
var viewport = viewer.viewport;
|
||||
if (viewer.preserveImageSizeOnResize) {
|
||||
var resizeRatio = prevContainerSize.x / containerSize.x;
|
||||
var zoom = viewport.getZoom() * resizeRatio;
|
||||
var center = viewport.getCenter();
|
||||
viewport.resize(containerSize, false);
|
||||
viewport.zoomTo(zoom, null, true);
|
||||
viewport.panTo(center, true);
|
||||
} else {
|
||||
// maintain image position
|
||||
var oldBounds = viewer.viewport.getBounds();
|
||||
var oldCenter = viewer.viewport.getCenter();
|
||||
resizeViewportAndRecenter(viewer, containerSize, oldBounds, oldCenter);
|
||||
var oldBounds = viewport.getBounds();
|
||||
viewport.resize(containerSize, true);
|
||||
viewport.fitBoundsWithConstraints(oldBounds, true);
|
||||
}
|
||||
THIS[ viewer.hash ].prevContainerSize = containerSize;
|
||||
THIS[ viewer.hash ].forceRedraw = true;
|
||||
THIS[viewer.hash].prevContainerSize = containerSize;
|
||||
THIS[viewer.hash].forceRedraw = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -3074,27 +3088,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();
|
||||
|
401
src/viewport.js
401
src/viewport.js
@ -237,6 +237,17 @@ $.Viewport.prototype = {
|
||||
* @returns {OpenSeadragon.Rect} The home bounds in vewport coordinates.
|
||||
*/
|
||||
getHomeBounds: function() {
|
||||
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();
|
||||
@ -254,8 +265,8 @@ $.Viewport.prototype = {
|
||||
* @param {Boolean} immediately
|
||||
* @fires OpenSeadragon.Viewer.event:home
|
||||
*/
|
||||
goHome: function( immediately ) {
|
||||
if( this.viewer ){
|
||||
goHome: function(immediately) {
|
||||
if (this.viewer) {
|
||||
/**
|
||||
* Raised when the "home" operation occurs (see {@link OpenSeadragon.Viewport#goHome}).
|
||||
*
|
||||
@ -266,11 +277,11 @@ $.Viewport.prototype = {
|
||||
* @property {Boolean} immediately
|
||||
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
||||
*/
|
||||
this.viewer.raiseEvent( 'home', {
|
||||
this.viewer.raiseEvent('home', {
|
||||
immediately: immediately
|
||||
});
|
||||
}
|
||||
return this.fitBounds( this.getHomeBounds(), immediately );
|
||||
return this.fitBounds(this.getHomeBounds(), immediately);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -317,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() {
|
||||
@ -326,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) {
|
||||
@ -341,7 +352,9 @@ $.Viewport.prototype = {
|
||||
}, margins);
|
||||
|
||||
this._updateContainerInnerSize();
|
||||
if (this.viewer) {
|
||||
this.viewer.forceRedraw();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -350,14 +363,26 @@ $.Viewport.prototype = {
|
||||
* @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();
|
||||
getBounds: function(current) {
|
||||
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 ),
|
||||
center.y - ( height / 2.0 ),
|
||||
center.x - (width / 2.0),
|
||||
center.y - (height / 2.0),
|
||||
width,
|
||||
height
|
||||
);
|
||||
@ -369,8 +394,19 @@ $.Viewport.prototype = {
|
||||
* @returns {OpenSeadragon.Rect} The location you are zoomed/panned to,
|
||||
* including the space taken by margins, in viewport coordinates.
|
||||
*/
|
||||
getBoundsWithMargins: function( current ) {
|
||||
var bounds = this.getBounds(current);
|
||||
getBoundsWithMargins: function(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;
|
||||
@ -438,6 +474,13 @@ $.Viewport.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
// private
|
||||
_applyZoomConstraints: function(zoom) {
|
||||
return Math.max(
|
||||
Math.min(zoom, this.getMaxZoom()),
|
||||
this.getMinZoom());
|
||||
},
|
||||
|
||||
/**
|
||||
* @function
|
||||
* @private
|
||||
@ -519,40 +562,44 @@ $.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;
|
||||
applyConstraints: function(immediately) {
|
||||
var actualZoom = this.getZoom();
|
||||
var constrainedZoom = this._applyZoomConstraints(actualZoom);
|
||||
|
||||
if ( actualZoom != constrainedZoom ) {
|
||||
this.zoomTo( constrainedZoom, this.zoomPoint, immediately );
|
||||
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 );
|
||||
ensureVisible: function(immediately) {
|
||||
return this.applyConstraints(immediately);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -562,41 +609,37 @@ $.Viewport.prototype = {
|
||||
* @param {Object} options (immediately=false, constraints=false)
|
||||
* @return {OpenSeadragon.Viewport} Chainable.
|
||||
*/
|
||||
_fitBounds: function( bounds, options ) {
|
||||
_fitBounds: function(bounds, options) {
|
||||
options = options || {};
|
||||
var immediately = options.immediately || false;
|
||||
var constraints = options.constraints || false;
|
||||
|
||||
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
|
||||
);
|
||||
bounds.height,
|
||||
bounds.degrees + this.getRotation())
|
||||
.getBoundingBox();
|
||||
|
||||
if ( newBounds.getAspectRatio() >= aspect ) {
|
||||
newBounds.height = bounds.width / aspect;
|
||||
newBounds.y = center.y - newBounds.height / 2;
|
||||
if (newBounds.getAspectRatio() >= aspect) {
|
||||
newBounds.height = newBounds.width / aspect;
|
||||
} else {
|
||||
newBounds.width = bounds.height * aspect;
|
||||
newBounds.x = center.x - newBounds.width / 2;
|
||||
newBounds.width = newBounds.height * aspect;
|
||||
}
|
||||
|
||||
this.panTo( this.getCenter( true ), true );
|
||||
this.zoomTo( this.getZoom( true ), null, true );
|
||||
|
||||
var oldBounds = this.getBounds();
|
||||
var oldZoom = this.getZoom();
|
||||
// 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) {
|
||||
var newBoundsAspectRatio = newBounds.getAspectRatio();
|
||||
var newConstrainedZoom = Math.max(
|
||||
Math.min(newZoom, this.getMaxZoom() ),
|
||||
this.getMinZoom()
|
||||
);
|
||||
var newConstrainedZoom = this._applyZoomConstraints(newZoom);
|
||||
|
||||
if (newZoom !== newConstrainedZoom) {
|
||||
newZoom = newConstrainedZoom;
|
||||
@ -606,58 +649,70 @@ $.Viewport.prototype = {
|
||||
newBounds.y = center.y - newBounds.height / 2;
|
||||
}
|
||||
|
||||
newBounds = this._applyBoundaryConstraints( newBounds, immediately );
|
||||
newBounds = this._applyBoundaryConstraints(newBounds, immediately);
|
||||
center = newBounds.getCenter();
|
||||
}
|
||||
|
||||
if (immediately) {
|
||||
this.panTo( center, true );
|
||||
this.panTo(center, true);
|
||||
return this.zoomTo(newZoom, null, true);
|
||||
}
|
||||
|
||||
if (Math.abs(newZoom - oldZoom) < 0.00000001 ||
|
||||
Math.abs(newBounds.width - oldBounds.width) < 0.00000001) {
|
||||
return this.panTo( center, immediately );
|
||||
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);
|
||||
}
|
||||
|
||||
var 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 );
|
||||
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 ) {
|
||||
return this._fitBounds( bounds, {
|
||||
fitBounds: function(bounds, immediately) {
|
||||
return this._fitBounds(bounds, {
|
||||
immediately: immediately,
|
||||
constraints: false
|
||||
} );
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 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 ) {
|
||||
return this._fitBounds( bounds, {
|
||||
fitBoundsWithConstraints: function(bounds, immediately) {
|
||||
return this._fitBounds(bounds, {
|
||||
immediately: immediately,
|
||||
constraints: true
|
||||
} );
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
@ -752,7 +807,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
|
||||
*/
|
||||
@ -842,7 +902,7 @@ $.Viewport.prototype = {
|
||||
* @fires OpenSeadragon.Viewer.event:resize
|
||||
*/
|
||||
resize: function( newContainerSize, maintain ) {
|
||||
var oldBounds = this.getBounds(),
|
||||
var oldBounds = this.getBoundsNoRotate(),
|
||||
newBounds = oldBounds,
|
||||
widthDeltaFactor;
|
||||
|
||||
@ -888,37 +948,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 {
|
||||
this.centerSpringX.shiftBy(deltaZoomPoints.x);
|
||||
this.centerSpringY.shiftBy(deltaZoomPoints.y);
|
||||
|
||||
if (this.zoomSpring.isAtTargetValue()) {
|
||||
this.zoomPoint = null;
|
||||
}
|
||||
} else {
|
||||
this.zoomSpring.update();
|
||||
}
|
||||
|
||||
this.centerSpringX.update();
|
||||
this.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;
|
||||
@ -994,7 +1054,8 @@ $.Viewport.prototype = {
|
||||
* @returns {OpenSeadragon.Point}
|
||||
*/
|
||||
pixelFromPointNoRotate: function(point, current) {
|
||||
return this._pixelFromPointNoRotate(point, this.getBounds(current));
|
||||
return this._pixelFromPointNoRotate(
|
||||
point, this.getBoundsNoRotate(current));
|
||||
},
|
||||
|
||||
/**
|
||||
@ -1005,7 +1066,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
|
||||
@ -1036,7 +1097,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(
|
||||
@ -1085,8 +1146,18 @@ $.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(
|
||||
@ -1119,8 +1190,18 @@ $.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);
|
||||
@ -1150,6 +1231,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 +1279,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(
|
||||
@ -1224,10 +1335,12 @@ $.Viewport.prototype = {
|
||||
* @param {OpenSeadragon.Point} pixel
|
||||
* @returns {OpenSeadragon.Point}
|
||||
*/
|
||||
windowToImageCoordinates: function( pixel ) {
|
||||
windowToImageCoordinates: function(pixel) {
|
||||
$.console.assert(this.viewer,
|
||||
"[Viewport.windowToImageCoordinates] the viewport must have a viewer.");
|
||||
var viewerCoordinates = pixel.minus(
|
||||
OpenSeadragon.getElementPosition( this.viewer.element ));
|
||||
return this.viewerElementToImageCoordinates( viewerCoordinates );
|
||||
$.getElementPosition(this.viewer.element));
|
||||
return this.viewerElementToImageCoordinates(viewerCoordinates);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -1236,10 +1349,12 @@ $.Viewport.prototype = {
|
||||
* @param {OpenSeadragon.Point} pixel
|
||||
* @returns {OpenSeadragon.Point}
|
||||
*/
|
||||
imageToWindowCoordinates: function( pixel ) {
|
||||
var viewerCoordinates = this.imageToViewerElementCoordinates( pixel );
|
||||
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,15 +1377,45 @@ $.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 ) {
|
||||
windowToViewportCoordinates: function(pixel) {
|
||||
$.console.assert(this.viewer,
|
||||
"[Viewport.windowToViewportCoordinates] the viewport must have a viewer.");
|
||||
var viewerCoordinates = pixel.minus(
|
||||
OpenSeadragon.getElementPosition( this.viewer.element ));
|
||||
return this.viewerElementToViewportCoordinates( viewerCoordinates );
|
||||
$.getElementPosition(this.viewer.element));
|
||||
return this.viewerElementToViewportCoordinates(viewerCoordinates);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -1278,10 +1423,12 @@ $.Viewport.prototype = {
|
||||
* @param {OpenSeadragon.Point} point
|
||||
* @returns {OpenSeadragon.Point}
|
||||
*/
|
||||
viewportToWindowCoordinates: function( point ) {
|
||||
var viewerCoordinates = this.viewportToViewerElementCoordinates( 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));
|
||||
},
|
||||
|
||||
/**
|
||||
@ -1296,9 +1443,19 @@ $.Viewport.prototype = {
|
||||
* target zoom.
|
||||
* @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.');
|
||||
viewportToImageZoom: function(viewportZoom) {
|
||||
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._contentSizeNoRotate.x;
|
||||
@ -1320,9 +1477,19 @@ $.Viewport.prototype = {
|
||||
* target zoom.
|
||||
* @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.');
|
||||
imageToViewportZoom: function(imageZoom) {
|
||||
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._contentSizeNoRotate.x;
|
||||
|
42
src/world.js
42
src/world.js
@ -375,33 +375,39 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W
|
||||
var oldContentSize = this._contentSize ? this._contentSize.clone() : null;
|
||||
var oldContentFactor = this._contentFactor || 0;
|
||||
|
||||
if ( !this._items.length ) {
|
||||
if (!this._items.length) {
|
||||
this._homeBounds = new $.Rect(0, 0, 1, 1);
|
||||
this._contentSize = new $.Point(1, 1);
|
||||
this._contentFactor = 1;
|
||||
} else {
|
||||
var bounds = this._items[0].getBounds();
|
||||
this._contentFactor = this._items[0].getContentSize().x / bounds.width;
|
||||
var left = bounds.x;
|
||||
var top = bounds.y;
|
||||
var right = bounds.x + bounds.width;
|
||||
var bottom = bounds.y + bounds.height;
|
||||
var box;
|
||||
for ( var i = 1; i < this._items.length; i++ ) {
|
||||
box = this._items[i].getBounds();
|
||||
this._contentFactor = Math.max(this._contentFactor, this._items[i].getContentSize().x / box.width);
|
||||
left = Math.min( left, box.x );
|
||||
top = Math.min( top, box.y );
|
||||
right = Math.max( right, box.x + box.width );
|
||||
bottom = Math.max( bottom, box.y + box.height );
|
||||
var item = this._items[0];
|
||||
var bounds = item.getBounds();
|
||||
this._contentFactor = item.getContentSize().x / bounds.width;
|
||||
var clippedBounds = item.getClippedBounds();
|
||||
var left = clippedBounds.x;
|
||||
var top = clippedBounds.y;
|
||||
var right = clippedBounds.x + clippedBounds.width;
|
||||
var bottom = clippedBounds.y + clippedBounds.height;
|
||||
for (var i = 1; i < this._items.length; i++) {
|
||||
item = this._items[i];
|
||||
bounds = item.getBounds();
|
||||
this._contentFactor = Math.max(this._contentFactor,
|
||||
item.getContentSize().x / bounds.width);
|
||||
clippedBounds = item.getClippedBounds();
|
||||
left = Math.min(left, clippedBounds.x);
|
||||
top = Math.min(top, clippedBounds.y);
|
||||
right = Math.max(right, clippedBounds.x + clippedBounds.width);
|
||||
bottom = Math.max(bottom, clippedBounds.y + clippedBounds.height);
|
||||
}
|
||||
|
||||
this._homeBounds = new $.Rect( left, top, right - left, bottom - top );
|
||||
this._contentSize = new $.Point(this._homeBounds.width * this._contentFactor,
|
||||
this._homeBounds = new $.Rect(left, top, right - left, bottom - top);
|
||||
this._contentSize = new $.Point(
|
||||
this._homeBounds.width * this._contentFactor,
|
||||
this._homeBounds.height * this._contentFactor);
|
||||
}
|
||||
|
||||
if (this._contentFactor !== oldContentFactor || !this._homeBounds.equals(oldHomeBounds) ||
|
||||
if (this._contentFactor !== oldContentFactor ||
|
||||
!this._homeBounds.equals(oldHomeBounds) ||
|
||||
!this._contentSize.equals(oldContentSize)) {
|
||||
/**
|
||||
* Raised when the home bounds or content factor change.
|
||||
|
@ -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
|
||||
|
@ -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
134
test/helpers/touch.js
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
})();
|
@ -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,20 +781,57 @@
|
||||
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(
|
||||
bounds,
|
||||
new OpenSeadragon.Rect(-250, -0.25, 500, 0.5),
|
||||
epsilon,
|
||||
'Pan should not be prevented');
|
||||
|
||||
viewer.close();
|
||||
start();
|
||||
};
|
||||
|
||||
viewer.addHandler( 'open', onOpen );
|
||||
viewer.open( '/test/data/testpattern.dzi' );
|
||||
} );
|
||||
viewer.addHandler('open', onOpen);
|
||||
viewer.open('/test/data/testpattern.dzi');
|
||||
});
|
||||
|
||||
// ----------
|
||||
asyncTest( 'EventSource/MouseTracker/Viewer: event.originalEvent event.userData canvas-drag canvas-drag-end canvas-release canvas-click', function () {
|
||||
@ -949,6 +1037,59 @@
|
||||
viewer.open( '/test/data/testpattern.dzi' );
|
||||
} );
|
||||
|
||||
// ----------
|
||||
test('EventSource: addOnceHandler', function() {
|
||||
var eventSource = new OpenSeadragon.EventSource();
|
||||
var userData = 'data';
|
||||
var eventData = {
|
||||
foo: 1
|
||||
};
|
||||
var handlerCalledCount = 0;
|
||||
eventSource.addOnceHandler('test-event', function(event) {
|
||||
handlerCalledCount++;
|
||||
strictEqual(event.foo, eventData.foo,
|
||||
'Event data should be transmitted to the event.');
|
||||
strictEqual(event.userData, userData,
|
||||
'User data should be transmitted to the event.');
|
||||
}, userData);
|
||||
strictEqual(0, handlerCalledCount,
|
||||
'Handler should not have been called yet.');
|
||||
eventSource.raiseEvent('test-event', eventData);
|
||||
strictEqual(1, handlerCalledCount,
|
||||
'Handler should have been called once.');
|
||||
eventSource.raiseEvent('test-event', eventData);
|
||||
strictEqual(1, handlerCalledCount,
|
||||
'Handler should still have been called once.');
|
||||
});
|
||||
|
||||
// ----------
|
||||
test('EventSource: addOnceHandler 2 times', function() {
|
||||
var eventSource = new OpenSeadragon.EventSource();
|
||||
var userData = 'data';
|
||||
var eventData = {
|
||||
foo: 1
|
||||
};
|
||||
var handlerCalledCount = 0;
|
||||
eventSource.addOnceHandler('test-event', function(event) {
|
||||
handlerCalledCount++;
|
||||
strictEqual(event.foo, eventData.foo,
|
||||
'Event data should be transmitted to the event.');
|
||||
strictEqual(event.userData, userData,
|
||||
'User data should be transmitted to the event.');
|
||||
}, userData, 2);
|
||||
strictEqual(0, handlerCalledCount,
|
||||
'Handler should not have been called yet.');
|
||||
eventSource.raiseEvent('test-event', eventData);
|
||||
strictEqual(1, handlerCalledCount,
|
||||
'Handler should have been called once.');
|
||||
eventSource.raiseEvent('test-event', eventData);
|
||||
strictEqual(2, handlerCalledCount,
|
||||
'Handler should have been called twice.');
|
||||
eventSource.raiseEvent('test-event', eventData);
|
||||
strictEqual(2, handlerCalledCount,
|
||||
'Handler should still have been called twice.');
|
||||
});
|
||||
|
||||
// ----------
|
||||
asyncTest( 'Viewer: tile-drawing event', function () {
|
||||
var tileDrawing = function ( event ) {
|
||||
|
@ -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]
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
})();
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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);
|
||||
});
|
||||
|
||||
})();
|
||||
|
@ -4,7 +4,7 @@
|
||||
var viewer;
|
||||
|
||||
module('TiledImage', {
|
||||
setup: function () {
|
||||
setup: function() {
|
||||
var example = $('<div id="example"></div>').appendTo("#qunit-fixture");
|
||||
|
||||
testLog.reset();
|
||||
@ -15,7 +15,7 @@
|
||||
springStiffness: 100 // Faster animation = faster tests
|
||||
});
|
||||
},
|
||||
teardown: function () {
|
||||
teardown: function() {
|
||||
if (viewer && viewer.close) {
|
||||
viewer.close();
|
||||
}
|
||||
@ -87,7 +87,7 @@
|
||||
|
||||
// ----------
|
||||
asyncTest('animation', function() {
|
||||
viewer.addHandler("open", function () {
|
||||
viewer.addHandler("open", function() {
|
||||
var image = viewer.world.getItemAt(0);
|
||||
propEqual(image.getBounds(), new OpenSeadragon.Rect(0, 0, 1, 1), 'target bounds on open');
|
||||
propEqual(image.getBounds(true), new OpenSeadragon.Rect(0, 0, 1, 1), 'current bounds on open');
|
||||
@ -206,10 +206,12 @@
|
||||
propEqual(image.getClip(), clip, 'clip is set correctly');
|
||||
|
||||
Util.spyOnce(viewer.drawer, 'setClip', function(rect) {
|
||||
ok(true, 'drawer.setClip is called');
|
||||
var pixelRatio = viewer.viewport.getContainerSize().x / image.getContentSize().x;
|
||||
var canvasClip = clip.times(pixelRatio * OpenSeadragon.pixelDensityRatio);
|
||||
propEqual(rect, canvasClip, 'clipping to correct rect');
|
||||
var homeBounds = viewer.viewport.getHomeBounds();
|
||||
var canvasClip = viewer.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
|
||||
}]);
|
||||
});
|
||||
})();
|
||||
|
@ -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',
|
||||
@ -336,7 +409,7 @@
|
||||
viewer.open(DZI_PATH);
|
||||
});
|
||||
|
||||
asyncTest('ensureVisible', function(){
|
||||
asyncTest('ensureVisible', function() {
|
||||
var openHandler = function(event) {
|
||||
viewer.removeHandler('open', openHandler);
|
||||
var viewport = viewer.viewport;
|
||||
@ -354,17 +427,114 @@
|
||||
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(
|
||||
bounds,
|
||||
new OpenSeadragon.Rect(0.7, 0.7, 1, 1),
|
||||
EPSILON,
|
||||
"Viewport.applyConstraints should move viewport.");
|
||||
start();
|
||||
};
|
||||
viewer.addHandler('open', openHandler);
|
||||
viewer.open(DZI_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 +544,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 +577,7 @@
|
||||
viewport.fitBoundsWithConstraints(rect, true);
|
||||
propEqual(
|
||||
viewport.getBounds(),
|
||||
expectedRectsFitBounds[i],
|
||||
expectedRectsFitBoundsWithConstraints[i],
|
||||
"Fit bounds correctly."
|
||||
);
|
||||
}
|
||||
@ -409,6 +587,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 +656,7 @@
|
||||
viewer.addHandler('open', openHandler);
|
||||
viewer.open(WIDE_PATH);
|
||||
});
|
||||
// End fitBounds tests.
|
||||
|
||||
asyncTest('panBy', function(){
|
||||
var openHandler = function(event) {
|
||||
|
@ -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. -->
|
||||
|
Loading…
Reference in New Issue
Block a user