diff --git a/src/drawer.js b/src/drawer.js index 2ce83584..c8175abd 100644 --- a/src/drawer.js +++ b/src/drawer.js @@ -548,8 +548,8 @@ $.Drawer.prototype = { context.strokeStyle = this.debugGridColor[colorIndex]; context.fillStyle = this.debugGridColor[colorIndex]; - if ( this.viewport.degrees !== 0 ) { - this._offsetForRotation({degrees: this.viewport.degrees}); + if (this.viewport.getRotation(true) % 360 !== 0 ) { + this._offsetForRotation({degrees: this.viewport.getRotation(true)}); } if (tiledImage.getRotation(true) % 360 !== 0) { this._offsetForRotation({ @@ -558,10 +558,11 @@ $.Drawer.prototype = { tiledImage._getRotationPoint(true), true) }); } - if (tiledImage.viewport.degrees === 0 && tiledImage.getRotation(true) % 360 === 0){ - if(tiledImage._drawer.viewer.viewport.getFlip()) { - tiledImage._drawer._flip(); - } + if (tiledImage.viewport.getRotation(true) % 360 === 0 && + tiledImage.getRotation(true) % 360 === 0) { + if(tiledImage._drawer.viewer.viewport.getFlip()) { + tiledImage._drawer._flip(); + } } context.strokeRect( @@ -576,7 +577,7 @@ $.Drawer.prototype = { // Rotate the text the right way around. context.translate( tileCenterX, tileCenterY ); - context.rotate( Math.PI / 180 * -this.viewport.degrees ); + context.rotate( Math.PI / 180 * -this.viewport.getRotation(true) ); context.translate( -tileCenterX, -tileCenterY ); if( tile.x === 0 && tile.y === 0 ){ @@ -622,17 +623,18 @@ $.Drawer.prototype = { (tile.position.y + 70) * $.pixelDensityRatio ); - if ( this.viewport.degrees !== 0 ) { + if (this.viewport.getRotation(true) % 360 !== 0 ) { this._restoreRotationChanges(); } if (tiledImage.getRotation(true) % 360 !== 0) { this._restoreRotationChanges(); } - if (tiledImage.viewport.degrees === 0 && tiledImage.getRotation(true) % 360 === 0){ - if(tiledImage._drawer.viewer.viewport.getFlip()) { - tiledImage._drawer._flip(); - } + if (tiledImage.viewport.getRotation(true) % 360 === 0 && + tiledImage.getRotation(true) % 360 === 0) { + if(tiledImage._drawer.viewer.viewport.getFlip()) { + tiledImage._drawer._flip(); + } } context.restore(); diff --git a/src/navigator.js b/src/navigator.js index cb46962e..e55b9c52 100644 --- a/src/navigator.js +++ b/src/navigator.js @@ -124,7 +124,7 @@ $.Navigator = function( options ){ showSequenceControl: false, immediateRender: true, blendTime: 0, - animationTime: 0, + animationTime: options.animationTime, autoResize: options.autoResize, // prevent resizing the navigator from adding unwanted space around the image minZoomImageRatio: 1.0, diff --git a/src/openseadragon.js b/src/openseadragon.js index fded2a1c..87858e29 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -332,7 +332,7 @@ * * @property {Number} [animationTime=1.2] * Specifies the animation duration per each {@link OpenSeadragon.Spring} - * which occur when the image is dragged or zoomed. + * which occur when the image is dragged, zoomed or rotated. * * @property {OpenSeadragon.GestureSettings} [gestureSettingsMouse] * Settings for gestures generated by a mouse pointer device. (See {@link OpenSeadragon.GestureSettings}) @@ -1640,8 +1640,8 @@ function OpenSeadragon( options ){ /** * Compute the modulo of a number but makes sure to always return - * a positive value. - * @param {Number} number the number to computes the modulo of + * a positive value (also known as Euclidean modulo). + * @param {Number} number the number to compute the modulo of * @param {Number} modulo the modulo * @returns {Number} the result of the modulo of number */ @@ -1653,6 +1653,7 @@ function OpenSeadragon( options ){ return result; }, + /** * Determines if a point is within the bounding rectangle of the given element (hit-test). * @function diff --git a/src/overlay.js b/src/overlay.js index f7d91c7a..fa22494d 100644 --- a/src/overlay.js +++ b/src/overlay.js @@ -299,18 +299,18 @@ this.adjust(position, size); var rotate = 0; - if (viewport.degrees && + if (viewport.getRotation(true) && 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); + var boundingBox = this._getBoundingBox(rect, viewport.getRotation(true)); position = boundingBox.getTopLeft(); size = boundingBox.getSize(); } else { - rotate = viewport.degrees; + rotate = viewport.getRotation(true); } } @@ -447,7 +447,7 @@ // private _adjustBoundsForRotation: function(viewport, bounds) { if (!viewport || - viewport.degrees === 0 || + viewport.getRotation(true) === 0 || this.rotationMode === $.OverlayRotationMode.EXACT) { return bounds; } @@ -467,7 +467,7 @@ } // NO_ROTATION case - return bounds.rotate(-viewport.degrees, + return bounds.rotate(-viewport.getRotation(true), this._getPlacementPoint(bounds)); } }; diff --git a/src/tiledimage.js b/src/tiledimage.js index 3f83c8de..8e4b14f6 100644 --- a/src/tiledimage.js +++ b/src/tiledimage.js @@ -1886,7 +1886,8 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag .getIntegerBoundingBox(); if(this._drawer.viewer.viewport.getFlip()) { - if (this.viewport.degrees !== 0 || this.getRotation(true) % 360 !== 0) { + if (this.viewport.getRotation(true) % 360 !== 0 || + this.getRotation(true) % 360 !== 0) { bounds.x = this._drawer.viewer.container.clientWidth - (bounds.x + bounds.width); } } @@ -1899,9 +1900,9 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag // When scaling, we must rotate only when blending the sketch canvas to // avoid interpolation if (!sketchScale) { - if (this.viewport.degrees !== 0) { + if (this.viewport.getRotation(true) % 360 !== 0) { this._drawer._offsetForRotation({ - degrees: this.viewport.degrees, + degrees: this.viewport.getRotation(true), useSketch: useSketch }); } @@ -1914,7 +1915,8 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag }); } - if (this.viewport.degrees === 0 && this.getRotation(true) % 360 === 0){ + if (this.viewport.getRotation(true) % 360 === 0 && + this.getRotation(true) % 360 === 0) { if(this._drawer.viewer.viewport.getFlip()) { this._drawer._flip(); } @@ -2026,16 +2028,16 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag if (this.getRotation(true) % 360 !== 0) { this._drawer._restoreRotationChanges(useSketch); } - if (this.viewport.degrees !== 0) { + if (this.viewport.getRotation(true) % 360 !== 0) { this._drawer._restoreRotationChanges(useSketch); } } if (useSketch) { if (sketchScale) { - if (this.viewport.degrees !== 0) { + if (this.viewport.getRotation(true) % 360 !== 0) { this._drawer._offsetForRotation({ - degrees: this.viewport.degrees, + degrees: this.viewport.getRotation(true), useSketch: false }); } @@ -2059,14 +2061,15 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag if (this.getRotation(true) % 360 !== 0) { this._drawer._restoreRotationChanges(false); } - if (this.viewport.degrees !== 0) { + if (this.viewport.getRotation(true) % 360 !== 0) { this._drawer._restoreRotationChanges(false); } } } if (!sketchScale) { - if (this.viewport.degrees === 0 && this.getRotation(true) % 360 === 0){ + if (this.viewport.getRotation(true) % 360 === 0 && + this.getRotation(true) % 360 === 0) { if(this._drawer.viewer.viewport.getFlip()) { this._drawer._flip(); } diff --git a/src/viewer.js b/src/viewer.js index df553e4c..ce1d6aef 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -449,7 +449,8 @@ $.Viewer = function( options ) { opacity: this.navigatorOpacity, borderColor: this.navigatorBorderColor, displayRegionColor: this.navigatorDisplayRegionColor, - crossOriginPolicy: this.crossOriginPolicy + crossOriginPolicy: this.crossOriginPolicy, + animationTime: this.animationTime, }); } @@ -2817,18 +2818,18 @@ function onCanvasKeyPress( event ) { break; case 114: //r - clockwise rotation if(this.viewport.flipped){ - this.viewport.setRotation($.positiveModulo(this.viewport.degrees - this.rotationIncrement, 360)); + this.viewport.setRotation(this.viewport.getRotation() - this.rotationIncrement); } else{ - this.viewport.setRotation($.positiveModulo(this.viewport.degrees + this.rotationIncrement, 360)); + this.viewport.setRotation(this.viewport.getRotation() + this.rotationIncrement); } this.viewport.applyConstraints(); event.preventDefault = true; break; case 82: //R - counterclockwise rotation if(this.viewport.flipped){ - this.viewport.setRotation($.positiveModulo(this.viewport.degrees + this.rotationIncrement, 360)); + this.viewport.setRotation(this.viewport.getRotation() + this.rotationIncrement); } else{ - this.viewport.setRotation($.positiveModulo(this.viewport.degrees - this.rotationIncrement, 360)); + this.viewport.setRotation(this.viewport.getRotation() - this.rotationIncrement); } this.viewport.applyConstraints(); event.preventDefault = true; @@ -3715,9 +3716,9 @@ function onRotateLeft() { var currRotation = this.viewport.getRotation(); if ( this.viewport.flipped ){ - currRotation = $.positiveModulo(currRotation + this.rotationIncrement, 360); + currRotation += this.rotationIncrement; } else { - currRotation = $.positiveModulo(currRotation - this.rotationIncrement, 360); + currRotation -= this.rotationIncrement; } this.viewport.setRotation(currRotation); } @@ -3728,9 +3729,9 @@ function onRotateRight() { var currRotation = this.viewport.getRotation(); if ( this.viewport.flipped ){ - currRotation = $.positiveModulo(currRotation - this.rotationIncrement, 360); + currRotation -= this.rotationIncrement; } else { - currRotation = $.positiveModulo(currRotation + this.rotationIncrement, 360); + currRotation += this.rotationIncrement; } this.viewport.setRotation(currRotation); } diff --git a/src/viewport.js b/src/viewport.js index d4595b87..cd578645 100644 --- a/src/viewport.js +++ b/src/viewport.js @@ -86,6 +86,9 @@ $.Viewport = function( options ) { delete options.margins; + options.initialDegrees = options.degrees; + delete options.degrees; + $.extend( true, this, { //required settings @@ -107,7 +110,7 @@ $.Viewport = function( options ) { defaultZoomLevel: $.DEFAULT_SETTINGS.defaultZoomLevel, minZoomLevel: $.DEFAULT_SETTINGS.minZoomLevel, maxZoomLevel: $.DEFAULT_SETTINGS.maxZoomLevel, - degrees: $.DEFAULT_SETTINGS.degrees, + initialDegrees: $.DEFAULT_SETTINGS.degrees, flipped: $.DEFAULT_SETTINGS.flipped, homeFillsViewer: $.DEFAULT_SETTINGS.homeFillsViewer, silenceMultiImageWarnings: $.DEFAULT_SETTINGS.silenceMultiImageWarnings @@ -133,9 +136,16 @@ $.Viewport = function( options ) { animationTime: this.animationTime }); + this.degreesSpring = new $.Spring({ + initial: options.initialDegrees, + springStiffness: this.springStiffness, + animationTime: this.animationTime + }); + this._oldCenterX = this.centerSpringX.current.value; this._oldCenterY = this.centerSpringY.current.value; this._oldZoom = this.zoomSpring.current.value; + this._oldDegrees = this.degreesSpring.current.value; this._setContentBounds(new $.Rect(0, 0, 1, 1), 1); @@ -145,6 +155,19 @@ $.Viewport = function( options ) { /** @lends OpenSeadragon.Viewport.prototype */ $.Viewport.prototype = { + + // deprecated + get degrees () { + $.console.warn('Accessing [Viewport.degrees] is deprecated. Use viewport.getRotation instead.'); + return this.getRotation(); + }, + + // deprecated + set degrees (degrees) { + $.console.warn('Setting [Viewport.degrees] is deprecated. Use viewport.setRotation instead.'); + this.setRotation(degrees); + }, + /** * Updates the viewport's home bounds and constraints for the given content size. * @function @@ -184,7 +207,7 @@ $.Viewport.prototype = { this._contentSizeNoRotate = this._contentBoundsNoRotate.getSize().times( contentFactor); - this._contentBounds = bounds.rotate(this.degrees).getBoundingBox(); + this._contentBounds = bounds.rotate(this.getRotation()).getBoundingBox(); this._contentSize = this._contentBounds.getSize().times(contentFactor); this._contentAspectRatio = this._contentSize.x / this._contentSize.y; @@ -367,7 +390,7 @@ $.Viewport.prototype = { * @returns {OpenSeadragon.Rect} The location you are zoomed/panned to, in viewport coordinates. */ getBounds: function(current) { - return this.getBoundsNoRotate(current).rotate(-this.getRotation()); + return this.getBoundsNoRotate(current).rotate(-this.getRotation(current)); }, /** @@ -399,7 +422,7 @@ $.Viewport.prototype = { */ getBoundsWithMargins: function(current) { return this.getBoundsNoRotateWithMargins(current).rotate( - -this.getRotation(), this.getCenter(current)); + -this.getRotation(current), this.getCenter(current)); }, /** @@ -611,7 +634,7 @@ $.Viewport.prototype = { bounds.y !== constrainedBounds.y || immediately) { this.fitBounds( - constrainedBounds.rotate(-this.getRotation()), + constrainedBounds.rotate(-this.getRotation(true)), immediately); } return this; @@ -904,13 +927,36 @@ $.Viewport.prototype = { * Rotates this viewport to the angle specified. * @function * @param {Number} degrees The degrees to set the rotation to. + * @param {Boolean} [immediately=false] Whether to animate to the new angle + * or rotate immediately. * @return {OpenSeadragon.Viewport} Chainable. */ - setRotation: function(degrees) { + setRotation: function(degrees, immediately) { if (!this.viewer || !this.viewer.drawer.canRotate()) { return this; } - this.degrees = $.positiveModulo(degrees, 360); + + if (this.degreesSpring.target.value === degrees && + this.degreesSpring.isAtTargetValue()) { + return this; + } + if (immediately) { + this.degreesSpring.resetTo(degrees); + } else { + var normalizedFrom = $.positiveModulo(this.degreesSpring.current.value, 360); + var normalizedTo = $.positiveModulo(degrees, 360); + var diff = normalizedTo - normalizedFrom; + if (diff > 180) { + normalizedTo -= 360; + } else if (diff < -180) { + normalizedTo += 360; + } + + var reverseDiff = normalizedFrom - normalizedTo; + this.degreesSpring.resetTo(degrees + reverseDiff); + this.degreesSpring.springTo(degrees); + } + this._setContentBounds( this.viewer.world.getHomeBounds(), this.viewer.world.getContentFactor()); @@ -933,10 +979,13 @@ $.Viewport.prototype = { /** * Gets the current rotation in degrees. * @function + * @param {Boolean} [current=false] True for current rotation, false for target. * @return {Number} The current rotation in degrees. */ - getRotation: function() { - return this.degrees; + getRotation: function(current) { + return current ? + this.degreesSpring.current.value : + this.degreesSpring.target.value; }, /** @@ -1004,13 +1053,18 @@ $.Viewport.prototype = { this.centerSpringX.update(); this.centerSpringY.update(); + this.degreesSpring.update(); + var changed = this.centerSpringX.current.value !== this._oldCenterX || this.centerSpringY.current.value !== this._oldCenterY || - this.zoomSpring.current.value !== this._oldZoom; + this.zoomSpring.current.value !== this._oldZoom || + this.degreesSpring.current.value !== this._oldDegrees; + this._oldCenterX = this.centerSpringX.current.value; this._oldCenterY = this.centerSpringY.current.value; this._oldZoom = this.zoomSpring.current.value; + this._oldDegrees = this.degreesSpring.current.value; return changed; }, @@ -1061,7 +1115,7 @@ $.Viewport.prototype = { */ deltaPixelsFromPoints: function(deltaPoints, current) { return this.deltaPixelsFromPointsNoRotate( - deltaPoints.rotate(this.getRotation()), + deltaPoints.rotate(this.getRotation(current)), current); }, @@ -1090,7 +1144,7 @@ $.Viewport.prototype = { */ deltaPointsFromPixels: function(deltaPixels, current) { return this.deltaPointsFromPixelsNoRotate(deltaPixels, current) - .rotate(-this.getRotation()); + .rotate(-this.getRotation(current)); }, /** @@ -1132,7 +1186,7 @@ $.Viewport.prototype = { // private _pixelFromPoint: function(point, bounds) { return this._pixelFromPointNoRotate( - point.rotate(this.getRotation(), this.getCenter(true)), + point.rotate(this.getRotation(true), this.getCenter(true)), bounds); }, @@ -1165,8 +1219,8 @@ $.Viewport.prototype = { */ pointFromPixel: function(pixel, current) { return this.pointFromPixelNoRotate(pixel, current).rotate( - -this.getRotation(), - this.getCenter(true) + -this.getRotation(current), + this.getCenter(current) ); }, diff --git a/test/modules/controls.js b/test/modules/controls.js index 0e6bdf7c..7e774d9b 100644 --- a/test/modules/controls.js +++ b/test/modules/controls.js @@ -230,11 +230,11 @@ // Now simulate the left/right button clicks. // TODO: re-factor simulateViewerClickWithDrag so it'll accept any element, and use that. - assert.equal(viewer.viewport.degrees, 0, "Image should start at 0 degrees rotation"); + assert.equal(viewer.viewport.getRotation(), 0, "Image should start at 0 degrees rotation"); viewer.rotateLeftButton.onRelease(); - assert.equal(viewer.viewport.degrees, 270, "Image should be 270 degrees rotation (left)"); + assert.equal(viewer.viewport.getRotation(), -90, "Image should be -90 degrees rotation (left)"); viewer.rotateRightButton.onRelease(); - assert.equal(viewer.viewport.degrees, 0, "Image should be 270 degrees rotation (right)"); + assert.equal(viewer.viewport.getRotation(), 0, "Image should be 0 degrees rotation (right)"); viewer.close(); done(); diff --git a/test/modules/drawer.js b/test/modules/drawer.js index ba53ba2d..4fd1a29f 100644 --- a/test/modules/drawer.js +++ b/test/modules/drawer.js @@ -45,7 +45,7 @@ }); viewer.addHandler('open', function handler(event) { - viewer.viewport.setRotation(30); + viewer.viewport.setRotation(30, true); Util.spyOnce(viewer.drawer.context, 'rotate', function() { assert.ok(true, 'drawing with new rotation'); done(); diff --git a/test/modules/units.js b/test/modules/units.js index 6d0bc74d..46e27ec2 100644 --- a/test/modules/units.js +++ b/test/modules/units.js @@ -210,13 +210,13 @@ checkPoint(assert, ' after zoom and pan'); //Restore rotation - viewer.viewport.setRotation(0); + viewer.viewport.setRotation(0, true); done(); }); viewer.viewport.zoomTo(0.8).panTo(new OpenSeadragon.Point(0.1, 0.2)); }); - viewer.viewport.setRotation(45); + viewer.viewport.setRotation(45, true); viewer.open([{ tileSource: "/test/data/testpattern.dzi" }, { diff --git a/test/modules/viewport.js b/test/modules/viewport.js index d820d5d6..59597060 100644 --- a/test/modules/viewport.js +++ b/test/modules/viewport.js @@ -245,7 +245,7 @@ function openHandler() { viewer.removeHandler('open', openHandler); var viewport = viewer.viewport; - viewport.setRotation(-675); + viewport.setRotation(-675, true); Util.assertRectangleEquals( assert, viewport.getHomeBoundsNoRotate(), @@ -267,7 +267,7 @@ function openHandler() { viewer.removeHandler('open', openHandler); var viewport = viewer.viewport; - viewport.setRotation(-675); + viewport.setRotation(-675, true); Util.assertRectangleEquals( assert, viewport.getHomeBounds(), @@ -531,7 +531,7 @@ var openHandler = function() { viewer.removeHandler('open', openHandler); var viewport = viewer.viewport; - viewport.setRotation(45); + viewport.setRotation(45, true); viewport.fitBounds(new OpenSeadragon.Rect(1, 1, 1, 1), true); viewport.applyConstraints(true); var bounds = viewport.getBounds(); @@ -555,7 +555,7 @@ var viewport = viewer.viewport; viewport.setFlip(true); - viewport.setRotation(45); + viewport.setRotation(45, true); viewport.fitBounds(new OpenSeadragon.Rect(1, 1, 1, 1), true); viewport.applyConstraints(true); @@ -657,7 +657,7 @@ var openHandler = function(event) { viewer.removeHandler('open', openHandler); var viewport = viewer.viewport; - viewport.setRotation(45); + viewport.setRotation(45, true); for(var i = 0; i < testRectsFitBounds.length; i++){ var rect = testRectsFitBounds[i]; @@ -1064,11 +1064,23 @@ var viewport = viewer.viewport; assert.propEqual(viewport.getRotation, 0, "Original rotation should be 0 degrees"); - viewport.setRotation(90); + viewport.setRotation(90, true); assert.propEqual(viewport.getRotation, 90, "Rotation should be 90 degrees"); - viewport.setRotation(-75); + viewport.setRotation(-75, true); assert.propEqual(viewport.getRotation, -75, "Rotation should be -75 degrees"); + viewport.setRotation(0, true); + assert.strictEqual(viewport.getRotation(true), 0, 'viewport has default current rotation'); + assert.strictEqual(viewport.getRotation(false), 0, 'viewport has default target rotation'); + + viewport.setRotation(400); + assert.strictEqual(viewport.getRotation(true), 0, 'current rotation is not changed'); + assert.strictEqual(viewport.getRotation(false), 400, 'target rotation is set correctly'); + + viewport.setRotation(200, true); + assert.strictEqual(viewport.getRotation(true), 200, 'current rotation is set correctly'); + assert.strictEqual(viewport.getRotation(false), 200, 'target rotation is set correctly'); + done(); }; @@ -1085,9 +1097,9 @@ viewport.setFlip(true); assert.propEqual(viewport.getRotation, 0, "Original flipped rotation should be 0 degrees"); - viewport.setRotation(90); + viewport.setRotation(90, true); assert.propEqual(viewport.getRotation, 90, "Flipped rotation should be 90 degrees"); - viewport.setRotation(-75); + viewport.setRotation(-75, true); assert.propEqual(viewport.getRotation, -75, "Flipped rotation should be -75 degrees"); done();