diff --git a/src/viewer.js b/src/viewer.js index 748137fe..7052455a 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -3049,17 +3049,16 @@ function onCanvasDrag( event ) { this.viewport.centerSpringX.target.value += delta.x; this.viewport.centerSpringY.target.value += delta.y; - var bounds = this.viewport.getBounds(); var constrainedBounds = this.viewport.getConstrainedBounds(); this.viewport.centerSpringX.target.value -= delta.x; this.viewport.centerSpringY.target.value -= delta.y; - if (bounds.x !== constrainedBounds.x) { + if (constrainedBounds.xConstrained) { event.delta.x = 0; } - if (bounds.y !== constrainedBounds.y) { + if (constrainedBounds.yConstrained) { event.delta.y = 0; } } diff --git a/src/viewport.js b/src/viewport.js index 77d02f2c..187822a3 100644 --- a/src/viewport.js +++ b/src/viewport.js @@ -483,7 +483,7 @@ $.Viewport.prototype = { ); newZoomPixel = this._pixelFromPoint(this.zoomPoint, bounds); - deltaZoomPixels = newZoomPixel.minus( oldZoomPixel ); + deltaZoomPixels = newZoomPixel.minus( oldZoomPixel ).rotate(-this.getRotation(true)); deltaZoomPoints = deltaZoomPixels.divide( this._containerInnerSize.x * zoom ); return centerTarget.plus( deltaZoomPoints ); @@ -514,34 +514,37 @@ $.Viewport.prototype = { * @param {OpenSeadragon.Rect} bounds * @returns {OpenSeadragon.Rect} constrained bounds. */ - _applyBoundaryConstraints: function(bounds) { - var newBounds = new $.Rect( - bounds.x, - bounds.y, - bounds.width, - bounds.height); + _applyBoundaryConstraints: function(bounds) { + var newBounds = this.viewportToViewerElementRectangle(bounds).getBoundingBox(); + var cb = this.viewportToViewerElementRectangle(this._contentBoundsNoRotate).getBoundingBox(); + + var xConstrained = false; + var yConstrained = false; if (this.wrapHorizontal) { //do nothing } else { var boundsRight = newBounds.x + newBounds.width; - var contentRight = this._contentBoundsNoRotate.x + this._contentBoundsNoRotate.width; + var contentRight = cb.x + cb.width; var horizontalThreshold, leftDx, rightDx; - if (newBounds.width > this._contentBoundsNoRotate.width) { - horizontalThreshold = this.visibilityRatio * this._contentBoundsNoRotate.width; + if (newBounds.width > cb.width) { + horizontalThreshold = this.visibilityRatio * cb.width; } else { horizontalThreshold = this.visibilityRatio * newBounds.width; } - leftDx = this._contentBoundsNoRotate.x - boundsRight + horizontalThreshold; + leftDx = cb.x - boundsRight + horizontalThreshold; rightDx = contentRight - newBounds.x - horizontalThreshold; - if (horizontalThreshold > this._contentBoundsNoRotate.width) { + if (horizontalThreshold > cb.width) { newBounds.x += (leftDx + rightDx) / 2; + xConstrained = true; } else if (rightDx < 0) { newBounds.x += rightDx; + xConstrained = true; } else if (leftDx > 0) { newBounds.x += leftDx; + xConstrained = true; } } @@ -550,28 +553,37 @@ $.Viewport.prototype = { //do nothing } else { var boundsBottom = newBounds.y + newBounds.height; - var contentBottom = this._contentBoundsNoRotate.y + this._contentBoundsNoRotate.height; + var contentBottom = cb.y + cb.height; var verticalThreshold, topDy, bottomDy; - if (newBounds.height > this._contentBoundsNoRotate.height) { - verticalThreshold = this.visibilityRatio * this._contentBoundsNoRotate.height; + if (newBounds.height > cb.height) { + verticalThreshold = this.visibilityRatio * cb.height; } else{ verticalThreshold = this.visibilityRatio * newBounds.height; } - topDy = this._contentBoundsNoRotate.y - boundsBottom + verticalThreshold; + topDy = cb.y - boundsBottom + verticalThreshold; bottomDy = contentBottom - newBounds.y - verticalThreshold; - if (verticalThreshold > this._contentBoundsNoRotate.height) { + if (verticalThreshold > cb.height) { newBounds.y += (topDy + bottomDy) / 2; + yConstrained = true; } else if (bottomDy < 0) { newBounds.y += bottomDy; + yConstrained = true; } else if (topDy > 0) { newBounds.y += topDy; + yConstrained = true; } } - return newBounds; + var constraintApplied = xConstrained || yConstrained; + var newViewportBounds = constraintApplied ? this.viewerElementToViewportRectangle(newBounds) : bounds.clone(); + newViewportBounds.xConstrained = xConstrained; + newViewportBounds.yConstrained = yConstrained; + newViewportBounds.constraintApplied = constraintApplied; + + return newViewportBounds; }, /** @@ -605,7 +617,7 @@ $.Viewport.prototype = { * @function * @param {Boolean} [immediately=false] * @returns {OpenSeadragon.Viewport} Chainable. - * @fires OpenSeadragon.Viewer.event:constrain + * @fires OpenSeadragon.Viewer.event:constrain if constraints were applied */ applyConstraints: function(immediately) { var actualZoom = this.getZoom(); @@ -615,17 +627,13 @@ $.Viewport.prototype = { this.zoomTo(constrainedZoom, this.zoomPoint, immediately); } - var bounds = this.getBoundsNoRotate(); - var constrainedBounds = this._applyBoundaryConstraints(bounds); - this._raiseConstraintsEvent(immediately); + var constrainedBounds = this.getConstrainedBounds(false); - if (bounds.x !== constrainedBounds.x || - bounds.y !== constrainedBounds.y || - immediately) { - this.fitBounds( - constrainedBounds.rotate(-this.getRotation(true)), - immediately); + if(constrainedBounds.constraintApplied){ + this.fitBounds(constrainedBounds, immediately); + this._raiseConstraintsEvent(immediately); } + return this; }, @@ -675,45 +683,51 @@ $.Viewport.prototype = { newBounds.y = center.y - newBounds.height / 2; var newZoom = 1.0 / newBounds.width; - if (constraints) { - var newBoundsAspectRatio = newBounds.getAspectRatio(); - var newConstrainedZoom = this._applyZoomConstraints(newZoom); - - if (newZoom !== newConstrainedZoom) { - newZoom = newConstrainedZoom; - newBounds.width = 1.0 / newZoom; - newBounds.x = center.x - newBounds.width / 2; - newBounds.height = newBounds.width / newBoundsAspectRatio; - newBounds.y = center.y - newBounds.height / 2; - } - - newBounds = this._applyBoundaryConstraints(newBounds); - center = newBounds.getCenter(); - this._raiseConstraintsEvent(immediately); - } - if (immediately) { this.panTo(center, true); - return this.zoomTo(newZoom, null, true); + this.zoomTo(newZoom, null, true); + if(constraints){ + this.applyConstraints(true); + } + return this; } - this.panTo(this.getCenter(true), true); - this.zoomTo(this.getZoom(true), null, true); + var currentCenter = this.getCenter(true); + var currentZoom = this.getZoom(true); + this.panTo(currentCenter, true); + this.zoomTo(currentZoom, 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); + this.zoomTo(newZoom, null, true); + this.panTo(center, immediately); + if(constraints){ + this.applyConstraints(false); + } + return this; } - newBounds = newBounds.rotate(-this.getRotation()); - var referencePoint = newBounds.getTopLeft().times(newZoom) - .minus(oldBounds.getTopLeft().times(oldZoom)) - .divide(newZoom - oldZoom); + if(constraints){ + this.panTo(center, false); + this.zoomTo(newZoom, null, false); - return this.zoomTo(newZoom, referencePoint, immediately); + var constrainedBounds = this.getConstrainedBounds(); + + this.panTo(currentCenter, true); + this.zoomTo(currentZoom, null, true); + + this.fitBounds(constrainedBounds); + } else { + var rotatedNewBounds = newBounds.rotate(-this.getRotation()); + var referencePoint = rotatedNewBounds.getTopLeft().times(newZoom) + .minus(oldBounds.getTopLeft().times(oldZoom)) + .divide(newZoom - oldZoom); + + this.zoomTo(newZoom, referencePoint, immediately); + } + return this; }, /** @@ -787,7 +801,10 @@ $.Viewport.prototype = { * Returns bounds taking constraints into account * Added to improve constrained panning * @param {Boolean} current - Pass true for the current location; defaults to false (target location). - * @returns {OpenSeadragon.Viewport} Chainable. + * @returns {OpenSeadragon.Rect} The bounds in viewport coordinates after applying constraints. The returned $.Rect + * contains additional properties constraintsApplied, xConstrained and yConstrained. + * These flags indicate whether the viewport bounds were modified by the constraints + * of the viewer rectangle, and in which dimension(s). */ getConstrainedBounds: function(current) { var bounds, diff --git a/test/demo/fitboundswithconstraints.html b/test/demo/fitboundswithconstraints.html index 51889d88..617b139c 100644 --- a/test/demo/fitboundswithconstraints.html +++ b/test/demo/fitboundswithconstraints.html @@ -3,111 +3,163 @@ OpenSeadragon fitBoundsWithConstraints() Demo + -
- Simple demo page to show 'viewport.fitBounds().applyConstraints()' issue. +
+
+
+
+ Simple demo page to show viewport.fitBounds() with and without constraints. The viewer + is set up with visibilityRatio = 1 and constrainDuringPan = true to clearly demonstrate the + constraints. +
+ +

Pick a method to use:

+
+
+
viewport.fitBounds(bounds); //Ignores constraints
+
+
+
viewport.fitBoundsWithConstraints(bounds);
+
+
+
viewport.fitBoundsWithConstraints(bounds, true); //immediate
+
+
+
//Initially ignore constraints
+viewport.fitBounds(bounds);
+
+//Apply constraints after 1 second delay
+setTimeout(() => viewport.applyConstraints(), 1000);
+
+
+ +

Click to fit overlay bounds:

+
+

overlay.getBounds(viewer.viewport):

+
Pick an overlay above to show the bounds
+
-
-
- - - diff --git a/test/modules/viewport.js b/test/modules/viewport.js index e8267efc..6362a296 100644 --- a/test/modules/viewport.js +++ b/test/modules/viewport.js @@ -539,7 +539,7 @@ var bounds = viewport.getBounds(); Util.assertRectangleEquals( assert, - new OpenSeadragon.Rect(1.2071067811865466, 0.20710678118654746, Math.sqrt(2), Math.sqrt(2), 45), + new OpenSeadragon.Rect(1.0, 0.0, Math.sqrt(2), Math.sqrt(2), 45), bounds, EPSILON, "Viewport.applyConstraints with rotation should move viewport."); @@ -564,7 +564,7 @@ var bounds = viewport.getBounds(); Util.assertRectangleEquals( assert, - new OpenSeadragon.Rect(1.2071067811865466, 0.20710678118654746, Math.sqrt(2), Math.sqrt(2), 45), + new OpenSeadragon.Rect(1.0, 0.0, Math.sqrt(2), Math.sqrt(2), 45), bounds, EPSILON, "Viewport.applyConstraints flipped and with rotation should move viewport.");