From ac18d5629d8bb51d8804bf1f67e812f372e7e94c Mon Sep 17 00:00:00 2001 From: Tom Date: Fri, 2 Dec 2022 16:43:39 -0500 Subject: [PATCH 01/11] fixed viewport constraint behavior when viewer is rotated --- src/viewer.js | 5 ++--- src/viewport.js | 57 +++++++++++++++++++++++++++---------------------- 2 files changed, 34 insertions(+), 28 deletions(-) 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 4a68849c..5d3a9f2a 100644 --- a/src/viewport.js +++ b/src/viewport.js @@ -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,36 @@ $.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 newViewportBounds = this.viewerElementToViewportRectangle(newBounds); + newViewportBounds.xConstrained = xConstrained; + newViewportBounds.yConstrained = yConstrained; + newViewportBounds.constraintApplied = xConstrained || yConstrained; + + return newViewportBounds; }, /** @@ -615,17 +626,13 @@ $.Viewport.prototype = { this.zoomTo(constrainedZoom, this.zoomPoint, immediately); } - var bounds = this.getBoundsNoRotate(); - var constrainedBounds = this._applyBoundaryConstraints(bounds); + var constrainedBounds = this.getConstrainedBounds(false); this._raiseConstraintsEvent(immediately); - 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); } + return this; }, From aede32c335027076c1ccff4db1c8c2e2ef70bc40 Mon Sep 17 00:00:00 2001 From: Tom Date: Fri, 2 Dec 2022 18:46:57 -0500 Subject: [PATCH 02/11] added documentation to getConstrainedBounds --- src/viewport.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/viewport.js b/src/viewport.js index 5d3a9f2a..2d9781fd 100644 --- a/src/viewport.js +++ b/src/viewport.js @@ -616,7 +616,7 @@ $.Viewport.prototype = { * @function * @param {Boolean} [immediately=false] * @returns {OpenSeadragon.Viewport} Chainable. - * @fires OpenSeadragon.Viewer.event:constrain + * @fires OpenSeadragon.Viewer.event:constrain if the */ applyConstraints: function(immediately) { var actualZoom = this.getZoom(); @@ -627,10 +627,10 @@ $.Viewport.prototype = { } var constrainedBounds = this.getConstrainedBounds(false); - this._raiseConstraintsEvent(immediately); if(constrainedBounds.constraintApplied){ this.fitBounds(constrainedBounds, immediately); + this._raiseConstraintsEvent(immediately); } return this; @@ -794,7 +794,9 @@ $.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 after applying constraints. The returned $.Rect contains additonal + * properties xConstrained, yConstrained, constraintsApplied indicating the status + * of the constraining operation; x and y are in the viewer element coordinate system. */ getConstrainedBounds: function(current) { var bounds, From 8744a429f14a15174f51449735a9fa65dbc9c3e8 Mon Sep 17 00:00:00 2001 From: Tom Date: Fri, 2 Dec 2022 18:48:28 -0500 Subject: [PATCH 03/11] added documentation to _raiseConstraintsEvent --- src/viewport.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/viewport.js b/src/viewport.js index 2d9781fd..659cef60 100644 --- a/src/viewport.js +++ b/src/viewport.js @@ -616,7 +616,7 @@ $.Viewport.prototype = { * @function * @param {Boolean} [immediately=false] * @returns {OpenSeadragon.Viewport} Chainable. - * @fires OpenSeadragon.Viewer.event:constrain if the + * @fires OpenSeadragon.Viewer.event:constrain if constraints were applied */ applyConstraints: function(immediately) { var actualZoom = this.getZoom(); From 78c6295acfc366a3a1bd83b675f15ba30e0a4680 Mon Sep 17 00:00:00 2001 From: Tom Date: Fri, 2 Dec 2022 22:08:15 -0500 Subject: [PATCH 04/11] update unit tests --- src/viewport.js | 5 +++-- test/modules/viewport.js | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/viewport.js b/src/viewport.js index 659cef60..02bf316f 100644 --- a/src/viewport.js +++ b/src/viewport.js @@ -577,10 +577,11 @@ $.Viewport.prototype = { } - var newViewportBounds = this.viewerElementToViewportRectangle(newBounds); + var constraintApplied = xConstrained || yConstrained; + var newViewportBounds = constraintApplied ? this.viewerElementToViewportRectangle(newBounds) : bounds.clone(); newViewportBounds.xConstrained = xConstrained; newViewportBounds.yConstrained = yConstrained; - newViewportBounds.constraintApplied = xConstrained || yConstrained; + newViewportBounds.constraintApplied = constraintApplied; return newViewportBounds; }, diff --git a/test/modules/viewport.js b/test/modules/viewport.js index 1e4c91f8..69344515 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."); From 757bf8690e8b1008991203e13aa0efa1c38cef21 Mon Sep 17 00:00:00 2001 From: Tom Date: Tue, 6 Dec 2022 16:42:13 -0500 Subject: [PATCH 05/11] fix Viewport._fixBounds to work with boundary constraints, and updated the demo page to show the behavior. --- src/viewport.js | 69 ++++---- test/demo/fitboundswithconstraints.html | 215 +++++++++++++++--------- 2 files changed, 172 insertions(+), 112 deletions(-) diff --git a/src/viewport.js b/src/viewport.js index 02bf316f..05a2b5c5 100644 --- a/src/viewport.js +++ b/src/viewport.js @@ -683,45 +683,55 @@ $.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); + var referencePoint; - return this.zoomTo(newZoom, referencePoint, immediately); + if(constraints){ + this.panTo(center, false); + this.zoomTo(newZoom, null, false); + + var constrainedBounds = this.getConstrainedBounds(); + + this.panTo(currentCenter, true); + this.zoomTo(currentZoom, null, true); + + this.fitBounds(constrainedBounds); + + // this.zoomTo(newZoom, referencePoint, immediately); + } else { + var rotatedNewBounds = newBounds.rotate(-this.getRotation()); + referencePoint = rotatedNewBounds.getTopLeft().times(newZoom) + .minus(oldBounds.getTopLeft().times(oldZoom)) + .divide(newZoom - oldZoom); + + this.zoomTo(newZoom, referencePoint, immediately); + } + return this; }, /** @@ -796,8 +806,9 @@ $.Viewport.prototype = { * Added to improve constrained panning * @param {Boolean} current - Pass true for the current location; defaults to false (target location). * @returns {OpenSeadragon.Rect} The bounds after applying constraints. The returned $.Rect contains additonal - * properties xConstrained, yConstrained, constraintsApplied indicating the status - * of the constraining operation; x and y are in the viewer element coordinate system. + * 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..85ce14bb 100644 --- a/test/demo/fitboundswithconstraints.html +++ b/test/demo/fitboundswithconstraints.html @@ -3,111 +3,160 @@ 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
    +
    -
    -
    - - - From 256514bca6da2064a53b598f095e650d68e136c9 Mon Sep 17 00:00:00 2001 From: Tom Date: Tue, 6 Dec 2022 16:52:48 -0500 Subject: [PATCH 06/11] small cleanup --- src/viewport.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/viewport.js b/src/viewport.js index 05a2b5c5..afbfbae7 100644 --- a/src/viewport.js +++ b/src/viewport.js @@ -709,8 +709,6 @@ $.Viewport.prototype = { return this; } - var referencePoint; - if(constraints){ this.panTo(center, false); this.zoomTo(newZoom, null, false); @@ -725,7 +723,7 @@ $.Viewport.prototype = { // this.zoomTo(newZoom, referencePoint, immediately); } else { var rotatedNewBounds = newBounds.rotate(-this.getRotation()); - referencePoint = rotatedNewBounds.getTopLeft().times(newZoom) + var referencePoint = rotatedNewBounds.getTopLeft().times(newZoom) .minus(oldBounds.getTopLeft().times(oldZoom)) .divide(newZoom - oldZoom); From 66b4b294243d403ac806daa5af4ce31f73b0e329 Mon Sep 17 00:00:00 2001 From: Tom Date: Tue, 6 Dec 2022 16:53:19 -0500 Subject: [PATCH 07/11] small cleanup --- src/viewport.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/viewport.js b/src/viewport.js index afbfbae7..76becbeb 100644 --- a/src/viewport.js +++ b/src/viewport.js @@ -719,8 +719,6 @@ $.Viewport.prototype = { this.zoomTo(currentZoom, null, true); this.fitBounds(constrainedBounds); - - // this.zoomTo(newZoom, referencePoint, immediately); } else { var rotatedNewBounds = newBounds.rotate(-this.getRotation()); var referencePoint = rotatedNewBounds.getTopLeft().times(newZoom) From 524b42c778c40a6621dca0aca4df52828e3b5a6d Mon Sep 17 00:00:00 2001 From: Tom Date: Tue, 6 Dec 2022 17:21:03 -0500 Subject: [PATCH 08/11] demo update --- src/viewport.js | 8 ++++---- test/demo/fitboundswithconstraints.html | 12 +++++++----- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/viewport.js b/src/viewport.js index 76becbeb..abdd6892 100644 --- a/src/viewport.js +++ b/src/viewport.js @@ -801,10 +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.Rect} The bounds after applying constraints. The returned $.Rect contains additonal - * 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). + * @returns {OpenSeadragon.Rect} The bounds in viewport coordinates after applying constraints. The returned $.Rect + * contains additonal 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 85ce14bb..d91c52e5 100644 --- a/test/demo/fitboundswithconstraints.html +++ b/test/demo/fitboundswithconstraints.html @@ -12,9 +12,10 @@ border:thin black solid; margin-right:20px; } - - #controls li { - cursor: pointer; + #buttons button{ + width:10em; + text-align:center; + margin:5px; } .layout{ display:grid; @@ -63,7 +64,7 @@ setTimeout(() => viewport.applyConstraints(), 1000);

    Click to fit overlay bounds:

    -
      +

      overlay.getBounds(viewer.viewport):

      Pick an overlay above to show the bounds
      @@ -122,7 +123,8 @@ setTimeout(() => viewport.applyConstraints(), 1000); viewer.currentOverlays.forEach(overlay=>{ var text = $(overlay.element).text(); - $('
    • ').text(text).appendTo('#controls ul').on('click',()=>{ + var div=$('
      ').appendTo('#buttons'); + var buttons=$('