Merge pull request #1 from openseadragon/master

Update Master
This commit is contained in:
Nelson Campos 2018-09-20 14:06:53 +01:00 committed by GitHub
commit ab98173a13
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 5057 additions and 2537 deletions

View File

@ -50,6 +50,7 @@
"no-multi-spaces": [ "no-multi-spaces": [
"error", "error",
{ {
"ignoreEOLComments": true,
"exceptions": { "exceptions": {
"Property": true, "Property": true,
"VariableDeclarator": true, "VariableDeclarator": true,
@ -259,7 +260,7 @@
"space-unary-ops": [ "space-unary-ops": [
"error", "error",
{ {
"words": false, "words": true,
"nonwords": false "nonwords": false
} }
], ],

1
.gitignore vendored
View File

@ -7,3 +7,4 @@ instrumented/
.idea .idea
/nbproject/private/ /nbproject/private/
.directory .directory
test/demo/temp

View File

@ -1,9 +1,16 @@
OPENSEADRAGON CHANGELOG OPENSEADRAGON CHANGELOG
======================= =======================
2.4.0: (In Progress) 2.4.1: (In progress)
* You can now turn off the default canvas image smoothing, if you want sharp pixels when zoomed in past 100% (#1507)
* Fixed problem with navigator highlight rectangle when returning from full screen with a custom navigator location (#1515)
* Added option to set rotation increment for nav buttons and keyboard (#1524)
2.4.0:
* BREAKING CHANGE: Viewer's canvas-double-click event is now fired before it initiates the zoom (#1288) * BREAKING CHANGE: Viewer's canvas-double-click event is now fired before it initiates the zoom (#1288)
* You can now flip the viewport to get a mirror image of the original (#1441)
* You can now prevent canvas-double-click events from zooming on a per-event basis (#1288) * You can now prevent canvas-double-click events from zooming on a per-event basis (#1288)
* Fixed: Opacity 0 images were causing unnecessary redraws (#1319) * Fixed: Opacity 0 images were causing unnecessary redraws (#1319)
* The "page" event is now fired after the page index has been updated (#1330) * The "page" event is now fired after the page index has been updated (#1330)
@ -12,6 +19,13 @@ OPENSEADRAGON CHANGELOG
* Added canvas-key events, along with the ability to cancel key actions (#1414) * Added canvas-key events, along with the ability to cancel key actions (#1414)
* Added optional zoom in the middle of the image instead of pointer position (#1423) * Added optional zoom in the middle of the image instead of pointer position (#1423)
* Now supporting square edge tiles that are padded rather than cropped (#1426) * Now supporting square edge tiles that are padded rather than cropped (#1426)
* Fixed an issue causing the simple image tileSource to sometimes show duplicate copies (#1370)
* Fixed an issue causing seams to appear in semi-transparent PNG tiled images (#1470)
* Added visual customization options for the navigator (#1480)
* You can now prevent canvas-drag events on the navigator (#1484)
* You can now prevent canvas-click events on the navigator (#1416)
* The navigator can now be restricted to just horizontal or just vertical panning (#1416)
* Fixed DziTileSource so it doesn't load levels above maxLevel or below minLevel, if set (#1492)
2.3.1: 2.3.1:

BIN
images/flip_grouphover.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

BIN
images/flip_hover.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

BIN
images/flip_pressed.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

BIN
images/flip_rest.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

6633
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "openseadragon", "name": "openseadragon",
"version": "2.3.1", "version": "2.4.0",
"description": "Provides a smooth, zoomable user interface for HTML/Javascript.", "description": "Provides a smooth, zoomable user interface for HTML/Javascript.",
"keywords": [ "keywords": [
"image", "image",
@ -28,16 +28,16 @@
"url": "https://github.com/openseadragon/openseadragon.git" "url": "https://github.com/openseadragon/openseadragon.git"
}, },
"devDependencies": { "devDependencies": {
"grunt": "^1.0.1", "grunt": "^1.0.2",
"grunt-contrib-clean": "^1.1.0", "grunt-contrib-clean": "^1.1.0",
"grunt-contrib-compress": "^1.4.3", "grunt-contrib-compress": "^1.4.3",
"grunt-contrib-concat": "^1.0.1", "grunt-contrib-concat": "^1.0.1",
"grunt-contrib-connect": "^1.0.2", "grunt-contrib-connect": "^1.0.2",
"grunt-contrib-qunit": "^2.0.0", "grunt-contrib-qunit": "^2.0.0",
"grunt-contrib-uglify": "^3.2.1", "grunt-contrib-uglify": "^3.2.1",
"grunt-contrib-watch": "^1.0.0", "grunt-contrib-watch": "^1.1.0",
"grunt-eslint": "^19.0.0", "grunt-eslint": "^20.1.0",
"grunt-git-describe": "^2.3.2", "grunt-git-describe": "^2.4.4",
"grunt-istanbul": "^0.8.0", "grunt-istanbul": "^0.8.0",
"grunt-text-replace": "^0.4.0", "grunt-text-replace": "^0.4.0",
"qunitjs": "2.4.1" "qunitjs": "2.4.1"

View File

@ -83,7 +83,7 @@ $.ButtonGroup = function( options ) {
$.setElementTouchActionNone( this.element ); $.setElementTouchActionNone( this.element );
/** /**
* Tracks mouse/touch/key events accross the group of buttons. * Tracks mouse/touch/key events across the group of buttons.
* @member {OpenSeadragon.MouseTracker} tracker * @member {OpenSeadragon.MouseTracker} tracker
* @memberof OpenSeadragon.ButtonGroup# * @memberof OpenSeadragon.ButtonGroup#
*/ */

View File

@ -167,7 +167,7 @@ $.Control.prototype = {
/** /**
* Determines if the control is currently visible. * Determines if the control is currently visible.
* @function * @function
* @return {Boolean} true if currenly visible, false otherwise. * @return {Boolean} true if currently visible, false otherwise.
*/ */
isVisible: function() { isVisible: function() {
return this.wrapper.style.display != "none"; return this.wrapper.style.display != "none";

View File

@ -48,7 +48,7 @@ $.Drawer = function( options ) {
$.console.assert( options.viewer, "[Drawer] options.viewer is required" ); $.console.assert( options.viewer, "[Drawer] options.viewer is required" );
//backward compatibility for positional args while prefering more //backward compatibility for positional args while preferring more
//idiomatic javascript options object as the only argument //idiomatic javascript options object as the only argument
var args = arguments; var args = arguments;
@ -500,6 +500,10 @@ $.Drawer.prototype = {
if ( this.viewport.degrees !== 0 ) { if ( this.viewport.degrees !== 0 ) {
this._offsetForRotation({degrees: this.viewport.degrees}); this._offsetForRotation({degrees: this.viewport.degrees});
} else{
if(this.viewer.viewport.flipped) {
this._flip();
}
} }
if (tiledImage.getRotation(true) % 360 !== 0) { if (tiledImage.getRotation(true) % 360 !== 0) {
this._offsetForRotation({ this._offsetForRotation({
@ -596,6 +600,27 @@ $.Drawer.prototype = {
} }
}, },
/**
* Turns image smoothing on or off for this viewer. Note: Ignored in some (especially older) browsers that do not support this property.
*
* @function
* @param {Boolean} [imageSmoothingEnabled] - Whether or not the image is
* drawn smoothly on the canvas; see imageSmoothingEnabled in
* {@link OpenSeadragon.Options} for more explanation.
*/
setImageSmoothingEnabled: function(imageSmoothingEnabled){
if ( this.useCanvas ) {
var context = this.context;
context.mozImageSmoothingEnabled = imageSmoothingEnabled;
context.webkitImageSmoothingEnabled = imageSmoothingEnabled;
context.msImageSmoothingEnabled = imageSmoothingEnabled;
context.imageSmoothingEnabled = imageSmoothingEnabled;
this.viewer.forceRedraw();
}
},
/** /**
* Get the canvas size * Get the canvas size
* @param {Boolean} sketch If set to true return the size of the sketch canvas * @param {Boolean} sketch If set to true return the size of the sketch canvas
@ -620,10 +645,28 @@ $.Drawer.prototype = {
context.save(); context.save();
context.translate(point.x, point.y); context.translate(point.x, point.y);
if(this.viewer.viewport.flipped){
context.rotate(Math.PI / 180 * -options.degrees);
context.scale(-1, 1);
} else{
context.rotate(Math.PI / 180 * options.degrees); context.rotate(Math.PI / 180 * options.degrees);
}
context.translate(-point.x, -point.y); context.translate(-point.x, -point.y);
}, },
// private
_flip: function(options) {
options = options || {};
var point = options.point ?
options.point.times($.pixelDensityRatio) :
this.getCanvasCenter();
var context = this._getContext(options.useSketch);
context.translate(point.x, 0);
context.scale(-1, 1);
context.translate(-point.x, 0);
},
// private // private
_restoreRotationChanges: function(useSketch) { _restoreRotationChanges: function(useSketch) {
var context = this._getContext(useSketch); var context = this._getContext(useSketch);

View File

@ -123,7 +123,7 @@ $.extend( $.DziTileSource.prototype, $.TileSource.prototype, /** @lends OpenSead
* *
* @function * @function
* @param {Object|XMLDocument} data - the raw configuration * @param {Object|XMLDocument} data - the raw configuration
* @param {String} url - the url the data was retreived from if any. * @param {String} url - the url the data was retrieved from if any.
* @return {Object} options - A dictionary of keyword arguments sufficient * @return {Object} options - A dictionary of keyword arguments sufficient
* to configure this tile sources constructor. * to configure this tile sources constructor.
*/ */
@ -182,6 +182,10 @@ $.extend( $.DziTileSource.prototype, $.TileSource.prototype, /** @lends OpenSead
yMax, yMax,
i; i;
if ((this.minLevel && level < this.minLevel) || (this.maxLevel && level > this.maxLevel)) {
return false;
}
if ( !rects || !rects.length ) { if ( !rects || !rects.length ) {
return true; return true;
} }

View File

@ -84,7 +84,7 @@ ImageJob.prototype = {
}; };
this.jobId = window.setTimeout(function(){ this.jobId = window.setTimeout(function(){
self.errorMsg = "Image load exceeded timeout"; self.errorMsg = "Image load exceeded timeout (" + self.timeout + " ms)";
self.finish(false); self.finish(false);
}, this.timeout); }, this.timeout);

View File

@ -88,7 +88,7 @@
* *
* @function * @function
* @param {Object} options - the options * @param {Object} options - the options
* @param {String} dataUrl - the url the image was retreived from, if any. * @param {String} dataUrl - the url the image was retrieved from, if any.
* @return {Object} options - A dictionary of keyword arguments sufficient * @return {Object} options - A dictionary of keyword arguments sufficient
* to configure this tile sources constructor. * to configure this tile sources constructor.
*/ */
@ -198,7 +198,7 @@
// private // private
// //
// Builds the differents levels of the pyramid if possible // Builds the different levels of the pyramid if possible
// (i.e. if canvas API enabled and no canvas tainting issue). // (i.e. if canvas API enabled and no canvas tainting issue).
_buildLevels: function () { _buildLevels: function () {
var levels = [{ var levels = [{

View File

@ -121,7 +121,7 @@ $.extend( $.LegacyTileSource.prototype, $.TileSource.prototype, /** @lends OpenS
* *
* @function * @function
* @param {Object|XMLDocument} configuration - the raw configuration * @param {Object|XMLDocument} configuration - the raw configuration
* @param {String} dataUrl - the url the data was retreived from if any. * @param {String} dataUrl - the url the data was retrieved from if any.
* @return {Object} options - A dictionary of keyword arguments sufficient * @return {Object} options - A dictionary of keyword arguments sufficient
* to configure this tile sources constructor. * to configure this tile sources constructor.
*/ */
@ -190,7 +190,7 @@ $.extend( $.LegacyTileSource.prototype, $.TileSource.prototype, /** @lends OpenS
} ); } );
/** /**
* This method removes any files from the Array which dont conform to our * This method removes any files from the Array which don't conform to our
* basic requirements for a 'level' in the LegacyTileSource. * basic requirements for a 'level' in the LegacyTileSource.
* @private * @private
* @inner * @inner

View File

@ -110,7 +110,11 @@ $.Navigator = function( options ){
animationTime: 0, animationTime: 0,
autoResize: options.autoResize, autoResize: options.autoResize,
// prevent resizing the navigator from adding unwanted space around the image // prevent resizing the navigator from adding unwanted space around the image
minZoomImageRatio: 1.0 minZoomImageRatio: 1.0,
background: options.background,
opacity: options.opacity,
borderColor: options.borderColor,
displayRegionColor: options.displayRegionColor
}); });
options.minPixelRatio = this.minPixelRatio = viewer.minPixelRatio; options.minPixelRatio = this.minPixelRatio = viewer.minPixelRatio;
@ -127,10 +131,10 @@ $.Navigator = function( options ){
if ( options.controlOptions.anchor != $.ControlAnchor.NONE ) { if ( options.controlOptions.anchor != $.ControlAnchor.NONE ) {
(function( style, borderWidth ){ (function( style, borderWidth ){
style.margin = '0px'; style.margin = '0px';
style.border = borderWidth + 'px solid #555'; style.border = borderWidth + 'px solid ' + options.borderColor;
style.padding = '0px'; style.padding = '0px';
style.background = '#000'; style.background = options.background;
style.opacity = 0.8; style.opacity = options.opacity;
style.overflow = 'hidden'; style.overflow = 'hidden';
}( this.element.style, this.borderWidth)); }( this.element.style, this.borderWidth));
} }
@ -145,10 +149,10 @@ $.Navigator = function( options ){
style.left = '0px'; style.left = '0px';
style.fontSize = '0px'; style.fontSize = '0px';
style.overflow = 'hidden'; style.overflow = 'hidden';
style.border = borderWidth + 'px solid #900'; style.border = borderWidth + 'px solid ' + options.displayRegionColor;
style.margin = '0px'; style.margin = '0px';
style.padding = '0px'; style.padding = '0px';
//TODO: IE doesnt like this property being set //TODO: IE doesn't like this property being set
//try{ style.outline = '2px auto #909'; }catch(e){/*ignore*/} //try{ style.outline = '2px auto #909'; }catch(e){/*ignore*/}
style.background = 'transparent'; style.background = 'transparent';
@ -208,12 +212,14 @@ $.Navigator = function( options ){
var degrees = options.viewer.viewport ? var degrees = options.viewer.viewport ?
options.viewer.viewport.getRotation() : options.viewer.viewport.getRotation() :
options.viewer.degrees || 0; options.viewer.degrees || 0;
rotate(degrees); rotate(degrees);
options.viewer.addHandler("rotate", function (args) { options.viewer.addHandler("rotate", function (args) {
rotate(args.degrees); rotate(args.degrees);
}); });
} }
// Remove the base class' (Viewer's) innerTracker and replace it with our own // Remove the base class' (Viewer's) innerTracker and replace it with our own
this.innerTracker.destroy(); this.innerTracker.destroy();
this.innerTracker = new $.MouseTracker({ this.innerTracker = new $.MouseTracker({
@ -271,6 +277,22 @@ $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /*
} }
} }
}, },
/**
/* Flip navigator element
* @param {Boolean} state - Flip state to set.
*/
setFlip: function(state) {
this.viewport.setFlip(state);
this.setDisplayTransform(this.viewer.viewport.getFlip() ? "scale(-1,1)" : "scale(1,1)");
return this;
},
setDisplayTransform: function(rule) {
setElementTransform(this.displayRegion, rule);
setElementTransform(this.canvas, rule);
setElementTransform(this.element, rule);
},
/** /**
* Used to update the navigator minimap's viewport rectangle when a change in the viewer's viewport occurs. * Used to update the navigator minimap's viewport rectangle when a change in the viewer's viewport occurs.
@ -399,16 +421,55 @@ $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /*
} }
}); });
/** /**
* @private * @private
* @inner * @inner
* @function * @function
*/ */
function onCanvasClick( event ) { function onCanvasClick( event ) {
if ( event.quick && this.viewer.viewport ) { var canvasClickEventArgs = {
this.viewer.viewport.panTo(this.viewport.pointFromPixel(event.position)); tracker: event.eventSource,
position: event.position,
quick: event.quick,
shift: event.shift,
originalEvent: event.originalEvent,
preventDefaultAction: event.preventDefaultAction
};
/**
* Raised when a click event occurs on the {@link OpenSeadragon.Viewer#navigator} element.
*
* @event navigator-click
* @memberof OpenSeadragon.Viewer
* @type {object}
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
* @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
* @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
* @property {Boolean} quick - True only if the clickDistThreshold and clickTimeThreshold are both passed. Useful for differentiating between clicks and drags.
* @property {Boolean} shift - True if the shift key was pressed during this event.
* @property {Object} originalEvent - The original DOM event.
* @property {?Object} userData - Arbitrary subscriber-defined object.
* @property {Boolean} preventDefaultAction - Set to true to prevent default click to zoom behaviour. Default: false.
*/
this.viewer.raiseEvent('navigator-click', canvasClickEventArgs);
if ( !canvasClickEventArgs.preventDefaultAction && event.quick && this.viewer.viewport && (this.panVertical || this.panHorizontal)) {
if(this.viewer.viewport.flipped) {
event.position.x = this.viewport.getContainerSize().x - event.position.x;
}
var target = this.viewport.pointFromPixel(event.position);
if (!this.panVertical) {
// perform only horizonal pan
target.y = this.viewer.viewport.getCenter(true).y;
} else if (!this.panHorizontal) {
// perform only vertical pan
target.x = this.viewer.viewport.getCenter(true).x;
}
this.viewer.viewport.panTo(target);
this.viewer.viewport.applyConstraints(); this.viewer.viewport.applyConstraints();
} }
} }
/** /**
@ -417,13 +478,47 @@ function onCanvasClick( event ) {
* @function * @function
*/ */
function onCanvasDrag( event ) { function onCanvasDrag( event ) {
if ( this.viewer.viewport ) { var canvasDragEventArgs = {
tracker: event.eventSource,
position: event.position,
delta: event.delta,
speed: event.speed,
direction: event.direction,
shift: event.shift,
originalEvent: event.originalEvent,
preventDefaultAction: event.preventDefaultAction
};
/**
* Raised when a drag event occurs on the {@link OpenSeadragon.Viewer#navigator} element.
*
* @event navigator-drag
* @memberof OpenSeadragon.Viewer
* @type {object}
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
* @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
* @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
* @property {OpenSeadragon.Point} delta - The x,y components of the difference between start drag and end drag.
* @property {Number} speed - Current computed speed, in pixels per second.
* @property {Number} direction - Current computed direction, expressed as an angle counterclockwise relative to the positive X axis (-pi to pi, in radians). Only valid if speed > 0.
* @property {Boolean} shift - True if the shift key was pressed during this event.
* @property {Object} originalEvent - The original DOM event.
* @property {?Object} userData - Arbitrary subscriber-defined object.
* @property {Boolean} preventDefaultAction - Set to true to prevent default click to zoom behaviour. Default: false.
*/
this.viewer.raiseEvent('navigator-drag', canvasDragEventArgs);
if ( !canvasDragEventArgs.preventDefaultAction && this.viewer.viewport ) {
if( !this.panHorizontal ){ if( !this.panHorizontal ){
event.delta.x = 0; event.delta.x = 0;
} }
if( !this.panVertical ){ if( !this.panVertical ){
event.delta.y = 0; event.delta.y = 0;
} }
if(this.viewer.viewport.flipped){
event.delta.x = -event.delta.x;
}
this.viewer.viewport.panBy( this.viewer.viewport.panBy(
this.viewport.deltaPointsFromPixels( this.viewport.deltaPointsFromPixels(
event.delta event.delta
@ -476,7 +571,7 @@ function onCanvasScroll( event ) {
originalEvent: event.originalEvent originalEvent: event.originalEvent
}); });
//dont scroll the page up and down if the user is scrolling //don't scroll the page up and down if the user is scrolling
//in the navigator //in the navigator
return false; return false;
} }
@ -488,11 +583,15 @@ function onCanvasScroll( event ) {
* @param {Number} degrees * @param {Number} degrees
*/ */
function _setTransformRotate( element, degrees ) { function _setTransformRotate( element, degrees ) {
element.style.webkitTransform = "rotate(" + degrees + "deg)"; setElementTransform(element, "rotate(" + degrees + "deg)");
element.style.mozTransform = "rotate(" + degrees + "deg)"; }
element.style.msTransform = "rotate(" + degrees + "deg)";
element.style.oTransform = "rotate(" + degrees + "deg)"; function setElementTransform( element, rule ) {
element.style.transform = "rotate(" + degrees + "deg)"; element.style.webkitTransform = rule;
element.style.mozTransform = rule;
element.style.msTransform = rule;
element.style.oTransform = rule;
element.style.transform = rule;
} }
}( OpenSeadragon )); }( OpenSeadragon ));

View File

@ -198,6 +198,11 @@
* 'destination-over', 'destination-atop', 'destination-in', * 'destination-over', 'destination-atop', 'destination-in',
* 'destination-out', 'lighter', 'copy' or 'xor' * 'destination-out', 'lighter', 'copy' or 'xor'
* *
* @property {Boolean} [imageSmoothingEnabled=true]
* Image smoothing for canvas rendering (only if canvas is used). Note: Ignored
* by some (especially older) browsers which do not support this canvas property.
* This property can be changed in {@link Viewer.Drawer.setImageSmoothingEnabled}.
*
* @property {String|CanvasGradient|CanvasPattern|Function} [placeholderFillStyle=null] * @property {String|CanvasGradient|CanvasPattern|Function} [placeholderFillStyle=null]
* Draws a colored rectangle behind the tile if it is not loaded yet. * Draws a colored rectangle behind the tile if it is not loaded yet.
* You can pass a CSS color value like "#FF8800". * You can pass a CSS color value like "#FF8800".
@ -206,6 +211,9 @@
* @property {Number} [degrees=0] * @property {Number} [degrees=0]
* Initial rotation. * Initial rotation.
* *
* @property {Boolean} [flipped=false]
* Initial flip state.
*
* @property {Number} [minZoomLevel=null] * @property {Number} [minZoomLevel=null]
* *
* @property {Number} [maxZoomLevel=null] * @property {Number} [maxZoomLevel=null]
@ -239,7 +247,7 @@
* The maximum ratio to allow a zoom-in to affect the highest level pixel * The maximum ratio to allow a zoom-in to affect the highest level pixel
* ratio. This can be set to Infinity to allow 'infinite' zooming into the * ratio. This can be set to Infinity to allow 'infinite' zooming into the
* image though it is less effective visually if the HTML5 Canvas is not * image though it is less effective visually if the HTML5 Canvas is not
* availble on the viewing device. * available on the viewing device.
* *
* @property {Number} [smoothTileEdgesMinZoom=1.1] * @property {Number} [smoothTileEdgesMinZoom=1.1]
* A zoom percentage ( where 1 is 100% ) of the highest resolution level. * A zoom percentage ( where 1 is 100% ) of the highest resolution level.
@ -263,6 +271,9 @@
* events between different devices, causing the faster devices to slow down enough to make the zoom control * events between different devices, causing the faster devices to slow down enough to make the zoom control
* more manageable. * more manageable.
* *
* @property {Number} [rotationIncrement=90]
* The number of degrees to rotate right or left when the rotate buttons or keyboard shortcuts are activated.
*
* @property {Number} [pixelsPerWheelLine=40] * @property {Number} [pixelsPerWheelLine=40]
* For pixel-resolution scrolling devices, the number of pixels equal to one scroll line. * For pixel-resolution scrolling devices, the number of pixels equal to one scroll line.
* *
@ -416,9 +427,21 @@
* @property {Boolean} [navigatorRotate=true] * @property {Boolean} [navigatorRotate=true]
* If true, the navigator will be rotated together with the viewer. * If true, the navigator will be rotated together with the viewer.
* *
* @property {String} [navigatorBackground='#000']
* Specifies the background color of the navigator minimap
*
* @property {Number} [navigatorOpacity=0.8]
* Specifies the opacity of the navigator minimap.
*
* @property {String} [navigatorBorderColor='#555']
* Specifies the border color of the navigator minimap
*
* @property {String} [navigatorDisplayRegionColor='#900']
* Specifies the border color of the display region rectangle of the navigator minimap
*
* @property {Number} [controlsFadeDelay=2000] * @property {Number} [controlsFadeDelay=2000]
* The number of milliseconds to wait once the user has stopped interacting * The number of milliseconds to wait once the user has stopped interacting
* with the interface before begining to fade the controls. Assumes * with the interface before beginning to fade the controls. Assumes
* showNavigationControl and autoHideControls are both true. * showNavigationControl and autoHideControls are both true.
* *
* @property {Number} [controlsFadeLength=1500] * @property {Number} [controlsFadeLength=1500]
@ -436,7 +459,7 @@
* @property {Number} [minPixelRatio=0.5] * @property {Number} [minPixelRatio=0.5]
* The higher the minPixelRatio, the lower the quality of the image that * The higher the minPixelRatio, the lower the quality of the image that
* is considered sufficient to stop rendering a given zoom level. For * is considered sufficient to stop rendering a given zoom level. For
* example, if you are targeting mobile devices with less bandwith you may * example, if you are targeting mobile devices with less bandwidth you may
* try setting this to 1.5 or higher. * try setting this to 1.5 or higher.
* *
* @property {Boolean} [mouseNavEnabled=true] * @property {Boolean} [mouseNavEnabled=true]
@ -478,6 +501,10 @@
* Note: {@link OpenSeadragon.Options.showNavigationControl} is overriding * Note: {@link OpenSeadragon.Options.showNavigationControl} is overriding
* this setting when set to false. * this setting when set to false.
* *
* @property {Boolean} [showFlipControl=false]
* If true then the flip controls will be displayed as part of the
* standard controls.
*
* @property {Boolean} [showSequenceControl=true] * @property {Boolean} [showSequenceControl=true]
* If sequenceMode is true, then provide buttons for navigating forward and * If sequenceMode is true, then provide buttons for navigating forward and
* backward through the images. * backward through the images.
@ -688,6 +715,12 @@
* @property {String} rotateright.HOVER * @property {String} rotateright.HOVER
* @property {String} rotateright.DOWN * @property {String} rotateright.DOWN
* *
* @property {Object} flip - Images for the flip button.
* @property {String} flip.REST
* @property {String} flip.GROUP
* @property {String} flip.HOVER
* @property {String} flip.DOWN
*
* @property {Object} previous - Images for the previous button. * @property {Object} previous - Images for the previous button.
* @property {String} previous.REST * @property {String} previous.REST
* @property {String} previous.GROUP * @property {String} previous.GROUP
@ -1107,6 +1140,7 @@ function OpenSeadragon( options ){
autoResize: true, autoResize: true,
preserveImageSizeOnResize: false, // requires autoResize=true preserveImageSizeOnResize: false, // requires autoResize=true
minScrollDeltaTime: 50, minScrollDeltaTime: 50,
rotationIncrement: 90,
//DEFAULT CONTROL SETTINGS //DEFAULT CONTROL SETTINGS
showSequenceControl: true, //SEQUENCE showSequenceControl: true, //SEQUENCE
@ -1120,6 +1154,7 @@ function OpenSeadragon( options ){
showHomeControl: true, //HOME showHomeControl: true, //HOME
showFullPageControl: true, //FULL showFullPageControl: true, //FULL
showRotationControl: false, //ROTATION showRotationControl: false, //ROTATION
showFlipControl: false, //FLIP
controlsFadeDelay: 2000, //ZOOM/HOME/FULL/SEQUENCE controlsFadeDelay: 2000, //ZOOM/HOME/FULL/SEQUENCE
controlsFadeLength: 1500, //ZOOM/HOME/FULL/SEQUENCE controlsFadeLength: 1500, //ZOOM/HOME/FULL/SEQUENCE
mouseNavEnabled: true, //GENERAL MOUSE INTERACTIVITY mouseNavEnabled: true, //GENERAL MOUSE INTERACTIVITY
@ -1137,14 +1172,22 @@ function OpenSeadragon( options ){
navigatorAutoResize: true, navigatorAutoResize: true,
navigatorAutoFade: true, navigatorAutoFade: true,
navigatorRotate: true, navigatorRotate: true,
navigatorBackground: '#000',
navigatorOpacity: 0.8,
navigatorBorderColor: '#555',
navigatorDisplayRegionColor: '#900',
// INITIAL ROTATION // INITIAL ROTATION
degrees: 0, degrees: 0,
// INITIAL FLIP STATE
flipped: false,
// APPEARANCE // APPEARANCE
opacity: 1, opacity: 1,
preload: false, preload: false,
compositeOperation: null, compositeOperation: null,
imageSmoothingEnabled: true,
placeholderFillStyle: null, placeholderFillStyle: null,
//REFERENCE STRIP SETTINGS //REFERENCE STRIP SETTINGS
@ -1209,6 +1252,12 @@ function OpenSeadragon( options ){
HOVER: 'rotateright_hover.png', HOVER: 'rotateright_hover.png',
DOWN: 'rotateright_pressed.png' DOWN: 'rotateright_pressed.png'
}, },
flip: { // Flip icon designed by Yaroslav Samoylov from the Noun Project and modified by Nelson Campos ncampos@criteriamarathon.com, https://thenounproject.com/term/flip/136289/
REST: 'flip_rest.png',
GROUP: 'flip_grouphover.png',
HOVER: 'flip_hover.png',
DOWN: 'flip_pressed.png'
},
previous: { previous: {
REST: 'previous_rest.png', REST: 'previous_rest.png',
GROUP: 'previous_grouphover.png', GROUP: 'previous_grouphover.png',
@ -1294,7 +1343,7 @@ function OpenSeadragon( options ){
/** /**
* Determines the position of the upper-left corner of the element. * Determines the position of the upper-left corner of the element.
* @function * @function
* @param {Element|String} element - the elemenet we want the position for. * @param {Element|String} element - the element we want the position for.
* @returns {OpenSeadragon.Point} - the position of the upper left corner of the element. * @returns {OpenSeadragon.Point} - the position of the upper left corner of the element.
*/ */
getElementPosition: function( element ) { getElementPosition: function( element ) {
@ -2037,7 +2086,7 @@ function OpenSeadragon( options ){
/** /**
* Similar to OpenSeadragon.delegate, but it does not immediately call * Similar to OpenSeadragon.delegate, but it does not immediately call
* the method on the object, returning a function which can be called * the method on the object, returning a function which can be called
* repeatedly to delegate the method. It also allows additonal arguments * repeatedly to delegate the method. It also allows additional arguments
* to be passed during construction which will be added during each * to be passed during construction which will be added during each
* invocation, and each invocation can add additional arguments as well. * invocation, and each invocation can add additional arguments as well.
* *
@ -2071,7 +2120,7 @@ function OpenSeadragon( options ){
/** /**
* Retreives the value of a url parameter from the window.location string. * Retrieves the value of a url parameter from the window.location string.
* @function * @function
* @param {String} key * @param {String} key
* @returns {String} The value of the url parameter or null if no param matches. * @returns {String} The value of the url parameter or null if no param matches.
@ -2565,7 +2614,7 @@ function OpenSeadragon( options ){
//TODO: $.console is often used inside a try/catch block which generally //TODO: $.console is often used inside a try/catch block which generally
// prevents allowings errors to occur with detection until a debugger // prevents allowings errors to occur with detection until a debugger
// is attached. Although I've been guilty of the same anti-pattern // is attached. Although I've been guilty of the same anti-pattern
// I eventually was convinced that errors should naturally propogate in // I eventually was convinced that errors should naturally propagate in
// all but the most special cases. // all but the most special cases.
/** /**
* A convenient alias for console when available, and a simple null * A convenient alias for console when available, and a simple null

View File

@ -121,7 +121,7 @@ $.extend( $.OsmTileSource.prototype, $.TileSource.prototype, /** @lends OpenSead
* *
* @function * @function
* @param {Object} data - the raw configuration * @param {Object} data - the raw configuration
* @param {String} url - the url the data was retreived from if any. * @param {String} url - the url the data was retrieved from if any.
* @return {Object} options - A dictionary of keyword arguments sufficient * @return {Object} options - A dictionary of keyword arguments sufficient
* to configure this tile sources constructor. * to configure this tile sources constructor.
*/ */

View File

@ -84,10 +84,10 @@ $.Point.prototype = {
}, },
/** /**
* Substract another Point to this point and return a new Point. * Subtract another Point to this point and return a new Point.
* @function * @function
* @param {OpenSeadragon.Point} point The point to substract vector components. * @param {OpenSeadragon.Point} point The point to subtract vector components.
* @returns {OpenSeadragon.Point} A new point representing the substraction of the * @returns {OpenSeadragon.Point} A new point representing the subtraction of the
* vector components * vector components
*/ */
minus: function( point ) { minus: function( point ) {

View File

@ -53,7 +53,7 @@ var THIS = {};
* TODO: The difficult part of this feature is figuring out how to express * TODO: The difficult part of this feature is figuring out how to express
* this functionality as a combination of the functionality already * this functionality as a combination of the functionality already
* provided by Drawer, Viewport, TileSource, and Navigator. It may * provided by Drawer, Viewport, TileSource, and Navigator. It may
* require better abstraction at those points in order to effeciently * require better abstraction at those points in order to efficiently
* reuse those paradigms. * reuse those paradigms.
*/ */
/** /**

View File

@ -57,7 +57,8 @@ var I18N = {
NextPage: "Next page", NextPage: "Next page",
PreviousPage: "Previous page", PreviousPage: "Previous page",
RotateLeft: "Rotate left", RotateLeft: "Rotate left",
RotateRight: "Rotate right" RotateRight: "Rotate right",
Flip: "Flip Horizontally"
} }
}; };

View File

@ -356,10 +356,10 @@ $.Tile.prototype = {
//clearing only the inside of the rectangle occupied //clearing only the inside of the rectangle occupied
//by the png prevents edge flikering //by the png prevents edge flikering
context.clearRect( context.clearRect(
position.x + 1, position.x,
position.y + 1, position.y,
size.x - 2, size.x,
size.y - 2 size.y
); );
} }
@ -367,16 +367,21 @@ $.Tile.prototype = {
// changes as we are rendering the image // changes as we are rendering the image
drawingHandler({context: context, tile: this, rendered: rendered}); drawingHandler({context: context, tile: this, rendered: rendered});
if (!this.sourceBounds) { // Just in case var sourceWidth, sourceHeight;
this.sourceBounds = new $.Rect(0, 0, rendered.canvas.width, rendered.canvas.height); if (this.sourceBounds) {
sourceWidth = Math.min(this.sourceBounds.width, rendered.canvas.width);
sourceHeight = Math.min(this.sourceBounds.height, rendered.canvas.height);
} else {
sourceWidth = rendered.canvas.width;
sourceHeight = rendered.canvas.height;
} }
context.drawImage( context.drawImage(
rendered.canvas, rendered.canvas,
this.sourceBounds.x, 0,
this.sourceBounds.y, 0,
this.sourceBounds.width, sourceWidth,
this.sourceBounds.height, sourceHeight,
position.x, position.x,
position.y, position.y,
size.x, size.x,

View File

@ -85,7 +85,11 @@
*/ */
$.TiledImage = function( options ) { $.TiledImage = function( options ) {
var _this = this; var _this = this;
/**
* The {@link OpenSeadragon.TileSource} that defines this TiledImage.
* @member {OpenSeadragon.TileSource} source
* @memberof OpenSeadragon.TiledImage#
*/
$.console.assert( options.tileCache, "[TiledImage] options.tileCache is required" ); $.console.assert( options.tileCache, "[TiledImage] options.tileCache is required" );
$.console.assert( options.drawer, "[TiledImage] options.drawer is required" ); $.console.assert( options.drawer, "[TiledImage] options.drawer is required" );
$.console.assert( options.viewer, "[TiledImage] options.viewer is required" ); $.console.assert( options.viewer, "[TiledImage] options.viewer is required" );
@ -968,6 +972,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
// Calculations for the interval of levels to draw // Calculations for the interval of levels to draw
// can return invalid intervals; fix that here if necessary // can return invalid intervals; fix that here if necessary
highestLevel = Math.max(highestLevel, this.source.minLevel || 0);
lowestLevel = Math.min(lowestLevel, highestLevel); lowestLevel = Math.min(lowestLevel, highestLevel);
return { return {
lowestLevel: lowestLevel, lowestLevel: lowestLevel,
@ -1886,6 +1891,10 @@ function drawTiles( tiledImage, lastDrawn ) {
degrees: tiledImage.viewport.degrees, degrees: tiledImage.viewport.degrees,
useSketch: useSketch useSketch: useSketch
}); });
} else {
if(tiledImage._drawer.viewer.viewport.flipped) {
tiledImage._drawer._flip({});
}
} }
if (tiledImage.getRotation(true) % 360 !== 0) { if (tiledImage.getRotation(true) % 360 !== 0) {
tiledImage._drawer._offsetForRotation({ tiledImage._drawer._offsetForRotation({
@ -1969,6 +1978,10 @@ function drawTiles( tiledImage, lastDrawn ) {
} }
if (tiledImage.viewport.degrees !== 0) { if (tiledImage.viewport.degrees !== 0) {
tiledImage._drawer._restoreRotationChanges(useSketch); tiledImage._drawer._restoreRotationChanges(useSketch);
} else{
if(tiledImage._drawer.viewer.viewport.flipped) {
tiledImage._drawer._flip({});
}
} }
} }

View File

@ -113,7 +113,7 @@ $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLeve
//by asynchronously fetching their configuration data. //by asynchronously fetching their configuration data.
$.EventSource.call( this ); $.EventSource.call( this );
//we allow options to override anything we dont treat as //we allow options to override anything we don't treat as
//required via idiomatic options or which is functionally //required via idiomatic options or which is functionally
//set depending on the state of the readiness of this tile //set depending on the state of the readiness of this tile
//source //source
@ -172,7 +172,7 @@ $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLeve
} }
if (this.url) { if (this.url) {
//in case the getImageInfo method is overriden and/or implies an //in case the getImageInfo method is overridden and/or implies an
//async mechanism set some safe defaults first //async mechanism set some safe defaults first
this.aspectRatio = 1; this.aspectRatio = 1;
this.dimensions = new $.Point( 10, 10 ); this.dimensions = new $.Point( 10, 10 );
@ -360,7 +360,7 @@ $.TileSource.prototype = {
if (point.x >= 1) { if (point.x >= 1) {
x = this.getNumTiles(level).x - 1; x = this.getNumTiles(level).x - 1;
} }
var EPSILON = 1e-16; var EPSILON = 1e-15;
if (point.y >= 1 / this.aspectRatio - EPSILON) { if (point.y >= 1 / this.aspectRatio - EPSILON) {
y = this.getNumTiles(level).y - 1; y = this.getNumTiles(level).y - 1;
} }
@ -392,9 +392,7 @@ $.TileSource.prototype = {
sy = Math.min( sy, dimensionsScaled.y - py ); sy = Math.min( sy, dimensionsScaled.y - py );
if (isSource) { if (isSource) {
scale = 1; return new $.Rect(0, 0, sx, sy);
px = 0;
py = 0;
} }
return new $.Rect( px * scale, py * scale, sx * scale, sy * scale ); return new $.Rect( px * scale, py * scale, sx * scale, sy * scale );

View File

@ -110,7 +110,7 @@ $.extend( $.TmsTileSource.prototype, $.TileSource.prototype, /** @lends OpenSead
* *
* @function * @function
* @param {Object} data - the raw configuration * @param {Object} data - the raw configuration
* @param {String} url - the url the data was retreived from if any. * @param {String} url - the url the data was retrieved from if any.
* @return {Object} options - A dictionary of keyword arguments sufficient * @return {Object} options - A dictionary of keyword arguments sufficient
* to configure this tile sources constructor. * to configure this tile sources constructor.
*/ */

View File

@ -69,7 +69,7 @@ $.Viewer = function( options ) {
i; i;
//backward compatibility for positional args while prefering more //backward compatibility for positional args while preferring more
//idiomatic javascript options object as the only argument //idiomatic javascript options object as the only argument
if( !$.isPlainObject( options ) ){ if( !$.isPlainObject( options ) ){
options = { options = {
@ -145,6 +145,8 @@ $.Viewer = function( options ) {
//These are originally not part options but declared as members //These are originally not part options but declared as members
//in initialize. It's still considered idiomatic to put them here //in initialize. It's still considered idiomatic to put them here
//source is here for backwards compatibility. It is not an official
//part of the API and should not be relied upon.
source: null, source: null,
/** /**
* Handles rendering of tiles in the viewer. Created for each TileSource opened. * Handles rendering of tiles in the viewer. Created for each TileSource opened.
@ -152,6 +154,11 @@ $.Viewer = function( options ) {
* @memberof OpenSeadragon.Viewer# * @memberof OpenSeadragon.Viewer#
*/ */
drawer: null, drawer: null,
/**
* Keeps track of all of the tiled images in the scene.
* @member {OpenSeadragon.Drawer} world
* @memberof OpenSeadragon.Viewer#
*/
world: null, world: null,
/** /**
* Handles coordinate-related functionality - zoom, pan, rotation, etc. Created for each TileSource opened. * Handles coordinate-related functionality - zoom, pan, rotation, etc. Created for each TileSource opened.
@ -367,6 +374,7 @@ $.Viewer = function( options ) {
maxZoomLevel: this.maxZoomLevel, maxZoomLevel: this.maxZoomLevel,
viewer: this, viewer: this,
degrees: this.degrees, degrees: this.degrees,
flipped: this.flipped,
navigatorRotate: this.navigatorRotate, navigatorRotate: this.navigatorRotate,
homeFillsViewer: this.homeFillsViewer, homeFillsViewer: this.homeFillsViewer,
margins: this.viewportMargins margins: this.viewportMargins
@ -428,6 +436,10 @@ $.Viewer = function( options ) {
prefixUrl: this.prefixUrl, prefixUrl: this.prefixUrl,
viewer: this, viewer: this,
navigatorRotate: this.navigatorRotate, navigatorRotate: this.navigatorRotate,
background: this.navigatorBackground,
opacity: this.navigatorOpacity,
borderColor: this.navigatorBorderColor,
displayRegionColor: this.navigatorDisplayRegionColor,
crossOriginPolicy: this.crossOriginPolicy crossOriginPolicy: this.crossOriginPolicy
}); });
} }
@ -454,6 +466,12 @@ $.Viewer = function( options ) {
$.requestAnimationFrame( function(){ $.requestAnimationFrame( function(){
beginControlsAutoHide( _this ); beginControlsAutoHide( _this );
} ); } );
// Initial canvas options
if ( this.imageSmoothingEnabled !== undefined && !this.imageSmoothingEnabled){
this.drawer.setImageSmoothingEnabled(this.imageSmoothingEnabled);
}
}; };
$.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, /** @lends OpenSeadragon.Viewer.prototype */{ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, /** @lends OpenSeadragon.Viewer.prototype */{
@ -894,7 +912,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
nodes, nodes,
i; i;
//dont bother modifying the DOM if we are already in full page mode. //don't bother modifying the DOM if we are already in full page mode.
if ( fullPage == this.isFullPage() ) { if ( fullPage == this.isFullPage() ) {
return this; return this;
} }
@ -949,7 +967,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
bodyStyle.height = "100%"; bodyStyle.height = "100%";
docStyle.height = "100%"; docStyle.height = "100%";
//when entering full screen on the ipad it wasnt sufficient to leave //when entering full screen on the ipad it wasn't sufficient to leave
//the body intact as only only the top half of the screen would //the body intact as only only the top half of the screen would
//respond to touch events on the canvas, while the bottom half treated //respond to touch events on the canvas, while the bottom half treated
//them as touch events on the document body. Thus we remove and store //them as touch events on the document body. Thus we remove and store
@ -1156,7 +1174,10 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
} }
} }
if ( _this.navigator && _this.viewport ) { if ( _this.navigator && _this.viewport ) {
//09/08/2018 - Fabroh : Fix issue #1504 : Ensure to get the navigator updated on fullscreen out with custom location with a timeout
setTimeout(function(){
_this.navigator.update( _this.viewport ); _this.navigator.update( _this.viewport );
});
} }
/** /**
* Raised when the viewer has changed to/from full-screen mode (see {@link OpenSeadragon.Viewer#setFullScreen}). * Raised when the viewer has changed to/from full-screen mode (see {@link OpenSeadragon.Viewer#setFullScreen}).
@ -1674,6 +1695,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
onFullScreenHandler = $.delegate( this, onFullScreen ), onFullScreenHandler = $.delegate( this, onFullScreen ),
onRotateLeftHandler = $.delegate( this, onRotateLeft ), onRotateLeftHandler = $.delegate( this, onRotateLeft ),
onRotateRightHandler = $.delegate( this, onRotateRight ), onRotateRightHandler = $.delegate( this, onRotateRight ),
onFlipHandler = $.delegate( this, onFlip),
onFocusHandler = $.delegate( this, onFocus ), onFocusHandler = $.delegate( this, onFocus ),
onBlurHandler = $.delegate( this, onBlur ), onBlurHandler = $.delegate( this, onBlur ),
navImages = this.navImages, navImages = this.navImages,
@ -1685,7 +1707,8 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
if( this.zoomInButton || this.zoomOutButton || if( this.zoomInButton || this.zoomOutButton ||
this.homeButton || this.fullPageButton || this.homeButton || this.fullPageButton ||
this.rotateLeftButton || this.rotateRightButton ) { this.rotateLeftButton || this.rotateRightButton ||
this.flipButton ) {
//if we are binding to custom buttons then layout and //if we are binding to custom buttons then layout and
//grouping is the responsibility of the page author //grouping is the responsibility of the page author
useGroup = false; useGroup = false;
@ -1789,7 +1812,22 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
onFocus: onFocusHandler, onFocus: onFocusHandler,
onBlur: onBlurHandler onBlur: onBlurHandler
})); }));
}
if ( this.showFlipControl ) {
buttons.push( this.flipButton = new $.Button({
element: this.flipButton ? $.getElement( this.flipButton ) : null,
clickTimeThreshold: this.clickTimeThreshold,
clickDistThreshold: this.clickDistThreshold,
tooltip: $.getString( "Tooltips.Flip" ),
srcRest: resolveUrl( this.prefixUrl, navImages.flip.REST ),
srcGroup: resolveUrl( this.prefixUrl, navImages.flip.GROUP ),
srcHover: resolveUrl( this.prefixUrl, navImages.flip.HOVER ),
srcDown: resolveUrl( this.prefixUrl, navImages.flip.DOWN ),
onRelease: onFlipHandler,
onFocus: onFocusHandler,
onBlur: onBlurHandler
}));
} }
if ( useGroup ) { if ( useGroup ) {
@ -1868,11 +1906,11 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
* is closed which include when changing page. * is closed which include when changing page.
* @method * @method
* @param {Element|String|Object} element - A reference to an element or an id for * @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 overlaid. Or an Object specifying the configuration for the overlay.
* If using an object, see {@link OpenSeadragon.Overlay} for a list of * If using an object, see {@link OpenSeadragon.Overlay} for a list of
* all available options. * all available options.
* @param {OpenSeadragon.Point|OpenSeadragon.Rect} location - The point or * @param {OpenSeadragon.Point|OpenSeadragon.Rect} location - The point or
* rectangle which will be overlayed. This is a viewport relative location. * rectangle which will be overlaid. This is a viewport relative location.
* @param {OpenSeadragon.Placement} placement - The position of the * @param {OpenSeadragon.Placement} placement - The position of the
* viewport which the location coordinates will be treated as relative * viewport which the location coordinates will be treated as relative
* to. * to.
@ -1931,9 +1969,9 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
* element id moving it to the new location, relative to the new placement. * element id moving it to the new location, relative to the new placement.
* @method * @method
* @param {Element|String} element - A reference to an element or an id for * @param {Element|String} element - A reference to an element or an id for
* the element which is overlayed. * the element which is overlaid.
* @param {OpenSeadragon.Point|OpenSeadragon.Rect} location - The point or * @param {OpenSeadragon.Point|OpenSeadragon.Rect} location - The point or
* rectangle which will be overlayed. This is a viewport relative location. * rectangle which will be overlaid. This is a viewport relative location.
* @param {OpenSeadragon.Placement} placement - The position of the * @param {OpenSeadragon.Placement} placement - The position of the
* viewport which the location coordinates will be treated as relative * viewport which the location coordinates will be treated as relative
* to. * to.
@ -2602,6 +2640,25 @@ function onCanvasKeyPress( event ) {
this.viewport.applyConstraints(); this.viewport.applyConstraints();
} }
return false; return false;
case 114: //r - clockwise rotation
if(this.viewport.flipped){
this.viewport.setRotation($.positiveModulo(this.viewport.degrees - this.rotationIncrement, 360));
} else{
this.viewport.setRotation($.positiveModulo(this.viewport.degrees + this.rotationIncrement, 360));
}
this.viewport.applyConstraints();
return false;
case 82: //R - counterclockwise rotation
if(this.viewport.flipped){
this.viewport.setRotation($.positiveModulo(this.viewport.degrees + this.rotationIncrement, 360));
} else{
this.viewport.setRotation($.positiveModulo(this.viewport.degrees - this.rotationIncrement, 360));
}
this.viewport.applyConstraints();
return false;
case 102: //f
this.viewport.toggleFlip();
return false;
default: default:
// console.log( 'navigator keycode %s', event.keyCode ); // console.log( 'navigator keycode %s', event.keyCode );
return true; return true;
@ -2620,6 +2677,9 @@ function onCanvasClick( event ) {
if ( !haveKeyboardFocus ) { if ( !haveKeyboardFocus ) {
this.canvas.focus(); this.canvas.focus();
} }
if(this.viewport.flipped){
event.position.x = this.viewport.getContainerSize().x - event.position.x;
}
var canvasClickEventArgs = { var canvasClickEventArgs = {
tracker: event.eventSource, tracker: event.eventSource,
@ -2739,6 +2799,9 @@ function onCanvasDrag( event ) {
if( !this.panVertical ){ if( !this.panVertical ){
event.delta.y = 0; event.delta.y = 0;
} }
if(this.viewport.flipped){
event.delta.x = -event.delta.x;
}
if( this.constrainDuringPan ){ if( this.constrainDuringPan ){
var delta = this.viewport.deltaPointsFromPixels( event.delta.negate() ); var delta = this.viewport.deltaPointsFromPixels( event.delta.negate() );
@ -3064,6 +3127,10 @@ function onCanvasScroll( event ) {
if (deltaScrollTime > this.minScrollDeltaTime) { if (deltaScrollTime > this.minScrollDeltaTime) {
this._lastScrollTime = thisScrollTime; this._lastScrollTime = thisScrollTime;
if(this.viewport.flipped){
event.position.x = this.viewport.getContainerSize().x - event.position.x;
}
if ( !event.preventDefaultAction && this.viewport ) { if ( !event.preventDefaultAction && this.viewport ) {
gestureSettings = this.gestureSettingsByDeviceType( event.pointerType ); gestureSettings = this.gestureSettingsByDeviceType( event.pointerType );
if ( gestureSettings.scrollToZoom ) { if ( gestureSettings.scrollToZoom ) {
@ -3423,38 +3490,37 @@ function onFullScreen() {
} }
} }
/**
* Note: The current rotation feature is limited to 90 degree turns.
*/
function onRotateLeft() { function onRotateLeft() {
if ( this.viewport ) { if ( this.viewport ) {
var currRotation = this.viewport.getRotation(); var currRotation = this.viewport.getRotation();
if (currRotation === 0) {
currRotation = 270; if ( this.viewport.flipped ){
} currRotation = $.positiveModulo(currRotation + this.rotationIncrement, 360);
else { } else {
currRotation -= 90; currRotation = $.positiveModulo(currRotation - this.rotationIncrement, 360);
} }
this.viewport.setRotation(currRotation); this.viewport.setRotation(currRotation);
} }
} }
/**
* Note: The current rotation feature is limited to 90 degree turns.
*/
function onRotateRight() { function onRotateRight() {
if ( this.viewport ) { if ( this.viewport ) {
var currRotation = this.viewport.getRotation(); var currRotation = this.viewport.getRotation();
if (currRotation === 270) {
currRotation = 0; if ( this.viewport.flipped ){
} currRotation = $.positiveModulo(currRotation - this.rotationIncrement, 360);
else { } else {
currRotation += 90; currRotation = $.positiveModulo(currRotation + this.rotationIncrement, 360);
} }
this.viewport.setRotation(currRotation); this.viewport.setRotation(currRotation);
} }
} }
/**
* Note: When pressed flip control button
*/
function onFlip() {
this.viewport.toggleFlip();
}
function onPrevious(){ function onPrevious(){
var previous = this._sequenceIndex - 1; var previous = this._sequenceIndex - 1;

View File

@ -57,7 +57,7 @@
*/ */
$.Viewport = function( options ) { $.Viewport = function( options ) {
//backward compatibility for positional args while prefering more //backward compatibility for positional args while preferring more
//idiomatic javascript options object as the only argument //idiomatic javascript options object as the only argument
var args = arguments; var args = arguments;
if (args.length && args[0] instanceof $.Point) { if (args.length && args[0] instanceof $.Point) {
@ -107,6 +107,7 @@ $.Viewport = function( options ) {
minZoomLevel: $.DEFAULT_SETTINGS.minZoomLevel, minZoomLevel: $.DEFAULT_SETTINGS.minZoomLevel,
maxZoomLevel: $.DEFAULT_SETTINGS.maxZoomLevel, maxZoomLevel: $.DEFAULT_SETTINGS.maxZoomLevel,
degrees: $.DEFAULT_SETTINGS.degrees, degrees: $.DEFAULT_SETTINGS.degrees,
flipped: $.DEFAULT_SETTINGS.flipped,
homeFillsViewer: $.DEFAULT_SETTINGS.homeFillsViewer homeFillsViewer: $.DEFAULT_SETTINGS.homeFillsViewer
}, options ); }, options );
@ -880,7 +881,6 @@ $.Viewport.prototype = {
if (!this.viewer || !this.viewer.drawer.canRotate()) { if (!this.viewer || !this.viewer.drawer.canRotate()) {
return this; return this;
} }
this.degrees = $.positiveModulo(degrees, 360); this.degrees = $.positiveModulo(degrees, 360);
this._setContentBounds( this._setContentBounds(
this.viewer.world.getHomeBounds(), this.viewer.world.getHomeBounds(),
@ -1518,7 +1518,58 @@ $.Viewport.prototype = {
var scale = this._contentBoundsNoRotate.width; var scale = this._contentBoundsNoRotate.width;
var viewportToImageZoomRatio = (imageWidth / containerWidth) / scale; var viewportToImageZoomRatio = (imageWidth / containerWidth) / scale;
return imageZoom * viewportToImageZoomRatio; return imageZoom * viewportToImageZoomRatio;
},
/**
* Toggles flip state and demands a new drawing on navigator and viewer objects.
* @function
* @return {OpenSeadragon.Viewport} Chainable.
*/
toggleFlip: function() {
this.setFlip(!this.getFlip());
return this;
},
/**
* Gets flip state stored on viewport.
* @function
* @return {Boolean} Flip state.
*/
getFlip: function() {
return this.flipped;
},
/**
* Sets flip state according to the state input argument.
* @function
* @param {Boolean} state - Flip state to set.
* @return {OpenSeadragon.Viewport} Chainable.
*/
setFlip: function( state ) {
if ( this.flipped === state ) {
return this;
} }
this.flipped = state;
if(this.viewer.navigator){
this.viewer.navigator.setFlip(this.getFlip());
}
this.viewer.forceRedraw();
/**
* Raised when flip state has been changed.
*
* @event flip
* @memberof OpenSeadragon.Viewer
* @type {object}
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
* @property {Number} flipped - The flip state after this change.
* @property {?Object} userData - Arbitrary subscriber-defined object.
*/
this.viewer.raiseEvent('flip', {"flipped": state});
return this;
}
}; };
}( OpenSeadragon )); }( OpenSeadragon ));

View File

@ -114,7 +114,7 @@
* *
* @function * @function
* @param {Object} data - the raw configuration * @param {Object} data - the raw configuration
* @param {String} url - the url the data was retreived from if any. * @param {String} url - the url the data was retrieved from if any.
* @return {Object} options - A dictionary of keyword arguments sufficient * @return {Object} options - A dictionary of keyword arguments sufficient
* to configure this tile sources constructor. * to configure this tile sources constructor.
*/ */

View File

@ -0,0 +1,43 @@
<!DOCTYPE html>
<html>
<head>
<title>OpenSeadragon With Custom Navigator Location</title>
<script type="text/javascript" src='../../build/openseadragon/openseadragon.js'></script>
<script type="text/javascript" src='../lib/jquery-1.9.1.min.js'></script>
<style type="text/css">
.openseadragon1 {
width: 800px;
height: 600px;
}
.openseadragon-small
{
width: 100px;
height: 80px;
border: 1px solid black;
color: #333; /* text color for messages */
background-color: black;
}
</style>
</head>
<body>
<div>
Simple demo page to show custom location for navigator.
</div>
<div id="navigatorDiv" class="openseadragon-small"></div>
<div id="contentDiv" class="openseadragon1"></div>
<script type="text/javascript">
var viewer = OpenSeadragon({
// debugMode: true,
id: "contentDiv",
prefixUrl: "../../build/openseadragon/images/",
tileSources: "../data/testpattern.dzi",
showNavigator:true,
navigatorId: "navigatorDiv",
});
</script>
</body>
</html>

View File

@ -0,0 +1,35 @@
<!DOCTYPE html>
<html>
<head>
<title>OpenSeadragon Demo - Timeout Certain</title>
<script type="text/javascript" src='../../build/openseadragon/openseadragon.js'></script>
<script type="text/javascript" src='../lib/jquery-1.9.1.min.js'></script>
<style type="text/css">
.openseadragon1 {
width: 800px;
height: 600px;
}
</style>
</head>
<body>
<div>
<p>The ImageLoader timeout is set to 0 ms, and the tile source is remote.</p>
<p>In any web browser on any network connection, OpenSeadragon should not load properly, and the browser console log should show tile loading errors.</p>
</div>
<div id="contentDiv" class="openseadragon1"></div>
<script type="text/javascript">
var viewer = OpenSeadragon({
// debugMode: true,
id: "contentDiv",
prefixUrl: "../../build/openseadragon/images/",
tileSources: "http://wellcomelibrary.org/iiif-img/b11768265-0/a6801943-b8b4-4674-908c-7d5b27e70569/info.json",
showNavigator:true,
timeout: 0
});
</script>
</body>
</html>

View File

@ -0,0 +1,35 @@
<!DOCTYPE html>
<html>
<head>
<title>OpenSeadragon Demo - Timeout Unlikely</title>
<script type="text/javascript" src='../../build/openseadragon/openseadragon.js'></script>
<script type="text/javascript" src='../lib/jquery-1.9.1.min.js'></script>
<style type="text/css">
.openseadragon1 {
width: 800px;
height: 600px;
}
</style>
</head>
<body>
<div>
<p>The ImageLoader timeout is set to 24 hours, and the tile source is remote.</p>
<p>In any web browser on nearly any network connection, OpenSeadragon should load properly, and the browser console log should not show any tile loading errors.</p>
</div>
<div id="contentDiv" class="openseadragon1"></div>
<script type="text/javascript">
var viewer = OpenSeadragon({
// debugMode: true,
id: "contentDiv",
prefixUrl: "../../build/openseadragon/images/",
tileSources: "http://wellcomelibrary.org/iiif-img/b11768265-0/a6801943-b8b4-4674-908c-7d5b27e70569/info.json",
showNavigator:true,
timeout: 1000 * 60 * 60 * 24
});
</script>
</body>
</html>

View File

@ -107,6 +107,17 @@
maxLevel: 0, maxLevel: 0,
}); });
assertTileAtPoint(0, new OpenSeadragon.Point(1, 1239 / 4036), new OpenSeadragon.Point(0, 0)); assertTileAtPoint(0, new OpenSeadragon.Point(1, 1239 / 4036), new OpenSeadragon.Point(0, 0));
// Test for issue #1362
tileSource = new OpenSeadragon.TileSource({
width: 2000,
height: 3033,
tileWidth: 2000,
tileHeight: 3033,
tileOverlap: 0,
maxLevel: 0,
});
assertTileAtPoint(0, new OpenSeadragon.Point(1, 3033 / 2000), new OpenSeadragon.Point(0, 0));
}); });
}()); }());

View File

@ -118,6 +118,7 @@
assert, assert,
actual, actual,
expected, expected,
1e-15,
"Correctly converted coordinates " + orig "Correctly converted coordinates " + orig
); );
} else { } else {
@ -136,8 +137,8 @@
viewer.open(DZI_PATH); viewer.open(DZI_PATH);
}; };
function assertPointsEquals(assert, actual, expected, message) { function assertPointsEquals(assert, actual, expected, variance, message) {
Util.assertPointsEquals(assert, actual, expected, 1e-15, message); Util.assertPointsEquals(assert, actual, expected, variance, message);
} }
// Tests start here. // Tests start here.
@ -470,6 +471,32 @@
bounds, bounds,
EPSILON, EPSILON,
"Viewport.applyConstraints should move viewport."); "Viewport.applyConstraints should move viewport.");
done();
};
viewer.addHandler('open', openHandler);
viewer.open(DZI_PATH);
});
QUnit.test('applyConstraints flipped', function(assert) {
var done = assert.async();
var openHandler = function() {
viewer.removeHandler('open', openHandler);
var viewport = viewer.viewport;
viewport.setFlip(true);
viewport.fitBounds(new OpenSeadragon.Rect(1, 1, 1, 1), true);
viewport.visibilityRatio = 0.3;
viewport.applyConstraints(true);
var bounds = viewport.getBounds();
Util.assertRectangleEquals(
assert,
new OpenSeadragon.Rect(0.7, 0.7, 1, 1),
bounds,
EPSILON,
"Viewport.applyConstraints should move flipped viewport.");
done(); done();
}; };
viewer.addHandler('open', openHandler); viewer.addHandler('open', openHandler);
@ -514,6 +541,32 @@
new OpenSeadragon.Rect(1, 0, Math.sqrt(2), Math.sqrt(2), 45), new OpenSeadragon.Rect(1, 0, Math.sqrt(2), Math.sqrt(2), 45),
EPSILON, EPSILON,
"Viewport.applyConstraints with rotation should move viewport."); "Viewport.applyConstraints with rotation should move viewport.");
done();
};
viewer.addHandler('open', openHandler);
viewer.open(DZI_PATH);
});
QUnit.test('applyConstraints flipped with rotation', function(assert) {
var done = assert.async();
var openHandler = function() {
viewer.removeHandler('open', openHandler);
var viewport = viewer.viewport;
viewport.setFlip(true);
viewport.setRotation(45);
viewport.fitBounds(new OpenSeadragon.Rect(1, 1, 1, 1), true);
viewport.applyConstraints(true);
var bounds = viewport.getBounds();
Util.assertRectangleEquals(
assert,
bounds,
new OpenSeadragon.Rect(1, 0, Math.sqrt(2), Math.sqrt(2), 45),
EPSILON,
"Viewport.applyConstraints flipped and with rotation should move viewport.");
done(); done();
}; };
viewer.addHandler('open', openHandler); viewer.addHandler('open', openHandler);
@ -742,6 +795,30 @@
viewer.open(DZI_PATH); viewer.open(DZI_PATH);
}); });
QUnit.test('panBy flipped', function(assert) {
var done = assert.async();
var openHandler = function(event) {
viewer.removeHandler('open', openHandler);
var viewport = viewer.viewport;
viewport.setFlip(true);
for (var i = 0; i < testPoints.length; i++){
var expected = viewport.getCenter().plus(testPoints[i]);
viewport.panBy(testPoints[i], true);
assert.propEqual(
viewport.getCenter(),
expected,
"Panned flipped by the correct amount."
);
}
done();
};
viewer.addHandler('open', openHandler);
viewer.open(DZI_PATH);
});
QUnit.test('panTo', function(assert) { QUnit.test('panTo', function(assert) {
var done = assert.async(); var done = assert.async();
var openHandler = function(event) { var openHandler = function(event) {
@ -763,6 +840,29 @@
viewer.open(DZI_PATH); viewer.open(DZI_PATH);
}); });
QUnit.test('panTo flipped', function(assert) {
var done = assert.async();
var openHandler = function(event) {
viewer.removeHandler('open', openHandler);
var viewport = viewer.viewport;
viewport.setFlip(true);
for (var i = 0; i < testPoints.length; i++){
viewport.panTo(testPoints[i], true);
assert.propEqual(
viewport.getCenter(),
testPoints[i],
"Panned flipped to the correct location."
);
}
done();
};
viewer.addHandler('open', openHandler);
viewer.open(DZI_PATH);
});
QUnit.test('zoomBy no ref point', function(assert) { QUnit.test('zoomBy no ref point', function(assert) {
var done = assert.async(); var done = assert.async();
var openHandler = function(event) { var openHandler = function(event) {
@ -821,6 +921,45 @@
viewer.open(DZI_PATH); viewer.open(DZI_PATH);
}); });
QUnit.test('zoomBy flipped with ref point', function(assert) {
var done = assert.async();
var openHandler = function(event) {
viewer.removeHandler('open', openHandler);
var viewport = viewer.viewport;
viewport.setFlip(true);
var expectedFlippedCenters = [
new OpenSeadragon.Point(5, 5),
new OpenSeadragon.Point(6.996, 6.996),
new OpenSeadragon.Point(7.246, 6.996),
new OpenSeadragon.Point(7.246, 6.996),
new OpenSeadragon.Point(7.621, 7.371),
new OpenSeadragon.Point(7.621, 7.371),
];
for (var i = 0; i < testZoomLevels.length; i++) {
viewport.zoomBy(testZoomLevels[i], testPoints[i], true);
assert.propEqual(
testZoomLevels[i],
viewport.getZoom(),
"Zoomed flipped by the correct amount."
);
assertPointsEquals(
assert,
expectedFlippedCenters[i],
viewport.getCenter(),
1e-6,
"Panned flipped to the correct location."
);
}
done();
};
viewer.addHandler('open', openHandler);
viewer.open(DZI_PATH);
});
QUnit.test('zoomTo no ref point', function(assert) { QUnit.test('zoomTo no ref point', function(assert) {
var done = assert.async(); var done = assert.async();
var openHandler = function(event) { var openHandler = function(event) {
@ -866,8 +1005,8 @@
); );
assertPointsEquals( assertPointsEquals(
assert, assert,
viewport.getCenter(),
expectedCenters[i], expectedCenters[i],
viewport.getCenter(),
1e-14, 1e-14,
"Panned to the correct location." "Panned to the correct location."
); );
@ -879,6 +1018,45 @@
viewer.open(DZI_PATH); viewer.open(DZI_PATH);
}); });
QUnit.test('zoomTo flipped with ref point', function(assert) {
var done = assert.async();
var openHandler = function(event) {
viewer.removeHandler('open', openHandler);
var viewport = viewer.viewport;
viewport.setFlip(true);
var expectedFlippedCenters = [
new OpenSeadragon.Point(5, 5),
new OpenSeadragon.Point(4.7505, 4.7505),
new OpenSeadragon.Point(4.6005, 4.7505),
new OpenSeadragon.Point(4.8455, 4.9955),
new OpenSeadragon.Point(5.2205, 5.3705),
new OpenSeadragon.Point(5.2205, 5.3705),
];
for (var i = 0; i < testZoomLevels.length; i++) {
viewport.zoomTo(testZoomLevels[i], testPoints[i], true);
assert.propEqual(
viewport.getZoom(),
testZoomLevels[i],
"Zoomed flipped to the correct level."
);
assertPointsEquals(
assert,
expectedFlippedCenters[i],
viewport.getCenter(),
1e-14,
"Panned flipped to the correct location."
);
}
done();
};
viewer.addHandler('open', openHandler);
viewer.open(DZI_PATH);
});
QUnit.test('rotation', function(assert){ QUnit.test('rotation', function(assert){
var done = assert.async(); var done = assert.async();
var openHandler = function(event) { var openHandler = function(event) {
@ -890,6 +1068,28 @@
assert.propEqual(viewport.getRotation, 90, "Rotation should be 90 degrees"); assert.propEqual(viewport.getRotation, 90, "Rotation should be 90 degrees");
viewport.setRotation(-75); viewport.setRotation(-75);
assert.propEqual(viewport.getRotation, -75, "Rotation should be -75 degrees"); assert.propEqual(viewport.getRotation, -75, "Rotation should be -75 degrees");
done();
};
viewer.addHandler('open', openHandler);
viewer.open(DZI_PATH);
});
QUnit.test('rotation (flipped)', function(assert){
var done = assert.async();
var openHandler = function(event) {
viewer.removeHandler('open', openHandler);
var viewport = viewer.viewport;
viewport.setFlip(true);
assert.propEqual(viewport.getRotation, 0, "Original flipped rotation should be 0 degrees");
viewport.setRotation(90);
assert.propEqual(viewport.getRotation, 90, "Flipped rotation should be 90 degrees");
viewport.setRotation(-75);
assert.propEqual(viewport.getRotation, -75, "Flipped rotation should be -75 degrees");
done(); done();
}; };
@ -1140,4 +1340,44 @@
}); });
}); });
QUnit.test('toggleFlipState', function(assert) {
var done = assert.async();
var openHandler = function(event) {
viewer.removeHandler('open', openHandler);
var viewport = viewer.viewport;
assert.deepEqual(viewport.getFlip(), false, "Get original flip state should be false");
viewport.toggleFlip();
assert.deepEqual(viewport.getFlip(), true, "Toggling flip state variable, viewport should be true");
viewport.toggleFlip();
assert.deepEqual(viewport.getFlip(), false, "Toggling back flip state variable, viewport should be false again");
done();
};
viewer.addHandler('open', openHandler);
viewer.open(DZI_PATH);
});
QUnit.test('setFlipState', function(assert) {
var done = assert.async();
var openHandler = function(event) {
viewer.removeHandler('open', openHandler);
var viewport = viewer.viewport;
assert.deepEqual(viewport.getFlip(), false, "Get original flip state should be false");
viewport.setFlip(true);
assert.deepEqual(viewport.getFlip(), true, "Setting flip state variable should be true");
viewport.setFlip(false);
assert.deepEqual(viewport.getFlip(), false, "Unsetting flip state variable, viewport should be false again");
done();
};
viewer.addHandler('open', openHandler);
viewer.open(DZI_PATH);
});
})(); })();